/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: sw=2 ts=4 et : */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef _QUEUEPARAMTRAITS_H_ #define _QUEUEPARAMTRAITS_H_ 1 #include "mozilla/ipc/SharedMemoryBasic.h" #include "mozilla/Assertions.h" #include "mozilla/IntegerRange.h" #include "mozilla/ipc/Shmem.h" #include "mozilla/ipc/ProtocolUtils.h" #include "mozilla/Logging.h" #include "mozilla/TimeStamp.h" #include "mozilla/TypeTraits.h" #include "nsString.h" namespace mozilla { namespace webgl { enum class QueueStatus { // Operation was successful kSuccess, // The operation failed because the queue isn't ready for it. // Either the queue is too full for an insert or too empty for a remove. // The operation may succeed if retried. kNotReady, // The operation required more room than the queue supports. // It should not be retried -- it will always fail. kTooSmall, // The operation failed for some reason that is unrecoverable. // All values below this value indicate a fata error. kFatalError, // Fatal error: Internal processing ran out of memory. This is likely e.g. // during de-serialization. kOOMError, }; inline bool IsSuccess(QueueStatus status) { return status == QueueStatus::kSuccess; } inline bool operator!(const QueueStatus status) { return !IsSuccess(status); } template struct RemoveCVR { typedef typename std::remove_reference::type>::type Type; }; inline size_t UsedBytes(size_t aQueueBufferSize, size_t aRead, size_t aWrite) { return (aRead <= aWrite) ? aWrite - aRead : (aQueueBufferSize - aRead) + aWrite; } inline size_t FreeBytes(size_t aQueueBufferSize, size_t aRead, size_t aWrite) { // Remember, queueSize is queueBufferSize-1 return (aQueueBufferSize - 1) - UsedBytes(aQueueBufferSize, aRead, aWrite); } template struct IsTriviallySerializable : public std::integral_constant::value || std::is_arithmetic::value> {}; /** * QueueParamTraits provide the user with a way to implement PCQ argument * (de)serialization. It uses a PcqView, which permits the system to * abandon all changes to the underlying PCQ if any operation fails. * * The transactional nature of PCQ operations make the ideal behavior a bit * complex. Since the PCQ has a fixed amount of memory available to it, * TryInsert operations operations are expected to sometimes fail and be * re-issued later. We want these failures to be inexpensive. The same * goes for TryRemove, which fails when there isn't enough data in * the queue yet for them to complete. * * Their expected interface is: * * template<> struct QueueParamTraits::Type> { * // Write data from aArg into the PCQ. * static QueueStatus Write(ProducerView& aProducerView, const Arg& aArg) * {...}; * * // Read data from the PCQ into aArg, or just skip the data if aArg is null. * static QueueStatus Read(ConsumerView& aConsumerView, Arg* aArg) {...} * }; */ template struct QueueParamTraits; // Todo: s/QueueParamTraits/SizedParamTraits/ /** * The marshaller handles all data insertion into the queue. */ class Marshaller { public: static QueueStatus WriteObject(uint8_t* aQueue, size_t aQueueBufferSize, size_t aRead, size_t* aWrite, const void* aArg, size_t aArgLength) { const uint8_t* buf = reinterpret_cast(aArg); if (FreeBytes(aQueueBufferSize, aRead, *aWrite) < aArgLength) { return QueueStatus::kNotReady; } if (*aWrite + aArgLength <= aQueueBufferSize) { memcpy(aQueue + *aWrite, buf, aArgLength); } else { size_t firstLen = aQueueBufferSize - *aWrite; memcpy(aQueue + *aWrite, buf, firstLen); memcpy(aQueue, &buf[firstLen], aArgLength - firstLen); } *aWrite = (*aWrite + aArgLength) % aQueueBufferSize; return QueueStatus::kSuccess; } static QueueStatus ReadObject(const uint8_t* aQueue, size_t aQueueBufferSize, size_t* aRead, size_t aWrite, void* aArg, size_t aArgLength) { if (UsedBytes(aQueueBufferSize, *aRead, aWrite) < aArgLength) { return QueueStatus::kNotReady; } if (aArg) { uint8_t* buf = reinterpret_cast(aArg); if (*aRead + aArgLength <= aQueueBufferSize) { memcpy(buf, aQueue + *aRead, aArgLength); } else { size_t firstLen = aQueueBufferSize - *aRead; memcpy(buf, aQueue + *aRead, firstLen); memcpy(&buf[firstLen], aQueue, aArgLength - firstLen); } } *aRead = (*aRead + aArgLength) % aQueueBufferSize; return QueueStatus::kSuccess; } }; /** * Used to give QueueParamTraits a way to write to the Producer without * actually altering it, in case the transaction fails. * THis object maintains the error state of the transaction and * discards commands issued after an error is encountered. */ template class ProducerView { public: using Producer = _Producer; ProducerView(Producer* aProducer, size_t aRead, size_t* aWrite) : mProducer(aProducer), mRead(aRead), mWrite(aWrite), mStatus(QueueStatus::kSuccess) {} /** * Copy bytes from aBuffer to the producer if there is enough room. * aBufferSize must not be 0. */ inline QueueStatus Write(const uint8_t* begin, const uint8_t* end); template inline QueueStatus Write(const T* begin, const T* end) { return Write(reinterpret_cast(begin), reinterpret_cast(end)); } /** * Serialize aArg using Arg's QueueParamTraits. */ template QueueStatus WriteParam(const Arg& aArg) { return mozilla::webgl::QueueParamTraits< typename RemoveCVR::Type>::Write(*this, aArg); } QueueStatus GetStatus() { return mStatus; } private: Producer* mProducer; size_t mRead; size_t* mWrite; QueueStatus mStatus; }; /** * Used to give QueueParamTraits a way to read from the Consumer without * actually altering it, in case the transaction fails. */ template class ConsumerView { public: using Consumer = _Consumer; ConsumerView(Consumer* aConsumer, size_t* aRead, size_t aWrite) : mConsumer(aConsumer), mRead(aRead), mWrite(aWrite), mStatus(QueueStatus::kSuccess) {} /** * Read bytes from the consumer if there is enough data. aBuffer may * be null (in which case the data is skipped) */ inline QueueStatus Read(uint8_t* begin, uint8_t* end); template inline QueueStatus Read(T* begin, T* end) { return Read(reinterpret_cast(begin), reinterpret_cast(end)); } /** * Deserialize aArg using Arg's QueueParamTraits. * If the return value is not Success then aArg is not changed. */ template QueueStatus ReadParam(Arg* aArg) { MOZ_ASSERT(aArg); return mozilla::webgl::QueueParamTraits>::Read(*this, aArg); } QueueStatus GetStatus() { return mStatus; } private: Consumer* mConsumer; size_t* mRead; size_t mWrite; QueueStatus mStatus; }; template QueueStatus ProducerView::Write(const uint8_t* begin, const uint8_t* end) { MOZ_ASSERT(begin); MOZ_ASSERT(begin < end); if (IsSuccess(mStatus)) { mStatus = mProducer->WriteObject(mRead, mWrite, begin, end - begin); } return mStatus; } template QueueStatus ConsumerView::Read(uint8_t* begin, uint8_t* end) { MOZ_ASSERT(begin < end); if (IsSuccess(mStatus)) { mStatus = mConsumer->ReadObject(mRead, mWrite, begin, end - begin); } return mStatus; } // --------------------------------------------------------------- /** * True for types that can be (de)serialized by memcpy. */ template struct QueueParamTraits { template static QueueStatus Write(ProducerView& aProducerView, const Arg& aArg) { static_assert(mozilla::webgl::template IsTriviallySerializable::value, "No QueueParamTraits specialization was found for this type " "and it does not satisfy IsTriviallySerializable."); // Write self as binary const auto begin = &aArg; return aProducerView.Write(begin, begin + 1); } template static QueueStatus Read(ConsumerView& aConsumerView, Arg* aArg) { static_assert(mozilla::webgl::template IsTriviallySerializable::value, "No QueueParamTraits specialization was found for this type " "and it does not satisfy IsTriviallySerializable."); // Read self as binary return aConsumerView.Read(aArg, aArg + 1); } }; // --------------------------------------------------------------- // Adapted from IPC::EnumSerializer, this class safely handles enum values, // validating that they are in range using the same EnumValidators as IPDL // (namely ContiguousEnumValidator and ContiguousEnumValidatorInclusive). template struct EnumSerializer { typedef E ParamType; typedef typename std::underlying_type::type DataType; template static auto Write(ProducerView& aProducerView, const ParamType& aValue) { MOZ_RELEASE_ASSERT(EnumValidator::IsLegalValue(aValue)); return aProducerView.WriteParam(DataType(aValue)); } template static bool Read(ConsumerView& aConsumerView, ParamType* aResult) { DataType value; if (!aConsumerView.ReadParam(&value)) { CrashReporter::AnnotateCrashReport( CrashReporter::Annotation::IPCReadErrorReason, "Bad iter"_ns); return IsSuccess(aConsumerView.GetStatus()); } if (!EnumValidator::IsLegalValue(ParamType(value))) { CrashReporter::AnnotateCrashReport( CrashReporter::Annotation::IPCReadErrorReason, "Illegal value"_ns); return IsSuccess(aConsumerView.GetStatus()); } *aResult = ParamType(value); return true; } }; using IPC::ContiguousEnumValidator; using IPC::ContiguousEnumValidatorInclusive; template struct ContiguousEnumSerializer : EnumSerializer> {}; template struct ContiguousEnumSerializerInclusive : EnumSerializer> { }; // --------------------------------------------------------------- template <> struct QueueParamTraits : public ContiguousEnumSerializerInclusive< QueueStatus, QueueStatus::kSuccess, QueueStatus::kOOMError> {}; // --------------------------------------------------------------- template <> struct QueueParamTraits { using ParamType = nsACString; template static QueueStatus Write(ProducerView& aProducerView, const ParamType& aArg) { if ((!aProducerView.WriteParam(aArg.IsVoid())) || aArg.IsVoid()) { return aProducerView.GetStatus(); } uint32_t len = aArg.Length(); if ((!aProducerView.WriteParam(len)) || (len == 0)) { return aProducerView.GetStatus(); } return aProducerView.Write(aArg.BeginReading(), len); } template static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { bool isVoid = false; if (!IsSuccess(aConsumerView.ReadParam(&isVoid))) { return aConsumerView.GetStatus(); } aArg->SetIsVoid(isVoid); if (isVoid) { return QueueStatus::kSuccess; } uint32_t len = 0; if (!IsSuccess(aConsumerView.ReadParam(&len))) { return aConsumerView.GetStatus(); } if (len == 0) { *aArg = ""; return QueueStatus::kSuccess; } char* buf = new char[len + 1]; if (!buf) { return QueueStatus::kOOMError; } if (!IsSuccess(aConsumerView.Read(buf, len))) { return aConsumerView.GetStatus(); } buf[len] = '\0'; aArg->Adopt(buf, len); return QueueStatus::kSuccess; } }; template <> struct QueueParamTraits { using ParamType = nsAString; template static QueueStatus Write(ProducerView& aProducerView, const ParamType& aArg) { if ((!aProducerView.WriteParam(aArg.IsVoid())) || (aArg.IsVoid())) { return aProducerView.GetStatus(); } // DLP: No idea if this includes null terminator uint32_t len = aArg.Length(); if ((!aProducerView.WriteParam(len)) || (len == 0)) { return aProducerView.GetStatus(); } constexpr const uint32_t sizeofchar = sizeof(typename ParamType::char_type); return aProducerView.Write(aArg.BeginReading(), len * sizeofchar); } template static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { bool isVoid = false; if (!aConsumerView.ReadParam(&isVoid)) { return aConsumerView.GetStatus(); } aArg->SetIsVoid(isVoid); if (isVoid) { return QueueStatus::kSuccess; } // DLP: No idea if this includes null terminator uint32_t len = 0; if (!aConsumerView.ReadParam(&len)) { return aConsumerView.GetStatus(); } if (len == 0) { *aArg = nsString(); return QueueStatus::kSuccess; } uint32_t sizeofchar = sizeof(typename ParamType::char_type); typename ParamType::char_type* buf = nullptr; buf = static_cast( malloc((len + 1) * sizeofchar)); if (!buf) { return QueueStatus::kOOMError; } if (!aConsumerView.Read(buf, len * sizeofchar)) { return aConsumerView.GetStatus(); } buf[len] = L'\0'; aArg->Adopt(buf, len); return QueueStatus::kSuccess; } }; template <> struct QueueParamTraits : public QueueParamTraits { using ParamType = nsCString; }; template <> struct QueueParamTraits : public QueueParamTraits { using ParamType = nsString; }; // --------------------------------------------------------------- template ::value> struct NSArrayQueueParamTraits; // For ElementTypes that are !IsTriviallySerializable template struct NSArrayQueueParamTraits, false> { using ElementType = _ElementType; using ParamType = nsTArray; template static QueueStatus Write(ProducerView& aProducerView, const ParamType& aArg) { aProducerView.WriteParam(aArg.Length()); for (auto& elt : aArg) { aProducerView.WriteParam(elt); } return aProducerView.GetStatus(); } template static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { size_t arrayLen; if (!aConsumerView.ReadParam(&arrayLen)) { return aConsumerView.GetStatus(); } if (!aArg->AppendElements(arrayLen, fallible)) { return QueueStatus::kOOMError; } for (auto i : IntegerRange(arrayLen)) { ElementType& elt = aArg->ElementAt(i); aConsumerView.ReadParam(elt); } return aConsumerView.GetStatus(); } }; // For ElementTypes that are IsTriviallySerializable template struct NSArrayQueueParamTraits, true> { using ElementType = _ElementType; using ParamType = nsTArray; // TODO: Are there alignment issues? template static QueueStatus Write(ProducerView& aProducerView, const ParamType& aArg) { size_t arrayLen = aArg.Length(); aProducerView.WriteParam(arrayLen); return aProducerView.Write(&aArg[0], aArg.Length() * sizeof(ElementType)); } template static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { size_t arrayLen; if (!aConsumerView.ReadParam(&arrayLen)) { return aConsumerView.GetStatus(); } if (!aArg->AppendElements(arrayLen, fallible)) { return QueueStatus::kOOMError; } return aConsumerView.Read(aArg->Elements(), arrayLen * sizeof(ElementType)); } }; template struct QueueParamTraits> : public NSArrayQueueParamTraits> { using ParamType = nsTArray; }; // --------------------------------------------------------------- template ::value> struct ArrayQueueParamTraits; // For ElementTypes that are !IsTriviallySerializable template struct ArrayQueueParamTraits, false> { using ElementType = _ElementType; using ParamType = Array; template static QueueStatus Write(ProducerView& aProducerView, const ParamType& aArg) { for (const auto& elt : aArg) { aProducerView.WriteParam(elt); } return aProducerView.GetStatus(); } template static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { for (auto& elt : *aArg) { aConsumerView.ReadParam(elt); } return aConsumerView.GetStatus(); } }; // For ElementTypes that are IsTriviallySerializable template struct ArrayQueueParamTraits, true> { using ElementType = _ElementType; using ParamType = Array; template static QueueStatus Write(ProducerView& aProducerView, const ParamType& aArg) { return aProducerView.Write(aArg.begin(), sizeof(ElementType[Length])); } template static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { return aConsumerView.Read(aArg->begin(), sizeof(ElementType[Length])); } }; template struct QueueParamTraits> : public ArrayQueueParamTraits> { using ParamType = Array; }; // --------------------------------------------------------------- template struct QueueParamTraits> { using ParamType = Maybe; template static QueueStatus Write(ProducerView& aProducerView, const ParamType& aArg) { aProducerView.WriteParam(static_cast(aArg)); return aArg ? aProducerView.WriteParam(aArg.ref()) : aProducerView.GetStatus(); } template static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { bool isSome; if (!aConsumerView.ReadParam(&isSome)) { return aConsumerView.GetStatus(); } if (!isSome) { aArg->reset(); return QueueStatus::kSuccess; } aArg->emplace(); return aConsumerView.ReadParam(aArg->ptr()); } }; // --------------------------------------------------------------- // Maybe needs special behavior since Variant is not default // constructable. The Variant's first type must be default constructible. template struct QueueParamTraits>> { using ParamType = Maybe>; template static QueueStatus Write(ProducerView& aProducerView, const ParamType& aArg) { aProducerView.WriteParam(aArg.mIsSome); return (aArg.mIsSome) ? aProducerView.WriteParam(aArg.ref()) : aProducerView.GetStatus(); } template static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { bool isSome; if (!aConsumerView.ReadParam(&isSome)) { return aConsumerView.GetStatus(); } if (!isSome) { aArg->reset(); return QueueStatus::kSuccess; } aArg->emplace(VariantType()); return aConsumerView.ReadParam(aArg->ptr()); } }; // --------------------------------------------------------------- template struct QueueParamTraits> { using ParamType = std::pair; template static QueueStatus Write(ProducerView& aProducerView, const ParamType& aArg) { aProducerView.WriteParam(aArg.first()); return aProducerView.WriteParam(aArg.second()); } template static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { aConsumerView.ReadParam(aArg->first()); return aConsumerView.ReadParam(aArg->second()); } }; // --------------------------------------------------------------- template struct QueueParamTraits> { using ParamType = UniquePtr; template static QueueStatus Write(ProducerView& aProducerView, const ParamType& aArg) { // TODO: Clean up move with PCQ aProducerView.WriteParam(!static_cast(aArg)); if (aArg && aProducerView.WriteParam(*aArg.get())) { const_cast(aArg).reset(); } return aProducerView.GetStatus(); } template static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { bool isNull; if (!aConsumerView.ReadParam(&isNull)) { return aConsumerView.GetStatus(); } if (isNull) { aArg->reset(nullptr); return QueueStatus::kSuccess; } T* obj = nullptr; obj = new T(); if (!obj) { return QueueStatus::kOOMError; } aArg->reset(obj); return aConsumerView.ReadParam(obj); } }; // --------------------------------------------------------------- template <> struct QueueParamTraits { using ParamType = mozilla::ipc::Shmem; template static QueueStatus Write(ProducerView& aProducerView, ParamType&& aParam) { if (!aProducerView.WriteParam( aParam.Id(mozilla::ipc::Shmem::PrivateIPDLCaller()))) { return aProducerView.GetStatus(); } aParam.RevokeRights(mozilla::ipc::Shmem::PrivateIPDLCaller()); aParam.forget(mozilla::ipc::Shmem::PrivateIPDLCaller()); } template static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aResult) { ParamType::id_t id; if (!aConsumerView.ReadParam(&id)) { return aConsumerView.GetStatus(); } mozilla::ipc::Shmem::SharedMemory* rawmem = aConsumerView.LookupSharedMemory(id); if (!rawmem) { return QueueStatus::kFatalError; } *aResult = mozilla::ipc::Shmem(mozilla::ipc::Shmem::PrivateIPDLCaller(), rawmem, id); return QueueStatus::kSuccess; } }; } // namespace webgl } // namespace mozilla #endif // _QUEUEPARAMTRAITS_H_