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 | |
21 | using namespace folly; |
22 | using folly::bser::serialization_opts; |
23 | using folly::io::QueueAppender; |
24 | |
25 | namespace folly { |
26 | namespace bser { |
27 | |
28 | const uint8_t kMagic[2] = {0, 1}; |
29 | |
30 | static void bserEncode( |
31 | dynamic const& dyn, |
32 | QueueAppender& appender, |
33 | const serialization_opts& opts); |
34 | |
35 | serialization_opts::serialization_opts() |
36 | : sort_keys(false), growth_increment(8192) {} |
37 | |
38 | static 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 | |
52 | static 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 | |
81 | static 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 | |
87 | static 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 | |
98 | static 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 | |
135 | static 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 | |
158 | static 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 | |
191 | std::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 | |
246 | fbstring 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 | |