On Mon, Sep 16, 2024, 1:26 AM Michael S. Tsirkin <m...@redhat.com> wrote:

> On Fri, Sep 06, 2024 at 01:57:32AM +0600, Dorjoy Chowdhury wrote:
> > Nitro Secure Module (NSM)[1] device is used in AWS Nitro Enclaves[2]
> > for stripped down TPM functionality like cryptographic attestation.
> > The requests to and responses from NSM device are CBOR[3] encoded.
> >
> > This commit adds support for NSM device in QEMU. Although related to
> > AWS Nitro Enclaves, the virito-nsm device is independent and can be
> > used in other machine types as well. The libcbor[4] library has been
> > used for the CBOR encoding and decoding functionalities.
> >
> > [1]
> https://lists.oasis-open.org/archives/virtio-comment/202310/msg00387.html
> > [2] https://docs.aws.amazon.com/enclaves/latest/user/nitro-enclave.html
> > [3] http://cbor.io/
> > [4] https://libcbor.readthedocs.io/en/latest/
> >
> > Signed-off-by: Dorjoy Chowdhury <dorjoychy...@gmail.com>
> > ---
> >  MAINTAINERS                      |   10 +
> >  hw/virtio/Kconfig                |    5 +
> >  hw/virtio/cbor-helpers.c         |  326 ++++++
> >  hw/virtio/meson.build            |    6 +
> >  hw/virtio/virtio-nsm-pci.c       |   73 ++
> >  hw/virtio/virtio-nsm.c           | 1665 ++++++++++++++++++++++++++++++
> >  include/hw/virtio/cbor-helpers.h |   46 +
> >  include/hw/virtio/virtio-nsm.h   |   59 ++
> >  meson.build                      |    2 +
> >  9 files changed, 2192 insertions(+)
> >  create mode 100644 hw/virtio/cbor-helpers.c
> >  create mode 100644 hw/virtio/virtio-nsm-pci.c
> >  create mode 100644 hw/virtio/virtio-nsm.c
> >  create mode 100644 include/hw/virtio/cbor-helpers.h
> >  create mode 100644 include/hw/virtio/virtio-nsm.h
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index c14ac014e2..b371c24747 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -2342,6 +2342,16 @@ F: include/sysemu/rng*.h
> >  F: backends/rng*.c
> >  F: tests/qtest/virtio-rng-test.c
> >
> > +virtio-nsm
> > +M: Alexander Graf <g...@amazon.com>
> > +M: Dorjoy Chowdhury <dorjoychy...@gmail.com>
> > +S: Maintained
> > +F: hw/virtio/cbor-helpers.c
> > +F: hw/virtio/virtio-nsm.c
> > +F: hw/virtio/virtio-nsm-pci.c
> > +F: include/hw/virtio/cbor-helpers.h
> > +F: include/hw/virtio/virtio-nsm.h
> > +
> >  vhost-user-stubs
> >  M: Alex Bennée <alex.ben...@linaro.org>
> >  S: Maintained
> > diff --git a/hw/virtio/Kconfig b/hw/virtio/Kconfig
> > index aa63ff7fd4..29fee32035 100644
> > --- a/hw/virtio/Kconfig
> > +++ b/hw/virtio/Kconfig
> > @@ -6,6 +6,11 @@ config VIRTIO_RNG
> >      default y
> >      depends on VIRTIO
> >
> > +config VIRTIO_NSM
> > +   bool
> > +   default y
> > +   depends on VIRTIO
> > +
> >  config VIRTIO_IOMMU
> >      bool
> >      default y
> > diff --git a/hw/virtio/cbor-helpers.c b/hw/virtio/cbor-helpers.c
> > new file mode 100644
> > index 0000000000..a0e58d6862
> > --- /dev/null
> > +++ b/hw/virtio/cbor-helpers.c
> > @@ -0,0 +1,326 @@
> > +/*
> > + * QEMU CBOR helpers
> > + *
> > + * Copyright (c) 2024 Dorjoy Chowdhury <dorjoychy...@gmail.com>
> > + *
> > + * This work is licensed under the terms of the GNU GPL, version 2 or
> > + * (at your option) any later version.  See the COPYING file in the
> > + * top-level directory.
> > + */
> > +
> > +#include "hw/virtio/cbor-helpers.h"
> > +
> > +bool qemu_cbor_map_add(cbor_item_t *map, cbor_item_t *key, cbor_item_t
> *value)
> > +{
> > +    bool success = false;
> > +    struct cbor_pair pair = (struct cbor_pair) {
> > +        .key = cbor_move(key),
> > +        .value = cbor_move(value)
> > +    };
> > +
> > +    success = cbor_map_add(map, pair);
> > +    if (!success) {
> > +        cbor_incref(pair.key);
> > +        cbor_incref(pair.value);
> > +    }
> > +
> > +    return success;
> > +}
> > +
> > +bool qemu_cbor_array_push(cbor_item_t *array, cbor_item_t *value)
> > +{
> > +    bool success = false;
> > +
> > +    success = cbor_array_push(array, cbor_move(value));
> > +    if (!success) {
> > +        cbor_incref(value);
> > +    }
> > +
> > +    return success;
> > +}
> > +
> > +bool qemu_cbor_add_bool_to_map(cbor_item_t *map, const char *key, bool
> value)
> > +{
> > +    cbor_item_t *key_cbor = NULL;
> > +    cbor_item_t *value_cbor = NULL;
> > +
> > +    key_cbor = cbor_build_string(key);
> > +    if (!key_cbor) {
> > +        goto cleanup;
> > +    }
> > +    value_cbor = cbor_build_bool(value);
> > +    if (!value_cbor) {
> > +        goto cleanup;
> > +    }
> > +    if (!qemu_cbor_map_add(map, key_cbor, value_cbor)) {
> > +        goto cleanup;
> > +    }
> > +
> > +    return true;
> > +
> > + cleanup:
> > +    if (key_cbor) {
> > +        cbor_decref(&key_cbor);
> > +    }
> > +    if (value_cbor) {
> > +        cbor_decref(&value_cbor);
> > +    }
> > +    return false;
> > +}
> > +
> > +bool qemu_cbor_add_uint8_to_map(cbor_item_t *map, const char *key,
> > +                                uint8_t value)
> > +{
> > +    cbor_item_t *key_cbor = NULL;
> > +    cbor_item_t *value_cbor = NULL;
> > +
> > +    key_cbor = cbor_build_string(key);
> > +    if (!key_cbor) {
> > +        goto cleanup;
> > +    }
> > +    value_cbor = cbor_build_uint8(value);
> > +    if (!value_cbor) {
> > +        goto cleanup;
> > +    }
> > +    if (!qemu_cbor_map_add(map, key_cbor, value_cbor)) {
> > +        goto cleanup;
> > +    }
> > +
> > +    return true;
> > +
> > + cleanup:
> > +    if (key_cbor) {
> > +        cbor_decref(&key_cbor);
> > +    }
> > +    if (value_cbor) {
> > +        cbor_decref(&value_cbor);
> > +    }
> > +    return false;
> > +}
> > +
> > +bool qemu_cbor_add_map_to_map(cbor_item_t *map, const char *key,
> > +                              size_t nested_map_size,
> > +                              cbor_item_t **nested_map)
> > +{
> > +    cbor_item_t *key_cbor = NULL;
> > +    cbor_item_t *value_cbor = NULL;
> > +
> > +    key_cbor = cbor_build_string(key);
> > +    if (!key_cbor) {
> > +        goto cleanup;
> > +    }
> > +    value_cbor = cbor_new_definite_map(nested_map_size);
> > +    if (!value_cbor) {
> > +        goto cleanup;
> > +    }
> > +    if (!qemu_cbor_map_add(map, key_cbor, value_cbor)) {
> > +        goto cleanup;
> > +    }
> > +    *nested_map = value_cbor;
> > +
> > +    return true;
> > +
> > + cleanup:
> > +    if (key_cbor) {
> > +        cbor_decref(&key_cbor);
> > +    }
> > +    if (value_cbor) {
> > +        cbor_decref(&value_cbor);
> > +    }
> > +    return false;
> > +}
> > +
> > +bool qemu_cbor_add_bytestring_to_map(cbor_item_t *map, const char *key,
> > +                                     uint8_t *arr, size_t len)
> > +{
> > +    cbor_item_t *key_cbor = NULL;
> > +    cbor_item_t *value_cbor = NULL;
> > +
> > +    key_cbor = cbor_build_string(key);
> > +    if (!key_cbor) {
> > +        goto cleanup;
> > +    }
> > +    value_cbor = cbor_build_bytestring(arr, len);
> > +    if (!value_cbor) {
> > +        goto cleanup;
> > +    }
> > +    if (!qemu_cbor_map_add(map, key_cbor, value_cbor)) {
> > +        goto cleanup;
> > +    }
> > +
> > +    return true;
> > +
> > + cleanup:
> > +    if (key_cbor) {
> > +        cbor_decref(&key_cbor);
> > +    }
> > +    if (value_cbor) {
> > +        cbor_decref(&value_cbor);
> > +    }
> > +    return false;
> > +}
> > +
> > +bool qemu_cbor_add_bytestring_or_null_to_map(cbor_item_t *map, const
> char *key,
> > +                                             uint8_t *arr, size_t len)
> > +{
> > +    cbor_item_t *key_cbor = NULL;
> > +    cbor_item_t *value_cbor = NULL;
> > +
> > +    key_cbor = cbor_build_string(key);
> > +    if (!key_cbor) {
> > +        goto cleanup;
> > +    }
> > +    if (len) {
> > +        value_cbor = cbor_build_bytestring(arr, len);
> > +    } else {
> > +        value_cbor = cbor_new_null();
> > +    }
> > +    if (!value_cbor) {
> > +        goto cleanup;
> > +    }
> > +    if (!qemu_cbor_map_add(map, key_cbor, value_cbor)) {
> > +        goto cleanup;
> > +    }
> > +
> > +    return true;
> > +
> > + cleanup:
> > +    if (key_cbor) {
> > +        cbor_decref(&key_cbor);
> > +    }
> > +    if (value_cbor) {
> > +        cbor_decref(&value_cbor);
> > +    }
> > +    return false;
> > +}
> > +
> > +bool qemu_cbor_add_string_to_map(cbor_item_t *map, const char *key,
> > +                                 const char *value)
> > +{
> > +    cbor_item_t *key_cbor = NULL;
> > +    cbor_item_t *value_cbor = NULL;
> > +
> > +    key_cbor = cbor_build_string(key);
> > +    if (!key_cbor) {
> > +        goto cleanup;
> > +    }
> > +    value_cbor = cbor_build_string(value);
> > +    if (!value_cbor) {
> > +        goto cleanup;
> > +    }
> > +    if (!qemu_cbor_map_add(map, key_cbor, value_cbor)) {
> > +        goto cleanup;
> > +    }
> > +
> > +    return true;
> > +
> > + cleanup:
> > +    if (key_cbor) {
> > +        cbor_decref(&key_cbor);
> > +    }
> > +    if (value_cbor) {
> > +        cbor_decref(&value_cbor);
> > +    }
> > +    return false;
> > +}
> > +
> > +bool qemu_cbor_add_uint8_array_to_map(cbor_item_t *map, const char *key,
> > +                                      uint8_t *arr, size_t len)
> > +{
> > +    cbor_item_t *key_cbor = NULL;
> > +    cbor_item_t *value_cbor = NULL;
> > +
> > +    key_cbor = cbor_build_string(key);
> > +    if (!key_cbor) {
> > +        goto cleanup;
> > +    }
> > +    value_cbor = cbor_new_definite_array(len);
> > +    if (!value_cbor) {
> > +        goto cleanup;
> > +    }
> > +
> > +    for (int i = 0; i < len; ++i) {
> > +        cbor_item_t *tmp = cbor_build_uint8(arr[i]);
> > +        if (!tmp) {
> > +            goto cleanup;
> > +        }
> > +        if (!qemu_cbor_array_push(value_cbor, tmp)) {
> > +            cbor_decref(&tmp);
> > +            goto cleanup;
> > +        }
> > +    }
> > +    if (!qemu_cbor_map_add(map, key_cbor, value_cbor)) {
> > +        goto cleanup;
> > +    }
> > +
> > +    return true;
> > +
> > + cleanup:
> > +    if (key_cbor) {
> > +        cbor_decref(&key_cbor);
> > +    }
> > +    if (value_cbor) {
> > +        cbor_decref(&value_cbor);
> > +    }
> > +    return false;
> > +}
> > +
> > +bool qemu_cbor_add_uint8_key_bytestring_to_map(cbor_item_t *map,
> uint8_t key,
> > +                                               uint8_t *buf, size_t len)
> > +{
> > +    cbor_item_t *key_cbor = NULL;
> > +    cbor_item_t *value_cbor = NULL;
> > +
> > +    key_cbor = cbor_build_uint8(key);
> > +    if (!key_cbor) {
> > +        goto cleanup;
> > +    }
> > +    value_cbor = cbor_build_bytestring(buf, len);
> > +    if (!value_cbor) {
> > +        goto cleanup;
> > +    }
> > +    if (!qemu_cbor_map_add(map, key_cbor, value_cbor)) {
> > +        goto cleanup;
> > +    }
> > +
> > +    return true;
> > +
> > + cleanup:
> > +    if (key_cbor) {
> > +        cbor_decref(&key_cbor);
> > +    }
> > +    if (value_cbor) {
> > +        cbor_decref(&value_cbor);
> > +    }
> > +    return false;
> > +}
> > +
> > +bool qemu_cbor_add_uint64_to_map(cbor_item_t *map, const char *key,
> > +                                 uint64_t value)
> > +{
> > +    cbor_item_t *key_cbor = NULL;
> > +    cbor_item_t *value_cbor = NULL;
> > +
> > +    key_cbor = cbor_build_string(key);
> > +    if (!key_cbor) {
> > +        goto cleanup;
> > +    }
> > +    value_cbor = cbor_build_uint64(value);
> > +    if (!value_cbor) {
> > +        goto cleanup;
> > +    }
> > +    if (!qemu_cbor_map_add(map, key_cbor, value_cbor)) {
> > +        goto cleanup;
> > +    }
> > +
> > +    return true;
> > +
> > + cleanup:
> > +    if (key_cbor) {
> > +        cbor_decref(&key_cbor);
> > +    }
> > +    if (value_cbor) {
> > +        cbor_decref(&value_cbor);
> > +    }
> > +    return false;
> > +}
> > diff --git a/hw/virtio/meson.build b/hw/virtio/meson.build
> > index 621fc65454..1fe7cb4d72 100644
> > --- a/hw/virtio/meson.build
> > +++ b/hw/virtio/meson.build
> > @@ -54,6 +54,9 @@ specific_virtio_ss.add(when: 'CONFIG_VIRTIO_PMEM',
> if_true: files('virtio-pmem.c
> >  specific_virtio_ss.add(when: 'CONFIG_VHOST_VSOCK', if_true:
> files('vhost-vsock.c'))
> >  specific_virtio_ss.add(when: 'CONFIG_VHOST_USER_VSOCK', if_true:
> files('vhost-user-vsock.c'))
> >  specific_virtio_ss.add(when: 'CONFIG_VIRTIO_RNG', if_true:
> files('virtio-rng.c'))
> > +if libcbor.found()
> > +  specific_virtio_ss.add(when: 'CONFIG_VIRTIO_NSM', if_true:
> [files('virtio-nsm.c', 'cbor-helpers.c'), libcbor])
> > +endif
> >  specific_virtio_ss.add(when: 'CONFIG_VIRTIO_MEM', if_true:
> files('virtio-mem.c'))
> >  specific_virtio_ss.add(when: 'CONFIG_VHOST_USER_SCMI', if_true:
> files('vhost-user-scmi.c'))
> >  specific_virtio_ss.add(when: ['CONFIG_VIRTIO_PCI',
> 'CONFIG_VHOST_USER_SCMI'], if_true: files('vhost-user-scmi-pci.c'))
> > @@ -70,6 +73,9 @@ virtio_pci_ss.add(when: 'CONFIG_VIRTIO_CRYPTO',
> if_true: files('virtio-crypto-pc
> >  virtio_pci_ss.add(when: 'CONFIG_VIRTIO_INPUT_HOST', if_true:
> files('virtio-input-host-pci.c'))
> >  virtio_pci_ss.add(when: 'CONFIG_VIRTIO_INPUT', if_true:
> files('virtio-input-pci.c'))
> >  virtio_pci_ss.add(when: 'CONFIG_VIRTIO_RNG', if_true:
> files('virtio-rng-pci.c'))
> > +if libcbor.found()
> > +  virtio_pci_ss.add(when: 'CONFIG_VIRTIO_NSM', if_true:
> [files('virtio-nsm-pci.c', 'cbor-helpers.c'), libcbor])
> > +endif
> >  virtio_pci_ss.add(when: 'CONFIG_VIRTIO_BALLOON', if_true:
> files('virtio-balloon-pci.c'))
> >  virtio_pci_ss.add(when: 'CONFIG_VIRTIO_9P', if_true:
> files('virtio-9p-pci.c'))
> >  virtio_pci_ss.add(when: 'CONFIG_VIRTIO_SCSI', if_true:
> files('virtio-scsi-pci.c'))
> > diff --git a/hw/virtio/virtio-nsm-pci.c b/hw/virtio/virtio-nsm-pci.c
> > new file mode 100644
> > index 0000000000..dca797315a
> > --- /dev/null
> > +++ b/hw/virtio/virtio-nsm-pci.c
> > @@ -0,0 +1,73 @@
> > +/*
> > + * AWS Nitro Secure Module (NSM) device
> > + *
> > + * Copyright (c) 2024 Dorjoy Chowdhury <dorjoychy...@gmail.com>
> > + *
> > + * This work is licensed under the terms of the GNU GPL, version 2 or
> > + * (at your option) any later version.  See the COPYING file in the
> > + * top-level directory.
> > + */
> > +
> > +#include "qemu/osdep.h"
> > +
> > +#include "hw/virtio/virtio-pci.h"
> > +#include "hw/virtio/virtio-nsm.h"
> > +#include "hw/qdev-properties.h"
> > +#include "qapi/error.h"
> > +#include "qemu/module.h"
> > +#include "qom/object.h"
> > +
> > +typedef struct VirtIONsmPCI VirtIONsmPCI;
> > +
> > +#define TYPE_VIRTIO_NSM_PCI "virtio-nsm-pci-base"
> > +DECLARE_INSTANCE_CHECKER(VirtIONsmPCI, VIRTIO_NSM_PCI,
> > +                         TYPE_VIRTIO_NSM_PCI)
> > +
> > +struct VirtIONsmPCI {
> > +    VirtIOPCIProxy parent_obj;
> > +    VirtIONSM vdev;
> > +};
> > +
> > +static void virtio_nsm_pci_realize(VirtIOPCIProxy *vpci_dev, Error
> **errp)
> > +{
> > +    VirtIONsmPCI *vnsm = VIRTIO_NSM_PCI(vpci_dev);
> > +    DeviceState *vdev = DEVICE(&vnsm->vdev);
> > +
> > +    virtio_pci_force_virtio_1(vpci_dev);
> > +
> > +    if (!qdev_realize(vdev, BUS(&vpci_dev->bus), errp)) {
> > +        return;
> > +    }
> > +}
> > +
> > +static void virtio_nsm_pci_class_init(ObjectClass *klass, void *data)
> > +{
> > +    DeviceClass *dc = DEVICE_CLASS(klass);
> > +    VirtioPCIClass *k = VIRTIO_PCI_CLASS(klass);
> > +
> > +    k->realize = virtio_nsm_pci_realize;
> > +    set_bit(DEVICE_CATEGORY_MISC, dc->categories);
> > +}
> > +
> > +static void virtio_nsm_initfn(Object *obj)
> > +{
> > +    VirtIONsmPCI *dev = VIRTIO_NSM_PCI(obj);
> > +
> > +    virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
> > +                                TYPE_VIRTIO_NSM);
> > +}
> > +
> > +static const VirtioPCIDeviceTypeInfo virtio_nsm_pci_info = {
> > +    .base_name             = TYPE_VIRTIO_NSM_PCI,
> > +    .generic_name          = "virtio-nsm-pci",
> > +    .instance_size = sizeof(VirtIONsmPCI),
> > +    .instance_init = virtio_nsm_initfn,
> > +    .class_init    = virtio_nsm_pci_class_init,
> > +};
> > +
> > +static void virtio_nsm_pci_register(void)
> > +{
> > +    virtio_pci_types_register(&virtio_nsm_pci_info);
> > +}
> > +
> > +type_init(virtio_nsm_pci_register)
> > diff --git a/hw/virtio/virtio-nsm.c b/hw/virtio/virtio-nsm.c
> > new file mode 100644
> > index 0000000000..0f4edd92ff
> > --- /dev/null
> > +++ b/hw/virtio/virtio-nsm.c
> > @@ -0,0 +1,1665 @@
> > +/*
> > + * AWS Nitro Secure Module (NSM) device
> > + *
> > + * Copyright (c) 2024 Dorjoy Chowdhury <dorjoychy...@gmail.com>
> > + *
> > + * This work is licensed under the terms of the GNU GPL, version 2 or
> > + * (at your option) any later version.  See the COPYING file in the
> > + * top-level directory.
> > + */
> > +
> > +#include "qemu/osdep.h"
> > +#include "qemu/iov.h"
> > +#include "qemu/guest-random.h"
> > +#include "qapi/error.h"
> > +
> > +#include "crypto/hash.h"
> > +#include "hw/virtio/virtio.h"
> > +#include "hw/virtio/virtio-nsm.h"
> > +#include "hw/virtio/cbor-helpers.h"
> > +#include "standard-headers/linux/virtio_ids.h"
> > +
> > +#define NSM_RESPONSE_BUF_SIZE     0x3000
> > +#define NSM_PCR_DATA_REQ_MAX_SIZE 512
> > +
> > +enum NSMResponseTypes {
> > +    NSM_SUCCESS = 0,
> > +    NSM_INVALID_ARGUMENT = 1,
> > +    NSM_INVALID_INDEX = 2,
> > +    NSM_READONLY_INDEX = 3,
> > +    NSM_INVALID_OPERATION = 4,
> > +    NSM_BUFFER_TOO_SMALL = 5,
> > +    NSM_INPUT_TOO_LARGE = 6,
> > +    NSM_INTERNAL_ERROR = 7,
> > +};
> > +
> > +static const char *error_string(enum NSMResponseTypes type)
> > +{
> > +    const char *str;
> > +    switch (type) {
> > +    case NSM_INVALID_ARGUMENT:
> > +        str = "InvalidArgument";
> > +        break;
> > +    case NSM_INVALID_INDEX:
> > +        str = "InvalidIndex";
> > +        break;
> > +    case NSM_READONLY_INDEX:
> > +        str = "ReadOnlyIndex";
> > +        break;
> > +    case NSM_INVALID_OPERATION:
> > +        str = "InvalidOperation";
> > +        break;
> > +    case NSM_BUFFER_TOO_SMALL:
> > +        str = "BufferTooSmall";
> > +        break;
> > +    case NSM_INPUT_TOO_LARGE:
> > +        str = "InputTooLarge";
> > +        break;
> > +    default:
> > +        str = "InternalError";
> > +        break;
> > +    }
> > +
> > +    return str;
> > +}
> > +
> > +/*
> > + * Error response structure:
> > + *
> > + * {
> > + *   Map(1) {
> > + *     key = String("Error"),
> > + *     value = String(error_name)
> > + *   }
> > + * }
> > + *
> > + * where error_name can be one of the following:
> > + *   InvalidArgument
> > + *   InvalidIndex
> > + *   InvalidResponse
> > + *   ReadOnlyIndex
> > + *   InvalidOperation
> > + *   BufferTooSmall
> > + *   InputTooLarge
> > + *   InternalError
> > + */
> > +
> > +static bool error_response(struct iovec *response, enum
> NSMResponseTypes error,
> > +                           Error **errp)
> > +{
> > +    cbor_item_t *root;
> > +    size_t len;
> > +    bool r = false;
> > +
> > +    root = cbor_new_definite_map(1);
> > +    if (!root) {
> > +        goto err;
> > +    }
> > +
> > +    if (!qemu_cbor_add_string_to_map(root, "Error",
> error_string(error))) {
> > +        goto err;
> > +    }
> > +
> > +    len = cbor_serialize(root, response->iov_base, response->iov_len);
>
> As far as I can tell, all these also need to be switched to use
> iov_from_buf.
>

Sorry I didn't understand this. The iovecs passed in these functions are
not the iovecs from virtqueue. We make an iovec for the response and then
pass it down. We do the "iov_from_buf" after calling
"get_nsm_request_response" in "handle_input" function. Am I missing
something?

Regards,
Dorjoy

Reply via email to