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,223 @@
|
|
|
1
|
+
enum Pending {
|
|
2
|
+
Items(usize),
|
|
3
|
+
Indefinite { map: bool, items: usize },
|
|
4
|
+
Tag,
|
|
5
|
+
// Chunks of an indefinite-length byte (false) or text (true) string.
|
|
6
|
+
Chunks { text: bool },
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/// Where a scan of an incomplete item stopped: the offset of the first head it could not read,
|
|
10
|
+
/// relative to the start of the item, and the containers open at that point.
|
|
11
|
+
#[derive(Default)]
|
|
12
|
+
struct Progress {
|
|
13
|
+
resume: usize,
|
|
14
|
+
stack: Vec<Pending>,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/// The progress of scanning one item across calls, so a streaming decoder fed in parts scans
|
|
18
|
+
/// each byte once rather than once per part.
|
|
19
|
+
#[magnus::wrap(class = "Ciborium::Native::ScanState", free_immediately)]
|
|
20
|
+
pub(crate) struct ScanState(std::cell::RefCell<Progress>);
|
|
21
|
+
|
|
22
|
+
impl ScanState {
|
|
23
|
+
pub(crate) fn new() -> Self {
|
|
24
|
+
Self(std::cell::RefCell::new(Progress::default()))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// Forgets the progress, for when the next scan starts at another item.
|
|
28
|
+
pub(crate) fn clear(&self) {
|
|
29
|
+
*self.0.borrow_mut() = Progress::default();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
pub(crate) enum Scan {
|
|
34
|
+
Complete(usize),
|
|
35
|
+
Incomplete,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// Finds where the data item at `pos` ends without building Ruby objects, checking the same
|
|
39
|
+
/// well-formedness rules as the decoder except those that need the decoded values.
|
|
40
|
+
pub(crate) struct Scanner<'a> {
|
|
41
|
+
cursor: crate::cursor::Cursor<'a>,
|
|
42
|
+
max_depth: usize,
|
|
43
|
+
stack: Vec<Pending>,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
impl<'a> Scanner<'a> {
|
|
47
|
+
pub(crate) fn new(input: &'a [u8], pos: usize, max_depth: usize) -> Self {
|
|
48
|
+
Self {
|
|
49
|
+
cursor: crate::cursor::Cursor::new(input, pos),
|
|
50
|
+
max_depth,
|
|
51
|
+
stack: Vec::new(),
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// Scans the item at `pos`, continuing from where `state` stopped, and records where this
|
|
56
|
+
/// scan stops when the item is still incomplete.
|
|
57
|
+
pub(crate) fn scan_resuming(
|
|
58
|
+
input: &'a [u8],
|
|
59
|
+
pos: usize,
|
|
60
|
+
max_depth: usize,
|
|
61
|
+
state: &ScanState,
|
|
62
|
+
) -> Result<Scan, crate::error::Error> {
|
|
63
|
+
let mut progress = state.0.borrow_mut();
|
|
64
|
+
let mut scanner = crate::scan::Scanner::new(input, pos, max_depth);
|
|
65
|
+
if pos + progress.resume <= input.len() {
|
|
66
|
+
scanner.cursor = crate::cursor::Cursor::new(input, pos + progress.resume);
|
|
67
|
+
scanner.stack = std::mem::take(&mut progress.stack);
|
|
68
|
+
}
|
|
69
|
+
let result = scanner.run();
|
|
70
|
+
*progress = match result {
|
|
71
|
+
Ok(Scan::Incomplete) => Progress {
|
|
72
|
+
resume: scanner.cursor.pos() - pos,
|
|
73
|
+
stack: scanner.stack,
|
|
74
|
+
},
|
|
75
|
+
_ => Progress::default(),
|
|
76
|
+
};
|
|
77
|
+
result
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
pub(crate) fn scan(&mut self) -> Result<usize, crate::error::Error> {
|
|
81
|
+
match self.run()? {
|
|
82
|
+
Scan::Complete(end) => Ok(end),
|
|
83
|
+
Scan::Incomplete => Err(crate::error::Error::Incomplete),
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// Scans until the item ends or the input does. On running out of input, the cursor and
|
|
88
|
+
/// stack are left at the start of the head that could not be read in full.
|
|
89
|
+
fn run(&mut self) -> Result<Scan, crate::error::Error> {
|
|
90
|
+
loop {
|
|
91
|
+
let start = self.cursor.pos();
|
|
92
|
+
match self.step(start) {
|
|
93
|
+
Ok(true) => return Ok(Scan::Complete(self.cursor.pos())),
|
|
94
|
+
Ok(false) => {}
|
|
95
|
+
Err(crate::error::Error::Incomplete) => {
|
|
96
|
+
self.cursor = crate::cursor::Cursor::new(self.cursor.input(), start);
|
|
97
|
+
return Ok(Scan::Incomplete);
|
|
98
|
+
}
|
|
99
|
+
Err(error) => return Err(error),
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/// Reads one head with its body, if any. Returns true once the top-level item is finished.
|
|
105
|
+
/// Leaves the stack untouched when the input ends before the head and body do.
|
|
106
|
+
fn step(&mut self, start: usize) -> Result<bool, crate::error::Error> {
|
|
107
|
+
let header = self.cursor.head()?;
|
|
108
|
+
if let Some(Pending::Chunks { text }) = self.stack.last() {
|
|
109
|
+
let text = *text;
|
|
110
|
+
match header {
|
|
111
|
+
ciborium_ll::Header::Break => {
|
|
112
|
+
self.stack.pop();
|
|
113
|
+
return Ok(self.complete());
|
|
114
|
+
}
|
|
115
|
+
ciborium_ll::Header::Bytes(Some(len)) if !text => {
|
|
116
|
+
self.cursor.string(Some(len), false)?;
|
|
117
|
+
}
|
|
118
|
+
ciborium_ll::Header::Text(Some(len)) if text => {
|
|
119
|
+
self.cursor.string(Some(len), true)?;
|
|
120
|
+
}
|
|
121
|
+
_ => {
|
|
122
|
+
return Err(crate::error::Error::malformed(format!(
|
|
123
|
+
"invalid chunk in indefinite-length string at offset {start}"
|
|
124
|
+
)));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return Ok(false);
|
|
128
|
+
}
|
|
129
|
+
match header {
|
|
130
|
+
ciborium_ll::Header::Positive(_)
|
|
131
|
+
| ciborium_ll::Header::Negative(_)
|
|
132
|
+
| ciborium_ll::Header::Float(_)
|
|
133
|
+
| ciborium_ll::Header::Simple(_)
|
|
134
|
+
| ciborium_ll::Header::Array(Some(0))
|
|
135
|
+
| ciborium_ll::Header::Map(Some(0)) => {}
|
|
136
|
+
ciborium_ll::Header::Bytes(Some(len)) => {
|
|
137
|
+
self.cursor.string(Some(len), false)?;
|
|
138
|
+
}
|
|
139
|
+
ciborium_ll::Header::Text(Some(len)) => {
|
|
140
|
+
self.cursor.string(Some(len), true)?;
|
|
141
|
+
}
|
|
142
|
+
ciborium_ll::Header::Bytes(None) => {
|
|
143
|
+
self.open(Pending::Chunks { text: false })?;
|
|
144
|
+
return Ok(false);
|
|
145
|
+
}
|
|
146
|
+
ciborium_ll::Header::Text(None) => {
|
|
147
|
+
self.open(Pending::Chunks { text: true })?;
|
|
148
|
+
return Ok(false);
|
|
149
|
+
}
|
|
150
|
+
ciborium_ll::Header::Array(Some(n)) => {
|
|
151
|
+
self.open(Pending::Items(n))?;
|
|
152
|
+
return Ok(false);
|
|
153
|
+
}
|
|
154
|
+
ciborium_ll::Header::Map(Some(n)) => {
|
|
155
|
+
self.open(Pending::Items(n.saturating_mul(2)))?;
|
|
156
|
+
return Ok(false);
|
|
157
|
+
}
|
|
158
|
+
ciborium_ll::Header::Array(None) => {
|
|
159
|
+
self.open(Pending::Indefinite {
|
|
160
|
+
map: false,
|
|
161
|
+
items: 0,
|
|
162
|
+
})?;
|
|
163
|
+
return Ok(false);
|
|
164
|
+
}
|
|
165
|
+
ciborium_ll::Header::Map(None) => {
|
|
166
|
+
self.open(Pending::Indefinite {
|
|
167
|
+
map: true,
|
|
168
|
+
items: 0,
|
|
169
|
+
})?;
|
|
170
|
+
return Ok(false);
|
|
171
|
+
}
|
|
172
|
+
ciborium_ll::Header::Tag(_) => {
|
|
173
|
+
self.open(Pending::Tag)?;
|
|
174
|
+
return Ok(false);
|
|
175
|
+
}
|
|
176
|
+
ciborium_ll::Header::Break => match self.stack.last() {
|
|
177
|
+
Some(Pending::Indefinite { map, items }) if !map || items % 2 == 0 => {
|
|
178
|
+
self.stack.pop();
|
|
179
|
+
}
|
|
180
|
+
_ => {
|
|
181
|
+
return Err(crate::error::Error::malformed(format!(
|
|
182
|
+
"unexpected break at offset {start}"
|
|
183
|
+
)));
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
}
|
|
187
|
+
Ok(self.complete())
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
fn open(&mut self, pending: Pending) -> Result<(), crate::error::Error> {
|
|
191
|
+
// An indefinite-length string adds no nesting the decoder counts, and holds only chunks,
|
|
192
|
+
// so it is always the innermost entry.
|
|
193
|
+
if !matches!(pending, Pending::Chunks { .. }) && self.stack.len() >= self.max_depth {
|
|
194
|
+
return Err(crate::error::Error::Depth(self.max_depth));
|
|
195
|
+
}
|
|
196
|
+
self.stack.push(pending);
|
|
197
|
+
Ok(())
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/// Counts a finished item against its enclosing containers. Returns true once the top-level
|
|
201
|
+
/// item is finished.
|
|
202
|
+
fn complete(&mut self) -> bool {
|
|
203
|
+
loop {
|
|
204
|
+
match self.stack.last_mut() {
|
|
205
|
+
None => return true,
|
|
206
|
+
Some(Pending::Items(n)) => {
|
|
207
|
+
*n -= 1;
|
|
208
|
+
if *n > 0 {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
Some(Pending::Indefinite { items, .. }) => {
|
|
213
|
+
*items += 1;
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
Some(Pending::Tag) => {}
|
|
217
|
+
// Popped before its string is counted, so never the innermost entry here.
|
|
218
|
+
Some(Pending::Chunks { .. }) => return false,
|
|
219
|
+
}
|
|
220
|
+
self.stack.pop();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
module Ciborium
|
|
5
|
+
module Compat
|
|
6
|
+
# A byte queue in front of an optional IO, as CBOR::Buffer of the cbor gem: reads refill from
|
|
7
|
+
# the IO when the queue runs dry, and writes go out to it once the queue fills.
|
|
8
|
+
class Buffer
|
|
9
|
+
DEFAULT_IO_BUFFER_SIZE = 32 * 1024 #: Integer
|
|
10
|
+
|
|
11
|
+
# @rbs @io: untyped
|
|
12
|
+
# @rbs @io_buffer_size: Integer
|
|
13
|
+
# @rbs @data: String
|
|
14
|
+
# @rbs @pos: Integer
|
|
15
|
+
|
|
16
|
+
attr_reader :io #: untyped
|
|
17
|
+
|
|
18
|
+
# The cbor gem's read_reference_threshold and write_reference_threshold tune its chunk
|
|
19
|
+
# sharing, which this buffer does not do, so they are accepted and ignored.
|
|
20
|
+
#
|
|
21
|
+
#: (?untyped io, ?Hash[Symbol, untyped]? options) -> void
|
|
22
|
+
def initialize(io = nil, options = nil)
|
|
23
|
+
if options.nil? && io.is_a?(Hash)
|
|
24
|
+
options = io
|
|
25
|
+
io = nil
|
|
26
|
+
end
|
|
27
|
+
@io = io
|
|
28
|
+
@io_buffer_size = Integer(options ? options.fetch(:io_buffer_size, DEFAULT_IO_BUFFER_SIZE) : DEFAULT_IO_BUFFER_SIZE)
|
|
29
|
+
@data = "".b
|
|
30
|
+
@pos = 0
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
#: () -> nil
|
|
34
|
+
def clear
|
|
35
|
+
@data = "".b
|
|
36
|
+
@pos = 0
|
|
37
|
+
nil
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
#: () -> Integer
|
|
41
|
+
def size
|
|
42
|
+
@data.bytesize - @pos
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
#: () -> bool
|
|
46
|
+
def empty?
|
|
47
|
+
size.zero?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
#: (String data) -> Integer
|
|
51
|
+
def write(data)
|
|
52
|
+
@data << data.b
|
|
53
|
+
flush if @io && size >= @io_buffer_size
|
|
54
|
+
data.bytesize
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
#: (String data) -> self
|
|
58
|
+
def <<(data)
|
|
59
|
+
write(data)
|
|
60
|
+
self
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Consumes up to n bytes, or everything without n, refilling from the IO until it ends.
|
|
64
|
+
# Returns nil when n is positive and nothing is left.
|
|
65
|
+
#
|
|
66
|
+
#: (?Integer? n, ?String? out) -> String?
|
|
67
|
+
def read(n = nil, out = nil)
|
|
68
|
+
return read_all(nil, out) if n.nil?
|
|
69
|
+
return replace(out, "".b) if n.zero?
|
|
70
|
+
|
|
71
|
+
fill(n)
|
|
72
|
+
return if empty?
|
|
73
|
+
|
|
74
|
+
replace(out, take(n))
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Consumes exactly n bytes, or everything without n. Raises EOFError, consuming nothing,
|
|
78
|
+
# when fewer than n bytes are left.
|
|
79
|
+
#
|
|
80
|
+
#: (?Integer? n, ?String? out) -> String
|
|
81
|
+
def read_all(n = nil, out = nil)
|
|
82
|
+
if n.nil?
|
|
83
|
+
fill(nil)
|
|
84
|
+
return replace(out, take(size))
|
|
85
|
+
end
|
|
86
|
+
return replace(out, "".b) if n.zero?
|
|
87
|
+
|
|
88
|
+
fill(n)
|
|
89
|
+
raise ::EOFError, "end of buffer reached" if size < n
|
|
90
|
+
|
|
91
|
+
replace(out, take(n))
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Consumes up to n bytes and returns how many it consumed.
|
|
95
|
+
#
|
|
96
|
+
#: (Integer n) -> Integer
|
|
97
|
+
def skip(n)
|
|
98
|
+
fill(n)
|
|
99
|
+
take(n).bytesize
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Consumes exactly n bytes, or raises EOFError consuming nothing.
|
|
103
|
+
#
|
|
104
|
+
#: (Integer n) -> self
|
|
105
|
+
def skip_all(n)
|
|
106
|
+
fill(n)
|
|
107
|
+
raise ::EOFError, "end of buffer reached" if size < n
|
|
108
|
+
|
|
109
|
+
take(n)
|
|
110
|
+
self
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
#: () -> String
|
|
114
|
+
def to_str
|
|
115
|
+
@data.byteslice(@pos..) || "".b
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
alias_method :to_s, :to_str
|
|
119
|
+
|
|
120
|
+
#: () -> Array[String]
|
|
121
|
+
def to_a
|
|
122
|
+
[to_str]
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Writes the queued bytes to the IO, if there is one.
|
|
126
|
+
#
|
|
127
|
+
#: () -> self
|
|
128
|
+
def flush
|
|
129
|
+
write_to(@io) if @io
|
|
130
|
+
self
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
#: () -> nil
|
|
134
|
+
def close
|
|
135
|
+
@io&.close
|
|
136
|
+
nil
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Writes the queued bytes to io and consumes them.
|
|
140
|
+
#
|
|
141
|
+
#: (untyped io) -> Integer
|
|
142
|
+
def write_to(io)
|
|
143
|
+
data = take(size)
|
|
144
|
+
io.write(data)
|
|
145
|
+
data.bytesize
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
#: (Integer? n) -> void
|
|
149
|
+
private def fill(n)
|
|
150
|
+
return unless @io
|
|
151
|
+
|
|
152
|
+
while n.nil? || size < n
|
|
153
|
+
chunk = read_io
|
|
154
|
+
break unless chunk
|
|
155
|
+
|
|
156
|
+
@data << chunk.b
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
#: () -> String?
|
|
161
|
+
private def read_io
|
|
162
|
+
if @io.respond_to?(:readpartial)
|
|
163
|
+
@io.readpartial(@io_buffer_size)
|
|
164
|
+
else
|
|
165
|
+
@io.read(@io_buffer_size)
|
|
166
|
+
end
|
|
167
|
+
rescue ::EOFError
|
|
168
|
+
nil
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
#: (Integer n) -> String
|
|
172
|
+
private def take(n)
|
|
173
|
+
data = @data.byteslice(@pos, n) || "".b
|
|
174
|
+
@pos += data.bytesize
|
|
175
|
+
if @pos == @data.bytesize
|
|
176
|
+
@data = "".b
|
|
177
|
+
@pos = 0
|
|
178
|
+
end
|
|
179
|
+
data
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
#: (String? out, String data) -> String
|
|
183
|
+
private def replace(out, data)
|
|
184
|
+
return data unless out
|
|
185
|
+
|
|
186
|
+
out.replace(data)
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
module Ciborium
|
|
5
|
+
module Compat
|
|
6
|
+
class UnpackError < StandardError; end
|
|
7
|
+
|
|
8
|
+
class MalformedFormatError < UnpackError; end
|
|
9
|
+
|
|
10
|
+
class StackError < UnpackError; end
|
|
11
|
+
|
|
12
|
+
# Raised by Unpacker#read_array_header and #read_map_header on an item of another type.
|
|
13
|
+
class TypeError < StandardError; end
|
|
14
|
+
|
|
15
|
+
# Raises the cbor gem's exception for a Ciborium decoding failure, keeping it as the cause.
|
|
16
|
+
#
|
|
17
|
+
#: [T] () { () -> T } -> T
|
|
18
|
+
def self.translate_errors
|
|
19
|
+
yield
|
|
20
|
+
rescue IncompleteError
|
|
21
|
+
raise ::EOFError, "end of buffer reached"
|
|
22
|
+
rescue DepthError
|
|
23
|
+
raise StackError, "nesting too deep"
|
|
24
|
+
rescue UnexpectedTypeError
|
|
25
|
+
raise TypeError, "unexpected type"
|
|
26
|
+
rescue DecodeError
|
|
27
|
+
raise MalformedFormatError, "malformed CBOR"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
module Ciborium
|
|
5
|
+
module Compat
|
|
6
|
+
# CBOR::Packer of the cbor gem, writing through Ciborium::Encoder.
|
|
7
|
+
class Packer
|
|
8
|
+
# @rbs @buffer: Buffer
|
|
9
|
+
# @rbs @encoder: Ciborium::Encoder
|
|
10
|
+
|
|
11
|
+
attr_reader :buffer #: Buffer
|
|
12
|
+
|
|
13
|
+
#: (?untyped io, ?Hash[Symbol, untyped]? options) -> void
|
|
14
|
+
def initialize(io = nil, options = nil)
|
|
15
|
+
@buffer = Buffer.new(io, options)
|
|
16
|
+
@encoder = Ciborium::Encoder.new(**Compat.encode_options)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
#: (untyped object) -> self
|
|
20
|
+
def write(object)
|
|
21
|
+
@encoder.write(object)
|
|
22
|
+
drain
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
alias_method :pack, :write
|
|
26
|
+
|
|
27
|
+
#: () -> self
|
|
28
|
+
def write_nil
|
|
29
|
+
write(nil)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
#: (Integer length) -> self
|
|
33
|
+
def write_array_header(length)
|
|
34
|
+
@encoder.write_array_header(length)
|
|
35
|
+
drain
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
#: (Integer length) -> self
|
|
39
|
+
def write_map_header(length)
|
|
40
|
+
@encoder.write_map_header(length)
|
|
41
|
+
drain
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
#: () -> self
|
|
45
|
+
def flush
|
|
46
|
+
@buffer.flush
|
|
47
|
+
self
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
#: () -> nil
|
|
51
|
+
def clear
|
|
52
|
+
@buffer.clear
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
#: () -> Integer
|
|
56
|
+
def size
|
|
57
|
+
@buffer.size
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
#: () -> bool
|
|
61
|
+
def empty?
|
|
62
|
+
@buffer.empty?
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
#: (untyped io) -> Integer
|
|
66
|
+
def write_to(io)
|
|
67
|
+
@buffer.write_to(io)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
#: () -> String
|
|
71
|
+
def to_str
|
|
72
|
+
@buffer.to_str
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
alias_method :to_s, :to_str
|
|
76
|
+
|
|
77
|
+
#: () -> Array[String]
|
|
78
|
+
def to_a
|
|
79
|
+
@buffer.to_a
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
#: () -> self
|
|
83
|
+
private def drain
|
|
84
|
+
@buffer << @encoder.bytes
|
|
85
|
+
@encoder.clear
|
|
86
|
+
self
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
module Ciborium
|
|
5
|
+
module Compat
|
|
6
|
+
# CBOR::Unpacker of the cbor gem, reading through Ciborium::Decoder. Input passes through
|
|
7
|
+
# buffer, which also reads from io when more is needed.
|
|
8
|
+
class Unpacker
|
|
9
|
+
# @rbs @buffer: Buffer
|
|
10
|
+
# @rbs @decoder: Ciborium::Decoder
|
|
11
|
+
|
|
12
|
+
attr_reader :buffer #: Buffer
|
|
13
|
+
|
|
14
|
+
# options takes symbolize_keys, and the Buffer options.
|
|
15
|
+
#
|
|
16
|
+
#: (?untyped io, ?Hash[Symbol, untyped]? options) -> void
|
|
17
|
+
def initialize(io = nil, options = nil)
|
|
18
|
+
if options.nil? && io.is_a?(Hash)
|
|
19
|
+
options = io
|
|
20
|
+
io = nil
|
|
21
|
+
end
|
|
22
|
+
@buffer = Buffer.new(io, options)
|
|
23
|
+
@decoder = Ciborium::Decoder.new(**Compat.decode_options, symbolize_keys: !!options&.[](:symbolize_keys))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
#: (String data) -> self
|
|
27
|
+
def feed(data)
|
|
28
|
+
@buffer << data
|
|
29
|
+
self
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
#: () -> untyped
|
|
33
|
+
def read
|
|
34
|
+
with_input { @decoder.read }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
alias_method :unpack, :read
|
|
38
|
+
|
|
39
|
+
#: () -> nil
|
|
40
|
+
def skip
|
|
41
|
+
with_input { @decoder.skip }
|
|
42
|
+
nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Whether the next item is null. Consumes nothing.
|
|
46
|
+
#
|
|
47
|
+
#: () -> bool
|
|
48
|
+
def skip_nil
|
|
49
|
+
with_input { @decoder.next_nil? }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
#: () -> Integer
|
|
53
|
+
def read_array_header
|
|
54
|
+
with_input { @decoder.read_array_header }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
#: () -> Integer
|
|
58
|
+
def read_map_header
|
|
59
|
+
with_input { @decoder.read_map_header }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Yields each item until the input runs out, leaving a partial item buffered.
|
|
63
|
+
#
|
|
64
|
+
#: () { (untyped) -> void } -> nil
|
|
65
|
+
#: () -> Enumerator[untyped, nil]
|
|
66
|
+
def each
|
|
67
|
+
return enum_for(:each) unless block_given?
|
|
68
|
+
|
|
69
|
+
loop do
|
|
70
|
+
item = begin
|
|
71
|
+
read
|
|
72
|
+
rescue ::EOFError
|
|
73
|
+
break
|
|
74
|
+
end
|
|
75
|
+
yield item
|
|
76
|
+
end
|
|
77
|
+
nil
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
#: (String data) { (untyped) -> void } -> nil
|
|
81
|
+
def feed_each(data, &block)
|
|
82
|
+
feed(data)
|
|
83
|
+
each(&block)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
#: () -> nil
|
|
87
|
+
def reset
|
|
88
|
+
@buffer.clear
|
|
89
|
+
@decoder.reset
|
|
90
|
+
nil
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Runs the block on the decoder, moving more input into it until the block stops raising
|
|
94
|
+
# IncompleteError or the input ends.
|
|
95
|
+
#
|
|
96
|
+
#: [T] () { () -> T } -> T
|
|
97
|
+
private def with_input
|
|
98
|
+
Compat.translate_errors do
|
|
99
|
+
@decoder.feed(@buffer.read_all(@buffer.size)) unless @buffer.empty?
|
|
100
|
+
yield
|
|
101
|
+
rescue IncompleteError
|
|
102
|
+
chunk = @buffer.read(Buffer::DEFAULT_IO_BUFFER_SIZE)
|
|
103
|
+
raise unless chunk
|
|
104
|
+
|
|
105
|
+
@decoder.feed(chunk)
|
|
106
|
+
retry
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|