Add CBOR reading utilities

The reading is very simplistic, parsing individual tokens. The larger
structure of the data would be interpreted by the client. There is a
function to skip over a more complex items but it doesn't extract any of
the data and just moves past it in the input buffer. Skipping supports a
limited depth of nesting and fails if the nesting goes too deep.

A fuzzer is included. As are a set of tests that no doubt could be
expanded upon.

Change-Id: I251e34aea69eba18a81edc42ab34a54a6a04b45b
Reviewed-on: https://pigweed-review.googlesource.com/c/open-dice/+/65401
Reviewed-by: Darren Krahn <dkrahn@google.com>
Commit-Queue: Andrew Scull <ascull@google.com>
Pigweed-Auto-Submit: Andrew Scull <ascull@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index 917b4e8..af1a1fc 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -32,6 +32,13 @@
   sources = [ "src/cbor_writer.c" ]
 }
 
+pw_source_set("cbor_reader") {
+  public = [
+    "include/dice/cbor_reader.h",
+  ]
+  sources = [ "src/cbor_reader.c" ]
+}
+
 config("standalone_ops_config") {
   include_dirs = [ "//include/dice/config/standalone" ]
 }
@@ -157,6 +164,13 @@
   ]
 }
 
+pw_test("cbor_reader_test") {
+  sources = [ "src/cbor_reader_test.cc" ]
+  deps = [
+    ":cbor_reader",
+  ]
+}
+
 pw_executable("cbor_writer_fuzzer") {
   sources = [ "src/cbor_writer_fuzzer.cc" ]
   deps = [
@@ -164,6 +178,13 @@
   ]
 }
 
+pw_executable("cbor_reader_fuzzer") {
+  sources = [ "src/cbor_reader_fuzzer.cc" ]
+  deps = [
+    ":cbor_reader",
+  ]
+}
+
 pw_test("dice_test") {
   sources = [ "src/dice_test.cc" ]
   deps = [
@@ -276,6 +297,7 @@
   tests = [
     ":boringssl_ops_test",
     ":cbor_cert_op_test",
+    ":cbor_reader_test",
     ":cbor_writer_test",
     ":dice_test",
     ":mbedtls_ops_test",
@@ -288,6 +310,7 @@
   deps = [
     ":boringssl_ops_fuzzer",
     ":cbor_cert_op_fuzzer",
+    ":cbor_reader_fuzzer",
     ":cbor_writer_fuzzer",
     ":mbedtls_ops_fuzzer",
     ":template_cbor_cert_op_fuzzer",
diff --git a/include/dice/cbor_reader.h b/include/dice/cbor_reader.h
new file mode 100644
index 0000000..5086d9e
--- /dev/null
+++ b/include/dice/cbor_reader.h
@@ -0,0 +1,88 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#ifndef DICE_CBOR_READER_H_
+#define DICE_CBOR_READER_H_
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct CborIn {
+  const uint8_t* buffer;
+  size_t buffer_size;
+  size_t cursor;
+};
+
+enum CborReadResult {
+  CBOR_READ_RESULT_OK,
+  // The end of the input was reached before the token was fully read.
+  CBOR_READ_RESULT_END,
+  // A malformed or unsupported token was found.
+  CBOR_READ_RESULT_MALFORMED,
+  // The requested token was not found.
+  CBOR_READ_RESULT_NOT_FOUND,
+};
+
+// Initializes an input stream for reading CBOR tokens.
+static inline void CborInInit(const uint8_t* buffer, size_t buffer_size,
+                              struct CborIn* in) {
+  in->buffer = buffer;
+  in->buffer_size = buffer_size;
+  in->cursor = 0;
+}
+
+// Returns the number of bytes that have been read from the input.
+static inline size_t CborInOffset(const struct CborIn* in) {
+  return in->cursor;
+}
+
+// Returns whether the input stream has been fully consumed.
+static inline bool CborInAtEnd(const struct CborIn* in) {
+  return in->cursor == in->buffer_size;
+}
+
+// These functions read simple CBOR tokens from the input stream. Interpreting
+// the greater structure of the data left to the caller and it is expected that
+// these functions are just being used to validate and extract data from a known
+// structure.
+enum CborReadResult CborReadInt(struct CborIn* in, int64_t* val);
+enum CborReadResult CborReadUint(struct CborIn* in, uint64_t* val);
+enum CborReadResult CborReadBstr(struct CborIn* in, size_t* data_size,
+                                 const uint8_t** data);
+enum CborReadResult CborReadTstr(struct CborIn* in, size_t* size,
+                                 const char** str);
+enum CborReadResult CborReadArray(struct CborIn* in, size_t* num_elements);
+enum CborReadResult CborReadMap(struct CborIn* in, size_t* num_pairs);
+enum CborReadResult CborReadFalse(struct CborIn* in);
+enum CborReadResult CborReadTrue(struct CborIn* in);
+enum CborReadResult CborReadNull(struct CborIn* in);
+
+// Skips over the next CBOR item in the input. The item may contain nested
+// items, in the case of an array or map, and this function will attempt to
+// descend and skip all nested items in order to skip the parent item. There is
+// a limit on the level of nesting, after which this function will fail with
+// CBOR_READ_RESULT_MALFORMED.
+#define CBOR_READ_SKIP_STACK_SIZE 10
+enum CborReadResult CborReadSkip(struct CborIn* in);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // DICE_CBOR_READER_H_
diff --git a/src/cbor_reader.c b/src/cbor_reader.c
new file mode 100644
index 0000000..3b0b343
--- /dev/null
+++ b/src/cbor_reader.c
@@ -0,0 +1,267 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "dice/cbor_reader.h"
+
+enum CborType {
+  CBOR_TYPE_UINT = 0,
+  CBOR_TYPE_NINT = 1,
+  CBOR_TYPE_BSTR = 2,
+  CBOR_TYPE_TSTR = 3,
+  CBOR_TYPE_ARRAY = 4,
+  CBOR_TYPE_MAP = 5,
+  CBOR_TYPE_TAG_NOT_SUPPORTED = 6,
+  CBOR_TYPE_SIMPLE = 7,
+};
+
+static bool CborReadWouldOverflow(size_t size, struct CborIn* in) {
+  return size > SIZE_MAX - in->cursor || in->cursor + size > in->buffer_size;
+}
+
+static enum CborReadResult CborPeekIntialValueAndArgument(struct CborIn* in,
+                                                          uint8_t* size,
+                                                          enum CborType* type,
+                                                          uint64_t* val) {
+  uint8_t initial_byte;
+  uint8_t additional_information;
+  uint64_t value;
+  uint8_t bytes = 1;
+  if (CborInAtEnd(in)) {
+    return CBOR_READ_RESULT_END;
+  }
+  initial_byte = in->buffer[in->cursor];
+  *type = initial_byte >> 5;
+  additional_information = initial_byte & 0x1f;
+  if (additional_information <= 23) {
+    value = additional_information;
+  } else if (additional_information <= 27) {
+    bytes += 1 << (additional_information - 24);
+    if (CborReadWouldOverflow(bytes, in)) {
+      return CBOR_READ_RESULT_END;
+    }
+    value = 0;
+    if (bytes == 2) {
+      value |= in->buffer[in->cursor + 1];
+    } else if (bytes == 3) {
+      value |= (uint16_t)in->buffer[in->cursor + 1] << 8;
+      value |= (uint16_t)in->buffer[in->cursor + 2];
+    } else if (bytes == 5) {
+      value |= (uint32_t)in->buffer[in->cursor + 1] << 24;
+      value |= (uint32_t)in->buffer[in->cursor + 2] << 16;
+      value |= (uint32_t)in->buffer[in->cursor + 3] << 8;
+      value |= (uint32_t)in->buffer[in->cursor + 4];
+    } else if (bytes == 9) {
+      value |= (uint64_t)in->buffer[in->cursor + 1] << 56;
+      value |= (uint64_t)in->buffer[in->cursor + 2] << 48;
+      value |= (uint64_t)in->buffer[in->cursor + 3] << 40;
+      value |= (uint64_t)in->buffer[in->cursor + 4] << 32;
+      value |= (uint64_t)in->buffer[in->cursor + 5] << 24;
+      value |= (uint64_t)in->buffer[in->cursor + 6] << 16;
+      value |= (uint64_t)in->buffer[in->cursor + 7] << 8;
+      value |= (uint64_t)in->buffer[in->cursor + 8];
+    }
+  } else {
+    // Indefinite lengths and reserved values are not supported.
+    return CBOR_READ_RESULT_MALFORMED;
+  }
+  *val = value;
+  *size = bytes;
+  return CBOR_READ_RESULT_OK;
+}
+
+static enum CborReadResult CborReadSize(struct CborIn* in, enum CborType type,
+                                        size_t* size) {
+  uint8_t bytes;
+  enum CborType in_type;
+  uint64_t raw;
+  enum CborReadResult res =
+      CborPeekIntialValueAndArgument(in, &bytes, &in_type, &raw);
+  if (res != CBOR_READ_RESULT_OK) {
+    return res;
+  }
+  if (in_type != type) {
+    return CBOR_READ_RESULT_NOT_FOUND;
+  }
+  if (raw > SIZE_MAX) {
+    return CBOR_READ_RESULT_MALFORMED;
+  }
+  *size = raw;
+  in->cursor += bytes;
+  return CBOR_READ_RESULT_OK;
+}
+
+static enum CborReadResult CborReadStr(struct CborIn* in, enum CborType type,
+                                       size_t* data_size,
+                                       const uint8_t** data) {
+  size_t size;
+  struct CborIn peeker = *in;
+  enum CborReadResult res = CborReadSize(&peeker, type, &size);
+  if (res != CBOR_READ_RESULT_OK) {
+    return res;
+  }
+  if (CborReadWouldOverflow(size, &peeker)) {
+    return CBOR_READ_RESULT_END;
+  }
+  *data_size = size;
+  *data = &in->buffer[peeker.cursor];
+  in->cursor = peeker.cursor + size;
+  return CBOR_READ_RESULT_OK;
+}
+
+static enum CborReadResult CborReadSimple(struct CborIn* in, uint8_t val) {
+  uint8_t bytes;
+  enum CborType type;
+  uint64_t raw;
+  enum CborReadResult res =
+      CborPeekIntialValueAndArgument(in, &bytes, &type, &raw);
+  if (res != CBOR_READ_RESULT_OK) {
+    return res;
+  }
+  if (type != CBOR_TYPE_SIMPLE || raw != val) {
+    return CBOR_READ_RESULT_NOT_FOUND;
+  }
+  in->cursor += bytes;
+  return CBOR_READ_RESULT_OK;
+}
+
+enum CborReadResult CborReadInt(struct CborIn* in, int64_t* val) {
+  uint8_t bytes;
+  enum CborType type;
+  uint64_t raw;
+  enum CborReadResult res =
+      CborPeekIntialValueAndArgument(in, &bytes, &type, &raw);
+  if (res != CBOR_READ_RESULT_OK) {
+    return res;
+  }
+  if (type != CBOR_TYPE_UINT && type != CBOR_TYPE_NINT) {
+    return CBOR_READ_RESULT_NOT_FOUND;
+  }
+  if (raw > INT64_MAX) {
+    return CBOR_READ_RESULT_MALFORMED;
+  }
+  *val = (type == CBOR_TYPE_NINT) ? (-1 - (int64_t)raw) : (int64_t)raw;
+  in->cursor += bytes;
+  return CBOR_READ_RESULT_OK;
+}
+
+enum CborReadResult CborReadUint(struct CborIn* in, uint64_t* val) {
+  uint8_t bytes;
+  enum CborType type;
+  enum CborReadResult res =
+      CborPeekIntialValueAndArgument(in, &bytes, &type, val);
+  if (res != CBOR_READ_RESULT_OK) {
+    return res;
+  }
+  if (type != CBOR_TYPE_UINT) {
+    return CBOR_READ_RESULT_NOT_FOUND;
+  }
+  in->cursor += bytes;
+  return CBOR_READ_RESULT_OK;
+}
+
+enum CborReadResult CborReadBstr(struct CborIn* in, size_t* data_size,
+                                 const uint8_t** data) {
+  return CborReadStr(in, CBOR_TYPE_BSTR, data_size, data);
+}
+
+enum CborReadResult CborReadTstr(struct CborIn* in, size_t* size,
+                                 const char** str) {
+  return CborReadStr(in, CBOR_TYPE_TSTR, size, (const uint8_t**)str);
+}
+
+enum CborReadResult CborReadArray(struct CborIn* in, size_t* num_elements) {
+  return CborReadSize(in, CBOR_TYPE_ARRAY, num_elements);
+}
+
+enum CborReadResult CborReadMap(struct CborIn* in, size_t* num_pairs) {
+  return CborReadSize(in, CBOR_TYPE_MAP, num_pairs);
+}
+
+enum CborReadResult CborReadFalse(struct CborIn* in) {
+  return CborReadSimple(in, /*val=*/20);
+}
+
+enum CborReadResult CborReadTrue(struct CborIn* in) {
+  return CborReadSimple(in, /*val=*/21);
+}
+
+enum CborReadResult CborReadNull(struct CborIn* in) {
+  return CborReadSimple(in, /*val=*/22);
+}
+
+enum CborReadResult CborReadSkip(struct CborIn* in) {
+  struct CborIn peeker = *in;
+  size_t size_stack[CBOR_READ_SKIP_STACK_SIZE];
+  size_t stack_size = 0;
+
+  size_stack[stack_size++] = 1;
+
+  while (stack_size > 0) {
+    // Get the type
+    uint8_t bytes;
+    enum CborType type;
+    uint64_t val;
+    enum CborReadResult res;
+
+    res = CborPeekIntialValueAndArgument(&peeker, &bytes, &type, &val);
+    if (res != CBOR_READ_RESULT_OK) {
+      return res;
+    }
+
+    if (CborReadWouldOverflow(bytes, &peeker)) {
+      return CBOR_READ_RESULT_END;
+    }
+    peeker.cursor += bytes;
+
+    if (--size_stack[stack_size - 1] == 0) {
+      --stack_size;
+    }
+
+    switch (type) {
+      case CBOR_TYPE_UINT:
+      case CBOR_TYPE_NINT:
+      case CBOR_TYPE_SIMPLE:
+        continue;
+      case CBOR_TYPE_BSTR:
+      case CBOR_TYPE_TSTR:
+        if (CborReadWouldOverflow(val, &peeker)) {
+          return CBOR_READ_RESULT_END;
+        }
+        peeker.cursor += val;
+        continue;
+      case CBOR_TYPE_MAP:
+        if (val > UINT64_MAX / 2) {
+          return CBOR_READ_RESULT_END;
+        }
+        val *= 2;
+        break;
+      case CBOR_TYPE_ARRAY:
+        break;
+      default:
+        return CBOR_READ_RESULT_MALFORMED;
+    }
+
+    // Push a new level of nesting to the stack.
+    if (val == 0) {
+      continue;
+    }
+    if (stack_size == CBOR_READ_SKIP_STACK_SIZE) {
+      return CBOR_READ_RESULT_MALFORMED;
+    }
+    size_stack[stack_size++] = val;
+  }
+
+  in->cursor = peeker.cursor;
+  return CBOR_READ_RESULT_OK;
+}
diff --git a/src/cbor_reader_fuzzer.cc b/src/cbor_reader_fuzzer.cc
new file mode 100644
index 0000000..9dc08fb
--- /dev/null
+++ b/src/cbor_reader_fuzzer.cc
@@ -0,0 +1,64 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "dice/cbor_reader.h"
+#include "fuzzer/FuzzedDataProvider.h"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  int64_t signed_int;
+  uint64_t unsigned_int;
+  size_t sz;
+  const uint8_t* ptr;
+  const char* str;
+  CborIn in;
+  CborIn peeker;
+
+  CborInInit(data, size, &in);
+
+  do {
+    peeker = in;
+    CborReadInt(&peeker, &signed_int);
+
+    peeker = in;
+    CborReadUint(&peeker, &unsigned_int);
+
+    peeker = in;
+    CborReadBstr(&peeker, &sz, &ptr);
+
+    peeker = in;
+    CborReadTstr(&peeker, &sz, &str);
+
+    peeker = in;
+    CborReadArray(&peeker, &sz);
+
+    peeker = in;
+    CborReadMap(&peeker, &sz);
+
+    peeker = in;
+    CborReadFalse(&peeker);
+
+    peeker = in;
+    CborReadTrue(&peeker);
+
+    peeker = in;
+    CborReadNull(&peeker);
+
+    if (CborReadSkip(&in) != CBOR_READ_RESULT_OK) {
+      // Cannot progress futher with this buffer.
+      break;
+    }
+  } while (!CborInAtEnd(&in));
+
+  return 0;
+}
diff --git a/src/cbor_reader_test.cc b/src/cbor_reader_test.cc
new file mode 100644
index 0000000..b491025
--- /dev/null
+++ b/src/cbor_reader_test.cc
@@ -0,0 +1,372 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "dice/cbor_reader.h"
+
+#include "dice/test_framework.h"
+
+namespace {
+
+extern "C" {
+
+TEST(CborReaderTest, Int1Byte) {
+  const uint8_t buffer[] = {0, 23, 0x20, 0x37};
+  int64_t val;
+  CborIn in;
+  CborInInit(buffer, sizeof(buffer), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadInt(&in, &val));
+  EXPECT_EQ(0, val);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadInt(&in, &val));
+  EXPECT_EQ(23, val);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadInt(&in, &val));
+  EXPECT_EQ(-1, val);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadInt(&in, &val));
+  EXPECT_EQ(-24, val);
+  EXPECT_TRUE(CborInAtEnd(&in));
+}
+
+TEST(CborReaderTest, Int2Bytes) {
+  const uint8_t buffer[] = {24, 24, 24, 0xff, 0x38, 24, 0x38, 0xff};
+  int64_t val;
+  CborIn in;
+  CborInInit(buffer, sizeof(buffer), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadInt(&in, &val));
+  EXPECT_EQ(24, val);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadInt(&in, &val));
+  EXPECT_EQ(0xff, val);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadInt(&in, &val));
+  EXPECT_EQ(-25, val);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadInt(&in, &val));
+  EXPECT_EQ(-0x100, val);
+  EXPECT_TRUE(CborInAtEnd(&in));
+}
+
+TEST(CborReaderTest, Int3Bytes) {
+  const uint8_t buffer[] = {25,   0x01, 0x00, 25,   0xff, 0xff,
+                            0x39, 0x01, 0x00, 0x39, 0xff, 0xff};
+  int64_t val;
+  CborIn in;
+  CborInInit(buffer, sizeof(buffer), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadInt(&in, &val));
+  EXPECT_EQ(0x100, val);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadInt(&in, &val));
+  EXPECT_EQ(0xffff, val);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadInt(&in, &val));
+  EXPECT_EQ(-0x101, val);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadInt(&in, &val));
+  EXPECT_EQ(-0x10000, val);
+  EXPECT_TRUE(CborInAtEnd(&in));
+}
+
+TEST(CborReaderTest, Int5Bytes) {
+  const uint8_t buffer[] = {26,   0x00, 0x01, 0x00, 0x00, 26,   0xff,
+                            0xff, 0xff, 0xff, 0x3a, 0x00, 0x01, 0x00,
+                            0x00, 0x3a, 0xff, 0xff, 0xff, 0xff};
+  int64_t val;
+  CborIn in;
+  CborInInit(buffer, sizeof(buffer), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadInt(&in, &val));
+  EXPECT_EQ(0x10000, val);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadInt(&in, &val));
+  EXPECT_EQ(0xffffffff, val);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadInt(&in, &val));
+  EXPECT_EQ(-0x10001, val);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadInt(&in, &val));
+  EXPECT_EQ(-0x100000000, val);
+  EXPECT_TRUE(CborInAtEnd(&in));
+}
+
+TEST(CborReaderTest, Int9Bytes) {
+  const uint8_t buffer[] = {
+      27,   0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 27,   0x7f, 0xff,
+      0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3b, 0x00, 0x00, 0x00, 0x01, 0x00,
+      0x00, 0x00, 0x00, 0x3b, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+  int64_t val;
+  CborIn in;
+  CborInInit(buffer, sizeof(buffer), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadInt(&in, &val));
+  EXPECT_EQ(0x100000000, val);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadInt(&in, &val));
+  EXPECT_EQ(INT64_MAX, val);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadInt(&in, &val));
+  EXPECT_EQ(-0x100000001, val);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadInt(&in, &val));
+  EXPECT_EQ(INT64_MIN, val);
+  EXPECT_TRUE(CborInAtEnd(&in));
+}
+
+TEST(CborReaderTest, Uint9Bytes) {
+  const uint8_t buffer[] = {27, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+                            27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+  uint64_t val;
+  CborIn in;
+  CborInInit(buffer, sizeof(buffer), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadUint(&in, &val));
+  EXPECT_EQ(0x100000000u, val);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadUint(&in, &val));
+  EXPECT_EQ(UINT64_MAX, val);
+  EXPECT_TRUE(CborInAtEnd(&in));
+}
+
+TEST(CborReaderTest, IntByteOrder) {
+  const uint8_t buffer[] = {
+      25,   0x12, 0x34, 26,   0x12, 0x34, 0x56, 0x78, 27,
+      0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0,
+  };
+  int64_t val;
+  CborIn in;
+  CborInInit(buffer, sizeof(buffer), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadInt(&in, &val));
+  EXPECT_EQ(0x1234, val);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadInt(&in, &val));
+  EXPECT_EQ(0x12345678, val);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadInt(&in, &val));
+  EXPECT_EQ(0x123456789abcdef0, val);
+  EXPECT_TRUE(CborInAtEnd(&in));
+}
+
+TEST(CborReaderTest, IntMalformed) {
+  const uint8_t kTooBigBuffer[] = {27, 0x80, 0, 0, 0, 0, 0, 0, 0};
+  const uint8_t kTooSmallBuffer[] = {0x3b, 0x80, 0, 0, 0, 0, 0, 0, 0};
+  const uint8_t kBadAddlBuffer[] = {30};
+  const uint8_t kNegBadAddlBuffer[] = {0x3c};
+  int64_t val;
+  CborIn in;
+  CborInInit(kTooBigBuffer, sizeof(kTooBigBuffer), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_MALFORMED, CborReadInt(&in, &val));
+  CborInInit(kTooSmallBuffer, sizeof(kTooSmallBuffer), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_MALFORMED, CborReadInt(&in, &val));
+  CborInInit(kBadAddlBuffer, sizeof(kBadAddlBuffer), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_MALFORMED, CborReadInt(&in, &val));
+  CborInInit(kNegBadAddlBuffer, sizeof(kNegBadAddlBuffer), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_MALFORMED, CborReadInt(&in, &val));
+  EXPECT_FALSE(CborInAtEnd(&in));
+}
+
+TEST(CborReaderTest, IntTooShort) {
+  const uint8_t buffer[] = {27, 0x40, 0, 0, 0, 0, 0, 0};
+  int64_t val;
+  CborIn in;
+  CborInInit(buffer, sizeof(buffer), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_END, CborReadInt(&in, &val));
+  EXPECT_FALSE(CborInAtEnd(&in));
+}
+
+TEST(CborReaderTest, BstrEncoding) {
+  const uint8_t buffer[] = {0x45, 'h', 'e', 'l', 'l', 'o'};
+  const uint8_t kData[] = {'h', 'e', 'l', 'l', 'o'};
+  size_t data_size;
+  const uint8_t* data;
+  CborIn in;
+  CborInInit(buffer, sizeof(buffer), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadBstr(&in, &data_size, &data));
+  EXPECT_EQ(sizeof(kData), data_size);
+  EXPECT_EQ(0, memcmp(data, kData, data_size));
+  EXPECT_TRUE(CborInAtEnd(&in));
+}
+
+TEST(CborReaderTest, BstrLongEncoding) {
+  const uint8_t buffer[] = {
+      0x58, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99,
+  };
+  size_t data_size;
+  const uint8_t* data;
+  CborIn in;
+  CborInInit(buffer, sizeof(buffer), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadBstr(&in, &data_size, &data));
+  EXPECT_EQ(32u, data_size);
+  EXPECT_EQ(0, memcmp(data, buffer + 2, 32));
+  EXPECT_TRUE(CborInAtEnd(&in));
+}
+
+TEST(CborReaderTest, TstrEncoding) {
+  const uint8_t buffer[] = {0x65, 'w', 'o', 'r', 'l', 'd'};
+  const char kStr[] = "world";
+  size_t size;
+  const char* str;
+  CborIn in;
+  CborInInit(buffer, sizeof(buffer), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadTstr(&in, &size, &str));
+  EXPECT_EQ(strlen(kStr), size);
+  EXPECT_EQ(0, memcmp(str, kStr, size));
+  EXPECT_TRUE(CborInAtEnd(&in));
+}
+
+TEST(CborReaderTest, ArrayEncoding) {
+  const uint8_t buffer[] = {0x98, 29};
+  size_t num_elements;
+  CborIn in;
+  CborInInit(buffer, sizeof(buffer), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadArray(&in, &num_elements));
+  EXPECT_EQ(29u, num_elements);
+  EXPECT_TRUE(CborInAtEnd(&in));
+}
+
+TEST(CborReaderTest, MapEncoding) {
+  const uint8_t buffer[] = {0xb9, 0x02, 0x50};
+  size_t num_pairs;
+  CborIn in;
+  CborInInit(buffer, sizeof(buffer), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadMap(&in, &num_pairs));
+  EXPECT_EQ(592u, num_pairs);
+  EXPECT_TRUE(CborInAtEnd(&in));
+}
+
+TEST(CborReaderTest, FalseEncoding) {
+  const uint8_t buffer[] = {0xf4};
+  CborIn in;
+  CborInInit(buffer, sizeof(buffer), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadFalse(&in));
+  EXPECT_TRUE(CborInAtEnd(&in));
+}
+
+TEST(CborReaderTest, TrueEncoding) {
+  const uint8_t buffer[] = {0xf5};
+  CborIn in;
+  CborInInit(buffer, sizeof(buffer), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadTrue(&in));
+  EXPECT_TRUE(CborInAtEnd(&in));
+}
+
+TEST(CborReaderTest, NullEncoding) {
+  const uint8_t buffer[] = {0xf6};
+  CborIn in;
+  CborInInit(buffer, sizeof(buffer), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadNull(&in));
+  EXPECT_TRUE(CborInAtEnd(&in));
+}
+
+TEST(CborReaderTest, Skip) {
+  const uint8_t buffer[] = {0x84, 0x03, 0xa2, 0x82, 0x23, 0x05, 0xf4,
+                            0x16, 0xf6, 0x61, 0x44, 0x41, 0xaa};
+  CborIn in;
+  CborInInit(buffer, sizeof(buffer), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadSkip(&in));
+  EXPECT_TRUE(CborInAtEnd(&in));
+}
+
+TEST(CborReaderTest, SkipTooDeeplyNestedMalformed) {
+  const uint8_t map[] = {0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1,
+                         0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1};
+  const uint8_t array[] = {0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+                           0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+                           0x82, 0x82, 0x82, 0x82, 0x82, 0x82};
+  CborIn in;
+  CborInInit(map, sizeof(map), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_MALFORMED, CborReadSkip(&in));
+  EXPECT_EQ(0u, CborInOffset(&in));
+  CborInInit(array, sizeof(array), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_MALFORMED, CborReadSkip(&in));
+  EXPECT_EQ(0u, CborInOffset(&in));
+}
+
+TEST(CborReaderTest, SkipTagMalformed) {
+  const uint8_t tag[] = {0xc4, 0xf5};
+  const uint8_t nested_tag[] = {0x82, 0xa1, 0x02, 0xc7, 0x04, 0x09};
+  CborIn in;
+  CborInInit(tag, sizeof(tag), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_MALFORMED, CborReadSkip(&in));
+  EXPECT_EQ(0u, CborInOffset(&in));
+  CborInInit(nested_tag, sizeof(nested_tag), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_MALFORMED, CborReadSkip(&in));
+  EXPECT_EQ(0u, CborInOffset(&in));
+}
+
+TEST(CborReaderTest, EmptyBufferAtEnd) {
+  int64_t val;
+  uint64_t uval;
+  size_t size;
+  const uint8_t* data;
+  const char* str;
+  CborIn in;
+  CborInInit(nullptr, 0, &in);
+  EXPECT_TRUE(CborInAtEnd(&in));
+  EXPECT_EQ(CBOR_READ_RESULT_END, CborReadInt(&in, &val));
+  EXPECT_EQ(CBOR_READ_RESULT_END, CborReadUint(&in, &uval));
+  EXPECT_EQ(CBOR_READ_RESULT_END, CborReadBstr(&in, &size, &data));
+  EXPECT_EQ(CBOR_READ_RESULT_END, CborReadTstr(&in, &size, &str));
+  EXPECT_EQ(CBOR_READ_RESULT_END, CborReadArray(&in, &size));
+  EXPECT_EQ(CBOR_READ_RESULT_END, CborReadMap(&in, &size));
+  EXPECT_EQ(CBOR_READ_RESULT_END, CborReadFalse(&in));
+  EXPECT_EQ(CBOR_READ_RESULT_END, CborReadTrue(&in));
+  EXPECT_EQ(CBOR_READ_RESULT_END, CborReadNull(&in));
+  EXPECT_EQ(0u, CborInOffset(&in));
+}
+
+TEST(CborReaderTest, NotFound) {
+  const uint8_t buffer[] = {0xc0, 0x08};
+  int64_t val;
+  uint64_t uval;
+  size_t size;
+  const uint8_t* data;
+  const char* str;
+  CborIn in;
+  CborInInit(buffer, sizeof(buffer), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_NOT_FOUND, CborReadInt(&in, &val));
+  EXPECT_EQ(CBOR_READ_RESULT_NOT_FOUND, CborReadUint(&in, &uval));
+  EXPECT_EQ(CBOR_READ_RESULT_NOT_FOUND, CborReadBstr(&in, &size, &data));
+  EXPECT_EQ(CBOR_READ_RESULT_NOT_FOUND, CborReadTstr(&in, &size, &str));
+  EXPECT_EQ(CBOR_READ_RESULT_NOT_FOUND, CborReadArray(&in, &size));
+  EXPECT_EQ(CBOR_READ_RESULT_NOT_FOUND, CborReadMap(&in, &size));
+  EXPECT_EQ(CBOR_READ_RESULT_NOT_FOUND, CborReadFalse(&in));
+  EXPECT_EQ(CBOR_READ_RESULT_NOT_FOUND, CborReadTrue(&in));
+  EXPECT_EQ(CBOR_READ_RESULT_NOT_FOUND, CborReadNull(&in));
+  EXPECT_EQ(0u, CborInOffset(&in));
+}
+
+TEST(CborReaderTest, SimpleValueNotFound) {
+  const uint8_t buffer[] = {0xf7};
+  CborIn in;
+  CborInInit(buffer, sizeof(buffer), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_NOT_FOUND, CborReadFalse(&in));
+  EXPECT_EQ(CBOR_READ_RESULT_NOT_FOUND, CborReadTrue(&in));
+  EXPECT_EQ(CBOR_READ_RESULT_NOT_FOUND, CborReadNull(&in));
+  EXPECT_EQ(0u, CborInOffset(&in));
+}
+
+TEST(CborReaderTest, IndefiniteLengthMalformed) {
+  size_t size;
+  const uint8_t* data;
+  const char* str;
+  CborIn in;
+  const uint8_t bstr[] = {0x5f, 0x44, 0xaa, 0xbb, 0xcc, 0xdd,
+                          0x43, 0xee, 0xff, 0x99, 0xff};
+  CborInInit(bstr, sizeof(bstr), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_MALFORMED, CborReadBstr(&in, &size, &data));
+  EXPECT_EQ(CBOR_READ_RESULT_MALFORMED, CborReadSkip(&in));
+  EXPECT_EQ(0u, CborInOffset(&in));
+  const uint8_t tstr[] = {0x7f, 0x64, 0x41, 0x42, 0x43, 0x44,
+                          0x63, 0x30, 0x31, 0x32, 0xff};
+  CborInInit(tstr, sizeof(tstr), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_MALFORMED, CborReadTstr(&in, &size, &str));
+  EXPECT_EQ(CBOR_READ_RESULT_MALFORMED, CborReadSkip(&in));
+  EXPECT_EQ(0u, CborInOffset(&in));
+  const uint8_t array[] = {0x9f, 0x01, 0x82, 0x02, 0x03,
+                           0x82, 0x04, 0x05, 0xff};
+  CborInInit(array, sizeof(array), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_MALFORMED, CborReadArray(&in, &size));
+  EXPECT_EQ(CBOR_READ_RESULT_MALFORMED, CborReadSkip(&in));
+  EXPECT_EQ(0u, CborInOffset(&in));
+  const uint8_t map[] = {0xbf, 0x63, 0x46, 0x75, 0x6e, 0xf5,
+                         0x63, 0x41, 0x6d, 0x74, 0x21, 0xff};
+  CborInInit(map, sizeof(map), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_MALFORMED, CborReadMap(&in, &size));
+  EXPECT_EQ(CBOR_READ_RESULT_MALFORMED, CborReadSkip(&in));
+  EXPECT_EQ(0u, CborInOffset(&in));
+}
+}
+
+}  // namespace