serialize.c++ 10.5 KB
Newer Older
Kenton Varda's avatar
Kenton Varda committed
1 2
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License:
Kenton Varda's avatar
Kenton Varda committed
3
//
Kenton Varda's avatar
Kenton Varda committed
4 5 6 7 8 9
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
Kenton Varda's avatar
Kenton Varda committed
10
//
Kenton Varda's avatar
Kenton Varda committed
11 12
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
Kenton Varda's avatar
Kenton Varda committed
13
//
Kenton Varda's avatar
Kenton Varda committed
14 15 16 17 18 19 20
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
Kenton Varda's avatar
Kenton Varda committed
21 22

#include "serialize.h"
23
#include "layout.h"
Kenton Varda's avatar
Kenton Varda committed
24
#include <kj/debug.h>
25
#include <exception>
Kenton Varda's avatar
Kenton Varda committed
26

27
namespace capnp {
Kenton Varda's avatar
Kenton Varda committed
28

29 30
FlatArrayMessageReader::FlatArrayMessageReader(
    kj::ArrayPtr<const word> array, ReaderOptions options)
31
    : MessageReader(options), end(array.end()) {
Kenton Varda's avatar
Kenton Varda committed
32 33 34 35 36
  if (array.size() < 1) {
    // Assume empty message.
    return;
  }

37 38
  const _::WireValue<uint32_t>* table =
      reinterpret_cast<const _::WireValue<uint32_t>*>(array.begin());
Kenton Varda's avatar
Kenton Varda committed
39

40
  uint segmentCount = table[0].get() + 1;
Kenton Varda's avatar
Kenton Varda committed
41 42
  size_t offset = segmentCount / 2u + 1u;

43
  KJ_REQUIRE(array.size() >= offset, "Message ends prematurely in segment table.") {
Kenton Varda's avatar
Kenton Varda committed
44 45 46
    return;
  }

47 48
  {
    uint segmentSize = table[1].get();
Kenton Varda's avatar
Kenton Varda committed
49

50 51 52 53
    KJ_REQUIRE(array.size() >= offset + segmentSize,
               "Message ends prematurely in first segment.") {
      return;
    }
Kenton Varda's avatar
Kenton Varda committed
54

55 56 57
    segment0 = array.slice(offset, offset + segmentSize);
    offset += segmentSize;
  }
Kenton Varda's avatar
Kenton Varda committed
58 59

  if (segmentCount > 1) {
Kenton Varda's avatar
Kenton Varda committed
60
    moreSegments = kj::heapArray<kj::ArrayPtr<const word>>(segmentCount - 1);
Kenton Varda's avatar
Kenton Varda committed
61 62 63 64

    for (uint i = 1; i < segmentCount; i++) {
      uint segmentSize = table[i + 1].get();

65
      KJ_REQUIRE(array.size() >= offset + segmentSize, "Message ends prematurely.") {
Kenton Varda's avatar
Kenton Varda committed
66 67 68 69 70 71 72 73
        moreSegments = nullptr;
        return;
      }

      moreSegments[i - 1] = array.slice(offset, offset + segmentSize);
      offset += segmentSize;
    }
  }
74 75

  end = array.begin() + offset;
Kenton Varda's avatar
Kenton Varda committed
76 77
}

78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
size_t expectedSizeInWordsFromPrefix(kj::ArrayPtr<const word> array) {
  if (array.size() < 1) {
    // All messages are at least one word.
    return 1;
  }

  const _::WireValue<uint32_t>* table =
      reinterpret_cast<const _::WireValue<uint32_t>*>(array.begin());

  uint segmentCount = table[0].get() + 1;
  size_t offset = segmentCount / 2u + 1u;

  // If the array is too small to contain the full segment table, truncate segmentCount to just
  // what is available.
  segmentCount = kj::min(segmentCount, array.size() * 2 - 1u);

  size_t totalSize = offset;
  for (uint i = 0; i < segmentCount; i++) {
    totalSize += table[i + 1].get();
  }
  return totalSize;
}

101
kj::ArrayPtr<const word> FlatArrayMessageReader::getSegment(uint id) {
Kenton Varda's avatar
Kenton Varda committed
102 103 104 105 106 107 108 109 110
  if (id == 0) {
    return segment0;
  } else if (id <= moreSegments.size()) {
    return moreSegments[id - 1];
  } else {
    return nullptr;
  }
}

111 112 113 114 115 116 117
kj::ArrayPtr<const word> initMessageBuilderFromFlatArrayCopy(
    kj::ArrayPtr<const word> array, MessageBuilder& target, ReaderOptions options) {
  FlatArrayMessageReader reader(array, options);
  target.setRoot(reader.getRoot<AnyPointer>());
  return kj::arrayPtr(reader.getEnd(), array.end());
}

118
kj::Array<word> messageToFlatArray(kj::ArrayPtr<const kj::ArrayPtr<const word>> segments) {
119
  kj::Array<word> result = kj::heapArray<word>(computeSerializedSizeInWords(segments));
Kenton Varda's avatar
Kenton Varda committed
120

121 122
  _::WireValue<uint32_t>* table =
      reinterpret_cast<_::WireValue<uint32_t>*>(result.begin());
Kenton Varda's avatar
Kenton Varda committed
123

124 125 126
  // We write the segment count - 1 because this makes the first word zero for single-segment
  // messages, improving compression.  We don't bother doing this with segment sizes because
  // one-word segments are rare anyway.
127
  table[0].set(segments.size() - 1);
Kenton Varda's avatar
Kenton Varda committed
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144

  for (uint i = 0; i < segments.size(); i++) {
    table[i + 1].set(segments[i].size());
  }

  if (segments.size() % 2 == 0) {
    // Set padding byte.
    table[segments.size() + 1].set(0);
  }

  word* dst = result.begin() + segments.size() / 2 + 1;

  for (auto& segment: segments) {
    memcpy(dst, segment.begin(), segment.size() * sizeof(word));
    dst += segment.size();
  }

145
  KJ_DASSERT(dst == result.end(), "Buffer overrun/underrun bug in code above.");
Kenton Varda's avatar
Kenton Varda committed
146

Kenton Varda's avatar
Kenton Varda committed
147
  return kj::mv(result);
Kenton Varda's avatar
Kenton Varda committed
148 149
}

150 151 152 153 154 155 156 157 158 159 160 161
size_t computeSerializedSizeInWords(kj::ArrayPtr<const kj::ArrayPtr<const word>> segments) {
  KJ_REQUIRE(segments.size() > 0, "Tried to serialize uninitialized message.");

  size_t totalSize = segments.size() / 2 + 1;

  for (auto& segment: segments) {
    totalSize += segment.size();
  }

  return totalSize;
}

Kenton Varda's avatar
Kenton Varda committed
162 163
// =======================================================================================

164
InputStreamMessageReader::InputStreamMessageReader(
165
    kj::InputStream& inputStream, ReaderOptions options, kj::ArrayPtr<word> scratchSpace)
166
    : MessageReader(options), inputStream(inputStream), readPos(nullptr) {
167
  _::WireValue<uint32_t> firstWord[2];
Kenton Varda's avatar
Kenton Varda committed
168

Kenton Varda's avatar
Kenton Varda committed
169
  inputStream.read(firstWord, sizeof(firstWord));
Kenton Varda's avatar
Kenton Varda committed
170

171
  uint segmentCount = firstWord[0].get() + 1;
172
  uint segment0Size = segmentCount == 0 ? 0 : firstWord[1].get();
Kenton Varda's avatar
Kenton Varda committed
173

174
  size_t totalWords = segment0Size;
Kenton Varda's avatar
Kenton Varda committed
175

176
  // Reject messages with too many segments for security reasons.
177
  KJ_REQUIRE(segmentCount < 512, "Message has too many segments.") {
178 179
    segmentCount = 1;
    segment0Size = 1;
180
    break;
181
  }
182

183
  // Read sizes for all segments except the first.  Include padding if necessary.
184
  KJ_STACK_ARRAY(_::WireValue<uint32_t>, moreSizes, segmentCount & ~1, 16, 64);
185
  if (segmentCount > 1) {
186
    inputStream.read(moreSizes.begin(), moreSizes.size() * sizeof(moreSizes[0]));
187 188
    for (uint i = 0; i < segmentCount - 1; i++) {
      totalWords += moreSizes[i].get();
Kenton Varda's avatar
Kenton Varda committed
189 190 191
    }
  }

192 193 194
  // Don't accept a message which the receiver couldn't possibly traverse without hitting the
  // traversal limit.  Without this check, a malicious client could transmit a very large segment
  // size to make the receiver allocate excessive space and possibly crash.
195 196
  KJ_REQUIRE(totalWords <= options.traversalLimitInWords,
             "Message is too large.  To increase the limit on the receiving end, see "
197
             "capnp::ReaderOptions.") {
198
    segmentCount = 1;
199
    segment0Size = kj::min(segment0Size, options.traversalLimitInWords);
200
    totalWords = segment0Size;
201
    break;
202
  }
203

204
  if (scratchSpace.size() < totalWords) {
205 206
    // TODO(perf):  Consider allocating each segment as a separate chunk to reduce memory
    //   fragmentation.
Kenton Varda's avatar
Kenton Varda committed
207
    ownedSpace = kj::heapArray<word>(totalWords);
208
    scratchSpace = ownedSpace;
Kenton Varda's avatar
Kenton Varda committed
209 210
  }

211
  segment0 = scratchSpace.slice(0, segment0Size);
Kenton Varda's avatar
Kenton Varda committed
212

213
  if (segmentCount > 1) {
Kenton Varda's avatar
Kenton Varda committed
214
    moreSegments = kj::heapArray<kj::ArrayPtr<const word>>(segmentCount - 1);
215
    size_t offset = segment0Size;
Kenton Varda's avatar
Kenton Varda committed
216

217 218 219 220
    for (uint i = 0; i < segmentCount - 1; i++) {
      uint segmentSize = moreSizes[i].get();
      moreSegments[i] = scratchSpace.slice(offset, offset + segmentSize);
      offset += segmentSize;
Kenton Varda's avatar
Kenton Varda committed
221 222 223
    }
  }

224
  if (segmentCount == 1) {
Kenton Varda's avatar
Kenton Varda committed
225
    inputStream.read(scratchSpace.begin(), totalWords * sizeof(word));
226
  } else if (segmentCount > 1) {
227
    readPos = scratchSpace.asBytes().begin();
228
    readPos += inputStream.read(readPos, segment0Size * sizeof(word), totalWords * sizeof(word));
Kenton Varda's avatar
Kenton Varda committed
229
  }
230
}
Kenton Varda's avatar
Kenton Varda committed
231

232
InputStreamMessageReader::~InputStreamMessageReader() noexcept(false) {
233
  if (readPos != nullptr) {
234 235 236 237
    unwindDetector.catchExceptionsIfUnwinding([&]() {
      // Note that lazy reads only happen when we have multiple segments, so moreSegments.back() is
      // valid.
      const byte* allEnd = reinterpret_cast<const byte*>(moreSegments.back().end());
Kenton Varda's avatar
Kenton Varda committed
238
      inputStream.skip(allEnd - readPos);
239
    });
Kenton Varda's avatar
Kenton Varda committed
240 241 242
  }
}

243
kj::ArrayPtr<const word> InputStreamMessageReader::getSegment(uint id) {
Kenton Varda's avatar
Kenton Varda committed
244 245 246 247
  if (id > moreSegments.size()) {
    return nullptr;
  }

248
  kj::ArrayPtr<const word> segment = id == 0 ? segment0 : moreSegments[id - 1];
Kenton Varda's avatar
Kenton Varda committed
249

250 251 252 253 254 255 256 257
  if (readPos != nullptr) {
    // May need to lazily read more data.
    const byte* segmentEnd = reinterpret_cast<const byte*>(segment.end());
    if (readPos < segmentEnd) {
      // Note that lazy reads only happen when we have multiple segments, so moreSegments.back() is
      // valid.
      const byte* allEnd = reinterpret_cast<const byte*>(moreSegments.back().end());
      readPos += inputStream.read(readPos, segmentEnd - readPos, allEnd - readPos);
Kenton Varda's avatar
Kenton Varda committed
258 259 260
    }
  }

261
  return segment;
Kenton Varda's avatar
Kenton Varda committed
262 263
}

264 265 266 267 268 269
void readMessageCopy(kj::InputStream& input, MessageBuilder& target,
                     ReaderOptions options, kj::ArrayPtr<word> scratchSpace) {
  InputStreamMessageReader message(input, options, scratchSpace);
  target.setRoot(message.getRoot<AnyPointer>());
}

Kenton Varda's avatar
Kenton Varda committed
270 271
// -------------------------------------------------------------------

272
void writeMessage(kj::OutputStream& output, kj::ArrayPtr<const kj::ArrayPtr<const word>> segments) {
273
  KJ_REQUIRE(segments.size() > 0, "Tried to serialize uninitialized message.");
274

275
  KJ_STACK_ARRAY(_::WireValue<uint32_t>, table, (segments.size() + 2) & ~size_t(1), 16, 64);
Kenton Varda's avatar
Kenton Varda committed
276

277 278 279
  // We write the segment count - 1 because this makes the first word zero for single-segment
  // messages, improving compression.  We don't bother doing this with segment sizes because
  // one-word segments are rare anyway.
280
  table[0].set(segments.size() - 1);
Kenton Varda's avatar
Kenton Varda committed
281 282 283 284 285 286 287 288
  for (uint i = 0; i < segments.size(); i++) {
    table[i + 1].set(segments[i].size());
  }
  if (segments.size() % 2 == 0) {
    // Set padding byte.
    table[segments.size() + 1].set(0);
  }

289
  KJ_STACK_ARRAY(kj::ArrayPtr<const byte>, pieces, segments.size() + 1, 4, 32);
290
  pieces[0] = table.asBytes();
Kenton Varda's avatar
Kenton Varda committed
291

292
  for (uint i = 0; i < segments.size(); i++) {
293
    pieces[i + 1] = segments[i].asBytes();
Kenton Varda's avatar
Kenton Varda committed
294
  }
295

Kenton Varda's avatar
Kenton Varda committed
296
  output.write(pieces);
Kenton Varda's avatar
Kenton Varda committed
297 298 299
}

// =======================================================================================
300

301
StreamFdMessageReader::~StreamFdMessageReader() noexcept(false) {}
Kenton Varda's avatar
Kenton Varda committed
302

303
void writeMessageToFd(int fd, kj::ArrayPtr<const kj::ArrayPtr<const word>> segments) {
304
  kj::FdOutputStream stream(fd);
Kenton Varda's avatar
Kenton Varda committed
305 306 307
  writeMessage(stream, segments);
}

308 309 310 311 312 313
void readMessageCopyFromFd(int fd, MessageBuilder& target,
                           ReaderOptions options, kj::ArrayPtr<word> scratchSpace) {
  kj::FdInputStream stream(fd);
  readMessageCopy(stream, target, options, scratchSpace);
}

314
}  // namespace capnp