1/*
2 * Copyright 2016-present Facebook, Inc.
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 <folly/experimental/bser/Bser.h>
18
19#include <folly/io/Cursor.h>
20
21using namespace folly;
22using folly::bser::serialization_opts;
23using folly::io::QueueAppender;
24
25namespace folly {
26namespace bser {
27
28const uint8_t kMagic[2] = {0, 1};
29
30static void bserEncode(
31 dynamic const& dyn,
32 QueueAppender& appender,
33 const serialization_opts& opts);
34
35serialization_opts::serialization_opts()
36 : sort_keys(false), growth_increment(8192) {}
37
38static const dynamic* getTemplate(
39 const serialization_opts& opts,
40 dynamic const& dynArray) {
41 if (!opts.templates.hasValue()) {
42 return nullptr;
43 }
44 const auto& templates = opts.templates.value();
45 const auto it = templates.find(&dynArray);
46 if (it == templates.end()) {
47 return nullptr;
48 }
49 return &it->second;
50}
51
52static void bserEncodeInt(int64_t ival, QueueAppender& appender) {
53 /* Return the smallest size int that can store the value */
54 auto size =
55 ((ival == ((int8_t)ival))
56 ? 1
57 : (ival == ((int16_t)ival)) ? 2 : (ival == ((int32_t)ival)) ? 4 : 8);
58
59 switch (size) {
60 case 1:
61 appender.write((int8_t)BserType::Int8);
62 appender.write(int8_t(ival));
63 return;
64 case 2:
65 appender.write((int8_t)BserType::Int16);
66 appender.write(int16_t(ival));
67 return;
68 case 4:
69 appender.write((int8_t)BserType::Int32);
70 appender.write(int32_t(ival));
71 return;
72 case 8:
73 appender.write((int8_t)BserType::Int64);
74 appender.write(ival);
75 return;
76 default:
77 throw std::runtime_error("impossible integer size");
78 }
79}
80
81static void bserEncodeString(folly::StringPiece str, QueueAppender& appender) {
82 appender.write((int8_t)BserType::String);
83 bserEncodeInt(int64_t(str.size()), appender);
84 appender.push((uint8_t*)str.data(), str.size());
85}
86
87static void bserEncodeArraySimple(
88 dynamic const& dyn,
89 QueueAppender& appender,
90 const serialization_opts& opts) {
91 appender.write((int8_t)BserType::Array);
92 bserEncodeInt(int64_t(dyn.size()), appender);
93 for (const auto& ele : dyn) {
94 bserEncode(ele, appender, opts);
95 }
96}
97
98static void bserEncodeArray(
99 dynamic const& dyn,
100 QueueAppender& appender,
101 const serialization_opts& opts) {
102 auto templ = getTemplate(opts, dyn);
103 if (UNLIKELY(templ != nullptr)) {
104 appender.write((int8_t)BserType::Template);
105
106 // Emit the list of property names
107 bserEncodeArraySimple(*templ, appender, opts);
108
109 // The number of objects in the array
110 bserEncodeInt(int64_t(dyn.size()), appender);
111
112 // For each object in the array
113 for (const auto& ele : dyn) {
114 // For each key in the template
115 for (const auto& name : *templ) {
116 if (auto found = ele.get_ptr(name)) {
117 if (found->isNull()) {
118 // Prefer to Skip rather than encode a null value for
119 // compatibility with the other bser implementations
120 appender.write((int8_t)BserType::Skip);
121 } else {
122 bserEncode(*found, appender, opts);
123 }
124 } else {
125 appender.write((int8_t)BserType::Skip);
126 }
127 }
128 }
129 return;
130 }
131
132 bserEncodeArraySimple(dyn, appender, opts);
133}
134
135static void bserEncodeObject(
136 dynamic const& dyn,
137 QueueAppender& appender,
138 const serialization_opts& opts) {
139 appender.write((int8_t)BserType::Object);
140 bserEncodeInt(int64_t(dyn.size()), appender);
141
142 if (opts.sort_keys) {
143 std::vector<std::pair<dynamic, dynamic>> sorted(
144 dyn.items().begin(), dyn.items().end());
145 std::sort(sorted.begin(), sorted.end());
146 for (const auto& item : sorted) {
147 bserEncode(item.first, appender, opts);
148 bserEncode(item.second, appender, opts);
149 }
150 } else {
151 for (const auto& item : dyn.items()) {
152 bserEncode(item.first, appender, opts);
153 bserEncode(item.second, appender, opts);
154 }
155 }
156}
157
158static void bserEncode(
159 dynamic const& dyn,
160 QueueAppender& appender,
161 const serialization_opts& opts) {
162 switch (dyn.type()) {
163 case dynamic::Type::NULLT:
164 appender.write((int8_t)BserType::Null);
165 return;
166 case dynamic::Type::BOOL:
167 appender.write(
168 (int8_t)(dyn.getBool() ? BserType::True : BserType::False));
169 return;
170 case dynamic::Type::DOUBLE: {
171 double dval = dyn.getDouble();
172 appender.write((int8_t)BserType::Real);
173 appender.write(dval);
174 return;
175 }
176 case dynamic::Type::INT64:
177 bserEncodeInt(dyn.getInt(), appender);
178 return;
179 case dynamic::Type::OBJECT:
180 bserEncodeObject(dyn, appender, opts);
181 return;
182 case dynamic::Type::ARRAY:
183 bserEncodeArray(dyn, appender, opts);
184 return;
185 case dynamic::Type::STRING:
186 bserEncodeString(dyn.getString(), appender);
187 return;
188 }
189}
190
191std::unique_ptr<folly::IOBuf> toBserIOBuf(
192 folly::dynamic const& dyn,
193 const serialization_opts& opts) {
194 IOBufQueue q(IOBufQueue::cacheChainLength());
195 uint8_t hdrbuf[sizeof(kMagic) + 1 + sizeof(int64_t)];
196
197 // Reserve some headroom for the overall PDU size; we'll fill this in
198 // after we've serialized the data and know the length
199 auto firstbuf = IOBuf::create(opts.growth_increment);
200 firstbuf->advance(sizeof(hdrbuf));
201 q.append(std::move(firstbuf));
202
203 // encode the value
204 QueueAppender appender(&q, opts.growth_increment);
205 bserEncode(dyn, appender, opts);
206
207 // compute the length
208 auto len = q.chainLength();
209 if (len > uint64_t(std::numeric_limits<int64_t>::max())) {
210 throw std::range_error(folly::to<std::string>(
211 "serialized data size ", len, " is too large to represent as BSER"));
212 }
213
214 // This is a bit verbose, but it computes a header that is appropriate
215 // to the size of the serialized data
216
217 memcpy(hdrbuf, kMagic, sizeof(kMagic));
218 size_t hdrlen = sizeof(kMagic) + 1;
219 auto magicptr = hdrbuf + sizeof(kMagic);
220 auto lenptr = hdrbuf + hdrlen;
221
222 if (len > uint64_t(std::numeric_limits<int32_t>::max())) {
223 *magicptr = (int8_t)BserType::Int64;
224 *(int64_t*)lenptr = (int64_t)len;
225 hdrlen += sizeof(int64_t);
226 } else if (len > uint64_t(std::numeric_limits<int16_t>::max())) {
227 *magicptr = (int8_t)BserType::Int32;
228 *(int32_t*)lenptr = (int32_t)len;
229 hdrlen += sizeof(int32_t);
230 } else if (len > uint64_t(std::numeric_limits<int8_t>::max())) {
231 *magicptr = (int8_t)BserType::Int16;
232 *(int16_t*)lenptr = (int16_t)len;
233 hdrlen += sizeof(int16_t);
234 } else {
235 *magicptr = (int8_t)BserType::Int8;
236 *(int8_t*)lenptr = (int8_t)len;
237 hdrlen += sizeof(int8_t);
238 }
239
240 // and place the data in the headroom
241 q.prepend(hdrbuf, hdrlen);
242
243 return q.move();
244}
245
246fbstring toBser(dynamic const& dyn, const serialization_opts& opts) {
247 auto buf = toBserIOBuf(dyn, opts);
248 return buf->moveToFbString();
249}
250} // namespace bser
251} // namespace folly
252
253/* vim:ts=2:sw=2:et:
254 */
255