ciborium 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +9 -0
- data/LICENSE.txt +21 -0
- data/NOTICE.md +26 -0
- data/README.md +221 -0
- data/Rakefile +46 -0
- data/ext/ciborium/Cargo.lock +339 -0
- data/ext/ciborium/Cargo.toml +26 -0
- data/ext/ciborium/build.rs +4 -0
- data/ext/ciborium/extconf.rb +8 -0
- data/ext/ciborium/src/canonical.rs +60 -0
- data/ext/ciborium/src/cursor.rs +106 -0
- data/ext/ciborium/src/decode.rs +325 -0
- data/ext/ciborium/src/encode.rs +330 -0
- data/ext/ciborium/src/error.rs +53 -0
- data/ext/ciborium/src/lib.rs +153 -0
- data/ext/ciborium/src/scan.rs +223 -0
- data/lib/ciborium/compat/buffer.rb +190 -0
- data/lib/ciborium/compat/errors.rb +30 -0
- data/lib/ciborium/compat/packer.rb +90 -0
- data/lib/ciborium/compat/unpacker.rb +111 -0
- data/lib/ciborium/compat.rb +110 -0
- data/lib/ciborium/decode_options.rb +61 -0
- data/lib/ciborium/decoder.rb +141 -0
- data/lib/ciborium/encode_options.rb +41 -0
- data/lib/ciborium/encoded.rb +10 -0
- data/lib/ciborium/encoder.rb +80 -0
- data/lib/ciborium/errors.rb +25 -0
- data/lib/ciborium/extensions/bignum.rb +35 -0
- data/lib/ciborium/extensions/regexp.rb +41 -0
- data/lib/ciborium/extensions/time.rb +39 -0
- data/lib/ciborium/extensions/uri.rb +29 -0
- data/lib/ciborium/extensions.rb +92 -0
- data/lib/ciborium/native.rb +25 -0
- data/lib/ciborium/replace.rb +105 -0
- data/lib/ciborium/simple.rb +21 -0
- data/lib/ciborium/tagged.rb +18 -0
- data/lib/ciborium/version.rb +6 -0
- data/lib/ciborium.rb +65 -0
- data/sig/generated/ciborium/compat/buffer.rbs +95 -0
- data/sig/generated/ciborium/compat/errors.rbs +23 -0
- data/sig/generated/ciborium/compat/packer.rbs +53 -0
- data/sig/generated/ciborium/compat/unpacker.rbs +59 -0
- data/sig/generated/ciborium/compat.rbs +57 -0
- data/sig/generated/ciborium/decode_options.rbs +41 -0
- data/sig/generated/ciborium/decoder.rbs +85 -0
- data/sig/generated/ciborium/encode_options.rbs +33 -0
- data/sig/generated/ciborium/encoded.rbs +16 -0
- data/sig/generated/ciborium/encoder.rbs +50 -0
- data/sig/generated/ciborium/errors.rbs +32 -0
- data/sig/generated/ciborium/extensions/bignum.rbs +20 -0
- data/sig/generated/ciborium/extensions/regexp.rbs +30 -0
- data/sig/generated/ciborium/extensions/time.rbs +22 -0
- data/sig/generated/ciborium/extensions/uri.rbs +20 -0
- data/sig/generated/ciborium/extensions.rbs +35 -0
- data/sig/generated/ciborium/native.rbs +10 -0
- data/sig/generated/ciborium/replace.rbs +37 -0
- data/sig/generated/ciborium/version.rbs +5 -0
- data/sig/generated/ciborium.rbs +30 -0
- data/sig/manual/ciborium/extensions.rbs +12 -0
- data/sig/manual/ciborium/native.rbs +23 -0
- data/sig/manual/ciborium/tagged.rbs +25 -0
- metadata +123 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/// Serializes Ruby objects into CBOR. Recursion is bounded by `max_depth`, which also stops
|
|
2
|
+
/// cyclic structures.
|
|
3
|
+
pub(crate) struct Encoder<'a> {
|
|
4
|
+
ruby: &'a magnus::Ruby,
|
|
5
|
+
out: Vec<u8>,
|
|
6
|
+
max_depth: usize,
|
|
7
|
+
ctap2: bool,
|
|
8
|
+
fallback: Option<magnus::Value>,
|
|
9
|
+
// Pairs of [class, callable] for allowlisted extensions, consulted in order.
|
|
10
|
+
class_handlers: Option<magnus::RArray>,
|
|
11
|
+
native_module: magnus::RModule,
|
|
12
|
+
tagged_class: magnus::RClass,
|
|
13
|
+
simple_class: magnus::RClass,
|
|
14
|
+
encoded_class: magnus::RClass,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
impl<'a> Encoder<'a> {
|
|
18
|
+
pub(crate) fn new(
|
|
19
|
+
ruby: &'a magnus::Ruby,
|
|
20
|
+
max_depth: usize,
|
|
21
|
+
flags: u32,
|
|
22
|
+
fallback: Option<magnus::Value>,
|
|
23
|
+
class_handlers: Option<magnus::RArray>,
|
|
24
|
+
) -> Result<Self, crate::error::Error> {
|
|
25
|
+
use magnus::Module as _;
|
|
26
|
+
let ciborium = ruby
|
|
27
|
+
.class_object()
|
|
28
|
+
.const_get::<_, magnus::RModule>("Ciborium")?;
|
|
29
|
+
Ok(Self {
|
|
30
|
+
ruby,
|
|
31
|
+
out: Vec::new(),
|
|
32
|
+
max_depth,
|
|
33
|
+
ctap2: flags & crate::decode::FLAG_CTAP2 != 0,
|
|
34
|
+
fallback,
|
|
35
|
+
class_handlers,
|
|
36
|
+
native_module: ciborium.const_get("Native")?,
|
|
37
|
+
tagged_class: ciborium.const_get("Tagged")?,
|
|
38
|
+
simple_class: ciborium.const_get("Simple")?,
|
|
39
|
+
encoded_class: ciborium.const_get("Encoded")?,
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
pub(crate) fn into_bytes(self) -> Vec<u8> {
|
|
44
|
+
self.out
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
fn encoder(&mut self) -> ciborium_ll::Encoder<&mut Vec<u8>> {
|
|
48
|
+
ciborium_ll::Encoder::from(&mut self.out)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
pub(crate) fn write(
|
|
52
|
+
&mut self,
|
|
53
|
+
value: magnus::Value,
|
|
54
|
+
depth: usize,
|
|
55
|
+
) -> Result<(), crate::error::Error> {
|
|
56
|
+
use magnus::value::ReprValue as _;
|
|
57
|
+
if depth > self.max_depth {
|
|
58
|
+
return Err(crate::error::Error::Encode(format!(
|
|
59
|
+
"nesting exceeds max_depth ({})",
|
|
60
|
+
self.max_depth
|
|
61
|
+
)));
|
|
62
|
+
}
|
|
63
|
+
if value.is_nil() {
|
|
64
|
+
return self.push(ciborium_ll::Header::Simple(22));
|
|
65
|
+
}
|
|
66
|
+
if magnus::value::Qtrue::from_value(value).is_some() {
|
|
67
|
+
return self.push(ciborium_ll::Header::Simple(21));
|
|
68
|
+
}
|
|
69
|
+
if magnus::value::Qfalse::from_value(value).is_some() {
|
|
70
|
+
return self.push(ciborium_ll::Header::Simple(20));
|
|
71
|
+
}
|
|
72
|
+
if let Some(integer) = magnus::Integer::from_value(value) {
|
|
73
|
+
if self.integer(integer)? {
|
|
74
|
+
return Ok(());
|
|
75
|
+
}
|
|
76
|
+
if self.fallback.is_none() && self.class_handler(value)?.is_none() {
|
|
77
|
+
return Err(crate::error::Error::Encode(
|
|
78
|
+
"integer is outside -2**64..2**64-1 and the bignum extension is not allowlisted"
|
|
79
|
+
.to_owned(),
|
|
80
|
+
));
|
|
81
|
+
}
|
|
82
|
+
return self.extend(value, depth);
|
|
83
|
+
}
|
|
84
|
+
if let Some(float) = magnus::Float::from_value(value) {
|
|
85
|
+
return self.push(ciborium_ll::Header::Float(float.to_f64()));
|
|
86
|
+
}
|
|
87
|
+
if let Some(string) = magnus::RString::from_value(value) {
|
|
88
|
+
return self.string(string);
|
|
89
|
+
}
|
|
90
|
+
if let Some(symbol) = magnus::Symbol::from_value(value) {
|
|
91
|
+
let name: magnus::RString = symbol.funcall("name", ())?;
|
|
92
|
+
return self.string(name);
|
|
93
|
+
}
|
|
94
|
+
if let Some(array) = magnus::RArray::from_value(value) {
|
|
95
|
+
return self.array(array, depth);
|
|
96
|
+
}
|
|
97
|
+
if let Some(hash) = magnus::RHash::from_value(value) {
|
|
98
|
+
return self.hash(hash, depth);
|
|
99
|
+
}
|
|
100
|
+
if value.is_kind_of(self.tagged_class) {
|
|
101
|
+
return self.tagged(value, depth);
|
|
102
|
+
}
|
|
103
|
+
if value.is_kind_of(self.simple_class) {
|
|
104
|
+
let n: u8 = value.funcall("value", ())?;
|
|
105
|
+
if (24..32).contains(&n) {
|
|
106
|
+
return Err(crate::error::Error::Encode(format!(
|
|
107
|
+
"simple value {n} is reserved"
|
|
108
|
+
)));
|
|
109
|
+
}
|
|
110
|
+
return self.push(ciborium_ll::Header::Simple(n));
|
|
111
|
+
}
|
|
112
|
+
if value.is_kind_of(self.encoded_class) {
|
|
113
|
+
let bytes: magnus::RString = value.funcall("bytes", ())?;
|
|
114
|
+
return self.raw(bytes, depth);
|
|
115
|
+
}
|
|
116
|
+
self.extend(value, depth)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/// Encodes what an extension or the fallback returns in place of `value`.
|
|
120
|
+
fn extend(&mut self, value: magnus::Value, depth: usize) -> Result<(), crate::error::Error> {
|
|
121
|
+
use magnus::value::ReprValue as _;
|
|
122
|
+
if let Some(handler) = self.class_handler(value)? {
|
|
123
|
+
let replacement: magnus::Value = handler.funcall("call", (value,))?;
|
|
124
|
+
return self.write(replacement, depth + 1);
|
|
125
|
+
}
|
|
126
|
+
let Some(fallback) = self.fallback else {
|
|
127
|
+
return Err(crate::error::Error::Encode(format!(
|
|
128
|
+
"cannot encode an instance of {}",
|
|
129
|
+
// SAFETY: the class name is copied into the message before any Ruby code runs.
|
|
130
|
+
unsafe { value.classname() }
|
|
131
|
+
)));
|
|
132
|
+
};
|
|
133
|
+
let replacement: magnus::Value = fallback.funcall("call", (value,))?;
|
|
134
|
+
self.write(replacement, depth + 1)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
fn push(&mut self, header: ciborium_ll::Header) -> Result<(), crate::error::Error> {
|
|
138
|
+
self.encoder()
|
|
139
|
+
.push(header)
|
|
140
|
+
.map_err(|_| crate::error::Error::Encode("failed to write output".to_owned()))
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
fn class_handler(
|
|
144
|
+
&self,
|
|
145
|
+
value: magnus::Value,
|
|
146
|
+
) -> Result<Option<magnus::Value>, crate::error::Error> {
|
|
147
|
+
use magnus::value::ReprValue as _;
|
|
148
|
+
let Some(handlers) = self.class_handlers else {
|
|
149
|
+
return Ok(None);
|
|
150
|
+
};
|
|
151
|
+
for i in 0..handlers.len() {
|
|
152
|
+
let (module, handler): (magnus::Value, magnus::Value) = handlers.entry(i as isize)?;
|
|
153
|
+
let matched = if let Some(class) = magnus::RClass::from_value(module) {
|
|
154
|
+
value.is_kind_of(class)
|
|
155
|
+
} else if let Some(module) = magnus::RModule::from_value(module) {
|
|
156
|
+
value.is_kind_of(module)
|
|
157
|
+
} else {
|
|
158
|
+
false
|
|
159
|
+
};
|
|
160
|
+
if matched {
|
|
161
|
+
return Ok(Some(handler));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
Ok(None)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/// Writes an integer in major type 0 or 1. Returns false when it is outside -2**64..2**64-1.
|
|
168
|
+
fn integer(&mut self, integer: magnus::Integer) -> Result<bool, crate::error::Error> {
|
|
169
|
+
use magnus::value::ReprValue as _;
|
|
170
|
+
if let Ok(n) = integer.to_u64() {
|
|
171
|
+
self.push(ciborium_ll::Header::Positive(n))?;
|
|
172
|
+
return Ok(true);
|
|
173
|
+
}
|
|
174
|
+
if let Ok(n) = integer.to_i64() {
|
|
175
|
+
// A negative n is encoded as the argument -1 - n, which is !n in two's complement.
|
|
176
|
+
self.push(ciborium_ll::Header::Negative(!n as u64))?;
|
|
177
|
+
return Ok(true);
|
|
178
|
+
}
|
|
179
|
+
let complement: magnus::Integer = integer.funcall("~", ())?;
|
|
180
|
+
if let Ok(n) = complement.to_u64() {
|
|
181
|
+
self.push(ciborium_ll::Header::Negative(n))?;
|
|
182
|
+
return Ok(true);
|
|
183
|
+
}
|
|
184
|
+
Ok(false)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
fn string(&mut self, string: magnus::RString) -> Result<(), crate::error::Error> {
|
|
188
|
+
use magnus::encoding::EncodingCapable as _;
|
|
189
|
+
use magnus::value::ReprValue as _;
|
|
190
|
+
let encoding = string.enc_get();
|
|
191
|
+
if encoding == self.ruby.ascii8bit_encindex() {
|
|
192
|
+
// SAFETY: the bytes are copied into the output before any Ruby code runs.
|
|
193
|
+
let bytes = unsafe { string.as_slice() };
|
|
194
|
+
return self
|
|
195
|
+
.encoder()
|
|
196
|
+
.bytes(bytes, None)
|
|
197
|
+
.map_err(|_| crate::error::Error::Encode("failed to write output".to_owned()));
|
|
198
|
+
}
|
|
199
|
+
let utf8 =
|
|
200
|
+
if encoding == self.ruby.utf8_encindex() || encoding == self.ruby.usascii_encindex() {
|
|
201
|
+
string
|
|
202
|
+
} else {
|
|
203
|
+
self.native_module.funcall("transcode", (string,))?
|
|
204
|
+
};
|
|
205
|
+
// SAFETY: the bytes are copied into the output before any Ruby code runs.
|
|
206
|
+
let bytes = unsafe { utf8.as_slice() };
|
|
207
|
+
let Ok(text) = std::str::from_utf8(bytes) else {
|
|
208
|
+
return Err(crate::error::Error::Encode(
|
|
209
|
+
"string is not valid UTF-8".to_owned(),
|
|
210
|
+
));
|
|
211
|
+
};
|
|
212
|
+
self.encoder()
|
|
213
|
+
.text(text, None)
|
|
214
|
+
.map_err(|_| crate::error::Error::Encode("failed to write output".to_owned()))
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
fn array(&mut self, array: magnus::RArray, depth: usize) -> Result<(), crate::error::Error> {
|
|
218
|
+
let len = array.len();
|
|
219
|
+
self.push(ciborium_ll::Header::Array(Some(len)))?;
|
|
220
|
+
// Indexing rather than iterating, because a fallback may change the array meanwhile.
|
|
221
|
+
for i in 0..len {
|
|
222
|
+
let item: magnus::Value = array.entry(i as isize)?;
|
|
223
|
+
self.write(item, depth + 1)?;
|
|
224
|
+
}
|
|
225
|
+
if array.len() != len {
|
|
226
|
+
return Err(crate::error::Error::Encode(
|
|
227
|
+
"array was modified during encoding".to_owned(),
|
|
228
|
+
));
|
|
229
|
+
}
|
|
230
|
+
Ok(())
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
fn hash(&mut self, hash: magnus::RHash, depth: usize) -> Result<(), crate::error::Error> {
|
|
234
|
+
let len = hash.len();
|
|
235
|
+
self.push(ciborium_ll::Header::Map(Some(len)))?;
|
|
236
|
+
let start = self.out.len();
|
|
237
|
+
// Where each pair's value begins and ends, for sorting by key afterwards.
|
|
238
|
+
let mut pairs = Vec::new();
|
|
239
|
+
let mut failure = None;
|
|
240
|
+
hash.foreach(|key: magnus::Value, value: magnus::Value| {
|
|
241
|
+
let key_start = self.out.len();
|
|
242
|
+
let result = self.write(key, depth + 1).and_then(|()| {
|
|
243
|
+
let value_start = self.out.len();
|
|
244
|
+
self.write(value, depth + 1)?;
|
|
245
|
+
Ok((key_start, value_start, self.out.len()))
|
|
246
|
+
});
|
|
247
|
+
match result {
|
|
248
|
+
Ok(pair) => {
|
|
249
|
+
pairs.push(pair);
|
|
250
|
+
Ok(magnus::r_hash::ForEach::Continue)
|
|
251
|
+
}
|
|
252
|
+
Err(error) => {
|
|
253
|
+
failure = Some(error);
|
|
254
|
+
Ok(magnus::r_hash::ForEach::Stop)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
})?;
|
|
258
|
+
if let Some(error) = failure {
|
|
259
|
+
return Err(error);
|
|
260
|
+
}
|
|
261
|
+
if pairs.len() != len || hash.len() != len {
|
|
262
|
+
return Err(crate::error::Error::Encode(
|
|
263
|
+
"hash was modified during encoding".to_owned(),
|
|
264
|
+
));
|
|
265
|
+
}
|
|
266
|
+
if self.ctap2 {
|
|
267
|
+
self.sort_pairs(start, &mut pairs)?;
|
|
268
|
+
}
|
|
269
|
+
Ok(())
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/// Reorders the pairs written since `start` into the ctap2 profile's key order.
|
|
273
|
+
fn sort_pairs(
|
|
274
|
+
&mut self,
|
|
275
|
+
start: usize,
|
|
276
|
+
pairs: &mut [(usize, usize, usize)],
|
|
277
|
+
) -> Result<(), crate::error::Error> {
|
|
278
|
+
let body = self.out.split_off(start);
|
|
279
|
+
let key = |&(from, to, _): &(usize, usize, usize)| &body[from - start..to - start];
|
|
280
|
+
pairs.sort_by(|a, b| crate::canonical::Ctap2::key_order(key(a), key(b)));
|
|
281
|
+
if pairs.windows(2).any(|w| key(&w[0]) == key(&w[1])) {
|
|
282
|
+
return Err(crate::error::Error::Encode(
|
|
283
|
+
"two map keys have the same encoding".to_owned(),
|
|
284
|
+
));
|
|
285
|
+
}
|
|
286
|
+
for &(from, _, to) in pairs.iter() {
|
|
287
|
+
self.out.extend_from_slice(&body[from - start..to - start]);
|
|
288
|
+
}
|
|
289
|
+
Ok(())
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
fn tagged(&mut self, tagged: magnus::Value, depth: usize) -> Result<(), crate::error::Error> {
|
|
293
|
+
use magnus::value::ReprValue as _;
|
|
294
|
+
if self.ctap2 {
|
|
295
|
+
return Err(crate::error::Error::Encode(
|
|
296
|
+
"tags are not allowed by the ctap2 profile".to_owned(),
|
|
297
|
+
));
|
|
298
|
+
}
|
|
299
|
+
let tag: u64 = tagged.funcall("tag", ())?;
|
|
300
|
+
let value: magnus::Value = tagged.funcall("value", ())?;
|
|
301
|
+
self.push(ciborium_ll::Header::Tag(tag))?;
|
|
302
|
+
self.write(value, depth + 1)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/// Splices pre-encoded bytes after checking they hold exactly one well-formed item.
|
|
306
|
+
fn raw(&mut self, bytes: magnus::RString, depth: usize) -> Result<(), crate::error::Error> {
|
|
307
|
+
if self.ctap2 {
|
|
308
|
+
return Err(crate::error::Error::Encode(
|
|
309
|
+
"Encoded cannot be checked against the ctap2 profile".to_owned(),
|
|
310
|
+
));
|
|
311
|
+
}
|
|
312
|
+
crate::with_frozen_bytes(bytes, |input| {
|
|
313
|
+
let end = crate::scan::Scanner::new(input, 0, self.max_depth - depth)
|
|
314
|
+
.scan()
|
|
315
|
+
.map_err(|error| {
|
|
316
|
+
crate::error::Error::Encode(format!(
|
|
317
|
+
"Encoded does not hold a well-formed data item: {}",
|
|
318
|
+
error.message()
|
|
319
|
+
))
|
|
320
|
+
})?;
|
|
321
|
+
if end != input.len() {
|
|
322
|
+
return Err(crate::error::Error::Encode(
|
|
323
|
+
"Encoded holds bytes after its data item".to_owned(),
|
|
324
|
+
));
|
|
325
|
+
}
|
|
326
|
+
self.out.extend_from_slice(input);
|
|
327
|
+
Ok(())
|
|
328
|
+
})
|
|
329
|
+
}
|
|
330
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/// Failures inside the native extension, raised as the Ruby classes defined in lib/ciborium/errors.rb.
|
|
2
|
+
#[derive(Debug)]
|
|
3
|
+
pub(crate) enum Error {
|
|
4
|
+
Malformed(String),
|
|
5
|
+
Incomplete,
|
|
6
|
+
Depth(usize),
|
|
7
|
+
Encode(String),
|
|
8
|
+
Ruby(magnus::Error),
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
impl From<magnus::Error> for Error {
|
|
12
|
+
fn from(error: magnus::Error) -> Self {
|
|
13
|
+
Self::Ruby(error)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
impl Error {
|
|
18
|
+
pub(crate) fn malformed(message: impl Into<String>) -> Self {
|
|
19
|
+
Self::Malformed(message.into())
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
pub(crate) fn message(&self) -> String {
|
|
23
|
+
match self {
|
|
24
|
+
Self::Ruby(error) => error.to_string(),
|
|
25
|
+
Self::Malformed(message) | Self::Encode(message) => message.clone(),
|
|
26
|
+
Self::Incomplete => "unexpected end of input".to_owned(),
|
|
27
|
+
Self::Depth(max) => format!("nesting exceeds max_depth ({max})"),
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
pub(crate) fn into_ruby(self, ruby: &magnus::Ruby) -> magnus::Error {
|
|
32
|
+
let name = match self {
|
|
33
|
+
Self::Ruby(error) => return error,
|
|
34
|
+
Self::Malformed(_) => "MalformedError",
|
|
35
|
+
Self::Incomplete => "IncompleteError",
|
|
36
|
+
Self::Depth(_) => "DepthError",
|
|
37
|
+
Self::Encode(_) => "EncodeError",
|
|
38
|
+
};
|
|
39
|
+
crate::error::Error::ruby_error(ruby, name, self.message())
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
fn ruby_error(ruby: &magnus::Ruby, name: &str, message: String) -> magnus::Error {
|
|
43
|
+
use magnus::Module as _;
|
|
44
|
+
let class = ruby
|
|
45
|
+
.class_object()
|
|
46
|
+
.const_get::<_, magnus::RModule>("Ciborium")
|
|
47
|
+
.and_then(|module| module.const_get::<_, magnus::ExceptionClass>(name));
|
|
48
|
+
match class {
|
|
49
|
+
Ok(class) => magnus::Error::new(class, message),
|
|
50
|
+
Err(lookup_error) => lookup_error,
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
mod canonical;
|
|
2
|
+
mod cursor;
|
|
3
|
+
mod decode;
|
|
4
|
+
mod encode;
|
|
5
|
+
mod error;
|
|
6
|
+
mod scan;
|
|
7
|
+
|
|
8
|
+
/// Runs `f` over the bytes of `string` without letting Ruby code mutate or free them meanwhile.
|
|
9
|
+
fn with_frozen_bytes<T>(string: magnus::RString, f: impl FnOnce(&[u8]) -> T) -> T {
|
|
10
|
+
// A frozen copy shares the buffer copy-on-write, so later mutation of the caller's string
|
|
11
|
+
// leaves these bytes untouched.
|
|
12
|
+
let frozen = magnus::RString::new_frozen(string);
|
|
13
|
+
// SAFETY: frozen cannot be modified, and it stays on the stack, which the GC scans and pins,
|
|
14
|
+
// until f returns.
|
|
15
|
+
let result = f(unsafe { frozen.as_slice() });
|
|
16
|
+
std::hint::black_box(frozen);
|
|
17
|
+
result
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/// Decodes the item starting at `offset` and returns it with the offset just past it.
|
|
21
|
+
fn decode(
|
|
22
|
+
ruby: &magnus::Ruby,
|
|
23
|
+
input: magnus::RString,
|
|
24
|
+
offset: usize,
|
|
25
|
+
max_depth: usize,
|
|
26
|
+
flags: u32,
|
|
27
|
+
handlers: Option<magnus::RHash>,
|
|
28
|
+
) -> Result<(magnus::Value, usize), magnus::Error> {
|
|
29
|
+
crate::with_frozen_bytes(input, |bytes| {
|
|
30
|
+
let mut decoder =
|
|
31
|
+
crate::decode::Decoder::new(ruby, bytes, offset, max_depth, flags, handlers)?;
|
|
32
|
+
let value = decoder.decode()?;
|
|
33
|
+
std::hint::black_box(decoder.pin());
|
|
34
|
+
Ok((value, decoder.pos()))
|
|
35
|
+
})
|
|
36
|
+
.map_err(|error: crate::error::Error| error.into_ruby(ruby))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// Returns the offset just past the item starting at `offset`, or nil when the input ends
|
|
40
|
+
/// before it does. `state` carries the progress of an incomplete item from one call to the next.
|
|
41
|
+
fn scan(
|
|
42
|
+
ruby: &magnus::Ruby,
|
|
43
|
+
input: magnus::RString,
|
|
44
|
+
offset: usize,
|
|
45
|
+
max_depth: usize,
|
|
46
|
+
state: &crate::scan::ScanState,
|
|
47
|
+
) -> Result<Option<usize>, magnus::Error> {
|
|
48
|
+
// SAFETY: scanning runs no Ruby code, so nothing can modify or free the string meanwhile.
|
|
49
|
+
let result =
|
|
50
|
+
crate::scan::Scanner::scan_resuming(unsafe { input.as_slice() }, offset, max_depth, state);
|
|
51
|
+
match result {
|
|
52
|
+
Ok(crate::scan::Scan::Complete(end)) => Ok(Some(end)),
|
|
53
|
+
Ok(crate::scan::Scan::Incomplete) => Ok(None),
|
|
54
|
+
Err(error) => Err(error.into_ruby(ruby)),
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// Reads the head at `offset` as `[major type, argument, end offset]`, or nil when the input
|
|
59
|
+
/// ends before it does. The argument is nil for indefinite lengths and for major type 7.
|
|
60
|
+
fn read_head(
|
|
61
|
+
ruby: &magnus::Ruby,
|
|
62
|
+
input: magnus::RString,
|
|
63
|
+
offset: usize,
|
|
64
|
+
) -> Result<Option<(u8, Option<u64>, usize)>, magnus::Error> {
|
|
65
|
+
// SAFETY: reading a head runs no Ruby code, so nothing can modify or free the string meanwhile.
|
|
66
|
+
let mut cursor = crate::cursor::Cursor::new(unsafe { input.as_slice() }, offset);
|
|
67
|
+
let header = match cursor.head() {
|
|
68
|
+
Ok(header) => header,
|
|
69
|
+
Err(crate::error::Error::Incomplete) => return Ok(None),
|
|
70
|
+
Err(error) => return Err(error.into_ruby(ruby)),
|
|
71
|
+
};
|
|
72
|
+
let (major, argument) = match header {
|
|
73
|
+
ciborium_ll::Header::Positive(n) => (0, Some(n)),
|
|
74
|
+
ciborium_ll::Header::Negative(n) => (1, Some(n)),
|
|
75
|
+
ciborium_ll::Header::Bytes(len) => (2, len.map(|n| n as u64)),
|
|
76
|
+
ciborium_ll::Header::Text(len) => (3, len.map(|n| n as u64)),
|
|
77
|
+
ciborium_ll::Header::Array(len) => (4, len.map(|n| n as u64)),
|
|
78
|
+
ciborium_ll::Header::Map(len) => (5, len.map(|n| n as u64)),
|
|
79
|
+
ciborium_ll::Header::Tag(n) => (6, Some(n)),
|
|
80
|
+
ciborium_ll::Header::Float(_)
|
|
81
|
+
| ciborium_ll::Header::Simple(_)
|
|
82
|
+
| ciborium_ll::Header::Break => (7, None),
|
|
83
|
+
};
|
|
84
|
+
Ok(Some((major, argument, cursor.pos())))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// Encodes a head of major type 4 (array), 5 (map) or 6 (tag) with its argument.
|
|
88
|
+
fn encode_head(
|
|
89
|
+
ruby: &magnus::Ruby,
|
|
90
|
+
major: u8,
|
|
91
|
+
argument: u64,
|
|
92
|
+
) -> Result<magnus::RString, magnus::Error> {
|
|
93
|
+
let length = || {
|
|
94
|
+
usize::try_from(argument)
|
|
95
|
+
.map_err(|_| magnus::Error::new(ruby.exception_range_error(), "length is too large"))
|
|
96
|
+
};
|
|
97
|
+
let header = match major {
|
|
98
|
+
4 => ciborium_ll::Header::Array(Some(length()?)),
|
|
99
|
+
5 => ciborium_ll::Header::Map(Some(length()?)),
|
|
100
|
+
6 => ciborium_ll::Header::Tag(argument),
|
|
101
|
+
_ => {
|
|
102
|
+
return Err(magnus::Error::new(
|
|
103
|
+
ruby.exception_arg_error(),
|
|
104
|
+
format!("unsupported major type {major}"),
|
|
105
|
+
));
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
let mut out = Vec::new();
|
|
109
|
+
ciborium_ll::Encoder::from(&mut out)
|
|
110
|
+
.push(header)
|
|
111
|
+
.map_err(|error| magnus::Error::new(ruby.exception_io_error(), error.to_string()))?;
|
|
112
|
+
Ok(ruby.str_from_slice(&out))
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// Encodes `value` into a binary String.
|
|
116
|
+
fn encode(
|
|
117
|
+
ruby: &magnus::Ruby,
|
|
118
|
+
value: magnus::Value,
|
|
119
|
+
max_depth: usize,
|
|
120
|
+
flags: u32,
|
|
121
|
+
fallback: Option<magnus::Value>,
|
|
122
|
+
class_handlers: Option<magnus::RArray>,
|
|
123
|
+
) -> Result<magnus::RString, magnus::Error> {
|
|
124
|
+
let run = || -> Result<Vec<u8>, crate::error::Error> {
|
|
125
|
+
let mut encoder =
|
|
126
|
+
crate::encode::Encoder::new(ruby, max_depth, flags, fallback, class_handlers)?;
|
|
127
|
+
encoder.write(value, 0)?;
|
|
128
|
+
Ok(encoder.into_bytes())
|
|
129
|
+
};
|
|
130
|
+
match run() {
|
|
131
|
+
Ok(bytes) => Ok(ruby.str_from_slice(&bytes)),
|
|
132
|
+
Err(error) => Err(error.into_ruby(ruby)),
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
#[magnus::init]
|
|
137
|
+
fn init(ruby: &magnus::Ruby) -> Result<(), magnus::Error> {
|
|
138
|
+
use magnus::Module as _;
|
|
139
|
+
use magnus::Object as _;
|
|
140
|
+
let native = ruby.define_module("Ciborium")?.define_module("Native")?;
|
|
141
|
+
native.define_module_function("decode", magnus::function!(crate::decode, 5))?;
|
|
142
|
+
native.define_module_function("encode", magnus::function!(crate::encode, 5))?;
|
|
143
|
+
native.define_module_function("scan", magnus::function!(crate::scan, 4))?;
|
|
144
|
+
let scan_state = native.define_class("ScanState", ruby.class_object())?;
|
|
145
|
+
scan_state.define_singleton_method("new", magnus::function!(crate::scan::ScanState::new, 0))?;
|
|
146
|
+
scan_state.define_method("clear", magnus::method!(crate::scan::ScanState::clear, 0))?;
|
|
147
|
+
native.define_module_function("read_head", magnus::function!(crate::read_head, 2))?;
|
|
148
|
+
native.define_module_function("encode_head", magnus::function!(crate::encode_head, 2))?;
|
|
149
|
+
native.const_set("FLAG_SYMBOLIZE_KEYS", crate::decode::FLAG_SYMBOLIZE_KEYS)?;
|
|
150
|
+
native.const_set("FLAG_LAST_WINS", crate::decode::FLAG_LAST_WINS)?;
|
|
151
|
+
native.const_set("FLAG_CTAP2", crate::decode::FLAG_CTAP2)?;
|
|
152
|
+
Ok(())
|
|
153
|
+
}
|