{"id":27506,"url":"https://patchwork.libcamera.org/api/patches/27506/?format=json","web_url":"https://patchwork.libcamera.org/patch/27506/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20260724210544.8012-1-magdum.foss@gmail.com>","date":"2026-07-24T21:05:44","name":"[v2] libcamera: Serialize local control names in IPA format v3","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"ad2bf151c57ec9885d7d6b81047a8edc157e1d0e","submitter":{"id":453,"url":"https://patchwork.libcamera.org/api/people/453/?format=json","name":"Magdum","email":"magdum.foss@gmail.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/27506/mbox/","series":[{"id":6081,"url":"https://patchwork.libcamera.org/api/series/6081/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=6081","date":"2026-07-24T21:05:44","name":"[v2] libcamera: Serialize local control names in IPA format v3","version":2,"mbox":"https://patchwork.libcamera.org/series/6081/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/27506/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/27506/checks/","tags":{},"headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id E6492BE080\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 24 Jul 2026 21:05:48 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id A1E1C67F22;\n\tFri, 24 Jul 2026 23:05:48 +0200 (CEST)","from mail-wm1-x334.google.com (mail-wm1-x334.google.com\n\t[IPv6:2a00:1450:4864:20::334])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id B2F3D67F22\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 24 Jul 2026 23:05:47 +0200 (CEST)","by mail-wm1-x334.google.com with SMTP id\n\t5b1f17b1804b1-4954afac04bso9687705e9.0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 24 Jul 2026 14:05:47 -0700 (PDT)","from magdum-System-Product-Name.vodafone.ultrahub\n\t([2a02:810d:4b14:4600:4f39:30e9:8fd8:737c])\n\tby smtp.gmail.com with ESMTPSA id\n\t5b1f17b1804b1-4957b816ef8sm64803155e9.1.2026.07.24.14.05.46\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tFri, 24 Jul 2026 14:05:46 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=gmail.com header.i=@gmail.com\n\theader.b=\"IWP+VZ6c\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=gmail.com; s=20251104; t=1784927147; x=1785531947;\n\tdarn=lists.libcamera.org; \n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:from:to:cc:subject:date\n\t:message-id:reply-to:content-type;\n\tbh=1pm0zdejAZbdW766O665QqntJdFakEns0w0WWONWTzQ=;\n\tb=IWP+VZ6czoxEIYLMr5bM5+/i46ZWl1rQDMn1bDCAOtGIsSo5n8ePCj2ZTsWwyCVs7p\n\tX6VVh3PqerQA/FAEhbxJ0TSwUpvSnExWQhlGbfqtTQM16d88qhW0/LGz+48B0/Fups5y\n\tZdN5LPetrREsCgr9Kl5vqyjEz0KzuCXHUuq5MXoIo7QC/meQ6IU+/htzK5iB+XLlfObX\n\t8pbbKFGsCXf6zXYX8PlI7VACu7rpqgTu3cOUEUu1vJEKaXUGDtCHGPj9C1gc4Fzvpr3f\n\t0qAsvtamKrg3lGB55/KmwNarRkW8ZaPzGLqnmlwK+qTsPH2AO55UTCg4fsozztoKHdjl\n\tsXKw==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20251104; t=1784927147; x=1785531947;\n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from\n\t:to:cc:subject:date:message-id:reply-to:content-type;\n\tbh=1pm0zdejAZbdW766O665QqntJdFakEns0w0WWONWTzQ=;\n\tb=qB4PB0wE0UYjAF3MvrnYTgI6K15QCqPfs7iwfPnr5FguQHwahLX9wYm51utSZcYLzy\n\t/sNnwNmR1KLbesOFcgbvyLv4oPRAtM7nWlvJ55Vm7icfxAIavhHDLLsqE2AUPMKXfh/G\n\tX8I6CBnprvjrCP4xMkZzq7VQiThw5kcyt5w/WmEc/aoWw3xNuvaeMvcqAOyFZfFANDIJ\n\t4gfFsoiXS5NOXl6f0G4XOuuQn6XR5BI6YtJNptqxHb5FrXrmZATyJxQnP8sxIlb3Noj2\n\tf9bgGbbnEOJ1Ud9PbUgJSEJwz6i7t78dEFf2rBO3OgAb3KXTzQXKuT2ppb6HteIrIqCa\n\tfQkw==","X-Gm-Message-State":"AOJu0Ywx38v+AOLwOY3lA2XS1PlXe6yVH634sHane+sw2xaL6DwT0ODX\n\tjHBG5dzGBkdNDdm1o/78MOAE/lM6RsBLHdKl3QvRLui0WAPwBIDOlaX8PyTxQ02Y3mM=","X-Gm-Gg":"AR+sD131K+LcZ+pJDB325hLqfHG2BO4V00KLgLof8rclmrVJl6dM6Q9FMs7tF4Q5s/a\n\trvuPAj2PEAk0LryfpJgAjr9QU1gXmEgn1WEWUZ6p+cQS8q6wf/tgLKww1wI8IYNe2qsGeP545Ln\n\tAm0eewo+zG0mbh3jfrXL7uas1+UBrDjnB5AWv4N4utVErMnThCMQ29zrx1sN7s0GKOy3wazDL5V\n\tiPARLUwiALJNdwmtoEwQhhwJ2awLscbRa9hFDJOXd9BTwsnq+i6wpps1hQEWfaa6/4RyQoxbE3j\n\tOlDUKXYvJKIierxZZm63g5JayrpoNJLX+SC5s88E8zlbu3tLdtpSEf9Ux7gNPA1+EZ03knrP747\n\tgfFDUDZWQShYvSVOpi4YbjqXj9IW3dOyyCf4EAuqsnbuexwkOUnFWSNbkIeLBdX63SawxZ5DRkX\n\tWJZ9RbO6/JBR1jXY8vmZ/wvUiCuvnfMOYuJ6WjhkqCPSwQed73MqJI","X-Received":"by 2002:a05:600c:1387:b0:495:689a:b3ff with SMTP id\n\t5b1f17b1804b1-496b56b212fmr122625e9.8.1784927147059; \n\tFri, 24 Jul 2026 14:05:47 -0700 (PDT)","From":"Magdum <magdum.foss@gmail.com>","To":"libcamera-devel@lists.libcamera.org","Cc":"Magdum <magdum.foss@gmail.com>","Subject":"[PATCH v2] libcamera: Serialize local control names in IPA format v3","Date":"Fri, 24 Jul 2026 23:05:44 +0200","Message-ID":"<20260724210544.8012-1-magdum.foss@gmail.com>","X-Mailer":"git-send-email 2.43.0","In-Reply-To":"<20260723174644.6580-2-magdum.foss@gmail.com>","References":"<20260723174644.6580-2-magdum.foss@gmail.com>","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"},"content":"Bump IPA controls serialization format to v3 and add name_len to\nControlInfoMap entries.\n\nSerialize ontrol names for local (V4L2-style) id maps to preserve\nnames across IPC.\n\nExtend serializer tests with V4L2-like name round-trip coverage and\nmalformed payload rejection for oversized length, missing terminator,\nand too-long serialize input.\n\nSigned-off-by: Magdum <magdum.foss@gmail.com>\n---\n include/libcamera/ipa/ipa_controls.h         |   8 +-\n src/libcamera/control_serializer.cpp         | 141 ++++++++++++++-----\n src/libcamera/ipa_controls.cpp               |  10 +-\n test/serialization/control_serialization.cpp | 109 ++++++++++++++\n 4 files changed, 224 insertions(+), 44 deletions(-)","diff":"diff --git a/include/libcamera/ipa/ipa_controls.h b/include/libcamera/ipa/ipa_controls.h\nindex 6af962ff3..cf4bbe3cf 100644\n--- a/include/libcamera/ipa/ipa_controls.h\n+++ b/include/libcamera/ipa/ipa_controls.h\n@@ -15,7 +15,7 @@ namespace libcamera {\n extern \"C\" {\n #endif\n \n-#define IPA_CONTROLS_FORMAT_VERSION\t2\n+#define IPA_CONTROLS_FORMAT_VERSION 3\n \n enum ipa_controls_id_map_type {\n \tIPA_CONTROL_ID_MAP_CONTROLS,\n@@ -50,7 +50,11 @@ struct ipa_control_info_entry {\n \tuint32_t id;\n \tuint32_t type;\n \tuint8_t direction;\n-\tuint8_t padding[7];\n+\tuint8_t padding[3];\n+\t/* Length of the control name. */\n+\tuint32_t name_len;\n+\t/* Offset of the control name in the values section. */\n+\tuint32_t name_offset;\n \tstruct ipa_control_value_entry min;\n \tstruct ipa_control_value_entry max;\n \tstruct ipa_control_value_entry def;\ndiff --git a/src/libcamera/control_serializer.cpp b/src/libcamera/control_serializer.cpp\nindex c0285cc6c..a420d4700 100644\n--- a/src/libcamera/control_serializer.cpp\n+++ b/src/libcamera/control_serializer.cpp\n@@ -31,6 +31,33 @@ namespace libcamera {\n \n LOG_DEFINE_CATEGORY(Serializer)\n \n+namespace {\n+\n+constexpr uint32_t kMaxControlNameLength = 1024;\n+\n+enum ipa_controls_id_map_type idMapTypeFor(const ControlIdMap &idmap)\n+{\n+\tif (&idmap == &controls::controls)\n+\t\treturn IPA_CONTROL_ID_MAP_CONTROLS;\n+\tif (&idmap == &properties::properties)\n+\t\treturn IPA_CONTROL_ID_MAP_PROPERTIES;\n+\n+\treturn IPA_CONTROL_ID_MAP_V4L2;\n+}\n+\n+bool idMapRequiresLocalIds(enum ipa_controls_id_map_type idMapType)\n+{\n+\treturn idMapType == IPA_CONTROL_ID_MAP_V4L2;\n+}\n+\n+size_t serializedControlNameSize(const ControlId *id,\n+\t\tenum ipa_controls_id_map_type idMapType)\n+{\n+\treturn idMapRequiresLocalIds(idMapType) ? id->name().size() + 1 : 1;\n+}\n+\n+} /* namespace */\n+\n /**\n  * \\class ControlSerializer\n  * \\brief Serializer and deserializer for control-related classes\n@@ -165,10 +192,14 @@ size_t ControlSerializer::binarySize(const ControlInfoMap &infoMap)\n {\n \tsize_t size = sizeof(struct ipa_controls_header)\n \t\t    + infoMap.size() * sizeof(struct ipa_control_info_entry);\n+\tenum ipa_controls_id_map_type idMapType = idMapTypeFor(infoMap.idmap());\n \n-\tfor (const auto &ctrl : infoMap)\n+\tfor (const auto &ctrl : infoMap) {\n \t\tsize += binarySize(ctrl.second);\n \n+\t\tsize += serializedControlNameSize(ctrl.first, idMapType);\n+\t}\n+\n \treturn size;\n }\n \n@@ -183,8 +214,7 @@ size_t ControlSerializer::binarySize(const ControlInfoMap &infoMap)\n  */\n size_t ControlSerializer::binarySize(const ControlList &list)\n {\n-\tsize_t size = sizeof(struct ipa_controls_header)\n-\t\t    + list.size() * sizeof(struct ipa_control_list_entry);\n+\tsize_t size = sizeof(struct ipa_controls_header) + list.size() * sizeof(struct ipa_control_list_entry);\n \n \tfor (const auto &ctrl : list)\n \t\tsize += binarySize(ctrl.second);\n@@ -231,24 +261,21 @@ int ControlSerializer::serialize(const ControlInfoMap &infoMap,\n \t\treturn 0;\n \t}\n \n+\tenum ipa_controls_id_map_type idMapType = idMapTypeFor(infoMap.idmap());\n+\n \t/* Compute entries and data required sizes. */\n \tsize_t entriesSize = infoMap.size()\n \t\t\t   * sizeof(struct ipa_control_info_entry);\n \tsize_t valuesSize = 0;\n-\tfor (const auto &ctrl : infoMap)\n+\tfor (const auto &ctrl : infoMap) {\n \t\tvaluesSize += binarySize(ctrl.second);\n-\n-\tconst ControlIdMap *idmap = &infoMap.idmap();\n-\tenum ipa_controls_id_map_type idMapType;\n-\tif (idmap == &controls::controls)\n-\t\tidMapType = IPA_CONTROL_ID_MAP_CONTROLS;\n-\telse if (idmap == &properties::properties)\n-\t\tidMapType = IPA_CONTROL_ID_MAP_PROPERTIES;\n-\telse\n-\t\tidMapType = IPA_CONTROL_ID_MAP_V4L2;\n+\t\tvaluesSize += idMapRequiresLocalIds(idMapType)\n+\t\t\t\t      ? ctrl.first->name().size()\n+\t\t\t\t      : 0;\n+\t}\n \n \t/* Prepare the packet header. */\n-\tstruct ipa_controls_header hdr;\n+\tstruct ipa_controls_header hdr = {};\n \thdr.version = IPA_CONTROLS_FORMAT_VERSION;\n \thdr.handle = serial_;\n \thdr.entries = infoMap.size();\n@@ -267,21 +294,29 @@ int ControlSerializer::serialize(const ControlInfoMap &infoMap,\n \t */\n \tserial_ += 2;\n \n-\t/*\n-\t * Serialize all entries.\n-\t * \\todo Serialize the control name too\n-\t */\n+\t/* Serialize all entries. */\n \tByteStreamBuffer entries = buffer.carveOut(entriesSize);\n \tByteStreamBuffer values = buffer.carveOut(valuesSize);\n+\tstatic const std::string emptyName;\n \n \tfor (const auto &ctrl : infoMap) {\n \t\tconst ControlId *id = ctrl.first;\n \t\tconst ControlInfo &info = ctrl.second;\n+\t\tconst std::string &name = idMapRequiresLocalIds(idMapType)\n+\t\t\t\t\t\t  ? id->name()\n+\t\t\t\t\t\t  : emptyName;\n \n-\t\tstruct ipa_control_info_entry entry;\n+\t\tif (name.size() > kMaxControlNameLength) {\n+\t\t\tLOG(Serializer, Error)\n+\t\t\t\t<< \"Control name too long: \" << name.size();\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n+\t\tstruct ipa_control_info_entry entry = {};\n \t\tentry.id = id->id();\n \t\tentry.type = id->type();\n \t\tentry.direction = static_cast<ControlId::DirectionFlags::Type>(id->direction());\n+\t\tentry.name_len = static_cast<uint32_t>(name.size());\n \n \t\tpopulateControlValueEntry(entry.min, info.min(), values.offset());\n \t\tstore(info.min(), values);\n@@ -292,6 +327,11 @@ int ControlSerializer::serialize(const ControlInfoMap &infoMap,\n \t\tpopulateControlValueEntry(entry.def, info.def(), values.offset());\n \t\tstore(info.def(), values);\n \n+\t\tentry.name_offset = values.offset();\n+\t\tvalues.write(Span<const uint8_t>(\n+\t\t\treinterpret_cast<const uint8_t *>(name.c_str()),\n+\t\t\tname.size()));\n+\n \t\tentries.write(&entry);\n \t}\n \n@@ -355,7 +395,7 @@ int ControlSerializer::serialize(const ControlList &list,\n \t\tvaluesSize += binarySize(ctrl.second);\n \n \t/* Prepare the packet header. */\n-\tstruct ipa_controls_header hdr;\n+\tstruct ipa_controls_header hdr = {};\n \thdr.version = IPA_CONTROLS_FORMAT_VERSION;\n \thdr.handle = infoMapHandle;\n \thdr.entries = list.size();\n@@ -485,25 +525,6 @@ ControlInfoMap ControlSerializer::deserialize<ControlInfoMap>(ByteStreamBuffer &\n \n \t\tControlType type = static_cast<ControlType>(entry->type);\n \n-\t\t/* If we're using a local id map, populate it. */\n-\t\tif (localIdMap) {\n-\t\t\tControlId::DirectionFlags flags{\n-\t\t\t\tstatic_cast<ControlId::Direction>(entry->direction)\n-\t\t\t};\n-\n-\t\t\t/**\n-\t\t\t * \\todo Find a way to preserve the control name for\n-\t\t\t * debugging purpose.\n-\t\t\t */\n-\t\t\tcontrolIds_.emplace_back(std::make_unique<ControlId>(entry->id,\n-\t\t\t\t\t\t\t\t\t     \"\", \"local\", type,\n-\t\t\t\t\t\t\t\t\t     flags));\n-\t\t\t(*localIdMap)[entry->id] = controlIds_.back().get();\n-\t\t}\n-\n-\t\tconst ControlId *controlId = idMap->at(entry->id);\n-\t\tASSERT(controlId);\n-\n \t\tconst ipa_control_value_entry &min_entry = entry->min;\n \t\tconst ipa_control_value_entry &max_entry = entry->max;\n \t\tconst ipa_control_value_entry &def_entry = entry->def;\n@@ -538,6 +559,48 @@ ControlInfoMap ControlSerializer::deserialize<ControlInfoMap>(ByteStreamBuffer &\n \t\t\tloadControlValue(values, static_cast<ControlType>(def_entry.type),\n \t\t\t\t\t def_entry.is_array, def_entry.count);\n \n+\t\t/*\n+\t\t * Deserialize the null-terminated control name from the values\n+\t\t * section. Reject unreasonably long names to guard against\n+\t\t * malformed packets.\n+\t\t */\n+\t\tif (entry->name_len > kMaxControlNameLength) {\n+\t\t\tLOG(Serializer, Error)\n+\t\t\t\t<< \"Control name too long: \" << entry->name_len;\n+\t\t\treturn {};\n+\t\t}\n+\n+\t\tif (entry->name_offset != values.offset()) {\n+\t\t\tLOG(Serializer, Error)\n+\t\t\t\t<< \"Bad data, name offset mismatch (entry \"\n+\t\t\t\t<< i << \")\";\n+\t\t\treturn {};\n+\t\t}\n+\n+\t\tconst auto *nameData = entry->name_len\n+            ? values.read<const uint8_t>(entry->name_len) : nullptr;\n+\t\tif (entry->name_len && !nameData) {\n+\t\t\tLOG(Serializer, Error) << \"Out of data reading control name\";\n+\t\t\treturn {};\n+\t\t}\n+\n+\t\t/* If we're using a local id map, populate it with the restored name. */\n+\t\tif (localIdMap) {\n+\t\t\tstd::string ctrlName(reinterpret_cast<const char *>(nameData),\n+\t\t\t\t\t     entry->name_len);\n+\n+\t\t\tControlId::DirectionFlags flags{\n+\t\t\t\tstatic_cast<ControlId::Direction>(entry->direction)\n+\t\t\t};\n+\n+\t\t\tcontrolIds_.emplace_back(std::make_unique<ControlId>(entry->id,\n+\t\t\t\t\t\t\t\t\t     ctrlName, \"local\",\n+\t\t\t\t\t\t\t\t\t     type, flags));\n+\t\t\t(*localIdMap)[entry->id] = controlIds_.back().get();\n+\t\t}\n+\n+\t\tconst ControlId *controlId = idMap->at(entry->id);\n+\t\tASSERT(controlId);\n \n \t\t/* Create and store the ControlInfo. */\n \t\tctrls.emplace(controlId, ControlInfo(min, max, def));\ndiff --git a/src/libcamera/ipa_controls.cpp b/src/libcamera/ipa_controls.cpp\nindex 61af7433d..181317616 100644\n--- a/src/libcamera/ipa_controls.cpp\n+++ b/src/libcamera/ipa_controls.cpp\n@@ -234,14 +234,18 @@ static_assert(sizeof(ipa_control_list_entry) == 20,\n  * \\var ipa_control_info_entry::id\n  * The numerical ID of the control\n  * \\var ipa_control_info_entry::type\n- * The type of the control (defined by enum ControlType)\n- * info data (shall be a multiple of 8 bytes)\n+ * The type of the control (defined by enum ControlType).\n+ * The info data shall be a multiple of 8 bytes.\n  * \\var ipa_control_info_entry::direction\n  * The directions in which the control is allowed to be sent. This is a flags\n  * value, where 0x1 signifies input (as controls), and 0x2 signifies output (as\n  * metadata). \\sa ControlId::Direction\n  * \\var ipa_control_info_entry::padding\n  * Padding bytes (shall be set to 0)\n+ * \\var ipa_control_info_entry::name_len\n+ * Length of the control name\n+ * \\var ipa_control_info_entry::name_offset\n+ * Offset of the control name in the values section\n  * \\var ipa_control_info_entry::min\n  * The description of the serialized ControlValue (min)\n  * \\var ipa_control_info_entry::max\n@@ -250,7 +254,7 @@ static_assert(sizeof(ipa_control_list_entry) == 20,\n  * The description of the serialized ControlValue (def)\n  */\n \n-static_assert(sizeof(ipa_control_info_entry) == 64,\n+static_assert(sizeof(ipa_control_info_entry) == 68,\n \t      \"Invalid ABI size change for struct ipa_control_info_entry\");\n \n } /* namespace libcamera */\ndiff --git a/test/serialization/control_serialization.cpp b/test/serialization/control_serialization.cpp\nindex 06c572b77..b1638db9f 100644\n--- a/test/serialization/control_serialization.cpp\n+++ b/test/serialization/control_serialization.cpp\n@@ -11,6 +11,8 @@\n #include <libcamera/control_ids.h>\n #include <libcamera/controls.h>\n \n+#include <libcamera/ipa/ipa_controls.h>\n+\n #include \"libcamera/internal/byte_stream_buffer.h\"\n #include \"libcamera/internal/control_serializer.h\"\n \n@@ -169,6 +171,113 @@ protected:\n \t\t\treturn TestFail;\n \t\t}\n \n+\t\t/* Build a local (V4L2-like) ControlInfoMap and verify name round-trip. */\n+\t\tvector<unique_ptr<ControlId>> v4l2ControlIds;\n+\t\tControlIdMap v4l2IdMap;\n+\t\tconstexpr uint32_t kV4L2TestControlId = 0x009a2001;\n+\t\tconst string kV4L2ControlName = \"V4L2_CID_TEST_GAIN\";\n+\n+\t\tv4l2ControlIds.emplace_back(std::make_unique<ControlId>(\n+\t\t\tkV4L2TestControlId, kV4L2ControlName, \"v4l2\",\n+\t\t\tControlTypeInteger32, ControlId::Direction::In));\n+\t\tv4l2IdMap.emplace(kV4L2TestControlId, v4l2ControlIds.back().get());\n+\n+\t\tControlInfoMap::Map v4l2Info;\n+\t\tv4l2Info.emplace(v4l2ControlIds.back().get(),\n+\t\t\t\t ControlInfo(ControlValue(int32_t{ 0 }),\n+\t\t\t\t\t     ControlValue(int32_t{ 255 }),\n+\t\t\t\t\t     ControlValue(int32_t{ 16 })));\n+\t\tControlInfoMap v4l2InfoMap(std::move(v4l2Info), v4l2IdMap);\n+\n+\t\tControlSerializer v4l2Serializer(ControlSerializer::Role::Proxy);\n+\t\tControlSerializer v4l2Deserializer(ControlSerializer::Role::Worker);\n+\n+\t\tsize = v4l2Serializer.binarySize(v4l2InfoMap);\n+\t\tinfoData.resize(size);\n+\t\tbuffer = ByteStreamBuffer(infoData.data(), infoData.size());\n+\n+\t\tret = v4l2Serializer.serialize(v4l2InfoMap, buffer);\n+\t\tif (ret < 0 || buffer.overflow()) {\n+\t\t\tcerr << \"Failed to serialize V4L2-like ControlInfoMap\" << endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tbuffer = ByteStreamBuffer(const_cast<const uint8_t *>(infoData.data()),\n+\t\t\t\t\t  infoData.size());\n+\t\tControlInfoMap v4l2InfoMapDes =\n+\t\t\tv4l2Deserializer.deserialize<ControlInfoMap>(buffer);\n+\t\tif (v4l2InfoMapDes.empty()) {\n+\t\t\tcerr << \"Failed to deserialize V4L2-like ControlInfoMap\" << endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tauto idIt = v4l2InfoMapDes.idmap().find(kV4L2TestControlId);\n+\t\tif (idIt == v4l2InfoMapDes.idmap().end()) {\n+\t\t\tcerr << \"Deserialized V4L2-like id map misses test control\" << endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tif (idIt->second->name() != kV4L2ControlName) {\n+\t\t\tcerr << \"Deserialized V4L2-like control name doesn't match\" << endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\t/* Reject malformed packets with over-sized names. */\n+\t\tvector<uint8_t> badNameLenData = infoData;\n+\t\tauto *badNameLenHeader =\n+\t\t\treinterpret_cast<ipa_controls_header *>(badNameLenData.data());\n+\t\tauto *badNameLenEntry = reinterpret_cast<ipa_control_info_entry *>(\n+\t\t\tbadNameLenData.data() + sizeof(*badNameLenHeader));\n+\t\tbadNameLenEntry->name_len = 2048;\n+\n+\t\tControlSerializer badNameLenDeserializer(ControlSerializer::Role::Worker);\n+\t\tbuffer = ByteStreamBuffer(const_cast<const uint8_t *>(badNameLenData.data()),\n+\t\t\t\t\t  badNameLenData.size());\n+\t\tif (!badNameLenDeserializer.deserialize<ControlInfoMap>(buffer).empty()) {\n+\t\t\tcerr << \"Oversized control name should be rejected\" << endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\t/* Reject malformed packets with non-null-terminated names. */\n+\t\tvector<uint8_t> badTermData = infoData;\n+\t\tbadTermData.back() = 'X';\n+\n+\t\tControlSerializer badTermDeserializer(ControlSerializer::Role::Worker);\n+\t\tbuffer = ByteStreamBuffer(const_cast<const uint8_t *>(badTermData.data()),\n+\t\t\t\t\t  badTermData.size());\n+\t\tif (!badTermDeserializer.deserialize<ControlInfoMap>(buffer).empty()) {\n+\t\t\tcerr << \"Control name without null terminator should be rejected\" << endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\t/* Reject too-long names at serialization time. */\n+\t\tvector<unique_ptr<ControlId>> longNameControlIds;\n+\t\tControlIdMap longNameIdMap;\n+\t\tstring longName(1025, 'n');\n+\n+\t\tlongNameControlIds.emplace_back(std::make_unique<ControlId>(\n+\t\t\t0x009a2002, longName, \"v4l2\", ControlTypeInteger32,\n+\t\t\tControlId::Direction::In));\n+\t\tlongNameIdMap.emplace(0x009a2002, longNameControlIds.back().get());\n+\n+\t\tControlInfoMap::Map longNameInfo;\n+\t\tlongNameInfo.emplace(longNameControlIds.back().get(),\n+\t\t\t\t     ControlInfo(ControlValue(int32_t{ 0 }),\n+\t\t\t\t\t\t ControlValue(int32_t{ 255 }),\n+\t\t\t\t\t\t ControlValue(int32_t{ 16 })));\n+\t\tControlInfoMap longNameInfoMap(std::move(longNameInfo), longNameIdMap);\n+\n+\t\tControlSerializer longNameSerializer(ControlSerializer::Role::Proxy);\n+\t\tsize = longNameSerializer.binarySize(longNameInfoMap);\n+\t\tinfoData.resize(size);\n+\t\tbuffer = ByteStreamBuffer(infoData.data(), infoData.size());\n+\n+\t\tret = longNameSerializer.serialize(longNameInfoMap, buffer);\n+\t\tif (ret != -EINVAL) {\n+\t\t\tcerr << \"Too-long control name should fail serialization\" << endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n \t\treturn TestPass;\n \t}\n };\n","prefixes":["v2"]}