1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "tools/cddl/codegen.h"
6
7 #include <cinttypes>
8 #include <iostream>
9 #include <limits>
10 #include <memory>
11 #include <set>
12 #include <sstream>
13 #include <string>
14 #include <utility>
15 #include <vector>
16
17 #include "absl/algorithm/container.h"
18 #include "absl/types/optional.h"
19
20 // Convert '-' to '_' to use a CDDL identifier as a C identifier.
ToUnderscoreId(const std::string & x)21 std::string ToUnderscoreId(const std::string& x) {
22 std::string result(x);
23 for (auto& c : result) {
24 if (c == '-')
25 c = '_';
26 }
27 return result;
28 }
29
30 // Convert a CDDL identifier to camel case for use as a C typename. E.g.
31 // presentation-connection-message to PresentationConnectionMessage.
ToCamelCase(const std::string & x)32 std::string ToCamelCase(const std::string& x) {
33 std::string result(x);
34 result[0] = toupper(result[0]);
35 size_t new_size = 1;
36 size_t result_size = result.size();
37 for (size_t i = 1; i < result_size; ++i, ++new_size) {
38 if (result[i] == '-') {
39 ++i;
40 if (i < result_size)
41 result[new_size] = toupper(result[i]);
42 } else {
43 result[new_size] = result[i];
44 }
45 }
46 result.resize(new_size);
47 return result;
48 }
49
50 // Returns a string which represents the C++ type of |cpp_type|. Returns an
51 // empty string if there is no valid representation for |cpp_type| (e.g. a
52 // vector with an invalid element type).
CppTypeToString(const CppType & cpp_type)53 std::string CppTypeToString(const CppType& cpp_type) {
54 switch (cpp_type.which) {
55 case CppType::Which::kUint64:
56 return "uint64_t";
57 case CppType::Which::kString:
58 return "std::string";
59 case CppType::Which::kBytes: {
60 if (cpp_type.bytes_type.fixed_size) {
61 std::string size_string =
62 std::to_string(cpp_type.bytes_type.fixed_size.value());
63 return "std::array<uint8_t, " + size_string + ">";
64 } else {
65 return "std::vector<uint8_t>";
66 }
67 }
68 case CppType::Which::kVector: {
69 std::string element_string =
70 CppTypeToString(*cpp_type.vector_type.element_type);
71 if (element_string.empty())
72 return std::string();
73 return "std::vector<" + element_string + ">";
74 }
75 case CppType::Which::kEnum:
76 return ToCamelCase(cpp_type.name);
77 case CppType::Which::kStruct:
78 return ToCamelCase(cpp_type.name);
79 case CppType::Which::kTaggedType:
80 return CppTypeToString(*cpp_type.tagged_type.real_type);
81 default:
82 return std::string();
83 }
84 }
85
WriteEnumEqualityOperatorSwitchCases(int fd,const CppType & parent,std::string child_name,std::string parent_name)86 bool WriteEnumEqualityOperatorSwitchCases(int fd,
87 const CppType& parent,
88 std::string child_name,
89 std::string parent_name) {
90 for (const auto& x : parent.enum_type.members) {
91 std::string enum_value = "k" + ToCamelCase(x.first);
92 dprintf(fd, " case %s::%s: return parent == %s::%s;\n",
93 child_name.c_str(), enum_value.c_str(), parent_name.c_str(),
94 enum_value.c_str());
95 }
96
97 return absl::c_all_of(parent.enum_type.sub_members,
98 [&fd, &child_name, &parent_name](CppType* new_parent) {
99 return WriteEnumEqualityOperatorSwitchCases(
100 fd, *new_parent, child_name, parent_name);
101 });
102 }
103
104 // Write the equality operators for comparing an enum and its parent types.
WriteEnumEqualityOperator(int fd,const CppType & type,const CppType & parent)105 bool WriteEnumEqualityOperator(int fd,
106 const CppType& type,
107 const CppType& parent) {
108 std::string name = ToCamelCase(type.name);
109 std::string parent_name = ToCamelCase(parent.name);
110
111 // Define type == parentType.
112 dprintf(fd, "inline bool operator==(const %s& child, const %s& parent) {\n",
113 name.c_str(), parent_name.c_str());
114 dprintf(fd, " switch (child) {\n");
115 if (!WriteEnumEqualityOperatorSwitchCases(fd, parent, name, parent_name)) {
116 return false;
117 }
118 dprintf(fd, " default: return false;\n");
119 dprintf(fd, " }\n}\n");
120
121 // Define parentType == type.
122 dprintf(fd, "inline bool operator==(const %s& parent, const %s& child) {\n",
123 parent_name.c_str(), name.c_str());
124 dprintf(fd, " return child == parent;\n}\n");
125
126 // Define type != parentType.
127 dprintf(fd, "inline bool operator!=(const %s& child, const %s& parent) {\n",
128 name.c_str(), parent_name.c_str());
129 dprintf(fd, " return !(child == parent);\n}\n");
130
131 // Define parentType != type.
132 dprintf(fd, "inline bool operator!=(const %s& parent, const %s& child) {\n",
133 parent_name.c_str(), name.c_str());
134 dprintf(fd, " return !(parent == child);\n}\n");
135
136 return true;
137 }
138
WriteEnumStreamOperatorSwitchCases(int fd,const CppType & type,std::string name)139 bool WriteEnumStreamOperatorSwitchCases(int fd,
140 const CppType& type,
141 std::string name) {
142 for (const auto& x : type.enum_type.members) {
143 std::string enum_value = "k" + ToCamelCase(x.first);
144 dprintf(fd, " case %s::%s: os << \"%s\"; break;\n", name.c_str(),
145 enum_value.c_str(), enum_value.c_str());
146 }
147
148 return absl::c_all_of(
149 type.enum_type.sub_members, [&fd, &name](CppType* parent) {
150 return WriteEnumStreamOperatorSwitchCases(fd, *parent, name);
151 });
152 }
153
WriteEnumOperators(int fd,const CppType & type)154 bool WriteEnumOperators(int fd, const CppType& type) {
155 // Write << operator.
156 std::string name = ToCamelCase(type.name);
157 dprintf(
158 fd,
159 "inline std::ostream& operator<<(std::ostream& os, const %s& val) {\n",
160 name.c_str());
161 dprintf(fd, " switch (val) {\n");
162 if (!WriteEnumStreamOperatorSwitchCases(fd, type, name)) {
163 return false;
164 }
165 dprintf(fd,
166 " default: os << \"Unknown Value: \" << static_cast<int>(val);"
167 "\n break;\n }\n return os;\n}\n");
168
169 // Write equality operators.
170 return absl::c_all_of(type.enum_type.sub_members,
171 [&fd, &type](CppType* parent) {
172 return WriteEnumEqualityOperator(fd, type, *parent);
173 });
174 }
175
176 // Writes the equality operator for a specific Discriminated Union.
WriteDiscriminatedUnionEqualityOperator(int fd,const CppType & type,const std::string & name_prefix="")177 bool WriteDiscriminatedUnionEqualityOperator(
178 int fd,
179 const CppType& type,
180 const std::string& name_prefix = "") {
181 const std::string name = name_prefix + ToCamelCase(type.name);
182 dprintf(fd, "\nbool %s::operator==(const %s& other) const {\n", name.c_str(),
183 name.c_str());
184 dprintf(fd, " return this->which == other.which");
185 for (auto* union_member : type.discriminated_union.members) {
186 dprintf(fd, " &&\n ");
187 switch (union_member->which) {
188 case CppType::Which::kUint64:
189 dprintf(fd,
190 "(this->which != Which::kUint64 || this->uint == other.uint)");
191 break;
192 case CppType::Which::kString:
193 dprintf(fd,
194 "(this->which != Which::kString || this->str == other.str)");
195 break;
196 case CppType::Which::kBytes:
197 dprintf(fd,
198 "(this->which != Which::kBytes || this->bytes == other.bytes)");
199 break;
200 default:
201 return false;
202 }
203 }
204 dprintf(fd, ";\n}\n");
205 dprintf(fd, "bool %s::operator!=(const %s& other) const {\n", name.c_str(),
206 name.c_str());
207 dprintf(fd, " return !(*this == other);\n}\n");
208 return true;
209 }
210
211 // Writes the equality operator for a specific C++ struct.
WriteStructEqualityOperator(int fd,const CppType & type,const std::string & name_prefix="")212 bool WriteStructEqualityOperator(int fd,
213 const CppType& type,
214 const std::string& name_prefix = "") {
215 const std::string name = name_prefix + ToCamelCase(type.name);
216 dprintf(fd, "\nbool %s::operator==(const %s& other) const {\n", name.c_str(),
217 name.c_str());
218 for (size_t i = 0; i < type.struct_type.members.size(); i++) {
219 if (i == 0) {
220 dprintf(fd, " return ");
221 } else {
222 dprintf(fd, " &&\n ");
223 }
224 auto name = ToUnderscoreId(type.struct_type.members[i].name);
225 dprintf(fd, "this->%s == other.%s", name.c_str(), name.c_str());
226 }
227 dprintf(fd, ";\n}");
228 dprintf(fd, "\nbool %s::operator!=(const %s& other) const {\n", name.c_str(),
229 name.c_str());
230 dprintf(fd, " return !(*this == other);\n}\n");
231 std::string new_prefix = name_prefix + ToCamelCase(type.name) + "::";
232 for (const auto& x : type.struct_type.members) {
233 // NOTE: Don't need to call recursively on struct members, since all structs
234 // are handled in the calling method.
235 if (x.type->which == CppType::Which::kDiscriminatedUnion) {
236 if (!WriteDiscriminatedUnionEqualityOperator(fd, *x.type, new_prefix)) {
237 return false;
238 }
239 }
240 }
241 return true;
242 }
243
244 // Write the C++ struct member definitions of every type in |members| to the
245 // file descriptor |fd|.
WriteStructMembers(int fd,const std::string & name,const std::vector<CppType::Struct::CppMember> & members)246 bool WriteStructMembers(
247 int fd,
248 const std::string& name,
249 const std::vector<CppType::Struct::CppMember>& members) {
250 for (const auto& x : members) {
251 std::string type_string;
252 switch (x.type->which) {
253 case CppType::Which::kStruct: {
254 if (x.type->struct_type.key_type ==
255 CppType::Struct::KeyType::kPlainGroup) {
256 if (!WriteStructMembers(fd, x.type->name,
257 x.type->struct_type.members))
258 return false;
259 continue;
260 } else {
261 type_string = ToCamelCase(x.name);
262 }
263 } break;
264 case CppType::Which::kOptional: {
265 // TODO(btolsch): Make this optional<T> when one lands.
266 dprintf(fd, " bool has_%s;\n", ToUnderscoreId(x.name).c_str());
267 type_string = CppTypeToString(*x.type->optional_type);
268 } break;
269 case CppType::Which::kDiscriminatedUnion: {
270 std::string cid = ToUnderscoreId(x.name);
271 type_string = ToCamelCase(x.name);
272 dprintf(fd, " struct %s {\n", type_string.c_str());
273 dprintf(fd, " %s();\n ~%s();\n\n", type_string.c_str(),
274 type_string.c_str());
275
276 dprintf(fd, " bool operator==(const %s& other) const;\n",
277 type_string.c_str());
278 dprintf(fd, " bool operator!=(const %s& other) const;\n\n",
279 type_string.c_str());
280 dprintf(fd, " enum class Which {\n");
281 for (auto* union_member : x.type->discriminated_union.members) {
282 switch (union_member->which) {
283 case CppType::Which::kUint64:
284 dprintf(fd, " kUint64,\n");
285 break;
286 case CppType::Which::kString:
287 dprintf(fd, " kString,\n");
288 break;
289 case CppType::Which::kBytes:
290 dprintf(fd, " kBytes,\n");
291 break;
292 default:
293 return false;
294 }
295 }
296 dprintf(fd, " kUninitialized,\n");
297 dprintf(fd, " } which;\n");
298 dprintf(fd, " union {\n");
299 for (auto* union_member : x.type->discriminated_union.members) {
300 switch (union_member->which) {
301 case CppType::Which::kUint64:
302 dprintf(fd, " uint64_t uint;\n");
303 break;
304 case CppType::Which::kString:
305 dprintf(fd, " std::string str;\n");
306 break;
307 case CppType::Which::kBytes:
308 dprintf(fd, " std::vector<uint8_t> bytes;\n");
309 break;
310 default:
311 return false;
312 }
313 }
314 // NOTE: This member allows the union to be easily constructed in an
315 // effectively uninitialized state. Its value should never be used.
316 dprintf(fd, " bool placeholder_;\n");
317 dprintf(fd, " };\n");
318 dprintf(fd, " };\n");
319 } break;
320 default:
321 type_string = CppTypeToString(*x.type);
322 break;
323 }
324 if (type_string.empty())
325 return false;
326 dprintf(fd, " %s %s;\n", type_string.c_str(),
327 ToUnderscoreId(x.name).c_str());
328 }
329 return true;
330 }
331
WriteEnumMembers(int fd,const CppType & type)332 void WriteEnumMembers(int fd, const CppType& type) {
333 for (const auto& x : type.enum_type.members) {
334 dprintf(fd, " k%s = %" PRIu64 "ull,\n", ToCamelCase(x.first).c_str(),
335 x.second);
336 }
337 for (const auto* x : type.enum_type.sub_members) {
338 WriteEnumMembers(fd, *x);
339 }
340 }
341
342 // Writes a C++ type definition for |type| to the file descriptor |fd|. This
343 // only generates a definition for enums and structs.
WriteTypeDefinition(int fd,const CppType & type)344 bool WriteTypeDefinition(int fd, const CppType& type) {
345 std::string name = ToCamelCase(type.name);
346 switch (type.which) {
347 case CppType::Which::kEnum: {
348 dprintf(fd, "\nenum class %s : uint64_t {\n", name.c_str());
349 WriteEnumMembers(fd, type);
350 dprintf(fd, "};\n");
351 if (!WriteEnumOperators(fd, type))
352 return false;
353 } break;
354 case CppType::Which::kStruct: {
355 dprintf(fd, "\nstruct %s {\n", name.c_str());
356 if (type.type_key != absl::nullopt) {
357 dprintf(fd, " // type key: %" PRIu64 "\n", type.type_key.value());
358 }
359 dprintf(fd, " bool operator==(const %s& other) const;\n", name.c_str());
360 dprintf(fd, " bool operator!=(const %s& other) const;\n\n",
361 name.c_str());
362 if (!WriteStructMembers(fd, type.name, type.struct_type.members))
363 return false;
364 dprintf(fd, "};\n");
365 } break;
366 default:
367 break;
368 }
369 return true;
370 }
371
372 // Ensures that any dependencies within |cpp_type| are written to the file
373 // descriptor |fd| before writing |cpp_type| to the file descriptor |fd|. This
374 // is done by walking the tree of types defined by |cpp_type| (e.g. all the
375 // members for a struct). |defs| contains the names of types that have already
376 // been written. If a type hasn't been written and needs to be, its name will
377 // also be added to |defs|.
EnsureDependentTypeDefinitionsWritten(int fd,const CppType & cpp_type,std::set<std::string> * defs)378 bool EnsureDependentTypeDefinitionsWritten(int fd,
379 const CppType& cpp_type,
380 std::set<std::string>* defs) {
381 switch (cpp_type.which) {
382 case CppType::Which::kVector: {
383 return EnsureDependentTypeDefinitionsWritten(
384 fd, *cpp_type.vector_type.element_type, defs);
385 }
386 case CppType::Which::kEnum: {
387 if (defs->find(cpp_type.name) != defs->end())
388 return true;
389 for (const auto* x : cpp_type.enum_type.sub_members)
390 if (!EnsureDependentTypeDefinitionsWritten(fd, *x, defs))
391 return false;
392 defs->emplace(cpp_type.name);
393 WriteTypeDefinition(fd, cpp_type);
394 } break;
395 case CppType::Which::kStruct: {
396 if (cpp_type.struct_type.key_type !=
397 CppType::Struct::KeyType::kPlainGroup) {
398 if (defs->find(cpp_type.name) != defs->end())
399 return true;
400 for (const auto& x : cpp_type.struct_type.members)
401 if (!EnsureDependentTypeDefinitionsWritten(fd, *x.type, defs))
402 return false;
403 defs->emplace(cpp_type.name);
404 WriteTypeDefinition(fd, cpp_type);
405 }
406 } break;
407 case CppType::Which::kOptional: {
408 return EnsureDependentTypeDefinitionsWritten(fd, *cpp_type.optional_type,
409 defs);
410 }
411 case CppType::Which::kDiscriminatedUnion: {
412 for (const auto* x : cpp_type.discriminated_union.members)
413 if (!EnsureDependentTypeDefinitionsWritten(fd, *x, defs))
414 return false;
415 } break;
416 case CppType::Which::kTaggedType: {
417 if (!EnsureDependentTypeDefinitionsWritten(
418 fd, *cpp_type.tagged_type.real_type, defs)) {
419 return false;
420 }
421 } break;
422 default:
423 break;
424 }
425 return true;
426 }
427
428 // Writes the type definition for every C++ type in |table|. This function
429 // makes sure to write them in such an order that all type dependencies are
430 // written before they are need so the resulting text in the file descriptor
431 // |fd| will compile without modification. For example, the following would be
432 // bad output:
433 //
434 // struct Foo {
435 // Bar bar;
436 // int x;
437 // };
438 //
439 // struct Bar {
440 // int alpha;
441 // };
442 //
443 // This function ensures that Bar would be written sometime before Foo.
WriteTypeDefinitions(int fd,CppSymbolTable * table)444 bool WriteTypeDefinitions(int fd, CppSymbolTable* table) {
445 std::set<std::string> defs;
446 for (const std::unique_ptr<CppType>& real_type : table->cpp_types) {
447 if (real_type->which != CppType::Which::kStruct ||
448 real_type->struct_type.key_type ==
449 CppType::Struct::KeyType::kPlainGroup) {
450 continue;
451 }
452 if (!EnsureDependentTypeDefinitionsWritten(fd, *real_type, &defs))
453 return false;
454 }
455
456 dprintf(fd, "\nenum class Type : uint64_t {\n");
457 dprintf(fd, " kUnknown = 0ull,\n");
458 for (CppType* type : table->TypesWithId()) {
459 dprintf(fd, " k%s = %" PRIu64 "ull,\n", ToCamelCase(type->name).c_str(),
460 type->type_key.value());
461 }
462 dprintf(fd, "};\n");
463 return true;
464 }
465
466 // Writes a parser that takes in a uint64_t and outputs the corresponding Type
467 // if one matches up, or Type::kUnknown if none does.
468 // NOTE: In future, this could be changes to use a Trie, which would allow for
469 // manufacturers to more easily add their own type ids to ours.
WriteTypeParserDefinition(int fd,CppSymbolTable * table)470 bool WriteTypeParserDefinition(int fd, CppSymbolTable* table) {
471 dprintf(fd, "\n//static\n");
472 dprintf(fd, "Type TypeEnumValidator::SafeCast(uint64_t type_id) {\n");
473 dprintf(fd, " switch (type_id) {\n");
474 for (CppType* type : table->TypesWithId()) {
475 dprintf(fd, " case uint64_t{%" PRIu64 "}: return Type::k%s;\n",
476 type->type_key.value(), ToCamelCase(type->name).c_str());
477 }
478 dprintf(fd, " default: return Type::kUnknown;\n");
479 dprintf(fd, " }\n}\n");
480 return true;
481 }
482
483 // Writes the function prototypes for the encode and decode functions for each
484 // type in |table| to the file descriptor |fd|.
WriteFunctionDeclarations(int fd,CppSymbolTable * table)485 bool WriteFunctionDeclarations(int fd, CppSymbolTable* table) {
486 for (CppType* real_type : table->TypesWithId()) {
487 const auto& name = real_type->name;
488 if (real_type->which != CppType::Which::kStruct ||
489 real_type->struct_type.key_type ==
490 CppType::Struct::KeyType::kPlainGroup) {
491 return false;
492 }
493 std::string cpp_name = ToCamelCase(name);
494 dprintf(fd, "\nbool Encode%s(\n", cpp_name.c_str());
495 dprintf(fd, " const %s& data,\n", cpp_name.c_str());
496 dprintf(fd, " CborEncodeBuffer* buffer);\n");
497 dprintf(fd, "ssize_t Encode%s(\n", cpp_name.c_str());
498 dprintf(fd, " const %s& data,\n", cpp_name.c_str());
499 dprintf(fd, " uint8_t* buffer,\n size_t length);\n");
500 dprintf(fd, "ssize_t Decode%s(\n", cpp_name.c_str());
501 dprintf(fd, " const uint8_t* buffer,\n size_t length,\n");
502 dprintf(fd, " %s* data);\n", cpp_name.c_str());
503 }
504 return true;
505 }
506
507 bool WriteMapEncoder(int fd,
508 const std::string& name,
509 const std::vector<CppType::Struct::CppMember>& members,
510 const std::string& nested_type_scope,
511 int encoder_depth = 1);
512 bool WriteArrayEncoder(int fd,
513 const std::string& name,
514 const std::vector<CppType::Struct::CppMember>& members,
515 const std::string& nested_type_scope,
516 int encoder_depth = 1);
517
518 // Writes the encoding function for the C++ type |cpp_type| to the file
519 // descriptor |fd|. |name| is the C++ variable name that needs to be encoded.
520 // |nested_type_scope| is the closest C++ scope name (i.e. struct name), which
521 // may be used to access local enum constants. |encoder_depth| is used to
522 // independently name independent cbor encoders that need to be created.
WriteEncoder(int fd,const std::string & name,const CppType & cpp_type,const std::string & nested_type_scope,int encoder_depth)523 bool WriteEncoder(int fd,
524 const std::string& name,
525 const CppType& cpp_type,
526 const std::string& nested_type_scope,
527 int encoder_depth) {
528 switch (cpp_type.which) {
529 case CppType::Which::kStruct:
530 if (cpp_type.struct_type.key_type == CppType::Struct::KeyType::kMap) {
531 if (!WriteMapEncoder(fd, name, cpp_type.struct_type.members,
532 cpp_type.name, encoder_depth + 1)) {
533 return false;
534 }
535 return true;
536 } else if (cpp_type.struct_type.key_type ==
537 CppType::Struct::KeyType::kArray) {
538 if (!WriteArrayEncoder(fd, name, cpp_type.struct_type.members,
539 cpp_type.name, encoder_depth + 1)) {
540 return false;
541 }
542 return true;
543 } else {
544 for (const auto& x : cpp_type.struct_type.members) {
545 if (x.integer_key.has_value()) {
546 dprintf(fd,
547 " CBOR_RETURN_ON_ERROR(cbor_encode_uint("
548 "&encoder%d, %" PRIu64 ");\n",
549 encoder_depth, x.integer_key.value());
550 } else {
551 dprintf(fd,
552 " CBOR_RETURN_ON_ERROR(cbor_encode_text_string("
553 "&encoder%d, \"%s\", sizeof(\"%s\") - 1));\n",
554 encoder_depth, x.name.c_str(), x.name.c_str());
555 }
556 if (!WriteEncoder(fd, name + "." + ToUnderscoreId(x.name), *x.type,
557 nested_type_scope, encoder_depth)) {
558 return false;
559 }
560 }
561 return true;
562 }
563 case CppType::Which::kUint64:
564 dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_encode_uint(&encoder%d, %s));\n",
565 encoder_depth, ToUnderscoreId(name).c_str());
566 return true;
567 case CppType::Which::kString: {
568 std::string cid = ToUnderscoreId(name);
569 dprintf(fd, " if (!IsValidUtf8(%s)) {\n", cid.c_str());
570 dprintf(fd, " return -CborErrorInvalidUtf8TextString;\n");
571 dprintf(fd, " }\n");
572 dprintf(fd,
573 " CBOR_RETURN_ON_ERROR(cbor_encode_text_string(&encoder%d, "
574 "%s.c_str(), %s.size()));\n",
575 encoder_depth, cid.c_str(), cid.c_str());
576 return true;
577 }
578 case CppType::Which::kBytes: {
579 std::string cid = ToUnderscoreId(name);
580 dprintf(fd,
581 " CBOR_RETURN_ON_ERROR(cbor_encode_byte_string(&encoder%d, "
582 "%s.data(), "
583 "%s.size()));\n",
584 encoder_depth, cid.c_str(), cid.c_str());
585 return true;
586 }
587 case CppType::Which::kVector: {
588 std::string cid = ToUnderscoreId(name);
589 dprintf(fd, " {\n");
590 if (cpp_type.vector_type.min_length !=
591 CppType::Vector::kMinLengthUnbounded) {
592 dprintf(fd, " if (%s.size() < %d) {\n", cid.c_str(),
593 cpp_type.vector_type.min_length);
594 dprintf(fd, " return -CborErrorTooFewItems;\n");
595 dprintf(fd, " }\n");
596 }
597 if (cpp_type.vector_type.max_length !=
598 CppType::Vector::kMaxLengthUnbounded) {
599 dprintf(fd, " if (%s.size() > %d) {\n", cid.c_str(),
600 cpp_type.vector_type.max_length);
601 dprintf(fd, " return -CborErrorTooManyItems;\n");
602 dprintf(fd, " }\n");
603 }
604 dprintf(fd, " CborEncoder encoder%d;\n", encoder_depth + 1);
605 dprintf(fd,
606 " CBOR_RETURN_ON_ERROR(cbor_encoder_create_array(&encoder%d, "
607 "&encoder%d, %s.size()));\n",
608 encoder_depth, encoder_depth + 1, cid.c_str());
609 dprintf(fd, " for (const auto& x : %s) {\n", cid.c_str());
610 if (!WriteEncoder(fd, "x", *cpp_type.vector_type.element_type,
611 nested_type_scope, encoder_depth + 1)) {
612 return false;
613 }
614 dprintf(fd, " }\n");
615 dprintf(fd,
616 " CBOR_RETURN_ON_ERROR(cbor_encoder_close_container(&encoder%d, "
617 "&encoder%d));\n",
618 encoder_depth, encoder_depth + 1);
619 dprintf(fd, " }\n");
620 return true;
621 }
622 case CppType::Which::kEnum: {
623 dprintf(fd,
624 " CBOR_RETURN_ON_ERROR(cbor_encode_uint(&encoder%d, "
625 "static_cast<uint64_t>(%s)));\n",
626 encoder_depth, ToUnderscoreId(name).c_str());
627 return true;
628 }
629 case CppType::Which::kDiscriminatedUnion: {
630 for (const auto* union_member : cpp_type.discriminated_union.members) {
631 switch (union_member->which) {
632 case CppType::Which::kUint64:
633 dprintf(fd, " case %s::%s::Which::kUint64:\n",
634 ToCamelCase(nested_type_scope).c_str(),
635 ToCamelCase(cpp_type.name).c_str());
636 if (!WriteEncoder(fd, ToUnderscoreId(name + ".uint"), *union_member,
637 nested_type_scope, encoder_depth)) {
638 return false;
639 }
640 dprintf(fd, " break;\n");
641 break;
642 case CppType::Which::kString:
643 dprintf(fd, " case %s::%s::Which::kString:\n",
644 ToCamelCase(nested_type_scope).c_str(),
645 ToCamelCase(cpp_type.name).c_str());
646 if (!WriteEncoder(fd, ToUnderscoreId(name + ".str"), *union_member,
647 nested_type_scope, encoder_depth)) {
648 return false;
649 }
650 dprintf(fd, " break;\n");
651 break;
652 case CppType::Which::kBytes:
653 dprintf(fd, " case %s::%s::Which::kBytes:\n",
654 ToCamelCase(nested_type_scope).c_str(),
655 ToCamelCase(cpp_type.name).c_str());
656 if (!WriteEncoder(fd, ToUnderscoreId(name + ".bytes"),
657 *union_member, nested_type_scope,
658 encoder_depth)) {
659 return false;
660 }
661 dprintf(fd, " break;\n");
662 break;
663 default:
664 return false;
665 }
666 }
667 dprintf(fd, " case %s::%s::Which::kUninitialized:\n",
668 ToCamelCase(nested_type_scope).c_str(),
669 ToCamelCase(cpp_type.name).c_str());
670 dprintf(fd, " return -CborUnknownError;\n");
671 return true;
672 }
673 case CppType::Which::kTaggedType: {
674 dprintf(fd,
675 " CBOR_RETURN_ON_ERROR(cbor_encode_tag(&encoder%d, %" PRIu64
676 "ull));\n",
677 encoder_depth, cpp_type.tagged_type.tag);
678 if (!WriteEncoder(fd, name, *cpp_type.tagged_type.real_type,
679 nested_type_scope, encoder_depth)) {
680 return false;
681 }
682 return true;
683 }
684 default:
685 break;
686 }
687 return false;
688 }
689
690 struct MemberCountResult {
691 int num_required;
692 int num_optional;
693 };
694
CountMemberTypes(int fd,const std::string & name_id,const std::vector<CppType::Struct::CppMember> & members)695 MemberCountResult CountMemberTypes(
696 int fd,
697 const std::string& name_id,
698 const std::vector<CppType::Struct::CppMember>& members) {
699 int num_required = 0;
700 int num_optional = 0;
701 for (const auto& x : members) {
702 if (x.type->which == CppType::Which::kOptional) {
703 std::string x_id = ToUnderscoreId(x.name);
704 if (num_optional == 0) {
705 dprintf(fd, " int num_optionals_present = %s.has_%s;\n",
706 name_id.c_str(), x_id.c_str());
707 } else {
708 dprintf(fd, " num_optionals_present += %s.has_%s;\n", name_id.c_str(),
709 x_id.c_str());
710 }
711 ++num_optional;
712 } else {
713 ++num_required;
714 }
715 }
716 return MemberCountResult{num_required, num_optional};
717 }
718
719 // Writes the encoding function for a CBOR map with the C++ type members in
720 // |members| to the file descriptor |fd|. |name| is the C++ variable name that
721 // needs to be encoded. |nested_type_scope| is the closest C++ scope name (i.e.
722 // struct name), which may be used to access local enum constants.
723 // |encoder_depth| is used to independently name independent cbor encoders that
724 // need to be created.
WriteMapEncoder(int fd,const std::string & name,const std::vector<CppType::Struct::CppMember> & members,const std::string & nested_type_scope,int encoder_depth)725 bool WriteMapEncoder(int fd,
726 const std::string& name,
727 const std::vector<CppType::Struct::CppMember>& members,
728 const std::string& nested_type_scope,
729 int encoder_depth) {
730 std::string name_id = ToUnderscoreId(name);
731 dprintf(fd, " CborEncoder encoder%d;\n", encoder_depth);
732 MemberCountResult member_counts = CountMemberTypes(fd, name_id, members);
733 if (member_counts.num_optional == 0) {
734 dprintf(fd,
735 " CBOR_RETURN_ON_ERROR(cbor_encoder_create_map(&encoder%d, "
736 "&encoder%d, "
737 "%d));\n",
738 encoder_depth - 1, encoder_depth, member_counts.num_required);
739 } else {
740 dprintf(fd,
741 " CBOR_RETURN_ON_ERROR(cbor_encoder_create_map(&encoder%d, "
742 "&encoder%d, "
743 "%d + num_optionals_present));\n",
744 encoder_depth - 1, encoder_depth, member_counts.num_required);
745 }
746
747 for (const auto& x : members) {
748 std::string fullname = name;
749 CppType* member_type = x.type;
750 if (x.type->which != CppType::Which::kStruct ||
751 x.type->struct_type.key_type != CppType::Struct::KeyType::kPlainGroup) {
752 if (x.type->which == CppType::Which::kOptional) {
753 member_type = x.type->optional_type;
754 dprintf(fd, " if (%s.has_%s) {\n", name_id.c_str(),
755 ToUnderscoreId(x.name).c_str());
756 }
757
758 if (x.integer_key.has_value()) {
759 dprintf(fd,
760 " CBOR_RETURN_ON_ERROR(cbor_encode_uint(&encoder%d, %" PRIu64
761 "));\n",
762 encoder_depth, x.integer_key.value());
763 } else {
764 dprintf(fd,
765 " CBOR_RETURN_ON_ERROR(cbor_encode_text_string(&encoder%d, "
766 "\"%s\", sizeof(\"%s\") - 1));\n",
767 encoder_depth, x.name.c_str(), x.name.c_str());
768 }
769 if (x.type->which == CppType::Which::kDiscriminatedUnion) {
770 dprintf(fd, " switch (%s.%s.which) {\n", fullname.c_str(),
771 x.name.c_str());
772 }
773 fullname = fullname + "." + x.name;
774 }
775 if (!WriteEncoder(fd, fullname, *member_type, nested_type_scope,
776 encoder_depth)) {
777 return false;
778 }
779 if (x.type->which == CppType::Which::kOptional ||
780 x.type->which == CppType::Which::kDiscriminatedUnion) {
781 dprintf(fd, " }\n");
782 }
783 }
784
785 dprintf(fd,
786 " CBOR_RETURN_ON_ERROR(cbor_encoder_close_container(&encoder%d, "
787 "&encoder%d));\n",
788 encoder_depth - 1, encoder_depth);
789 return true;
790 }
791
792 // Writes the encoding function for a CBOR array with the C++ type members in
793 // |members| to the file descriptor |fd|. |name| is the C++ variable name that
794 // needs to be encoded. |nested_type_scope| is the closest C++ scope name (i.e.
795 // struct name), which may be used to access local enum constants.
796 // |encoder_depth| is used to independently name independent cbor encoders that
797 // need to be created.
WriteArrayEncoder(int fd,const std::string & name,const std::vector<CppType::Struct::CppMember> & members,const std::string & nested_type_scope,int encoder_depth)798 bool WriteArrayEncoder(int fd,
799 const std::string& name,
800 const std::vector<CppType::Struct::CppMember>& members,
801 const std::string& nested_type_scope,
802 int encoder_depth) {
803 std::string name_id = ToUnderscoreId(name);
804 dprintf(fd, " CborEncoder encoder%d;\n", encoder_depth);
805 MemberCountResult member_counts = CountMemberTypes(fd, name_id, members);
806 if (member_counts.num_optional == 0) {
807 dprintf(fd,
808 " CBOR_RETURN_ON_ERROR(cbor_encoder_create_array(&encoder%d, "
809 "&encoder%d, %d));\n",
810 encoder_depth - 1, encoder_depth, member_counts.num_required);
811 } else {
812 dprintf(fd,
813 " CBOR_RETURN_ON_ERROR(cbor_encoder_create_array(&encoder%d, "
814 "&encoder%d, %d + num_optionals_present));\n",
815 encoder_depth - 1, encoder_depth, member_counts.num_required);
816 }
817
818 for (const auto& x : members) {
819 std::string fullname = name;
820 CppType* member_type = x.type;
821 if (x.type->which != CppType::Which::kStruct ||
822 x.type->struct_type.key_type != CppType::Struct::KeyType::kPlainGroup) {
823 if (x.type->which == CppType::Which::kOptional) {
824 member_type = x.type->optional_type;
825 dprintf(fd, " if (%s.has_%s) {\n", name_id.c_str(),
826 ToUnderscoreId(x.name).c_str());
827 }
828 if (x.type->which == CppType::Which::kDiscriminatedUnion) {
829 dprintf(fd, " switch (%s.%s.which) {\n", fullname.c_str(),
830 x.name.c_str());
831 }
832 fullname = fullname + "." + x.name;
833 }
834 if (!WriteEncoder(fd, fullname, *member_type, nested_type_scope,
835 encoder_depth)) {
836 return false;
837 }
838 if (x.type->which == CppType::Which::kOptional ||
839 x.type->which == CppType::Which::kDiscriminatedUnion) {
840 dprintf(fd, " }\n");
841 }
842 }
843
844 dprintf(fd,
845 " CBOR_RETURN_ON_ERROR(cbor_encoder_close_container(&encoder%d, "
846 "&encoder%d));\n",
847 encoder_depth - 1, encoder_depth);
848 return true;
849 }
850
GetByte(uint64_t value,size_t byte)851 uint8_t GetByte(uint64_t value, size_t byte) {
852 return static_cast<uint8_t>((value >> (byte * 8)) & 0xFF);
853 }
854
GetEncodedTypeKey(const CppType & type)855 std::string GetEncodedTypeKey(const CppType& type) {
856 if (type.type_key == absl::nullopt) {
857 return "";
858 }
859
860 // Determine all constants needed for calculating the encoded id bytes.
861 uint64_t type_id = type.type_key.value();
862 uint8_t encoding_size;
863 uint8_t start_processing_byte;
864 if (type_id < 0x1 << 6) {
865 encoding_size = 0x0;
866 start_processing_byte = 0;
867 } else if (type_id < 0x1 << 14) {
868 encoding_size = 0x01;
869 start_processing_byte = 1;
870 } else if (type_id < 0x1 << 30) {
871 encoding_size = 0x02;
872 start_processing_byte = 3;
873 } else if (type_id < uint64_t{0x1} << 62) {
874 encoding_size = 0x03;
875 start_processing_byte = 7;
876 } else {
877 return "";
878 }
879
880 // Parse the encoded id into a string;
881 std::stringstream ss;
882 uint8_t first_byte =
883 encoding_size << 6 | GetByte(type_id, start_processing_byte);
884 ss << "{0x" << std::hex << uint32_t{first_byte};
885 for (int i = start_processing_byte - 1; i >= 0; i--) {
886 ss << ", 0x" << std::hex << uint32_t{GetByte(type_id, i)};
887 }
888 ss << "}";
889 return ss.str();
890 }
891
892 // Writes encoding functions for each type in |table| to the file descriptor
893 // |fd|.
WriteEncoders(int fd,CppSymbolTable * table)894 bool WriteEncoders(int fd, CppSymbolTable* table) {
895 for (CppType* real_type : table->TypesWithId()) {
896 const auto& name = real_type->name;
897 if (real_type->which != CppType::Which::kStruct ||
898 real_type->struct_type.key_type ==
899 CppType::Struct::KeyType::kPlainGroup) {
900 return false;
901 }
902 std::string cpp_name = ToCamelCase(name);
903
904 for (const auto& x : real_type->struct_type.members) {
905 if (x.type->which != CppType::Which::kDiscriminatedUnion)
906 continue;
907 std::string dunion_cpp_name = ToCamelCase(x.name);
908 dprintf(fd, "\n%s::%s::%s()\n", cpp_name.c_str(), dunion_cpp_name.c_str(),
909 dunion_cpp_name.c_str());
910 std::string cid = ToUnderscoreId(x.name);
911 std::string type_name = ToCamelCase(x.name);
912 dprintf(fd,
913 " : which(Which::kUninitialized), placeholder_(false) {}\n");
914
915 dprintf(fd, "\n%s::%s::~%s() {\n", cpp_name.c_str(),
916 dunion_cpp_name.c_str(), dunion_cpp_name.c_str());
917 dprintf(fd, " switch (which) {\n");
918 for (const auto* y : x.type->discriminated_union.members) {
919 switch (y->which) {
920 case CppType::Which::kUint64: {
921 dprintf(fd, " case Which::kUint64: break;\n");
922 } break;
923 case CppType::Which::kString: {
924 dprintf(fd, " case Which::kString:\n");
925 dprintf(fd, " str.std::string::~basic_string();\n");
926 dprintf(fd, " break;\n");
927 } break;
928 case CppType::Which::kBytes: {
929 dprintf(fd, " case Which::kBytes:\n");
930 dprintf(fd, " bytes.std::vector<uint8_t>::~vector();\n");
931 dprintf(fd, " break;\n");
932 } break;
933 default:
934 return false;
935 }
936 }
937 dprintf(fd, " case Which::kUninitialized: break;\n");
938 dprintf(fd, " }\n");
939 dprintf(fd, "}\n");
940 }
941
942 static const char vector_encode_function[] =
943 R"(
944 bool Encode%1$s(
945 const %1$s& data,
946 CborEncodeBuffer* buffer) {
947 if (buffer->AvailableLength() == 0 &&
948 !buffer->Append(CborEncodeBuffer::kDefaultInitialEncodeBufferSize))
949 return false;
950 const uint8_t type_id[] = %2$s;
951 if(!buffer->SetType(type_id, sizeof(type_id))) {
952 return false;
953 }
954 while (true) {
955 size_t available_length = buffer->AvailableLength();
956 ssize_t error_or_size = msgs::Encode%1$s(
957 data, buffer->Position(), available_length);
958 if (IsError(error_or_size)) {
959 return false;
960 } else if (error_or_size > static_cast<ssize_t>(available_length)) {
961 if (!buffer->ResizeBy(error_or_size - available_length))
962 return false;
963 } else {
964 buffer->ResizeBy(error_or_size - available_length);
965 return true;
966 }
967 }
968 }
969 )";
970
971 std::string encoded_id = GetEncodedTypeKey(*real_type);
972 if (encoded_id.empty()) {
973 return false;
974 }
975
976 dprintf(fd, vector_encode_function, cpp_name.c_str(), encoded_id.c_str());
977 dprintf(fd, "\nssize_t Encode%s(\n", cpp_name.c_str());
978 dprintf(fd, " const %s& data,\n", cpp_name.c_str());
979 dprintf(fd, " uint8_t* buffer,\n size_t length) {\n");
980 dprintf(fd, " CborEncoder encoder0;\n");
981 dprintf(fd, " cbor_encoder_init(&encoder0, buffer, length, 0);\n");
982
983 if (real_type->struct_type.key_type == CppType::Struct::KeyType::kMap) {
984 if (!WriteMapEncoder(fd, "data", real_type->struct_type.members, name))
985 return false;
986 } else {
987 if (!WriteArrayEncoder(fd, "data", real_type->struct_type.members,
988 name)) {
989 return false;
990 }
991 }
992
993 dprintf(fd,
994 " size_t extra_bytes_needed = "
995 "cbor_encoder_get_extra_bytes_needed(&encoder0);\n");
996 dprintf(fd, " if (extra_bytes_needed) {\n");
997 dprintf(fd,
998 " return static_cast<ssize_t>(length + extra_bytes_needed);\n");
999 dprintf(fd, " } else {\n");
1000 dprintf(fd,
1001 " return "
1002 "static_cast<ssize_t>(cbor_encoder_get_buffer_size(&encoder0, "
1003 "buffer));\n");
1004 dprintf(fd, " }\n");
1005 dprintf(fd, "}\n");
1006 }
1007 return true;
1008 }
1009
1010 bool WriteMapDecoder(int fd,
1011 const std::string& name,
1012 const std::string& member_accessor,
1013 const std::vector<CppType::Struct::CppMember>& members,
1014 int decoder_depth,
1015 int* temporary_count);
1016 bool WriteArrayDecoder(int fd,
1017 const std::string& name,
1018 const std::string& member_accessor,
1019 const std::vector<CppType::Struct::CppMember>& members,
1020 int decoder_depth,
1021 int* temporary_count);
1022
1023 // Writes the decoding function for the C++ type |cpp_type| to the file
1024 // descriptor |fd|. |name| is the C++ variable name that needs to be encoded.
1025 // |member_accessor| is either "." or "->" depending on whether |name| is a
1026 // pointer type. |decoder_depth| is used to independently name independent cbor
1027 // decoders that need to be created. |temporary_count| is used to ensure
1028 // temporaries get unique names by appending an automatically incremented
1029 // integer.
WriteDecoder(int fd,const std::string & name,const std::string & member_accessor,const CppType & cpp_type,int decoder_depth,int * temporary_count)1030 bool WriteDecoder(int fd,
1031 const std::string& name,
1032 const std::string& member_accessor,
1033 const CppType& cpp_type,
1034 int decoder_depth,
1035 int* temporary_count) {
1036 switch (cpp_type.which) {
1037 case CppType::Which::kUint64: {
1038 dprintf(fd,
1039 " CBOR_RETURN_ON_ERROR(cbor_value_get_uint64(&it%d, &%s));\n",
1040 decoder_depth, name.c_str());
1041 dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_value_advance_fixed(&it%d));\n",
1042 decoder_depth);
1043 return true;
1044 }
1045 case CppType::Which::kString: {
1046 int temp_length = (*temporary_count)++;
1047 dprintf(fd, " size_t length%d = 0;", temp_length);
1048 dprintf(fd,
1049 " CBOR_RETURN_ON_ERROR(cbor_value_validate(&it%d, "
1050 "CborValidateUtf8));\n",
1051 decoder_depth);
1052 dprintf(fd, " if (cbor_value_is_length_known(&it%d)) {\n",
1053 decoder_depth);
1054 dprintf(fd,
1055 " CBOR_RETURN_ON_ERROR(cbor_value_get_string_length(&it%d, "
1056 "&length%d));\n",
1057 decoder_depth, temp_length);
1058 dprintf(fd, " } else {\n");
1059 dprintf(
1060 fd,
1061 " CBOR_RETURN_ON_ERROR(cbor_value_calculate_string_length(&it%d, "
1062 "&length%d));\n",
1063 decoder_depth, temp_length);
1064 dprintf(fd, " }\n");
1065 dprintf(fd, " %s%sresize(length%d);\n", name.c_str(),
1066 member_accessor.c_str(), temp_length);
1067 dprintf(fd,
1068 " CBOR_RETURN_ON_ERROR(cbor_value_copy_text_string(&it%d, "
1069 "const_cast<char*>(%s%sdata()), &length%d, nullptr));\n",
1070 decoder_depth, name.c_str(), member_accessor.c_str(),
1071 temp_length);
1072 dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_value_advance(&it%d));\n",
1073 decoder_depth);
1074 return true;
1075 }
1076 case CppType::Which::kBytes: {
1077 int temp_length = (*temporary_count)++;
1078 dprintf(fd, " size_t length%d = 0;", temp_length);
1079 dprintf(fd, " if (cbor_value_is_length_known(&it%d)) {\n",
1080 decoder_depth);
1081 dprintf(fd,
1082 " CBOR_RETURN_ON_ERROR(cbor_value_get_string_length(&it%d, "
1083 "&length%d));\n",
1084 decoder_depth, temp_length);
1085 dprintf(fd, " } else {\n");
1086 dprintf(
1087 fd,
1088 " CBOR_RETURN_ON_ERROR(cbor_value_calculate_string_length(&it%d, "
1089 "&length%d));\n",
1090 decoder_depth, temp_length);
1091 dprintf(fd, " }\n");
1092 if (!cpp_type.bytes_type.fixed_size) {
1093 dprintf(fd, " %s%sresize(length%d);\n", name.c_str(),
1094 member_accessor.c_str(), temp_length);
1095 } else {
1096 dprintf(fd, " if (length%d < %d) {\n", temp_length,
1097 static_cast<int>(cpp_type.bytes_type.fixed_size.value()));
1098 dprintf(fd, " return -CborErrorTooFewItems;\n");
1099 dprintf(fd, " } else if (length%d > %d) {\n", temp_length,
1100 static_cast<int>(cpp_type.bytes_type.fixed_size.value()));
1101 dprintf(fd, " return -CborErrorTooManyItems;\n");
1102 dprintf(fd, " }\n");
1103 }
1104 dprintf(fd,
1105 " CBOR_RETURN_ON_ERROR(cbor_value_copy_byte_string(&it%d, "
1106 "const_cast<uint8_t*>(%s%sdata()), &length%d, nullptr));\n",
1107 decoder_depth, name.c_str(), member_accessor.c_str(),
1108 temp_length);
1109 dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_value_advance(&it%d));\n",
1110 decoder_depth);
1111 return true;
1112 }
1113 case CppType::Which::kVector: {
1114 dprintf(fd, " if (cbor_value_get_type(&it%d) != CborArrayType) {\n",
1115 decoder_depth);
1116 dprintf(fd, " return -1;\n");
1117 dprintf(fd, " }\n");
1118 dprintf(fd, " {\n");
1119 dprintf(fd, " CborValue it%d;\n", decoder_depth + 1);
1120 dprintf(fd, " size_t it%d_length = 0;\n", decoder_depth + 1);
1121 dprintf(fd,
1122 " CBOR_RETURN_ON_ERROR(cbor_value_get_array_length(&it%d, "
1123 "&it%d_length));\n",
1124 decoder_depth, decoder_depth + 1);
1125 if (cpp_type.vector_type.min_length !=
1126 CppType::Vector::kMinLengthUnbounded) {
1127 dprintf(fd, " if (it%d_length < %d) {\n", decoder_depth + 1,
1128 cpp_type.vector_type.min_length);
1129 dprintf(fd, " return -CborErrorTooFewItems;\n");
1130 dprintf(fd, " }\n");
1131 }
1132 if (cpp_type.vector_type.max_length !=
1133 CppType::Vector::kMaxLengthUnbounded) {
1134 dprintf(fd, " if (it%d_length > %d) {\n", decoder_depth + 1,
1135 cpp_type.vector_type.max_length);
1136 dprintf(fd, " return -CborErrorTooManyItems;\n");
1137 dprintf(fd, " }\n");
1138 }
1139 dprintf(fd, " %s%sresize(it%d_length);\n", name.c_str(),
1140 member_accessor.c_str(), decoder_depth + 1);
1141 dprintf(
1142 fd,
1143 " CBOR_RETURN_ON_ERROR(cbor_value_enter_container(&it%d, &it%d));\n",
1144 decoder_depth, decoder_depth + 1);
1145 dprintf(fd, " for (auto i = %s%sbegin(); i != %s%send(); ++i) {\n",
1146 name.c_str(), member_accessor.c_str(), name.c_str(),
1147 member_accessor.c_str());
1148 if (!WriteDecoder(fd, "(*i)", ".", *cpp_type.vector_type.element_type,
1149 decoder_depth + 1, temporary_count)) {
1150 return false;
1151 }
1152 dprintf(fd, " }\n");
1153 dprintf(
1154 fd,
1155 " CBOR_RETURN_ON_ERROR(cbor_value_leave_container(&it%d, &it%d));\n",
1156 decoder_depth, decoder_depth + 1);
1157 dprintf(fd, " }\n");
1158 return true;
1159 }
1160 case CppType::Which::kEnum: {
1161 dprintf(fd,
1162 " CBOR_RETURN_ON_ERROR(cbor_value_get_uint64(&it%d, "
1163 "reinterpret_cast<uint64_t*>(&%s)));\n",
1164 decoder_depth, name.c_str());
1165 dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_value_advance_fixed(&it%d));\n",
1166 decoder_depth);
1167 // TODO(btolsch): Validate against enum members.
1168 return true;
1169 }
1170 case CppType::Which::kStruct: {
1171 if (cpp_type.struct_type.key_type == CppType::Struct::KeyType::kMap) {
1172 return WriteMapDecoder(fd, name, member_accessor,
1173 cpp_type.struct_type.members, decoder_depth + 1,
1174 temporary_count);
1175 } else if (cpp_type.struct_type.key_type ==
1176 CppType::Struct::KeyType::kArray) {
1177 return WriteArrayDecoder(fd, name, member_accessor,
1178 cpp_type.struct_type.members,
1179 decoder_depth + 1, temporary_count);
1180 }
1181 } break;
1182 case CppType::Which::kDiscriminatedUnion: {
1183 int temp_value_type = (*temporary_count)++;
1184 dprintf(fd, " CborType type%d = cbor_value_get_type(&it%d);\n",
1185 temp_value_type, decoder_depth);
1186 bool first = true;
1187 for (const auto* x : cpp_type.discriminated_union.members) {
1188 if (first)
1189 first = false;
1190 else
1191 dprintf(fd, " else ");
1192 switch (x->which) {
1193 case CppType::Which::kUint64:
1194 dprintf(fd,
1195 " if (type%d == CborIntegerType && (it%d.flags & "
1196 "CborIteratorFlag_NegativeInteger) == 0) {\n",
1197 temp_value_type, decoder_depth);
1198 dprintf(fd, " %s.which = decltype(%s)::Which::kUint64;\n",
1199 name.c_str(), name.c_str());
1200 if (!WriteDecoder(fd, name + ".uint", ".", *x, decoder_depth,
1201 temporary_count)) {
1202 return false;
1203 }
1204 break;
1205 case CppType::Which::kString: {
1206 dprintf(fd, " if (type%d == CborTextStringType) {\n",
1207 temp_value_type);
1208 dprintf(fd, " %s.which = decltype(%s)::Which::kString;\n",
1209 name.c_str(), name.c_str());
1210 std::string str_name = name + ".str";
1211 dprintf(fd, " new (&%s) std::string();\n", str_name.c_str());
1212 if (!WriteDecoder(fd, str_name, ".", *x, decoder_depth,
1213 temporary_count)) {
1214 return false;
1215 }
1216 } break;
1217 case CppType::Which::kBytes: {
1218 dprintf(fd, " if (type%d == CborByteStringType) {\n",
1219 temp_value_type);
1220 std::string bytes_name = name + ".bytes";
1221 dprintf(fd, " %s.which = decltype(%s)::Which::kBytes;\n",
1222 name.c_str(), name.c_str());
1223 dprintf(fd, " new (&%s) std::vector<uint8_t>();\n",
1224 bytes_name.c_str());
1225 if (!WriteDecoder(fd, bytes_name, ".", *x, decoder_depth,
1226 temporary_count)) {
1227 return false;
1228 }
1229 } break;
1230 default:
1231 return false;
1232 }
1233 dprintf(fd, " }\n");
1234 }
1235 dprintf(fd, " else { return -1; }\n");
1236 return true;
1237 }
1238 case CppType::Which::kTaggedType: {
1239 int temp_tag = (*temporary_count)++;
1240 dprintf(fd, " uint64_t tag%d = 0;\n", temp_tag);
1241 dprintf(fd, " cbor_value_get_tag(&it%d, &tag%d);\n", decoder_depth,
1242 temp_tag);
1243 dprintf(fd, " if (tag%d != %" PRIu64 "ull) {\n", temp_tag,
1244 cpp_type.tagged_type.tag);
1245 dprintf(fd, " return -1;\n");
1246 dprintf(fd, " }\n");
1247 dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_value_advance_fixed(&it%d));\n",
1248 decoder_depth);
1249 if (!WriteDecoder(fd, name, member_accessor,
1250 *cpp_type.tagged_type.real_type, decoder_depth,
1251 temporary_count)) {
1252 return false;
1253 }
1254 return true;
1255 }
1256 default:
1257 break;
1258 }
1259 return false;
1260 }
1261
1262 // Writes the decoding function for the CBOR map with members in |members| to
1263 // the file descriptor |fd|. |name| is the C++ variable name that needs to be
1264 // encoded. |member_accessor| is either "." or "->" depending on whether |name|
1265 // is a pointer type. |decoder_depth| is used to independently name independent
1266 // cbor decoders that need to be created. |temporary_count| is used to ensure
1267 // temporaries get unique names by appending an automatically incremented
1268 // integer.
WriteMapDecoder(int fd,const std::string & name,const std::string & member_accessor,const std::vector<CppType::Struct::CppMember> & members,int decoder_depth,int * temporary_count)1269 bool WriteMapDecoder(int fd,
1270 const std::string& name,
1271 const std::string& member_accessor,
1272 const std::vector<CppType::Struct::CppMember>& members,
1273 int decoder_depth,
1274 int* temporary_count) {
1275 dprintf(fd, " if (cbor_value_get_type(&it%d) != CborMapType) {\n",
1276 decoder_depth - 1);
1277 dprintf(fd, " return -1;\n");
1278 dprintf(fd, " }\n");
1279 dprintf(fd, " CborValue it%d;\n", decoder_depth);
1280 dprintf(fd, " size_t it%d_length = 0;\n", decoder_depth);
1281 dprintf(fd,
1282 " CBOR_RETURN_ON_ERROR(cbor_value_get_map_length(&it%d, "
1283 "&it%d_length));\n",
1284 decoder_depth - 1, decoder_depth);
1285 int optional_members = 0;
1286 for (const auto& member : members) {
1287 if (member.type->which == CppType::Which::kOptional) {
1288 ++optional_members;
1289 }
1290 }
1291 dprintf(fd, " if (it%d_length != %d", decoder_depth,
1292 static_cast<int>(members.size()));
1293 for (int i = 0; i < optional_members; ++i) {
1294 dprintf(fd, " && it%d_length != %d", decoder_depth,
1295 static_cast<int>(members.size()) - i - 1);
1296 }
1297 dprintf(fd, ") {\n");
1298 dprintf(fd, " return -1;\n");
1299 dprintf(fd, " }\n");
1300 dprintf(fd,
1301 " CBOR_RETURN_ON_ERROR(cbor_value_enter_container(&it%d, &it%d));\n",
1302 decoder_depth - 1, decoder_depth);
1303 int member_pos = 0;
1304 for (const auto& x : members) {
1305 std::string cid = ToUnderscoreId(x.name);
1306 std::string fullname = name + member_accessor + cid;
1307 if (x.type->which == CppType::Which::kOptional) {
1308 // TODO(btolsch): This is wrong for the same reason as arrays, but will be
1309 // easier to handle when doing out-of-order keys.
1310 dprintf(fd, " if (it%d_length > %d) {\n", decoder_depth, member_pos);
1311
1312 if (x.integer_key.has_value()) {
1313 dprintf(fd,
1314 " CBOR_RETURN_ON_ERROR(EXPECT_INT_KEY_CONSTANT(&it%d, %" PRIu64
1315 "));\n",
1316 decoder_depth, x.integer_key.value());
1317 } else {
1318 dprintf(fd,
1319 " CBOR_RETURN_ON_ERROR(EXPECT_KEY_CONSTANT(&it%d, \"%s\"));\n",
1320 decoder_depth, x.name.c_str());
1321 }
1322 dprintf(fd, " %s%shas_%s = true;\n", name.c_str(),
1323 member_accessor.c_str(), cid.c_str());
1324 if (!WriteDecoder(fd, fullname, ".", *x.type->optional_type,
1325 decoder_depth, temporary_count)) {
1326 return false;
1327 }
1328 dprintf(fd, " } else {\n");
1329 dprintf(fd, " %s%shas_%s = false;\n", name.c_str(),
1330 member_accessor.c_str(), cid.c_str());
1331 dprintf(fd, " }\n");
1332 } else {
1333 if (x.integer_key.has_value()) {
1334 dprintf(fd,
1335 " CBOR_RETURN_ON_ERROR(EXPECT_INT_KEY_CONSTANT(&it%d, %" PRIu64
1336 "));\n",
1337 decoder_depth, x.integer_key.value());
1338 } else {
1339 dprintf(fd,
1340 " CBOR_RETURN_ON_ERROR(EXPECT_KEY_CONSTANT(&it%d, \"%s\"));\n",
1341 decoder_depth, x.name.c_str());
1342 }
1343 if (!WriteDecoder(fd, fullname, ".", *x.type, decoder_depth,
1344 temporary_count)) {
1345 return false;
1346 }
1347 }
1348 ++member_pos;
1349 }
1350 dprintf(fd,
1351 " CBOR_RETURN_ON_ERROR(cbor_value_leave_container(&it%d, &it%d));\n",
1352 decoder_depth - 1, decoder_depth);
1353 return true;
1354 }
1355
1356 // Writes the decoding function for the CBOR array with members in |members| to
1357 // the file descriptor |fd|. |name| is the C++ variable name that needs to be
1358 // encoded. |member_accessor| is either "." or "->" depending on whether |name|
1359 // is a pointer type. |decoder_depth| is used to independently name independent
1360 // cbor decoders that need to be created. |temporary_count| is used to ensure
1361 // temporaries get unique names by appending an automatically incremented
1362 // integer.
WriteArrayDecoder(int fd,const std::string & name,const std::string & member_accessor,const std::vector<CppType::Struct::CppMember> & members,int decoder_depth,int * temporary_count)1363 bool WriteArrayDecoder(int fd,
1364 const std::string& name,
1365 const std::string& member_accessor,
1366 const std::vector<CppType::Struct::CppMember>& members,
1367 int decoder_depth,
1368 int* temporary_count) {
1369 dprintf(fd, " if (cbor_value_get_type(&it%d) != CborArrayType) {\n",
1370 decoder_depth - 1);
1371 dprintf(fd, " return -1;\n");
1372 dprintf(fd, " }\n");
1373 dprintf(fd, " CborValue it%d;\n", decoder_depth);
1374 dprintf(fd, " size_t it%d_length = 0;\n", decoder_depth);
1375 dprintf(fd,
1376 " CBOR_RETURN_ON_ERROR(cbor_value_get_array_length(&it%d, "
1377 "&it%d_length));\n",
1378 decoder_depth - 1, decoder_depth);
1379 int optional_members = 0;
1380 for (const auto& member : members) {
1381 if (member.type->which == CppType::Which::kOptional) {
1382 ++optional_members;
1383 }
1384 }
1385 dprintf(fd, " if (it%d_length != %d", decoder_depth,
1386 static_cast<int>(members.size()));
1387 for (int i = 0; i < optional_members; ++i) {
1388 dprintf(fd, " && it%d_length != %d", decoder_depth,
1389 static_cast<int>(members.size()) - i - 1);
1390 }
1391 dprintf(fd, ") {\n");
1392 dprintf(fd, " return -1;\n");
1393 dprintf(fd, " }\n");
1394 dprintf(fd,
1395 " CBOR_RETURN_ON_ERROR(cbor_value_enter_container(&it%d, &it%d));\n",
1396 decoder_depth - 1, decoder_depth);
1397 int member_pos = 0;
1398 for (const auto& x : members) {
1399 std::string cid = ToUnderscoreId(x.name);
1400 std::string fullname = name + member_accessor + cid;
1401 if (x.type->which == CppType::Which::kOptional) {
1402 // TODO(btolsch): This only handles a single block of optionals and only
1403 // the ones present form a contiguous range from the start of the block.
1404 // However, we likely don't really need more than one optional for arrays
1405 // for the foreseeable future. The proper approach would be to have a set
1406 // of possible types for the next element and a map for the member to
1407 // which each corresponds.
1408 dprintf(fd, " if (it%d_length > %d) {\n", decoder_depth, member_pos);
1409 dprintf(fd, " %s%shas_%s = true;\n", name.c_str(),
1410 member_accessor.c_str(), cid.c_str());
1411 if (!WriteDecoder(fd, fullname, ".", *x.type->optional_type,
1412 decoder_depth, temporary_count)) {
1413 return false;
1414 }
1415 dprintf(fd, " } else {\n");
1416 dprintf(fd, " %s%shas_%s = false;\n", name.c_str(),
1417 member_accessor.c_str(), cid.c_str());
1418 dprintf(fd, " }\n");
1419 } else {
1420 if (!WriteDecoder(fd, fullname, ".", *x.type, decoder_depth,
1421 temporary_count)) {
1422 return false;
1423 }
1424 }
1425 ++member_pos;
1426 }
1427 dprintf(fd,
1428 " CBOR_RETURN_ON_ERROR(cbor_value_leave_container(&it%d, &it%d));\n",
1429 decoder_depth - 1, decoder_depth);
1430 return true;
1431 }
1432
1433 // Writes the equality operators for all structs.
WriteEqualityOperators(int fd,CppSymbolTable * table)1434 bool WriteEqualityOperators(int fd, CppSymbolTable* table) {
1435 for (const auto& pair : table->cpp_type_map) {
1436 CppType* real_type = pair.second;
1437 if (real_type->which == CppType::Which::kStruct &&
1438 real_type->struct_type.key_type !=
1439 CppType::Struct::KeyType::kPlainGroup) {
1440 if (!WriteStructEqualityOperator(fd, *real_type)) {
1441 return false;
1442 }
1443 }
1444 }
1445 return true;
1446 }
1447
1448 // Writes a decoder function definition for every type in |table| to the file
1449 // descriptor |fd|.
WriteDecoders(int fd,CppSymbolTable * table)1450 bool WriteDecoders(int fd, CppSymbolTable* table) {
1451 if (!WriteTypeParserDefinition(fd, table)) {
1452 return false;
1453 }
1454 for (CppType* real_type : table->TypesWithId()) {
1455 const auto& name = real_type->name;
1456 int temporary_count = 0;
1457 if (real_type->which != CppType::Which::kStruct ||
1458 real_type->struct_type.key_type ==
1459 CppType::Struct::KeyType::kPlainGroup) {
1460 continue;
1461 }
1462 std::string cpp_name = ToCamelCase(name);
1463 dprintf(fd, "\nssize_t Decode%s(\n", cpp_name.c_str());
1464 dprintf(fd, " const uint8_t* buffer,\n size_t length,\n");
1465 dprintf(fd, " %s* data) {\n", cpp_name.c_str());
1466 dprintf(fd, " CborParser parser;\n");
1467 dprintf(fd, " CborValue it0;\n");
1468 dprintf(
1469 fd,
1470 " CBOR_RETURN_ON_ERROR(cbor_parser_init(buffer, length, 0, &parser, "
1471 "&it0));\n");
1472 if (real_type->struct_type.key_type == CppType::Struct::KeyType::kMap) {
1473 if (!WriteMapDecoder(fd, "data", "->", real_type->struct_type.members, 1,
1474 &temporary_count)) {
1475 return false;
1476 }
1477 } else {
1478 if (!WriteArrayDecoder(fd, "data", "->", real_type->struct_type.members,
1479 1, &temporary_count)) {
1480 return false;
1481 }
1482 }
1483 dprintf(
1484 fd,
1485 " auto result = static_cast<ssize_t>(cbor_value_get_next_byte(&it0) - "
1486 "buffer);\n");
1487 dprintf(fd, " return result;\n");
1488 dprintf(fd, "}\n");
1489 }
1490 return true;
1491 }
1492
1493 // Converts the filename |header_filename| to a preprocessor token that can be
1494 // used as a header guard macro name.
ToHeaderGuard(const std::string & header_filename)1495 std::string ToHeaderGuard(const std::string& header_filename) {
1496 std::string result = header_filename;
1497 for (auto& c : result) {
1498 if (c == '/' || c == '.')
1499 c = '_';
1500 else
1501 c = toupper(c);
1502 }
1503 result += "_";
1504 return result;
1505 }
1506
WriteHeaderPrologue(int fd,const std::string & header_filename)1507 bool WriteHeaderPrologue(int fd, const std::string& header_filename) {
1508 static const char prologue[] =
1509 R"(#ifndef %s
1510 #define %s
1511
1512 #include <array>
1513 #include <cstdint>
1514 #include <iostream>
1515 #include <string>
1516 #include <vector>
1517
1518 #include "third_party/tinycbor/src/src/cbor.h"
1519
1520 namespace openscreen {
1521 namespace msgs {
1522
1523 enum CborErrors {
1524 kParserEOF = -CborErrorUnexpectedEOF,
1525 };
1526
1527 class CborEncodeBuffer;
1528 )";
1529 std::string header_guard = ToHeaderGuard(header_filename);
1530 dprintf(fd, prologue, header_guard.c_str(), header_guard.c_str());
1531 return true;
1532 }
1533
WriteHeaderEpilogue(int fd,const std::string & header_filename)1534 bool WriteHeaderEpilogue(int fd, const std::string& header_filename) {
1535 static const char epilogue[] = R"(
1536 class TypeEnumValidator {
1537 public:
1538 static Type SafeCast(uint64_t type_id);
1539 };
1540
1541 class CborEncodeBuffer {
1542 public:
1543 static constexpr size_t kDefaultInitialEncodeBufferSize = 250;
1544 static constexpr size_t kDefaultMaxEncodeBufferSize = 64000;
1545
1546 CborEncodeBuffer();
1547 CborEncodeBuffer(size_t initial_size, size_t max_size);
1548 ~CborEncodeBuffer();
1549
1550 bool Append(size_t length);
1551 bool ResizeBy(ssize_t length);
1552 bool SetType(const uint8_t encoded_id[], size_t size);
1553
1554 const uint8_t* data() const { return data_.data(); }
1555 size_t size() const { return data_.size(); }
1556
1557 uint8_t* Position() { return &data_[0] + position_; }
1558 size_t AvailableLength() { return data_.size() - position_; }
1559
1560 private:
1561 size_t max_size_;
1562 size_t position_;
1563 std::vector<uint8_t> data_;
1564 };
1565
1566 CborError ExpectKey(CborValue* it, const uint64_t key);
1567 CborError ExpectKey(CborValue* it, const char* key, size_t key_length);
1568
1569 } // namespace msgs
1570 } // namespace openscreen
1571 #endif // %s)";
1572 std::string header_guard = ToHeaderGuard(header_filename);
1573 dprintf(fd, epilogue, header_guard.c_str());
1574 return true;
1575 }
1576
WriteSourcePrologue(int fd,const std::string & header_filename)1577 bool WriteSourcePrologue(int fd, const std::string& header_filename) {
1578 static const char prologue[] =
1579 R"(#include "%s"
1580
1581 #include "third_party/tinycbor/src/src/utf8_p.h"
1582 #include "util/osp_logging.h"
1583
1584 namespace openscreen {
1585 namespace msgs {
1586 namespace {
1587
1588 /*
1589 * Encoder-specific errors, so it's fine to check these even in the
1590 * parser.
1591 */
1592 #define CBOR_RETURN_WHAT_ON_ERROR(stmt, what) \
1593 { \
1594 CborError error = stmt; \
1595 OSP_DCHECK_NE(error, CborErrorTooFewItems); \
1596 OSP_DCHECK_NE(error, CborErrorTooManyItems); \
1597 OSP_DCHECK_NE(error, CborErrorDataTooLarge); \
1598 if (error != CborNoError && error != CborErrorOutOfMemory) \
1599 return what; \
1600 }
1601 #define CBOR_RETURN_ON_ERROR_INTERNAL(stmt) \
1602 CBOR_RETURN_WHAT_ON_ERROR(stmt, error)
1603 #define CBOR_RETURN_ON_ERROR(stmt) CBOR_RETURN_WHAT_ON_ERROR(stmt, -error)
1604
1605 #define EXPECT_KEY_CONSTANT(it, key) ExpectKey(it, key, sizeof(key) - 1)
1606 #define EXPECT_INT_KEY_CONSTANT(it, key) ExpectKey(it, key)
1607
1608 bool IsValidUtf8(const std::string& s) {
1609 const uint8_t* buffer = reinterpret_cast<const uint8_t*>(s.data());
1610 const uint8_t* end = buffer + s.size();
1611 while (buffer < end) {
1612 // TODO(btolsch): This is an implementation detail of tinycbor so we should
1613 // eventually replace this call with our own utf8 validation.
1614 if (get_utf8(&buffer, end) == ~0u)
1615 return false;
1616 }
1617 return true;
1618 }
1619 } // namespace
1620
1621 CborError ExpectKey(CborValue* it, const uint64_t key) {
1622 if (!cbor_value_is_unsigned_integer(it))
1623 return CborErrorImproperValue;
1624 uint64_t observed_key;
1625 CBOR_RETURN_ON_ERROR_INTERNAL(cbor_value_get_uint64(it, &observed_key));
1626 if (observed_key != key)
1627 return CborErrorImproperValue;
1628 CBOR_RETURN_ON_ERROR_INTERNAL(cbor_value_advance_fixed(it));
1629 return CborNoError;
1630 }
1631
1632 CborError ExpectKey(CborValue* it, const char* key, size_t key_length) {
1633 if(!cbor_value_is_text_string(it))
1634 return CborErrorImproperValue;
1635 size_t observed_length = 0;
1636 CBOR_RETURN_ON_ERROR_INTERNAL(
1637 cbor_value_get_string_length(it, &observed_length));
1638 if (observed_length != key_length)
1639 return CborErrorImproperValue;
1640 std::string observed_key(key_length, 0);
1641 CBOR_RETURN_ON_ERROR_INTERNAL(cbor_value_copy_text_string(
1642 it, const_cast<char*>(observed_key.data()), &observed_length, nullptr));
1643 if (observed_key != key)
1644 return CborErrorImproperValue;
1645 CBOR_RETURN_ON_ERROR_INTERNAL(cbor_value_advance(it));
1646 return CborNoError;
1647 }
1648
1649 // static
1650 constexpr size_t CborEncodeBuffer::kDefaultInitialEncodeBufferSize;
1651
1652 // static
1653 constexpr size_t CborEncodeBuffer::kDefaultMaxEncodeBufferSize;
1654
1655 CborEncodeBuffer::CborEncodeBuffer()
1656 : max_size_(kDefaultMaxEncodeBufferSize),
1657 position_(0),
1658 data_(kDefaultInitialEncodeBufferSize) {}
1659 CborEncodeBuffer::CborEncodeBuffer(size_t initial_size, size_t max_size)
1660 : max_size_(max_size), position_(0), data_(initial_size) {}
1661 CborEncodeBuffer::~CborEncodeBuffer() = default;
1662
1663 bool CborEncodeBuffer::SetType(const uint8_t encoded_id[], size_t size) {
1664 if (this->AvailableLength() < size) {
1665 if (!this->ResizeBy(size)) {
1666 return false;
1667 }
1668 }
1669 memcpy(&data_[position_], encoded_id, size);
1670 position_ += size;
1671 return true;
1672 }
1673
1674 bool CborEncodeBuffer::Append(size_t length) {
1675 if (length == 0)
1676 return false;
1677 if ((data_.size() + length) > max_size_) {
1678 length = max_size_ - data_.size();
1679 if (length == 0)
1680 return false;
1681 }
1682 size_t append_area = data_.size();
1683 data_.resize(append_area + length);
1684 position_ = append_area;
1685 return true;
1686 }
1687
1688 bool CborEncodeBuffer::ResizeBy(ssize_t delta) {
1689 if (delta == 0)
1690 return true;
1691 if (delta < 0 && static_cast<size_t>(-delta) > data_.size())
1692 return false;
1693 if (delta > 0 && (data_.size() + delta) > max_size_)
1694 return false;
1695 data_.resize(data_.size() + delta);
1696 return true;
1697 }
1698
1699 bool IsError(ssize_t x) {
1700 return x < 0;
1701 }
1702 )";
1703 dprintf(fd, prologue, header_filename.c_str());
1704 return true;
1705 }
1706
WriteSourceEpilogue(int fd)1707 bool WriteSourceEpilogue(int fd) {
1708 static const char epilogue[] = R"(
1709 } // namespace msgs
1710 } // namespace openscreen)";
1711 dprintf(fd, epilogue);
1712 return true;
1713 }
1714