/* -*- 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 "ipc/EnumSerializer.h" #include "mozilla/gfx/2D.h" #include "mozilla/Assertions.h" #include "mozilla/IntegerRange.h" #include "mozilla/ipc/ProtocolUtils.h" #include "mozilla/ipc/SharedMemoryBasic.h" #include "mozilla/ipc/Shmem.h" #include "mozilla/Logging.h" #include "mozilla/TimeStamp.h" #include "mozilla/TypeTraits.h" #include "nsExceptionHandler.h" #include "nsString.h" #include "WebGLTypes.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_same::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; } }; template inline Range AsRange(T* const begin, T* const end) { const auto size = MaybeAs(end - begin); MOZ_RELEASE_ASSERT(size); return {begin, *size}; } /** * 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) {} template QueueStatus WriteFromRange(const Range& src) { if (!mStatus) return mStatus; mProducer->WriteFromRange(src); return mStatus; } /** * Copy bytes from aBuffer to the producer if there is enough room. * aBufferSize must not be 0. */ template inline QueueStatus Write(const T* begin, const T* end) { MOZ_RELEASE_ASSERT(begin <= end); if (!mStatus) return mStatus; WriteFromRange(AsRange(begin, end)); return mStatus; } template inline QueueStatus WritePod(const T& in) { static_assert(std::is_trivially_copyable_v); const auto begin = reinterpret_cast(&in); return Write(begin, begin + sizeof(T)); } /** * 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) */ template inline QueueStatus Read(T* const destBegin, T* const destEnd) { MOZ_ASSERT(destBegin); MOZ_RELEASE_ASSERT(destBegin <= destEnd); if (!mStatus) return mStatus; const auto dest = AsRange(destBegin, destEnd); const auto view = ReadRange(dest.length()); if (!view) return mStatus; const auto byteSize = ByteSize(dest); if (byteSize) { memcpy(dest.begin().get(), view->begin().get(), byteSize); } return mStatus; } /// Return a view wrapping the shmem. template inline Maybe> ReadRange(const size_t elemCount) { if (!mStatus) return {}; const auto view = mConsumer->template ReadRange(elemCount); if (!view) { mStatus = QueueStatus::kTooSmall; } return view; } template inline QueueStatus ReadPod(T* out) { static_assert(std::is_trivially_copyable_v); const auto begin = reinterpret_cast(out); return Read(begin, begin + sizeof(T)); } /** * 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; }; // --------------------------------------------------------------- /** * 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); } }; // --------------------------------------------------------------- template <> struct QueueParamTraits { using ParamType = bool; template static QueueStatus Write(ProducerView& aProducerView, const ParamType& aArg) { uint8_t temp = aArg ? 1 : 0; return aProducerView.WriteParam(temp); } template static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { uint8_t temp; if (IsSuccess(aConsumerView.ReadParam(&temp))) { MOZ_ASSERT(temp == 1 || temp == 0); *aArg = temp ? true : false; } return aConsumerView.GetStatus(); } }; // --------------------------------------------------------------- // 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 QueueStatus Write(ProducerView& aProducerView, const ParamType& aValue) { MOZ_RELEASE_ASSERT(EnumValidator::IsLegalValue(aValue)); return aProducerView.WriteParam(DataType(aValue)); } template static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aResult) { DataType value; if (!aConsumerView.ReadParam(&value)) { CrashReporter::AnnotateCrashReport( CrashReporter::Annotation::IPCReadErrorReason, "Bad iter"_ns); return aConsumerView.GetStatus(); } if (!EnumValidator::IsLegalValue(ParamType(value))) { CrashReporter::AnnotateCrashReport( CrashReporter::Annotation::IPCReadErrorReason, "Illegal value"_ns); return QueueStatus::kFatalError; } *aResult = ParamType(value); return QueueStatus::kSuccess; } }; 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 = webgl::TexUnpackBlobDesc; template static QueueStatus Write(ProducerView& view, const ParamType& in) { MOZ_RELEASE_ASSERT(!in.image); const bool isDataSurf = bool(in.dataSurf); if (!view.WriteParam(in.imageTarget) || !view.WriteParam(in.size) || !view.WriteParam(in.srcAlphaType) || !view.WriteParam(in.unpacking) || !view.WriteParam(in.cpuData) || !view.WriteParam(in.pboOffset) || !view.WriteParam(in.imageSize) || !view.WriteParam(in.sd) || !view.WriteParam(isDataSurf)) { return view.GetStatus(); } if (isDataSurf) { const auto& surf = in.dataSurf; gfx::DataSourceSurface::ScopedMap map(surf, gfx::DataSourceSurface::READ); if (!map.IsMapped()) { return QueueStatus::kOOMError; } const auto& surfSize = surf->GetSize(); const auto stride = *MaybeAs(map.GetStride()); if (!view.WriteParam(surfSize) || !view.WriteParam(surf->GetFormat()) || !view.WriteParam(stride)) { return view.GetStatus(); } const size_t dataSize = stride * surfSize.height; const auto& begin = map.GetData(); if (!view.Write(begin, begin + dataSize)) { return view.GetStatus(); } } return QueueStatus::kSuccess; } template static QueueStatus Read(ConsumerView& view, ParamType* const out) { bool isDataSurf; if (!view.ReadParam(&out->imageTarget) || !view.ReadParam(&out->size) || !view.ReadParam(&out->srcAlphaType) || !view.ReadParam(&out->unpacking) || !view.ReadParam(&out->cpuData) || !view.ReadParam(&out->pboOffset) || !view.ReadParam(&out->imageSize) || !view.ReadParam(&out->sd) || !view.ReadParam(&isDataSurf)) { return view.GetStatus(); } if (isDataSurf) { gfx::IntSize surfSize; gfx::SurfaceFormat format; size_t stride; if (!view.ReadParam(&surfSize) || !view.ReadParam(&format) || !view.ReadParam(&stride)) { return view.GetStatus(); } auto& surf = out->dataSurf; surf = gfx::Factory::CreateDataSourceSurfaceWithStride(surfSize, format, stride, true); if (!surf) { return QueueStatus::kOOMError; } gfx::DataSourceSurface::ScopedMap map(surf, gfx::DataSourceSurface::WRITE); if (!map.IsMapped()) { return QueueStatus::kOOMError; } const size_t dataSize = stride * surfSize.height; const auto& begin = map.GetData(); if (!view.Read(begin, begin + dataSize)) { return view.GetStatus(); } } return QueueStatus::kSuccess; } }; // --------------------------------------------------------------- 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_