// Copyright 2018 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "tools/cddl/codegen.h" #include #include #include #include #include #include #include #include #include "absl/algorithm/container.h" #include "absl/types/optional.h" // Convert '-' to '_' to use a CDDL identifier as a C identifier. std::string ToUnderscoreId(const std::string& x) { std::string result(x); for (auto& c : result) { if (c == '-') c = '_'; } return result; } // Convert a CDDL identifier to camel case for use as a C typename. E.g. // presentation-connection-message to PresentationConnectionMessage. std::string ToCamelCase(const std::string& x) { std::string result(x); result[0] = toupper(result[0]); size_t new_size = 1; size_t result_size = result.size(); for (size_t i = 1; i < result_size; ++i, ++new_size) { if (result[i] == '-') { ++i; if (i < result_size) result[new_size] = toupper(result[i]); } else { result[new_size] = result[i]; } } result.resize(new_size); return result; } // Returns a string which represents the C++ type of |cpp_type|. Returns an // empty string if there is no valid representation for |cpp_type| (e.g. a // vector with an invalid element type). std::string CppTypeToString(const CppType& cpp_type) { switch (cpp_type.which) { case CppType::Which::kUint64: return "uint64_t"; case CppType::Which::kString: return "std::string"; case CppType::Which::kBytes: { if (cpp_type.bytes_type.fixed_size) { std::string size_string = std::to_string(cpp_type.bytes_type.fixed_size.value()); return "std::array"; } else { return "std::vector"; } } case CppType::Which::kVector: { std::string element_string = CppTypeToString(*cpp_type.vector_type.element_type); if (element_string.empty()) return std::string(); return "std::vector<" + element_string + ">"; } case CppType::Which::kEnum: return ToCamelCase(cpp_type.name); case CppType::Which::kStruct: return ToCamelCase(cpp_type.name); case CppType::Which::kTaggedType: return CppTypeToString(*cpp_type.tagged_type.real_type); default: return std::string(); } } bool WriteEnumEqualityOperatorSwitchCases(int fd, const CppType& parent, std::string child_name, std::string parent_name) { for (const auto& x : parent.enum_type.members) { std::string enum_value = "k" + ToCamelCase(x.first); dprintf(fd, " case %s::%s: return parent == %s::%s;\n", child_name.c_str(), enum_value.c_str(), parent_name.c_str(), enum_value.c_str()); } return absl::c_all_of(parent.enum_type.sub_members, [&fd, &child_name, &parent_name](CppType* new_parent) { return WriteEnumEqualityOperatorSwitchCases( fd, *new_parent, child_name, parent_name); }); } // Write the equality operators for comparing an enum and its parent types. bool WriteEnumEqualityOperator(int fd, const CppType& type, const CppType& parent) { std::string name = ToCamelCase(type.name); std::string parent_name = ToCamelCase(parent.name); // Define type == parentType. dprintf(fd, "inline bool operator==(const %s& child, const %s& parent) {\n", name.c_str(), parent_name.c_str()); dprintf(fd, " switch (child) {\n"); if (!WriteEnumEqualityOperatorSwitchCases(fd, parent, name, parent_name)) { return false; } dprintf(fd, " default: return false;\n"); dprintf(fd, " }\n}\n"); // Define parentType == type. dprintf(fd, "inline bool operator==(const %s& parent, const %s& child) {\n", parent_name.c_str(), name.c_str()); dprintf(fd, " return child == parent;\n}\n"); // Define type != parentType. dprintf(fd, "inline bool operator!=(const %s& child, const %s& parent) {\n", name.c_str(), parent_name.c_str()); dprintf(fd, " return !(child == parent);\n}\n"); // Define parentType != type. dprintf(fd, "inline bool operator!=(const %s& parent, const %s& child) {\n", parent_name.c_str(), name.c_str()); dprintf(fd, " return !(parent == child);\n}\n"); return true; } bool WriteEnumStreamOperatorSwitchCases(int fd, const CppType& type, std::string name) { for (const auto& x : type.enum_type.members) { std::string enum_value = "k" + ToCamelCase(x.first); dprintf(fd, " case %s::%s: os << \"%s\"; break;\n", name.c_str(), enum_value.c_str(), enum_value.c_str()); } return absl::c_all_of( type.enum_type.sub_members, [&fd, &name](CppType* parent) { return WriteEnumStreamOperatorSwitchCases(fd, *parent, name); }); } bool WriteEnumOperators(int fd, const CppType& type) { // Write << operator. std::string name = ToCamelCase(type.name); dprintf( fd, "inline std::ostream& operator<<(std::ostream& os, const %s& val) {\n", name.c_str()); dprintf(fd, " switch (val) {\n"); if (!WriteEnumStreamOperatorSwitchCases(fd, type, name)) { return false; } dprintf(fd, " default: os << \"Unknown Value: \" << static_cast(val);" "\n break;\n }\n return os;\n}\n"); // Write equality operators. return absl::c_all_of(type.enum_type.sub_members, [&fd, &type](CppType* parent) { return WriteEnumEqualityOperator(fd, type, *parent); }); } // Writes the equality operator for a specific Discriminated Union. bool WriteDiscriminatedUnionEqualityOperator( int fd, const CppType& type, const std::string& name_prefix = "") { const std::string name = name_prefix + ToCamelCase(type.name); dprintf(fd, "\nbool %s::operator==(const %s& other) const {\n", name.c_str(), name.c_str()); dprintf(fd, " return this->which == other.which"); for (auto* union_member : type.discriminated_union.members) { dprintf(fd, " &&\n "); switch (union_member->which) { case CppType::Which::kUint64: dprintf(fd, "(this->which != Which::kUint64 || this->uint == other.uint)"); break; case CppType::Which::kString: dprintf(fd, "(this->which != Which::kString || this->str == other.str)"); break; case CppType::Which::kBytes: dprintf(fd, "(this->which != Which::kBytes || this->bytes == other.bytes)"); break; default: return false; } } dprintf(fd, ";\n}\n"); dprintf(fd, "bool %s::operator!=(const %s& other) const {\n", name.c_str(), name.c_str()); dprintf(fd, " return !(*this == other);\n}\n"); return true; } // Writes the equality operator for a specific C++ struct. bool WriteStructEqualityOperator(int fd, const CppType& type, const std::string& name_prefix = "") { const std::string name = name_prefix + ToCamelCase(type.name); dprintf(fd, "\nbool %s::operator==(const %s& other) const {\n", name.c_str(), name.c_str()); for (size_t i = 0; i < type.struct_type.members.size(); i++) { if (i == 0) { dprintf(fd, " return "); } else { dprintf(fd, " &&\n "); } auto name = ToUnderscoreId(type.struct_type.members[i].name); dprintf(fd, "this->%s == other.%s", name.c_str(), name.c_str()); } dprintf(fd, ";\n}"); dprintf(fd, "\nbool %s::operator!=(const %s& other) const {\n", name.c_str(), name.c_str()); dprintf(fd, " return !(*this == other);\n}\n"); std::string new_prefix = name_prefix + ToCamelCase(type.name) + "::"; for (const auto& x : type.struct_type.members) { // NOTE: Don't need to call recursively on struct members, since all structs // are handled in the calling method. if (x.type->which == CppType::Which::kDiscriminatedUnion) { if (!WriteDiscriminatedUnionEqualityOperator(fd, *x.type, new_prefix)) { return false; } } } return true; } // Write the C++ struct member definitions of every type in |members| to the // file descriptor |fd|. bool WriteStructMembers( int fd, const std::string& name, const std::vector& members) { for (const auto& x : members) { std::string type_string; switch (x.type->which) { case CppType::Which::kStruct: { if (x.type->struct_type.key_type == CppType::Struct::KeyType::kPlainGroup) { if (!WriteStructMembers(fd, x.type->name, x.type->struct_type.members)) return false; continue; } else { type_string = ToCamelCase(x.name); } } break; case CppType::Which::kOptional: { // TODO(btolsch): Make this optional when one lands. dprintf(fd, " bool has_%s;\n", ToUnderscoreId(x.name).c_str()); type_string = CppTypeToString(*x.type->optional_type); } break; case CppType::Which::kDiscriminatedUnion: { std::string cid = ToUnderscoreId(x.name); type_string = ToCamelCase(x.name); dprintf(fd, " struct %s {\n", type_string.c_str()); dprintf(fd, " %s();\n ~%s();\n\n", type_string.c_str(), type_string.c_str()); dprintf(fd, " bool operator==(const %s& other) const;\n", type_string.c_str()); dprintf(fd, " bool operator!=(const %s& other) const;\n\n", type_string.c_str()); dprintf(fd, " enum class Which {\n"); for (auto* union_member : x.type->discriminated_union.members) { switch (union_member->which) { case CppType::Which::kUint64: dprintf(fd, " kUint64,\n"); break; case CppType::Which::kString: dprintf(fd, " kString,\n"); break; case CppType::Which::kBytes: dprintf(fd, " kBytes,\n"); break; default: return false; } } dprintf(fd, " kUninitialized,\n"); dprintf(fd, " } which;\n"); dprintf(fd, " union {\n"); for (auto* union_member : x.type->discriminated_union.members) { switch (union_member->which) { case CppType::Which::kUint64: dprintf(fd, " uint64_t uint;\n"); break; case CppType::Which::kString: dprintf(fd, " std::string str;\n"); break; case CppType::Which::kBytes: dprintf(fd, " std::vector bytes;\n"); break; default: return false; } } // NOTE: This member allows the union to be easily constructed in an // effectively uninitialized state. Its value should never be used. dprintf(fd, " bool placeholder_;\n"); dprintf(fd, " };\n"); dprintf(fd, " };\n"); } break; default: type_string = CppTypeToString(*x.type); break; } if (type_string.empty()) return false; dprintf(fd, " %s %s;\n", type_string.c_str(), ToUnderscoreId(x.name).c_str()); } return true; } void WriteEnumMembers(int fd, const CppType& type) { for (const auto& x : type.enum_type.members) { dprintf(fd, " k%s = %" PRIu64 "ull,\n", ToCamelCase(x.first).c_str(), x.second); } for (const auto* x : type.enum_type.sub_members) { WriteEnumMembers(fd, *x); } } // Writes a C++ type definition for |type| to the file descriptor |fd|. This // only generates a definition for enums and structs. bool WriteTypeDefinition(int fd, const CppType& type) { std::string name = ToCamelCase(type.name); switch (type.which) { case CppType::Which::kEnum: { dprintf(fd, "\nenum class %s : uint64_t {\n", name.c_str()); WriteEnumMembers(fd, type); dprintf(fd, "};\n"); if (!WriteEnumOperators(fd, type)) return false; } break; case CppType::Which::kStruct: { dprintf(fd, "\nstruct %s {\n", name.c_str()); if (type.type_key != absl::nullopt) { dprintf(fd, " // type key: %" PRIu64 "\n", type.type_key.value()); } dprintf(fd, " bool operator==(const %s& other) const;\n", name.c_str()); dprintf(fd, " bool operator!=(const %s& other) const;\n\n", name.c_str()); if (!WriteStructMembers(fd, type.name, type.struct_type.members)) return false; dprintf(fd, "};\n"); } break; default: break; } return true; } // Ensures that any dependencies within |cpp_type| are written to the file // descriptor |fd| before writing |cpp_type| to the file descriptor |fd|. This // is done by walking the tree of types defined by |cpp_type| (e.g. all the // members for a struct). |defs| contains the names of types that have already // been written. If a type hasn't been written and needs to be, its name will // also be added to |defs|. bool EnsureDependentTypeDefinitionsWritten(int fd, const CppType& cpp_type, std::set* defs) { switch (cpp_type.which) { case CppType::Which::kVector: { return EnsureDependentTypeDefinitionsWritten( fd, *cpp_type.vector_type.element_type, defs); } break; case CppType::Which::kEnum: { if (defs->find(cpp_type.name) != defs->end()) return true; for (const auto* x : cpp_type.enum_type.sub_members) if (!EnsureDependentTypeDefinitionsWritten(fd, *x, defs)) return false; defs->emplace(cpp_type.name); WriteTypeDefinition(fd, cpp_type); } break; case CppType::Which::kStruct: { if (cpp_type.struct_type.key_type != CppType::Struct::KeyType::kPlainGroup) { if (defs->find(cpp_type.name) != defs->end()) return true; for (const auto& x : cpp_type.struct_type.members) if (!EnsureDependentTypeDefinitionsWritten(fd, *x.type, defs)) return false; defs->emplace(cpp_type.name); WriteTypeDefinition(fd, cpp_type); } } break; case CppType::Which::kOptional: { return EnsureDependentTypeDefinitionsWritten(fd, *cpp_type.optional_type, defs); } break; case CppType::Which::kDiscriminatedUnion: { for (const auto* x : cpp_type.discriminated_union.members) if (!EnsureDependentTypeDefinitionsWritten(fd, *x, defs)) return false; } break; case CppType::Which::kTaggedType: { if (!EnsureDependentTypeDefinitionsWritten( fd, *cpp_type.tagged_type.real_type, defs)) { return false; } } break; default: break; } return true; } // Writes the type definition for every C++ type in |table|. This function // makes sure to write them in such an order that all type dependencies are // written before they are need so the resulting text in the file descriptor // |fd| will compile without modification. For example, the following would be // bad output: // // struct Foo { // Bar bar; // int x; // }; // // struct Bar { // int alpha; // }; // // This function ensures that Bar would be written sometime before Foo. bool WriteTypeDefinitions(int fd, CppSymbolTable* table) { std::set defs; for (const std::unique_ptr& real_type : table->cpp_types) { if (real_type->which != CppType::Which::kStruct || real_type->struct_type.key_type == CppType::Struct::KeyType::kPlainGroup) { continue; } if (!EnsureDependentTypeDefinitionsWritten(fd, *real_type, &defs)) return false; } dprintf(fd, "\nenum class Type : uint64_t {\n"); dprintf(fd, " kUnknown = 0ull,\n"); for (CppType* type : table->TypesWithId()) { dprintf(fd, " k%s = %" PRIu64 "ull,\n", ToCamelCase(type->name).c_str(), type->type_key.value()); } dprintf(fd, "};\n"); return true; } // Writes a parser that takes in a uint64_t and outputs the corresponding Type // if one matches up, or Type::kUnknown if none does. // NOTE: In future, this could be changes to use a Trie, which would allow for // manufacturers to more easily add their own type ids to ours. bool WriteTypeParserDefinition(int fd, CppSymbolTable* table) { dprintf(fd, "\n//static\n"); dprintf(fd, "Type TypeEnumValidator::SafeCast(uint64_t type_id) {\n"); dprintf(fd, " switch (type_id) {\n"); for (CppType* type : table->TypesWithId()) { dprintf(fd, " case uint64_t{%" PRIu64 "}: return Type::k%s;\n", type->type_key.value(), ToCamelCase(type->name).c_str()); } dprintf(fd, " default: return Type::kUnknown;\n"); dprintf(fd, " }\n}\n"); return true; } // Writes the function prototypes for the encode and decode functions for each // type in |table| to the file descriptor |fd|. bool WriteFunctionDeclarations(int fd, CppSymbolTable* table) { for (CppType* real_type : table->TypesWithId()) { const auto& name = real_type->name; if (real_type->which != CppType::Which::kStruct || real_type->struct_type.key_type == CppType::Struct::KeyType::kPlainGroup) { return false; } std::string cpp_name = ToCamelCase(name); dprintf(fd, "\nbool Encode%s(\n", cpp_name.c_str()); dprintf(fd, " const %s& data,\n", cpp_name.c_str()); dprintf(fd, " CborEncodeBuffer* buffer);\n"); dprintf(fd, "ssize_t Encode%s(\n", cpp_name.c_str()); dprintf(fd, " const %s& data,\n", cpp_name.c_str()); dprintf(fd, " uint8_t* buffer,\n size_t length);\n"); dprintf(fd, "ssize_t Decode%s(\n", cpp_name.c_str()); dprintf(fd, " const uint8_t* buffer,\n size_t length,\n"); dprintf(fd, " %s* data);\n", cpp_name.c_str()); } return true; } bool WriteMapEncoder(int fd, const std::string& name, const std::vector& members, const std::string& nested_type_scope, int encoder_depth = 1); bool WriteArrayEncoder(int fd, const std::string& name, const std::vector& members, const std::string& nested_type_scope, int encoder_depth = 1); // Writes the encoding function for the C++ type |cpp_type| to the file // descriptor |fd|. |name| is the C++ variable name that needs to be encoded. // |nested_type_scope| is the closest C++ scope name (i.e. struct name), which // may be used to access local enum constants. |encoder_depth| is used to // independently name independent cbor encoders that need to be created. bool WriteEncoder(int fd, const std::string& name, const CppType& cpp_type, const std::string& nested_type_scope, int encoder_depth) { switch (cpp_type.which) { case CppType::Which::kStruct: if (cpp_type.struct_type.key_type == CppType::Struct::KeyType::kMap) { if (!WriteMapEncoder(fd, name, cpp_type.struct_type.members, cpp_type.name, encoder_depth + 1)) { return false; } return true; } else if (cpp_type.struct_type.key_type == CppType::Struct::KeyType::kArray) { if (!WriteArrayEncoder(fd, name, cpp_type.struct_type.members, cpp_type.name, encoder_depth + 1)) { return false; } return true; } else { for (const auto& x : cpp_type.struct_type.members) { if (x.integer_key.has_value()) { dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_encode_uint(" "&encoder%d, %" PRIu64 ");\n", encoder_depth, x.integer_key.value()); } else { dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_encode_text_string(" "&encoder%d, \"%s\", sizeof(\"%s\") - 1));\n", encoder_depth, x.name.c_str(), x.name.c_str()); } if (!WriteEncoder(fd, name + "." + ToUnderscoreId(x.name), *x.type, nested_type_scope, encoder_depth)) { return false; } } return true; } break; case CppType::Which::kUint64: dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_encode_uint(&encoder%d, %s));\n", encoder_depth, ToUnderscoreId(name).c_str()); return true; break; case CppType::Which::kString: { std::string cid = ToUnderscoreId(name); dprintf(fd, " if (!IsValidUtf8(%s)) {\n", cid.c_str()); dprintf(fd, " return -CborErrorInvalidUtf8TextString;\n"); dprintf(fd, " }\n"); dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_encode_text_string(&encoder%d, " "%s.c_str(), %s.size()));\n", encoder_depth, cid.c_str(), cid.c_str()); return true; } break; case CppType::Which::kBytes: { std::string cid = ToUnderscoreId(name); dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_encode_byte_string(&encoder%d, " "%s.data(), " "%s.size()));\n", encoder_depth, cid.c_str(), cid.c_str()); return true; } break; case CppType::Which::kVector: { std::string cid = ToUnderscoreId(name); dprintf(fd, " {\n"); if (cpp_type.vector_type.min_length != CppType::Vector::kMinLengthUnbounded) { dprintf(fd, " if (%s.size() < %d) {\n", cid.c_str(), cpp_type.vector_type.min_length); dprintf(fd, " return -CborErrorTooFewItems;\n"); dprintf(fd, " }\n"); } if (cpp_type.vector_type.max_length != CppType::Vector::kMaxLengthUnbounded) { dprintf(fd, " if (%s.size() > %d) {\n", cid.c_str(), cpp_type.vector_type.max_length); dprintf(fd, " return -CborErrorTooManyItems;\n"); dprintf(fd, " }\n"); } dprintf(fd, " CborEncoder encoder%d;\n", encoder_depth + 1); dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_encoder_create_array(&encoder%d, " "&encoder%d, %s.size()));\n", encoder_depth, encoder_depth + 1, cid.c_str()); dprintf(fd, " for (const auto& x : %s) {\n", cid.c_str()); if (!WriteEncoder(fd, "x", *cpp_type.vector_type.element_type, nested_type_scope, encoder_depth + 1)) { return false; } dprintf(fd, " }\n"); dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_encoder_close_container(&encoder%d, " "&encoder%d));\n", encoder_depth, encoder_depth + 1); dprintf(fd, " }\n"); return true; } break; case CppType::Which::kEnum: { dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_encode_uint(&encoder%d, " "static_cast(%s)));\n", encoder_depth, ToUnderscoreId(name).c_str()); return true; } break; case CppType::Which::kDiscriminatedUnion: { for (const auto* union_member : cpp_type.discriminated_union.members) { switch (union_member->which) { case CppType::Which::kUint64: dprintf(fd, " case %s::%s::Which::kUint64:\n", ToCamelCase(nested_type_scope).c_str(), ToCamelCase(cpp_type.name).c_str()); if (!WriteEncoder(fd, ToUnderscoreId(name + ".uint"), *union_member, nested_type_scope, encoder_depth)) { return false; } dprintf(fd, " break;\n"); break; case CppType::Which::kString: dprintf(fd, " case %s::%s::Which::kString:\n", ToCamelCase(nested_type_scope).c_str(), ToCamelCase(cpp_type.name).c_str()); if (!WriteEncoder(fd, ToUnderscoreId(name + ".str"), *union_member, nested_type_scope, encoder_depth)) { return false; } dprintf(fd, " break;\n"); break; case CppType::Which::kBytes: dprintf(fd, " case %s::%s::Which::kBytes:\n", ToCamelCase(nested_type_scope).c_str(), ToCamelCase(cpp_type.name).c_str()); if (!WriteEncoder(fd, ToUnderscoreId(name + ".bytes"), *union_member, nested_type_scope, encoder_depth)) { return false; } dprintf(fd, " break;\n"); break; default: return false; } } dprintf(fd, " case %s::%s::Which::kUninitialized:\n", ToCamelCase(nested_type_scope).c_str(), ToCamelCase(cpp_type.name).c_str()); dprintf(fd, " return -CborUnknownError;\n"); return true; } break; case CppType::Which::kTaggedType: { dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_encode_tag(&encoder%d, %" PRIu64 "ull));\n", encoder_depth, cpp_type.tagged_type.tag); if (!WriteEncoder(fd, name, *cpp_type.tagged_type.real_type, nested_type_scope, encoder_depth)) { return false; } return true; } break; default: break; } return false; } struct MemberCountResult { int num_required; int num_optional; }; MemberCountResult CountMemberTypes( int fd, const std::string& name_id, const std::vector& members) { int num_required = 0; int num_optional = 0; for (const auto& x : members) { if (x.type->which == CppType::Which::kOptional) { std::string x_id = ToUnderscoreId(x.name); if (num_optional == 0) { dprintf(fd, " int num_optionals_present = %s.has_%s;\n", name_id.c_str(), x_id.c_str()); } else { dprintf(fd, " num_optionals_present += %s.has_%s;\n", name_id.c_str(), x_id.c_str()); } ++num_optional; } else { ++num_required; } } return MemberCountResult{num_required, num_optional}; } // Writes the encoding function for a CBOR map with the C++ type members in // |members| to the file descriptor |fd|. |name| is the C++ variable name that // needs to be encoded. |nested_type_scope| is the closest C++ scope name (i.e. // struct name), which may be used to access local enum constants. // |encoder_depth| is used to independently name independent cbor encoders that // need to be created. bool WriteMapEncoder(int fd, const std::string& name, const std::vector& members, const std::string& nested_type_scope, int encoder_depth) { std::string name_id = ToUnderscoreId(name); dprintf(fd, " CborEncoder encoder%d;\n", encoder_depth); MemberCountResult member_counts = CountMemberTypes(fd, name_id, members); if (member_counts.num_optional == 0) { dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_encoder_create_map(&encoder%d, " "&encoder%d, " "%d));\n", encoder_depth - 1, encoder_depth, member_counts.num_required); } else { dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_encoder_create_map(&encoder%d, " "&encoder%d, " "%d + num_optionals_present));\n", encoder_depth - 1, encoder_depth, member_counts.num_required); } for (const auto& x : members) { std::string fullname = name; CppType* member_type = x.type; if (x.type->which != CppType::Which::kStruct || x.type->struct_type.key_type != CppType::Struct::KeyType::kPlainGroup) { if (x.type->which == CppType::Which::kOptional) { member_type = x.type->optional_type; dprintf(fd, " if (%s.has_%s) {\n", name_id.c_str(), ToUnderscoreId(x.name).c_str()); } if (x.integer_key.has_value()) { dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_encode_uint(&encoder%d, %" PRIu64 "));\n", encoder_depth, x.integer_key.value()); } else { dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_encode_text_string(&encoder%d, " "\"%s\", sizeof(\"%s\") - 1));\n", encoder_depth, x.name.c_str(), x.name.c_str()); } if (x.type->which == CppType::Which::kDiscriminatedUnion) { dprintf(fd, " switch (%s.%s.which) {\n", fullname.c_str(), x.name.c_str()); } fullname = fullname + "." + x.name; } if (!WriteEncoder(fd, fullname, *member_type, nested_type_scope, encoder_depth)) { return false; } if (x.type->which == CppType::Which::kOptional || x.type->which == CppType::Which::kDiscriminatedUnion) { dprintf(fd, " }\n"); } } dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_encoder_close_container(&encoder%d, " "&encoder%d));\n", encoder_depth - 1, encoder_depth); return true; } // Writes the encoding function for a CBOR array with the C++ type members in // |members| to the file descriptor |fd|. |name| is the C++ variable name that // needs to be encoded. |nested_type_scope| is the closest C++ scope name (i.e. // struct name), which may be used to access local enum constants. // |encoder_depth| is used to independently name independent cbor encoders that // need to be created. bool WriteArrayEncoder(int fd, const std::string& name, const std::vector& members, const std::string& nested_type_scope, int encoder_depth) { std::string name_id = ToUnderscoreId(name); dprintf(fd, " CborEncoder encoder%d;\n", encoder_depth); MemberCountResult member_counts = CountMemberTypes(fd, name_id, members); if (member_counts.num_optional == 0) { dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_encoder_create_array(&encoder%d, " "&encoder%d, %d));\n", encoder_depth - 1, encoder_depth, member_counts.num_required); } else { dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_encoder_create_array(&encoder%d, " "&encoder%d, %d + num_optionals_present));\n", encoder_depth - 1, encoder_depth, member_counts.num_required); } for (const auto& x : members) { std::string fullname = name; CppType* member_type = x.type; if (x.type->which != CppType::Which::kStruct || x.type->struct_type.key_type != CppType::Struct::KeyType::kPlainGroup) { if (x.type->which == CppType::Which::kOptional) { member_type = x.type->optional_type; dprintf(fd, " if (%s.has_%s) {\n", name_id.c_str(), ToUnderscoreId(x.name).c_str()); } if (x.type->which == CppType::Which::kDiscriminatedUnion) { dprintf(fd, " switch (%s.%s.which) {\n", fullname.c_str(), x.name.c_str()); } fullname = fullname + "." + x.name; } if (!WriteEncoder(fd, fullname, *member_type, nested_type_scope, encoder_depth)) { return false; } if (x.type->which == CppType::Which::kOptional || x.type->which == CppType::Which::kDiscriminatedUnion) { dprintf(fd, " }\n"); } } dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_encoder_close_container(&encoder%d, " "&encoder%d));\n", encoder_depth - 1, encoder_depth); return true; } uint8_t GetByte(uint64_t value, size_t byte) { return static_cast((value >> (byte * 8)) & 0xFF); } std::string GetEncodedTypeKey(const CppType& type) { if (type.type_key == absl::nullopt) { return ""; } // Determine all constants needed for calculating the encoded id bytes. uint64_t type_id = type.type_key.value(); uint8_t encoding_size; uint8_t start_processing_byte; if (type_id < 0x1 << 6) { encoding_size = 0x0; start_processing_byte = 0; } else if (type_id < 0x1 << 14) { encoding_size = 0x01; start_processing_byte = 1; } else if (type_id < 0x1 << 30) { encoding_size = 0x02; start_processing_byte = 3; } else if (type_id < uint64_t{0x1} << 62) { encoding_size = 0x03; start_processing_byte = 7; } else { return ""; } // Parse the encoded id into a string; std::stringstream ss; uint8_t first_byte = encoding_size << 6 | GetByte(type_id, start_processing_byte); ss << "{0x" << std::hex << uint32_t{first_byte}; for (int i = start_processing_byte - 1; i >= 0; i--) { ss << ", 0x" << std::hex << uint32_t{GetByte(type_id, i)}; } ss << "}"; return ss.str(); } // Writes encoding functions for each type in |table| to the file descriptor // |fd|. bool WriteEncoders(int fd, CppSymbolTable* table) { for (CppType* real_type : table->TypesWithId()) { const auto& name = real_type->name; if (real_type->which != CppType::Which::kStruct || real_type->struct_type.key_type == CppType::Struct::KeyType::kPlainGroup) { return false; } std::string cpp_name = ToCamelCase(name); for (const auto& x : real_type->struct_type.members) { if (x.type->which != CppType::Which::kDiscriminatedUnion) continue; std::string dunion_cpp_name = ToCamelCase(x.name); dprintf(fd, "\n%s::%s::%s()\n", cpp_name.c_str(), dunion_cpp_name.c_str(), dunion_cpp_name.c_str()); std::string cid = ToUnderscoreId(x.name); std::string type_name = ToCamelCase(x.name); dprintf(fd, " : which(Which::kUninitialized), placeholder_(false) {}\n"); dprintf(fd, "\n%s::%s::~%s() {\n", cpp_name.c_str(), dunion_cpp_name.c_str(), dunion_cpp_name.c_str()); dprintf(fd, " switch (which) {\n"); for (const auto* y : x.type->discriminated_union.members) { switch (y->which) { case CppType::Which::kUint64: { dprintf(fd, " case Which::kUint64: break;\n"); } break; case CppType::Which::kString: { dprintf(fd, " case Which::kString:\n"); dprintf(fd, " str.std::string::~basic_string();\n"); dprintf(fd, " break;\n"); } break; case CppType::Which::kBytes: { dprintf(fd, " case Which::kBytes:\n"); dprintf(fd, " bytes.std::vector::~vector();\n"); dprintf(fd, " break;\n"); } break; default: return false; } } dprintf(fd, " case Which::kUninitialized: break;\n"); dprintf(fd, " }\n"); dprintf(fd, "}\n"); } static const char vector_encode_function[] = R"( bool Encode%1$s( const %1$s& data, CborEncodeBuffer* buffer) { if (buffer->AvailableLength() == 0 && !buffer->Append(CborEncodeBuffer::kDefaultInitialEncodeBufferSize)) return false; const uint8_t type_id[] = %2$s; if(!buffer->SetType(type_id, sizeof(type_id))) { return false; } while (true) { size_t available_length = buffer->AvailableLength(); ssize_t error_or_size = msgs::Encode%1$s( data, buffer->Position(), available_length); if (IsError(error_or_size)) { return false; } else if (error_or_size > static_cast(available_length)) { if (!buffer->ResizeBy(error_or_size - available_length)) return false; } else { buffer->ResizeBy(error_or_size - available_length); return true; } } } )"; std::string encoded_id = GetEncodedTypeKey(*real_type); if (encoded_id.empty()) { return false; } dprintf(fd, vector_encode_function, cpp_name.c_str(), encoded_id.c_str()); dprintf(fd, "\nssize_t Encode%s(\n", cpp_name.c_str()); dprintf(fd, " const %s& data,\n", cpp_name.c_str()); dprintf(fd, " uint8_t* buffer,\n size_t length) {\n"); dprintf(fd, " CborEncoder encoder0;\n"); dprintf(fd, " cbor_encoder_init(&encoder0, buffer, length, 0);\n"); if (real_type->struct_type.key_type == CppType::Struct::KeyType::kMap) { if (!WriteMapEncoder(fd, "data", real_type->struct_type.members, name)) return false; } else { if (!WriteArrayEncoder(fd, "data", real_type->struct_type.members, name)) { return false; } } dprintf(fd, " size_t extra_bytes_needed = " "cbor_encoder_get_extra_bytes_needed(&encoder0);\n"); dprintf(fd, " if (extra_bytes_needed) {\n"); dprintf(fd, " return static_cast(length + extra_bytes_needed);\n"); dprintf(fd, " } else {\n"); dprintf(fd, " return " "static_cast(cbor_encoder_get_buffer_size(&encoder0, " "buffer));\n"); dprintf(fd, " }\n"); dprintf(fd, "}\n"); } return true; } bool WriteMapDecoder(int fd, const std::string& name, const std::string& member_accessor, const std::vector& members, int decoder_depth, int* temporary_count); bool WriteArrayDecoder(int fd, const std::string& name, const std::string& member_accessor, const std::vector& members, int decoder_depth, int* temporary_count); // Writes the decoding function for the C++ type |cpp_type| to the file // descriptor |fd|. |name| is the C++ variable name that needs to be encoded. // |member_accessor| is either "." or "->" depending on whether |name| is a // pointer type. |decoder_depth| is used to independently name independent cbor // decoders that need to be created. |temporary_count| is used to ensure // temporaries get unique names by appending an automatically incremented // integer. bool WriteDecoder(int fd, const std::string& name, const std::string& member_accessor, const CppType& cpp_type, int decoder_depth, int* temporary_count) { switch (cpp_type.which) { case CppType::Which::kUint64: { dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_value_get_uint64(&it%d, &%s));\n", decoder_depth, name.c_str()); dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_value_advance_fixed(&it%d));\n", decoder_depth); return true; } break; case CppType::Which::kString: { int temp_length = (*temporary_count)++; dprintf(fd, " size_t length%d = 0;", temp_length); dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_value_validate(&it%d, " "CborValidateUtf8));\n", decoder_depth); dprintf(fd, " if (cbor_value_is_length_known(&it%d)) {\n", decoder_depth); dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_value_get_string_length(&it%d, " "&length%d));\n", decoder_depth, temp_length); dprintf(fd, " } else {\n"); dprintf( fd, " CBOR_RETURN_ON_ERROR(cbor_value_calculate_string_length(&it%d, " "&length%d));\n", decoder_depth, temp_length); dprintf(fd, " }\n"); dprintf(fd, " %s%sresize(length%d);\n", name.c_str(), member_accessor.c_str(), temp_length); dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_value_copy_text_string(&it%d, " "const_cast(%s%sdata()), &length%d, nullptr));\n", decoder_depth, name.c_str(), member_accessor.c_str(), temp_length); dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_value_advance(&it%d));\n", decoder_depth); return true; } break; case CppType::Which::kBytes: { int temp_length = (*temporary_count)++; dprintf(fd, " size_t length%d = 0;", temp_length); dprintf(fd, " if (cbor_value_is_length_known(&it%d)) {\n", decoder_depth); dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_value_get_string_length(&it%d, " "&length%d));\n", decoder_depth, temp_length); dprintf(fd, " } else {\n"); dprintf( fd, " CBOR_RETURN_ON_ERROR(cbor_value_calculate_string_length(&it%d, " "&length%d));\n", decoder_depth, temp_length); dprintf(fd, " }\n"); if (!cpp_type.bytes_type.fixed_size) { dprintf(fd, " %s%sresize(length%d);\n", name.c_str(), member_accessor.c_str(), temp_length); } else { dprintf(fd, " if (length%d < %d) {\n", temp_length, static_cast(cpp_type.bytes_type.fixed_size.value())); dprintf(fd, " return -CborErrorTooFewItems;\n"); dprintf(fd, " } else if (length%d > %d) {\n", temp_length, static_cast(cpp_type.bytes_type.fixed_size.value())); dprintf(fd, " return -CborErrorTooManyItems;\n"); dprintf(fd, " }\n"); } dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_value_copy_byte_string(&it%d, " "const_cast(%s%sdata()), &length%d, nullptr));\n", decoder_depth, name.c_str(), member_accessor.c_str(), temp_length); dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_value_advance(&it%d));\n", decoder_depth); return true; } break; case CppType::Which::kVector: { dprintf(fd, " if (cbor_value_get_type(&it%d) != CborArrayType) {\n", decoder_depth); dprintf(fd, " return -1;\n"); dprintf(fd, " }\n"); dprintf(fd, " {\n"); dprintf(fd, " CborValue it%d;\n", decoder_depth + 1); dprintf(fd, " size_t it%d_length = 0;\n", decoder_depth + 1); dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_value_get_array_length(&it%d, " "&it%d_length));\n", decoder_depth, decoder_depth + 1); if (cpp_type.vector_type.min_length != CppType::Vector::kMinLengthUnbounded) { dprintf(fd, " if (it%d_length < %d) {\n", decoder_depth + 1, cpp_type.vector_type.min_length); dprintf(fd, " return -CborErrorTooFewItems;\n"); dprintf(fd, " }\n"); } if (cpp_type.vector_type.max_length != CppType::Vector::kMaxLengthUnbounded) { dprintf(fd, " if (it%d_length > %d) {\n", decoder_depth + 1, cpp_type.vector_type.max_length); dprintf(fd, " return -CborErrorTooManyItems;\n"); dprintf(fd, " }\n"); } dprintf(fd, " %s%sresize(it%d_length);\n", name.c_str(), member_accessor.c_str(), decoder_depth + 1); dprintf( fd, " CBOR_RETURN_ON_ERROR(cbor_value_enter_container(&it%d, &it%d));\n", decoder_depth, decoder_depth + 1); dprintf(fd, " for (auto i = %s%sbegin(); i != %s%send(); ++i) {\n", name.c_str(), member_accessor.c_str(), name.c_str(), member_accessor.c_str()); if (!WriteDecoder(fd, "(*i)", ".", *cpp_type.vector_type.element_type, decoder_depth + 1, temporary_count)) { return false; } dprintf(fd, " }\n"); dprintf( fd, " CBOR_RETURN_ON_ERROR(cbor_value_leave_container(&it%d, &it%d));\n", decoder_depth, decoder_depth + 1); dprintf(fd, " }\n"); return true; } break; case CppType::Which::kEnum: { dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_value_get_uint64(&it%d, " "reinterpret_cast(&%s)));\n", decoder_depth, name.c_str()); dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_value_advance_fixed(&it%d));\n", decoder_depth); // TODO(btolsch): Validate against enum members. return true; } break; case CppType::Which::kStruct: { if (cpp_type.struct_type.key_type == CppType::Struct::KeyType::kMap) { return WriteMapDecoder(fd, name, member_accessor, cpp_type.struct_type.members, decoder_depth + 1, temporary_count); } else if (cpp_type.struct_type.key_type == CppType::Struct::KeyType::kArray) { return WriteArrayDecoder(fd, name, member_accessor, cpp_type.struct_type.members, decoder_depth + 1, temporary_count); } } break; case CppType::Which::kDiscriminatedUnion: { int temp_value_type = (*temporary_count)++; dprintf(fd, " CborType type%d = cbor_value_get_type(&it%d);\n", temp_value_type, decoder_depth); bool first = true; for (const auto* x : cpp_type.discriminated_union.members) { if (first) first = false; else dprintf(fd, " else "); switch (x->which) { case CppType::Which::kUint64: dprintf(fd, " if (type%d == CborIntegerType && (it%d.flags & " "CborIteratorFlag_NegativeInteger) == 0) {\n", temp_value_type, decoder_depth); dprintf(fd, " %s.which = decltype(%s)::Which::kUint64;\n", name.c_str(), name.c_str()); if (!WriteDecoder(fd, name + ".uint", ".", *x, decoder_depth, temporary_count)) { return false; } break; case CppType::Which::kString: { dprintf(fd, " if (type%d == CborTextStringType) {\n", temp_value_type); dprintf(fd, " %s.which = decltype(%s)::Which::kString;\n", name.c_str(), name.c_str()); std::string str_name = name + ".str"; dprintf(fd, " new (&%s) std::string();\n", str_name.c_str()); if (!WriteDecoder(fd, str_name, ".", *x, decoder_depth, temporary_count)) { return false; } } break; case CppType::Which::kBytes: { dprintf(fd, " if (type%d == CborByteStringType) {\n", temp_value_type); std::string bytes_name = name + ".bytes"; dprintf(fd, " %s.which = decltype(%s)::Which::kBytes;\n", name.c_str(), name.c_str()); dprintf(fd, " new (&%s) std::vector();\n", bytes_name.c_str()); if (!WriteDecoder(fd, bytes_name, ".", *x, decoder_depth, temporary_count)) { return false; } } break; default: return false; } dprintf(fd, " }\n"); } dprintf(fd, " else { return -1; }\n"); return true; } break; case CppType::Which::kTaggedType: { int temp_tag = (*temporary_count)++; dprintf(fd, " uint64_t tag%d = 0;\n", temp_tag); dprintf(fd, " cbor_value_get_tag(&it%d, &tag%d);\n", decoder_depth, temp_tag); dprintf(fd, " if (tag%d != %" PRIu64 "ull) {\n", temp_tag, cpp_type.tagged_type.tag); dprintf(fd, " return -1;\n"); dprintf(fd, " }\n"); dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_value_advance_fixed(&it%d));\n", decoder_depth); if (!WriteDecoder(fd, name, member_accessor, *cpp_type.tagged_type.real_type, decoder_depth, temporary_count)) { return false; } return true; } break; default: break; } return false; } // Writes the decoding function for the CBOR map with members in |members| to // the file descriptor |fd|. |name| is the C++ variable name that needs to be // encoded. |member_accessor| is either "." or "->" depending on whether |name| // is a pointer type. |decoder_depth| is used to independently name independent // cbor decoders that need to be created. |temporary_count| is used to ensure // temporaries get unique names by appending an automatically incremented // integer. bool WriteMapDecoder(int fd, const std::string& name, const std::string& member_accessor, const std::vector& members, int decoder_depth, int* temporary_count) { dprintf(fd, " if (cbor_value_get_type(&it%d) != CborMapType) {\n", decoder_depth - 1); dprintf(fd, " return -1;\n"); dprintf(fd, " }\n"); dprintf(fd, " CborValue it%d;\n", decoder_depth); dprintf(fd, " size_t it%d_length = 0;\n", decoder_depth); dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_value_get_map_length(&it%d, " "&it%d_length));\n", decoder_depth - 1, decoder_depth); int optional_members = 0; for (const auto& member : members) { if (member.type->which == CppType::Which::kOptional) { ++optional_members; } } dprintf(fd, " if (it%d_length != %d", decoder_depth, static_cast(members.size())); for (int i = 0; i < optional_members; ++i) { dprintf(fd, " && it%d_length != %d", decoder_depth, static_cast(members.size()) - i - 1); } dprintf(fd, ") {\n"); dprintf(fd, " return -1;\n"); dprintf(fd, " }\n"); dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_value_enter_container(&it%d, &it%d));\n", decoder_depth - 1, decoder_depth); int member_pos = 0; for (const auto& x : members) { std::string cid = ToUnderscoreId(x.name); std::string fullname = name + member_accessor + cid; if (x.type->which == CppType::Which::kOptional) { // TODO(btolsch): This is wrong for the same reason as arrays, but will be // easier to handle when doing out-of-order keys. dprintf(fd, " if (it%d_length > %d) {\n", decoder_depth, member_pos); if (x.integer_key.has_value()) { dprintf(fd, " CBOR_RETURN_ON_ERROR(EXPECT_INT_KEY_CONSTANT(&it%d, %" PRIu64 "));\n", decoder_depth, x.integer_key.value()); } else { dprintf(fd, " CBOR_RETURN_ON_ERROR(EXPECT_KEY_CONSTANT(&it%d, \"%s\"));\n", decoder_depth, x.name.c_str()); } dprintf(fd, " %s%shas_%s = true;\n", name.c_str(), member_accessor.c_str(), cid.c_str()); if (!WriteDecoder(fd, fullname, ".", *x.type->optional_type, decoder_depth, temporary_count)) { return false; } dprintf(fd, " } else {\n"); dprintf(fd, " %s%shas_%s = false;\n", name.c_str(), member_accessor.c_str(), cid.c_str()); dprintf(fd, " }\n"); } else { if (x.integer_key.has_value()) { dprintf(fd, " CBOR_RETURN_ON_ERROR(EXPECT_INT_KEY_CONSTANT(&it%d, %" PRIu64 "));\n", decoder_depth, x.integer_key.value()); } else { dprintf(fd, " CBOR_RETURN_ON_ERROR(EXPECT_KEY_CONSTANT(&it%d, \"%s\"));\n", decoder_depth, x.name.c_str()); } if (!WriteDecoder(fd, fullname, ".", *x.type, decoder_depth, temporary_count)) { return false; } } ++member_pos; } dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_value_leave_container(&it%d, &it%d));\n", decoder_depth - 1, decoder_depth); return true; } // Writes the decoding function for the CBOR array with members in |members| to // the file descriptor |fd|. |name| is the C++ variable name that needs to be // encoded. |member_accessor| is either "." or "->" depending on whether |name| // is a pointer type. |decoder_depth| is used to independently name independent // cbor decoders that need to be created. |temporary_count| is used to ensure // temporaries get unique names by appending an automatically incremented // integer. bool WriteArrayDecoder(int fd, const std::string& name, const std::string& member_accessor, const std::vector& members, int decoder_depth, int* temporary_count) { dprintf(fd, " if (cbor_value_get_type(&it%d) != CborArrayType) {\n", decoder_depth - 1); dprintf(fd, " return -1;\n"); dprintf(fd, " }\n"); dprintf(fd, " CborValue it%d;\n", decoder_depth); dprintf(fd, " size_t it%d_length = 0;\n", decoder_depth); dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_value_get_array_length(&it%d, " "&it%d_length));\n", decoder_depth - 1, decoder_depth); int optional_members = 0; for (const auto& member : members) { if (member.type->which == CppType::Which::kOptional) { ++optional_members; } } dprintf(fd, " if (it%d_length != %d", decoder_depth, static_cast(members.size())); for (int i = 0; i < optional_members; ++i) { dprintf(fd, " && it%d_length != %d", decoder_depth, static_cast(members.size()) - i - 1); } dprintf(fd, ") {\n"); dprintf(fd, " return -1;\n"); dprintf(fd, " }\n"); dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_value_enter_container(&it%d, &it%d));\n", decoder_depth - 1, decoder_depth); int member_pos = 0; for (const auto& x : members) { std::string cid = ToUnderscoreId(x.name); std::string fullname = name + member_accessor + cid; if (x.type->which == CppType::Which::kOptional) { // TODO(btolsch): This only handles a single block of optionals and only // the ones present form a contiguous range from the start of the block. // However, we likely don't really need more than one optional for arrays // for the foreseeable future. The proper approach would be to have a set // of possible types for the next element and a map for the member to // which each corresponds. dprintf(fd, " if (it%d_length > %d) {\n", decoder_depth, member_pos); dprintf(fd, " %s%shas_%s = true;\n", name.c_str(), member_accessor.c_str(), cid.c_str()); if (!WriteDecoder(fd, fullname, ".", *x.type->optional_type, decoder_depth, temporary_count)) { return false; } dprintf(fd, " } else {\n"); dprintf(fd, " %s%shas_%s = false;\n", name.c_str(), member_accessor.c_str(), cid.c_str()); dprintf(fd, " }\n"); } else { if (!WriteDecoder(fd, fullname, ".", *x.type, decoder_depth, temporary_count)) { return false; } } ++member_pos; } dprintf(fd, " CBOR_RETURN_ON_ERROR(cbor_value_leave_container(&it%d, &it%d));\n", decoder_depth - 1, decoder_depth); return true; } // Writes the equality operators for all structs. bool WriteEqualityOperators(int fd, CppSymbolTable* table) { for (const auto& pair : table->cpp_type_map) { CppType* real_type = pair.second; if (real_type->which == CppType::Which::kStruct && real_type->struct_type.key_type != CppType::Struct::KeyType::kPlainGroup) { if (!WriteStructEqualityOperator(fd, *real_type)) { return false; } } } return true; } // Writes a decoder function definition for every type in |table| to the file // descriptor |fd|. bool WriteDecoders(int fd, CppSymbolTable* table) { if (!WriteTypeParserDefinition(fd, table)) { return false; } for (CppType* real_type : table->TypesWithId()) { const auto& name = real_type->name; int temporary_count = 0; if (real_type->which != CppType::Which::kStruct || real_type->struct_type.key_type == CppType::Struct::KeyType::kPlainGroup) { continue; } std::string cpp_name = ToCamelCase(name); dprintf(fd, "\nssize_t Decode%s(\n", cpp_name.c_str()); dprintf(fd, " const uint8_t* buffer,\n size_t length,\n"); dprintf(fd, " %s* data) {\n", cpp_name.c_str()); dprintf(fd, " CborParser parser;\n"); dprintf(fd, " CborValue it0;\n"); dprintf( fd, " CBOR_RETURN_ON_ERROR(cbor_parser_init(buffer, length, 0, &parser, " "&it0));\n"); if (real_type->struct_type.key_type == CppType::Struct::KeyType::kMap) { if (!WriteMapDecoder(fd, "data", "->", real_type->struct_type.members, 1, &temporary_count)) { return false; } } else { if (!WriteArrayDecoder(fd, "data", "->", real_type->struct_type.members, 1, &temporary_count)) { return false; } } dprintf( fd, " auto result = static_cast(cbor_value_get_next_byte(&it0) - " "buffer);\n"); dprintf(fd, " return result;\n"); dprintf(fd, "}\n"); } return true; } // Converts the filename |header_filename| to a preprocessor token that can be // used as a header guard macro name. std::string ToHeaderGuard(const std::string& header_filename) { std::string result = header_filename; for (auto& c : result) { if (c == '/' || c == '.') c = '_'; else c = toupper(c); } result += "_"; return result; } bool WriteHeaderPrologue(int fd, const std::string& header_filename) { static const char prologue[] = R"(#ifndef %s #define %s #include #include #include #include #include #include "third_party/tinycbor/src/src/cbor.h" namespace openscreen { namespace msgs { enum CborErrors { kParserEOF = -CborErrorUnexpectedEOF, }; class CborEncodeBuffer; )"; std::string header_guard = ToHeaderGuard(header_filename); dprintf(fd, prologue, header_guard.c_str(), header_guard.c_str()); return true; } bool WriteHeaderEpilogue(int fd, const std::string& header_filename) { static const char epilogue[] = R"( class TypeEnumValidator { public: static Type SafeCast(uint64_t type_id); }; class CborEncodeBuffer { public: static constexpr size_t kDefaultInitialEncodeBufferSize = 250; static constexpr size_t kDefaultMaxEncodeBufferSize = 64000; CborEncodeBuffer(); CborEncodeBuffer(size_t initial_size, size_t max_size); ~CborEncodeBuffer(); bool Append(size_t length); bool ResizeBy(ssize_t length); bool SetType(const uint8_t encoded_id[], size_t size); const uint8_t* data() const { return data_.data(); } size_t size() const { return data_.size(); } uint8_t* Position() { return &data_[0] + position_; } size_t AvailableLength() { return data_.size() - position_; } private: size_t max_size_; size_t position_; std::vector data_; }; CborError ExpectKey(CborValue* it, const uint64_t key); CborError ExpectKey(CborValue* it, const char* key, size_t key_length); } // namespace msgs } // namespace openscreen #endif // %s)"; std::string header_guard = ToHeaderGuard(header_filename); dprintf(fd, epilogue, header_guard.c_str()); return true; } bool WriteSourcePrologue(int fd, const std::string& header_filename) { static const char prologue[] = R"(#include "%s" #include "third_party/tinycbor/src/src/utf8_p.h" #include "util/osp_logging.h" namespace openscreen { namespace msgs { namespace { #define CBOR_RETURN_WHAT_ON_ERROR(stmt, what) \ { \ CborError error = stmt; \ /* Encoder-specific errors, so it's fine to check these even in the \ * parser. \ */ \ OSP_DCHECK_NE(error, CborErrorTooFewItems); \ OSP_DCHECK_NE(error, CborErrorTooManyItems); \ OSP_DCHECK_NE(error, CborErrorDataTooLarge); \ if (error != CborNoError && error != CborErrorOutOfMemory) \ return what; \ } #define CBOR_RETURN_ON_ERROR_INTERNAL(stmt) \ CBOR_RETURN_WHAT_ON_ERROR(stmt, error) #define CBOR_RETURN_ON_ERROR(stmt) CBOR_RETURN_WHAT_ON_ERROR(stmt, -error) #define EXPECT_KEY_CONSTANT(it, key) ExpectKey(it, key, sizeof(key) - 1) #define EXPECT_INT_KEY_CONSTANT(it, key) ExpectKey(it, key) bool IsValidUtf8(const std::string& s) { const uint8_t* buffer = reinterpret_cast(s.data()); const uint8_t* end = buffer + s.size(); while (buffer < end) { // TODO(btolsch): This is an implementation detail of tinycbor so we should // eventually replace this call with our own utf8 validation. if (get_utf8(&buffer, end) == ~0u) return false; } return true; } } // namespace CborError ExpectKey(CborValue* it, const uint64_t key) { if (!cbor_value_is_unsigned_integer(it)) return CborErrorImproperValue; uint64_t observed_key; CBOR_RETURN_ON_ERROR_INTERNAL(cbor_value_get_uint64(it, &observed_key)); if (observed_key != key) return CborErrorImproperValue; CBOR_RETURN_ON_ERROR_INTERNAL(cbor_value_advance_fixed(it)); return CborNoError; } CborError ExpectKey(CborValue* it, const char* key, size_t key_length) { if(!cbor_value_is_text_string(it)) return CborErrorImproperValue; size_t observed_length = 0; CBOR_RETURN_ON_ERROR_INTERNAL( cbor_value_get_string_length(it, &observed_length)); if (observed_length != key_length) return CborErrorImproperValue; std::string observed_key(key_length, 0); CBOR_RETURN_ON_ERROR_INTERNAL(cbor_value_copy_text_string( it, const_cast(observed_key.data()), &observed_length, nullptr)); if (observed_key != key) return CborErrorImproperValue; CBOR_RETURN_ON_ERROR_INTERNAL(cbor_value_advance(it)); return CborNoError; } // static constexpr size_t CborEncodeBuffer::kDefaultInitialEncodeBufferSize; // static constexpr size_t CborEncodeBuffer::kDefaultMaxEncodeBufferSize; CborEncodeBuffer::CborEncodeBuffer() : max_size_(kDefaultMaxEncodeBufferSize), position_(0), data_(kDefaultInitialEncodeBufferSize) {} CborEncodeBuffer::CborEncodeBuffer(size_t initial_size, size_t max_size) : max_size_(max_size), position_(0), data_(initial_size) {} CborEncodeBuffer::~CborEncodeBuffer() = default; bool CborEncodeBuffer::SetType(const uint8_t encoded_id[], size_t size) { if (this->AvailableLength() < size) { if (!this->ResizeBy(size)) { return false; } } memcpy(&data_[position_], encoded_id, size); position_ += size; return true; } bool CborEncodeBuffer::Append(size_t length) { if (length == 0) return false; if ((data_.size() + length) > max_size_) { length = max_size_ - data_.size(); if (length == 0) return false; } size_t append_area = data_.size(); data_.resize(append_area + length); position_ = append_area; return true; } bool CborEncodeBuffer::ResizeBy(ssize_t delta) { if (delta == 0) return true; if (delta < 0 && static_cast(-delta) > data_.size()) return false; if (delta > 0 && (data_.size() + delta) > max_size_) return false; data_.resize(data_.size() + delta); return true; } bool IsError(ssize_t x) { return x < 0; } )"; dprintf(fd, prologue, header_filename.c_str()); return true; } bool WriteSourceEpilogue(int fd) { static const char epilogue[] = R"( } // namespace msgs } // namespace openscreen)"; dprintf(fd, epilogue); return true; }