blob: 5a99ac6d0c96709d1caefe9eba2fe03321017800 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef DEVICE_FIDO_CBOR_EXTRACT_H_
#define DEVICE_FIDO_CBOR_EXTRACT_H_
#include "base/callback_forward.h"
#include "base/component_export.h"
#include "base/containers/span.h"
#include "base/memory/checked_ptr.h"
#include "components/cbor/values.h"
namespace device {
namespace cbor_extract {
// cbor_extract implements a framework for pulling select members out of a
// cbor::Value and checking that they have the expected type. It is intended for
// use in contexts where code-size is important.
//
// The top-level cbor::Value must be a map. The extraction is driven by a series
// of commands specified by an array of StepOrByte. There are helper functions
// below for constructing the StepOrByte values and using them is strongly
// advised because they're constexprs, thus have no code-size cost, but they can
// statically check for some mistakes.
//
// As an example, consider a CBOR map {1: 2}. In order to extract the member
// with key '1', we can use:
//
// struct MyObj {
// const int64_t *value;
// };
//
// static constexpr StepOrByte<MyObj> kSteps[] = {
// ELEMENT(Is::kRequired, MyObj, value),
// IntKey<MyObj>(1),
// Stop<MyObj>(),
// };
//
// Note that a Stop() is required at the end of every map. If you have nested
// maps, they are deliminated by Stop()s.
//
// ELEMENT specifies an extraction output and whether it's required to have been
// found in the input. The target structure should only contain pointers and the
// CBOR type is taken from the type of the struct member. (See comments in
// |Type| for the list of C++ types.) A value with an incorrect CBOR type is an
// error, even if marked optional. Missing optional values result in |nullptr|.
//
// Only 16 pointers in the output structure can be addressed. Referencing a
// member past the 15th is a compile-time error.
//
// Output values are also pointers into the input cbor::Value, so that cannot
// be destroyed until processing is complete.
//
// Keys for the element are either specified by IntKey<S>(x), where -128 <= x <
// 127, or StringKey<S>() followed by a NUL-terminated string:
//
// static constexpr StepOrByte<MyObj> kSteps[] = {
// ELEMENT(Is::kRequired, MyObj, value),
// StringKey<MyObj>(), 'k', 'e', 'y', '\0',
// Stop<MyObj>(),
// };
//
// Maps are recursed into and do not result in an output value. (If you want to
// extract a map itself, have an output with type |const cbor::Value *|.)
//
// static constexpr StepOrByte<MyObj> kSteps[] = {
// Map<MyObj>(),
// IntKey<MyObj>(2),
// ELEMENT(Is::kRequired, MyObj, value),
// StringKey<MyObj>(), 'k', 'e', 'y', '\0',
// Stop<MyObj>(),
// };
//
// A map cannot be optional at this time, although that can be fixed later.
//
// The target structure names gets repeated a lot. That's C++ templates for you.
//
// Because the StepOrByte helper functions are constexpr, the steps can be
// evaluated at compile time to produce a compact array of bytes. Each element
// takes a single byte.
enum class Is {
kRequired,
kOptional,
};
namespace internal {
// Type reflects the type of the struct members that can be given to ELEMENT().
enum class Type { // Output type
kBytestring = 0, // const std::vector<uint8_t>*
kString = 1, // const std::string*
kBoolean = 2, // const bool*
kInt = 3, // const int64_t*
kMap = 4, // <no output>
kArray = 5, // const std::vector<cbor::Value>*
kValue = 6, // const cbor::Value*
kStop = 7, // <no output>
};
// Step is an internal detail that needs to be in the header file in order to
// work.
struct Step {
Step() = default;
constexpr Step(uint8_t in_required,
uint8_t in_value_type,
uint8_t in_output_index)
: required(in_required),
value_type(in_value_type),
output_index(in_output_index) {}
struct {
bool required : 1;
uint8_t value_type : 3;
uint8_t output_index : 4;
};
};
} // namespace internal
// StepOrByte is an internal detail that needs to be in the header file in order
// to work.
template <typename S>
struct StepOrByte {
// STRING_KEY is the magic value of |u8| that indicates that this is not an
// integer key, but the a NUL-terminated string follows.
static constexpr uint8_t STRING_KEY = 127;
constexpr explicit StepOrByte(const internal::Step& in_step)
: step(in_step) {}
constexpr explicit StepOrByte(uint8_t b) : u8(b) {}
// This constructor is deliberately not |explicit| so that it's possible to
// write string keys in the steps array.
constexpr StepOrByte(char in_c) : c(in_c) {}
union {
char c;
uint8_t u8;
internal::Step step;
};
};
#define ELEMENT(required, clas, member) \
::device::cbor_extract::internal::Element(required, &clas::member, \
offsetof(clas, member))
template <typename S>
constexpr StepOrByte<S> IntKey(int key) {
if (key > std::numeric_limits<int8_t>::max() ||
key < std::numeric_limits<int8_t>::min() ||
key == StepOrByte<S>::STRING_KEY) {
// It's a compile-time error if __builtin_unreachable is reachable.
__builtin_unreachable();
}
return StepOrByte<S>(static_cast<char>(key));
}
template <typename S>
constexpr StepOrByte<S> StringKey() {
return StepOrByte<S>(static_cast<char>(StepOrByte<S>::STRING_KEY));
}
template <typename S>
constexpr StepOrByte<S> Map() {
return StepOrByte<S>(
internal::Step(true, static_cast<uint8_t>(internal::Type::kMap), -1));
}
template <typename S>
constexpr StepOrByte<S> Stop() {
return StepOrByte<S>(
internal::Step(false, static_cast<uint8_t>(internal::Type::kStop), 0));
}
namespace internal {
template <typename S, typename T>
constexpr StepOrByte<S> Element(const Is required,
T S::*member,
uintptr_t offset) {
// This generic version of |Element| causes a compile-time error if ELEMENT
// is used to reference a member with an invalid type.
__builtin_unreachable();
return StepOrByte<S>('\0');
}
// MemberNum translates an offset into a structure into an index if the
// structure is considered as an array of pointers.
constexpr uint8_t MemberNum(uintptr_t offset) {
if (offset % sizeof(void*)) {
__builtin_unreachable();
}
const uintptr_t index = offset / sizeof(void*);
if (index >= 16) {
__builtin_unreachable();
}
return static_cast<uint8_t>(index);
}
template <typename S>
constexpr StepOrByte<S> ElementImpl(const Is required,
uintptr_t offset,
internal::Type type) {
return StepOrByte<S>(internal::Step(required == Is::kRequired,
static_cast<uint8_t>(type),
MemberNum(offset)));
}
// These are specialisations of Element for each of the value output types.
template <typename S>
constexpr StepOrByte<S> Element(const Is required,
const std::vector<uint8_t>* S::*member,
uintptr_t offset) {
return ElementImpl<S>(required, offset, internal::Type::kBytestring);
}
template <typename S>
constexpr StepOrByte<S> Element(
const Is required,
CheckedPtr<const std::vector<uint8_t>> S::*member,
uintptr_t offset) {
return ElementImpl<S>(required, offset, internal::Type::kBytestring);
}
template <typename S>
constexpr StepOrByte<S> Element(const Is required,
const std::string* S::*member,
uintptr_t offset) {
return ElementImpl<S>(required, offset, internal::Type::kString);
}
template <typename S>
constexpr StepOrByte<S> Element(const Is required,
CheckedPtr<const std::string> S::*member,
uintptr_t offset) {
return ElementImpl<S>(required, offset, internal::Type::kString);
}
template <typename S>
constexpr StepOrByte<S> Element(const Is required,
const int64_t* S::*member,
uintptr_t offset) {
return ElementImpl<S>(required, offset, internal::Type::kInt);
}
template <typename S>
constexpr StepOrByte<S> Element(const Is required,
CheckedPtr<const int64_t> S::*member,
uintptr_t offset) {
return ElementImpl<S>(required, offset, internal::Type::kInt);
}
template <typename S>
constexpr StepOrByte<S> Element(const Is required,
const std::vector<cbor::Value>* S::*member,
uintptr_t offset) {
return ElementImpl<S>(required, offset, internal::Type::kArray);
}
template <typename S>
constexpr StepOrByte<S> Element(
const Is required,
CheckedPtr<const std::vector<cbor::Value>> S::*member,
uintptr_t offset) {
return ElementImpl<S>(required, offset, internal::Type::kArray);
}
template <typename S>
constexpr StepOrByte<S> Element(const Is required,
const cbor::Value* S::*member,
uintptr_t offset) {
return ElementImpl<S>(required, offset, internal::Type::kValue);
}
template <typename S>
constexpr StepOrByte<S> Element(const Is required,
CheckedPtr<const cbor::Value> S::*member,
uintptr_t offset) {
return ElementImpl<S>(required, offset, internal::Type::kValue);
}
template <typename S>
constexpr StepOrByte<S> Element(const Is required,
const bool* S::*member,
uintptr_t offset) {
return ElementImpl<S>(required, offset, internal::Type::kBoolean);
}
template <typename S>
constexpr StepOrByte<S> Element(const Is required,
CheckedPtr<const bool> S::*member,
uintptr_t offset) {
return ElementImpl<S>(required, offset, internal::Type::kBoolean);
}
COMPONENT_EXPORT(DEVICE_FIDO)
bool Extract(base::span<const void*> outputs,
base::span<const StepOrByte<void>> steps,
const cbor::Value::MapValue& map);
} // namespace internal
template <typename S>
bool Extract(S* output,
base::span<const StepOrByte<S>> steps,
const cbor::Value::MapValue& map) {
// The compiler enforces that |output| points to the correct type and this
// code then erases those types for use in the non-templated internal code. We
// don't want to template the internal code because we don't want the compiler
// to generate a copy for every type.
static_assert(sizeof(S) % sizeof(void*) == 0, "struct contains non-pointers");
static_assert(sizeof(S) >= sizeof(void*),
"empty output structures are invalid, even if you just want to "
"check that maps exist, because the code unconditionally "
"indexes offset zero.");
base::span<const void*> outputs(reinterpret_cast<const void**>(output),
sizeof(S) / sizeof(void*));
base::span<const StepOrByte<void>> steps_void(
reinterpret_cast<const StepOrByte<void>*>(steps.data()), steps.size());
return internal::Extract(outputs, steps_void, map);
}
// ForEachPublicKeyEntry is a helper for dealing with CTAP2 structures. It takes
// an array and, for each value in the array, it expects the value to be a map
// and, in the map, it expects the key "type" to result in a string. If that
// string is not "public-key", it ignores the array element. Otherwise it looks
// up |key| in the map and passes it to |callback|.
COMPONENT_EXPORT(DEVICE_FIDO)
bool ForEachPublicKeyEntry(
const cbor::Value::ArrayValue& array,
const cbor::Value& key,
base::RepeatingCallback<bool(const cbor::Value&)> callback);
} // namespace cbor_extract
} // namespace device
#endif // DEVICE_FIDO_CBOR_EXTRACT_H_