summaryrefslogtreecommitdiff
path: root/src/libcamera/ipa_data_serializer.cpp
diff options
context:
space:
mode:
authorPaul Elder <paul.elder@ideasonboard.com>2020-12-05 19:30:48 +0900
committerPaul Elder <paul.elder@ideasonboard.com>2021-02-16 19:21:18 +0900
commit13f7d58569776b82cfce7f772768973325d878ff (patch)
treed07f79316021c03a357f5c1b04f419461faa485e /src/libcamera/ipa_data_serializer.cpp
parentc6d4303b0b6124e7b6c31b34b43755bc740132bb (diff)
libcamera: Add IPADataSerializer
Add an IPADataSerializer which implements (de)serialization of built-in (PODs, vector, map, string) and libcamera data structures. This is intended to be used by the proxy and the proxy worker in the IPC layer. Signed-off-by: Paul Elder <paul.elder@ideasonboard.com> Reviewed-by: Jacopo Mondi <jacopo@jmondi.org> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Acked-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
Diffstat (limited to 'src/libcamera/ipa_data_serializer.cpp')
-rw-r--r--src/libcamera/ipa_data_serializer.cpp615
1 files changed, 615 insertions, 0 deletions
diff --git a/src/libcamera/ipa_data_serializer.cpp b/src/libcamera/ipa_data_serializer.cpp
new file mode 100644
index 00000000..131b3bd6
--- /dev/null
+++ b/src/libcamera/ipa_data_serializer.cpp
@@ -0,0 +1,615 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * ipa_data_serializer.cpp - Image Processing Algorithm data serializer
+ */
+
+#include "libcamera/internal/ipa_data_serializer.h"
+
+#include "libcamera/internal/log.h"
+
+/**
+ * \file ipa_data_serializer.h
+ * \brief IPA Data Serializer
+ */
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(IPADataSerializer)
+
+/**
+ * \class IPADataSerializer
+ * \brief IPA Data Serializer
+ *
+ * Static template class that provides functions for serializing and
+ * deserializing IPA data.
+ *
+ * \todo Switch to Span instead of byte and fd vector
+ *
+ * \todo Harden the vector and map deserializer
+ *
+ * \todo For FileDescriptors, instead of storing a validity flag, store an
+ * index into the fd array. This will allow us to use views instead of copying.
+ */
+
+namespace {
+
+/**
+ * \fn template<typename T> void appendPOD(std::vector<uint8_t> &vec, T val)
+ * \brief Append POD to end of byte vector, in little-endian order
+ * \tparam T Type of POD to append
+ * \param[in] vec Byte vector to append to
+ * \param[in] val Value to append
+ *
+ * This function is meant to be used by the IPA data serializer, and the
+ * generated IPA proxies.
+ */
+
+/**
+ * \fn template<typename T> T readPOD(std::vector<uint8_t>::iterator it, size_t pos,
+ * std::vector<uint8_t>::iterator end)
+ * \brief Read POD from byte vector, in little-endian order
+ * \tparam T Type of POD to read
+ * \param[in] it Iterator of byte vector to read from
+ * \param[in] pos Index in byte vector to read from
+ * \param[in] end Iterator marking end of byte vector
+ *
+ * This function is meant to be used by the IPA data serializer, and the
+ * generated IPA proxies.
+ *
+ * If the \a pos plus the byte-width of the desired POD is past \a end, it is
+ * a fata error will occur, as it means there is insufficient data for
+ * deserialization, which should never happen.
+ *
+ * \return The POD read from \a it at index \a pos
+ */
+
+/**
+ * \fn template<typename T> T readPOD(std::vector<uint8_t> &vec, size_t pos)
+ * \brief Read POD from byte vector, in little-endian order
+ * \tparam T Type of POD to read
+ * \param[in] vec Byte vector to read from
+ * \param[in] pos Index in vec to start reading from
+ *
+ * This function is meant to be used by the IPA data serializer, and the
+ * generated IPA proxies.
+ *
+ * If the \a pos plus the byte-width of the desired POD is past the end of
+ * \a vec, a fatal error will occur, as it means there is insufficient data
+ * for deserialization, which should never happen.
+ *
+ * \return The POD read from \a vec at index \a pos
+ */
+
+} /* namespace */
+
+/**
+ * \fn template<typename T> IPADataSerializer<T>::serialize(
+ * T data,
+ * ControlSerializer *cs = nullptr)
+ * \brief Serialize an object into byte vector and fd vector
+ * \tparam T Type of object to serialize
+ * \param[in] data Object to serialize
+ * \param[in] cs ControlSerializer
+ *
+ * \a cs is only necessary if the object type \a T or its members contain
+ * ControlList or ControlInfoMap.
+ *
+ * \return Tuple of byte vector and fd vector, that is the serialized form
+ * of \a data
+ */
+
+/**
+ * \fn template<typename T> IPADataSerializer<T>::deserialize(
+ * const std::vector<uint8_t> &data,
+ * ControlSerializer *cs = nullptr)
+ * \brief Deserialize byte vector into an object
+ * \tparam T Type of object to deserialize to
+ * \param[in] data Byte vector to deserialize from
+ * \param[in] cs ControlSerializer
+ *
+ * This version of deserialize() can be used if the object type \a T and its
+ * members don't have any FileDescriptor.
+ *
+ * \a cs is only necessary if the object type \a T or its members contain
+ * ControlList or ControlInfoMap.
+ *
+ * \return The deserialized object
+ */
+
+/**
+ * \fn template<typename T> IPADataSerializer<T>::deserialize(
+ * std::vector<uint8_t>::const_iterator dataBegin,
+ * std::vector<uint8_t>::const_iterator dataEnd,
+ * ControlSerializer *cs = nullptr)
+ * \brief Deserialize byte vector into an object
+ * \tparam T Type of object to deserialize to
+ * \param[in] dataBegin Begin iterator of byte vector to deserialize from
+ * \param[in] dataEnd End iterator of byte vector to deserialize from
+ * \param[in] cs ControlSerializer
+ *
+ * This version of deserialize() can be used if the object type \a T and its
+ * members don't have any FileDescriptor.
+ *
+ * \a cs is only necessary if the object type \a T or its members contain
+ * ControlList or ControlInfoMap.
+ *
+ * \return The deserialized object
+ */
+
+/**
+ * \fn template<typename T> IPADataSerializer<T>::deserialize(
+ * const std::vector<uint8_t> &data,
+ * const std::vector<int32_t> &fds,
+ * ControlSerializer *cs = nullptr)
+ * \brief Deserialize byte vector and fd vector into an object
+ * \tparam T Type of object to deserialize to
+ * \param[in] data Byte vector to deserialize from
+ * \param[in] fds Fd vector to deserialize from
+ * \param[in] cs ControlSerializer
+ *
+ * This version of deserialize() (or the iterator version) must be used if
+ * the object type \a T or its members contain FileDescriptor.
+ *
+ * \a cs is only necessary if the object type \a T or its members contain
+ * ControlList or ControlInfoMap.
+ *
+ * \return The deserialized object
+ */
+
+/**
+ * \fn template<typename T> IPADataSerializer::deserialize(
+ * std::vector<uint8_t>::const_iterator dataBegin,
+ * std::vector<uint8_t>::const_iterator dataEnd,
+ * std::vector<int32_t>::const_iterator fdsBegin,
+ * std::vector<int32_t>::const_iterator fdsEnd,
+ * ControlSerializer *cs = nullptr)
+ * \brief Deserialize byte vector and fd vector into an object
+ * \tparam T Type of object to deserialize to
+ * \param[in] dataBegin Begin iterator of byte vector to deserialize from
+ * \param[in] dataEnd End iterator of byte vector to deserialize from
+ * \param[in] fdsBegin Begin iterator of fd vector to deserialize from
+ * \param[in] fdsEnd End iterator of fd vector to deserialize from
+ * \param[in] cs ControlSerializer
+ *
+ * This version of deserialize() (or the vector version) must be used if
+ * the object type \a T or its members contain FileDescriptor.
+ *
+ * \a cs is only necessary if the object type \a T or its members contain
+ * ControlList or ControlInfoMap.
+ *
+ * \return The deserialized object
+ */
+
+#ifndef __DOXYGEN__
+
+#define DEFINE_POD_SERIALIZER(type) \
+ \
+template<> \
+std::tuple<std::vector<uint8_t>, std::vector<int32_t>> \
+IPADataSerializer<type>::serialize(const type &data, \
+ [[maybe_unused]] ControlSerializer *cs) \
+{ \
+ std::vector<uint8_t> dataVec; \
+ dataVec.reserve(sizeof(type)); \
+ appendPOD<type>(dataVec, data); \
+ \
+ return { dataVec, {} }; \
+} \
+ \
+template<> \
+type IPADataSerializer<type>::deserialize(std::vector<uint8_t>::const_iterator dataBegin, \
+ std::vector<uint8_t>::const_iterator dataEnd, \
+ [[maybe_unused]] ControlSerializer *cs) \
+{ \
+ return readPOD<type>(dataBegin, 0, dataEnd); \
+} \
+ \
+template<> \
+type IPADataSerializer<type>::deserialize(const std::vector<uint8_t> &data, \
+ ControlSerializer *cs) \
+{ \
+ return deserialize(data.cbegin(), data.end(), cs); \
+} \
+ \
+template<> \
+type IPADataSerializer<type>::deserialize(const std::vector<uint8_t> &data, \
+ [[maybe_unused]] const std::vector<int32_t> &fds, \
+ ControlSerializer *cs) \
+{ \
+ return deserialize(data.cbegin(), data.end(), cs); \
+} \
+ \
+template<> \
+type IPADataSerializer<type>::deserialize(std::vector<uint8_t>::const_iterator dataBegin, \
+ std::vector<uint8_t>::const_iterator dataEnd, \
+ [[maybe_unused]] std::vector<int32_t>::const_iterator fdsBegin, \
+ [[maybe_unused]] std::vector<int32_t>::const_iterator fdsEnd, \
+ ControlSerializer *cs) \
+{ \
+ return deserialize(dataBegin, dataEnd, cs); \
+}
+
+DEFINE_POD_SERIALIZER(bool)
+DEFINE_POD_SERIALIZER(uint8_t)
+DEFINE_POD_SERIALIZER(uint16_t)
+DEFINE_POD_SERIALIZER(uint32_t)
+DEFINE_POD_SERIALIZER(uint64_t)
+DEFINE_POD_SERIALIZER(int8_t)
+DEFINE_POD_SERIALIZER(int16_t)
+DEFINE_POD_SERIALIZER(int32_t)
+DEFINE_POD_SERIALIZER(int64_t)
+DEFINE_POD_SERIALIZER(float)
+DEFINE_POD_SERIALIZER(double)
+
+/*
+ * Strings are serialized simply by converting by {string.cbegin(), string.end()}.
+ * The size of the string is recorded by the container (struct, vector, map, or
+ * function parameter serdes).
+ */
+template<>
+std::tuple<std::vector<uint8_t>, std::vector<int32_t>>
+IPADataSerializer<std::string>::serialize(const std::string &data,
+ [[maybe_unused]] ControlSerializer *cs)
+{
+ return { { data.cbegin(), data.end() }, {} };
+}
+
+template<>
+std::string
+IPADataSerializer<std::string>::deserialize(const std::vector<uint8_t> &data,
+ [[maybe_unused]] ControlSerializer *cs)
+{
+ return { data.cbegin(), data.cend() };
+}
+
+template<>
+std::string
+IPADataSerializer<std::string>::deserialize(std::vector<uint8_t>::const_iterator dataBegin,
+ std::vector<uint8_t>::const_iterator dataEnd,
+ [[maybe_unused]] ControlSerializer *cs)
+{
+ return { dataBegin, dataEnd };
+}
+
+template<>
+std::string
+IPADataSerializer<std::string>::deserialize(const std::vector<uint8_t> &data,
+ [[maybe_unused]] const std::vector<int32_t> &fds,
+ [[maybe_unused]] ControlSerializer *cs)
+{
+ return { data.cbegin(), data.cend() };
+}
+
+template<>
+std::string
+IPADataSerializer<std::string>::deserialize(std::vector<uint8_t>::const_iterator dataBegin,
+ std::vector<uint8_t>::const_iterator dataEnd,
+ [[maybe_unused]] std::vector<int32_t>::const_iterator fdsBegin,
+ [[maybe_unused]] std::vector<int32_t>::const_iterator fdsEnd,
+ [[maybe_unused]] ControlSerializer *cs)
+{
+ return { dataBegin, dataEnd };
+}
+
+/*
+ * ControlList is serialized as:
+ *
+ * 4 bytes - uint32_t Size of serialized ControlInfoMap, in bytes
+ * 4 bytes - uint32_t Size of serialized ControlList, in bytes
+ * X bytes - Serialized ControlInfoMap (using ControlSerializer)
+ * X bytes - Serialized ControlList (using ControlSerializer)
+ *
+ * If data.infoMap() is nullptr, then the default controls::controls will
+ * be used. The serialized ControlInfoMap will have zero length.
+ */
+template<>
+std::tuple<std::vector<uint8_t>, std::vector<int32_t>>
+IPADataSerializer<ControlList>::serialize(const ControlList &data, ControlSerializer *cs)
+{
+ if (!cs)
+ LOG(IPADataSerializer, Fatal)
+ << "ControlSerializer not provided for serialization of ControlList";
+
+ size_t size;
+ std::vector<uint8_t> infoData;
+ int ret;
+
+ /*
+ * \todo Revisit this opportunistic serialization of the
+ * ControlInfoMap, as it could be fragile
+ */
+ if (data.infoMap() && !cs->isCached(*data.infoMap())) {
+ size = cs->binarySize(*data.infoMap());
+ infoData.resize(size);
+ ByteStreamBuffer buffer(infoData.data(), infoData.size());
+ ret = cs->serialize(*data.infoMap(), buffer);
+
+ if (ret < 0 || buffer.overflow()) {
+ LOG(IPADataSerializer, Error) << "Failed to serialize ControlList's ControlInfoMap";
+ return { {}, {} };
+ }
+ }
+
+ size = cs->binarySize(data);
+ std::vector<uint8_t> listData(size);
+ ByteStreamBuffer buffer(listData.data(), listData.size());
+ ret = cs->serialize(data, buffer);
+
+ if (ret < 0 || buffer.overflow()) {
+ LOG(IPADataSerializer, Error) << "Failed to serialize ControlList";
+ return { {}, {} };
+ }
+
+ std::vector<uint8_t> dataVec;
+ dataVec.reserve(8 + infoData.size() + listData.size());
+ appendPOD<uint32_t>(dataVec, infoData.size());
+ appendPOD<uint32_t>(dataVec, listData.size());
+ dataVec.insert(dataVec.end(), infoData.begin(), infoData.end());
+ dataVec.insert(dataVec.end(), listData.begin(), listData.end());
+
+ return { dataVec, {} };
+}
+
+template<>
+ControlList
+IPADataSerializer<ControlList>::deserialize(std::vector<uint8_t>::const_iterator dataBegin,
+ std::vector<uint8_t>::const_iterator dataEnd,
+ ControlSerializer *cs)
+{
+ if (!cs)
+ LOG(IPADataSerializer, Fatal)
+ << "ControlSerializer not provided for deserialization of ControlList";
+
+ if (std::distance(dataBegin, dataEnd) < 8)
+ return {};
+
+ uint32_t infoDataSize = readPOD<uint32_t>(dataBegin, 0, dataEnd);
+ uint32_t listDataSize = readPOD<uint32_t>(dataBegin, 4, dataEnd);
+
+ std::vector<uint8_t>::const_iterator it = dataBegin + 8;
+
+ if (infoDataSize + listDataSize < infoDataSize ||
+ static_cast<uint32_t>(std::distance(it, dataEnd)) < infoDataSize + listDataSize)
+ return {};
+
+ if (infoDataSize > 0) {
+ ByteStreamBuffer buffer(&*it, infoDataSize);
+ ControlInfoMap map = cs->deserialize<ControlInfoMap>(buffer);
+ /* It's fine if map is empty. */
+ if (buffer.overflow()) {
+ LOG(IPADataSerializer, Error)
+ << "Failed to deserialize ControlLists's ControlInfoMap: buffer overflow";
+ return ControlList();
+ }
+ }
+
+ it += infoDataSize;
+ ByteStreamBuffer buffer(&*it, listDataSize);
+ ControlList list = cs->deserialize<ControlList>(buffer);
+ if (buffer.overflow())
+ LOG(IPADataSerializer, Error) << "Failed to deserialize ControlList: buffer overflow";
+
+ return list;
+}
+
+template<>
+ControlList
+IPADataSerializer<ControlList>::deserialize(const std::vector<uint8_t> &data,
+ ControlSerializer *cs)
+{
+ return deserialize(data.cbegin(), data.end(), cs);
+}
+
+template<>
+ControlList
+IPADataSerializer<ControlList>::deserialize(const std::vector<uint8_t> &data,
+ [[maybe_unused]] const std::vector<int32_t> &fds,
+ ControlSerializer *cs)
+{
+ return deserialize(data.cbegin(), data.end(), cs);
+}
+
+template<>
+ControlList
+IPADataSerializer<ControlList>::deserialize(std::vector<uint8_t>::const_iterator dataBegin,
+ std::vector<uint8_t>::const_iterator dataEnd,
+ [[maybe_unused]] std::vector<int32_t>::const_iterator fdsBegin,
+ [[maybe_unused]] std::vector<int32_t>::const_iterator fdsEnd,
+ ControlSerializer *cs)
+{
+ return deserialize(dataBegin, dataEnd, cs);
+}
+
+/*
+ * const ControlInfoMap is serialized as:
+ *
+ * 4 bytes - uint32_t Size of serialized ControlInfoMap, in bytes
+ * X bytes - Serialized ControlInfoMap (using ControlSerializer)
+ */
+template<>
+std::tuple<std::vector<uint8_t>, std::vector<int32_t>>
+IPADataSerializer<ControlInfoMap>::serialize(const ControlInfoMap &map,
+ ControlSerializer *cs)
+{
+ if (!cs)
+ LOG(IPADataSerializer, Fatal)
+ << "ControlSerializer not provided for serialization of ControlInfoMap";
+
+ size_t size = cs->binarySize(map);
+ std::vector<uint8_t> infoData(size);
+ ByteStreamBuffer buffer(infoData.data(), infoData.size());
+ int ret = cs->serialize(map, buffer);
+
+ if (ret < 0 || buffer.overflow()) {
+ LOG(IPADataSerializer, Error) << "Failed to serialize ControlInfoMap";
+ return { {}, {} };
+ }
+
+ std::vector<uint8_t> dataVec;
+ appendPOD<uint32_t>(dataVec, infoData.size());
+ dataVec.insert(dataVec.end(), infoData.begin(), infoData.end());
+
+ return { dataVec, {} };
+}
+
+template<>
+ControlInfoMap
+IPADataSerializer<ControlInfoMap>::deserialize(std::vector<uint8_t>::const_iterator dataBegin,
+ std::vector<uint8_t>::const_iterator dataEnd,
+ ControlSerializer *cs)
+{
+ if (!cs)
+ LOG(IPADataSerializer, Fatal)
+ << "ControlSerializer not provided for deserialization of ControlInfoMap";
+
+ if (std::distance(dataBegin, dataEnd) < 4)
+ return {};
+
+ uint32_t infoDataSize = readPOD<uint32_t>(dataBegin, 0, dataEnd);
+
+ std::vector<uint8_t>::const_iterator it = dataBegin + 4;
+
+ if (static_cast<uint32_t>(std::distance(it, dataEnd)) < infoDataSize)
+ return {};
+
+ ByteStreamBuffer buffer(&*it, infoDataSize);
+ ControlInfoMap map = cs->deserialize<ControlInfoMap>(buffer);
+
+ return map;
+}
+
+template<>
+ControlInfoMap
+IPADataSerializer<ControlInfoMap>::deserialize(const std::vector<uint8_t> &data,
+ ControlSerializer *cs)
+{
+ return deserialize(data.cbegin(), data.end(), cs);
+}
+
+template<>
+ControlInfoMap
+IPADataSerializer<ControlInfoMap>::deserialize(const std::vector<uint8_t> &data,
+ [[maybe_unused]] const std::vector<int32_t> &fds,
+ ControlSerializer *cs)
+{
+ return deserialize(data.cbegin(), data.end(), cs);
+}
+
+template<>
+ControlInfoMap
+IPADataSerializer<ControlInfoMap>::deserialize(std::vector<uint8_t>::const_iterator dataBegin,
+ std::vector<uint8_t>::const_iterator dataEnd,
+ [[maybe_unused]] std::vector<int32_t>::const_iterator fdsBegin,
+ [[maybe_unused]] std::vector<int32_t>::const_iterator fdsEnd,
+ ControlSerializer *cs)
+{
+ return deserialize(dataBegin, dataEnd, cs);
+}
+
+/*
+ * FileDescriptors are serialized into a single byte that tells if the
+ * FileDescriptor is valid or not. If it is valid, then for serialization
+ * the fd will be written to the fd vector, or for deserialization the
+ * fd vector const_iterator will be valid.
+ *
+ * This validity is necessary so that we don't send -1 fd over sendmsg(). It
+ * also allows us to simply send the entire fd vector into the deserializer
+ * and it will be recursively consumed as necessary.
+ *
+ * \todo Consider serializing the FileDescriptor in 4 bytes to ensure
+ * 32-bit alignment of all serialized data
+ */
+template<>
+std::tuple<std::vector<uint8_t>, std::vector<int32_t>>
+IPADataSerializer<FileDescriptor>::serialize(const FileDescriptor &data,
+ [[maybe_unused]] ControlSerializer *cs)
+{
+ std::vector<uint8_t> dataVec = { data.isValid() };
+ std::vector<int32_t> fdVec;
+ if (data.isValid())
+ fdVec.push_back(data.fd());
+
+ return { dataVec, fdVec };
+}
+
+template<>
+FileDescriptor IPADataSerializer<FileDescriptor>::deserialize(std::vector<uint8_t>::const_iterator dataBegin,
+ std::vector<uint8_t>::const_iterator dataEnd,
+ std::vector<int32_t>::const_iterator fdsBegin,
+ std::vector<int32_t>::const_iterator fdsEnd,
+ [[maybe_unused]] ControlSerializer *cs)
+{
+ ASSERT(std::distance(dataBegin, dataEnd) >= 1);
+
+ bool valid = !!(*dataBegin);
+
+ ASSERT(!(valid && std::distance(fdsBegin, fdsEnd) < 1));
+
+ return valid ? FileDescriptor(*fdsBegin) : FileDescriptor();
+}
+
+template<>
+FileDescriptor IPADataSerializer<FileDescriptor>::deserialize(const std::vector<uint8_t> &data,
+ const std::vector<int32_t> &fds,
+ [[maybe_unused]] ControlSerializer *cs)
+{
+ return deserialize(data.cbegin(), data.end(), fds.cbegin(), fds.end());
+}
+
+/*
+ * FrameBuffer::Plane is serialized as:
+ *
+ * 1 byte - FileDescriptor
+ * 4 bytes - uint32_t Length
+ */
+template<>
+std::tuple<std::vector<uint8_t>, std::vector<int32_t>>
+IPADataSerializer<FrameBuffer::Plane>::serialize(const FrameBuffer::Plane &data,
+ [[maybe_unused]] ControlSerializer *cs)
+{
+ std::vector<uint8_t> dataVec;
+ std::vector<int32_t> fdsVec;
+
+ std::vector<uint8_t> fdBuf;
+ std::vector<int32_t> fdFds;
+ std::tie(fdBuf, fdFds) =
+ IPADataSerializer<FileDescriptor>::serialize(data.fd);
+ dataVec.insert(dataVec.end(), fdBuf.begin(), fdBuf.end());
+ fdsVec.insert(fdsVec.end(), fdFds.begin(), fdFds.end());
+
+ appendPOD<uint32_t>(dataVec, data.length);
+
+ return { dataVec, fdsVec };
+}
+
+template<>
+FrameBuffer::Plane
+IPADataSerializer<FrameBuffer::Plane>::deserialize(std::vector<uint8_t>::const_iterator dataBegin,
+ std::vector<uint8_t>::const_iterator dataEnd,
+ std::vector<int32_t>::const_iterator fdsBegin,
+ [[maybe_unused]] std::vector<int32_t>::const_iterator fdsEnd,
+ [[maybe_unused]] ControlSerializer *cs)
+{
+ FrameBuffer::Plane ret;
+
+ ret.fd = IPADataSerializer<FileDescriptor>::deserialize(dataBegin, dataBegin + 1,
+ fdsBegin, fdsBegin + 1);
+ ret.length = readPOD<uint32_t>(dataBegin, 1, dataEnd);
+
+ return ret;
+}
+
+template<>
+FrameBuffer::Plane
+IPADataSerializer<FrameBuffer::Plane>::deserialize(const std::vector<uint8_t> &data,
+ const std::vector<int32_t> &fds,
+ ControlSerializer *cs)
+{
+ return deserialize(data.cbegin(), data.end(), fds.cbegin(), fds.end(), cs);
+}
+
+#endif /* __DOXYGEN__ */
+
+} /* namespace libcamera */