{"id":2312,"url":"https://patchwork.libcamera.org/api/patches/2312/?format=json","web_url":"https://patchwork.libcamera.org/patch/2312/","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":"<20191108205409.18845-17-laurent.pinchart@ideasonboard.com>","date":"2019-11-08T20:54:01","name":"[libcamera-devel,v2,16/24] libcamera: Add controls serializer","commit_ref":null,"pull_url":null,"state":"accepted","archived":false,"hash":"a7f36e2f68ce718957a00147f1ccdc3f458cc822","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/?format=json","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/2312/mbox/","series":[{"id":568,"url":"https://patchwork.libcamera.org/api/series/568/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=568","date":"2019-11-08T20:53:45","name":"Control serialization and IPA C API","version":2,"mbox":"https://patchwork.libcamera.org/series/568/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/2312/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/2312/checks/","tags":{},"headers":{"Return-Path":"<laurent.pinchart@ideasonboard.com>","Received":["from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 2927E61546\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri,  8 Nov 2019 21:54:28 +0100 (CET)","from pendragon.bb.dnainternet.fi (81-175-216-236.bb.dnainternet.fi\n\t[81.175.216.236])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id C3E942D1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri,  8 Nov 2019 21:54:27 +0100 (CET)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1573246467;\n\tbh=xAZfJvSj5CodQ9KbrxTJtUQ38Gq1sAdzUb98Xlhq3YE=;\n\th=From:To:Subject:Date:In-Reply-To:References:From;\n\tb=PcgWvkPGDes3MhZTQJD5MDr0CW9EFWYVer7CMX+JCkhsnQrQe/Sl6UNV4p66TcQ7Y\n\tXxlUtQ8b36s8Y8GnNs3Y3rlAcExNsVfpoqrWBMq1b3gkz21AXUGFKF4t/pzkpdV0hl\n\t1R8vsouCgGtnAs1QEEb+GziFnGxyO4UaXL1q0P/o=","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"libcamera-devel@lists.libcamera.org","Date":"Fri,  8 Nov 2019 22:54:01 +0200","Message-Id":"<20191108205409.18845-17-laurent.pinchart@ideasonboard.com>","X-Mailer":"git-send-email 2.23.0","In-Reply-To":"<20191108205409.18845-1-laurent.pinchart@ideasonboard.com>","References":"<20191108205409.18845-1-laurent.pinchart@ideasonboard.com>","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","Subject":"[libcamera-devel] [PATCH v2 16/24] libcamera: Add controls\n\tserializer","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>","X-List-Received-Date":"Fri, 08 Nov 2019 20:54:29 -0000"},"content":"Add a new ControlSerializer helper to serialize and deserialize\nControlInfoMap and ControlList instances. This will be used to implement\nthe C IPA protocol and the communication with IPA through IPC.\n\nSigned-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n---\n src/libcamera/control_serializer.cpp       | 501 +++++++++++++++++++++\n src/libcamera/include/control_serializer.h |  52 +++\n src/libcamera/include/meson.build          |   1 +\n src/libcamera/meson.build                  |   1 +\n 4 files changed, 555 insertions(+)\n create mode 100644 src/libcamera/control_serializer.cpp\n create mode 100644 src/libcamera/include/control_serializer.h","diff":"diff --git a/src/libcamera/control_serializer.cpp b/src/libcamera/control_serializer.cpp\nnew file mode 100644\nindex 000000000000..5fe096128e49\n--- /dev/null\n+++ b/src/libcamera/control_serializer.cpp\n@@ -0,0 +1,501 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2019, Google Inc.\n+ *\n+ * control_serializer.cpp - Control (de)serializer\n+ */\n+\n+#include \"control_serializer.h\"\n+\n+#include <algorithm>\n+#include <memory>\n+#include <vector>\n+\n+#include <ipa/ipa_controls.h>\n+#include <libcamera/control_ids.h>\n+#include <libcamera/controls.h>\n+\n+#include \"byte_stream_buffer.h\"\n+#include \"log.h\"\n+\n+/**\n+ * \\file control_serializer.h\n+ * \\brief Serialization and deserialization helpers for controls\n+ */\n+\n+namespace libcamera {\n+\n+LOG_DEFINE_CATEGORY(Serializer)\n+\n+namespace {\n+\n+static constexpr size_t ControlValueSize[] = {\n+\t[ControlTypeNone]\t= 1,\n+\t[ControlTypeBool]\t= sizeof(bool),\n+\t[ControlTypeInteger32]\t= sizeof(int32_t),\n+\t[ControlTypeInteger64]\t= sizeof(int64_t),\n+};\n+\n+} /* namespace */\n+\n+/**\n+ * \\class ControlSerializer\n+ * \\brief Serializer and deserializer for control-related classes\n+ *\n+ * The control serializer is a helper to serialize and deserialize\n+ * ControlInfoMap and ControlValue instances for the purpose of communication\n+ * with IPA modules.\n+ *\n+ * Neither the ControlInfoMap nor the ControlList are self-contained data\n+ * container. ControlInfoMap references an external ControlId in each of its\n+ * entries, and ControlList references a ControlInfoMap for the purpose of\n+ * validation. Serializing and deserializing those objects thus requires a\n+ * context that maintains the associations between them. The control serializer\n+ * fulfils this task.\n+ *\n+ * ControlInfoMap instances can be serialized on their own, but require\n+ * ControlId instances to be provided at deserialization time. The serializer\n+ * recreates those ControlId instances and stores them in an internal cache,\n+ * from which the ControlInfoMap is populated.\n+ *\n+ * ControlList instances need to be associated with a ControlInfoMap when\n+ * deserialized. To make this possible, the control lists are serialized with a\n+ * handle to their ControlInfoMap, and the map is looked up from the handle at\n+ * deserialization time. To make this possible, the serializer assigns a\n+ * numerical handle to ControlInfoList instances when they are serialized, and\n+ * stores the mapping between handle and ControlInfoList both when serializing\n+ * (for the pipeline handler side) and deserializing (for the IPA side) them.\n+ * This mapping is used when serializing a ControlList to include the\n+ * corresponding ControlInfoMap handle in the binary data, and when\n+ * deserializing to retrieve the corresponding ControlInfoMap.\n+ *\n+ * In order to perform those tasks, the serializer keeps an internal state that\n+ * needs to be properly populated. This mechanism requires the ControlInfoMap\n+ * corresponding to a ControlList to have been serialized or deserialized\n+ * before the ControlList is serialized or deserialized. Failure to comply with\n+ * that constraint results in serialization or deserialization failure of the\n+ * ControlList.\n+ *\n+ * The serializer can be reset() to clear its internal state. This may be\n+ * performed when reconfiguring an IPA to avoid constant growth of the internal\n+ * state, especially if the contents of the ControlInfoMap instances change at\n+ * that time. A reset of the serializer invalidates all ControlList and\n+ * ControlInfoMap that have been previously deserialized. The caller shall thus\n+ * proceed with care to avoid stale references.\n+ */\n+\n+/**\n+ * \\brief Reset the serializer\n+ *\n+ * Reset the internal state of the serializer. This invalidates all the\n+ * ControlList and ControlInfoMap that have been previously deserialized.\n+ */\n+void ControlSerializer::reset()\n+{\n+\tserial_ = 0;\n+\n+\tinfoMapHandles_.clear();\n+\tinfoMaps_.clear();\n+\tcontrolIds_.clear();\n+}\n+\n+size_t ControlSerializer::binarySize(const ControlValue &value)\n+{\n+\treturn ControlValueSize[value.type()];\n+}\n+\n+size_t ControlSerializer::binarySize(const ControlRange &range)\n+{\n+\treturn binarySize(range.min()) + binarySize(range.max());\n+}\n+\n+/**\n+ * \\brief Retrieve the size in bytes required to serialize a ControlInfoMap\n+ * \\param[in] info The control info map\n+ *\n+ * Compute and return the size in bytes required to store the serialized\n+ * ControlInfoMap.\n+ *\n+ * \\return The size in bytes required to store the serialized ControlInfoMap\n+ */\n+size_t ControlSerializer::binarySize(const ControlInfoMap &info)\n+{\n+\tsize_t size = sizeof(struct ipa_controls_header)\n+\t\t    + info.size() * sizeof(struct ipa_control_range_entry);\n+\n+\tfor (const auto &ctrl : info)\n+\t\tsize += binarySize(ctrl.second);\n+\n+\treturn size;\n+}\n+\n+/**\n+ * \\brief Retrieve the size in bytes required to serialize a ControlList\n+ * \\param[in] list The control list\n+ *\n+ * Compute and return the size in bytes required to store the serialized\n+ * ControlList.\n+ *\n+ * \\return The size in bytes required to store the serialized ControlList\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_value_entry);\n+\n+\tfor (const auto &ctrl : list)\n+\t\tsize += binarySize(ctrl.second);\n+\n+\treturn size;\n+}\n+\n+void ControlSerializer::store(const ControlValue &value,\n+\t\t\t      ByteStreamBuffer &buffer)\n+{\n+\tswitch (value.type()) {\n+\tcase ControlTypeBool: {\n+\t\tbool data = value.get<bool>();\n+\t\tbuffer.write(&data);\n+\t\tbreak;\n+\t}\n+\n+\tcase ControlTypeInteger32: {\n+\t\tint32_t data = value.get<int32_t>();\n+\t\tbuffer.write(&data);\n+\t\tbreak;\n+\t}\n+\n+\tcase ControlTypeInteger64: {\n+\t\tuint64_t data = value.get<int64_t>();\n+\t\tbuffer.write(&data);\n+\t\tbreak;\n+\t}\n+\n+\tdefault:\n+\t\tbreak;\n+\t}\n+}\n+\n+void ControlSerializer::store(const ControlRange &range,\n+\t\t\t      ByteStreamBuffer &buffer)\n+{\n+\tstore(range.min(), buffer);\n+\tstore(range.max(), buffer);\n+}\n+\n+/**\n+ * \\brief Serialize a ControlInfoMap in a buffer\n+ * \\param[in] info The control info map to serialize\n+ * \\param[in] buffer The memory buffer where to serialize the ControlInfoMap\n+ *\n+ * Serialize the \\a info map into the \\a buffer using the serialization format\n+ * defined by the IPA context interface in ipa_controls.h.\n+ *\n+ * The serializer stores a reference to the \\a info internally. The caller\n+ * shall ensure that \\a info stays valid until the serializer is reset().\n+ *\n+ * \\return 0 on success, a negative error code otherwise\n+ * \\retval -ENOSPC Not enough space is available in the buffer\n+ */\n+int ControlSerializer::serialize(const ControlInfoMap &info,\n+\t\t\t\t ByteStreamBuffer &buffer)\n+{\n+\t/* Compute entries and data required sizes. */\n+\tsize_t entriesSize = info.size() * sizeof(struct ipa_control_range_entry);\n+\tsize_t valuesSize = 0;\n+\tfor (const auto &ctrl : info)\n+\t\tvaluesSize += binarySize(ctrl.second);\n+\n+\t/* Prepare the packet header, assign a handle to the ControlInfoMap. */\n+\tstruct ipa_controls_header hdr;\n+\thdr.version = IPA_CONTROLS_FORMAT_VERSION;\n+\thdr.handle = ++serial_;\n+\thdr.entries = info.size();\n+\thdr.size = sizeof(hdr) + entriesSize + valuesSize;\n+\thdr.data_offset = sizeof(hdr) + entriesSize;\n+\n+\tbuffer.write(&hdr);\n+\n+\t/*\n+\t * Serialize all entries.\n+\t * \\todo Serialize the control name too\n+\t */\n+\tByteStreamBuffer entries = buffer.carveOut(entriesSize);\n+\tByteStreamBuffer values = buffer.carveOut(valuesSize);\n+\n+\tfor (const auto &ctrl : info) {\n+\t\tconst ControlId *id = ctrl.first;\n+\t\tconst ControlRange &range = ctrl.second;\n+\n+\t\tstruct ipa_control_range_entry entry;\n+\t\tentry.id = id->id();\n+\t\tentry.type = id->type();\n+\t\tentry.offset = values.offset();\n+\t\tentries.write(&entry);\n+\n+\t\tstore(range, values);\n+\t}\n+\n+\tif (buffer.overflow())\n+\t\treturn -ENOSPC;\n+\n+\t/*\n+\t * Store the map to handle association, to be used to serialize and\n+\t * deserialize control lists.\n+\t */\n+\tinfoMapHandles_[&info] = hdr.handle;\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * \\brief Serialize a ControlList in a buffer\n+ * \\param[in] list The control list to serialize\n+ * \\param[in] buffer The memory buffer where to serialize the ControlList\n+ *\n+ * Serialize the \\a list into the \\a buffer using the serialization format\n+ * defined by the IPA context interface in ipa_controls.h.\n+ *\n+ * \\return 0 on success, a negative error code otherwise\n+ * \\retval -ENOSPC Not enough space is available in the buffer\n+ */\n+int ControlSerializer::serialize(const ControlList &list,\n+\t\t\t\t ByteStreamBuffer &buffer)\n+{\n+\t/*\n+\t * Find the ControlInfoMap handle for the ControlList if it has one, or\n+\t * use 0 for ControlList without a ControlInfoMap.\n+\t */\n+\tunsigned int infoMapHandle;\n+\tif (list.infoMap()) {\n+\t\tauto iter = infoMapHandles_.find(list.infoMap());\n+\t\tif (iter == infoMapHandles_.end()) {\n+\t\t\tLOG(Serializer, Error)\n+\t\t\t\t<< \"Can't serialize ControlList: unknown ControlInfoMap\";\n+\t\t\treturn -ENOENT;\n+\t\t}\n+\n+\t\tinfoMapHandle = iter->second;\n+\t} else {\n+\t\tinfoMapHandle = 0;\n+\t}\n+\n+\tsize_t entriesSize = list.size() * sizeof(struct ipa_control_value_entry);\n+\tsize_t valuesSize = 0;\n+\tfor (const auto &ctrl : list)\n+\t\tvaluesSize += binarySize(ctrl.second);\n+\n+\t/* Prepare the packet header. */\n+\tstruct ipa_controls_header hdr;\n+\thdr.version = IPA_CONTROLS_FORMAT_VERSION;\n+\thdr.handle = infoMapHandle;\n+\thdr.entries = list.size();\n+\thdr.size = sizeof(hdr) + entriesSize + valuesSize;\n+\thdr.data_offset = sizeof(hdr) + entriesSize;\n+\n+\tbuffer.write(&hdr);\n+\n+\tByteStreamBuffer entries = buffer.carveOut(entriesSize);\n+\tByteStreamBuffer values = buffer.carveOut(valuesSize);\n+\n+\t/* Serialize all entries. */\n+\tfor (const auto &ctrl : list) {\n+\t\tunsigned int id = ctrl.first;\n+\t\tconst ControlValue &value = ctrl.second;\n+\n+\t\tstruct ipa_control_value_entry entry;\n+\t\tentry.id = id;\n+\t\tentry.count = 1;\n+\t\tentry.type = value.type();\n+\t\tentry.offset = values.offset();\n+\t\tentries.write(&entry);\n+\n+\t\tstore(value, values);\n+\t}\n+\n+\tif (buffer.overflow())\n+\t\treturn -ENOSPC;\n+\n+\treturn 0;\n+}\n+\n+template<>\n+ControlValue ControlSerializer::load<ControlValue>(ControlType type,\n+\t\t\t\t\t\t   ByteStreamBuffer &b)\n+{\n+\tswitch (type) {\n+\tcase ControlTypeBool: {\n+\t\tbool value;\n+\t\tb.read(&value);\n+\t\treturn ControlValue(value);\n+\t}\n+\n+\tcase ControlTypeInteger32: {\n+\t\tint32_t value;\n+\t\tb.read(&value);\n+\t\treturn ControlValue(value);\n+\t}\n+\n+\tcase ControlTypeInteger64: {\n+\t\tint64_t value;\n+\t\tb.read(&value);\n+\t\treturn ControlValue(value);\n+\t}\n+\n+\tdefault:\n+\t\treturn ControlValue();\n+\t}\n+}\n+\n+template<>\n+ControlRange ControlSerializer::load<ControlRange>(ControlType type,\n+\t\t\t\t\t\t   ByteStreamBuffer &b)\n+{\n+\tControlValue min = load<ControlValue>(type, b);\n+\tControlValue max = load<ControlValue>(type, b);\n+\n+\treturn ControlRange(min, max);\n+}\n+\n+/**\n+ * \\fn template<typename T> T ControlSerializer::deserialize(ByteStreamBuffer &buffer)\n+ * \\brief Deserialize an object from a binary buffer\n+ * \\param[in] buffer The memory buffer that contains the object\n+ *\n+ * This method is only valid when specialized for ControlInfoMap or\n+ * ControlList. Any other typename \\a T is not supported.\n+ */\n+\n+/**\n+ * \\brief Deserialize a ControlInfoMap from a binary buffer\n+ * \\param[in] buffer The memory buffer that contains the serialized map\n+ *\n+ * Re-construct a ControlInfoMap from a binary \\a buffer containing data\n+ * serialized using the serialize() method.\n+ *\n+ * \\return The deserialized ControlInfoMap\n+ */\n+template<>\n+ControlInfoMap ControlSerializer::deserialize<ControlInfoMap>(ByteStreamBuffer &buffer)\n+{\n+\tstruct ipa_controls_header hdr;\n+\tbuffer.read(&hdr);\n+\n+\tif (hdr.version != IPA_CONTROLS_FORMAT_VERSION) {\n+\t\tLOG(Serializer, Error)\n+\t\t\t<< \"Unsupported controls format version \"\n+\t\t\t<< hdr.version;\n+\t\treturn {};\n+\t}\n+\n+\tByteStreamBuffer entries = buffer.carveOut(hdr.data_offset - sizeof(hdr));\n+\tByteStreamBuffer values = buffer.carveOut(hdr.size - hdr.data_offset);\n+\n+\tif (buffer.overflow())\n+\t\treturn {};\n+\n+\tControlInfoMap::Map ctrls;\n+\n+\tfor (unsigned int i = 0; i < hdr.entries; ++i) {\n+\t\tstruct ipa_control_range_entry entry;\n+\t\tentries.read(&entry);\n+\n+\t\t/* Create and cache the individual ControlId. */\n+\t\tControlType type = static_cast<ControlType>(entry.type);\n+\t\tcontrolIds_.emplace_back(utils::make_unique<ControlId>(entry.id, \"\", type));\n+\n+\t\tif (entry.offset != values.offset()) {\n+\t\t\tLOG(Serializer, Error)\n+\t\t\t\t<< \"Bad data, entry offset mismatch (entry \"\n+\t\t\t\t<< i << \")\";\n+\t\t\treturn {};\n+\t\t}\n+\n+\t\t/* Create and store the ControlRange. */\n+\t\tctrls.emplace(controlIds_.back().get(),\n+\t\t\t      load<ControlRange>(type, values));\n+\t}\n+\n+\t/*\n+\t * Create the ControlInfoMap in the cache, and store the map to handle\n+\t * association.\n+\t */\n+\tControlInfoMap &map = infoMaps_[hdr.handle] = std::move(ctrls);\n+\tinfoMapHandles_[&map] = hdr.handle;\n+\n+\treturn map;\n+}\n+\n+/**\n+ * \\brief Deserialize a ControlList from a binary buffer\n+ * \\param[in] buffer The memory buffer that contains the serialized list\n+ *\n+ * Re-construct a ControlList from a binary \\a buffer containing data\n+ * serialized using the serialize() method.\n+ *\n+ * \\return The deserialized ControlList\n+ */\n+template<>\n+ControlList ControlSerializer::deserialize<ControlList>(ByteStreamBuffer &buffer)\n+{\n+\tstruct ipa_controls_header hdr;\n+\tbuffer.read(&hdr);\n+\n+\tif (hdr.version != IPA_CONTROLS_FORMAT_VERSION) {\n+\t\tLOG(Serializer, Error)\n+\t\t\t<< \"Unsupported controls format version \"\n+\t\t\t<< hdr.version;\n+\t\treturn {};\n+\t}\n+\n+\tByteStreamBuffer entries = buffer.carveOut(hdr.data_offset - sizeof(hdr));\n+\tByteStreamBuffer values = buffer.carveOut(hdr.size - hdr.data_offset);\n+\n+\tif (buffer.overflow())\n+\t\treturn {};\n+\n+\t/*\n+\t * Retrieve the ControlInfoMap associated with the ControlList based on\n+\t * its ID. The mapping between infoMap and ID is set up when serializing\n+\t * or deserializing ControlInfoMap. If no mapping is found (which is\n+\t * currently the case for ControlList related to libcamera controls),\n+\t * use the global control::control idmap.\n+\t */\n+\tconst ControlInfoMap *infoMap;\n+\tif (hdr.handle) {\n+\t\tauto iter = std::find_if(infoMapHandles_.begin(), infoMapHandles_.end(),\n+\t\t\t\t\t [&](decltype(infoMapHandles_)::value_type &entry) {\n+\t\t\t\t\t\t return entry.second == hdr.handle;\n+\t\t\t\t\t });\n+\t\tif (iter == infoMapHandles_.end()) {\n+\t\t\tLOG(Serializer, Error)\n+\t\t\t\t<< \"Can't deserialize ControlList: unknown ControlInfoMap\";\n+\t\t\treturn {};\n+\t\t}\n+\n+\t\tinfoMap = iter->first;\n+\t} else {\n+\t\tinfoMap = nullptr;\n+\t}\n+\n+\tControlList ctrls(infoMap ? infoMap->idmap() : controls::controls);\n+\n+\tfor (unsigned int i = 0; i < hdr.entries; ++i) {\n+\t\tstruct ipa_control_value_entry entry;\n+\t\tentries.read(&entry);\n+\n+\t\tif (entry.offset != values.offset()) {\n+\t\t\tLOG(Serializer, Error)\n+\t\t\t\t<< \"Bad data, entry offset mismatch (entry \"\n+\t\t\t\t<< i << \")\";\n+\t\t\treturn {};\n+\t\t}\n+\n+\t\tControlType type = static_cast<ControlType>(entry.type);\n+\t\tctrls.set(entry.id, load<ControlValue>(type, values));\n+\t}\n+\n+\treturn ctrls;\n+}\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/include/control_serializer.h b/src/libcamera/include/control_serializer.h\nnew file mode 100644\nindex 000000000000..bb3cb8e7b904\n--- /dev/null\n+++ b/src/libcamera/include/control_serializer.h\n@@ -0,0 +1,52 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2019, Google Inc.\n+ *\n+ * control_serializer.h - Control (de)serializer\n+ */\n+#ifndef __LIBCAMERA_CONTROL_SERIALIZER_H__\n+#define __LIBCAMERA_CONTROL_SERIALIZER_H__\n+\n+#include <map>\n+#include <memory>\n+#include <vector>\n+\n+#include <libcamera/controls.h>\n+\n+namespace libcamera {\n+\n+class ByteStreamBuffer;\n+\n+class ControlSerializer\n+{\n+public:\n+\tvoid reset();\n+\n+\tstatic size_t binarySize(const ControlInfoMap &info);\n+\tstatic size_t binarySize(const ControlList &list);\n+\n+\tint serialize(const ControlInfoMap &info, ByteStreamBuffer &buffer);\n+\tint serialize(const ControlList &list, ByteStreamBuffer &buffer);\n+\n+\ttemplate<typename T>\n+\tT deserialize(ByteStreamBuffer &buffer);\n+\n+private:\n+\tstatic size_t binarySize(const ControlValue &value);\n+\tstatic size_t binarySize(const ControlRange &range);\n+\n+\tstatic void store(const ControlValue &value, ByteStreamBuffer &buffer);\n+\tstatic void store(const ControlRange &range, ByteStreamBuffer &buffer);\n+\n+\ttemplate<typename T>\n+\tT load(ControlType type, ByteStreamBuffer &b);\n+\n+\tunsigned int serial_;\n+\tstd::vector<std::unique_ptr<ControlId>> controlIds_;\n+\tstd::map<unsigned int, ControlInfoMap> infoMaps_;\n+\tstd::map<const ControlInfoMap *, unsigned int> infoMapHandles_;\n+};\n+\n+} /* namespace libcamera */\n+\n+#endif /* __LIBCAMERA_CONTROL_SERIALIZER_H__ */\ndiff --git a/src/libcamera/include/meson.build b/src/libcamera/include/meson.build\nindex 1ff0198662cc..697294f4b09b 100644\n--- a/src/libcamera/include/meson.build\n+++ b/src/libcamera/include/meson.build\n@@ -2,6 +2,7 @@ libcamera_headers = files([\n     'byte_stream_buffer.h',\n     'camera_controls.h',\n     'camera_sensor.h',\n+    'control_serializer.h',\n     'control_validator.h',\n     'device_enumerator.h',\n     'device_enumerator_sysfs.h',\ndiff --git a/src/libcamera/meson.build b/src/libcamera/meson.build\nindex dab2d8ad2649..59cf582580c4 100644\n--- a/src/libcamera/meson.build\n+++ b/src/libcamera/meson.build\n@@ -7,6 +7,7 @@ libcamera_sources = files([\n     'camera_manager.cpp',\n     'camera_sensor.cpp',\n     'controls.cpp',\n+    'control_serializer.cpp',\n     'control_validator.cpp',\n     'device_enumerator.cpp',\n     'device_enumerator_sysfs.cpp',\n","prefixes":["libcamera-devel","v2","16/24"]}