[ddk][api] Add API to get length of device metadata

This patch adds a new method (device_get_metadata_size) to the DDK API, which allows
the retrieval of the size of a given metadata key on a device.

Test: runtests -t driver-test
Change-Id: Ib2535b17e3e2b6b9bc225835d6ac27e288aadd52
diff --git a/system/core/devmgr/devhost/api.cpp b/system/core/devmgr/devhost/api.cpp
index e1475a4..dcc5ede 100644
--- a/system/core/devmgr/devhost/api.cpp
+++ b/system/core/devmgr/devhost/api.cpp
@@ -237,6 +237,13 @@
     return devhost_get_metadata(dev_ref, type, buf, buflen, actual);
 }
 
+__EXPORT zx_status_t device_get_metadata_size(zx_device_t* dev, uint32_t type,
+                                                size_t* out_size) {
+    ApiAutoLock lock;
+    auto dev_ref = fbl::WrapRefPtr(dev);
+    return devhost_get_metadata_size(dev_ref, type, out_size);
+}
+
 __EXPORT zx_status_t device_add_metadata(zx_device_t* dev, uint32_t type,
                                          const void* data, size_t length) {
     ApiAutoLock lock;
diff --git a/system/core/devmgr/devhost/devhost.cpp b/system/core/devmgr/devhost/devhost.cpp
index f7e44db..7eed1eb 100644
--- a/system/core/devmgr/devhost/devhost.cpp
+++ b/system/core/devmgr/devhost/devhost.cpp
@@ -1117,6 +1117,30 @@
     return ZX_OK;
 }
 
+zx_status_t devhost_get_metadata_size(const fbl::RefPtr<zx_device_t>& dev, uint32_t type,
+                                        size_t* out_length) {
+
+    const zx::channel& rpc = *dev->rpc;
+    if (!rpc.is_valid()) {
+        return ZX_ERR_IO_REFUSED;
+    }
+    log_rpc(dev, "get-metadata");
+    zx_status_t call_status;
+    zx_status_t status = fuchsia_device_manager_CoordinatorGetMetadataSize(
+            rpc.get(), type, &call_status, out_length);
+    if (status != ZX_OK) {
+        log(ERROR, "devhost: rpc:get-metadata sending failed: %d\n", status);
+        return status;
+    }
+    if (call_status != ZX_OK) {
+        if (call_status != ZX_ERR_NOT_FOUND) {
+            log(ERROR, "devhost: rpc:get-metadata failed: %d\n", call_status);
+        }
+        return call_status;
+    }
+    return ZX_OK;
+}
+
 zx_status_t devhost_add_metadata(const fbl::RefPtr<zx_device_t>& dev, uint32_t type,
                                  const void* data, size_t length) {
     if (!data && length) {
diff --git a/system/core/devmgr/devhost/devhost.h b/system/core/devmgr/devhost/devhost.h
index 8731f3d..6eb5c5b 100644
--- a/system/core/devmgr/devhost/devhost.h
+++ b/system/core/devmgr/devhost/devhost.h
@@ -175,6 +175,9 @@
 zx_status_t devhost_get_metadata(const fbl::RefPtr<zx_device_t>& dev, uint32_t type, void* buf,
                                  size_t buflen, size_t* actual) REQ_DM_LOCK;
 
+zx_status_t devhost_get_metadata_size(const fbl::RefPtr<zx_device_t>& dev, uint32_t type,
+                                        size_t* size) REQ_DM_LOCK;
+
 zx_status_t devhost_add_metadata(const fbl::RefPtr<zx_device_t>& dev, uint32_t type,
                                  const void* data, size_t length) REQ_DM_LOCK;
 
diff --git a/system/core/devmgr/devmgr/coordinator.cpp b/system/core/devmgr/devmgr/coordinator.cpp
index cdaad7c..c70e3d7 100644
--- a/system/core/devmgr/devmgr/coordinator.cpp
+++ b/system/core/devmgr/devmgr/coordinator.cpp
@@ -1122,6 +1122,37 @@
     return ZX_ERR_NOT_FOUND;
 }
 
+zx_status_t Coordinator::GetMetadataSize(Device* dev, uint32_t type, size_t* size) {
+    // search dev and its parent devices for a match
+    Device* test = dev;
+    while (test) {
+        for (const auto& md : test->metadata) {
+            if (md.type == type) {
+                *size = md.length;
+                return ZX_OK;
+            }
+        }
+        test = test->parent;
+    }
+
+    // if no metadata is found, check list of metadata added via device_publish_metadata()
+    char path[fuchsia_device_manager_PATH_MAX];
+    zx_status_t status = GetTopoPath(dev, path, sizeof(path));
+    if (status != ZX_OK) {
+        return status;
+    }
+
+    for (const auto& md : published_metadata_) {
+        const char* md_path = md.Data() + md.length;
+        if (md.type == type && path_is_child(md_path, path)) {
+            *size = md.length;
+            return ZX_OK;
+        }
+    }
+
+    return ZX_ERR_NOT_FOUND;
+}
+
 zx_status_t Coordinator::AddMetadata(Device* dev, uint32_t type, const void* data,
                                      uint32_t length) {
     fbl::unique_ptr<Metadata> md;
@@ -1303,6 +1334,16 @@
     return fuchsia_device_manager_CoordinatorGetMetadata_reply(txn, status, data, actual);
 }
 
+static zx_status_t fidl_GetMetadataSize(void* ctx, uint32_t key, fidl_txn_t* txn) {
+    auto dev = static_cast<Device*>(ctx);
+    size_t size;
+    zx_status_t status = dev->coordinator->GetMetadataSize(dev, key, &size);
+    if (status != ZX_OK) {
+        return fuchsia_device_manager_CoordinatorGetMetadataSize_reply(txn, status, 0);
+    }
+    return fuchsia_device_manager_CoordinatorGetMetadataSize_reply(txn, status, size);
+}
+
 static zx_status_t fidl_AddMetadata(void* ctx, uint32_t key,
                                     const uint8_t* data_data, size_t data_count, fidl_txn_t* txn) {
     static_assert(fuchsia_device_manager_METADATA_MAX <= UINT32_MAX);
@@ -1440,6 +1481,7 @@
     .GetTopologicalPath = fidl_GetTopologicalPath,
     .LoadFirmware = fidl_LoadFirmware,
     .GetMetadata = fidl_GetMetadata,
+    .GetMetadataSize = fidl_GetMetadataSize,
     .AddMetadata = fidl_AddMetadata,
     .PublishMetadata = fidl_PublishMetadata,
     .DmCommand = fidl_DmCommand,
diff --git a/system/core/devmgr/devmgr/coordinator.h b/system/core/devmgr/devmgr/coordinator.h
index 1b42a4b..239d868 100644
--- a/system/core/devmgr/devmgr/coordinator.h
+++ b/system/core/devmgr/devmgr/coordinator.h
@@ -328,6 +328,7 @@
 
     zx_status_t GetMetadata(Device* dev, uint32_t type, void* buffer, size_t buflen,
                             size_t* actual);
+    zx_status_t GetMetadataSize(Device* dev, uint32_t type, size_t* size);
     zx_status_t AddMetadata(Device* dev, uint32_t type, const void* data, uint32_t length);
     zx_status_t PublishMetadata(Device* dev, const char* path, uint32_t type, const void* data,
                                 uint32_t length);
diff --git a/system/dev/test/ddk-test/metadata-test.c b/system/dev/test/ddk-test/metadata-test.c
index 2cec219..efa65cf 100644
--- a/system/dev/test/ddk-test/metadata-test.c
+++ b/system/dev/test/ddk-test/metadata-test.c
@@ -20,9 +20,14 @@
     status = device_get_metadata(ddk_test_dev, 1, buffer,sizeof(buffer), &actual);
     ASSERT_EQ(status, ZX_ERR_NOT_FOUND, "device_get_metadata did not return ZX_ERR_NOT_FOUND");
 
+    status = device_get_metadata_size(ddk_test_dev, 1, &actual);
+    ASSERT_EQ(status, ZX_ERR_NOT_FOUND, "device_get_metadata_size should return ZX_ERR_NOT_FOUND");
+
     status = device_add_metadata(ddk_test_dev, 1, TEST_STRING, strlen(TEST_STRING) + 1);
     ASSERT_EQ(status, ZX_OK, "device_add_metadata failed");
 
+    status = device_get_metadata_size(ddk_test_dev, 1, &actual);
+    ASSERT_EQ(strlen(TEST_STRING)+1, actual, "Incorrect output length was returned.");
     status = device_get_metadata(ddk_test_dev, 1, buffer, sizeof(buffer), &actual);
     ASSERT_EQ(status, ZX_OK, "device_get_metadata failed");
     ASSERT_EQ(actual, strlen(TEST_STRING) + 1, "");
diff --git a/system/fidl/fuchsia-device-manager/coordinator.fidl b/system/fidl/fuchsia-device-manager/coordinator.fidl
index 227b0a6..d31df50 100644
--- a/system/fidl/fuchsia-device-manager/coordinator.fidl
+++ b/system/fidl/fuchsia-device-manager/coordinator.fidl
@@ -128,6 +128,10 @@
     0x10000018: GetMetadata(uint32 key)
                     -> (zx.status status, vector<uint8>:METADATA_MAX? data);
 
+    /// Retrieve the metadata size associated with this device and the given key.
+    GetMetadataSize(uint32 key)
+                    -> (zx.status status, uint64 size);
+
     /// Add metadata blob associated with this device and the given key.
     /// TODO(teisenbe): Document the behavior of calling this twice with the same
     /// key.  I believe the current behavior results in inaccessible data that is
diff --git a/system/ulib/ddk/include/ddk/device.h b/system/ulib/ddk/include/ddk/device.h
index e0115bb..0e0a9e7 100644
--- a/system/ulib/ddk/include/ddk/device.h
+++ b/system/ulib/ddk/include/ddk/device.h
@@ -264,6 +264,10 @@
 zx_status_t device_get_metadata(zx_device_t* dev, uint32_t type, void* buf, size_t buflen,
                                 size_t* actual);
 
+// retrieves metadata size for a specific device
+// searches parent devices to find a match
+zx_status_t device_get_metadata_size(zx_device_t* dev, uint32_t type, size_t* out_size);
+
 // Adds metadata to a specific device.
 zx_status_t device_add_metadata(zx_device_t* dev, uint32_t type, const void* data, size_t length);