forked from chromium/chromium
-
Notifications
You must be signed in to change notification settings - Fork 0
/
cbor_extract.h
339 lines (298 loc) · 11.9 KB
/
cbor_extract.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
// 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_