1 /*
2  * Copyright 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #include <assert.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <unistd.h>
20 #include <cassert>
21 #include <list>
22 #include <map>
23 #include <vector>
24 
25 #include "bundler.h"
26 #include "bundler_generated.h"
27 #include "flatbuffers/idl.h"
28 #include "flatbuffers/util.h"
29 
30 using namespace bluetooth;
31 using namespace dumpsys;
32 
33 struct Opts opts;
34 
35 /**
36  * Load a binary schema from persistent store using flatbuffer API.
37  *
38  * @param filename; Name of file to open and read.
39  * @param binary_schema: Backing store for flatbuffer binary schema data.
40  *
41  * @return: True if operation successful, false otherwise.
42  */
LoadBinarySchema(const char * filename,std::string * binary_schema)43 bool LoadBinarySchema(const char* filename, std::string* binary_schema) {
44   assert(filename != nullptr);
45   assert(binary_schema != nullptr);
46   if (!flatbuffers::LoadFile(filename, helper::AsBinaryFile, binary_schema)) {
47     fprintf(stderr, "Unable to open binary flatbuffer schema file:%s\n", filename);
48     return false;
49   };
50   return true;
51 }
52 
53 /**
54  * Verify a binary schema using flatbuffer API.
55  *
56  * @param schema: Raw binary schema to verify
57  *
58  * @return: True if operation successful, false otherwise.
59  */
VerifyBinarySchema(const std::vector<uint8_t> & raw_schema)60 bool VerifyBinarySchema(const std::vector<uint8_t>& raw_schema) {
61   flatbuffers::Verifier verifier(raw_schema.data(), raw_schema.size());
62   if (!reflection::VerifySchemaBuffer(verifier)) {
63     return false;
64   }
65   return true;
66 }
67 
68 /**
69  * Bundle a set of binary flatbuffer schema into the bundler database.
70  *
71  * @param builder; Flatbuffer builder
72  * @param filenames: Set of filenames to include in bundle
73  * @param vector_map: Filename to filedata mapping
74  *
75  * @return: True if operation successful, false otherwise.
76  */
CreateBinarySchemaBundle(flatbuffers::FlatBufferBuilder * builder,const std::vector<std::string> & filenames,std::vector<flatbuffers::Offset<BundledSchemaMap>> * vector_map,std::list<std::string> * bundled_names)77 bool CreateBinarySchemaBundle(
78     flatbuffers::FlatBufferBuilder* builder,
79     const std::vector<std::string>& filenames,
80     std::vector<flatbuffers::Offset<BundledSchemaMap>>* vector_map,
81     std::list<std::string>* bundled_names) {
82   assert(builder != nullptr);
83   assert(vector_map != nullptr);
84   assert(bundled_names != nullptr);
85 
86   for (auto filename : filenames) {
87     std::string string_schema;
88     if (!LoadBinarySchema(filename.c_str(), &string_schema)) {
89       fprintf(stderr, "Unable to load binary schema from filename:%s\n", filename.c_str());
90       return false;
91     }
92     std::vector<uint8_t> raw_schema(string_schema.begin(), string_schema.end());
93     if (!VerifyBinarySchema(raw_schema)) {
94       fprintf(stderr, "Failed verification on binary schema filename:%s\n", filename.c_str());
95       return false;
96     }
97 
98     const reflection::Schema* schema = reflection::GetSchema(raw_schema.data());
99     if (schema->root_table() == nullptr) {
100       fprintf(stderr, "Unable to find root table for binary flatbuffer schema:%s\n", filename.c_str());
101       return false;
102     }
103 
104     bundled_names->push_back(schema->root_table()->name()->str());
105     auto name = builder->CreateString(schema->root_table()->name()->str());
106     auto data = builder->CreateVector<uint8_t>(raw_schema.data(), raw_schema.size());
107     vector_map->push_back(CreateBundledSchemaMap(*builder, name, data));
108 
109     if (opts.verbose) {
110       fprintf(stdout, "Bundled binary schema file:%s\n", schema->root_table()->name()->c_str());
111     }
112   }
113   return true;
114 }
115 
116 /**
117  * Write generated header file containing the bundled binary schema
118  * data and meta data
119  *
120  * @param data: Source file data.
121  * @param data_len: length of data
122  */
WriteHeaderFile(FILE * fp,const uint8_t * data,size_t data_len)123 void WriteHeaderFile(FILE* fp, const uint8_t* data, size_t data_len) {
124   assert(fp != nullptr);
125   std::string delim(kDefaultNamespaceDelim);
126   std::string ns_string(opts.ns_name);
127   std::vector<std::string> namespaces;
128 
129   size_t start = 0;
130   size_t end = ns_string.find(delim);
131   while (end != std::string::npos) {
132     namespaces.push_back(ns_string.substr(start, end - start));
133     start = end + delim.size();
134     end = ns_string.find(delim, start);
135   }
136   if (start != 0 && start != std::string::npos) {
137     namespaces.push_back(ns_string.substr(start));
138   } else if (!ns_string.empty()) {
139     namespaces.push_back(ns_string);
140   }
141 
142   std::string namespace_prefix;
143   for (const auto& name : namespaces) namespace_prefix += (name + '_');
144 
145   fprintf(
146       fp,
147       "// Generated file by bluetooth_flatbuffer bundler\n"
148       "#pragma once\n"
149       "#include <sys/types.h>\n"
150       "#include <string>\n");
151   for_each(
152       namespaces.begin(), namespaces.end(), [fp](const std::string& s) { fprintf(fp, "namespace %s {\n", s.c_str()); });
153   fprintf(
154       fp,
155       "extern const unsigned char* data;\n"
156       "extern const size_t data_size;\n"
157       "const std::string& GetBundledSchemaData();\n");
158   for_each(namespaces.crbegin(), namespaces.crend(), [fp](const std::string& s) {
159     fprintf(fp, "}  // namespace %s\n", s.c_str());
160   });
161   fprintf(
162       fp,
163       "namespace {\n"
164       "const unsigned char %sdata_[] = {\n",
165       namespace_prefix.c_str());
166 
167   for (auto i = 0; i < data_len; i++) {
168     fprintf(fp, " 0x%02x", data[i]);
169     if (i != data_len - 1) {
170       fprintf(fp, ",");
171     }
172     if ((i + 1) % 16 == 0) {
173       fprintf(fp, "\n");
174     }
175   }
176   fprintf(fp, " };\n");
177   fprintf(
178       fp,
179       "const std::string %sstring_data_(%sdata_, %sdata_ + sizeof(%sdata_));\n",
180       namespace_prefix.c_str(),
181       namespace_prefix.c_str(),
182       namespace_prefix.c_str(),
183       namespace_prefix.c_str());
184   fprintf(fp, "}  // namespace\n");
185   fprintf(fp, "const unsigned char* %s::data = %sdata_;\n", opts.ns_name, namespace_prefix.c_str());
186   fprintf(fp, "const size_t %s::data_size = %zu;\n", opts.ns_name, data_len);
187   fprintf(
188       fp,
189       "const std::string& %s::GetBundledSchemaData() { return %sstring_data_; }\n",
190       opts.ns_name,
191       namespace_prefix.c_str());
192 }
193 
ReadBundledSchema()194 int ReadBundledSchema() {
195   const char* filename = opts.filename;
196   assert(filename != nullptr);
197 
198   std::string flatfile_data;
199   if (!flatbuffers::LoadFile(filename, helper::AsBinaryFile, &flatfile_data)) {
200     fprintf(stderr, "Unable to load schema data file:%s\n", filename);
201     return -5;
202   }
203 
204   auto bundle_schema = flatbuffers::GetRoot<BundledSchema>(flatfile_data.c_str());
205   const flatbuffers::Vector<flatbuffers::Offset<BundledSchemaMap>>* map = bundle_schema->map();
206 
207   fprintf(stdout, "Bundle schema title:%s\n", bundle_schema->title()->c_str());
208   fprintf(stdout, "Bundle schema root_name:%s\n", bundle_schema->root_name()->c_str());
209   int cnt = 0;
210   for (auto it = map->cbegin(); it != map->cend(); ++it, cnt++) {
211     fprintf(stdout, "   %d name:%s schema:%s\n", cnt, it->name()->c_str(), "schema");
212   }
213   return EXIT_SUCCESS;
214 }
215 
WriteBundledSchema()216 int WriteBundledSchema() {
217   const char* filename = opts.filename;
218   assert(filename != nullptr);
219 
220   const char* main_root_name = opts.main_root_name;
221   if (main_root_name == nullptr) {
222     fprintf(stderr, "Must specify the name of the main root name for this bundle\n");
223     return EXIT_FAILURE;
224   }
225 
226   std::vector<std::string> bfbs_filenames;
227   for (int i = 0; i < opts.arg.c; i++) {
228     bfbs_filenames.push_back(std::string(opts.arg.v[i]));
229   }
230   if (bfbs_filenames.empty()) {
231     fprintf(stderr, "No bfbs files are specified to bundle\n");
232     return EXIT_FAILURE;
233   }
234 
235   flatbuffers::FlatBufferBuilder builder(1024);
236 
237   std::list<std::string> bundled_names;
238   std::vector<flatbuffers::Offset<BundledSchemaMap>> vector_map;
239   if (!CreateBinarySchemaBundle(&builder, bfbs_filenames, &vector_map, &bundled_names)) {
240     fprintf(stderr, "Unable to bundle schema bfbs files\n");
241     return EXIT_FAILURE;
242   }
243 
244   if (std::find(bundled_names.begin(), bundled_names.end(), main_root_name) == bundled_names.end()) {
245     fprintf(stderr, "The main root name must match one of the bundled schema names\n");
246     fprintf(stderr, "  main root name:%s\n", main_root_name);
247     for (auto name : bundled_names) {
248       fprintf(stderr, "  bundled schema name:%s\n", name.c_str());
249     }
250     return EXIT_FAILURE;
251   }
252 
253   const char* title = opts.title;
254   auto schema_offset = CreateBundledSchemaDirect(builder, title, main_root_name, &vector_map);
255   builder.Finish(schema_offset);
256 
257   std::string final_filename(opts.gen);
258   final_filename.append("/");
259   final_filename.append(filename);
260   if (!flatbuffers::SaveFile(
261           final_filename.c_str(), (const char*)builder.GetBufferPointer(), builder.GetSize(), helper::AsBinaryFile)) {
262     fprintf(stderr, "Unable to save file:%s\n", final_filename.c_str());
263     return EXIT_FAILURE;
264   }
265 
266   std::string header(opts.gen);
267   header += ("/" + std::string(opts.filename) + ".h");
268   FILE* fp = fopen(header.c_str(), "w+");
269   if (fp == nullptr) {
270     fprintf(stdout, "Unable to open for writing header file:%s\n", header.c_str());
271     return EXIT_FAILURE;
272   }
273   WriteHeaderFile(fp, builder.GetBufferPointer(), builder.GetSize());
274   fclose(fp);
275   return EXIT_SUCCESS;
276 }
277 
Usage(int argc,char ** argv)278 int Usage(int argc, char** argv) {
279   fprintf(
280       stderr,
281       "Usage: %s [-r | -w] [-f <filename>] [-g <gen_out_path>] [-n <namespace> ] [-v] -m <main_root_name> <file.bfbs "
282       "...>\n",
283       argv[0]);
284   fprintf(stderr, " -r|-w : Read or write a dumpsys file\n");
285   fprintf(stderr, " -f : Filename bundled schema to read or write (default:%s)\n", kDefaultBundleDataFile);
286   fprintf(stderr, " -g : Generated file output path\n");
287   fprintf(stderr, " -n : Namespace to embed binary output bundle data source\n");
288   fprintf(stderr, " -m : Name of the main root of this bundle\n");
289   fprintf(stderr, " -v : Verbose printing mode\n");
290   return EXIT_FAILURE;
291 }
292 
ParseArgs(int argc,char ** argv)293 void ParseArgs(int argc, char** argv) {
294   int opt;
295   int parsed_cnt = 1;
296   while ((opt = getopt(argc, argv, "f:g:m:n:rt:vw")) != -1) {
297     parsed_cnt++;
298     switch (opt) {
299       case 'f':
300         opts.filename = optarg;
301         parsed_cnt++;
302         break;
303       case 'g':
304         opts.gen = optarg;
305         parsed_cnt++;
306         break;
307       case 'm':
308         opts.main_root_name = optarg;
309         parsed_cnt++;
310         break;
311       case 'n':
312         opts.ns_name = optarg;
313         parsed_cnt++;
314         break;
315       case 'r':
316         opts.read = true;
317         break;
318       case 'w':
319         opts.write = true;
320         break;
321       case 't':
322         opts.title = optarg;
323         parsed_cnt++;
324         break;
325       case 'v':
326         opts.verbose = true;
327         break;
328       default:
329         exit(Usage(argc, argv));
330         break;
331     }
332   }
333   opts.arg.c = argc - parsed_cnt;
334   opts.arg.v = &argv[parsed_cnt];
335 }
336