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