zrip 0.1.2 → 0.2.1
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 +4 -4
- data/CHANGELOG.md +33 -0
- data/Cargo.lock +29 -64
- data/Cargo.toml +0 -1
- data/README.md +14 -41
- data/ext/zrip/Cargo.toml +5 -5
- data/ext/zrip/build.rs +23 -0
- data/ext/zrip/src/lib.rs +670 -340
- data/ext/zrip/src/rb.rs +667 -0
- data/lib/zrip/block_codec.rb +49 -0
- data/lib/zrip/dict_trainer.rb +32 -0
- data/lib/zrip/dictionary.rb +10 -0
- data/lib/zrip/frame_codec.rb +58 -0
- data/lib/zrip/version.rb +1 -1
- data/lib/zrip.rb +14 -0
- metadata +14 -5
data/ext/zrip/src/lib.rs
CHANGED
|
@@ -1,44 +1,89 @@
|
|
|
1
|
-
|
|
2
|
-
exception::ExceptionClass, function, method, prelude::*, r_string::RString, value::Opaque,
|
|
3
|
-
Error, Ruby,
|
|
4
|
-
};
|
|
5
|
-
use std::cell::RefCell;
|
|
6
|
-
use std::sync::{Mutex, OnceLock};
|
|
1
|
+
mod rb;
|
|
7
2
|
|
|
3
|
+
use std::ffi::c_void;
|
|
4
|
+
use std::panic::{catch_unwind, AssertUnwindSafe};
|
|
5
|
+
use std::sync::{Mutex, OnceLock, TryLockError};
|
|
6
|
+
|
|
7
|
+
use rb_sys::{rb_data_type_struct__bindgen_ty_1, rb_data_type_t, size_t, VALUE};
|
|
8
8
|
use zstd::dict::fastcover::FastCoverParams;
|
|
9
9
|
use zstd::{CompressContext, DecompressContext, Dictionary};
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
static
|
|
14
|
-
static
|
|
11
|
+
use crate::rb::{RbResult, RubyErr};
|
|
12
|
+
|
|
13
|
+
static DECOMPRESS_ERROR: OnceLock<GlobalValue> = OnceLock::new();
|
|
14
|
+
static COMPRESS_ERROR: OnceLock<GlobalValue> = OnceLock::new();
|
|
15
|
+
static MISSING_CONTENT_SIZE_ERROR: OnceLock<GlobalValue> = OnceLock::new();
|
|
16
|
+
static OUTPUT_SIZE_LIMIT_ERROR: OnceLock<GlobalValue> = OnceLock::new();
|
|
17
|
+
|
|
18
|
+
const GVL_COMPRESS_THRESHOLD: usize = 64 * 1024;
|
|
19
|
+
const GVL_FRAME_DECOMPRESS_THRESHOLD: usize = 64 * 1024;
|
|
15
20
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
#[derive(Copy, Clone)]
|
|
22
|
+
struct GlobalValue(VALUE);
|
|
23
|
+
|
|
24
|
+
unsafe impl Send for GlobalValue {}
|
|
25
|
+
unsafe impl Sync for GlobalValue {}
|
|
26
|
+
|
|
27
|
+
fn stored_value(lock: &OnceLock<GlobalValue>, name: &str) -> VALUE {
|
|
28
|
+
lock.get()
|
|
29
|
+
.unwrap_or_else(|| panic!("{name} not initialized"))
|
|
30
|
+
.0
|
|
22
31
|
}
|
|
23
32
|
|
|
24
|
-
fn
|
|
25
|
-
|
|
33
|
+
fn decompress_error() -> VALUE {
|
|
34
|
+
stored_value(&DECOMPRESS_ERROR, "DecompressError")
|
|
26
35
|
}
|
|
27
36
|
|
|
28
|
-
fn
|
|
29
|
-
|
|
30
|
-
*MISSING_CONTENT_SIZE_ERROR
|
|
31
|
-
.get()
|
|
32
|
-
.expect("MissingContentSizeError not initialized"),
|
|
33
|
-
)
|
|
37
|
+
fn compress_error() -> VALUE {
|
|
38
|
+
stored_value(&COMPRESS_ERROR, "CompressError")
|
|
34
39
|
}
|
|
35
40
|
|
|
36
|
-
fn output_size_limit_error(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
fn output_size_limit_error() -> VALUE {
|
|
42
|
+
stored_value(&OUTPUT_SIZE_LIMIT_ERROR, "OutputSizeLimitError")
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
fn should_release_compress_gvl(input_len: usize) -> bool {
|
|
46
|
+
input_len >= GVL_COMPRESS_THRESHOLD
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
fn should_release_frame_decompress_gvl(output_len: usize) -> bool {
|
|
50
|
+
output_len >= GVL_FRAME_DECOMPRESS_THRESHOLD
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
fn with_mutex<T, R, F>(
|
|
54
|
+
mutex: &Mutex<T>,
|
|
55
|
+
release_gvl: bool,
|
|
56
|
+
name: &str,
|
|
57
|
+
input: &mut rb::InputBytes,
|
|
58
|
+
func: F,
|
|
59
|
+
) -> RbResult<R>
|
|
60
|
+
where
|
|
61
|
+
F: FnOnce(&mut T, &[u8]) -> RbResult<R>,
|
|
62
|
+
{
|
|
63
|
+
if release_gvl {
|
|
64
|
+
input.lock_for_without_gvl(true)?;
|
|
65
|
+
return rb::maybe_without_gvl(true, || {
|
|
66
|
+
let mut guard = mutex
|
|
67
|
+
.lock()
|
|
68
|
+
.map_err(|_| RubyErr::runtime(format!("{name} mutex poisoned")))?;
|
|
69
|
+
func(&mut guard, input.as_slice())
|
|
70
|
+
})?;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
match mutex.try_lock() {
|
|
74
|
+
Ok(mut guard) => func(&mut guard, input.as_slice()),
|
|
75
|
+
Err(TryLockError::WouldBlock) => {
|
|
76
|
+
// Even a small input needs protection when contention releases GVL.
|
|
77
|
+
input.lock_for_without_gvl(true)?;
|
|
78
|
+
rb::maybe_without_gvl(true, || {
|
|
79
|
+
let mut guard = mutex
|
|
80
|
+
.lock()
|
|
81
|
+
.map_err(|_| RubyErr::runtime(format!("{name} mutex poisoned")))?;
|
|
82
|
+
func(&mut guard, input.as_slice())
|
|
83
|
+
})?
|
|
84
|
+
}
|
|
85
|
+
Err(TryLockError::Poisoned(_)) => Err(RubyErr::runtime(format!("{name} mutex poisoned"))),
|
|
86
|
+
}
|
|
42
87
|
}
|
|
43
88
|
|
|
44
89
|
// ---------- frame header parsing ----------
|
|
@@ -48,54 +93,17 @@ const ZSTD_FRAME_MAGIC: [u8; 4] = [0x28, 0xB5, 0x2F, 0xFD];
|
|
|
48
93
|
#[derive(Debug)]
|
|
49
94
|
enum BoundedError {
|
|
50
95
|
BadMagic,
|
|
51
|
-
|
|
52
|
-
OutputSizeLimit { declared: u64, limit: u64 },
|
|
96
|
+
OutputSizeLimit { limit: u64 },
|
|
53
97
|
DecoderFailed(String),
|
|
54
98
|
}
|
|
55
99
|
|
|
56
100
|
fn parse_frame_content_size(input: &[u8]) -> Result<Option<u64>, BoundedError> {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
let fhd = input[4];
|
|
64
|
-
let fcs_flag = (fhd >> 6) & 3;
|
|
65
|
-
let single_segment = (fhd >> 5) & 1;
|
|
66
|
-
let dict_id_flag = fhd & 3;
|
|
67
|
-
|
|
68
|
-
let window_desc_size = if single_segment == 0 { 1usize } else { 0 };
|
|
69
|
-
let dict_id_size = [0usize, 1, 2, 4][dict_id_flag as usize];
|
|
70
|
-
let fcs_field_size = match fcs_flag {
|
|
71
|
-
0 => {
|
|
72
|
-
if single_segment == 1 {
|
|
73
|
-
1usize
|
|
74
|
-
} else {
|
|
75
|
-
return Ok(None);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
1 => 2,
|
|
79
|
-
2 => 4,
|
|
80
|
-
3 => 8,
|
|
81
|
-
_ => unreachable!(),
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
let fcs_offset = 5 + window_desc_size + dict_id_size;
|
|
85
|
-
if input.len() < fcs_offset + fcs_field_size {
|
|
86
|
-
return Err(BoundedError::BadMagic);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
let fcs_bytes = &input[fcs_offset..fcs_offset + fcs_field_size];
|
|
90
|
-
let value = match fcs_field_size {
|
|
91
|
-
1 => fcs_bytes[0] as u64,
|
|
92
|
-
2 => u16::from_le_bytes([fcs_bytes[0], fcs_bytes[1]]) as u64 + 256,
|
|
93
|
-
4 => u32::from_le_bytes([fcs_bytes[0], fcs_bytes[1], fcs_bytes[2], fcs_bytes[3]]) as u64,
|
|
94
|
-
8 => u64::from_le_bytes(fcs_bytes.try_into().unwrap()),
|
|
95
|
-
_ => unreachable!(),
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
Ok(Some(value))
|
|
101
|
+
zstd::frame::header::parse_frame_header(input)
|
|
102
|
+
.map(|header| header.frame_content_size)
|
|
103
|
+
.map_err(|err| match err {
|
|
104
|
+
zstd::DecompressError::BadMagic => BoundedError::BadMagic,
|
|
105
|
+
_ => BoundedError::DecoderFailed(err.to_string()),
|
|
106
|
+
})
|
|
99
107
|
}
|
|
100
108
|
|
|
101
109
|
fn decompress_bounded(
|
|
@@ -107,71 +115,186 @@ fn decompress_bounded(
|
|
|
107
115
|
return Err(BoundedError::BadMagic);
|
|
108
116
|
}
|
|
109
117
|
|
|
110
|
-
let
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
declared: n,
|
|
115
|
-
limit: max_output as u64,
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
if n > u64::from(u32::MAX) {
|
|
119
|
-
return Err(BoundedError::OutputSizeLimit {
|
|
120
|
-
declared: n,
|
|
121
|
-
limit: u64::from(u32::MAX),
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
n as usize
|
|
125
|
-
}
|
|
126
|
-
None => {
|
|
127
|
-
if max_output != 0 {
|
|
128
|
-
return Err(BoundedError::MissingContentSize);
|
|
129
|
-
}
|
|
130
|
-
1024 * 1024
|
|
131
|
-
}
|
|
118
|
+
let limit = if max_output == 0 {
|
|
119
|
+
usize::MAX
|
|
120
|
+
} else {
|
|
121
|
+
max_output
|
|
132
122
|
};
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
.
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
123
|
+
dctx.decompress_with_limit(compressed, limit)
|
|
124
|
+
.map(|out| out.into_owned())
|
|
125
|
+
.map_err(|err| match err {
|
|
126
|
+
zstd::DecompressError::OutputTooSmall => BoundedError::OutputSizeLimit {
|
|
127
|
+
limit: max_output as u64,
|
|
128
|
+
},
|
|
129
|
+
_ => BoundedError::DecoderFailed(err.to_string()),
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
fn decompress_work_size(compressed: &[u8], max_output: usize) -> usize {
|
|
134
|
+
match parse_frame_content_size(compressed) {
|
|
135
|
+
Ok(Some(n)) => usize::try_from(n).unwrap_or(usize::MAX),
|
|
136
|
+
Ok(None) if max_output != 0 => max_output,
|
|
137
|
+
_ => compressed.len(),
|
|
138
|
+
}
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
fn
|
|
141
|
+
fn bounded_err(err: BoundedError, prefix: &str) -> RubyErr {
|
|
142
142
|
match err {
|
|
143
|
-
BoundedError::BadMagic =>
|
|
144
|
-
decompress_error(
|
|
143
|
+
BoundedError::BadMagic => RubyErr::new(
|
|
144
|
+
decompress_error(),
|
|
145
145
|
format!("{prefix}: bad magic (input is not a Zstd frame)"),
|
|
146
146
|
),
|
|
147
|
-
BoundedError::
|
|
148
|
-
|
|
149
|
-
format!("{prefix}:
|
|
150
|
-
),
|
|
151
|
-
BoundedError::OutputSizeLimit { declared, limit } => Error::new(
|
|
152
|
-
output_size_limit_error(ruby),
|
|
153
|
-
format!("{prefix}: declared content size {declared} exceeds limit {limit}"),
|
|
147
|
+
BoundedError::OutputSizeLimit { limit } => RubyErr::new(
|
|
148
|
+
output_size_limit_error(),
|
|
149
|
+
format!("{prefix}: decompressed output exceeds limit {limit}"),
|
|
154
150
|
),
|
|
155
151
|
BoundedError::DecoderFailed(msg) => {
|
|
156
|
-
|
|
152
|
+
RubyErr::new(decompress_error(), format!("{prefix}: {msg}"))
|
|
157
153
|
}
|
|
158
154
|
}
|
|
159
155
|
}
|
|
160
156
|
|
|
161
157
|
// ---------- dict helper ----------
|
|
162
158
|
|
|
163
|
-
fn load_dict(
|
|
159
|
+
fn load_dict(bytes: &[u8]) -> RbResult<Dictionary> {
|
|
164
160
|
Dictionary::from_bytes(bytes).map_err(|_| {
|
|
165
|
-
|
|
166
|
-
ruby.exception_runtime_error(),
|
|
167
|
-
"dictionary must be in ZDICT format (use DictTrainer to train one)",
|
|
168
|
-
)
|
|
161
|
+
RubyErr::runtime("dictionary must be in ZDICT format (use DictTrainer to train one)")
|
|
169
162
|
})
|
|
170
163
|
}
|
|
171
164
|
|
|
165
|
+
// ---------- typed data ----------
|
|
166
|
+
|
|
167
|
+
struct NativeDataType(rb_data_type_t);
|
|
168
|
+
|
|
169
|
+
unsafe impl Send for NativeDataType {}
|
|
170
|
+
unsafe impl Sync for NativeDataType {}
|
|
171
|
+
|
|
172
|
+
static FRAME_CODEC_DATA_TYPE: OnceLock<NativeDataType> = OnceLock::new();
|
|
173
|
+
static BLOCK_CODEC_DATA_TYPE: OnceLock<NativeDataType> = OnceLock::new();
|
|
174
|
+
static DICT_TRAINER_DATA_TYPE: OnceLock<NativeDataType> = OnceLock::new();
|
|
175
|
+
|
|
176
|
+
fn frame_codec_data_type() -> *const rb_data_type_t {
|
|
177
|
+
&FRAME_CODEC_DATA_TYPE
|
|
178
|
+
.get_or_init(|| NativeDataType(make_frame_codec_data_type()))
|
|
179
|
+
.0
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
fn block_codec_data_type() -> *const rb_data_type_t {
|
|
183
|
+
&BLOCK_CODEC_DATA_TYPE
|
|
184
|
+
.get_or_init(|| NativeDataType(make_block_codec_data_type()))
|
|
185
|
+
.0
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
fn dict_trainer_data_type() -> *const rb_data_type_t {
|
|
189
|
+
&DICT_TRAINER_DATA_TYPE
|
|
190
|
+
.get_or_init(|| NativeDataType(make_dict_trainer_data_type()))
|
|
191
|
+
.0
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
fn make_frame_codec_data_type() -> rb_data_type_t {
|
|
195
|
+
rb_data_type_t {
|
|
196
|
+
wrap_struct_name: c"zrip_frame_codec".as_ptr(),
|
|
197
|
+
function: rb_data_type_struct__bindgen_ty_1 {
|
|
198
|
+
dmark: None,
|
|
199
|
+
dfree: Some(frame_codec_free),
|
|
200
|
+
dsize: Some(frame_codec_native_size),
|
|
201
|
+
dcompact: None,
|
|
202
|
+
reserved: [std::ptr::null_mut(); 1],
|
|
203
|
+
},
|
|
204
|
+
parent: std::ptr::null(),
|
|
205
|
+
data: std::ptr::null_mut(),
|
|
206
|
+
flags: rb::frozen_shareable_flags(),
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
fn make_block_codec_data_type() -> rb_data_type_t {
|
|
211
|
+
rb_data_type_t {
|
|
212
|
+
wrap_struct_name: c"zrip_block_codec".as_ptr(),
|
|
213
|
+
function: rb_data_type_struct__bindgen_ty_1 {
|
|
214
|
+
dmark: None,
|
|
215
|
+
dfree: Some(block_codec_free),
|
|
216
|
+
dsize: Some(block_codec_native_size),
|
|
217
|
+
dcompact: None,
|
|
218
|
+
reserved: [std::ptr::null_mut(); 1],
|
|
219
|
+
},
|
|
220
|
+
parent: std::ptr::null(),
|
|
221
|
+
data: std::ptr::null_mut(),
|
|
222
|
+
flags: 1,
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
fn make_dict_trainer_data_type() -> rb_data_type_t {
|
|
227
|
+
rb_data_type_t {
|
|
228
|
+
wrap_struct_name: c"zrip_dict_trainer".as_ptr(),
|
|
229
|
+
function: rb_data_type_struct__bindgen_ty_1 {
|
|
230
|
+
dmark: None,
|
|
231
|
+
dfree: Some(dict_trainer_free),
|
|
232
|
+
dsize: Some(dict_trainer_native_size),
|
|
233
|
+
dcompact: None,
|
|
234
|
+
reserved: [std::ptr::null_mut(); 1],
|
|
235
|
+
},
|
|
236
|
+
parent: std::ptr::null(),
|
|
237
|
+
data: std::ptr::null_mut(),
|
|
238
|
+
flags: 1,
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
unsafe extern "C" fn frame_codec_free(ptr: *mut c_void) {
|
|
243
|
+
if ptr.is_null() {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
let _ = catch_unwind(AssertUnwindSafe(|| unsafe {
|
|
248
|
+
drop(Box::from_raw(ptr as *mut FrameCodec));
|
|
249
|
+
}));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
unsafe extern "C" fn block_codec_free(ptr: *mut c_void) {
|
|
253
|
+
if ptr.is_null() {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
let _ = catch_unwind(AssertUnwindSafe(|| unsafe {
|
|
258
|
+
drop(Box::from_raw(ptr as *mut BlockCodec));
|
|
259
|
+
}));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
unsafe extern "C" fn dict_trainer_free(ptr: *mut c_void) {
|
|
263
|
+
if ptr.is_null() {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
let _ = catch_unwind(AssertUnwindSafe(|| unsafe {
|
|
268
|
+
drop(Box::from_raw(ptr as *mut RbDictTrainer));
|
|
269
|
+
}));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
unsafe extern "C" fn frame_codec_native_size(_ptr: *const c_void) -> size_t {
|
|
273
|
+
std::mem::size_of::<FrameCodec>() as size_t
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
unsafe extern "C" fn block_codec_native_size(_ptr: *const c_void) -> size_t {
|
|
277
|
+
std::mem::size_of::<BlockCodec>() as size_t
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
unsafe extern "C" fn dict_trainer_native_size(_ptr: *const c_void) -> size_t {
|
|
281
|
+
std::mem::size_of::<RbDictTrainer>() as size_t
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
unsafe fn frame_codec_ref(value: VALUE) -> RbResult<&'static FrameCodec> {
|
|
285
|
+
unsafe { rb::typed_data_ref(value, frame_codec_data_type(), "Zrip::FrameCodec") }
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
unsafe fn block_codec_ref(value: VALUE) -> RbResult<&'static BlockCodec> {
|
|
289
|
+
unsafe { rb::typed_data_ref(value, block_codec_data_type(), "Zrip::BlockCodec") }
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
unsafe fn dict_trainer_ref(value: VALUE) -> RbResult<&'static RbDictTrainer> {
|
|
293
|
+
unsafe { rb::typed_data_ref(value, dict_trainer_data_type(), "Zrip::DictTrainer") }
|
|
294
|
+
}
|
|
295
|
+
|
|
172
296
|
// ---------- FrameCodec ----------
|
|
173
297
|
|
|
174
|
-
#[magnus::wrap(class = "Zrip::FrameCodec", free_immediately, size)]
|
|
175
298
|
struct FrameCodec {
|
|
176
299
|
dict_len: usize,
|
|
177
300
|
dict_id: Option<u32>,
|
|
@@ -183,198 +306,311 @@ struct FrameCodec {
|
|
|
183
306
|
unsafe impl Send for FrameCodec {}
|
|
184
307
|
unsafe impl Sync for FrameCodec {}
|
|
185
308
|
|
|
186
|
-
fn
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
level: i32,
|
|
191
|
-
) -> Result<FrameCodec, Error> {
|
|
309
|
+
fn frame_codec_new_impl(class: VALUE, rb_dict: VALUE, id: VALUE, level: VALUE) -> RbResult<VALUE> {
|
|
310
|
+
let rb_dict = rb::value_to_option_bytes(rb_dict)?;
|
|
311
|
+
let id = rb::value_to_u32(id)?;
|
|
312
|
+
let level = rb::value_to_i32(level)?;
|
|
192
313
|
let (dict_len, dict_id, cctx, dctx) = match rb_dict {
|
|
193
314
|
None => {
|
|
194
315
|
let cctx = CompressContext::new(level).map_err(|e| {
|
|
195
|
-
|
|
196
|
-
compress_error(
|
|
316
|
+
RubyErr::new(
|
|
317
|
+
compress_error(),
|
|
197
318
|
format!("CompressContext::new failed: {e}"),
|
|
198
319
|
)
|
|
199
320
|
})?;
|
|
200
321
|
(0, None, cctx, DecompressContext::new())
|
|
201
322
|
}
|
|
202
|
-
Some(
|
|
203
|
-
let
|
|
204
|
-
s.freeze();
|
|
205
|
-
let dict = load_dict(ruby, &bytes)?;
|
|
323
|
+
Some(bytes) => {
|
|
324
|
+
let dict = load_dict(&bytes)?;
|
|
206
325
|
let dl = bytes.len();
|
|
207
326
|
let cctx = CompressContext::with_dict(level, dict.clone()).map_err(|e| {
|
|
208
|
-
|
|
209
|
-
compress_error(
|
|
327
|
+
RubyErr::new(
|
|
328
|
+
compress_error(),
|
|
210
329
|
format!("CompressContext::with_dict failed: {e}"),
|
|
211
330
|
)
|
|
212
331
|
})?;
|
|
213
|
-
let dctx = DecompressContext::with_dict(dict);
|
|
332
|
+
let dctx = DecompressContext::with_dict(dict.clone());
|
|
214
333
|
(dl, Some(id), cctx, dctx)
|
|
215
334
|
}
|
|
216
335
|
};
|
|
217
336
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
337
|
+
let codec = unsafe {
|
|
338
|
+
rb::wrap_typed_data(
|
|
339
|
+
class,
|
|
340
|
+
Box::new(FrameCodec {
|
|
341
|
+
dict_len,
|
|
342
|
+
dict_id,
|
|
343
|
+
level,
|
|
344
|
+
cctx: Mutex::new(cctx),
|
|
345
|
+
dctx: Mutex::new(dctx),
|
|
346
|
+
}),
|
|
347
|
+
frame_codec_data_type(),
|
|
348
|
+
)?
|
|
349
|
+
};
|
|
350
|
+
rb::make_shareable(codec)?;
|
|
351
|
+
Ok(codec)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
fn frame_codec_compress_impl(rb_self: VALUE, rb_input: VALUE) -> RbResult<VALUE> {
|
|
355
|
+
let rb_self = unsafe { frame_codec_ref(rb_self)? };
|
|
356
|
+
let mut input = rb::input_bytes(rb_input)?;
|
|
357
|
+
let release_gvl = should_release_compress_gvl(input.len());
|
|
358
|
+
let out = with_mutex(
|
|
359
|
+
&rb_self.cctx,
|
|
360
|
+
release_gvl,
|
|
361
|
+
"FrameCodec CCtx",
|
|
362
|
+
&mut input,
|
|
363
|
+
|cctx, input| {
|
|
364
|
+
cctx.compress(input)
|
|
365
|
+
.map(|out| out.into_owned())
|
|
366
|
+
.map_err(|e| RubyErr::new(compress_error(), format!("zstd compress failed: {e}")))
|
|
367
|
+
},
|
|
368
|
+
)?;
|
|
369
|
+
rb::new_binary_string(&out)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
fn frame_codec_decompress_impl(
|
|
373
|
+
rb_self: VALUE,
|
|
374
|
+
rb_input: VALUE,
|
|
375
|
+
max_output: VALUE,
|
|
376
|
+
) -> RbResult<VALUE> {
|
|
377
|
+
let rb_self = unsafe { frame_codec_ref(rb_self)? };
|
|
378
|
+
let max_output = rb::value_to_usize(max_output)?;
|
|
379
|
+
let mut compressed = rb::input_bytes(rb_input)?;
|
|
380
|
+
let release_gvl = should_release_frame_decompress_gvl(decompress_work_size(
|
|
381
|
+
compressed.as_slice(),
|
|
382
|
+
max_output,
|
|
383
|
+
));
|
|
384
|
+
let out = with_mutex(
|
|
385
|
+
&rb_self.dctx,
|
|
386
|
+
release_gvl,
|
|
387
|
+
"FrameCodec DCtx",
|
|
388
|
+
&mut compressed,
|
|
389
|
+
|dctx, compressed| {
|
|
390
|
+
decompress_bounded(compressed, max_output, dctx)
|
|
391
|
+
.map_err(|e| bounded_err(e, "zstd frame decode failed"))
|
|
392
|
+
},
|
|
393
|
+
)?;
|
|
394
|
+
rb::new_binary_string(&out)
|
|
225
395
|
}
|
|
226
396
|
|
|
227
|
-
fn
|
|
228
|
-
|
|
229
|
-
rb_self
|
|
230
|
-
rb_input: RString,
|
|
231
|
-
) -> Result<RString, Error> {
|
|
232
|
-
let input: &[u8] = unsafe { rb_input.as_slice() };
|
|
233
|
-
let mut cctx = rb_self.cctx.lock().expect("FrameCodec CCtx mutex poisoned");
|
|
234
|
-
let out = cctx
|
|
235
|
-
.compress(input)
|
|
236
|
-
.map_err(|e| Error::new(compress_error(ruby), format!("zstd compress failed: {e}")))?;
|
|
237
|
-
Ok(ruby.str_from_slice(&out))
|
|
397
|
+
fn frame_codec_size_impl(rb_self: VALUE) -> RbResult<VALUE> {
|
|
398
|
+
let rb_self = unsafe { frame_codec_ref(rb_self)? };
|
|
399
|
+
rb::usize_value(rb_self.dict_len)
|
|
238
400
|
}
|
|
239
401
|
|
|
240
|
-
fn
|
|
241
|
-
|
|
242
|
-
rb_self
|
|
243
|
-
rb_input: RString,
|
|
244
|
-
max_output: usize,
|
|
245
|
-
) -> Result<RString, Error> {
|
|
246
|
-
let compressed: &[u8] = unsafe { rb_input.as_slice() };
|
|
247
|
-
let mut dctx = rb_self.dctx.lock().expect("FrameCodec DCtx mutex poisoned");
|
|
248
|
-
let out = decompress_bounded(compressed, max_output, &mut dctx)
|
|
249
|
-
.map_err(|e| raise_bounded(ruby, e, "zstd frame decode failed"))?;
|
|
250
|
-
Ok(ruby.str_from_slice(&out))
|
|
402
|
+
fn frame_codec_has_dict_impl(rb_self: VALUE) -> RbResult<VALUE> {
|
|
403
|
+
let rb_self = unsafe { frame_codec_ref(rb_self)? };
|
|
404
|
+
Ok(rb::bool_value(rb_self.dict_id.is_some()))
|
|
251
405
|
}
|
|
252
406
|
|
|
253
|
-
fn
|
|
254
|
-
rb_self
|
|
407
|
+
fn frame_codec_id_impl(rb_self: VALUE) -> RbResult<VALUE> {
|
|
408
|
+
let rb_self = unsafe { frame_codec_ref(rb_self)? };
|
|
409
|
+
rb::u32_option_value(rb_self.dict_id)
|
|
255
410
|
}
|
|
256
411
|
|
|
257
|
-
fn
|
|
258
|
-
rb_self
|
|
412
|
+
fn frame_codec_level_impl(rb_self: VALUE) -> RbResult<VALUE> {
|
|
413
|
+
let rb_self = unsafe { frame_codec_ref(rb_self)? };
|
|
414
|
+
Ok(rb::i32_value(rb_self.level))
|
|
259
415
|
}
|
|
260
416
|
|
|
261
|
-
fn
|
|
262
|
-
|
|
417
|
+
fn frame_codec_get_frame_content_size_impl(rb_input: VALUE) -> RbResult<VALUE> {
|
|
418
|
+
let bytes = rb::input_bytes(rb_input)?;
|
|
419
|
+
match parse_frame_content_size(bytes.as_slice()) {
|
|
420
|
+
Ok(v) => rb::u64_option_value(v),
|
|
421
|
+
Err(BoundedError::BadMagic) => Err(RubyErr::new(
|
|
422
|
+
decompress_error(),
|
|
423
|
+
"zstd frame header parse failed: bad magic (input is not a Zstd frame)",
|
|
424
|
+
)),
|
|
425
|
+
Err(e) => Err(bounded_err(e, "zstd frame header parse failed")),
|
|
426
|
+
}
|
|
263
427
|
}
|
|
264
428
|
|
|
265
|
-
|
|
266
|
-
|
|
429
|
+
unsafe extern "C" fn frame_codec_new(
|
|
430
|
+
class: VALUE,
|
|
431
|
+
rb_dict: VALUE,
|
|
432
|
+
id: VALUE,
|
|
433
|
+
level: VALUE,
|
|
434
|
+
) -> VALUE {
|
|
435
|
+
rb::wrap(|| frame_codec_new_impl(class, rb_dict, id, level))
|
|
267
436
|
}
|
|
268
437
|
|
|
269
|
-
fn
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
438
|
+
unsafe extern "C" fn frame_codec_compress(rb_self: VALUE, rb_input: VALUE) -> VALUE {
|
|
439
|
+
rb::wrap(|| frame_codec_compress_impl(rb_self, rb_input))
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
unsafe extern "C" fn frame_codec_decompress(
|
|
443
|
+
rb_self: VALUE,
|
|
444
|
+
rb_input: VALUE,
|
|
445
|
+
max_output: VALUE,
|
|
446
|
+
) -> VALUE {
|
|
447
|
+
rb::wrap(|| frame_codec_decompress_impl(rb_self, rb_input, max_output))
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
unsafe extern "C" fn frame_codec_size(rb_self: VALUE) -> VALUE {
|
|
451
|
+
rb::wrap(|| frame_codec_size_impl(rb_self))
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
unsafe extern "C" fn frame_codec_has_dict(rb_self: VALUE) -> VALUE {
|
|
455
|
+
rb::wrap(|| frame_codec_has_dict_impl(rb_self))
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
unsafe extern "C" fn frame_codec_id(rb_self: VALUE) -> VALUE {
|
|
459
|
+
rb::wrap(|| frame_codec_id_impl(rb_self))
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
unsafe extern "C" fn frame_codec_level(rb_self: VALUE) -> VALUE {
|
|
463
|
+
rb::wrap(|| frame_codec_level_impl(rb_self))
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
unsafe extern "C" fn frame_codec_get_frame_content_size(_class: VALUE, rb_input: VALUE) -> VALUE {
|
|
467
|
+
rb::wrap(|| frame_codec_get_frame_content_size_impl(rb_input))
|
|
282
468
|
}
|
|
283
469
|
|
|
284
470
|
// ---------- BlockCodec ----------
|
|
285
471
|
|
|
286
|
-
#[magnus::wrap(class = "Zrip::BlockCodec", free_immediately, size)]
|
|
287
472
|
struct BlockCodec {
|
|
288
473
|
dict_len: usize,
|
|
289
474
|
dict_id: Option<u32>,
|
|
290
475
|
level: i32,
|
|
291
|
-
cctx:
|
|
292
|
-
dctx:
|
|
476
|
+
cctx: Mutex<CompressContext>,
|
|
477
|
+
dctx: Mutex<DecompressContext>,
|
|
293
478
|
}
|
|
294
479
|
|
|
295
|
-
fn
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
level: i32,
|
|
300
|
-
) -> Result<BlockCodec, Error> {
|
|
480
|
+
fn block_codec_new_impl(class: VALUE, rb_dict: VALUE, id: VALUE, level: VALUE) -> RbResult<VALUE> {
|
|
481
|
+
let rb_dict = rb::value_to_option_bytes(rb_dict)?;
|
|
482
|
+
let id = rb::value_to_u32(id)?;
|
|
483
|
+
let level = rb::value_to_i32(level)?;
|
|
301
484
|
let (dict_len, dict_id, cctx, dctx) = match rb_dict {
|
|
302
485
|
None => {
|
|
303
486
|
let cctx = CompressContext::new(level).map_err(|e| {
|
|
304
|
-
|
|
305
|
-
compress_error(
|
|
487
|
+
RubyErr::new(
|
|
488
|
+
compress_error(),
|
|
306
489
|
format!("CompressContext::new failed: {e}"),
|
|
307
490
|
)
|
|
308
491
|
})?;
|
|
309
492
|
(0, None, cctx, DecompressContext::new())
|
|
310
493
|
}
|
|
311
|
-
Some(
|
|
312
|
-
let
|
|
313
|
-
let dict = load_dict(ruby, &bytes)?;
|
|
494
|
+
Some(bytes) => {
|
|
495
|
+
let dict = load_dict(&bytes)?;
|
|
314
496
|
let dl = bytes.len();
|
|
315
497
|
let cctx = CompressContext::with_dict(level, dict.clone()).map_err(|e| {
|
|
316
|
-
|
|
317
|
-
compress_error(
|
|
498
|
+
RubyErr::new(
|
|
499
|
+
compress_error(),
|
|
318
500
|
format!("CompressContext::with_dict failed: {e}"),
|
|
319
501
|
)
|
|
320
502
|
})?;
|
|
321
|
-
let dctx = DecompressContext::with_dict(dict);
|
|
503
|
+
let dctx = DecompressContext::with_dict(dict.clone());
|
|
322
504
|
(dl, Some(id), cctx, dctx)
|
|
323
505
|
}
|
|
324
506
|
};
|
|
325
507
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
508
|
+
unsafe {
|
|
509
|
+
rb::wrap_typed_data(
|
|
510
|
+
class,
|
|
511
|
+
Box::new(BlockCodec {
|
|
512
|
+
dict_len,
|
|
513
|
+
dict_id,
|
|
514
|
+
level,
|
|
515
|
+
cctx: Mutex::new(cctx),
|
|
516
|
+
dctx: Mutex::new(dctx),
|
|
517
|
+
}),
|
|
518
|
+
block_codec_data_type(),
|
|
519
|
+
)
|
|
520
|
+
}
|
|
333
521
|
}
|
|
334
522
|
|
|
335
|
-
fn
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
523
|
+
fn block_codec_compress_impl(rb_self: VALUE, rb_input: VALUE) -> RbResult<VALUE> {
|
|
524
|
+
let rb_self = unsafe { block_codec_ref(rb_self)? };
|
|
525
|
+
let mut input = rb::input_bytes(rb_input)?;
|
|
526
|
+
let release_gvl = should_release_compress_gvl(input.len());
|
|
527
|
+
let out = with_mutex(
|
|
528
|
+
&rb_self.cctx,
|
|
529
|
+
release_gvl,
|
|
530
|
+
"BlockCodec CCtx",
|
|
531
|
+
&mut input,
|
|
532
|
+
|cctx, input| {
|
|
533
|
+
cctx.compress(input)
|
|
534
|
+
.map(|out| out.into_owned())
|
|
535
|
+
.map_err(|e| RubyErr::new(compress_error(), format!("zstd compress failed: {e}")))
|
|
536
|
+
},
|
|
537
|
+
)?;
|
|
538
|
+
rb::new_binary_string(&out)
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
fn block_codec_decompress_impl(
|
|
542
|
+
rb_self: VALUE,
|
|
543
|
+
rb_input: VALUE,
|
|
544
|
+
max_output: VALUE,
|
|
545
|
+
) -> RbResult<VALUE> {
|
|
546
|
+
let rb_self = unsafe { block_codec_ref(rb_self)? };
|
|
547
|
+
let max_output = rb::value_to_usize(max_output)?;
|
|
548
|
+
let mut compressed = rb::input_bytes(rb_input)?;
|
|
549
|
+
let out = with_mutex(
|
|
550
|
+
&rb_self.dctx,
|
|
551
|
+
false,
|
|
552
|
+
"BlockCodec DCtx",
|
|
553
|
+
&mut compressed,
|
|
554
|
+
|dctx, compressed| {
|
|
555
|
+
decompress_bounded(compressed, max_output, dctx)
|
|
556
|
+
.map_err(|e| bounded_err(e, "zstd block decode failed"))
|
|
557
|
+
},
|
|
558
|
+
)?;
|
|
559
|
+
rb::new_binary_string(&out)
|
|
346
560
|
}
|
|
347
561
|
|
|
348
|
-
fn
|
|
349
|
-
|
|
350
|
-
rb_self
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
) ->
|
|
354
|
-
let
|
|
355
|
-
|
|
356
|
-
let out = decompress_bounded(compressed, max_output, &mut dctx)
|
|
357
|
-
.map_err(|e| raise_bounded(ruby, e, "zstd block decode failed"))?;
|
|
358
|
-
Ok(ruby.str_from_slice(&out))
|
|
562
|
+
fn block_codec_size_impl(rb_self: VALUE) -> RbResult<VALUE> {
|
|
563
|
+
let rb_self = unsafe { block_codec_ref(rb_self)? };
|
|
564
|
+
rb::usize_value(rb_self.dict_len)
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
fn block_codec_has_dict_impl(rb_self: VALUE) -> RbResult<VALUE> {
|
|
568
|
+
let rb_self = unsafe { block_codec_ref(rb_self)? };
|
|
569
|
+
Ok(rb::bool_value(rb_self.dict_id.is_some()))
|
|
359
570
|
}
|
|
360
571
|
|
|
361
|
-
fn
|
|
362
|
-
rb_self
|
|
572
|
+
fn block_codec_level_impl(rb_self: VALUE) -> RbResult<VALUE> {
|
|
573
|
+
let rb_self = unsafe { block_codec_ref(rb_self)? };
|
|
574
|
+
Ok(rb::i32_value(rb_self.level))
|
|
363
575
|
}
|
|
364
576
|
|
|
365
|
-
|
|
366
|
-
|
|
577
|
+
unsafe extern "C" fn block_codec_new(
|
|
578
|
+
class: VALUE,
|
|
579
|
+
rb_dict: VALUE,
|
|
580
|
+
id: VALUE,
|
|
581
|
+
level: VALUE,
|
|
582
|
+
) -> VALUE {
|
|
583
|
+
rb::wrap(|| block_codec_new_impl(class, rb_dict, id, level))
|
|
367
584
|
}
|
|
368
585
|
|
|
369
|
-
fn
|
|
370
|
-
rb_self
|
|
586
|
+
unsafe extern "C" fn block_codec_compress(rb_self: VALUE, rb_input: VALUE) -> VALUE {
|
|
587
|
+
rb::wrap(|| block_codec_compress_impl(rb_self, rb_input))
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
unsafe extern "C" fn block_codec_decompress(
|
|
591
|
+
rb_self: VALUE,
|
|
592
|
+
rb_input: VALUE,
|
|
593
|
+
max_output: VALUE,
|
|
594
|
+
) -> VALUE {
|
|
595
|
+
rb::wrap(|| block_codec_decompress_impl(rb_self, rb_input, max_output))
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
unsafe extern "C" fn block_codec_size(rb_self: VALUE) -> VALUE {
|
|
599
|
+
rb::wrap(|| block_codec_size_impl(rb_self))
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
unsafe extern "C" fn block_codec_has_dict(rb_self: VALUE) -> VALUE {
|
|
603
|
+
rb::wrap(|| block_codec_has_dict_impl(rb_self))
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
unsafe extern "C" fn block_codec_level(rb_self: VALUE) -> VALUE {
|
|
607
|
+
rb::wrap(|| block_codec_level_impl(rb_self))
|
|
371
608
|
}
|
|
372
609
|
|
|
373
610
|
// ---------- DictTrainer ----------
|
|
374
611
|
|
|
375
|
-
#[magnus::wrap(class = "Zrip::DictTrainer", free_immediately, size)]
|
|
376
612
|
struct RbDictTrainer {
|
|
377
|
-
inner:
|
|
613
|
+
inner: Mutex<Option<TrainerState>>,
|
|
378
614
|
max_dict_size: usize,
|
|
379
615
|
}
|
|
380
616
|
|
|
@@ -383,67 +619,78 @@ struct TrainerState {
|
|
|
383
619
|
total_bytes: usize,
|
|
384
620
|
}
|
|
385
621
|
|
|
386
|
-
fn
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
622
|
+
fn dict_trainer_new_impl(class: VALUE, max_dict_size: VALUE) -> RbResult<VALUE> {
|
|
623
|
+
let max_dict_size = rb::value_to_usize(max_dict_size)?;
|
|
624
|
+
unsafe {
|
|
625
|
+
rb::wrap_typed_data(
|
|
626
|
+
class,
|
|
627
|
+
Box::new(RbDictTrainer {
|
|
628
|
+
max_dict_size,
|
|
629
|
+
inner: Mutex::new(Some(TrainerState {
|
|
630
|
+
samples: Vec::new(),
|
|
631
|
+
total_bytes: 0,
|
|
632
|
+
})),
|
|
633
|
+
}),
|
|
634
|
+
dict_trainer_data_type(),
|
|
635
|
+
)
|
|
393
636
|
}
|
|
394
637
|
}
|
|
395
638
|
|
|
396
|
-
fn
|
|
397
|
-
|
|
398
|
-
rb_self
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
)
|
|
407
|
-
})?;
|
|
408
|
-
let data: Vec<u8> = unsafe { rb_data.as_slice().to_vec() };
|
|
639
|
+
fn dict_trainer_add_sample_impl(rb_self: VALUE, rb_data: VALUE) -> RbResult<VALUE> {
|
|
640
|
+
let data = rb::value_to_bytes(rb_data)?;
|
|
641
|
+
let rb_self = unsafe { dict_trainer_ref(rb_self)? };
|
|
642
|
+
let mut borrow = rb_self
|
|
643
|
+
.inner
|
|
644
|
+
.lock()
|
|
645
|
+
.map_err(|_| RubyErr::runtime("DictTrainer mutex poisoned"))?;
|
|
646
|
+
let state = borrow
|
|
647
|
+
.as_mut()
|
|
648
|
+
.ok_or_else(|| RubyErr::runtime("DictTrainer already consumed by #train"))?;
|
|
409
649
|
if data.len() < 4 {
|
|
410
|
-
return Ok(());
|
|
650
|
+
return Ok(rb::qnil());
|
|
411
651
|
}
|
|
412
652
|
state.total_bytes += data.len();
|
|
413
653
|
state.samples.push(data);
|
|
414
|
-
Ok(())
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
fn
|
|
418
|
-
let
|
|
419
|
-
borrow
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
654
|
+
Ok(rb::qnil())
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
fn dict_trainer_sample_count_impl(rb_self: VALUE) -> RbResult<VALUE> {
|
|
658
|
+
let rb_self = unsafe { dict_trainer_ref(rb_self)? };
|
|
659
|
+
let borrow = rb_self
|
|
660
|
+
.inner
|
|
661
|
+
.lock()
|
|
662
|
+
.map_err(|_| RubyErr::runtime("DictTrainer mutex poisoned"))?;
|
|
663
|
+
let value = borrow
|
|
664
|
+
.as_ref()
|
|
665
|
+
.map(|s| s.samples.len())
|
|
666
|
+
.ok_or_else(|| RubyErr::runtime("DictTrainer already consumed by #train"))?;
|
|
667
|
+
rb::usize_value(value)
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
fn dict_trainer_total_bytes_impl(rb_self: VALUE) -> RbResult<VALUE> {
|
|
671
|
+
let rb_self = unsafe { dict_trainer_ref(rb_self)? };
|
|
672
|
+
let borrow = rb_self
|
|
673
|
+
.inner
|
|
674
|
+
.lock()
|
|
675
|
+
.map_err(|_| RubyErr::runtime("DictTrainer mutex poisoned"))?;
|
|
676
|
+
let value = borrow
|
|
677
|
+
.as_ref()
|
|
678
|
+
.map(|s| s.total_bytes)
|
|
679
|
+
.ok_or_else(|| RubyErr::runtime("DictTrainer already consumed by #train"))?;
|
|
680
|
+
rb::usize_value(value)
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
fn dict_trainer_train_impl(rb_self: VALUE) -> RbResult<VALUE> {
|
|
684
|
+
let rb_self = unsafe { dict_trainer_ref(rb_self)? };
|
|
685
|
+
let state = rb_self
|
|
686
|
+
.inner
|
|
687
|
+
.lock()
|
|
688
|
+
.map_err(|_| RubyErr::runtime("DictTrainer mutex poisoned"))?
|
|
689
|
+
.take()
|
|
690
|
+
.ok_or_else(|| RubyErr::runtime("DictTrainer already consumed by #train"))?;
|
|
444
691
|
|
|
445
692
|
if state.samples.len() < 2 {
|
|
446
|
-
return
|
|
693
|
+
return rb::new_binary_string(b"");
|
|
447
694
|
}
|
|
448
695
|
|
|
449
696
|
let refs: Vec<&[u8]> = state.samples.iter().map(|s| s.as_slice()).collect();
|
|
@@ -456,81 +703,146 @@ fn dict_trainer_train(ruby: &Ruby, rb_self: &RbDictTrainer) -> Result<RString, E
|
|
|
456
703
|
let dict_bytes =
|
|
457
704
|
zstd::dict::finalize::finalize_dictionary(&content, &refs, rb_self.max_dict_size);
|
|
458
705
|
|
|
459
|
-
|
|
706
|
+
rb::new_binary_string(&dict_bytes)
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
fn dict_trainer_max_dict_size_impl(rb_self: VALUE) -> RbResult<VALUE> {
|
|
710
|
+
let rb_self = unsafe { dict_trainer_ref(rb_self)? };
|
|
711
|
+
rb::usize_value(rb_self.max_dict_size)
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
fn dict_trainer_trained_impl(rb_self: VALUE) -> RbResult<VALUE> {
|
|
715
|
+
let rb_self = unsafe { dict_trainer_ref(rb_self)? };
|
|
716
|
+
let borrow = rb_self
|
|
717
|
+
.inner
|
|
718
|
+
.lock()
|
|
719
|
+
.map_err(|_| RubyErr::runtime("DictTrainer mutex poisoned"))?;
|
|
720
|
+
Ok(rb::bool_value(borrow.is_none()))
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
unsafe extern "C" fn dict_trainer_new(class: VALUE, max_dict_size: VALUE) -> VALUE {
|
|
724
|
+
rb::wrap(|| dict_trainer_new_impl(class, max_dict_size))
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
unsafe extern "C" fn dict_trainer_add_sample(rb_self: VALUE, rb_data: VALUE) -> VALUE {
|
|
728
|
+
rb::wrap(|| dict_trainer_add_sample_impl(rb_self, rb_data))
|
|
460
729
|
}
|
|
461
730
|
|
|
462
|
-
fn
|
|
463
|
-
rb_self
|
|
731
|
+
unsafe extern "C" fn dict_trainer_sample_count(rb_self: VALUE) -> VALUE {
|
|
732
|
+
rb::wrap(|| dict_trainer_sample_count_impl(rb_self))
|
|
464
733
|
}
|
|
465
734
|
|
|
466
|
-
fn
|
|
467
|
-
|
|
735
|
+
unsafe extern "C" fn dict_trainer_total_bytes(rb_self: VALUE) -> VALUE {
|
|
736
|
+
rb::wrap(|| dict_trainer_total_bytes_impl(rb_self))
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
unsafe extern "C" fn dict_trainer_train(rb_self: VALUE) -> VALUE {
|
|
740
|
+
rb::wrap(|| dict_trainer_train_impl(rb_self))
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
unsafe extern "C" fn dict_trainer_max_dict_size(rb_self: VALUE) -> VALUE {
|
|
744
|
+
rb::wrap(|| dict_trainer_max_dict_size_impl(rb_self))
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
unsafe extern "C" fn dict_trainer_trained(rb_self: VALUE) -> VALUE {
|
|
748
|
+
rb::wrap(|| dict_trainer_trained_impl(rb_self))
|
|
468
749
|
}
|
|
469
750
|
|
|
470
751
|
// ---------- module init ----------
|
|
471
752
|
|
|
472
|
-
#
|
|
473
|
-
|
|
474
|
-
|
|
753
|
+
/// # Safety
|
|
754
|
+
///
|
|
755
|
+
/// Ruby calls this function while loading the native extension. The Ruby VM
|
|
756
|
+
/// must be initialized, and the symbol must only be entered by Ruby's extension
|
|
757
|
+
/// loader.
|
|
758
|
+
#[no_mangle]
|
|
759
|
+
pub unsafe extern "C" fn Init_zrip() {
|
|
760
|
+
rb::wrap_init(init);
|
|
761
|
+
}
|
|
475
762
|
|
|
476
|
-
|
|
763
|
+
fn init() -> RbResult<()> {
|
|
764
|
+
#[cfg(ruby_engine = "mri")]
|
|
765
|
+
unsafe {
|
|
766
|
+
rb_sys::rb_ext_ractor_safe(true);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
let module = unsafe { rb::define_module(c"Zrip")? };
|
|
477
770
|
|
|
478
771
|
let decompress_error_class =
|
|
479
|
-
module
|
|
772
|
+
unsafe { rb::define_error_under(module, c"DecompressError", rb_sys::rb_eStandardError)? };
|
|
480
773
|
DECOMPRESS_ERROR
|
|
481
|
-
.set(
|
|
774
|
+
.set(GlobalValue(decompress_error_class))
|
|
482
775
|
.unwrap_or_else(|_| panic!("init called more than once"));
|
|
483
776
|
|
|
484
777
|
let compress_error_class =
|
|
485
|
-
module
|
|
778
|
+
unsafe { rb::define_error_under(module, c"CompressError", rb_sys::rb_eStandardError)? };
|
|
486
779
|
COMPRESS_ERROR
|
|
487
|
-
.set(
|
|
780
|
+
.set(GlobalValue(compress_error_class))
|
|
488
781
|
.unwrap_or_else(|_| panic!("init called more than once"));
|
|
489
782
|
|
|
490
|
-
let missing_content_size_error_class =
|
|
491
|
-
module
|
|
783
|
+
let missing_content_size_error_class = unsafe {
|
|
784
|
+
rb::define_error_under(module, c"MissingContentSizeError", decompress_error_class)?
|
|
785
|
+
};
|
|
492
786
|
MISSING_CONTENT_SIZE_ERROR
|
|
493
|
-
.set(
|
|
787
|
+
.set(GlobalValue(missing_content_size_error_class))
|
|
494
788
|
.unwrap_or_else(|_| panic!("init called more than once"));
|
|
495
789
|
|
|
496
790
|
let output_size_limit_error_class =
|
|
497
|
-
module
|
|
791
|
+
unsafe { rb::define_error_under(module, c"OutputSizeLimitError", decompress_error_class)? };
|
|
498
792
|
OUTPUT_SIZE_LIMIT_ERROR
|
|
499
|
-
.set(
|
|
793
|
+
.set(GlobalValue(output_size_limit_error_class))
|
|
500
794
|
.unwrap_or_else(|_| panic!("init called more than once"));
|
|
501
795
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
"
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
796
|
+
let frame_codec_class =
|
|
797
|
+
unsafe { rb::define_class_under(module, c"FrameCodec", rb_sys::rb_cObject)? };
|
|
798
|
+
unsafe {
|
|
799
|
+
rb::undef_alloc_func(frame_codec_class)?;
|
|
800
|
+
rb::define_singleton_method_3(frame_codec_class, c"_native_new", frame_codec_new)?;
|
|
801
|
+
rb::define_singleton_method_1(
|
|
802
|
+
frame_codec_class,
|
|
803
|
+
c"get_frame_content_size",
|
|
804
|
+
frame_codec_get_frame_content_size,
|
|
805
|
+
)?;
|
|
806
|
+
rb::define_method_1(frame_codec_class, c"compress", frame_codec_compress)?;
|
|
807
|
+
rb::define_method_2(
|
|
808
|
+
frame_codec_class,
|
|
809
|
+
c"_native_decompress",
|
|
810
|
+
frame_codec_decompress,
|
|
811
|
+
)?;
|
|
812
|
+
rb::define_method_0(frame_codec_class, c"size", frame_codec_size)?;
|
|
813
|
+
rb::define_method_0(frame_codec_class, c"has_dict?", frame_codec_has_dict)?;
|
|
814
|
+
rb::define_method_0(frame_codec_class, c"id", frame_codec_id)?;
|
|
815
|
+
rb::define_method_0(frame_codec_class, c"level", frame_codec_level)?;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
let block_codec_class =
|
|
819
|
+
unsafe { rb::define_class_under(module, c"BlockCodec", rb_sys::rb_cObject)? };
|
|
820
|
+
unsafe {
|
|
821
|
+
rb::undef_alloc_func(block_codec_class)?;
|
|
822
|
+
rb::define_singleton_method_3(block_codec_class, c"_native_new", block_codec_new)?;
|
|
823
|
+
rb::define_method_1(block_codec_class, c"compress", block_codec_compress)?;
|
|
824
|
+
rb::define_method_2(
|
|
825
|
+
block_codec_class,
|
|
826
|
+
c"_native_decompress",
|
|
827
|
+
block_codec_decompress,
|
|
828
|
+
)?;
|
|
829
|
+
rb::define_method_0(block_codec_class, c"size", block_codec_size)?;
|
|
830
|
+
rb::define_method_0(block_codec_class, c"has_dict?", block_codec_has_dict)?;
|
|
831
|
+
rb::define_method_0(block_codec_class, c"level", block_codec_level)?;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
let trainer_class =
|
|
835
|
+
unsafe { rb::define_class_under(module, c"DictTrainer", rb_sys::rb_cObject)? };
|
|
836
|
+
unsafe {
|
|
837
|
+
rb::undef_alloc_func(trainer_class)?;
|
|
838
|
+
rb::define_singleton_method_1(trainer_class, c"_native_new", dict_trainer_new)?;
|
|
839
|
+
rb::define_method_1(trainer_class, c"add_sample", dict_trainer_add_sample)?;
|
|
840
|
+
rb::define_method_0(trainer_class, c"sample_count", dict_trainer_sample_count)?;
|
|
841
|
+
rb::define_method_0(trainer_class, c"total_bytes", dict_trainer_total_bytes)?;
|
|
842
|
+
rb::define_method_0(trainer_class, c"train", dict_trainer_train)?;
|
|
843
|
+
rb::define_method_0(trainer_class, c"max_dict_size", dict_trainer_max_dict_size)?;
|
|
844
|
+
rb::define_method_0(trainer_class, c"trained?", dict_trainer_trained)?;
|
|
845
|
+
}
|
|
534
846
|
|
|
535
847
|
Ok(())
|
|
536
848
|
}
|
|
@@ -573,4 +885,22 @@ mod tests {
|
|
|
573
885
|
let fcs = parse_frame_content_size(&compressed).unwrap();
|
|
574
886
|
assert_eq!(fcs, Some(data.len() as u64));
|
|
575
887
|
}
|
|
888
|
+
|
|
889
|
+
#[test]
|
|
890
|
+
fn bounded_decompress_uses_total_concatenated_limit() {
|
|
891
|
+
let mut dctx = DecompressContext::new();
|
|
892
|
+
let mut compressed = zstd::compress(&[b'a'; 100], 1).unwrap();
|
|
893
|
+
compressed.extend_from_slice(&zstd::compress(&[b'b'; 100], 1).unwrap());
|
|
894
|
+
|
|
895
|
+
assert!(matches!(
|
|
896
|
+
decompress_bounded(&compressed, 199, &mut dctx),
|
|
897
|
+
Err(BoundedError::OutputSizeLimit { .. })
|
|
898
|
+
));
|
|
899
|
+
assert_eq!(
|
|
900
|
+
decompress_bounded(&compressed, 200, &mut dctx)
|
|
901
|
+
.unwrap()
|
|
902
|
+
.len(),
|
|
903
|
+
200
|
|
904
|
+
);
|
|
905
|
+
}
|
|
576
906
|
}
|