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/String.h>
20#include <folly/io/Cursor.h>
21
22using namespace folly;
23using folly::io::Cursor;
24
25namespace folly {
26namespace bser {
27static dynamic parseBser(Cursor& curs);
28
29template <typename... ARGS>
30[[noreturn]] static void throwDecodeError(Cursor& curs, ARGS&&... args) {
31 throw BserDecodeError(folly::to<std::string>(
32 std::forward<ARGS>(args)...,
33 " with ",
34 curs.length(),
35 " bytes remaining in cursor"));
36}
37
38static int64_t decodeInt(Cursor& curs) {
39 auto enc = (BserType)curs.read<int8_t>();
40 switch (enc) {
41 case BserType::Int8:
42 return curs.read<int8_t>();
43 case BserType::Int16:
44 return curs.read<int16_t>();
45 case BserType::Int32:
46 return curs.read<int32_t>();
47 case BserType::Int64:
48 return curs.read<int64_t>();
49 default:
50 throwDecodeError(
51 curs, "invalid integer encoding detected (", (int8_t)enc, ")");
52 }
53}
54
55static std::string decodeString(Cursor& curs) {
56 auto len = decodeInt(curs);
57 std::string str;
58
59 if (len < 0) {
60 throw std::range_error("string length must not be negative");
61 }
62
63 // We could use Cursor::readFixedString() here, but we'd like
64 // to throw our own exception with some increased diagnostics.
65 str.resize(len);
66
67 // The start of the string data, mutable.
68 auto* dest = &str[0];
69
70 auto pulled = curs.pullAtMost(dest, len);
71 if (pulled != size_t(len)) {
72 // Saw this case when decodeHeader was returning the incorrect length
73 // and we were splitting off too few bytes from the IOBufQueue
74 throwDecodeError(
75 curs,
76 "no data available while decoding a string, header was "
77 "not decoded properly");
78 }
79
80 return str;
81}
82
83static dynamic decodeArray(Cursor& curs) {
84 dynamic arr = dynamic::array();
85 auto size = decodeInt(curs);
86 while (size-- > 0) {
87 arr.push_back(parseBser(curs));
88 }
89 return arr;
90}
91
92static dynamic decodeObject(Cursor& curs) {
93 dynamic obj = dynamic::object;
94 auto size = decodeInt(curs);
95 while (size-- > 0) {
96 if ((BserType)curs.read<int8_t>() != BserType::String) {
97 throwDecodeError(curs, "expected String");
98 }
99 auto key = decodeString(curs);
100 obj[key] = parseBser(curs);
101 }
102 return obj;
103}
104
105static dynamic decodeTemplate(Cursor& curs) {
106 dynamic arr = folly::dynamic::array;
107
108 // List of property names
109 if ((BserType)curs.read<int8_t>() != BserType::Array) {
110 throw std::runtime_error("Expected array encoding for property names");
111 }
112 auto names = decodeArray(curs);
113
114 auto size = decodeInt(curs);
115
116 while (size-- > 0) {
117 dynamic obj = dynamic::object;
118
119 for (auto& name : names) {
120 auto bytes = curs.peekBytes();
121 if ((BserType)bytes.at(0) == BserType::Skip) {
122 obj[name.getString()] = nullptr;
123 curs.skipAtMost(1);
124 continue;
125 }
126
127 obj[name.getString()] = parseBser(curs);
128 }
129
130 arr.push_back(std::move(obj));
131 }
132
133 return arr;
134}
135
136static dynamic parseBser(Cursor& curs) {
137 switch ((BserType)curs.read<int8_t>()) {
138 case BserType::Int8:
139 return curs.read<int8_t>();
140 case BserType::Int16:
141 return curs.read<int16_t>();
142 case BserType::Int32:
143 return curs.read<int32_t>();
144 case BserType::Int64:
145 return curs.read<int64_t>();
146 case BserType::Real: {
147 double dval;
148 curs.pull((void*)&dval, sizeof(dval));
149 return dval;
150 }
151 case BserType::Null:
152 return nullptr;
153 case BserType::True:
154 return (bool)true;
155 case BserType::False:
156 return (bool)false;
157 case BserType::String:
158 return decodeString(curs);
159 case BserType::Array:
160 return decodeArray(curs);
161 case BserType::Object:
162 return decodeObject(curs);
163 case BserType::Template:
164 return decodeTemplate(curs);
165 case BserType::Skip:
166 throw std::runtime_error(
167 "Skip not valid at this location in the bser stream");
168 default:
169 throw std::runtime_error("invalid bser encoding");
170 }
171}
172
173static size_t decodeHeader(Cursor& curs) {
174 char header[sizeof(kMagic)];
175 curs.pull(header, sizeof(header));
176 if (memcmp(header, kMagic, sizeof(kMagic))) {
177 throw std::runtime_error("invalid BSER magic header");
178 }
179
180 auto enc = (BserType)curs.peekBytes().at(0);
181 size_t int_size;
182 switch (enc) {
183 case BserType::Int8:
184 int_size = 1;
185 break;
186 case BserType::Int16:
187 int_size = 2;
188 break;
189 case BserType::Int32:
190 int_size = 4;
191 break;
192 case BserType::Int64:
193 int_size = 8;
194 break;
195 default:
196 int_size = 0;
197 }
198
199 return int_size + 3 /* magic + int type */ + decodeInt(curs);
200}
201
202size_t decodePduLength(const folly::IOBuf* buf) {
203 Cursor curs(buf);
204 return decodeHeader(curs);
205}
206
207folly::dynamic parseBser(const IOBuf* buf) {
208 Cursor curs(buf);
209
210 decodeHeader(curs);
211 return parseBser(curs);
212}
213
214folly::dynamic parseBser(ByteRange str) {
215 auto buf = IOBuf::wrapBuffer(str.data(), str.size());
216 return parseBser(&*buf);
217}
218
219folly::dynamic parseBser(StringPiece str) {
220 return parseBser(ByteRange((uint8_t*)str.data(), str.size()));
221}
222} // namespace bser
223} // namespace folly
224
225/* vim:ts=2:sw=2:et:
226 */
227