diff --git a/serialization/argumentswriter.cpp b/serialization/argumentswriter.cpp index 29d6284..3876d9b 100644 --- a/serialization/argumentswriter.cpp +++ b/serialization/argumentswriter.cpp @@ -1,1196 +1,1197 @@ #include "arguments.h" #include "arguments_p.h" #include "basictypeio.h" #include "malloccache.h" #include enum { StructAlignment = 8 }; static constexpr byte alignLog[9] = { 0, 0, 1, 0, 2, 0, 0, 0, 3 }; inline constexpr byte alignmentLog2(uint32 alignment) { // The following is not constexpr in C++14, and it hasn't triggered in ages // assert(alignment <= 8 && (alignment < 2 || alignLog[alignment] != 0)); return alignLog[alignment]; } class Arguments::Writer::Private { public: Private() : m_signaturePosition(0), m_data(reinterpret_cast(malloc(InitialDataCapacity))), m_dataCapacity(InitialDataCapacity), m_dataPosition(SignatureReservedSpace), m_nilArrayNesting(0) { m_signature.ptr = reinterpret_cast(m_data + 1); // reserve a byte for length prefix m_signature.length = 0; } Private(const Private &other); void operator=(const Private &other); void reserveData(uint32 size) { if (likely(size <= m_dataCapacity)) { return; } uint32 newCapacity = m_dataCapacity; do { newCapacity *= 2; } while (size > newCapacity); byte *const oldDataPointer = m_data; m_data = reinterpret_cast(realloc(m_data, newCapacity)); m_signature.ptr += m_data - oldDataPointer; m_dataCapacity = newCapacity; } bool insideVariant() { return !m_queuedData.empty(); } // We don't know how long a variant signature is when starting the variant, but we have to // insert the signature into the datastream before the data. For that reason, we need a // postprocessing pass to fix things up once the outermost variant is closed. // QueuedDataInfo stores enough information about data inside variants to be able to do // the patching up while respecting alignment and other requirements. struct QueuedDataInfo { constexpr QueuedDataInfo(byte alignment, byte size_) : alignmentExponent(alignmentLog2(alignment)), size(size_) {} byte alignment() const { return 1 << alignmentExponent; } byte alignmentExponent : 2; // powers of 2, so 1, 2, 4, 8 byte size : 6; // that's up to 63 enum SizeCode { LargestSize = 60, ArrayLengthField, ArrayLengthEndMark, VariantSignature }; }; // The parameter is not a QueuedDataInfo because the compiler doesn't seem to optimize away // QueuedDataInfo construction when insideVariant() is false, despite inlining. void maybeQueueData(byte alignment, byte size) { if (insideVariant()) { m_queuedData.push_back(QueuedDataInfo(alignment, size)); } } // Caution: does not ensure that enough space is available! void appendBulkData(chunk data) { // Align only the first of the back-to-back data chunks - otherwise, when storing values which // are 8 byte aligned, the second half of an element straddling a chunk boundary // (QueuedDataInfo::LargestSize == 60) would start at an 8-byte aligned position (so 64) // instead of 60 where we want it in order to just write a contiguous block of data. memcpy(m_data + m_dataPosition, data.ptr, data.length); m_dataPosition += data.length; if (insideVariant()) { for (uint32 l = data.length; l; ) { uint32 chunkSize = std::min(l, uint32(QueuedDataInfo::LargestSize)); m_queuedData.push_back(QueuedDataInfo(1, chunkSize)); l -= chunkSize; } } } void alignData(uint32 alignment) { if (insideVariant()) { m_queuedData.push_back(QueuedDataInfo(alignment, 0)); } zeroPad(m_data, alignment, &m_dataPosition); } uint32 m_dataElementsCountBeforeNilArray; uint32 m_dataPositionBeforeVariant; Nesting m_nesting; cstring m_signature; uint32 m_signaturePosition; byte *m_data; uint32 m_dataCapacity; uint32 m_dataPosition; int m_nilArrayNesting; std::vector m_fileDescriptors; Error m_error; enum { InitialDataCapacity = 512, // max signature length (255) + length prefix(1) + null terminator(1), rounded up to multiple of 8 // because that doesn't change alignment SignatureReservedSpace = 264 }; #ifdef WITH_DICT_ENTRY enum DictEntryState : byte { RequireBeginDictEntry = 0, InDictEntry, RequireEndDictEntry, AfterEndDictEntry }; #endif struct ArrayInfo { uint32 containedTypeBegin; // to rewind when reading the next element #ifdef WITH_DICT_ENTRY DictEntryState dictEntryState; uint32 lengthFieldPosition : 24; #else uint32 lengthFieldPosition; #endif }; struct VariantInfo { // a variant switches the currently parsed signature, so we // need to store the old signature and parse position. uint32 prevSignatureOffset; // relative to m_data uint32 prevSignaturePosition; }; struct StructInfo { uint32 containedTypeBegin; }; struct AggregateInfo { IoState aggregateType; // can be BeginArray, BeginDict, BeginStruct, BeginVariant union { ArrayInfo arr; VariantInfo var; StructInfo sct; }; }; // this keeps track of which aggregates we are currently in #ifdef HAVE_BOOST boost::small_vector m_aggregateStack; #else std::vector m_aggregateStack; #endif std::vector m_queuedData; }; thread_local static MallocCache allocCache; Arguments::Writer::Private::Private(const Private &other) { *this = other; } void Arguments::Writer::Private::operator=(const Private &other) { if (&other == this) { assert(false); // if this happens, the (internal) caller did something wrong return; } m_dataElementsCountBeforeNilArray = other.m_dataElementsCountBeforeNilArray; m_dataPositionBeforeVariant = other.m_dataPositionBeforeVariant; m_nesting = other.m_nesting; m_signature.ptr = other.m_signature.ptr; // ### still needs adjustment, done after allocating m_data m_signature.length = other.m_signature.length; m_signaturePosition = other.m_signaturePosition; m_dataCapacity = other.m_dataCapacity; m_dataPosition = other.m_dataPosition; // handle *m_data and the data it's pointing to m_data = reinterpret_cast(malloc(m_dataCapacity)); memcpy(m_data, other.m_data, m_dataPosition); m_signature.ptr += m_data - other.m_data; m_nilArrayNesting = other.m_nilArrayNesting; m_fileDescriptors = other.m_fileDescriptors; m_error = other.m_error; m_aggregateStack = other.m_aggregateStack; m_queuedData = other.m_queuedData; } Arguments::Writer::Writer() : d(new(allocCache.allocate()) Private), m_state(AnyData) { } Arguments::Writer::Writer(Writer &&other) : d(other.d), m_state(other.m_state), m_u(other.m_u) { other.d = nullptr; } void Arguments::Writer::operator=(Writer &&other) { if (&other == this) { return; } d = other.d; m_state = other.m_state; m_u = other.m_u; other.d = nullptr; } Arguments::Writer::Writer(const Writer &other) : d(nullptr), m_state(other.m_state), m_u(other.m_u) { if (other.d) { d = new(allocCache.allocate()) Private(*other.d); } } void Arguments::Writer::operator=(const Writer &other) { if (&other == this) { return; } m_state = other.m_state; m_u = other.m_u; if (d && other.d) { *d = *other.d; } else { Writer temp(other); std::swap(d, temp.d); } } Arguments::Writer::~Writer() { if (d) { free(d->m_data); d->m_data = nullptr; d->~Private(); allocCache.free(d); d = nullptr; } } bool Arguments::Writer::isValid() const { return !d->m_error.isError(); } Error Arguments::Writer::error() const { return d->m_error; } cstring Arguments::Writer::stateString() const { return printableState(m_state); } bool Arguments::Writer::isInsideEmptyArray() const { return d->m_nilArrayNesting > 0; } cstring Arguments::Writer::currentSignature() const { return d->m_signature; } uint32 Arguments::Writer::currentSignaturePosition() const { return d->m_signaturePosition; } void Arguments::Writer::doWritePrimitiveType(IoState type, uint32 alignAndSize) { d->reserveData(d->m_dataPosition + (alignAndSize << 1)); zeroPad(d->m_data, alignAndSize, &d->m_dataPosition); switch(type) { case Boolean: { uint32 num = m_u.Boolean ? 1 : 0; basic::writeUint32(d->m_data + d->m_dataPosition, num); break; } case Byte: d->m_data[d->m_dataPosition] = m_u.Byte; break; case Int16: basic::writeInt16(d->m_data + d->m_dataPosition, m_u.Int16); break; case Uint16: basic::writeUint16(d->m_data + d->m_dataPosition, m_u.Uint16); break; case Int32: basic::writeInt32(d->m_data + d->m_dataPosition, m_u.Int32); break; case Uint32: basic::writeUint32(d->m_data + d->m_dataPosition, m_u.Uint32); break; case Int64: basic::writeInt64(d->m_data + d->m_dataPosition, m_u.Int64); break; case Uint64: basic::writeUint64(d->m_data + d->m_dataPosition, m_u.Uint64); break; case Double: basic::writeDouble(d->m_data + d->m_dataPosition, m_u.Double); break; case UnixFd: { const uint32 index = d->m_fileDescriptors.size(); if (!d->m_nilArrayNesting) { d->m_fileDescriptors.push_back(m_u.Int32); } basic::writeUint32(d->m_data + d->m_dataPosition, index); break; } default: assert(false); VALID_IF(false, Error::InvalidType); } d->m_dataPosition += alignAndSize; d->maybeQueueData(alignAndSize, alignAndSize); } void Arguments::Writer::doWriteString(IoState type, uint32 lengthPrefixSize) { if (type == String) { VALID_IF(Arguments::isStringValid(cstring(m_u.String.ptr, m_u.String.length)), Error::InvalidString); } else if (type == ObjectPath) { VALID_IF(Arguments::isObjectPathValid(cstring(m_u.String.ptr, m_u.String.length)), Error::InvalidObjectPath); } else if (type == Signature) { VALID_IF(Arguments::isSignatureValid(cstring(m_u.String.ptr, m_u.String.length)), Error::InvalidSignature); } d->reserveData(d->m_dataPosition + (lengthPrefixSize << 1) + m_u.String.length + 1); zeroPad(d->m_data, lengthPrefixSize, &d->m_dataPosition); if (lengthPrefixSize == 1) { d->m_data[d->m_dataPosition] = m_u.String.length; } else { basic::writeUint32(d->m_data + d->m_dataPosition, m_u.String.length); } d->m_dataPosition += lengthPrefixSize; d->maybeQueueData(lengthPrefixSize, lengthPrefixSize); d->appendBulkData(chunk(m_u.String.ptr, m_u.String.length + 1)); } void Arguments::Writer::advanceState(cstring signatureFragment, IoState newState) { // what needs to happen here: // - if we are in an existing portion of the signature (like writing the >1st iteration of an array) // check if the type to be written is the same as the one that's already in the signature // - otherwise we still need to check if the data we're adding conforms with the spec, e.g. // no empty structs, dict entries must have primitive key type and exactly one value type // - check well-formedness of data: strings, maximum serialized array length and message length // (variant signature length only being known after finishing a variant introduces uncertainty // of final data stream size - due to alignment padding, a variant signature longer by one can // cause an up to seven bytes longer message. in other cases it won't change message length at all.) // - increase size of data buffer when it gets too small // - store information about variants and arrays, in order to: // - know what the final binary message size will be // - in finish(), create the final data stream with inline variant signatures and array lengths if (unlikely(m_state == InvalidData)) { return; } // can't do the following because a dict is one aggregate in our counting, but two according to // the spec: an array (one) containing dict entries (two) // assert(d->m_nesting.total() == d->m_aggregateStack.size()); assert((d->m_nesting.total() == 0) == d->m_aggregateStack.empty()); m_state = AnyData; uint32 alignment = 1; bool isPrimitiveType = false; bool isStringType = false; if (signatureFragment.length) { const TypeInfo ty = typeInfo(signatureFragment.ptr[0]); alignment = ty.alignment; isPrimitiveType = ty.isPrimitive; isStringType = ty.isString; } bool isWritingSignature = d->m_signaturePosition == d->m_signature.length; if (isWritingSignature) { // signature additions must conform to syntax VALID_IF(d->m_signaturePosition + signatureFragment.length <= MaxSignatureLength, Error::SignatureTooLong); } if (!d->m_aggregateStack.empty()) { Private::AggregateInfo &aggregateInfo = d->m_aggregateStack.back(); switch (aggregateInfo.aggregateType) { case BeginVariant: // arrays and variants may contain just one single complete type; note that this will // trigger only when not inside an aggregate inside the variant or (see below) array if (d->m_signaturePosition >= 1) { VALID_IF(newState == EndVariant, Error::NotSingleCompleteTypeInVariant); } break; case BeginArray: if (d->m_signaturePosition >= aggregateInfo.arr.containedTypeBegin + 1 && newState != EndArray) { // we are not at start of contained type's signature, the array is at top of stack // -> we are at the end of the single complete type inside the array, start the next // entry. TODO: check compatibility (essentially what's in the else branch below) d->m_signaturePosition = aggregateInfo.arr.containedTypeBegin; isWritingSignature = false; } break; case BeginDict: if (d->m_signaturePosition == aggregateInfo.arr.containedTypeBegin) { #ifdef WITH_DICT_ENTRY if (aggregateInfo.arr.dictEntryState == Private::RequireBeginDictEntry) { // This is only reached immediately after beginDict() so it's kinda wasteful, oh well. VALID_IF(newState == BeginDictEntry, Error::MissingBeginDictEntry); aggregateInfo.arr.dictEntryState = Private::InDictEntry; m_state = DictKey; return; // BeginDictEntry writes no data } #endif VALID_IF(isPrimitiveType || isStringType, Error::InvalidKeyTypeInDict); } #ifdef WITH_DICT_ENTRY // TODO test this part of the state machine if (d->m_signaturePosition >= aggregateInfo.arr.containedTypeBegin + 2) { if (aggregateInfo.arr.dictEntryState == Private::RequireEndDictEntry) { VALID_IF(newState == EndDictEntry, Error::MissingEndDictEntry); aggregateInfo.arr.dictEntryState = Private::AfterEndDictEntry; m_state = BeginDictEntry; return; // EndDictEntry writes no data } else { // v should've been caught earlier assert(aggregateInfo.arr.dictEntryState == Private::AfterEndDictEntry); VALID_IF(newState == BeginDictEntry || newState == EndDict, Error::MissingBeginDictEntry); // "fall through", the rest (another iteration or finish) is handled below } } else if (d->m_signaturePosition >= aggregateInfo.arr.containedTypeBegin + 1) { assert(aggregateInfo.arr.dictEntryState == Private::InDictEntry); aggregateInfo.arr.dictEntryState = Private::RequireEndDictEntry; // Setting EndDictEntry after writing a primitive type works fine, but setting it after // ending another aggregate would be somewhat involved and need to happen somewhere // else, so just don't do that. We still produce an error when endDictEntry() is not // used correctly. // m_state = EndDictEntry; // continue and write the dict entry's value } #endif // first type has been checked already, second must be present (checked in EndDict // state handler). no third type allowed. if (d->m_signaturePosition >= aggregateInfo.arr.containedTypeBegin + 2 && newState != EndDict) { // align to dict entry d->alignData(StructAlignment); d->m_signaturePosition = aggregateInfo.arr.containedTypeBegin; isWritingSignature = false; m_state = DictKey; #ifdef WITH_DICT_ENTRY assert(newState == BeginDictEntry); aggregateInfo.arr.dictEntryState = Private::InDictEntry; return; // BeginDictEntry writes no data #endif } break; default: break; } } if (isWritingSignature) { // extend the signature for (uint32 i = 0; i < signatureFragment.length; i++) { d->m_signature.ptr[d->m_signaturePosition++] = signatureFragment.ptr[i]; } d->m_signature.length += signatureFragment.length; } else { // Do not try to prevent several iterations through a nil array. Two reasons: // - We may be writing a nil array in the >1st iteration of a non-nil outer array. // This would need to be distinguished from just iterating through a nil array // several times. Which is well possible. We don't bother with that because... // - As a QtDBus unittest illustrates, somebody may choose to serialize a fixed length // series of data elements as an array (instead of struct), so that a trivial // serialization of such data just to fill in type information in an outer empty array // would end up iterating through the inner, implicitly empty array several times. // All in all it is just not much of a benefit to be strict, so don't. //VALID_IF(likely(!d->m_nilArrayNesting), Error::ExtraIterationInEmptyArray); // signature must match first iteration (of an array/dict) VALID_IF(d->m_signaturePosition + signatureFragment.length <= d->m_signature.length, Error::TypeMismatchInSubsequentArrayIteration); // TODO need to apply special checks for state changes with no explicit signature char? // (end of array, end of variant) for (uint32 i = 0; i < signatureFragment.length; i++) { VALID_IF(d->m_signature.ptr[d->m_signaturePosition++] == signatureFragment.ptr[i], Error::TypeMismatchInSubsequentArrayIteration); } } if (isPrimitiveType) { doWritePrimitiveType(newState, alignment); return; } if (isStringType) { // In case of nil array, skip writing to make sure that the input string (which is explicitly // allowed to be garbage) is not validated and no wild pointer is dereferenced. if (likely(!d->m_nilArrayNesting)) { doWriteString(newState, alignment); } else { // The alignment of the first element in a nil array determines where array data starts, // which is needed to serialize the length correctly. Write the minimum to achieve that. // (The check to see if we're really at the first element is omitted - for performance // it's worth trying to add that check) d->alignData(alignment); } return; } Private::AggregateInfo aggregateInfo; switch (newState) { case BeginStruct: VALID_IF(d->m_nesting.beginParen(), Error::ExcessiveNesting); aggregateInfo.aggregateType = BeginStruct; aggregateInfo.sct.containedTypeBegin = d->m_signaturePosition; d->m_aggregateStack.push_back(aggregateInfo); d->alignData(alignment); break; case EndStruct: - d->m_nesting.endParen(); VALID_IF(!d->m_aggregateStack.empty(), Error::CannotEndStructHere); aggregateInfo = d->m_aggregateStack.back(); VALID_IF(aggregateInfo.aggregateType == BeginStruct && d->m_signaturePosition > aggregateInfo.sct.containedTypeBegin + 1, Error::EmptyStruct); // empty structs are not allowed + d->m_nesting.endParen(); d->m_aggregateStack.pop_back(); break; case BeginVariant: { VALID_IF(d->m_nesting.beginVariant(), Error::ExcessiveNesting); aggregateInfo.aggregateType = BeginVariant; Private::VariantInfo &variantInfo = aggregateInfo.var; variantInfo.prevSignatureOffset = uint32(reinterpret_cast(d->m_signature.ptr) - d->m_data); d->m_signature.ptr[-1] = byte(d->m_signature.length); variantInfo.prevSignaturePosition = d->m_signaturePosition; if (!d->insideVariant()) { d->m_dataPositionBeforeVariant = d->m_dataPosition; } d->m_aggregateStack.push_back(aggregateInfo); d->m_queuedData.reserve(16); d->m_queuedData.push_back(Private::QueuedDataInfo(1, Private::QueuedDataInfo::VariantSignature)); const uint32 newDataPosition = d->m_dataPosition + Private::SignatureReservedSpace; d->reserveData(newDataPosition); // allocate new signature in the data buffer, reserve one byte for length prefix d->m_signature.ptr = reinterpret_cast(d->m_data) + d->m_dataPosition + 1; d->m_signature.length = 0; d->m_signaturePosition = 0; d->m_dataPosition = newDataPosition; break; } case EndVariant: { - d->m_nesting.endVariant(); VALID_IF(!d->m_aggregateStack.empty(), Error::CannotEndVariantHere); aggregateInfo = d->m_aggregateStack.back(); VALID_IF(aggregateInfo.aggregateType == BeginVariant, Error::CannotEndVariantHere); + d->m_nesting.endVariant(); if (likely(!d->m_nilArrayNesting)) { // Empty variants are not allowed. As an exception, in nil arrays they are // allowed for writing a type signature like "av" in the shortest possible way. // No use adding stuff when it's not required or even possible. VALID_IF(d->m_signaturePosition > 0, Error::EmptyVariant); assert(d->m_signaturePosition <= MaxSignatureLength); // should have been caught earlier } d->m_signature.ptr[-1] = byte(d->m_signaturePosition); Private::VariantInfo &variantInfo = aggregateInfo.var; d->m_signature.ptr = reinterpret_cast(d->m_data) + variantInfo.prevSignatureOffset; d->m_signature.length = d->m_signature.ptr[-1]; d->m_signaturePosition = variantInfo.prevSignaturePosition; d->m_aggregateStack.pop_back(); // if not in any variant anymore, flush queued data and resume unqueued operation if (d->m_signature.ptr == reinterpret_cast(d->m_data) + 1) { flushQueuedData(); } break; } case BeginDict: case BeginArray: { VALID_IF(d->m_nesting.beginArray(), Error::ExcessiveNesting); if (newState == BeginDict) { // not re-opened before each element: there is no observable difference for clients VALID_IF(d->m_nesting.beginParen(), Error::ExcessiveNesting); } aggregateInfo.aggregateType = newState; aggregateInfo.arr.containedTypeBegin = d->m_signaturePosition; d->reserveData(d->m_dataPosition + (sizeof(uint32) << 1)); zeroPad(d->m_data, sizeof(uint32), &d->m_dataPosition); basic::writeUint32(d->m_data + d->m_dataPosition, 0); aggregateInfo.arr.lengthFieldPosition = d->m_dataPosition; d->m_dataPosition += sizeof(uint32); d->maybeQueueData(sizeof(uint32), Private::QueuedDataInfo::ArrayLengthField); if (newState == BeginDict) { d->alignData(StructAlignment); #ifdef WITH_DICT_ENTRY m_state = BeginDictEntry; aggregateInfo.arr.dictEntryState = Private::RequireBeginDictEntry; #else m_state = DictKey; #endif } d->m_aggregateStack.push_back(aggregateInfo); break; } case EndDict: case EndArray: { const bool isDict = newState == EndDict; - if (isDict) { - d->m_nesting.endParen(); - } - d->m_nesting.endArray(); + VALID_IF(!d->m_aggregateStack.empty(), Error::CannotEndArrayHere); aggregateInfo = d->m_aggregateStack.back(); VALID_IF(aggregateInfo.aggregateType == (isDict ? BeginDict : BeginArray), Error::CannotEndArrayOrDictHere); VALID_IF(d->m_signaturePosition >= aggregateInfo.arr.containedTypeBegin + (isDict ? 3 : 1), Error::TooFewTypesInArrayOrDict); + if (isDict) { + d->m_nesting.endParen(); + } + d->m_nesting.endArray(); // array data starts (and in empty arrays ends) at the first array element position *after alignment* const uint32 contentAlign = isDict ? 8 : typeInfo(d->m_signature.ptr[aggregateInfo.arr.containedTypeBegin]).alignment; const uint32 arrayDataStart = align(aggregateInfo.arr.lengthFieldPosition + sizeof(uint32), contentAlign); if (unlikely(d->m_nilArrayNesting)) { if (--d->m_nilArrayNesting == 0) { d->m_dataPosition = arrayDataStart; if (d->insideVariant()) { assert(d->m_queuedData.begin() + d->m_dataElementsCountBeforeNilArray <= d->m_queuedData.end()); d->m_queuedData.erase(d->m_queuedData.begin() + d->m_dataElementsCountBeforeNilArray, d->m_queuedData.end()); assert((d->m_queuedData.end() - 2)->size == Private::QueuedDataInfo::ArrayLengthField); // align, but don't have actual data for the first element d->m_queuedData.back().size = 0; } } } // (arrange to) patch in the array length now that it is known if (d->insideVariant()) { d->m_queuedData.push_back(Private::QueuedDataInfo(1, Private::QueuedDataInfo::ArrayLengthEndMark)); } else { const uint32 arrayLength = d->m_dataPosition - arrayDataStart; VALID_IF(arrayLength <= SpecMaxArrayLength, Error::ArrayOrDictTooLong); basic::writeUint32(d->m_data + aggregateInfo.arr.lengthFieldPosition, arrayLength); } d->m_aggregateStack.pop_back(); break; } #ifdef WITH_DICT_ENTRY case BeginDictEntry: case EndDictEntry: break; #endif default: VALID_IF(false, Error::InvalidType); break; } } void Arguments::Writer::beginArrayOrDict(IoState beginWhat, ArrayOption option) { assert(beginWhat == BeginArray || beginWhat == BeginDict); if (unlikely(option == RestartEmptyArrayToWriteTypes)) { if (!d->m_aggregateStack.empty()) { Private::AggregateInfo &aggregateInfo = d->m_aggregateStack.back(); if (aggregateInfo.aggregateType == beginWhat) { // No writes to the array or dict may have occurred yet if (d->m_signaturePosition == aggregateInfo.arr.containedTypeBegin) { // Fix up state as if beginArray/Dict() had been called with WriteTypesOfEmptyArray // in the first place. After that small fixup we're done and return. // The code is a slightly modified version of code below under: if (isEmpty) { if (!d->m_nilArrayNesting) { d->m_nilArrayNesting = 1; d->m_dataElementsCountBeforeNilArray = d->m_queuedData.size() + 2; // +2 as below // Now correct for the elements already added in advanceState() with BeginArray / BeginDict d->m_dataElementsCountBeforeNilArray -= (beginWhat == BeginDict) ? 2 : 1; } else { // The array may be implicitly nil (so our poor API client doesn't notice) because // an array below in the aggregate stack is nil, so just allow this as a no-op. } return; } } } VALID_IF(false, Error::InvalidStateToRestartEmptyArray); } const bool isEmpty = (option != NonEmptyArray) || d->m_nilArrayNesting; if (isEmpty) { if (!d->m_nilArrayNesting++) { // For simplictiy and performance in the fast path, we keep storing the data chunks and any // variant signatures written inside an empty array. When we close the array, though, we // throw away all that data and signatures and keep only changes in the signature containing // the topmost empty array. // +2 -> keep ArrayLengthField, and first data element for alignment purposes d->m_dataElementsCountBeforeNilArray = d->m_queuedData.size() + 2; } } if (beginWhat == BeginArray) { advanceState(cstring("a", strlen("a")), beginWhat); } else { advanceState(cstring("a{", strlen("a{")), beginWhat); } } void Arguments::Writer::beginArray(ArrayOption option) { beginArrayOrDict(BeginArray, option); } void Arguments::Writer::endArray() { advanceState(cstring(), EndArray); } void Arguments::Writer::beginDict(ArrayOption option) { beginArrayOrDict(BeginDict, option); } void Arguments::Writer::endDict() { advanceState(cstring("}", strlen("}")), EndDict); } #ifdef WITH_DICT_ENTRY void Arguments::Writer::beginDictEntry() { VALID_IF(m_state == BeginDictEntry, Error::MisplacedBeginDictEntry); advanceState(cstring(), BeginDictEntry); } void Arguments::Writer::endDictEntry() { if (!d->m_aggregateStack.empty()) { Private::AggregateInfo &aggregateInfo = d->m_aggregateStack.back(); if (aggregateInfo.aggregateType == BeginDict && aggregateInfo.arr.dictEntryState == Private::RequireEndDictEntry) { advanceState(cstring(), EndDictEntry); return; } } VALID_IF(false, Error::MisplacedEndDictEntry); } #endif void Arguments::Writer::beginStruct() { advanceState(cstring("(", strlen("(")), BeginStruct); } void Arguments::Writer::endStruct() { advanceState(cstring(")", strlen(")")), EndStruct); } void Arguments::Writer::beginVariant() { advanceState(cstring("v", strlen("v")), BeginVariant); } void Arguments::Writer::endVariant() { advanceState(cstring(), EndVariant); } void Arguments::Writer::writeVariantForMessageHeader(char sig) { // Note: the sugnature we're vorking with there is a(yv) // If we know that and can trust the client, this can be very easy and fast... d->m_signature.ptr[3] = 'v'; d->m_signature.length = 4; d->m_signaturePosition = 4; d->reserveData(d->m_dataPosition + 3); d->m_data[d->m_dataPosition++] = 1; d->m_data[d->m_dataPosition++] = sig; d->m_data[d->m_dataPosition++] = 0; } void Arguments::Writer::fixupAfterWriteVariantForMessageHeader() { // We just wrote something to the main signature when we shouldn't have. d->m_signature.length = 4; d->m_signaturePosition = 4; } static char letterForPrimitiveIoState(Arguments::IoState ios) { if (ios < Arguments::Boolean || ios > Arguments::Double) { return 'c'; // a known invalid letter that won't trip up typeInfo() } static const char letters[] = { 'b', // Boolean 'y', // Byte 'n', // Int16 'q', // Uint16 'i', // Int32 'u', // Uint32 'x', // Int64 't', // Uint64 'd' // Double }; return letters[size_t(ios) - size_t(Arguments::Boolean)]; // TODO do we need the casts? } void Arguments::Writer::writePrimitiveArray(IoState type, chunk data) { const char letterCode = letterForPrimitiveIoState(type); if (letterCode == 'c' || data.length > SpecMaxArrayLength) { m_state = InvalidData; d->m_error.setCode(Error::NotPrimitiveType); return; } const TypeInfo elementType = typeInfo(letterCode); if (!isAligned(data.length, elementType.alignment)) { return; } beginArray(data.length ? NonEmptyArray : WriteTypesOfEmptyArray); // dummy write to write the signature... m_u.Uint64 = 0; advanceState(cstring(&letterCode, /*length*/ 1), elementType.state()); if (!data.length) { // oh! a nil array. endArray(); return; } // undo the dummy write (except for the preceding alignment bytes, if any) d->m_dataPosition -= elementType.alignment; if (d->insideVariant()) { d->m_queuedData.pop_back(); d->m_queuedData.push_back(Private::QueuedDataInfo(elementType.alignment, 0)); } // append the payload d->reserveData(d->m_dataPosition + data.length); d->appendBulkData(data); endArray(); } Arguments Arguments::Writer::finish() { // what needs to happen here: // - check if the message can be closed - basically the aggregate stack must be empty // - close the signature by adding the terminating null // TODO set error in returned Arguments in error cases Arguments args; if (m_state == InvalidData) { return args; } if (d->m_nesting.total() != 0) { m_state = InvalidData; d->m_error.setCode(Error::CannotEndArgumentsHere); return args; } assert(!d->m_nilArrayNesting); assert(!d->insideVariant()); assert(d->m_signaturePosition <= MaxSignatureLength); // this should have been caught before assert(d->m_signature.ptr == reinterpret_cast(d->m_data) + 1); // Note that we still keep the full SignatureReservedSpace for the main signature, which means // less copying around to shrink the gap between signature and data, but also wastes an enormous // amount of space (relative to the possible minimum) in some cases. It should not be a big space // problem because normally not many D-Bus Message / Arguments instances exist at the same time. d->m_signature.length = d->m_signaturePosition; d->m_signature.ptr[d->m_signature.length] = '\0'; args.d->m_error = d->m_error; // OK, so this length check is more of a sanity check. The actual limit limits the size of the // full message. Here we take the size of the "payload" and don't add the size of the signature - // why bother doing it accurately when the real check with full information comes later anyway? bool success = true; const uint32 dataSize = d->m_dataPosition - Private::SignatureReservedSpace; if (success && dataSize > SpecMaxMessageLength) { success = false; d->m_error.setCode(Error::ArgumentsTooLong); } if (!dataSize || !success) { args.d->m_memOwnership = nullptr; args.d->m_signature = cstring(); args.d->m_data = chunk(); } else { args.d->m_memOwnership = d->m_data; args.d->m_signature = cstring(d->m_data + 1 /* w/o length prefix */, d->m_signature.length); args.d->m_data = chunk(d->m_data + Private::SignatureReservedSpace, dataSize); d->m_data = nullptr; // now owned by Arguments and later freed there } if (!success) { m_state = InvalidData; return Arguments(); } args.d->m_fileDescriptors = std::move(d->m_fileDescriptors); m_state = Finished; return std::move(args); } struct ArrayLengthField { uint32 lengthFieldPosition; uint32 dataStartPosition; }; void Arguments::Writer::flushQueuedData() { const uint32 count = d->m_queuedData.size(); assert(count); // just don't call this method otherwise! // Note: if one of signature or data is nonempty, the other must also be nonempty. // Even "empty" things like empty arrays or null strings have a size field, in that case // (for all(?) types) of value zero. // Copy the signature and main data (thus the whole contents) into one allocated block, // which is good to have for performance and simplicity reasons. // The maximum alignment blowup for naturally aligned types is just less than a factor of 2. // Structs and dict entries are always 8 byte aligned so they add a maximum blowup of 7 bytes // each (when they contain a byte). // Those estimates are very conservative (but easy!), so some space optimization is possible. uint32 inPos = d->m_dataPositionBeforeVariant; uint32 outPos = d->m_dataPositionBeforeVariant; byte *const buffer = d->m_data; std::vector lengthFieldStack; for (uint32 i = 0; i < count; i++) { const Private::QueuedDataInfo ei = d->m_queuedData[i]; switch (ei.size) { case 0: { inPos = align(inPos, ei.alignment()); zeroPad(buffer, ei.alignment(), &outPos); } break; default: { assert(ei.size && ei.size <= Private::QueuedDataInfo::LargestSize); inPos = align(inPos, ei.alignment()); zeroPad(buffer, ei.alignment(), &outPos); // copy data chunk memmove(buffer + outPos, buffer + inPos, ei.size); inPos += ei.size; outPos += ei.size; } break; case Private::QueuedDataInfo::ArrayLengthField: { // start of an array // alignment padding before length field inPos = align(inPos, ei.alignment()); zeroPad(buffer, ei.alignment(), &outPos); // reserve length field ArrayLengthField al; al.lengthFieldPosition = outPos; inPos += sizeof(uint32); outPos += sizeof(uint32); // alignment padding before first array element assert(i + 1 < d->m_queuedData.size()); const uint32 contentsAlignment = d->m_queuedData[i + 1].alignment(); inPos = align(inPos, contentsAlignment); zeroPad(buffer, contentsAlignment, &outPos); // array data starts at the first array element position after alignment al.dataStartPosition = outPos; lengthFieldStack.push_back(al); } break; case Private::QueuedDataInfo::ArrayLengthEndMark: { // end of an array // just put the now known array length in front of the array const ArrayLengthField al = lengthFieldStack.back(); const uint32 arrayLength = outPos - al.dataStartPosition; if (arrayLength > SpecMaxArrayLength) { m_state = InvalidData; d->m_error.setCode(Error::ArrayOrDictTooLong); i = count + 1; // break out of the loop break; } basic::writeUint32(buffer + al.lengthFieldPosition, arrayLength); lengthFieldStack.pop_back(); } break; case Private::QueuedDataInfo::VariantSignature: { // move the signature and add its null terminator const uint32 length = buffer[inPos] + 1; // + length prefix memmove(buffer + outPos, buffer + inPos, length); buffer[outPos + length] = '\0'; outPos += length + 1; // + null terminator inPos += Private::Private::SignatureReservedSpace; } break; } } assert(m_state == InvalidData || lengthFieldStack.empty()); d->m_dataPosition = outPos; d->m_queuedData.clear(); } std::vector Arguments::Writer::aggregateStack() const { std::vector ret; ret.reserve(d->m_aggregateStack.size()); for (Private::AggregateInfo &aggregate : d->m_aggregateStack) { ret.push_back(aggregate.aggregateType); } return ret; } uint32 Arguments::Writer::aggregateDepth() const { return d->m_aggregateStack.size(); } Arguments::IoState Arguments::Writer::currentAggregate() const { if (d->m_aggregateStack.empty()) { return NotStarted; } return d->m_aggregateStack.back().aggregateType; } chunk Arguments::Writer::peekSerializedData() const { chunk ret; if (isValid() && m_state != InvalidData && d->m_nesting.total() == 0) { ret.ptr = d->m_data; ret.length = d->m_dataPosition; } return ret; } const std::vector &Arguments::Writer::fileDescriptors() const { return d->m_fileDescriptors; } void Arguments::Writer::writeBoolean(bool b) { m_u.Boolean = b; advanceState(cstring("b", strlen("b")), Boolean); } void Arguments::Writer::writeByte(byte b) { m_u.Byte = b; advanceState(cstring("y", strlen("y")), Byte); } void Arguments::Writer::writeInt16(int16 i) { m_u.Int16 = i; advanceState(cstring("n", strlen("n")), Int16); } void Arguments::Writer::writeUint16(uint16 i) { m_u.Uint16 = i; advanceState(cstring("q", strlen("q")), Uint16); } void Arguments::Writer::writeInt32(int32 i) { m_u.Int32 = i; advanceState(cstring("i", strlen("i")), Int32); } void Arguments::Writer::writeUint32(uint32 i) { m_u.Uint32 = i; advanceState(cstring("u", strlen("u")), Uint32); } void Arguments::Writer::writeInt64(int64 i) { m_u.Int64 = i; advanceState(cstring("x", strlen("x")), Int64); } void Arguments::Writer::writeUint64(uint64 i) { m_u.Uint64 = i; advanceState(cstring("t", strlen("t")), Uint64); } void Arguments::Writer::writeDouble(double d) { m_u.Double = d; advanceState(cstring("d", strlen("d")), Double); } void Arguments::Writer::writeString(cstring string) { m_u.String.ptr = string.ptr; m_u.String.length = string.length; advanceState(cstring("s", strlen("s")), String); } void Arguments::Writer::writeObjectPath(cstring objectPath) { m_u.String.ptr = objectPath.ptr; m_u.String.length = objectPath.length; advanceState(cstring("o", strlen("o")), ObjectPath); } void Arguments::Writer::writeSignature(cstring signature) { m_u.String.ptr = signature.ptr; m_u.String.length = signature.length; advanceState(cstring("g", strlen("g")), Signature); } void Arguments::Writer::writeUnixFd(int32 fd) { m_u.Int32 = fd; advanceState(cstring("h", strlen("h")), UnixFd); } diff --git a/tests/serialization/tst_arguments.cpp b/tests/serialization/tst_arguments.cpp index 68b5da4..c568d19 100644 --- a/tests/serialization/tst_arguments.cpp +++ b/tests/serialization/tst_arguments.cpp @@ -1,1917 +1,1951 @@ /* Copyright (C) 2013 Andreas Hartmetz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LGPL. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Alternatively, this file is available under the Mozilla Public License Version 1.1. You may obtain a copy of the License at http://www.mozilla.org/MPL/ */ #include "arguments.h" #include "../testutil.h" #include #include #include // Handy helpers static void printChunk(chunk a) { std::cout << "Array: "; for (uint32 i = 0; i < a.length; i++) { std::cout << int(a.ptr[i]) << '|'; } std::cout << '\n'; } static bool chunksEqual(chunk a1, chunk a2) { if (a1.length != a2.length) { std::cout << "Different lengths.\n"; printChunk(a1); printChunk(a2); return false; } for (uint32 i = 0; i < a1.length; i++) { if (a1.ptr[i] != a2.ptr[i]) { std::cout << "Different content.\n"; printChunk(a1); printChunk(a2); return false; } } return true; } static bool stringsEqual(cstring s1, cstring s2) { return chunksEqual(chunk(s1.ptr, s1.length), chunk(s2.ptr, s2.length)); } static void maybeBeginDictEntry(Arguments::Writer *writer) { (void) writer; #ifdef WITH_DICT_ENTRY writer->beginDictEntry(); #endif } static void maybeEndDictEntry(Arguments::Writer *writer) { (void) writer; #ifdef WITH_DICT_ENTRY writer->endDictEntry(); #endif } // This class does: // 1) iterates over the full Arguments with m_reader // 2) skips whole aggregates at and below nesting level m_skipAggregatesFromLevel with m_skippingReader // 3) skips nil arrays at and below nil array nesting level m_skipNilArraysFromLevel with m_skippingReader // It even skips aggregates inside nil arrays as 2) + 3) imply. // It checks: // a) where nothing is skipped, that the aggregate structure and data read are the same class SkipChecker { public: SkipChecker(Arguments::Reader *reader, Arguments::Reader *skippingReader, int skipAggregatesFromLevel, int skipNilArraysFromLevel) : m_nestingLevel(0), m_nilArrayNesting(0), m_skipAggregatesFromLevel(skipAggregatesFromLevel), m_skipNilArraysFromLevel(skipNilArraysFromLevel), m_reader(reader), m_skippingReader(skippingReader) {} template void readAndCompare(F readFunc) { Arguments::IoState rState = m_reader->state(); auto rval = (*m_reader.*readFunc)(); if (m_nestingLevel < m_skipAggregatesFromLevel && m_nilArrayNesting < m_skipNilArraysFromLevel) { Arguments::IoState sState = m_skippingReader->state(); TEST(rState == sState); auto sval = (*m_skippingReader.*readFunc)(); if (!m_nilArrayNesting) { TEST(myEqual(rval, sval)); } } } #ifdef WITH_DICT_ENTRY void beginDictEntry() { m_reader->beginDictEntry(); if (m_nestingLevel < m_skipAggregatesFromLevel && m_nilArrayNesting < m_skipNilArraysFromLevel) { m_skippingReader->beginDictEntry(); } } void endDictEntry() { m_reader->endDictEntry(); if (m_nestingLevel < m_skipAggregatesFromLevel && m_nilArrayNesting < m_skipNilArraysFromLevel) { m_skippingReader->endDictEntry(); } } #endif template void beginAggregate(F beginFunc, G skipFunc) { (*m_reader.*beginFunc)(); m_nestingLevel++; if (m_nilArrayNesting < m_skipNilArraysFromLevel) { if (m_nestingLevel < m_skipAggregatesFromLevel) { (*m_skippingReader.*beginFunc)(); } else if (m_nestingLevel == m_skipAggregatesFromLevel) { (*m_skippingReader.*skipFunc)(); } } } template void beginArrayAggregate(F beginFunc, G skipFunc) { const bool hasData = (*m_reader.*beginFunc)(Arguments::Reader::ReadTypesOnlyIfEmpty); m_nestingLevel++; m_nilArrayNesting += hasData ? 0 : 1; if (m_nestingLevel > m_skipAggregatesFromLevel || m_nilArrayNesting > m_skipNilArraysFromLevel) { // we're already skipping, do nothing } else if (m_nestingLevel == m_skipAggregatesFromLevel) { (*m_skippingReader.*skipFunc)(); } else if (m_nilArrayNesting == m_skipNilArraysFromLevel) { (*m_skippingReader.*beginFunc)(Arguments::Reader::SkipIfEmpty); } else { (*m_skippingReader.*beginFunc)(Arguments::Reader::ReadTypesOnlyIfEmpty); } } template void endAggregate(F endFunc, bool isArrayType) { (*m_reader.*endFunc)(); // when skipping a nil array: do the last part of the beginArray(), endArray() sequence // when using skip*(): do not call end() on that level, skip*() moves right past the aggregate if (m_nestingLevel < m_skipAggregatesFromLevel && (m_nilArrayNesting < m_skipNilArraysFromLevel || (isArrayType && m_nilArrayNesting == m_skipNilArraysFromLevel))) { (*m_skippingReader.*endFunc)(); } else { // we've already skipped the current aggregate } m_nestingLevel--; if (isArrayType && m_nilArrayNesting) { m_nilArrayNesting--; } } int m_nestingLevel; int m_nilArrayNesting; const int m_skipAggregatesFromLevel; const int m_skipNilArraysFromLevel; private: template bool myEqual(const T &a, const T &b) { return a == b; } bool myEqua(const chunk &a, const chunk &b) { return chunksEqual(a, b); } bool myEqual(const cstring &a, const cstring &b) { return stringsEqual(a, b); } Arguments::Reader *m_reader; Arguments::Reader *m_skippingReader; }; static void testReadWithSkip(const Arguments &arg, bool debugPrint) { // it would be even better to decide when to skip more "randomly", but given that it doesn't make // much difference in the implementation, this should do. // loop over when to skip aggregates voluntarily (on "skipper") for (int aggregateSkipLevel = 1 /* 1 orig, 15 = H4X disabled*/; aggregateSkipLevel < 16; aggregateSkipLevel++) { // loop over when to skip empty aka nil arrays - on "reader", which: // - cross checks aggregate skipping vs. skipping nil arrays // - is also the primary test for nil arrays for (int nilArraySkipLevel = 1; nilArraySkipLevel < 8; nilArraySkipLevel++) { // loop over *how* to skip empty aka nil arrays, // beginArray(Arguments::Reader::ReadTypesOnlyIfEmpty) or skipArray() Arguments::Reader reader(arg); Arguments::Reader skippingReader(arg); SkipChecker checker(&reader, &skippingReader, aggregateSkipLevel, nilArraySkipLevel); bool isDone = false; while (!isDone) { TEST(reader.state() != Arguments::InvalidData); TEST(skippingReader.state() != Arguments::InvalidData); if (debugPrint) { std::cerr << "Reader state: " << reader.stateString().ptr << '\n'; std::cerr << "Skipping reader state: " << skippingReader.stateString().ptr << '\n'; } switch(reader.state()) { case Arguments::Finished: TEST(checker.m_nestingLevel == 0); TEST(checker.m_nilArrayNesting == 0); isDone = true; break; case Arguments::BeginStruct: //std::cerr << "Beginning struct\n"; checker.beginAggregate(&Arguments::Reader::beginStruct, &Arguments::Reader::skipStruct); break; case Arguments::EndStruct: checker.endAggregate(&Arguments::Reader::endStruct, false); break; case Arguments::BeginVariant: //std::cerr << "Beginning variant\n"; checker.beginAggregate(&Arguments::Reader::beginVariant, &Arguments::Reader::skipVariant); break; case Arguments::EndVariant: checker.endAggregate(&Arguments::Reader::endVariant, false); break; case Arguments::BeginArray: checker.beginArrayAggregate(&Arguments::Reader::beginArray, &Arguments::Reader::skipArray); break; case Arguments::EndArray: checker.endAggregate(&Arguments::Reader::endArray, true); break; case Arguments::BeginDict: checker.beginArrayAggregate(&Arguments::Reader::beginDict, &Arguments::Reader::skipDict); break; #ifdef WITH_DICT_ENTRY case Arguments::BeginDictEntry: checker.beginDictEntry(); break; case Arguments::EndDictEntry: checker.endDictEntry(); break; #endif case Arguments::EndDict: checker.endAggregate(&Arguments::Reader::endDict, true); break; case Arguments::Byte: checker.readAndCompare(&Arguments::Reader::readByte); break; case Arguments::Boolean: checker.readAndCompare(&Arguments::Reader::readBoolean); break; case Arguments::Int16: checker.readAndCompare(&Arguments::Reader::readInt16); break; case Arguments::Uint16: checker.readAndCompare(&Arguments::Reader::readUint16); break; case Arguments::Int32: checker.readAndCompare(&Arguments::Reader::readInt32); break; case Arguments::Uint32: checker.readAndCompare(&Arguments::Reader::readUint32); break; case Arguments::Int64: checker.readAndCompare(&Arguments::Reader::readInt64); break; case Arguments::Uint64: checker.readAndCompare(&Arguments::Reader::readUint64); break; case Arguments::Double: checker.readAndCompare(&Arguments::Reader::readDouble); break; case Arguments::String: checker.readAndCompare(&Arguments::Reader::readString); break; case Arguments::ObjectPath: checker.readAndCompare(&Arguments::Reader::readObjectPath); break; case Arguments::Signature: checker.readAndCompare(&Arguments::Reader::readSignature); break; case Arguments::UnixFd: checker.readAndCompare(&Arguments::Reader::readUnixFd); break; case Arguments::NeedMoreData: // ### would be nice to test this as well default: TEST(false); break; } } TEST(reader.state() == Arguments::Finished); TEST(skippingReader.state() == Arguments::Finished); } } } // When using this to iterate over the reader, it will make an exact copy using the Writer. // You need to do something only in states where something special should happen. static void defaultReadToWrite(Arguments::Reader *reader, Arguments::Writer *writer) { switch(reader->state()) { case Arguments::BeginStruct: case Arguments::EndStruct: case Arguments::BeginVariant: case Arguments::EndVariant: case Arguments::EndArray: #ifdef WITH_DICT_ENTRY case Arguments::BeginDictEntry: case Arguments::EndDictEntry: #endif case Arguments::EndDict: case Arguments::Byte: case Arguments::Boolean: case Arguments::Int16: case Arguments::Uint16: case Arguments::Int32: case Arguments::Uint32: case Arguments::Int64: case Arguments::Uint64: case Arguments::Double: case Arguments::UnixFd: Arguments::copyOneElement(reader, writer); break; // special handling for BeginArray and BeginDict to avoid "fast copy" for primitive arrays case Arguments::BeginArray: { const bool hasData = reader->beginArray(Arguments::Reader::ReadTypesOnlyIfEmpty); writer->beginArray(hasData ? Arguments::Writer::NonEmptyArray : Arguments::Writer::WriteTypesOfEmptyArray); break; } case Arguments::BeginDict: { const bool hasData = reader->beginDict(Arguments::Reader::ReadTypesOnlyIfEmpty); writer->beginDict(hasData ? Arguments::Writer::NonEmptyArray : Arguments::Writer::WriteTypesOfEmptyArray); break; } case Arguments::String: { const cstring s = reader->readString(); if (!reader->isInsideEmptyArray()) { TEST(Arguments::isStringValid(s)); } writer->writeString(s); break; } case Arguments::ObjectPath: { const cstring objectPath = reader->readObjectPath(); if (!reader->isInsideEmptyArray()) { TEST(Arguments::isObjectPathValid(objectPath)); } writer->writeObjectPath(objectPath); break; } case Arguments::Signature: { const cstring signature = reader->readSignature(); if (!reader->isInsideEmptyArray()) { TEST(Arguments::isSignatureValid(signature)); } writer->writeSignature(signature); break; } writer->writeUnixFd(reader->readUnixFd()); break; // special cases follow case Arguments::Finished: break; // You *probably* want to handle that one in the caller, but you don't have to case Arguments::NeedMoreData: TEST(false); // No way to handle that one here break; default: TEST(false); break; } } static void verifyAfterRoundtrip(const Arguments &original, const Arguments::Reader &originalReader, const Arguments ©, const Arguments::Writer ©Writer, bool debugPrint) { TEST(originalReader.state() == Arguments::Finished); TEST(copyWriter.state() == Arguments::Finished); cstring originalSignature = original.signature(); cstring copySignature = copy.signature(); if (originalSignature.length) { TEST(Arguments::isSignatureValid(copySignature)); TEST(stringsEqual(originalSignature, copySignature)); } else { TEST(copySignature.length == 0); } chunk originalData = original.data(); chunk copyData = copy.data(); TEST(originalData.length == copyData.length); if (debugPrint && !chunksEqual(originalData, copyData)) { printChunk(originalData); printChunk(copyData); } TEST(chunksEqual(originalData, copyData)); } static void doRoundtripWithShortReads(const Arguments &original, uint32 dataIncrement, bool debugPrint) { const chunk data = original.data(); chunk shortData; Arguments arg(nullptr, original.signature(), shortData, original.fileDescriptors()); Arguments::Reader reader(arg); Arguments::Writer writer; bool isDone = false; while (!isDone) { TEST(writer.state() != Arguments::InvalidData); if (debugPrint) { std::cout << "Reader state: " << reader.stateString().ptr << '\n'; } switch(reader.state()) { case Arguments::Finished: isDone = true; break; case Arguments::NeedMoreData: { TEST(shortData.length < data.length); // reallocate shortData to test that Reader can handle the data moving around - and // allocate the new one before destroying the old one to make sure that the pointer differs chunk oldData = shortData; shortData.length = std::min(shortData.length + dataIncrement, data.length); shortData.ptr = reinterpret_cast(malloc(shortData.length)); for (uint32 i = 0; i < shortData.length; i++) { shortData.ptr[i] = data.ptr[i]; } // clobber it to provoke errors that only valgrind might find otherwise for (uint32 i = 0; i < oldData.length; i++) { oldData.ptr[i] = 0xff; } if (oldData.ptr) { free(oldData.ptr); } reader.replaceData(shortData); break; } default: defaultReadToWrite(&reader, &writer); break; } } Arguments copy = writer.finish(); verifyAfterRoundtrip(original, reader, copy, writer, debugPrint); if (shortData.ptr) { free(shortData.ptr); } } static void doRoundtripWithReaderCopy(const Arguments &original, uint32 dataIncrement, bool debugPrint) { Arguments::Reader *reader = new Arguments::Reader(original); Arguments::Writer writer; bool isDone = false; uint32 i = 0; while (!isDone) { TEST(writer.state() != Arguments::InvalidData); if (i++ == dataIncrement) { Arguments::Reader *copy = new Arguments::Reader(*reader); delete reader; reader = copy; } if (debugPrint) { std::cout << "Reader state: " << reader->stateString().ptr << '\n'; } switch(reader->state()) { case Arguments::Finished: isDone = true; break; default: defaultReadToWrite(reader, &writer); break; } } Arguments copy = writer.finish(); verifyAfterRoundtrip(original, *reader, copy, writer, debugPrint); delete reader; } static void doRoundtripWithWriterCopy(const Arguments &original, uint32 dataIncrement, bool debugPrint) { Arguments::Reader reader(original); Arguments::Writer *writer = new Arguments::Writer; bool isDone = false; uint32 i = 0; while (!isDone) { TEST(writer->state() != Arguments::InvalidData); if (i++ == dataIncrement) { Arguments::Writer *copy = new Arguments::Writer(*writer); delete writer; writer = copy; } if (debugPrint) { std::cout << "Reader state: " << reader.stateString().ptr << '\n'; } switch(reader.state()) { case Arguments::Finished: isDone = true; break; default: defaultReadToWrite(&reader, writer); break; } } Arguments copy = writer->finish(); verifyAfterRoundtrip(original, reader, copy, *writer, debugPrint); delete writer; } static void doRoundtripForReal(const Arguments &original, uint32 dataIncrement, bool debugPrint) { doRoundtripWithShortReads(original, dataIncrement, debugPrint); doRoundtripWithReaderCopy(original, dataIncrement, debugPrint); doRoundtripWithWriterCopy(original, dataIncrement, debugPrint); } // not returning by value to avoid the move constructor or assignment operator - // those should have separate tests static Arguments *shallowCopy(const Arguments &original) { // File descriptors can't do shallow copies - don't care for now, file descriptors are an identity // type, not a value type (and therefore don't fit well into the whole data model), and in the vast // majority of messages there aren't any. cstring signature = original.signature(); chunk data = original.data(); return new Arguments(nullptr, signature, data, original.fileDescriptors()); } static void shallowAssign(Arguments *copy, const Arguments &original) { cstring signature = original.signature(); chunk data = original.data(); *copy = Arguments(nullptr, signature, data, original.fileDescriptors()); } static void doRoundtripWithCopyAssignEtc(const Arguments &arg_in, uint32 dataIncrement, bool debugPrint) { { // just pass through doRoundtripForReal(arg_in, dataIncrement, debugPrint); } { // shallow copy Arguments *shallowDuplicate = shallowCopy(arg_in); doRoundtripForReal(*shallowDuplicate, dataIncrement, debugPrint); delete shallowDuplicate; } { // assignment from shallow copy Arguments shallowAssigned; shallowAssign(&shallowAssigned, arg_in); doRoundtripForReal(shallowAssigned, dataIncrement, debugPrint); } { // deep copy Arguments original(arg_in); doRoundtripForReal(original, dataIncrement, debugPrint); } { // move construction from shallow copy Arguments *shallowDuplicate = shallowCopy(arg_in); Arguments shallowMoveConstructed(std::move(*shallowDuplicate)); doRoundtripForReal(shallowMoveConstructed, dataIncrement, debugPrint); delete shallowDuplicate; } { // move assignment (hopefully, may the compiler optimize this to move-construction?) from shallow copy Arguments *shallowDuplicate = shallowCopy(arg_in); Arguments shallowMoveAssigned; shallowMoveAssigned = std::move(*shallowDuplicate); doRoundtripForReal(shallowMoveAssigned, dataIncrement, debugPrint); delete shallowDuplicate; } { // move construction from deep copy Arguments duplicate(arg_in); Arguments moveConstructed(std::move(duplicate)); doRoundtripForReal(moveConstructed, dataIncrement, debugPrint); } { // move assignment (hopefully, may the compiler optimize this to move-construction?) from deep copy Arguments duplicate(arg_in); Arguments moveAssigned; moveAssigned = std::move(duplicate); doRoundtripForReal(moveAssigned, dataIncrement, debugPrint); } } static void doRoundtrip(const Arguments &arg, bool debugPrint = false) { const uint32 maxIncrement = arg.data().length; for (uint32 i = 1; i <= maxIncrement; i++) { doRoundtripWithCopyAssignEtc(arg, i, debugPrint); } testReadWithSkip(arg, debugPrint); } // Tests proper static void test_stringValidation() { { cstring emptyWithNull(""); cstring emptyWithoutNull; TEST(!Arguments::isStringValid(emptyWithoutNull)); TEST(Arguments::isStringValid(emptyWithNull)); TEST(!Arguments::isObjectPathValid(emptyWithoutNull)); TEST(!Arguments::isObjectPathValid(emptyWithNull)); TEST(Arguments::isSignatureValid(emptyWithNull)); TEST(!Arguments::isSignatureValid(emptyWithoutNull)); TEST(!Arguments::isSignatureValid(emptyWithNull, Arguments::VariantSignature)); TEST(!Arguments::isSignatureValid(emptyWithoutNull, Arguments::VariantSignature)); } { cstring trivial("i"); TEST(Arguments::isSignatureValid(trivial)); TEST(Arguments::isSignatureValid(trivial, Arguments::VariantSignature)); } { cstring list("iqb"); TEST(Arguments::isSignatureValid(list)); TEST(!Arguments::isSignatureValid(list, Arguments::VariantSignature)); cstring list2("aii"); TEST(Arguments::isSignatureValid(list2)); TEST(!Arguments::isSignatureValid(list2, Arguments::VariantSignature)); } { cstring simpleArray("ai"); TEST(Arguments::isSignatureValid(simpleArray)); TEST(Arguments::isSignatureValid(simpleArray, Arguments::VariantSignature)); } { cstring messyArray("a(iaia{ia{iv}})"); TEST(Arguments::isSignatureValid(messyArray)); TEST(Arguments::isSignatureValid(messyArray, Arguments::VariantSignature)); } { cstring dictFail("a{vi}"); TEST(!Arguments::isSignatureValid(dictFail)); TEST(!Arguments::isSignatureValid(dictFail, Arguments::VariantSignature)); } { cstring emptyStruct("()"); TEST(!Arguments::isSignatureValid(emptyStruct)); TEST(!Arguments::isSignatureValid(emptyStruct, Arguments::VariantSignature)); cstring emptyStruct2("(())"); TEST(!Arguments::isSignatureValid(emptyStruct2)); TEST(!Arguments::isSignatureValid(emptyStruct2, Arguments::VariantSignature)); cstring miniStruct("(t)"); TEST(Arguments::isSignatureValid(miniStruct)); TEST(Arguments::isSignatureValid(miniStruct, Arguments::VariantSignature)); cstring badStruct("((i)"); TEST(!Arguments::isSignatureValid(badStruct)); TEST(!Arguments::isSignatureValid(badStruct, Arguments::VariantSignature)); cstring badStruct2("(i))"); TEST(!Arguments::isSignatureValid(badStruct2)); TEST(!Arguments::isSignatureValid(badStruct2, Arguments::VariantSignature)); } { cstring nullStr; cstring emptyStr(""); TEST(!Arguments::isObjectPathValid(nullStr)); TEST(!Arguments::isObjectPathValid(emptyStr)); TEST(Arguments::isObjectPathValid(cstring("/"))); TEST(!Arguments::isObjectPathValid(cstring("/abc/"))); TEST(Arguments::isObjectPathValid(cstring("/abc"))); TEST(Arguments::isObjectPathValid(cstring("/abc/def"))); TEST(!Arguments::isObjectPathValid(cstring("/abc&def"))); TEST(!Arguments::isObjectPathValid(cstring("/abc//def"))); TEST(Arguments::isObjectPathValid(cstring("/aZ/0123_zAZa9_/_"))); } { cstring maxStruct("((((((((((((((((((((((((((((((((i" "))))))))))))))))))))))))))))))))"); TEST(Arguments::isSignatureValid(maxStruct)); TEST(Arguments::isSignatureValid(maxStruct, Arguments::VariantSignature)); cstring struct33("(((((((((((((((((((((((((((((((((i" // too much nesting by one ")))))))))))))))))))))))))))))))))"); TEST(!Arguments::isSignatureValid(struct33)); TEST(!Arguments::isSignatureValid(struct33, Arguments::VariantSignature)); cstring maxArray("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaai"); TEST(Arguments::isSignatureValid(maxArray)); TEST(Arguments::isSignatureValid(maxArray, Arguments::VariantSignature)); cstring array33("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaai"); TEST(!Arguments::isSignatureValid(array33)); TEST(!Arguments::isSignatureValid(array33, Arguments::VariantSignature)); } } static void test_nesting() { { Arguments::Writer writer; for (int i = 0; i < 32; i++) { writer.beginArray(); } TEST(writer.state() != Arguments::InvalidData); writer.beginArray(); TEST(writer.state() == Arguments::InvalidData); } { Arguments::Writer writer; for (int i = 0; i < 32; i++) { writer.beginDict(); maybeBeginDictEntry(&writer); writer.writeInt32(i); // key, next nested dict is value } TEST(writer.state() != Arguments::InvalidData); writer.beginStruct(); TEST(writer.state() == Arguments::InvalidData); } { Arguments::Writer writer; for (int i = 0; i < 32; i++) { writer.beginDict(); maybeBeginDictEntry(&writer); writer.writeInt32(i); // key, next nested dict is value } TEST(writer.state() != Arguments::InvalidData); writer.beginArray(); TEST(writer.state() == Arguments::InvalidData); } { Arguments::Writer writer; for (int i = 0; i < 64; i++) { writer.beginVariant(); } TEST(writer.state() != Arguments::InvalidData); writer.beginVariant(); TEST(writer.state() == Arguments::InvalidData); } } struct LengthPrefixedData { uint32 length; byte data[256]; }; static void test_roundtrip() { doRoundtrip(Arguments(nullptr, cstring(""), chunk())); { byte data[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; doRoundtrip(Arguments(nullptr, cstring("i"), chunk(data, 4))); doRoundtrip(Arguments(nullptr, cstring("yyyy"), chunk(data, 4))); doRoundtrip(Arguments(nullptr, cstring("iy"), chunk(data, 5))); doRoundtrip(Arguments(nullptr, cstring("iiy"), chunk(data, 9))); doRoundtrip(Arguments(nullptr, cstring("nquy"), chunk(data, 9))); doRoundtrip(Arguments(nullptr, cstring("unqy"), chunk(data, 9))); doRoundtrip(Arguments(nullptr, cstring("nqy"), chunk(data, 5))); doRoundtrip(Arguments(nullptr, cstring("qny"), chunk(data, 5))); doRoundtrip(Arguments(nullptr, cstring("yyny"), chunk(data, 5))); doRoundtrip(Arguments(nullptr, cstring("qyyy"), chunk(data, 5))); doRoundtrip(Arguments(nullptr, cstring("d"), chunk(data, 8))); doRoundtrip(Arguments(nullptr, cstring("dy"), chunk(data, 9))); doRoundtrip(Arguments(nullptr, cstring("x"), chunk(data, 8))); doRoundtrip(Arguments(nullptr, cstring("xy"), chunk(data, 9))); doRoundtrip(Arguments(nullptr, cstring("t"), chunk(data, 8))); doRoundtrip(Arguments(nullptr, cstring("ty"), chunk(data, 9))); } { LengthPrefixedData testArray = {0, {0}}; for (int i = 0; i < 64; i++) { testArray.data[i] = i; } byte *testData = reinterpret_cast(&testArray); testArray.length = 1; doRoundtrip(Arguments(nullptr, cstring("ay"), chunk(testData, 5))); testArray.length = 4; doRoundtrip(Arguments(nullptr, cstring("ai"), chunk(testData, 8))); testArray.length = 8; doRoundtrip(Arguments(nullptr, cstring("ai"), chunk(testData, 12))); testArray.length = 64; doRoundtrip(Arguments(nullptr, cstring("ai"), chunk(testData, 68))); doRoundtrip(Arguments(nullptr, cstring("an"), chunk(testData, 68))); testArray.data[0] = 0; testArray.data[1] = 0; // zero out padding testArray.data[2] = 0; testArray.data[3] = 0; testArray.length = 56; doRoundtrip(Arguments(nullptr, cstring("ad"), chunk(testData, 64))); } { LengthPrefixedData testString; for (int i = 0; i < 200; i++) { testString.data[i] = 'A' + i % 53; // stay in the 7-bit ASCII range } testString.data[200] = '\0'; testString.length = 200; byte *testData = reinterpret_cast(&testString); doRoundtrip(Arguments(nullptr, cstring("s"), chunk(testData, 205))); } { LengthPrefixedData testDict; testDict.length = 2; testDict.data[0] = 0; testDict.data[1] = 0; // zero padding; dict entries are always 8-aligned. testDict.data[2] = 0; testDict.data[3] = 0; testDict.data[4] = 23; testDict.data[5] = 42; byte *testData = reinterpret_cast(&testDict); doRoundtrip(Arguments(nullptr, cstring("a{yy}"), chunk(testData, 10))); } { byte testData[36] = { 5, // variant signature length '(', 'y', 'g', 'd', ')', '\0', // signature: struct of: byte, signature (easiest because // its length prefix is byte order independent), double 0, // pad to 8-byte boundary for struct 23, // the byte 6, 'i', 'a', '{', 'i', 'v', '}', '\0', // the signature 0, 0, 0, 0, 0, 0, 0, // padding to 24 bytes (next 8-byte boundary) 1, 2, 3, 4, 5, 6, 7, 8, // the double 20, 21, 22, 23 // the int (not part of the variant) }; doRoundtrip(Arguments(nullptr, cstring("vi"), chunk(testData, sizeof(testData)))); } { // Spec says: alignment padding after array length, even if the array contains no data. Test this // with different types and alignment situations. byte testData[40] = { 0, 0, 0, 0, // length of array of uint64s - zero 0, 0, 0, 0, // alignment padding to 8 bytes (= natural alignment of uint64) // ... zero uint64s ... 1, 2, 3, 4, // a uint32 to change the alignment, just to test 0, 0, 0, 0, // length of array of int64s - zero // no alignment padding needed here 0, 0, 0, 0, // length of dict {uint32, uint32} - zero 0, 0, 0, 0, // alignment padding to 8 bytes (= alignment of dict entry) // some data (single bytes) between the arrays to prevent all those zeros from accidentally // looking valid when the Reader is confused. Also upset the alignment a bit. 101, 102, 103, 104, 105, 0, 0, 0, // padding to alignment of array size 0, 0, 0, 0, // length of array of structs - zero 0, 0, 0, 0 // alignment padding to 8 bytes (= alignment of struct) }; doRoundtrip(Arguments(nullptr, cstring("atuaxa{uu}yyyyya(u)"), chunk(testData, sizeof(testData)))); } } static void test_writerMisuse() { // Array { Arguments::Writer writer; writer.beginArray(); writer.endArray(); // wrong, must contain exactly one type TEST(writer.state() == Arguments::InvalidData); } { Arguments::Writer writer; writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); writer.endArray(); // even with no elements it, must contain exactly one type TEST(writer.state() == Arguments::InvalidData); } { Arguments::Writer writer; writer.beginArray(); writer.writeByte(1); writer.endArray(); TEST(writer.state() != Arguments::InvalidData); } { Arguments::Writer writer; writer.beginArray(); writer.endArray(); // wrong, must contain exactly one type TEST(writer.state() == Arguments::InvalidData); } { Arguments::Writer writer; writer.beginArray(); writer.writeByte(1); writer.writeUint16(2); // wrong, different from first element TEST(writer.state() == Arguments::InvalidData); } { Arguments::Writer writer; writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); writer.beginVariant(); writer.endVariant(); // empty variants are okay if and only if inside an empty array writer.endArray(); TEST(writer.state() != Arguments::InvalidData); } // Dict { Arguments::Writer writer; writer.beginDict(); writer.endDict(); // wrong, must contain exactly two types TEST(writer.state() == Arguments::InvalidData); } { Arguments::Writer writer; writer.beginDict(Arguments::Writer::WriteTypesOfEmptyArray); writer.endDict(); // wrong, must contain exactly two types TEST(writer.state() == Arguments::InvalidData); } { Arguments::Writer writer; writer.beginDict(); writer.writeByte(1); writer.endDict(); // wrong, must contain exactly two types TEST(writer.state() == Arguments::InvalidData); } { Arguments::Writer writer; writer.beginDict(Arguments::Writer::WriteTypesOfEmptyArray); writer.writeByte(1); writer.endDict(); // wrong, must contain exactly two types TEST(writer.state() == Arguments::InvalidData); } { Arguments::Writer writer; writer.beginDict(); maybeBeginDictEntry(&writer); writer.writeByte(1); writer.writeByte(2); maybeEndDictEntry(&writer); writer.endDict(); TEST(writer.state() != Arguments::InvalidData); } { Arguments::Writer writer; writer.beginDict(); maybeBeginDictEntry(&writer); writer.writeByte(1); writer.writeByte(2); maybeEndDictEntry(&writer); // second key-value pair maybeBeginDictEntry(&writer); TEST(writer.state() != Arguments::InvalidData); writer.writeUint16(3); // wrong, incompatible with first element TEST(writer.state() == Arguments::InvalidData); } { Arguments::Writer writer; writer.beginDict(); maybeBeginDictEntry(&writer); writer.writeByte(1); writer.writeByte(2); maybeEndDictEntry(&writer); // second key-value pair maybeBeginDictEntry(&writer); writer.writeByte(3); TEST(writer.state() != Arguments::InvalidData); writer.writeUint16(4); // wrong, incompatible with first element TEST(writer.state() == Arguments::InvalidData); } { Arguments::Writer writer; writer.beginDict(); maybeBeginDictEntry(&writer); writer.beginVariant(); // wrong, key type must be basic TEST(writer.state() == Arguments::InvalidData); } // Variant { // this and the next are a baseline to make sure that the following test fails for a good reason Arguments::Writer writer; writer.beginVariant(); writer.writeByte(1); writer.endVariant(); TEST(writer.state() != Arguments::InvalidData); } { Arguments::Writer writer; writer.beginVariant(); writer.endVariant(); TEST(writer.state() == Arguments::InvalidData); } { Arguments::Writer writer; writer.beginVariant(); writer.writeByte(1); writer.writeByte(2); // wrong, a variant may contain only one or zero single complete types TEST(writer.state() == Arguments::InvalidData); } { Arguments::Writer writer; writer.beginStruct(); writer.writeByte(1); TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::InvalidData); // can't finish while inside an aggregate TEST(arg.signature().length == 0); // should not be written on error } } static void addSomeVariantStuff(Arguments::Writer *writer) { // maybe should have typed the following into hackertyper.com to make it look more "legit" ;) static const char *aVeryLongString = "ujfgosuideuvcevfgeoauiyetoraedtmzaubeodtraueonuljfgonuiljofnuilojf" "0ij948h534ownlyejglunh4owny9hw3v9woni09ulgh4wuvcbeginVariant(); writer->beginVariant(); writer->beginVariant(); writer->beginStruct(); writer->writeString(cstring("Smoerebroed smoerebroed")); writer->beginStruct(); writer->writeString(cstring(aVeryLongString)); writer->writeString(cstring("Bork bork bork")); writer->beginVariant(); writer->beginStruct(); writer->writeString(cstring("Quite nesty")); writer->writeObjectPath(cstring("/path/to/object")); writer->writeUint64(234234234); writer->writeByte(2); writer->writeUint64(234234223434); writer->writeUint16(34); writer->endStruct(); writer->endVariant(); writer->beginStruct(); writer->writeByte(34); writer->endStruct(); writer->endStruct(); writer->writeString(cstring("Another string")); writer->endStruct(); writer->endVariant(); writer->endVariant(); writer->endVariant(); } static void test_complicated() { Arguments arg; { Arguments::Writer writer; // NeedMoreData-related bugs are less dangerous inside arrays, so we try to provoke one here; // the reason for arrays preventing failures is that they have a length prefix which enables // and encourages pre-fetching all the array's data before processing *anything* inside the // array. therefore no NeedMoreData state happens while really deserializing the array's // contents. but we exactly want NeedMoreData while in the middle of deserializing something // meaty, specifically variants. see Reader::replaceData(). addSomeVariantStuff(&writer); writer.writeInt64(234234); writer.writeByte(115); writer.beginVariant(); writer.beginDict(); maybeBeginDictEntry(&writer); writer.writeByte(23); writer.beginVariant(); writer.writeString(cstring("twenty-three")); writer.endVariant(); maybeEndDictEntry(&writer); // key-value pair 2 maybeBeginDictEntry(&writer); writer.writeByte(83); writer.beginVariant(); writer.writeObjectPath(cstring("/foo/bar/object")); writer.endVariant(); maybeEndDictEntry(&writer); // key-value pair 3 maybeBeginDictEntry(&writer); writer.writeByte(234); writer.beginVariant(); writer.beginArray(); writer.writeUint16(234); writer.writeUint16(234); writer.writeUint16(234); writer.endArray(); writer.endVariant(); maybeEndDictEntry(&writer); // key-value pair 4 maybeBeginDictEntry(&writer); writer.writeByte(25); writer.beginVariant(); addSomeVariantStuff(&writer); writer.endVariant(); maybeEndDictEntry(&writer); writer.endDict(); writer.endVariant(); writer.writeString("Hello D-Bus!"); writer.beginArray(); writer.writeDouble(1.567898); writer.writeDouble(1.523428); writer.writeDouble(1.621133); writer.writeDouble(1.982342); writer.endArray(); TEST(writer.state() != Arguments::InvalidData); arg = writer.finish(); TEST(writer.state() != Arguments::InvalidData); } doRoundtrip(arg); } static void test_alignment() { { Arguments::Writer writer; writer.writeByte(123); writer.beginArray(); writer.writeByte(64); writer.endArray(); for (int i = 123; i < 150; i++) { writer.writeByte(i); } TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() != Arguments::InvalidData); doRoundtrip(arg); } { Arguments::Writer writer; writer.writeByte(123); writer.beginStruct(); writer.writeByte(110); writer.endStruct(); writer.writeByte(200); Arguments arg = writer.finish(); doRoundtrip(arg); } } static void test_arrayOfVariant() { // non-empty array { Arguments::Writer writer; writer.writeByte(123); writer.beginArray(); writer.beginVariant(); writer.writeByte(64); writer.endVariant(); writer.endArray(); writer.writeByte(123); TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() != Arguments::InvalidData); doRoundtrip(arg); } // empty array { Arguments::Writer writer; writer.writeByte(123); writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); writer.beginVariant(); writer.endVariant(); writer.endArray(); writer.writeByte(123); TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() != Arguments::InvalidData); doRoundtrip(arg); } } static void test_realMessage() { Arguments arg; // non-empty array { Arguments::Writer writer; writer.writeString(cstring("message")); writer.writeString(cstring("konversation")); writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); writer.beginVariant(); writer.endVariant(); writer.endArray(); writer.writeString(cstring("")); writer.writeString(cstring("<fredrikh> he's never on irc")); writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); writer.writeByte(123); // may not show up in the output writer.endArray(); writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); writer.writeString(cstring("dummy, I may not show up in the output!")); writer.endArray(); writer.writeInt32(-1); writer.writeInt64(46137372); TEST(writer.state() != Arguments::InvalidData); arg = writer.finish(); TEST(writer.state() != Arguments::InvalidData); } doRoundtrip(arg); } static void test_isWritingSignatureBug() { { // This was the original test, so it's the one with the comments :) Arguments::Writer writer; writer.beginArray(); writer.beginStruct(); writer.beginDict(); maybeBeginDictEntry(&writer); writer.writeByte(1); writer.writeByte(2); maybeEndDictEntry(&writer); writer.endDict(); // Must add more stuff after the inner dict to ensure that the signature position of the // dict's value is well inside the existing signature in the second dict entry. // See isWritingSignature in Writer::advanceState(). writer.writeUint16(1); writer.writeUint16(2); writer.endStruct(); writer.beginStruct(); writer.beginDict(); maybeBeginDictEntry(&writer); writer.writeByte(1); writer.writeByte(2); maybeEndDictEntry(&writer); // In the second pass, we are definitely NOT writing a new part of the dict signature, // which used to go (that was the bug!!) through a different code path in // Arguments::Writer::advanceState(). maybeBeginDictEntry(&writer); writer.writeByte(1); TEST(writer.state() != Arguments::InvalidData); writer.writeUint16(2); TEST(writer.state() == Arguments::InvalidData); } { // For completeness, do the equivalent of the previous test with an array inside Arguments::Writer writer; writer.beginArray(); writer.beginStruct(); writer.beginArray(); writer.writeByte(1); writer.endArray(); writer.writeUint16(1); writer.writeUint16(2); writer.endStruct(); writer.beginStruct(); writer.beginArray(); writer.writeByte(1); writer.writeByte(1); TEST(writer.state() != Arguments::InvalidData); writer.writeUint16(2); TEST(writer.state() == Arguments::InvalidData); } } static void writeValue(Arguments::Writer *writer, uint32 typeIndex, const void *value) { switch (typeIndex) { case 0: break; case 1: writer->writeByte(*static_cast(value)); break; case 2: writer->writeUint16(*static_cast(value)); break; case 3: writer->writeUint32(*static_cast(value)); break; case 4: writer->writeUint64(*static_cast(value)); break; default: TEST(false); } } static bool checkValue(Arguments::Reader *reader, uint32 typeIndex, const void *expected) { switch (typeIndex) { case 0: return true; case 1: return reader->readByte() == *static_cast(expected); case 2: return reader->readUint16() == *static_cast(expected); case 3: return reader->readUint32() == *static_cast(expected); case 4: return reader->readUint64() == *static_cast(expected); default: TEST(false); } return false; } static void test_primitiveArray() { // TODO also test some error cases static const uint32 testDataSize = 16384; byte testData[testDataSize]; for (uint32 i = 0; i < testDataSize; i++) { testData[i] = i & 0xff; } for (uint i = 0; i < 4; i++) { const bool writeAsPrimitive = i & 0x1; const bool readAsPrimitive = i & 0x2; static const uint32 arrayTypesCount = 5; // those types must be compatible with writeValue() and readValue() static Arguments::IoState arrayTypes[arrayTypesCount] = { Arguments::InvalidData, Arguments::Byte, Arguments::Uint16, Arguments::Uint32, Arguments::Uint64 }; for (uint otherType = 0; otherType < arrayTypesCount; otherType++) { // an array with no type in it is ill-formed, so we start with 1 (Byte) for (uint typeInArray = 1; typeInArray < arrayTypesCount; typeInArray++) { static const uint32 arraySizesCount = 12; static const uint32 arraySizes[arraySizesCount] = { 0, 1, 2, 3, 4, 7, 8, 9, 511, 512, 513, 2048 // dataSize / sizeof(uint64) == 2048 }; for (uint k = 0; k < arraySizesCount; k++) { static const uint64_t otherValue = ~0llu; const uint32 arraySize = arraySizes[k]; const uint32 dataSize = arraySize << (typeInArray - 1); TEST(dataSize <= testDataSize); Arguments arg; { Arguments::Writer writer; // write something before the array to test different starting position alignments writeValue(&writer, otherType, &otherValue); if (writeAsPrimitive) { writer.writePrimitiveArray(arrayTypes[typeInArray], chunk(testData, dataSize)); } else { writer.beginArray(arraySize ? Arguments::Writer::NonEmptyArray : Arguments::Writer::WriteTypesOfEmptyArray); byte *testDataPtr = testData; if (arraySize) { for (uint m = 0; m < arraySize; m++) { writeValue(&writer, typeInArray, testDataPtr); testDataPtr += 1 << (typeInArray - 1); } } else { writeValue(&writer, typeInArray, testDataPtr); } writer.endArray(); } TEST(writer.state() != Arguments::InvalidData); // TEST(writer.state() == Arguments::AnyData); // TODO do we handle AnyData consistently, and do we really need it anyway? writeValue(&writer, otherType, &otherValue); TEST(writer.state() != Arguments::InvalidData); arg = writer.finish(); TEST(writer.state() == Arguments::Finished); } { Arguments::Reader reader(arg); TEST(checkValue(&reader, otherType, &otherValue)); if (readAsPrimitive) { TEST(reader.state() == Arguments::BeginArray); std::pair ret = reader.readPrimitiveArray(); TEST(ret.first == arrayTypes[typeInArray]); TEST(chunksEqual(chunk(testData, dataSize), ret.second)); } else { TEST(reader.state() == Arguments::BeginArray); const bool hasData = reader.beginArray(Arguments::Reader::ReadTypesOnlyIfEmpty); TEST(hasData == (arraySize != 0)); TEST(reader.state() != Arguments::InvalidData); byte *testDataPtr = testData; if (arraySize) { for (uint m = 0; m < arraySize; m++) { TEST(reader.state() != Arguments::InvalidData); TEST(checkValue(&reader, typeInArray, testDataPtr)); TEST(reader.state() != Arguments::InvalidData); testDataPtr += 1 << (typeInArray - 1); } } else { TEST(reader.state() == arrayTypes[typeInArray]); // next: dummy read, necessary to move forward; value is ignored checkValue(&reader, typeInArray, testDataPtr); TEST(reader.state() != Arguments::InvalidData); } TEST(reader.state() == Arguments::EndArray); reader.endArray(); TEST(reader.state() != Arguments::InvalidData); } TEST(reader.state() != Arguments::InvalidData); TEST(checkValue(&reader, otherType, &otherValue)); TEST(reader.state() == Arguments::Finished); } // the data generated here nicely stresses the empty array skipping code if (i == 0 && arraySize < 100) { testReadWithSkip(arg, false); } } } } } } static void test_signatureLengths() { for (int i = 0; i <= 256; i++) { Arguments::Writer writer; for (int j = 0; j < i; j++) { writer.writeByte(255); } if (i == 256) { TEST(writer.state() == Arguments::InvalidData); break; } TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); // The full doRoundtrip() just here makes this whole file take several seconds to execute // instead of a fraction of a second. This way is much quicker. doRoundtripForReal(arg, 2048, false); Arguments argCopy = arg; doRoundtripForReal(argCopy, 2048, false); } for (int i = 1 /* variants may not be empty */; i <= 256; i++) { Arguments::Writer writer; writer.beginVariant(); switch (i) { case 0: TEST(false); case 1: writer.writeByte(255); break; case 2: // "ay" signature is two letters writer.beginArray(); writer.writeByte(255); writer.endArray(); break; default: // (y), (yy), ... writer.beginStruct(); for (int j = strlen("()"); j < i; j++) { writer.writeByte(255); } writer.endStruct(); break; } writer.endVariant(); if (i == 256) { TEST(writer.state() == Arguments::InvalidData); break; } TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); doRoundtripForReal(arg, 2048, false); Arguments argCopy = arg; doRoundtripForReal(argCopy, 2048, false); } } static void test_emptyArrayAndDict() { // Arrays { Arguments::Writer writer; writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); writer.writeByte(0); writer.endArray(); TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); doRoundtrip(arg, false); } { Arguments::Writer writer; writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); writer.writeByte(0); writer.endArray(); writer.endArray(); TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); doRoundtrip(arg, false); } { Arguments::Writer writer; writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); writer.beginStruct(); writer.writeByte(0); writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); writer.writeByte(0); writer.endArray(); writer.endStruct(); writer.endArray(); TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); doRoundtrip(arg, false); } { Arguments::Writer writer; writer.writeUint32(987654321); writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); writer.beginStruct(); writer.writeDouble(0); writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); writer.writeByte(0); writer.endArray(); writer.endStruct(); writer.endArray(); TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); doRoundtrip(arg, false); } { Arguments::Writer writer; writer.writeString(cstring("xy")); writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); writer.beginStruct(); writer.writeUint32(12345678); //It is implicitly clear that an array inside a nil array is also nil //writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); //TODO add a test for writing >1 element in nested empty array - I've tried that and it fails // like it should, but it needs a proper standalone test writer.beginArray(); writer.writeByte(0); writer.endArray(); writer.writeByte(12); writer.endStruct(); writer.endArray(); TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); doRoundtrip(arg, false); } { Arguments::Writer writer; writer.writeString(cstring("xy")); writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); writer.beginStruct(); writer.writeByte(123); writer.beginVariant(); writer.endVariant(); writer.endStruct(); writer.endArray(); TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); doRoundtrip(arg, false); } { for (int i = 0; i < 8; i++) { Arguments::Writer writer; writer.beginStruct(); writer.writeByte(123); writer.beginArray(i ? Arguments::Writer::NonEmptyArray : Arguments::Writer::WriteTypesOfEmptyArray); for (int j = 0; j < std::max(i, 1); j++) { writer.writeUint16(52345); } writer.endArray(); writer.writeByte(123); writer.endStruct(); TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); doRoundtrip(arg, false); } } for (int i = 0; i < 4; i++) { // Test RestartEmptyArrayToWriteTypes and writing an empty array inside the >1st iteration of another array Arguments::Writer writer; writer.beginArray((i & 2) ? Arguments::Writer::WriteTypesOfEmptyArray : Arguments::Writer::NonEmptyArray); // v don't care, the logic error is only in the second iteration writer.beginArray(Arguments::Writer::NonEmptyArray); writer.writeString(cstring("a")); writer.endArray(); if (i & 1) { writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); } else { writer.beginArray(Arguments::Writer::NonEmptyArray); writer.beginArray(Arguments::Writer::RestartEmptyArrayToWriteTypes); } writer.writeString(cstring("a")); writer.endArray(); writer.endArray(); TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); doRoundtrip(arg, false); } for (int i = 0; i < 3; i++) { // Test arrays inside empty arrays and especially peekPrimitiveArray / readPrimitiveArray Arguments::Writer writer; const bool outerEmpty = i > 1; const bool innerEmpty = i > 0; writer.beginArray(outerEmpty ? Arguments::Writer::WriteTypesOfEmptyArray : Arguments::Writer::NonEmptyArray); writer.beginArray(innerEmpty ? Arguments::Writer::WriteTypesOfEmptyArray : Arguments::Writer::NonEmptyArray); // Iterating several times through an empty array is allowed while writing writer.writeUint64(1234); writer.writeUint64(1234); TEST(writer.state() != Arguments::InvalidData); writer.endArray(); writer.endArray(); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); { Arguments::Reader reader(arg); reader.beginArray(); if (outerEmpty) { TEST(reader.state() == Arguments::EndArray); reader.endArray(); } else { TEST(reader.state() == Arguments::BeginArray); // the inner array reader.beginArray(Arguments::Reader::ReadTypesOnlyIfEmpty); TEST(reader.state() == Arguments::Uint64); reader.readUint64(); if (!innerEmpty) { reader.readUint64(); } TEST(reader.state() == Arguments::EndArray); reader.endArray(); reader.endArray(); } TEST(reader.state() == Arguments::Finished); } { Arguments::Reader reader(arg); TEST(reader.peekPrimitiveArray(Arguments::Reader::ReadTypesOnlyIfEmpty) == Arguments::BeginArray); reader.beginArray(Arguments::Reader::ReadTypesOnlyIfEmpty); TEST(reader.state() == Arguments::BeginArray); if (innerEmpty) { TEST(reader.peekPrimitiveArray() == Arguments::BeginArray); } else { TEST(reader.peekPrimitiveArray() == Arguments::Uint64); } TEST(reader.peekPrimitiveArray(Arguments::Reader::ReadTypesOnlyIfEmpty) == Arguments::Uint64); std::pair array = reader.readPrimitiveArray(); TEST(array.first == Arguments::Uint64); if (innerEmpty) { TEST(array.second.ptr == nullptr); TEST(array.second.length == 0); } else { TEST(array.second.length == 2 * sizeof(uint64)); } reader.endArray(); TEST(reader.state() == Arguments::Finished); } } { for (int i = 0; i <= 32; i++) { Arguments::Writer writer; for (int j = 0; j <= i; j++) { writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); if (j == 32) { TEST(writer.state() == Arguments::InvalidData); } } if (i == 32) { TEST(writer.state() == Arguments::InvalidData); break; } writer.writeUint16(52345); for (int j = 0; j <= i; j++) { writer.endArray(); } TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); doRoundtrip(arg, false); } } // Dicts { Arguments::Writer writer; writer.beginDict(Arguments::Writer::WriteTypesOfEmptyArray); maybeBeginDictEntry(&writer); writer.writeByte(0); writer.writeString(cstring("a")); maybeEndDictEntry(&writer); writer.endDict(); TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); doRoundtrip(arg, false); } { Arguments::Writer writer; writer.beginDict(Arguments::Writer::WriteTypesOfEmptyArray); maybeBeginDictEntry(&writer); writer.writeString(cstring("a")); writer.beginVariant(); writer.endVariant(); maybeEndDictEntry(&writer); writer.endDict(); TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); doRoundtrip(arg, false); } { Arguments::Writer writer; writer.beginDict(Arguments::Writer::WriteTypesOfEmptyArray); maybeBeginDictEntry(&writer); writer.writeString(cstring("a")); writer.beginVariant(); writer.endVariant(); maybeEndDictEntry(&writer); maybeBeginDictEntry(&writer); writer.writeString(cstring("a")); writer.beginVariant(); writer.endVariant(); maybeEndDictEntry(&writer); writer.endDict(); TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); doRoundtrip(arg, false); } { Arguments::Writer writer; writer.beginDict(Arguments::Writer::WriteTypesOfEmptyArray); maybeBeginDictEntry(&writer); writer.writeString(cstring("a")); writer.beginVariant(); TEST(writer.state() != Arguments::InvalidData); writer.writeByte(0); // variants in nil arrays may contain data but it will be discarded, i.e. there will only be an // empty variant in the output writer.endVariant(); maybeEndDictEntry(&writer); writer.endDict(); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); doRoundtrip(arg, false); } for (int i = 0; i < 4; i++) { // Test RestartEmptyArrayToWriteTypes and writing an empty dict inside the >1st iteration of another dict Arguments::Writer writer; writer.beginDict((i & 2) ? Arguments::Writer::WriteTypesOfEmptyArray : Arguments::Writer::NonEmptyArray); maybeBeginDictEntry(&writer); writer.writeString(cstring("a")); // v don't care, the logic error is only in the second iteration writer.beginDict(Arguments::Writer::NonEmptyArray); maybeBeginDictEntry(&writer); writer.writeString(cstring("a")); writer.writeInt32(1234); maybeEndDictEntry(&writer); writer.endDict(); maybeEndDictEntry(&writer); maybeBeginDictEntry(&writer); writer.writeString(cstring("a")); if (i & 1) { writer.beginDict(Arguments::Writer::WriteTypesOfEmptyArray); maybeBeginDictEntry(&writer); } else { writer.beginDict(Arguments::Writer::NonEmptyArray); writer.beginDict(Arguments::Writer::RestartEmptyArrayToWriteTypes); maybeBeginDictEntry(&writer); } writer.writeString(cstring("a")); writer.writeInt32(1234); maybeEndDictEntry(&writer); writer.endDict(); maybeEndDictEntry(&writer); writer.endDict(); TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); doRoundtrip(arg, false); } { for (int i = 0; i <= 32; i++) { Arguments::Writer writer; for (int j = 0; j <= i; j++) { writer.beginDict(Arguments::Writer::WriteTypesOfEmptyArray); maybeBeginDictEntry(&writer); if (j == 32) { TEST(writer.state() == Arguments::InvalidData); } writer.writeUint16(12345); } if (i == 32) { TEST(writer.state() == Arguments::InvalidData); break; } writer.writeUint16(52345); for (int j = 0; j <= i; j++) { maybeEndDictEntry(&writer); writer.endDict(); } TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); doRoundtrip(arg, false); } } } static void test_fileDescriptors() { #ifdef __unix__ Arguments::Writer writer; writer.writeUnixFd(200); writer.writeByte(12); writer.writeUnixFd(1); Arguments arg = writer.finish(); doRoundtrip(arg, false); // doRoundtrip only checks the serialized data, but unfortunately file descriptors // are out of band, so check explicitly Arguments::Reader reader(arg); TEST(reader.readUnixFd() == 200); TEST(reader.readByte() == 12); TEST(reader.readUnixFd() == 1); TEST(reader.state() == Arguments::Finished); #endif } +static void test_closeWrongAggregate() +{ + for (int i = 0; i < 8; i++) { + for (int j = 0; j < 4; j++) { + Arguments::Writer writer; + switch (i % 4) { + case 0: writer.beginStruct(); break; + case 1: writer.beginVariant(); break; + case 2: writer.beginArray(); break; + case 3: writer.beginDict(); break; + } + + if (i < 4) { + writer.writeByte(123); + if (i == 3) { + writer.writeByte(123); // value for dict + } + } + + switch (j) { + case 0: writer.endStruct(); break; + case 1: writer.endVariant(); break; + case 2: writer.endArray(); break; + case 3: writer.endDict(); break; + } + + const bool isValid = writer.state() != Arguments::InvalidData; + TEST(isValid == (i == j)); + } + } +} + // TODO: test where we compare data and signature lengths of all combinations of zero/nonzero array // length and long/short type signature, to make sure that the signature is written but not // any data if the array is zero-length. // TODO test empty dicts, too int main(int, char *[]) { test_stringValidation(); test_nesting(); test_roundtrip(); test_writerMisuse(); // TODO test arrays where array length does not align with end of an element // (corruption of serialized data) test_complicated(); test_alignment(); test_arrayOfVariant(); test_realMessage(); test_isWritingSignatureBug(); test_primitiveArray(); test_signatureLengths(); test_emptyArrayAndDict(); test_fileDescriptors(); // TODO (maybe): specific tests for begin/endDictEntry() for both Reader and Writer. - // TODO many more misuse tests for Writer and maybe some for Reader + // TODO more misuse tests for Writer and maybe some for Reader + test_closeWrongAggregate(); + std::cout << "Passed!\n"; }