serialize-test.c++ 15.4 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
#ifndef _GNU_SOURCE
Ivan Shynkarenka's avatar
Ivan Shynkarenka committed
23 24 25
#define _GNU_SOURCE
#endif

Kenton Varda's avatar
Kenton Varda committed
26
#include "serialize.h"
Kenton Varda's avatar
Kenton Varda committed
27
#include <kj/debug.h>
28 29
#include <kj/compat/gtest.h>
#include <kj/miniposix.h>
Kenton Varda's avatar
Kenton Varda committed
30 31
#include <string>
#include <stdlib.h>
32
#include <fcntl.h>
Kenton Varda's avatar
Kenton Varda committed
33 34
#include "test-util.h"

35
namespace capnp {
36
namespace _ {  // private
Kenton Varda's avatar
Kenton Varda committed
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
namespace {

class TestMessageBuilder: public MallocMessageBuilder {
  // A MessageBuilder that tries to allocate an exact number of total segments, by allocating
  // minimum-size segments until it reaches the number, then allocating one large segment to
  // finish.

public:
  explicit TestMessageBuilder(uint desiredSegmentCount)
      : MallocMessageBuilder(0, AllocationStrategy::FIXED_SIZE),
        desiredSegmentCount(desiredSegmentCount) {}
  ~TestMessageBuilder() {
    EXPECT_EQ(0u, desiredSegmentCount);
  }

52
  kj::ArrayPtr<word> allocateSegment(uint minimumSize) override {
Kenton Varda's avatar
Kenton Varda committed
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
    if (desiredSegmentCount <= 1) {
      if (desiredSegmentCount < 1) {
        ADD_FAILURE() << "Allocated more segments than desired.";
      } else {
        --desiredSegmentCount;
      }
      return MallocMessageBuilder::allocateSegment(SUGGESTED_FIRST_SEGMENT_WORDS);
    } else {
      --desiredSegmentCount;
      return MallocMessageBuilder::allocateSegment(minimumSize);
    }
  }

private:
  uint desiredSegmentCount;
};

70 71 72 73 74 75
kj::Array<word> copyWords(kj::ArrayPtr<const word> input) {
  auto result = kj::heapArray<word>(input.size());
  memcpy(result.asBytes().begin(), input.asBytes().begin(), input.asBytes().size());
  return result;
}

Kenton Varda's avatar
Kenton Varda committed
76 77 78 79
TEST(Serialize, FlatArray) {
  TestMessageBuilder builder(1);
  initTestMessage(builder.initRoot<TestAllTypes>());

80
  kj::Array<word> serialized = messageToFlatArray(builder);
Kenton Varda's avatar
Kenton Varda committed
81

82 83 84 85 86 87
  {
    FlatArrayMessageReader reader(serialized.asPtr());
    checkTestMessage(reader.getRoot<TestAllTypes>());
    EXPECT_EQ(serialized.end(), reader.getEnd());
  }

88 89 90 91 92 93 94 95
  {
    MallocMessageBuilder builder2;
    auto remaining = initMessageBuilderFromFlatArrayCopy(serialized, builder2);
    checkTestMessage(builder2.getRoot<TestAllTypes>());
    EXPECT_EQ(serialized.end(), remaining.begin());
    EXPECT_EQ(serialized.end(), remaining.end());
  }

96 97 98 99 100 101 102 103
  kj::Array<word> serializedWithSuffix = kj::heapArray<word>(serialized.size() + 5);
  memcpy(serializedWithSuffix.begin(), serialized.begin(), serialized.size() * sizeof(word));

  {
    FlatArrayMessageReader reader(serializedWithSuffix.asPtr());
    checkTestMessage(reader.getRoot<TestAllTypes>());
    EXPECT_EQ(serializedWithSuffix.end() - 5, reader.getEnd());
  }
104

105 106 107 108 109 110 111 112 113 114 115 116 117 118
#if __i386__ || __x86_64__ || __aarch64__ || _MSC_VER
  // Try unaligned.
  {
    auto bytes = kj::heapArray<byte>(serializedWithSuffix.size() * sizeof(word) + 1);
    auto unalignedWords = kj::arrayPtr(
        reinterpret_cast<word*>(bytes.begin() + 1), serializedWithSuffix.size());
    memcpy(unalignedWords.asBytes().begin(), serializedWithSuffix.asBytes().begin(),
        serializedWithSuffix.asBytes().size());
    UnalignedFlatArrayMessageReader reader(unalignedWords);
    checkTestMessage(reader.getRoot<TestAllTypes>());
    EXPECT_EQ(unalignedWords.end() - 5, reader.getEnd());
  }
#endif

119 120 121 122 123 124 125
  {
    MallocMessageBuilder builder2;
    auto remaining = initMessageBuilderFromFlatArrayCopy(serializedWithSuffix, builder2);
    checkTestMessage(builder2.getRoot<TestAllTypes>());
    EXPECT_EQ(serializedWithSuffix.end() - 5, remaining.begin());
    EXPECT_EQ(serializedWithSuffix.end(), remaining.end());
  }
126 127 128 129 130 131 132 133 134 135

  {
    // Test expectedSizeInWordsFromPrefix(). We pass in a copy of the slice so that valgrind can
    // detect out-of-bounds access.
    EXPECT_EQ(1, expectedSizeInWordsFromPrefix(copyWords(serialized.slice(0, 0))));
    for (uint i = 1; i <= serialized.size(); i++) {
      EXPECT_EQ(serialized.size(),
          expectedSizeInWordsFromPrefix(copyWords(serialized.slice(0, i))));
    }
  }
Kenton Varda's avatar
Kenton Varda committed
136 137 138 139 140 141
}

TEST(Serialize, FlatArrayOddSegmentCount) {
  TestMessageBuilder builder(7);
  initTestMessage(builder.initRoot<TestAllTypes>());

142
  kj::Array<word> serialized = messageToFlatArray(builder);
Kenton Varda's avatar
Kenton Varda committed
143

144 145 146 147 148 149 150 151 152 153 154 155 156 157
  {
    FlatArrayMessageReader reader(serialized.asPtr());
    checkTestMessage(reader.getRoot<TestAllTypes>());
    EXPECT_EQ(serialized.end(), reader.getEnd());
  }

  kj::Array<word> serializedWithSuffix = kj::heapArray<word>(serialized.size() + 5);
  memcpy(serializedWithSuffix.begin(), serialized.begin(), serialized.size() * sizeof(word));

  {
    FlatArrayMessageReader reader(serializedWithSuffix.asPtr());
    checkTestMessage(reader.getRoot<TestAllTypes>());
    EXPECT_EQ(serializedWithSuffix.end() - 5, reader.getEnd());
  }
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174

  {
    // Test expectedSizeInWordsFromPrefix(). We pass in a copy of the slice so that valgrind can
    // detect out-of-bounds access.

    // Segment table is 4 words, so with fewer words we'll have incomplete information.
    for (uint i = 0; i < 4; i++) {
      size_t expectedSize = expectedSizeInWordsFromPrefix(copyWords(serialized.slice(0, i)));
      EXPECT_LT(expectedSize, serialized.size());
      EXPECT_GT(expectedSize, i);
    }
    // After that, we get the exact length.
    for (uint i = 4; i <= serialized.size(); i++) {
      EXPECT_EQ(serialized.size(),
          expectedSizeInWordsFromPrefix(copyWords(serialized.slice(0, i))));
    }
  }
Kenton Varda's avatar
Kenton Varda committed
175 176
}

177
TEST(Serialize, FlatArrayEvenSegmentCount) {
Kenton Varda's avatar
Kenton Varda committed
178 179 180
  TestMessageBuilder builder(10);
  initTestMessage(builder.initRoot<TestAllTypes>());

181
  kj::Array<word> serialized = messageToFlatArray(builder);
Kenton Varda's avatar
Kenton Varda committed
182

183 184 185 186 187 188 189 190 191 192 193 194 195 196
  {
    FlatArrayMessageReader reader(serialized.asPtr());
    checkTestMessage(reader.getRoot<TestAllTypes>());
    EXPECT_EQ(serialized.end(), reader.getEnd());
  }

  kj::Array<word> serializedWithSuffix = kj::heapArray<word>(serialized.size() + 5);
  memcpy(serializedWithSuffix.begin(), serialized.begin(), serialized.size() * sizeof(word));

  {
    FlatArrayMessageReader reader(serializedWithSuffix.asPtr());
    checkTestMessage(reader.getRoot<TestAllTypes>());
    EXPECT_EQ(serializedWithSuffix.end() - 5, reader.getEnd());
  }
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213

  {
    // Test expectedSizeInWordsFromPrefix(). We pass in a copy of the slice so that valgrind can
    // detect out-of-bounds access.

    // Segment table is 6 words, so with fewer words we'll have incomplete information.
    for (uint i = 0; i < 6; i++) {
      size_t expectedSize = expectedSizeInWordsFromPrefix(copyWords(serialized.slice(0, i)));
      EXPECT_LT(expectedSize, serialized.size());
      EXPECT_GT(expectedSize, i);
    }
    // After that, we get the exact length.
    for (uint i = 6; i <= serialized.size(); i++) {
      EXPECT_EQ(serialized.size(),
          expectedSizeInWordsFromPrefix(copyWords(serialized.slice(0, i))));
    }
  }
Kenton Varda's avatar
Kenton Varda committed
214 215
}

216
class TestInputStream: public kj::InputStream {
Kenton Varda's avatar
Kenton Varda committed
217
public:
218
  TestInputStream(kj::ArrayPtr<const word> data, bool lazy)
219 220
      : pos(data.asChars().begin()),
        end(data.asChars().end()),
221
        lazy(lazy) {}
Kenton Varda's avatar
Kenton Varda committed
222 223
  ~TestInputStream() {}

224
  size_t tryRead(void* buffer, size_t minBytes, size_t maxBytes) override {
225
    KJ_ASSERT(maxBytes <= size_t(end - pos), "Overran end of stream.");
226 227 228 229
    size_t amount = lazy ? minBytes : maxBytes;
    memcpy(buffer, pos, amount);
    pos += amount;
    return amount;
Kenton Varda's avatar
Kenton Varda committed
230 231 232 233 234
  }

private:
  const char* pos;
  const char* end;
235
  bool lazy;
Kenton Varda's avatar
Kenton Varda committed
236 237 238 239 240 241
};

TEST(Serialize, InputStream) {
  TestMessageBuilder builder(1);
  initTestMessage(builder.initRoot<TestAllTypes>());

242
  kj::Array<word> serialized = messageToFlatArray(builder);
Kenton Varda's avatar
Kenton Varda committed
243

244 245
  TestInputStream stream(serialized.asPtr(), false);
  InputStreamMessageReader reader(stream, ReaderOptions());
Kenton Varda's avatar
Kenton Varda committed
246 247 248 249

  checkTestMessage(reader.getRoot<TestAllTypes>());
}

250
TEST(Serialize, InputStreamScratchSpace) {
Kenton Varda's avatar
Kenton Varda committed
251 252 253
  TestMessageBuilder builder(1);
  initTestMessage(builder.initRoot<TestAllTypes>());

254
  kj::Array<word> serialized = messageToFlatArray(builder);
Kenton Varda's avatar
Kenton Varda committed
255

256 257
  word scratch[4096];
  TestInputStream stream(serialized.asPtr(), false);
258
  InputStreamMessageReader reader(stream, ReaderOptions(), kj::ArrayPtr<word>(scratch, 4096));
Kenton Varda's avatar
Kenton Varda committed
259 260 261 262

  checkTestMessage(reader.getRoot<TestAllTypes>());
}

263
TEST(Serialize, InputStreamLazy) {
Kenton Varda's avatar
Kenton Varda committed
264 265 266
  TestMessageBuilder builder(1);
  initTestMessage(builder.initRoot<TestAllTypes>());

267
  kj::Array<word> serialized = messageToFlatArray(builder);
Kenton Varda's avatar
Kenton Varda committed
268

269 270
  TestInputStream stream(serialized.asPtr(), true);
  InputStreamMessageReader reader(stream, ReaderOptions());
Kenton Varda's avatar
Kenton Varda committed
271 272 273 274

  checkTestMessage(reader.getRoot<TestAllTypes>());
}

275
TEST(Serialize, InputStreamOddSegmentCount) {
Kenton Varda's avatar
Kenton Varda committed
276 277 278
  TestMessageBuilder builder(7);
  initTestMessage(builder.initRoot<TestAllTypes>());

279
  kj::Array<word> serialized = messageToFlatArray(builder);
Kenton Varda's avatar
Kenton Varda committed
280

281 282
  TestInputStream stream(serialized.asPtr(), false);
  InputStreamMessageReader reader(stream, ReaderOptions());
Kenton Varda's avatar
Kenton Varda committed
283 284 285 286

  checkTestMessage(reader.getRoot<TestAllTypes>());
}

287
TEST(Serialize, InputStreamOddSegmentCountLazy) {
Kenton Varda's avatar
Kenton Varda committed
288 289 290
  TestMessageBuilder builder(7);
  initTestMessage(builder.initRoot<TestAllTypes>());

291
  kj::Array<word> serialized = messageToFlatArray(builder);
Kenton Varda's avatar
Kenton Varda committed
292

293 294
  TestInputStream stream(serialized.asPtr(), true);
  InputStreamMessageReader reader(stream, ReaderOptions());
Kenton Varda's avatar
Kenton Varda committed
295 296 297 298

  checkTestMessage(reader.getRoot<TestAllTypes>());
}

299
TEST(Serialize, InputStreamEvenSegmentCount) {
Kenton Varda's avatar
Kenton Varda committed
300 301 302
  TestMessageBuilder builder(10);
  initTestMessage(builder.initRoot<TestAllTypes>());

303
  kj::Array<word> serialized = messageToFlatArray(builder);
Kenton Varda's avatar
Kenton Varda committed
304

305 306
  TestInputStream stream(serialized.asPtr(), false);
  InputStreamMessageReader reader(stream, ReaderOptions());
Kenton Varda's avatar
Kenton Varda committed
307 308 309 310

  checkTestMessage(reader.getRoot<TestAllTypes>());
}

311
TEST(Serialize, InputStreamEvenSegmentCountLazy) {
Kenton Varda's avatar
Kenton Varda committed
312 313 314
  TestMessageBuilder builder(10);
  initTestMessage(builder.initRoot<TestAllTypes>());

315
  kj::Array<word> serialized = messageToFlatArray(builder);
Kenton Varda's avatar
Kenton Varda committed
316

317 318
  TestInputStream stream(serialized.asPtr(), true);
  InputStreamMessageReader reader(stream, ReaderOptions());
Kenton Varda's avatar
Kenton Varda committed
319 320 321 322

  checkTestMessage(reader.getRoot<TestAllTypes>());
}

323 324 325 326 327 328 329 330 331 332 333 334 335 336
TEST(Serialize, InputStreamToBuilder) {
  TestMessageBuilder builder(1);
  initTestMessage(builder.initRoot<TestAllTypes>());

  kj::Array<word> serialized = messageToFlatArray(builder);

  TestInputStream stream(serialized.asPtr(), false);

  MallocMessageBuilder builder2;
  readMessageCopy(stream, builder2);

  checkTestMessage(builder2.getRoot<TestAllTypes>());
}

337
class TestOutputStream: public kj::OutputStream {
Kenton Varda's avatar
Kenton Varda committed
338 339 340 341 342 343 344 345
public:
  TestOutputStream() {}
  ~TestOutputStream() {}

  void write(const void* buffer, size_t size) override {
    data.append(reinterpret_cast<const char*>(buffer), size);
  }

346
  bool dataEquals(kj::ArrayPtr<const word> other) {
Kenton Varda's avatar
Kenton Varda committed
347
    return data ==
348
        std::string(other.asChars().begin(), other.asChars().size());
Kenton Varda's avatar
Kenton Varda committed
349 350 351 352 353 354 355 356 357 358
  }

private:
  std::string data;
};

TEST(Serialize, WriteMessage) {
  TestMessageBuilder builder(1);
  initTestMessage(builder.initRoot<TestAllTypes>());

359
  kj::Array<word> serialized = messageToFlatArray(builder);
Kenton Varda's avatar
Kenton Varda committed
360 361 362 363 364 365 366 367 368 369 370

  TestOutputStream output;
  writeMessage(output, builder);

  EXPECT_TRUE(output.dataEquals(serialized.asPtr()));
}

TEST(Serialize, WriteMessageOddSegmentCount) {
  TestMessageBuilder builder(7);
  initTestMessage(builder.initRoot<TestAllTypes>());

371
  kj::Array<word> serialized = messageToFlatArray(builder);
Kenton Varda's avatar
Kenton Varda committed
372 373 374 375 376 377 378

  TestOutputStream output;
  writeMessage(output, builder);

  EXPECT_TRUE(output.dataEquals(serialized.asPtr()));
}

379
TEST(Serialize, WriteMessageEvenSegmentCount) {
Kenton Varda's avatar
Kenton Varda committed
380 381 382
  TestMessageBuilder builder(10);
  initTestMessage(builder.initRoot<TestAllTypes>());

383
  kj::Array<word> serialized = messageToFlatArray(builder);
Kenton Varda's avatar
Kenton Varda committed
384 385 386 387 388 389 390

  TestOutputStream output;
  writeMessage(output, builder);

  EXPECT_TRUE(output.dataEquals(serialized.asPtr()));
}

391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
#if _WIN32
int mkstemp(char *tpl) {
  char* end = tpl + strlen(tpl);
  while (end > tpl && *(end-1) == 'X') --end;

  for (;;) {
    KJ_ASSERT(_mktemp(tpl) == tpl);

    int fd = open(tpl, O_RDWR | O_CREAT | O_EXCL | O_TEMPORARY | O_BINARY, 0700);
    if (fd >= 0) {
      return fd;
    }

    int error = errno;
    if (error != EEXIST && error != EINTR) {
406
      KJ_FAIL_SYSCALL("open(mktemp())", error, tpl);
407 408 409 410 411 412 413
    }

    memset(end, 'X', strlen(end));
  }
}
#endif

Kenton Varda's avatar
Kenton Varda committed
414
TEST(Serialize, FileDescriptors) {
Kenton Varda's avatar
Kenton Varda committed
415
#if _WIN32 || __ANDROID__
416 417 418
  // TODO(cleanup): Find the Windows temp directory? Seems overly difficult.
  char filename[] = "capnproto-serialize-test-XXXXXX";
#else
Kenton Varda's avatar
Kenton Varda committed
419
  char filename[] = "/tmp/capnproto-serialize-test-XXXXXX";
420
#endif
421
  kj::AutoCloseFd tmpfile(mkstemp(filename));
Kenton Varda's avatar
Kenton Varda committed
422 423
  ASSERT_GE(tmpfile.get(), 0);

424
#if !_WIN32
Kenton Varda's avatar
Kenton Varda committed
425
  // Unlink the file so that it will be deleted on close.
426
  // (For win32, we already handled this is mkstemp().)
Kenton Varda's avatar
Kenton Varda committed
427
  EXPECT_EQ(0, unlink(filename));
428
#endif
Kenton Varda's avatar
Kenton Varda committed
429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454

  {
    TestMessageBuilder builder(7);
    initTestMessage(builder.initRoot<TestAllTypes>());
    writeMessageToFd(tmpfile.get(), builder);
  }

  {
    TestMessageBuilder builder(1);
    builder.initRoot<TestAllTypes>().setTextField("second message in file");
    writeMessageToFd(tmpfile.get(), builder);
  }

  lseek(tmpfile, 0, SEEK_SET);

  {
    StreamFdMessageReader reader(tmpfile.get());
    checkTestMessage(reader.getRoot<TestAllTypes>());
  }

  {
    StreamFdMessageReader reader(tmpfile.get());
    EXPECT_EQ("second message in file", reader.getRoot<TestAllTypes>().getTextField());
  }
}

455
TEST(Serialize, RejectTooManySegments) {
Kenton Varda's avatar
Kenton Varda committed
456
  kj::Array<word> data = kj::heapArray<word>(8192);
457 458 459 460 461 462 463
  WireValue<uint32_t>* table = reinterpret_cast<WireValue<uint32_t>*>(data.begin());
  table[0].set(1024);
  for (uint i = 0; i < 1024; i++) {
    table[i+1].set(1);
  }
  TestInputStream input(data.asPtr(), false);

464
  kj::Maybe<kj::Exception> e = kj::runCatchingExceptions([&]() {
465
    InputStreamMessageReader reader(input);
466
#if !KJ_NO_EXCEPTIONS
467
    ADD_FAILURE() << "Should have thrown an exception.";
468 469 470
#endif
  });

471
  KJ_EXPECT(e != nullptr, "Should have thrown an exception.");
472 473
}

474
#if !__MINGW32__  // Inexplicably crashes when exception is thrown from constructor.
475 476 477 478
TEST(Serialize, RejectHugeMessage) {
  // A message whose root struct contains two words of data!
  AlignedData<4> data = {{0,0,0,0,3,0,0,0, 0,0,0,0,2,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0}};

479
  TestInputStream input(kj::arrayPtr(data.words, 4), false);
480 481 482 483 484

  // We'll set the traversal limit to 2 words so our 3-word message is too big.
  ReaderOptions options;
  options.traversalLimitInWords = 2;

485
  kj::Maybe<kj::Exception> e = kj::runCatchingExceptions([&]() {
486
    InputStreamMessageReader reader(input, options);
487
#if !KJ_NO_EXCEPTIONS
488
    ADD_FAILURE() << "Should have thrown an exception.";
489 490 491
#endif
  });

492
  KJ_EXPECT(e != nullptr, "Should have thrown an exception.");
493
}
494
#endif  // !__MINGW32__
495

496
// TODO(test):  Test error cases.
Kenton Varda's avatar
Kenton Varda committed
497 498

}  // namespace
499
}  // namespace _ (private)
500
}  // namespace capnp