1 /*
2 * Copyright (C) 2018 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
17 #include "lang_id/common/flatbuffers/model-utils.h"
18
19 #include <string.h>
20
21 #include <string>
22
23 #include "lang_id/common/lite_base/logging.h"
24 #include "lang_id/common/math/checksum.h"
25 #include "absl/strings/string_view.h"
26
27 namespace libtextclassifier3 {
28 namespace saft_fbs {
29
ClearlyFailsChecksum(const Model & model)30 bool ClearlyFailsChecksum(const Model &model) {
31 if (!flatbuffers::IsFieldPresent(&model, Model::VT_CRC32)) {
32 SAFTM_LOG(WARNING)
33 << "No CRC32, most likely an old model; skip CRC32 check";
34 return false;
35 }
36 const mobile::uint32 expected_crc32 = model.crc32();
37 const mobile::uint32 actual_crc32 = ComputeCrc2Checksum(&model);
38 if (actual_crc32 != expected_crc32) {
39 SAFTM_LOG(ERROR) << "Corrupt model: different CRC32: " << actual_crc32
40 << " vs " << expected_crc32;
41 return true;
42 }
43 SAFTM_DLOG(INFO) << "Successfully checked CRC32 " << actual_crc32;
44 return false;
45 }
46
GetVerifiedModelFromBytes(const char * data,size_t num_bytes)47 const Model *GetVerifiedModelFromBytes(const char *data, size_t num_bytes) {
48 if ((data == nullptr) || (num_bytes == 0)) {
49 SAFTM_LOG(ERROR) << "GetModel called on an empty sequence of bytes";
50 return nullptr;
51 }
52 const uint8_t *start = reinterpret_cast<const uint8_t *>(data);
53 flatbuffers::Verifier verifier(start, num_bytes);
54 if (!VerifyModelBuffer(verifier)) {
55 SAFTM_LOG(ERROR) << "Not a valid Model flatbuffer";
56 return nullptr;
57 }
58 const Model *model = GetModel(start);
59 if (model == nullptr) {
60 return nullptr;
61 }
62 if (ClearlyFailsChecksum(*model)) {
63 return nullptr;
64 }
65 return model;
66 }
67
GetInputByName(const Model * model,absl::string_view name)68 const ModelInput *GetInputByName(const Model *model, absl::string_view name) {
69 if (model == nullptr) {
70 SAFTM_LOG(ERROR) << "GetInputByName called with model == nullptr";
71 return nullptr;
72 }
73 const auto *inputs = model->inputs();
74 if (inputs == nullptr) {
75 // We should always have a list of inputs; maybe an empty one, if no inputs,
76 // but the list should be there.
77 SAFTM_LOG(ERROR) << "null inputs";
78 return nullptr;
79 }
80 for (const ModelInput *input : *inputs) {
81 if (input != nullptr) {
82 const flatbuffers::String *input_name = input->name();
83 if (input_name && input_name->str() == name) {
84 return input;
85 }
86 }
87 }
88 return nullptr;
89 }
90
GetInputBytes(const ModelInput * input)91 mobile::StringPiece GetInputBytes(const ModelInput *input) {
92 if ((input == nullptr) || (input->data() == nullptr)) {
93 SAFTM_LOG(ERROR) << "ModelInput has no content";
94 return mobile::StringPiece(nullptr, 0);
95 }
96 const flatbuffers::Vector<uint8_t> *input_data = input->data();
97 if (input_data == nullptr) {
98 SAFTM_LOG(ERROR) << "null input data";
99 return mobile::StringPiece(nullptr, 0);
100 }
101 return mobile::StringPiece(reinterpret_cast<const char *>(input_data->data()),
102 input_data->size());
103 }
104
FillParameters(const Model & model,mobile::TaskContext * context)105 bool FillParameters(const Model &model, mobile::TaskContext *context) {
106 if (context == nullptr) {
107 SAFTM_LOG(ERROR) << "null context";
108 return false;
109 }
110 const auto *parameters = model.parameters();
111 if (parameters == nullptr) {
112 // We should always have a list of parameters; maybe an empty one, if no
113 // parameters, but the list should be there.
114 SAFTM_LOG(ERROR) << "null list of parameters";
115 return false;
116 }
117 for (const ModelParameter *p : *parameters) {
118 if (p == nullptr) {
119 SAFTM_LOG(ERROR) << "null parameter";
120 return false;
121 }
122 if (p->name() == nullptr) {
123 SAFTM_LOG(ERROR) << "null parameter name";
124 return false;
125 }
126 const std::string name = p->name()->str();
127 if (name.empty()) {
128 SAFTM_LOG(ERROR) << "empty parameter name";
129 return false;
130 }
131 if (p->value() == nullptr) {
132 SAFTM_LOG(ERROR) << "null parameter name";
133 return false;
134 }
135 context->SetParameter(name, p->value()->str());
136 }
137 return true;
138 }
139
140 namespace {
141 // Updates |*crc| with the information from |s|. Auxiliary for
142 // ComputeCrc2Checksum.
143 //
144 // The bytes from |info| are also used to update the CRC32 checksum. |info|
145 // should be a brief tag that indicates what |s| represents. The idea is to add
146 // some structure to the information that goes into the CRC32 computation.
147 template <typename T>
UpdateCrc(mobile::Crc32 * crc,const flatbuffers::Vector<T> * s,mobile::StringPiece info)148 void UpdateCrc(mobile::Crc32 *crc, const flatbuffers::Vector<T> *s,
149 mobile::StringPiece info) {
150 crc->Update("|");
151 crc->Update(info.data(), info.size());
152 crc->Update(":");
153 if (s == nullptr) {
154 crc->Update("empty");
155 } else {
156 crc->Update(reinterpret_cast<const char *>(s->data()),
157 s->size() * sizeof(T));
158 }
159 }
160 } // namespace
161
ComputeCrc2Checksum(const Model * model)162 mobile::uint32 ComputeCrc2Checksum(const Model *model) {
163 // Implementation note: originally, I (salcianu@) thought we can just compute
164 // a CRC32 checksum of the model bytes. Unfortunately, the expected checksum
165 // is there too (and because we don't control the flatbuffer format, we can't
166 // "arrange" for it to be placed at the head / tail of those bytes). Instead,
167 // we traverse |model| and feed into the CRC32 computation those parts we are
168 // interested in (which excludes the crc32 field).
169 //
170 // Note: storing the checksum outside the Model would be too disruptive for
171 // the way we currently ship our models.
172 mobile::Crc32 crc;
173 if (model == nullptr) {
174 return crc.Get();
175 }
176 crc.Update("|Parameters:");
177 const auto *parameters = model->parameters();
178 if (parameters != nullptr) {
179 for (const ModelParameter *p : *parameters) {
180 if (p != nullptr) {
181 UpdateCrc(&crc, p->name(), "name");
182 UpdateCrc(&crc, p->value(), "value");
183 }
184 }
185 }
186 crc.Update("|Inputs:");
187 const auto *inputs = model->inputs();
188 if (inputs != nullptr) {
189 for (const ModelInput *input : *inputs) {
190 if (input != nullptr) {
191 UpdateCrc(&crc, input->name(), "name");
192 UpdateCrc(&crc, input->type(), "type");
193 UpdateCrc(&crc, input->sub_type(), "sub-type");
194 UpdateCrc(&crc, input->data(), "data");
195 }
196 }
197 }
198 return crc.Get();
199 }
200
201 } // namespace saft_fbs
202 } // namespace nlp_saft
203