fixup decode logic for fragmented IOBufs
[folly.git] / folly / experimental / bser / Load.cpp
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
22 using namespace folly;
23 using folly::io::Cursor;
24
25 namespace folly {
26 namespace bser {
27 static dynamic parseBser(Cursor& curs);
28
29 template <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
38 static 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
55 static 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   str.reserve(size_t(len));
63
64   // peekBytes will advance over any "empty" IOBuf elements until
65   // it reaches the next one with data, so do that to obtain the
66   // true remaining length.
67   size_t available = curs.peekBytes().size();
68   while (available < (size_t)len) {
69     if (available == 0) {
70       // Saw this case when we decodeHeader was returning the incorrect length
71       // and we were splitting off too few bytes from the IOBufQueue
72       throwDecodeError(
73           curs,
74           "no data available while decoding a string, header was "
75           "not decoded properly");
76     }
77     str.append(reinterpret_cast<const char*>(curs.data()), available);
78     curs.skipAtMost(available);
79     len -= available;
80     available = curs.peekBytes().size();
81   }
82
83   str.append(reinterpret_cast<const char*>(curs.data()), size_t(len));
84   curs.skipAtMost(size_t(len));
85   return str;
86 }
87
88 static dynamic decodeArray(Cursor& curs) {
89   dynamic arr = dynamic::array();
90   auto size = decodeInt(curs);
91   while (size-- > 0) {
92     arr.push_back(parseBser(curs));
93   }
94   return arr;
95 }
96
97 static dynamic decodeObject(Cursor& curs) {
98   dynamic obj = dynamic::object;
99   auto size = decodeInt(curs);
100   while (size-- > 0) {
101     if ((BserType)curs.read<int8_t>() != BserType::String) {
102       throwDecodeError(curs, "expected String");
103     }
104     auto key = decodeString(curs);
105     obj[key] = parseBser(curs);
106   }
107   return obj;
108 }
109
110 static dynamic decodeTemplate(Cursor& curs) {
111   dynamic arr = folly::dynamic::array;
112
113   // List of property names
114   if ((BserType)curs.read<int8_t>() != BserType::Array) {
115     throw std::runtime_error("Expected array encoding for property names");
116   }
117   auto names = decodeArray(curs);
118
119   auto size = decodeInt(curs);
120
121   while (size-- > 0) {
122     dynamic obj = dynamic::object;
123
124     for (auto& name : names) {
125       auto bytes = curs.peekBytes();
126       if ((BserType)bytes.at(0) == BserType::Skip) {
127         obj[name.getString()] = nullptr;
128         curs.skipAtMost(1);
129         continue;
130       }
131
132       obj[name.getString()] = parseBser(curs);
133     }
134
135     arr.push_back(std::move(obj));
136   }
137
138   return arr;
139 }
140
141 static dynamic parseBser(Cursor& curs) {
142   switch ((BserType)curs.read<int8_t>()) {
143     case BserType::Int8:
144       return curs.read<int8_t>();
145     case BserType::Int16:
146       return curs.read<int16_t>();
147     case BserType::Int32:
148       return curs.read<int32_t>();
149     case BserType::Int64:
150       return curs.read<int64_t>();
151     case BserType::Real: {
152       double dval;
153       curs.pull((void*)&dval, sizeof(dval));
154       return dval;
155     }
156     case BserType::Null:
157       return nullptr;
158     case BserType::True:
159       return (bool)true;
160     case BserType::False:
161       return (bool)false;
162     case BserType::String:
163       return decodeString(curs);
164     case BserType::Array:
165       return decodeArray(curs);
166     case BserType::Object:
167       return decodeObject(curs);
168     case BserType::Template:
169       return decodeTemplate(curs);
170     case BserType::Skip:
171       throw std::runtime_error(
172           "Skip not valid at this location in the bser stream");
173     default:
174       throw std::runtime_error("invalid bser encoding");
175   }
176 }
177
178 static size_t decodeHeader(Cursor& curs) {
179   char header[sizeof(kMagic)];
180   curs.pull(header, sizeof(header));
181   if (memcmp(header, kMagic, sizeof(kMagic))) {
182     throw std::runtime_error("invalid BSER magic header");
183   }
184
185   auto enc = (BserType)curs.peekBytes().at(0);
186   size_t int_size;
187   switch (enc) {
188     case BserType::Int8:
189       int_size = 1;
190       break;
191     case BserType::Int16:
192       int_size = 2;
193       break;
194     case BserType::Int32:
195       int_size = 4;
196       break;
197     case BserType::Int64:
198       int_size = 8;
199       break;
200     default:
201       int_size = 0;
202   }
203
204   return int_size + 3 /* magic + int type */ + decodeInt(curs);
205 }
206
207 size_t decodePduLength(const folly::IOBuf* buf) {
208   Cursor curs(buf);
209   return decodeHeader(curs);
210 }
211
212 folly::dynamic parseBser(const IOBuf* buf) {
213   Cursor curs(buf);
214
215   decodeHeader(curs);
216   return parseBser(curs);
217 }
218
219 folly::dynamic parseBser(ByteRange str) {
220   auto buf = IOBuf::wrapBuffer(str.data(), str.size());
221   return parseBser(&*buf);
222 }
223
224 folly::dynamic parseBser(StringPiece str) {
225   return parseBser(ByteRange((uint8_t*)str.data(), str.size()));
226 }
227 } // namespace bser
228 } // namespace folly
229
230 /* vim:ts=2:sw=2:et:
231  */