Commit 648d267d authored by Kenton Varda's avatar Kenton Varda

Finish rewriting WireFormat.

parent 36a60467
...@@ -31,28 +31,23 @@ namespace { ...@@ -31,28 +31,23 @@ namespace {
const int READONLY_SEGMENT_START = 123; const int READONLY_SEGMENT_START = 123;
const FieldDescriptor TEST_FIELDS[2] = { const FieldDescriptor TEST_FIELDS[2] = {
{ 1 * WORDS, 0, 0, FieldSize::FOUR_BYTES, 1, 0, 0, 0 }, { 1*WORDS, 0*REFERENCES, FieldSize::FOUR_BYTES, 0*ELEMENTS, 0*BYTES, 1, 0, 0*BYTES, 0*BITS, nullptr, nullptr },
{ 1 * WORDS, 1, 0, FieldSize::REFERENCE, 1, 0, 0, 0 } { 1*WORDS, 1*REFERENCES, FieldSize::REFERENCE , 0*ELEMENTS, 0*BYTES, 1, 0, 0*BYTES, 0*BITS, nullptr, nullptr }
}; };
extern const StructDescriptor TEST_STRUCT; extern const StructDescriptor TEST_STRUCT;
extern const Descriptor* const TEST_STRUCT_DEFAULT_REFS[1] = { const AlignedData<1> TEST_STRUCT_DEFAULT_VALUE = {
&TEST_STRUCT.base
};
const AlignedData<1> TEST_STRUCT_DEFAULT_DATA = {
{ 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0 } { 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0 }
}; };
const StructDescriptor TEST_STRUCT = { const StructDescriptor TEST_STRUCT = {
{ Descriptor::Kind::STRUCT }, { Descriptor::Kind::STRUCT },
2, FieldNumber(2),
WordCount8(1 * WORDS), 1 * WORDS,
1, 1 * REFERENCES,
TEST_FIELDS, TEST_FIELDS,
TEST_STRUCT_DEFAULT_DATA.bytes, TEST_STRUCT_DEFAULT_VALUE.words
TEST_STRUCT_DEFAULT_REFS
}; };
const int READONLY_SEGMENT_END = 321; const int READONLY_SEGMENT_END = 321;
...@@ -66,10 +61,8 @@ TEST(Descriptors, InReadOnlySegment) { ...@@ -66,10 +61,8 @@ TEST(Descriptors, InReadOnlySegment) {
EXPECT_LE((const void*)&READONLY_SEGMENT_START, (const void*)&TEST_FIELDS); EXPECT_LE((const void*)&READONLY_SEGMENT_START, (const void*)&TEST_FIELDS);
EXPECT_GE((const void*)&READONLY_SEGMENT_END , (const void*)&TEST_FIELDS); EXPECT_GE((const void*)&READONLY_SEGMENT_END , (const void*)&TEST_FIELDS);
EXPECT_LE((const void*)&READONLY_SEGMENT_START, (const void*)&TEST_STRUCT_DEFAULT_DATA); EXPECT_LE((const void*)&READONLY_SEGMENT_START, (const void*)&TEST_STRUCT_DEFAULT_VALUE);
EXPECT_GE((const void*)&READONLY_SEGMENT_END , (const void*)&TEST_STRUCT_DEFAULT_DATA); EXPECT_GE((const void*)&READONLY_SEGMENT_END , (const void*)&TEST_STRUCT_DEFAULT_VALUE);
EXPECT_LE((const void*)&READONLY_SEGMENT_START, (const void*)&TEST_STRUCT_DEFAULT_REFS);
EXPECT_GE((const void*)&READONLY_SEGMENT_END , (const void*)&TEST_STRUCT_DEFAULT_REFS);
EXPECT_LE((const void*)&READONLY_SEGMENT_START, (const void*)&TEST_STRUCT); EXPECT_LE((const void*)&READONLY_SEGMENT_START, (const void*)&TEST_STRUCT);
EXPECT_GE((const void*)&READONLY_SEGMENT_END , (const void*)&TEST_STRUCT); EXPECT_GE((const void*)&READONLY_SEGMENT_END , (const void*)&TEST_STRUCT);
} }
......
...@@ -90,6 +90,10 @@ public: ...@@ -90,6 +90,10 @@ public:
// This constructor was intended to be private, but GCC complains about it being private in a // This constructor was intended to be private, but GCC complains about it being private in a
// bunch of places that don't appear to even call it, so I made it public. Oh well. // bunch of places that don't appear to even call it, so I made it public. Oh well.
template <typename OtherNumber>
inline constexpr UnitRatio(const UnitRatio<OtherNumber, Unit1, Unit2>& other)
: unit1PerUnit2(other.unit1PerUnit2) {}
template <typename OtherNumber, typename Unit3> template <typename OtherNumber, typename Unit3>
inline constexpr UnitRatio<decltype(Number(1)*OtherNumber(1)), Unit3, Unit2> inline constexpr UnitRatio<decltype(Number(1)*OtherNumber(1)), Unit3, Unit2>
operator*(UnitRatio<OtherNumber, Unit3, Unit1> other) { operator*(UnitRatio<OtherNumber, Unit3, Unit1> other) {
...@@ -422,72 +426,38 @@ inline constexpr decltype(BYTES / ELEMENTS) bytesPerElement() { ...@@ -422,72 +426,38 @@ inline constexpr decltype(BYTES / ELEMENTS) bytesPerElement() {
#ifndef __CDT_PARSER__ #ifndef __CDT_PARSER__
template <typename T> template <typename T, typename U>
inline constexpr byte* operator+(byte* ptr, Quantity<T, byte> offset) { inline constexpr U* operator+(U* ptr, Quantity<T, U> offset) {
return ptr + offset / BYTES; return ptr + offset / unit<Quantity<T, U>>();
}
template <typename T>
inline constexpr const byte* operator+(const byte* ptr, Quantity<T, byte> offset) {
return ptr + offset / BYTES;
}
template <typename T>
inline constexpr byte* operator+=(byte*& ptr, Quantity<T, byte> offset) {
return ptr = ptr + offset / BYTES;
}
template <typename T>
inline constexpr const byte* operator+=(const byte* ptr, Quantity<T, byte> offset) {
return ptr = ptr + offset / BYTES;
}
template <typename T>
inline constexpr word* operator+(word* ptr, Quantity<T, word> offset) {
return ptr + offset / WORDS;
}
template <typename T>
inline constexpr const word* operator+(const word* ptr, Quantity<T, word> offset) {
return ptr + offset / WORDS;
}
template <typename T>
inline constexpr word* operator+=(word*& ptr, Quantity<T, word> offset) {
return ptr = ptr + offset / WORDS;
}
template <typename T>
inline constexpr const word* operator+=(const word*& ptr, Quantity<T, word> offset) {
return ptr = ptr + offset / WORDS;
}
template <typename T>
inline constexpr byte* operator-(byte* ptr, Quantity<T, byte> offset) {
return ptr - offset / BYTES;
} }
template <typename T> template <typename T, typename U>
inline constexpr const byte* operator-(const byte* ptr, Quantity<T, byte> offset) { inline constexpr const U* operator+(const U* ptr, Quantity<T, U> offset) {
return ptr - offset / BYTES; return ptr + offset / unit<Quantity<T, U>>();
} }
template <typename T> template <typename T, typename U>
inline constexpr byte* operator-=(byte*& ptr, Quantity<T, byte> offset) { inline constexpr U* operator+=(U*& ptr, Quantity<T, U> offset) {
return ptr = ptr - offset / BYTES; return ptr = ptr + offset / unit<Quantity<T, U>>();
} }
template <typename T> template <typename T, typename U>
inline constexpr const byte* operator-=(const byte* ptr, Quantity<T, byte> offset) { inline constexpr const U* operator+=(const U*& ptr, Quantity<T, U> offset) {
return ptr = ptr - offset / BYTES; return ptr = ptr + offset / unit<Quantity<T, U>>();
} }
template <typename T> template <typename T, typename U>
inline constexpr word* operator-(word* ptr, Quantity<T, word> offset) { inline constexpr U* operator-(U* ptr, Quantity<T, U> offset) {
return ptr - offset / WORDS; return ptr - offset / unit<Quantity<T, U>>();
} }
template <typename T> template <typename T, typename U>
inline constexpr const word* operator-(const word* ptr, Quantity<T, word> offset) { inline constexpr const U* operator-(const U* ptr, Quantity<T, U> offset) {
return ptr - offset / WORDS; return ptr - offset / unit<Quantity<T, U>>();
} }
template <typename T> template <typename T, typename U>
inline constexpr word* operator-=(word*& ptr, Quantity<T, word> offset) { inline constexpr U* operator-=(U*& ptr, Quantity<T, U> offset) {
return ptr = ptr - offset / WORDS; return ptr = ptr - offset / unit<Quantity<T, U>>();
} }
template <typename T> template <typename T, typename U>
inline constexpr const word* operator-=(const word*& ptr, Quantity<T, word> offset) { inline constexpr const U* operator-=(const U*& ptr, Quantity<T, U> offset) {
return ptr = ptr - offset / WORDS; return ptr = ptr - offset / unit<Quantity<T, U>>();
} }
#endif #endif
......
// Copyright (c) 2013, Kenton Varda <temporal@gmail.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "wire-format.h"
#include "descriptor.h"
#include <gtest/gtest.h>
namespace capnproto {
namespace internal {
namespace {
TEST(StructReader, RawData) {
AlignedData<2> data = {
{
// Struct ref, offset = 1, fieldCount = 1, dataSize = 1, referenceCount = 0
0x08, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
// Content for the data segment.
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef
}
};
StructReader reader = StructReader::readRootTrusted(data.words, data.words);
EXPECT_EQ(0xefcdab8967452301ull, reader.getDataField<uint64_t>(0 * ELEMENTS, 321u));
EXPECT_EQ(321u, reader.getDataField<uint64_t>(1 * ELEMENTS, 321u));
EXPECT_EQ(0x67452301u, reader.getDataField<uint32_t>(0 * ELEMENTS, 321u));
EXPECT_EQ(0xefcdab89u, reader.getDataField<uint32_t>(1 * ELEMENTS, 321u));
EXPECT_EQ(321u, reader.getDataField<uint32_t>(2 * ELEMENTS, 321u));
EXPECT_EQ(0x2301u, reader.getDataField<uint16_t>(0 * ELEMENTS, 321u));
EXPECT_EQ(0x6745u, reader.getDataField<uint16_t>(1 * ELEMENTS, 321u));
EXPECT_EQ(0xab89u, reader.getDataField<uint16_t>(2 * ELEMENTS, 321u));
EXPECT_EQ(0xefcdu, reader.getDataField<uint16_t>(3 * ELEMENTS, 321u));
EXPECT_EQ(321u, reader.getDataField<uint16_t>(4 * ELEMENTS, 321u));
// Bits
EXPECT_TRUE (reader.getDataField<bool>(0 * ELEMENTS, false));
EXPECT_FALSE(reader.getDataField<bool>(1 * ELEMENTS, false));
EXPECT_FALSE(reader.getDataField<bool>(2 * ELEMENTS, false));
EXPECT_FALSE(reader.getDataField<bool>(3 * ELEMENTS, false));
EXPECT_FALSE(reader.getDataField<bool>(4 * ELEMENTS, false));
EXPECT_FALSE(reader.getDataField<bool>(5 * ELEMENTS, false));
EXPECT_FALSE(reader.getDataField<bool>(6 * ELEMENTS, false));
EXPECT_FALSE(reader.getDataField<bool>(7 * ELEMENTS, false));
EXPECT_TRUE (reader.getDataField<bool>( 8 * ELEMENTS, false));
EXPECT_TRUE (reader.getDataField<bool>( 9 * ELEMENTS, false));
EXPECT_FALSE(reader.getDataField<bool>(10 * ELEMENTS, false));
EXPECT_FALSE(reader.getDataField<bool>(11 * ELEMENTS, false));
EXPECT_FALSE(reader.getDataField<bool>(12 * ELEMENTS, false));
EXPECT_TRUE (reader.getDataField<bool>(13 * ELEMENTS, false));
EXPECT_FALSE(reader.getDataField<bool>(14 * ELEMENTS, false));
EXPECT_FALSE(reader.getDataField<bool>(15 * ELEMENTS, false));
EXPECT_FALSE(reader.getDataField<bool>(64 * ELEMENTS, false));
EXPECT_TRUE (reader.getDataField<bool>(64 * ELEMENTS, true ));
// Field number guards.
EXPECT_EQ(0xefcdab89u,
reader.getDataFieldCheckingNumber<uint32_t>(FieldNumber(0), 1 * ELEMENTS, 321u));
EXPECT_EQ(321u,
reader.getDataFieldCheckingNumber<uint32_t>(FieldNumber(1), 1 * ELEMENTS, 321u));
EXPECT_TRUE (reader.getDataFieldCheckingNumber<bool>(FieldNumber(0), 0 * ELEMENTS, false));
EXPECT_TRUE (reader.getDataFieldCheckingNumber<bool>(FieldNumber(0), 0 * ELEMENTS, true ));
EXPECT_FALSE(reader.getDataFieldCheckingNumber<bool>(FieldNumber(0), 1 * ELEMENTS, false));
EXPECT_FALSE(reader.getDataFieldCheckingNumber<bool>(FieldNumber(0), 1 * ELEMENTS, true ));
EXPECT_FALSE(reader.getDataFieldCheckingNumber<bool>(FieldNumber(1), 0 * ELEMENTS, false));
EXPECT_TRUE (reader.getDataFieldCheckingNumber<bool>(FieldNumber(1), 0 * ELEMENTS, true ));
}
} // namespace
} // namespace internal
} // namespace capnproto
...@@ -134,29 +134,13 @@ static_assert(REFERENCES * BITS_PER_REFERENCE / BITS_PER_BYTE / BYTES == sizeof( ...@@ -134,29 +134,13 @@ static_assert(REFERENCES * BITS_PER_REFERENCE / BITS_PER_BYTE / BYTES == sizeof(
// ======================================================================================= // =======================================================================================
template <typename T, typename U>
static inline decltype(T() / U()) divRoundingUp(T a, U b) {
return (a + b - 1) / b;
}
template <typename T, typename U>
static CAPNPROTO_ALWAYS_INLINE(T divRoundingUp(Quantity<T, U> a, Quantity<T, U> b));
template <typename T, typename U>
static inline T divRoundingUp(Quantity<T, U> a, Quantity<T, U> b) {
return (a + b - unit<Quantity<T, U>>()) / b;
}
template <typename T, typename T2, typename U, typename U2>
static CAPNPROTO_ALWAYS_INLINE(
decltype(Quantity<T, U>() / UnitRatio<T2, U, U2>())
divRoundingUp(Quantity<T, U> a, UnitRatio<T2, U, U2> b));
template <typename T, typename T2, typename U, typename U2>
static inline decltype(Quantity<T, U>() / UnitRatio<T2, U, U2>())
divRoundingUp(Quantity<T, U> a, UnitRatio<T2, U, U2> b) {
return (a + (unit<Quantity<T2, U2>>() * b - unit<Quantity<T2, U>>())) / b;
}
struct WireHelpers { struct WireHelpers {
static CAPNPROTO_ALWAYS_INLINE(WordCount roundUpToWords(BitCount64 bits)) {
static_assert(sizeof(word) == 8, "This code assumes 64-bit words.");
uint64_t bits2 = bits / BITS;
return ((bits2 >> 6) + ((bits2 & 63) != 0)) * WORDS;
}
static CAPNPROTO_ALWAYS_INLINE(word* allocate( static CAPNPROTO_ALWAYS_INLINE(word* allocate(
WireReference*& ref, SegmentBuilder*& segment, WordCount amount)) { WireReference*& ref, SegmentBuilder*& segment, WordCount amount)) {
word* ptr = segment->allocate(amount); word* ptr = segment->allocate(amount);
...@@ -208,23 +192,6 @@ struct WireHelpers { ...@@ -208,23 +192,6 @@ struct WireHelpers {
return true; return true;
} }
static CAPNPROTO_ALWAYS_INLINE(bool isStructCompatible(
const StructDescriptor* descriptor, const WireReference* ref)) {
if (ref->structRef.fieldCount.get() >= descriptor->fieldCount) {
// The incoming struct has all of the fields that we know about.
return ref->structRef.dataSize.get() >= descriptor->dataSize &&
ref->structRef.refCount.get() >= descriptor->referenceCount;
} else if (ref->structRef.fieldCount.get() > FieldNumber(0)) {
// We know about more fields than the struct has, and the struct is non-empty.
const FieldDescriptor* field = &descriptor->fields[ref->structRef.fieldCount.get().value - 1];
return ref->structRef.dataSize.get() >= field->requiredDataSize &&
ref->structRef.refCount.get() >= field->requiredReferenceCount;
} else {
// The incoming struct has no fields, so is necessarily compatible.
return true;
}
}
static CAPNPROTO_ALWAYS_INLINE(StructBuilder initStructReference( static CAPNPROTO_ALWAYS_INLINE(StructBuilder initStructReference(
FieldNumber fieldCount, WordCount dataSize, WireReferenceCount referenceCount, FieldNumber fieldCount, WordCount dataSize, WireReferenceCount referenceCount,
WireReference* ref, SegmentBuilder* segment)) { WireReference* ref, SegmentBuilder* segment)) {
...@@ -232,27 +199,25 @@ struct WireHelpers { ...@@ -232,27 +199,25 @@ struct WireHelpers {
// Allocate space for the new struct. // Allocate space for the new struct.
word* ptr = allocate(ref, segment, dataSize + referenceCount * WORDS_PER_REFERENCE); word* ptr = allocate(ref, segment, dataSize + referenceCount * WORDS_PER_REFERENCE);
// Advance the pointer to point between the data and reference segments.
ptr += dataSize;
// Initialize the reference. // Initialize the reference.
ref->setStruct(fieldCount, dataSize, referenceCount, segment->getOffsetTo(ptr)); ref->setStruct(fieldCount, dataSize, referenceCount, segment->getOffsetTo(ptr));
// Build the StructBuilder. // Build the StructBuilder.
return StructBuilder(segment, ptr); return StructBuilder(segment, ptr, reinterpret_cast<WireReference*>(ptr + dataSize));
} else { } else {
followFars(ref, segment); followFars(ref, segment);
CAPNPROTO_ASSERT(ref->tag() == WireReference::STRUCT, CAPNPROTO_DEBUG_ASSERT(ref->tag() == WireReference::STRUCT,
"Called getStruct{Field,Element}() but existing reference is not a struct."); "Called getStruct{Field,Element}() but existing reference is not a struct.");
CAPNPROTO_ASSERT(ref->structRef.fieldCount.get() == fieldCount, CAPNPROTO_DEBUG_ASSERT(ref->structRef.fieldCount.get() == fieldCount,
"Trying to update struct with incorrect field count."); "Trying to update struct with incorrect field count.");
CAPNPROTO_ASSERT(ref->structRef.dataSize.get() == dataSize, CAPNPROTO_DEBUG_ASSERT(ref->structRef.dataSize.get() == dataSize,
"Trying to update struct with incorrect data size."); "Trying to update struct with incorrect data size.");
CAPNPROTO_ASSERT(ref->structRef.refCount.get() == referenceCount, CAPNPROTO_DEBUG_ASSERT(ref->structRef.refCount.get() == referenceCount,
"Trying to update struct with incorrect reference count."); "Trying to update struct with incorrect reference count.");
return StructBuilder(segment, segment->getPtrUnchecked(ref->offset())); word* ptr = segment->getPtrUnchecked(ref->offset());
return StructBuilder(segment, ptr, reinterpret_cast<WireReference*>(ptr + dataSize));
} }
} }
...@@ -262,10 +227,9 @@ struct WireHelpers { ...@@ -262,10 +227,9 @@ struct WireHelpers {
CAPNPROTO_DEBUG_ASSERT(elementSize != FieldSize::STRUCT, CAPNPROTO_DEBUG_ASSERT(elementSize != FieldSize::STRUCT,
"Should have called initStructListReference() instead."); "Should have called initStructListReference() instead.");
// Calculate size of the list. Need to cast to uint here because a list can be up to // Calculate size of the list.
// 2**32-1 bits, so int would overflow. Plus uint division by a power of 2 is a bit shift. WordCount wordCount = roundUpToWords(
WordCount wordCount = divRoundingUp( ElementCount64(elementCount) * bitsPerElement(elementSize));
elementCount * bitsPerElement(elementSize), BITS_PER_WORD);
// Allocate the list. // Allocate the list.
word* ptr = allocate(ref, segment, wordCount); word* ptr = allocate(ref, segment, wordCount);
...@@ -321,15 +285,14 @@ struct WireHelpers { ...@@ -321,15 +285,14 @@ struct WireHelpers {
} }
static CAPNPROTO_ALWAYS_INLINE(StructReader readStructReference( static CAPNPROTO_ALWAYS_INLINE(StructReader readStructReference(
SegmentReader* segment, const WireReference* ref, const WireReference* defaultValue, SegmentReader* segment, const WireReference* ref, const word* defaultValue,
int recursionLimit)) { int recursionLimit)) {
const word* ptr; const word* ptr = ref->target();
if (ref == nullptr || ref->isNull()) { if (ref == nullptr || ref->isNull()) {
useDefault: useDefault:
segment = nullptr; segment = nullptr;
ref = defaultValue; ref = reinterpret_cast<const WireReference*>(defaultValue);
ptr = ref->target();
} else if (segment != nullptr) { } else if (segment != nullptr) {
if (CAPNPROTO_EXPECT_FALSE(recursionLimit == 0)) { if (CAPNPROTO_EXPECT_FALSE(recursionLimit == 0)) {
segment->getMessage()->reportInvalidData( segment->getMessage()->reportInvalidData(
...@@ -352,8 +315,6 @@ struct WireHelpers { ...@@ -352,8 +315,6 @@ struct WireHelpers {
WordCount size = ref->structRef.dataSize.get() + WordCount size = ref->structRef.dataSize.get() +
ref->structRef.refCount.get() * WORDS_PER_REFERENCE; ref->structRef.refCount.get() * WORDS_PER_REFERENCE;
ptr = ref->target();
if (CAPNPROTO_EXPECT_FALSE(!segment->containsInterval(ptr, ptr + size))) { if (CAPNPROTO_EXPECT_FALSE(!segment->containsInterval(ptr, ptr + size))) {
segment->getMessage()->reportInvalidData( segment->getMessage()->reportInvalidData(
"Message contained out-of-bounds struct reference."); "Message contained out-of-bounds struct reference.");
...@@ -369,12 +330,12 @@ struct WireHelpers { ...@@ -369,12 +330,12 @@ struct WireHelpers {
} }
static CAPNPROTO_ALWAYS_INLINE(ListReader readListReference( static CAPNPROTO_ALWAYS_INLINE(ListReader readListReference(
SegmentReader* segment, const WireReference* ref, const WireReference* defaultValue, SegmentReader* segment, const WireReference* ref, const word* defaultValue,
FieldSize expectedElementSize, int recursionLimit)) { FieldSize expectedElementSize, int recursionLimit)) {
if (ref == nullptr || ref->isNull()) { if (ref == nullptr || ref->isNull()) {
useDefault: useDefault:
segment = nullptr; segment = nullptr;
ref = defaultValue; ref = reinterpret_cast<const WireReference*>(defaultValue);
} else if (segment != nullptr) { } else if (segment != nullptr) {
if (CAPNPROTO_EXPECT_FALSE(recursionLimit == 0)) { if (CAPNPROTO_EXPECT_FALSE(recursionLimit == 0)) {
segment->getMessage()->reportInvalidData( segment->getMessage()->reportInvalidData(
...@@ -490,7 +451,7 @@ struct WireHelpers { ...@@ -490,7 +451,7 @@ struct WireHelpers {
if (segment != nullptr) { if (segment != nullptr) {
if (CAPNPROTO_EXPECT_FALSE(!segment->containsInterval(ptr, ptr + if (CAPNPROTO_EXPECT_FALSE(!segment->containsInterval(ptr, ptr +
divRoundingUp(ElementCount64(ref->listRef.elementCount()) * step, BITS_PER_WORD)))) { roundUpToWords(ElementCount64(ref->listRef.elementCount()) * step)))) {
segment->getMessage()->reportInvalidData( segment->getMessage()->reportInvalidData(
"Message contained out-of-bounds list reference."); "Message contained out-of-bounds list reference.");
goto useDefault; goto useDefault;
...@@ -547,114 +508,145 @@ struct WireHelpers { ...@@ -547,114 +508,145 @@ struct WireHelpers {
// ======================================================================================= // =======================================================================================
#if 0 StructBuilder StructBuilder::initRoot(SegmentBuilder* segment, word* location,
FieldNumber fieldCount, WordCount dataSize, WireReferenceCount referenceCount) {
return WireHelpers::initStructReference(
fieldCount, dataSize, referenceCount,
reinterpret_cast<WireReference*>(location), segment);
}
StructBuilder StructBuilder::getStructFieldInternal(int refIndex) const { StructBuilder StructBuilder::getStructField(
WireReferenceCount refIndex, FieldNumber fieldCount,
WordCount dataSize, WireReferenceCount referenceCount) const {
return WireHelpers::initStructReference( return WireHelpers::initStructReference(
descriptor->defaultReferences[refIndex]->asStruct(), fieldCount, dataSize, referenceCount,
reinterpret_cast<WireReference*>(ptr) + refIndex, segment); references + refIndex, segment);
} }
ListBuilder StructBuilder::initListFieldInternal(int refIndex, uint32_t elementCount) const { ListBuilder StructBuilder::initListField(
WireReferenceCount refIndex, FieldSize elementSize, ElementCount elementCount) const {
return WireHelpers::initListReference( return WireHelpers::initListReference(
descriptor->defaultReferences[refIndex]->asList(), references + refIndex, segment,
reinterpret_cast<WireReference*>(ptr) + refIndex, segment, elementCount); elementCount, elementSize);
}
ListBuilder StructBuilder::initStructListField(
WireReferenceCount refIndex, ElementCount elementCount,
FieldNumber fieldCount, WordCount dataSize, WireReferenceCount referenceCount) const {
return WireHelpers::initStructListReference(
references + refIndex, segment,
elementCount, fieldCount, dataSize, referenceCount);
} }
ListBuilder StructBuilder::getListFieldInternal(int refIndex) const { ListBuilder StructBuilder::getListField(WireReferenceCount refIndex, FieldSize elementSize) const {
return WireHelpers::getWritableListReference( return WireHelpers::getWritableListReference(
descriptor->defaultReferences[refIndex]->asList(), elementSize, references + refIndex, segment);
reinterpret_cast<WireReference*>(ptr) + refIndex, segment);
} }
StructReader StructBuilder::asReader() const { StructReader StructBuilder::asReader() const {
return StructReader(descriptor, segment, ptr, descriptor->defaultData, // HACK: We just give maxed-out field and reference counts because they are only used for
descriptor->fieldCount, 0 * BITS, std::numeric_limits<int>::max()); // checking for field presence.
static_assert(sizeof(WireReference::structRef.fieldCount) == 1,
"Has the maximum field count changed?");
static_assert(sizeof(WireReference::structRef.refCount) == 1,
"Has the maximum reference count changed?");
return StructReader(segment, data, FieldNumber(0xff),
intervalLength(data, reinterpret_cast<word*>(references)), 0xff * REFERENCES,
0 * BITS, std::numeric_limits<int>::max());
} }
StructReader StructReader::getStructFieldInternal(int fieldNumber, unsigned int refIndex) const { StructReader StructReader::readRootTrusted(const word* location, const word* defaultValue) {
return WireHelpers::readStructReference( return WireHelpers::readStructReference(nullptr, reinterpret_cast<const WireReference*>(location),
descriptor->defaultReferences[refIndex]->asStruct(), defaultValue, std::numeric_limits<int>::max());
fieldNumber < fieldCount
? reinterpret_cast<const WireReference*>(ptr) + refIndex
: nullptr,
segment, recursionLimit);
} }
ListReader StructReader::getListFieldInternal(int fieldNumber, unsigned int refIndex) const { StructReader StructReader::readRoot(const word* location, const word* defaultValue,
SegmentReader* segment, int recursionLimit) {
if (segment->containsInterval(location, location + 1 * REFERENCES * WORDS_PER_REFERENCE)) {
segment->getMessage()->reportInvalidData("Root location out-of-bounds.");
location = nullptr;
}
return WireHelpers::readStructReference(segment, reinterpret_cast<const WireReference*>(location),
defaultValue, recursionLimit);
}
StructReader StructReader::getStructField(
WireReferenceCount refIndex, const word* defaultValue) const {
const WireReference* ref = refIndex >= referenceCount ? nullptr :
reinterpret_cast<const WireReference*>(
reinterpret_cast<const word*>(ptr) + dataSize) + refIndex;
return WireHelpers::readStructReference(segment, ref, defaultValue, recursionLimit);
}
ListReader StructReader::getListField(
WireReferenceCount refIndex, FieldSize expectedElementSize, const word* defaultValue) const {
const WireReference* ref = refIndex >= referenceCount ? nullptr :
reinterpret_cast<const WireReference*>(
reinterpret_cast<const word*>(ptr) + dataSize) + refIndex;
return WireHelpers::readListReference( return WireHelpers::readListReference(
descriptor->defaultReferences[refIndex]->asList(), segment, ref, defaultValue, expectedElementSize, recursionLimit);
fieldNumber < fieldCount
? reinterpret_cast<const WireReference*>(ptr) + refIndex
: nullptr,
segment, recursionLimit);
} }
StructBuilder ListBuilder::getStructElementInternal( StructBuilder ListBuilder::getStructElement(
unsigned int index, WordCount elementSize) const { ElementCount index, decltype(WORDS/ELEMENTS) elementSize, WordCount structDataSize) const {
return StructBuilder( word* structPtr = ptr + elementSize * index;
descriptor->elementDescriptor->asStruct(), segment, return StructBuilder(segment, structPtr,
ptr + elementSize * index); reinterpret_cast<WireReference*>(structPtr + structDataSize));
} }
ListBuilder ListBuilder::initListElementInternal(unsigned int index, uint32_t size) const { ListBuilder ListBuilder::initListElement(
WireReferenceCount index, FieldSize elementSize, ElementCount elementCount) const {
return WireHelpers::initListReference( return WireHelpers::initListReference(
descriptor->elementDescriptor->asList(), reinterpret_cast<WireReference*>(ptr) + index, segment,
reinterpret_cast<WireReference*>(ptr) + index, elementCount, elementSize);
segment, size);
} }
ListBuilder ListBuilder::getListElementInternal(unsigned int index) const { ListBuilder ListBuilder::initStructListElement(
return WireHelpers::getWritableListReference( WireReferenceCount index, ElementCount elementCount,
descriptor->elementDescriptor->asList(), FieldNumber fieldCount, WordCount dataSize, WireReferenceCount referenceCount) const {
reinterpret_cast<WireReference*>(ptr) + index, return WireHelpers::initStructListReference(
segment); reinterpret_cast<WireReference*>(ptr) + index, segment,
elementCount, fieldCount, dataSize, referenceCount);
} }
ListReader ListBuilder::asReader() const { ListBuilder ListBuilder::getListElement(WireReferenceCount index, FieldSize elementSize) const {
return ListReader(descriptor, segment, ptr, elementCount, return WireHelpers::getWritableListReference(
sizeInBits(descriptor->elementSize), elementSize, reinterpret_cast<WireReference*>(ptr) + index, segment);
descriptor->elementSize == FieldSize::STRUCT
? descriptor->elementDescriptor->asStruct()->fieldCount : 0,
1 << 30);
} }
StructReader ListReader::getStructElementInternal(unsigned int index) const { ListReader ListBuilder::asReader(FieldSize elementSize) const {
const StructDescriptor* elementDescriptor; return ListReader(segment, ptr, elementCount, bitsPerElement(elementSize),
if (ptr == nullptr) { std::numeric_limits<int>::max());
elementDescriptor = descriptor->defaultReferences()[index]->asStruct(); }
} else {
elementDescriptor = descriptor->elementDescriptor->asStruct();
if (CAPNPROTO_EXPECT_FALSE(recursionLimit == 0)) {
segment->getMessage()->reportInvalidData(
"Message is too deeply-nested or contains cycles.");
} else {
BitCount64 indexBit = static_cast<uint64_t>(index) * stepBits;
return StructReader(
elementDescriptor, segment,
reinterpret_cast<const byte*>(ptr) + indexBit / BITS_PER_BYTE,
descriptor->defaultData, structFieldCount, indexBit % BITS_PER_BYTE,
recursionLimit - 1);
}
}
return StructReader(elementDescriptor, segment, nullptr, descriptor->defaultData, 0, 0 * BITS, 0); ListReader ListBuilder::asReader(FieldNumber fieldCount, WordCount dataSize,
WireReferenceCount referenceCount) const {
return ListReader(segment, ptr, elementCount,
(dataSize + referenceCount * WORDS_PER_REFERENCE) * BITS_PER_WORD / ELEMENTS,
fieldCount, dataSize, referenceCount, std::numeric_limits<int>::max());
} }
ListReader ListReader::getListElementInternal(unsigned int index) const { StructReader ListReader::getStructElement(ElementCount index, const word* defaultValue) const {
if (ptr == nullptr) { if (CAPNPROTO_EXPECT_FALSE((segment != nullptr) & (recursionLimit == 0))) {
return WireHelpers::readListReference( segment->getMessage()->reportInvalidData(
descriptor->defaultReferences()[index]->asList(), "Message is too deeply-nested or contains cycles.");
nullptr, segment, recursionLimit); return WireHelpers::readStructReference(nullptr, nullptr, defaultValue, recursionLimit);
} else { } else {
return WireHelpers::readListReference( BitCount64 indexBit = ElementCount64(index) * stepBits;
descriptor->elementDescriptor->asList(), return StructReader(
reinterpret_cast<const WireReference*>(ptr) + segment, reinterpret_cast<const byte*>(ptr) + indexBit / BITS_PER_BYTE,
index * (stepBits / BITS_PER_WORD / WORDS_PER_REFERENCE), structFieldCount, structDataSize, structReferenceCount, indexBit % BITS_PER_BYTE,
segment, recursionLimit); recursionLimit - 1);
} }
} }
#endif
ListReader ListReader::getListElement(
WireReferenceCount index, FieldSize expectedElementSize, const word* defaultValue) const {
return WireHelpers::readListReference(
segment, reinterpret_cast<const WireReference*>(ptr) + index,
defaultValue, expectedElementSize, recursionLimit);
}
} // namespace internal } // namespace internal
} // namespace capnproto } // namespace capnproto
...@@ -81,9 +81,10 @@ private: ...@@ -81,9 +81,10 @@ private:
class StructBuilder { class StructBuilder {
public: public:
inline StructBuilder(): segment(nullptr), ptr(nullptr) {} inline StructBuilder(): segment(nullptr), data(nullptr), references(nullptr) {}
static StructBuilder initRoot(SegmentBuilder* segment, word* location); static StructBuilder initRoot(SegmentBuilder* segment, word* location,
FieldNumber fieldCount, WordCount dataSize, WireReferenceCount referenceCount);
template <typename T> template <typename T>
CAPNPROTO_ALWAYS_INLINE(T getDataField(ElementCount offset) const); CAPNPROTO_ALWAYS_INLINE(T getDataField(ElementCount offset) const);
...@@ -95,16 +96,21 @@ public: ...@@ -95,16 +96,21 @@ public:
ElementCount offset, typename NoInfer<T>::Type value) const); ElementCount offset, typename NoInfer<T>::Type value) const);
// Set the data field value at the given offset. // Set the data field value at the given offset.
inline StructBuilder getStructField(WireReferenceCount refIndex) const; StructBuilder getStructField(
WireReferenceCount refIndex, FieldNumber fieldCount,
WordCount dataSize, WireReferenceCount referenceCount) const;
// Get the struct field at the given index in the reference segment. Allocates space for the // Get the struct field at the given index in the reference segment. Allocates space for the
// struct if necessary. // struct if necessary.
inline ListBuilder initListField(WireReferenceCount refIndex, FieldSize fieldSize, ListBuilder initListField(WireReferenceCount refIndex, FieldSize elementSize,
ElementCount elementCount) const; ElementCount elementCount) const;
ListBuilder initStructListField(
WireReferenceCount refIndex, ElementCount elementCount,
FieldNumber fieldCount, WordCount dataSize, WireReferenceCount referenceCount) const;
// Allocate a new list of the given size for the field at the given index in the reference // Allocate a new list of the given size for the field at the given index in the reference
// segment, and return a pointer to it. // segment, and return a pointer to it.
inline ListBuilder getListField(WireReferenceCount refIndex) const; ListBuilder getListField(WireReferenceCount refIndex, FieldSize elementSize) const;
// Get the already-allocated list field for the given reference index. Returns an empty list -- // Get the already-allocated list field for the given reference index. Returns an empty list --
// NOT necessarily the default value -- if the field is not initialized. // NOT necessarily the default value -- if the field is not initialized.
...@@ -112,11 +118,12 @@ public: ...@@ -112,11 +118,12 @@ public:
// Get a StructReader pointing at the same memory. // Get a StructReader pointing at the same memory.
private: private:
SegmentBuilder* segment; // Memory segment in which the struct resides. SegmentBuilder* segment; // Memory segment in which the struct resides.
word* ptr; // Pointer to the encoded struct (data followed by references). word* data; // Pointer to the encoded data.
WireReference* references; // Pointer to the encoded references.
inline StructBuilder(SegmentBuilder* segment, word* ptr) inline StructBuilder(SegmentBuilder* segment, word* data, WireReference* references)
: segment(segment), ptr(ptr) {} : segment(segment), data(data), references(references) {}
friend class ListBuilder; friend class ListBuilder;
friend struct WireHelpers; friend struct WireHelpers;
...@@ -128,6 +135,10 @@ public: ...@@ -128,6 +135,10 @@ public:
: segment(nullptr), ptr(nullptr), fieldCount(0), dataSize(0), referenceCount(0), : segment(nullptr), ptr(nullptr), fieldCount(0), dataSize(0), referenceCount(0),
bit0Offset(0 * BITS), recursionLimit(0) {} bit0Offset(0 * BITS), recursionLimit(0) {}
static StructReader readRootTrusted(const word* location, const word* defaultValue);
static StructReader readRoot(const word* location, const word* defaultValue,
SegmentReader* segment, int recursionLimit);
template <typename T> template <typename T>
CAPNPROTO_ALWAYS_INLINE( CAPNPROTO_ALWAYS_INLINE(
T getDataField(ElementCount offset, typename NoInfer<T>::Type defaultValue) const); T getDataField(ElementCount offset, typename NoInfer<T>::Type defaultValue) const);
...@@ -143,11 +154,13 @@ public: ...@@ -143,11 +154,13 @@ public:
// with later numbers, and therefore the offset being in-bounds alone does not prove that the // with later numbers, and therefore the offset being in-bounds alone does not prove that the
// struct contains the field. // struct contains the field.
inline StructReader getStructField(WireReferenceCount refIndex) const; StructReader getStructField(WireReferenceCount refIndex, const word* defaultValue) const;
// Get the struct field at the given index in the reference segment, or the default value if not // Get the struct field at the given index in the reference segment, or the default value if not
// initialized. // initialized. defaultValue will be interpreted as a trusted message -- it must point at a
// struct reference, which in turn points at the struct value.
inline ListReader getListField(WireReferenceCount refIndex, FieldSize expectedElementSize) const; ListReader getListField(WireReferenceCount refIndex, FieldSize expectedElementSize,
const word* defaultValue) const;
// Get the list field at the given index in the reference segment, or the default value if not // Get the list field at the given index in the reference segment, or the default value if not
// initialized. // initialized.
...@@ -208,20 +221,27 @@ public: ...@@ -208,20 +221,27 @@ public:
ElementCount index, typename NoInfer<T>::Type value) const); ElementCount index, typename NoInfer<T>::Type value) const);
// Set the element at the given index. // Set the element at the given index.
CAPNPROTO_ALWAYS_INLINE(StructBuilder getStructElement( StructBuilder getStructElement(
ElementCount index, decltype(WORDS/ELEMENTS) elementSize) const); ElementCount index, decltype(WORDS/ELEMENTS) elementSize, WordCount structDataSize) const;
// Get the struct element at the given index. elementSize is the size, in 64-bit words, of // Get the struct element at the given index. elementSize is the size, in 64-bit words, of
// each element. // each element.
CAPNPROTO_ALWAYS_INLINE( ListBuilder initListElement(
ListBuilder initListElement(WireReferenceCount index, ElementCount size) const); WireReferenceCount index, FieldSize elementSize, ElementCount elementCount) const;
ListBuilder initStructListElement(
WireReferenceCount index, ElementCount elementCount,
FieldNumber fieldCount, WordCount dataSize, WireReferenceCount referenceCount) const;
// Create a new list element of the given size at the given index. // Create a new list element of the given size at the given index.
CAPNPROTO_ALWAYS_INLINE(ListBuilder getListElement(WireReferenceCount index) const); ListBuilder getListElement(WireReferenceCount index, FieldSize elementSize) const;
// Get the existing list element at the given index. // Get the existing list element at the given index.
ListReader asReader() const; ListReader asReader(FieldSize elementSize) const;
// Get a ListReader pointing at the same memory. // Get a ListReader pointing at the same memory. Use this version only for non-struct lists.
ListReader asReader(FieldNumber fieldCount, WordCount dataSize,
WireReferenceCount referenceCount) const;
// Get a ListReader pointing at the same memory. Use this version only for struct lists.
private: private:
SegmentBuilder* segment; // Memory segment in which the list resides. SegmentBuilder* segment; // Memory segment in which the list resides.
...@@ -249,11 +269,11 @@ public: ...@@ -249,11 +269,11 @@ public:
CAPNPROTO_ALWAYS_INLINE(T getDataElement(ElementCount index) const); CAPNPROTO_ALWAYS_INLINE(T getDataElement(ElementCount index) const);
// Get the element of the given type at the given index. // Get the element of the given type at the given index.
CAPNPROTO_ALWAYS_INLINE(StructReader getStructElement(ElementCount index) const); StructReader getStructElement(ElementCount index, const word* defaultValue) const;
// Get the struct element at the given index. // Get the struct element at the given index.
CAPNPROTO_ALWAYS_INLINE(ListReader getListElement( ListReader getListElement(WireReferenceCount index, FieldSize expectedElementSize,
WireReferenceCount index, FieldSize expectedElementSize) const); const word* defaultValue) const;
// Get the list element at the given index. // Get the list element at the given index.
private: private:
...@@ -273,7 +293,9 @@ private: ...@@ -273,7 +293,9 @@ private:
FieldNumber structFieldCount; FieldNumber structFieldCount;
WordCount structDataSize; WordCount structDataSize;
WireReferenceCount structReferenceCount; WireReferenceCount structReferenceCount;
// If the elements are structs, the properties of the struct. // If the elements are structs, the properties of the struct. The field and reference counts are
// only used to check for field presence; the data size is also used to compute the reference
// pointer.
int recursionLimit; int recursionLimit;
// Limits the depth of message structures to guard against stack-overflow-based DoS attacks. // Limits the depth of message structures to guard against stack-overflow-based DoS attacks.
...@@ -302,26 +324,26 @@ private: ...@@ -302,26 +324,26 @@ private:
template <typename T> template <typename T>
inline T StructBuilder::getDataField(ElementCount offset) const { inline T StructBuilder::getDataField(ElementCount offset) const {
return reinterpret_cast<WireValue<T>*>(ptr)[offset / ELEMENTS].get(); return reinterpret_cast<WireValue<T>*>(data)[offset / ELEMENTS].get();
} }
template <> template <>
inline bool StructBuilder::getDataField<bool>(ElementCount offset) const { inline bool StructBuilder::getDataField<bool>(ElementCount offset) const {
BitCount boffset = offset * (1 * BITS / ELEMENTS); BitCount boffset = offset * (1 * BITS / ELEMENTS);
byte* b = reinterpret_cast<byte*>(ptr) + boffset / BITS_PER_BYTE; byte* b = reinterpret_cast<byte*>(data) + boffset / BITS_PER_BYTE;
return (*reinterpret_cast<uint8_t*>(b) & (1 << (boffset % BITS_PER_BYTE / BITS))) != 0; return (*reinterpret_cast<uint8_t*>(b) & (1 << (boffset % BITS_PER_BYTE / BITS))) != 0;
} }
template <typename T> template <typename T>
inline void StructBuilder::setDataField( inline void StructBuilder::setDataField(
ElementCount offset, typename NoInfer<T>::Type value) const { ElementCount offset, typename NoInfer<T>::Type value) const {
reinterpret_cast<WireValue<T>*>(ptr)[offset / ELEMENTS].set(value); reinterpret_cast<WireValue<T>*>(data)[offset / ELEMENTS].set(value);
} }
template <> template <>
inline void StructBuilder::setDataField<bool>(ElementCount offset, bool value) const { inline void StructBuilder::setDataField<bool>(ElementCount offset, bool value) const {
BitCount boffset = offset * (1 * BITS / ELEMENTS); BitCount boffset = offset * (1 * BITS / ELEMENTS);
byte* b = reinterpret_cast<byte*>(ptr) + boffset / BITS_PER_BYTE; byte* b = reinterpret_cast<byte*>(data) + boffset / BITS_PER_BYTE;
uint bitnum = boffset % BITS_PER_BYTE / BITS; uint bitnum = boffset % BITS_PER_BYTE / BITS;
*reinterpret_cast<uint8_t*>(b) = (*reinterpret_cast<uint8_t*>(b) & ~(1 << bitnum)) *reinterpret_cast<uint8_t*>(b) = (*reinterpret_cast<uint8_t*>(b) & ~(1 << bitnum))
| (static_cast<uint8_t>(value) << bitnum); | (static_cast<uint8_t>(value) << bitnum);
...@@ -332,7 +354,7 @@ inline void StructBuilder::setDataField<bool>(ElementCount offset, bool value) c ...@@ -332,7 +354,7 @@ inline void StructBuilder::setDataField<bool>(ElementCount offset, bool value) c
template <typename T> template <typename T>
T StructReader::getDataField(ElementCount offset, typename NoInfer<T>::Type defaultValue) const { T StructReader::getDataField(ElementCount offset, typename NoInfer<T>::Type defaultValue) const {
if (offset * bytesPerElement<T>() < dataSize * BYTES_PER_WORD) { if (offset * bytesPerElement<T>() < dataSize * BYTES_PER_WORD) {
return reinterpret_cast<WireValue<T>*>(ptr)[offset / ELEMENTS].get(); return reinterpret_cast<const WireValue<T>*>(ptr)[offset / ELEMENTS].get();
} else { } else {
return defaultValue; return defaultValue;
} }
...@@ -359,7 +381,7 @@ T StructReader::getDataFieldCheckingNumber( ...@@ -359,7 +381,7 @@ T StructReader::getDataFieldCheckingNumber(
// Intentionally use & rather than && to reduce branches. // Intentionally use & rather than && to reduce branches.
if ((fieldNumber < fieldCount) & if ((fieldNumber < fieldCount) &
(offset * bytesPerElement<T>() < dataSize * BYTES_PER_WORD)) { (offset * bytesPerElement<T>() < dataSize * BYTES_PER_WORD)) {
return reinterpret_cast<WireValue<T>*>(ptr)[offset / ELEMENTS].get(); return reinterpret_cast<const WireValue<T>*>(ptr)[offset / ELEMENTS].get();
} else { } else {
return defaultValue; return defaultValue;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment