1#![allow(
9 clippy::cast_possible_wrap,
10 clippy::cast_possible_truncation,
11 clippy::cast_sign_loss,
12 clippy::cast_precision_loss,
13 clippy::ptr_as_ptr
14)]
15
16use std::marker::PhantomData;
17use std::os::raw::{c_char, c_int, c_long, c_void};
18use std::ptr;
19
20use crate::cb::catch_callback;
21use crate::err::ReadStatError;
22use crate::rs_parser::ReadStatParser;
23
24#[repr(C)]
35pub struct ReadStatBufferCtx<'a> {
36 data: *const u8,
37 len: usize,
38 pos: usize,
39 _marker: PhantomData<&'a [u8]>,
40}
41
42impl<'a> ReadStatBufferCtx<'a> {
43 pub const fn new(bytes: &'a [u8]) -> Self {
48 Self {
49 data: bytes.as_ptr(),
50 len: bytes.len(),
51 pos: 0,
52 _marker: PhantomData,
53 }
54 }
55
56 pub fn configure_parser(
59 &mut self,
60 parser: ReadStatParser,
61 ) -> Result<ReadStatParser, ReadStatError> {
62 let ctx_ptr = std::ptr::from_mut::<Self>(self) as *mut c_void;
63 parser
64 .set_open_handler(Some(buffer_open))
65 .and_then(|p| p.set_close_handler(Some(buffer_close)))
66 .and_then(|p| p.set_seek_handler(Some(buffer_seek)))
67 .and_then(|p| p.set_read_handler(Some(buffer_read)))
68 .and_then(|p| p.set_update_handler(Some(buffer_update)))
69 .and_then(|p| p.set_io_ctx(ctx_ptr))
70 }
71}
72
73unsafe extern "C" fn buffer_open(_path: *const c_char, _io_ctx: *mut c_void) -> c_int {
75 catch_callback(-1, || 0)
76}
77
78unsafe extern "C" fn buffer_close(_io_ctx: *mut c_void) -> c_int {
80 catch_callback(-1, || 0)
81}
82
83unsafe extern "C" fn buffer_seek(
85 offset: readstat_sys::readstat_off_t,
86 whence: readstat_sys::readstat_io_flags_t,
87 io_ctx: *mut c_void,
88) -> readstat_sys::readstat_off_t {
89 catch_callback(-1, || unsafe { buffer_seek_inner(offset, whence, io_ctx) })
90}
91
92unsafe fn buffer_seek_inner(
93 offset: readstat_sys::readstat_off_t,
94 whence: readstat_sys::readstat_io_flags_t,
95 io_ctx: *mut c_void,
96) -> readstat_sys::readstat_off_t {
97 let ctx = unsafe { &mut *(io_ctx as *mut ReadStatBufferCtx<'_>) };
98
99 let newpos: i64 = match whence {
104 readstat_sys::readstat_io_flags_e_READSTAT_SEEK_SET => offset,
105 readstat_sys::readstat_io_flags_e_READSTAT_SEEK_CUR => {
106 match i64::try_from(ctx.pos)
107 .ok()
108 .and_then(|p| p.checked_add(offset))
109 {
110 Some(n) => n,
111 None => return -1,
112 }
113 }
114 readstat_sys::readstat_io_flags_e_READSTAT_SEEK_END => {
115 match i64::try_from(ctx.len)
116 .ok()
117 .and_then(|l| l.checked_add(offset))
118 {
119 Some(n) => n,
120 None => return -1,
121 }
122 }
123 _ => return -1,
124 };
125
126 if newpos < 0 || newpos > ctx.len as i64 {
127 return -1;
128 }
129
130 ctx.pos = newpos as usize;
131 newpos
132}
133
134unsafe extern "C" fn buffer_read(buf: *mut c_void, nbytes: usize, io_ctx: *mut c_void) -> isize {
136 catch_callback(-1, || unsafe { buffer_read_inner(buf, nbytes, io_ctx) })
137}
138
139unsafe fn buffer_read_inner(buf: *mut c_void, nbytes: usize, io_ctx: *mut c_void) -> isize {
140 let ctx = unsafe { &mut *(io_ctx as *mut ReadStatBufferCtx<'_>) };
141 let bytes_left = ctx.len.saturating_sub(ctx.pos);
142
143 let to_copy = if nbytes <= bytes_left {
144 nbytes
145 } else if bytes_left > 0 {
146 bytes_left
147 } else {
148 return 0;
149 };
150
151 unsafe {
152 ptr::copy_nonoverlapping(ctx.data.add(ctx.pos), buf as *mut u8, to_copy);
153 }
154 ctx.pos += to_copy;
155 to_copy as isize
156}
157
158unsafe extern "C" fn buffer_update(
160 _file_size: c_long,
161 progress_handler: readstat_sys::readstat_progress_handler,
162 user_ctx: *mut c_void,
163 io_ctx: *mut c_void,
164) -> readstat_sys::readstat_error_t {
165 catch_callback(
166 readstat_sys::readstat_error_e_READSTAT_ERROR_USER_ABORT,
167 || unsafe { buffer_update_inner(_file_size, progress_handler, user_ctx, io_ctx) },
168 )
169}
170
171unsafe fn buffer_update_inner(
172 _file_size: c_long,
173 progress_handler: readstat_sys::readstat_progress_handler,
174 user_ctx: *mut c_void,
175 io_ctx: *mut c_void,
176) -> readstat_sys::readstat_error_t {
177 let Some(handler) = progress_handler else {
178 return readstat_sys::readstat_error_e_READSTAT_OK;
179 };
180
181 let ctx = unsafe { &*(io_ctx as *mut ReadStatBufferCtx<'_>) };
182 let progress = if ctx.len > 0 {
183 ctx.pos as f64 / ctx.len as f64
184 } else {
185 1.0
186 };
187
188 if unsafe { handler(progress, user_ctx) } != 0 {
189 return readstat_sys::readstat_error_e_READSTAT_ERROR_USER_ABORT;
190 }
191
192 readstat_sys::readstat_error_e_READSTAT_OK
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[test]
200 fn buffer_ctx_new() {
201 let data = vec![1u8, 2, 3, 4, 5];
202 let ctx = ReadStatBufferCtx::new(&data);
203 assert_eq!(ctx.len, 5);
204 assert_eq!(ctx.pos, 0);
205 assert_eq!(ctx.data, data.as_ptr());
206 }
207
208 #[test]
209 fn buffer_ctx_empty() {
210 let data: Vec<u8> = vec![];
211 let ctx = ReadStatBufferCtx::new(&data);
212 assert_eq!(ctx.len, 0);
213 assert_eq!(ctx.pos, 0);
214 }
215}