Skip to content

Commit aeb2796

Browse files
maxtropetsCopilotachamayou
authored
More CBOR wrappers to allow serializing CBORs (#7577)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Amaury Chamayou <amchamay@microsoft.com>
1 parent 38fe99f commit aeb2796

3 files changed

Lines changed: 542 additions & 28 deletions

File tree

src/crypto/cbor.cpp

Lines changed: 273 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33

44
#include "crypto/cbor.h"
55

6+
#include "ccf/ds/hex.h"
7+
68
#include <algorithm>
79
#include <iomanip>
10+
#include <list>
811
#include <sstream>
912

1013
#define FMT_HEADER_ONLY
@@ -19,6 +22,65 @@ using namespace ccf::cbor;
1922

2023
namespace
2124
{
25+
/* Handy storage of 'cbor_raw's when recursively nesting objects. EverCBOR
26+
* collections work as pointers from one cbor_raw to another, with arrays
27+
* relying on space continuity, and that has to stay intact until calling
28+
* cbor_nondet_serialize. Therefore, the following choices have been made:
29+
*
30+
* - individual items stored in lists rather than collections to avoid
31+
* move-on-resize
32+
* - CBOR collections are made vectors for continuity, and only referenced
33+
* after filled up.
34+
*/
35+
class CborRawArena
36+
{
37+
public:
38+
CborRawArena() = default;
39+
~CborRawArena() = default;
40+
41+
void push(cbor_raw&& single)
42+
{
43+
singles.push_back(single);
44+
}
45+
46+
[[nodiscard]] cbor_raw* single() const
47+
{
48+
return const_cast<cbor_raw*>(&singles.back());
49+
}
50+
51+
void push(std::vector<cbor_raw>&& array)
52+
{
53+
arrays.push_back(array);
54+
}
55+
56+
[[nodiscard]] cbor_raw* array() const
57+
{
58+
return const_cast<cbor_raw*>(&arrays.back().front());
59+
}
60+
61+
void push(std::vector<cbor_map_entry>&& map)
62+
{
63+
maps.push_back(map);
64+
}
65+
66+
[[nodiscard]] cbor_map_entry* map() const
67+
{
68+
return const_cast<cbor_map_entry*>(&maps.back().front());
69+
}
70+
71+
// No copy
72+
CborRawArena(const CborRawArena&) = delete;
73+
CborRawArena& operator=(const CborRawArena&) = delete;
74+
75+
// No move
76+
CborRawArena(CborRawArena&&) = delete;
77+
CborRawArena& operator=(CborRawArena&&) = delete;
78+
79+
private:
80+
std::list<cbor_raw> singles;
81+
std::list<std::vector<cbor_raw>> arrays;
82+
std::list<std::vector<cbor_map_entry>> maps;
83+
};
2284
Value consume(cbor_nondet_t cbor);
2385

2486
void print_indent(std::ostringstream& os, size_t indent)
@@ -169,6 +231,181 @@ namespace
169231
}
170232
}
171233

234+
std::string format_simple(const Simple& v)
235+
{
236+
const auto casted = static_cast<int>(v);
237+
switch (casted)
238+
{
239+
case SimpleValue::False:
240+
return "Simple: False";
241+
case SimpleValue::True:
242+
return "Simple: True";
243+
case SimpleValue::Null:
244+
return "Simple: Null";
245+
case SimpleValue::Undefined:
246+
return "Simple: Undefined";
247+
default:
248+
return "Simple: " + std::to_string(casted);
249+
}
250+
}
251+
252+
cbor_raw to_raw_cbor(const Value& value, CborRawArena& arena);
253+
254+
cbor_raw to_raw_signed(const Signed& v)
255+
{
256+
return cbor_nondet_mk_int64(v);
257+
}
258+
259+
cbor_raw to_raw_string(const String& v)
260+
{
261+
cbor_raw result;
262+
if (!cbor_nondet_mk_text_string(
263+
reinterpret_cast<uint8_t*>(const_cast<char*>(v.data())),
264+
v.size(),
265+
&result))
266+
{
267+
throw CBOREncodeError(
268+
Error::ENCODE_FAILED, fmt::format("Encoding text string {} failed", v));
269+
}
270+
return result;
271+
}
272+
273+
cbor_raw to_raw_bytes(const Bytes& v)
274+
{
275+
cbor_raw result;
276+
if (!cbor_nondet_mk_byte_string(
277+
const_cast<uint8_t*>(v.data()), v.size(), &result))
278+
{
279+
throw CBOREncodeError(
280+
Error::ENCODE_FAILED,
281+
fmt::format("Encoding bytes string {} failed", ccf::ds::to_hex(v)));
282+
}
283+
return result;
284+
}
285+
286+
cbor_raw to_raw_simple(const Simple& v)
287+
{
288+
cbor_raw result;
289+
if (!cbor_nondet_mk_simple_value(v, &result))
290+
{
291+
throw CBOREncodeError(
292+
Error::ENCODE_FAILED,
293+
fmt::format("Encoding simple value {} failed", format_simple(v)));
294+
}
295+
return result;
296+
}
297+
298+
cbor_raw to_raw_tagged(const Tagged& v, CborRawArena& arena)
299+
{
300+
cbor_raw result;
301+
arena.push(to_raw_cbor(v.item, arena));
302+
if (!cbor_nondet_mk_tagged(v.tag, arena.single(), &result))
303+
{
304+
throw CBOREncodeError(
305+
Error::ENCODE_FAILED, fmt::format("Encoding tag {} failed", v.tag));
306+
}
307+
308+
return result;
309+
}
310+
311+
cbor_raw to_raw_array(const Array& v, CborRawArena& arena)
312+
{
313+
cbor_raw result;
314+
std::vector<cbor_raw> items;
315+
items.reserve(v.items.size());
316+
for (const auto& item : v.items)
317+
{
318+
items.push_back(to_raw_cbor(item, arena));
319+
}
320+
321+
size_t arr_size = items.size();
322+
323+
// A workaround to encode an empty array by passing a fake ptr with size=0.
324+
if (items.empty())
325+
{
326+
items.push_back(cbor_raw{});
327+
}
328+
329+
arena.push(std::move(items));
330+
if (!cbor_nondet_mk_array(arena.array(), arr_size, &result))
331+
{
332+
throw CBOREncodeError(
333+
Error::ENCODE_FAILED,
334+
fmt::format("Encoding array of size {} failed", arr_size));
335+
}
336+
337+
return result;
338+
}
339+
340+
cbor_raw to_raw_map(const Map& v, CborRawArena& arena)
341+
{
342+
cbor_raw result;
343+
344+
std::vector<cbor_map_entry> entries;
345+
entries.reserve(v.items.size());
346+
for (const auto& [key, value] : v.items)
347+
{
348+
auto cbor_key = to_raw_cbor(key, arena);
349+
auto cbor_value = to_raw_cbor(value, arena);
350+
entries.push_back(cbor_nondet_mk_map_entry(cbor_key, cbor_value));
351+
}
352+
353+
size_t map_size = entries.size();
354+
355+
// A workaround to encode an empty map by passing a fake ptr with size=0.
356+
if (entries.empty())
357+
{
358+
entries.push_back(cbor_map_entry{});
359+
}
360+
361+
arena.push(std::move(entries));
362+
if (!cbor_nondet_mk_map(arena.map(), map_size, &result))
363+
{
364+
throw CBOREncodeError(
365+
Error::ENCODE_FAILED,
366+
fmt::format("Encoding map of size {} failed", map_size));
367+
}
368+
369+
return result;
370+
}
371+
372+
cbor_raw to_raw_cbor(const Value& value, CborRawArena& arena)
373+
{
374+
return std::visit(
375+
[&](const auto& v) {
376+
using T = std::decay_t<decltype(v)>;
377+
if constexpr (std::is_same_v<T, Signed>)
378+
{
379+
return to_raw_signed(v);
380+
}
381+
if constexpr (std::is_same_v<T, String>)
382+
{
383+
return to_raw_string(v);
384+
}
385+
if constexpr (std::is_same_v<T, Bytes>)
386+
{
387+
return to_raw_bytes(v);
388+
}
389+
if constexpr (std::is_same_v<T, Simple>)
390+
{
391+
return to_raw_simple(v);
392+
}
393+
if constexpr (std::is_same_v<T, Tagged>)
394+
{
395+
return to_raw_tagged(v, arena);
396+
}
397+
if constexpr (std::is_same_v<T, Array>)
398+
{
399+
return to_raw_array(v, arena);
400+
}
401+
if constexpr (std::is_same_v<T, Map>)
402+
{
403+
return to_raw_map(v, arena);
404+
}
405+
},
406+
value->value);
407+
}
408+
172409
void print_value_impl(
173410
std::ostringstream& os, const Value& value, size_t indent)
174411
{
@@ -195,12 +432,7 @@ namespace
195432
{
196433
os << " ";
197434
}
198-
for (size_t i = 0; i < v.size(); ++i)
199-
{
200-
os << std::hex << std::setw(2) << std::setfill('0')
201-
<< static_cast<int>(v[i]);
202-
}
203-
os << std::dec << std::endl;
435+
os << ccf::ds::to_hex(v) << std::endl;
204436
}
205437
else if constexpr (std::is_same_v<T, String>)
206438
{
@@ -239,24 +471,7 @@ namespace
239471
else if constexpr (std::is_same_v<T, Simple>)
240472
{
241473
print_indent(os, indent);
242-
const auto casted = static_cast<int>(v);
243-
switch (casted)
244-
{
245-
case SimpleValue::False:
246-
os << "Simple: False" << std::endl;
247-
break;
248-
case SimpleValue::True:
249-
os << "Simple: True" << std::endl;
250-
break;
251-
case SimpleValue::Null:
252-
os << "Simple: Null" << std::endl;
253-
break;
254-
case SimpleValue::Undefined:
255-
os << "Simple: Undefined" << std::endl;
256-
break;
257-
default:
258-
os << "Simple: " << casted << std::endl;
259-
}
474+
os << format_simple(v) << std::endl;
260475
}
261476
},
262477
value->value);
@@ -265,6 +480,16 @@ namespace
265480

266481
namespace ccf::cbor
267482
{
483+
CBOREncodeError::CBOREncodeError(Error err, const std::string& what) :
484+
std::runtime_error(what),
485+
error(err)
486+
{}
487+
488+
Error CBOREncodeError::error_code() const
489+
{
490+
return error;
491+
}
492+
268493
CBORDecodeError::CBORDecodeError(Error err, const std::string& what) :
269494
std::runtime_error(what),
270495
error(err)
@@ -311,6 +536,30 @@ namespace ccf::cbor
311536
return consume(cbor);
312537
}
313538

539+
std::vector<uint8_t> serialize(const Value& value)
540+
{
541+
CborRawArena arena{};
542+
auto raw = to_raw_cbor(value, arena);
543+
const auto expected_size =
544+
cbor_nondet_size(raw, std::numeric_limits<size_t>::max());
545+
546+
std::vector<uint8_t> result(expected_size);
547+
548+
const auto bytes_written =
549+
cbor_nondet_serialize(raw, result.data(), expected_size);
550+
if (bytes_written != expected_size)
551+
{
552+
throw CBOREncodeError(
553+
Error::ENCODE_FAILED,
554+
fmt::format(
555+
"Encoded CBOR of size {} when expected {}",
556+
bytes_written,
557+
expected_size));
558+
}
559+
560+
return result;
561+
}
562+
314563
std::string to_string(const Value& value)
315564
{
316565
std::ostringstream os;

src/crypto/cbor.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,17 @@ namespace ccf::cbor
6060
KEY_NOT_FOUND = 2,
6161
OUT_OF_BOUND = 3,
6262
TYPE_MISMATCH = 4,
63+
ENCODE_FAILED = 5,
64+
};
65+
66+
class CBOREncodeError : public std::runtime_error
67+
{
68+
public:
69+
explicit CBOREncodeError(Error err, const std::string& what);
70+
[[nodiscard]] Error error_code() const;
71+
72+
private:
73+
Error error{Error::UNDEFINED};
6374
};
6475

6576
class CBORDecodeError : public std::runtime_error
@@ -92,6 +103,8 @@ namespace ccf::cbor
92103
Value make_bytes(std::span<const uint8_t> data);
93104

94105
Value parse(std::span<const uint8_t> raw);
106+
std::vector<uint8_t> serialize(const Value& value);
107+
95108
std::string to_string(const Value& value);
96109
bool simple_to_boolean(const Simple& value);
97110

0 commit comments

Comments
 (0)