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