|
1 | | -use std::cell::UnsafeCell; |
2 | | -use std::ffi::{c_char, c_int, c_void}; |
3 | | -use std::mem::MaybeUninit; |
4 | | -use std::ptr; |
5 | | -use std::slice; |
6 | | - |
7 | | -use cpython_sys::METH_FASTCALL; |
8 | | -use cpython_sys::Py_DecRef; |
9 | | -use cpython_sys::Py_buffer; |
10 | | -use cpython_sys::Py_ssize_t; |
11 | | -use cpython_sys::PyBuffer_Release; |
12 | | -use cpython_sys::PyBytes_AsString; |
13 | | -use cpython_sys::PyBytes_FromStringAndSize; |
14 | | -use cpython_sys::PyErr_NoMemory; |
15 | | -use cpython_sys::PyErr_SetString; |
16 | | -use cpython_sys::PyExc_TypeError; |
17 | | -use cpython_sys::PyMethodDef; |
18 | | -use cpython_sys::PyMethodDefFuncPointer; |
19 | | -use cpython_sys::PyModuleDef; |
20 | | -use cpython_sys::PyModuleDef_HEAD_INIT; |
21 | | -use cpython_sys::PyModuleDef_Init; |
22 | | -use cpython_sys::PyObject; |
23 | | -use cpython_sys::PyObject_GetBuffer; |
24 | | - |
25 | | -const PYBUF_SIMPLE: c_int = 0; |
26 | | -const PAD_BYTE: u8 = b'='; |
27 | | -const ENCODE_TABLE: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; |
28 | | - |
29 | | -#[inline] |
30 | | -fn encoded_output_len(input_len: usize) -> Option<usize> { |
31 | | - input_len |
32 | | - .checked_add(2) |
33 | | - .map(|n| n / 3) |
34 | | - .and_then(|blocks| blocks.checked_mul(4)) |
35 | | -} |
36 | | - |
37 | | -#[inline] |
38 | | -fn encode_into(input: &[u8], output: &mut [u8]) -> usize { |
39 | | - let mut src_index = 0; |
40 | | - let mut dst_index = 0; |
41 | | - let len = input.len(); |
42 | | - |
43 | | - while src_index + 3 <= len { |
44 | | - let chunk = (u32::from(input[src_index]) << 16) |
45 | | - | (u32::from(input[src_index + 1]) << 8) |
46 | | - | u32::from(input[src_index + 2]); |
47 | | - output[dst_index] = ENCODE_TABLE[((chunk >> 18) & 0x3f) as usize]; |
48 | | - output[dst_index + 1] = ENCODE_TABLE[((chunk >> 12) & 0x3f) as usize]; |
49 | | - output[dst_index + 2] = ENCODE_TABLE[((chunk >> 6) & 0x3f) as usize]; |
50 | | - output[dst_index + 3] = ENCODE_TABLE[(chunk & 0x3f) as usize]; |
51 | | - src_index += 3; |
52 | | - dst_index += 4; |
53 | | - } |
54 | | - |
55 | | - match len - src_index { |
56 | | - 0 => {} |
57 | | - 1 => { |
58 | | - let chunk = u32::from(input[src_index]) << 16; |
59 | | - output[dst_index] = ENCODE_TABLE[((chunk >> 18) & 0x3f) as usize]; |
60 | | - output[dst_index + 1] = ENCODE_TABLE[((chunk >> 12) & 0x3f) as usize]; |
61 | | - output[dst_index + 2] = PAD_BYTE; |
62 | | - output[dst_index + 3] = PAD_BYTE; |
63 | | - dst_index += 4; |
64 | | - } |
65 | | - 2 => { |
66 | | - let chunk = |
67 | | - (u32::from(input[src_index]) << 16) | (u32::from(input[src_index + 1]) << 8); |
68 | | - output[dst_index] = ENCODE_TABLE[((chunk >> 18) & 0x3f) as usize]; |
69 | | - output[dst_index + 1] = ENCODE_TABLE[((chunk >> 12) & 0x3f) as usize]; |
70 | | - output[dst_index + 2] = ENCODE_TABLE[((chunk >> 6) & 0x3f) as usize]; |
71 | | - output[dst_index + 3] = PAD_BYTE; |
72 | | - dst_index += 4; |
73 | | - } |
74 | | - _ => unreachable!("len - src_index cannot exceed 2"), |
75 | | - } |
76 | | - |
77 | | - dst_index |
78 | | -} |
79 | | - |
80 | | -struct BorrowedBuffer { |
81 | | - view: Py_buffer, |
82 | | -} |
| 1 | +//! The `_base64` module, implemented against `cpython-api`. |
83 | 2 |
|
84 | | -impl BorrowedBuffer { |
85 | | - fn from_object(obj: &PyObject) -> Result<Self, ()> { |
86 | | - let mut view = MaybeUninit::<Py_buffer>::uninit(); |
87 | | - let buffer = unsafe { |
88 | | - if PyObject_GetBuffer(obj.as_raw(), view.as_mut_ptr(), PYBUF_SIMPLE) != 0 { |
89 | | - return Err(()); |
90 | | - } |
91 | | - Self { |
92 | | - view: view.assume_init(), |
93 | | - } |
94 | | - }; |
95 | | - Ok(buffer) |
96 | | - } |
| 3 | +use std::mem::MaybeUninit; |
97 | 4 |
|
98 | | - fn len(&self) -> Py_ssize_t { |
99 | | - self.view.len |
100 | | - } |
| 5 | +use cpython_api::prelude::*; |
101 | 6 |
|
102 | | - fn as_ptr(&self) -> *const u8 { |
103 | | - self.view.buf.cast::<u8>() as *const u8 |
104 | | - } |
105 | | -} |
| 7 | +/// Per-module state, empty since the base64 module has no state |
| 8 | +struct Base64State; |
106 | 9 |
|
107 | | -impl Drop for BorrowedBuffer { |
108 | | - fn drop(&mut self) { |
109 | | - unsafe { |
110 | | - PyBuffer_Release(&mut self.view); |
111 | | - } |
| 10 | +impl ModuleState for Base64State { |
| 11 | + fn new<'py>(_ts: &ThreadState<'py>, _module: &Bound<'py, PyModule>) -> PyResult<Self> { |
| 12 | + Ok(Base64State) |
112 | 13 | } |
113 | 14 | } |
114 | 15 |
|
115 | | -/// # Safety |
116 | | -/// `module` must be a valid pointer of PyObject representing the module. |
117 | | -/// `args` must be a valid pointer to an array of valid PyObject pointers with length `nargs`. |
118 | | -pub unsafe extern "C" fn standard_b64encode( |
119 | | - _module: *mut PyObject, |
120 | | - args: *mut *mut PyObject, |
121 | | - nargs: Py_ssize_t, |
122 | | -) -> *mut PyObject { |
123 | | - if nargs != 1 { |
124 | | - unsafe { |
125 | | - PyErr_SetString( |
126 | | - PyExc_TypeError, |
127 | | - c"standard_b64encode() takes exactly one argument".as_ptr(), |
128 | | - ); |
129 | | - } |
130 | | - return ptr::null_mut(); |
131 | | - } |
132 | | - |
133 | | - let source = unsafe { &**args }; |
| 16 | +const PAD_BYTE: u8 = b'='; |
| 17 | +const ENCODE_TABLE: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; |
134 | 18 |
|
135 | | - // Safe cast by Safety |
136 | | - match standard_b64encode_impl(source) { |
137 | | - Ok(result) => result, |
138 | | - Err(_) => ptr::null_mut(), |
139 | | - } |
| 19 | +/// 4 output bytes per started block of 3 input bytes; `None` on overflow. |
| 20 | +#[inline] |
| 21 | +fn encoded_output_len(input_len: usize) -> Option<usize> { |
| 22 | + input_len.div_ceil(3).checked_mul(4) |
140 | 23 | } |
141 | 24 |
|
142 | | -fn standard_b64encode_impl(source: &PyObject) -> Result<*mut PyObject, ()> { |
143 | | - let buffer = match BorrowedBuffer::from_object(source) { |
144 | | - Ok(buf) => buf, |
145 | | - Err(_) => return Err(()), |
| 25 | +/// Encode `input` into `output`, which must be exactly |
| 26 | +/// `encoded_output_len(input.len())` bytes and is fully initialized on |
| 27 | +/// return. |
| 28 | +fn encode_into(input: &[u8], output: &mut [MaybeUninit<u8>]) { |
| 29 | + let mut chunks = input.chunks_exact(3); |
| 30 | + let mut out = output.iter_mut(); |
| 31 | + let mut put = |b: u8| { |
| 32 | + out.next() |
| 33 | + .expect("output sized to encoded_output_len") |
| 34 | + .write(b); |
146 | 35 | }; |
147 | 36 |
|
148 | | - let view_len = buffer.len(); |
149 | | - if view_len < 0 { |
150 | | - unsafe { |
151 | | - PyErr_SetString( |
152 | | - PyExc_TypeError, |
153 | | - c"standard_b64encode() argument has negative length".as_ptr(), |
154 | | - ); |
155 | | - } |
156 | | - return Err(()); |
| 37 | + for chunk in &mut chunks { |
| 38 | + let group = (u32::from(chunk[0]) << 16) | (u32::from(chunk[1]) << 8) | u32::from(chunk[2]); |
| 39 | + put(ENCODE_TABLE[(group >> 18 & 0x3f) as usize]); |
| 40 | + put(ENCODE_TABLE[(group >> 12 & 0x3f) as usize]); |
| 41 | + put(ENCODE_TABLE[(group >> 6 & 0x3f) as usize]); |
| 42 | + put(ENCODE_TABLE[(group & 0x3f) as usize]); |
157 | 43 | } |
158 | 44 |
|
159 | | - let input_len = view_len as usize; |
160 | | - let input = unsafe { slice::from_raw_parts(buffer.as_ptr(), input_len) }; |
161 | | - |
162 | | - let Some(output_len) = encoded_output_len(input_len) else { |
163 | | - unsafe { |
164 | | - PyErr_NoMemory(); |
| 45 | + match *chunks.remainder() { |
| 46 | + [] => {} |
| 47 | + [a] => { |
| 48 | + let group = u32::from(a) << 16; |
| 49 | + put(ENCODE_TABLE[(group >> 18 & 0x3f) as usize]); |
| 50 | + put(ENCODE_TABLE[(group >> 12 & 0x3f) as usize]); |
| 51 | + put(PAD_BYTE); |
| 52 | + put(PAD_BYTE); |
165 | 53 | } |
166 | | - return Err(()); |
167 | | - }; |
168 | | - |
169 | | - if output_len > isize::MAX as usize { |
170 | | - unsafe { |
171 | | - PyErr_NoMemory(); |
| 54 | + [a, b] => { |
| 55 | + let group = (u32::from(a) << 16) | (u32::from(b) << 8); |
| 56 | + put(ENCODE_TABLE[(group >> 18 & 0x3f) as usize]); |
| 57 | + put(ENCODE_TABLE[(group >> 12 & 0x3f) as usize]); |
| 58 | + put(ENCODE_TABLE[(group >> 6 & 0x3f) as usize]); |
| 59 | + put(PAD_BYTE); |
172 | 60 | } |
173 | | - return Err(()); |
| 61 | + _ => unreachable!("chunks_exact(3) remainder is at most 2 bytes"), |
174 | 62 | } |
175 | | - |
176 | | - let result = unsafe { PyBytes_FromStringAndSize(ptr::null(), output_len as Py_ssize_t) }; |
177 | | - if result.is_null() { |
178 | | - return Err(()); |
179 | | - } |
180 | | - |
181 | | - let dest_ptr = unsafe { PyBytes_AsString(result) }; |
182 | | - if dest_ptr.is_null() { |
183 | | - unsafe { |
184 | | - Py_DecRef(result); |
185 | | - } |
186 | | - return Err(()); |
187 | | - } |
188 | | - let dest = unsafe { slice::from_raw_parts_mut(dest_ptr.cast::<u8>(), output_len) }; |
189 | | - |
190 | | - let written = encode_into(input, dest); |
191 | | - debug_assert_eq!(written, output_len); |
192 | | - Ok(result) |
193 | | -} |
194 | | - |
195 | | -pub extern "C" fn _base64_clear(_obj: *mut PyObject) -> c_int { |
196 | | - //TODO |
197 | | - 0 |
198 | | -} |
199 | | - |
200 | | -pub extern "C" fn _base64_free(_o: *mut c_void) { |
201 | | - //TODO |
202 | 63 | } |
203 | 64 |
|
204 | | -pub struct ModuleDef { |
205 | | - ffi: UnsafeCell<PyModuleDef>, |
| 65 | +/// Encode a bytes-like object with the standard Base64 alphabet. |
| 66 | +#[pyfunction(signature = (data, /))] |
| 67 | +fn standard_b64encode<'py>( |
| 68 | + ts: &ThreadState<'py>, |
| 69 | + _state: &Base64State, |
| 70 | + data: PyBuffer<'py>, |
| 71 | +) -> PyResult<Bound<'py, PyBytes>> { |
| 72 | + let input = data.as_bytes(); |
| 73 | + let Some(output_len) = encoded_output_len(input.len()) else { |
| 74 | + return Err(PyMemoryError::raise(ts, "encoded result too long")); |
| 75 | + }; |
| 76 | + // Write straight into the bytes object's buffer — no intermediate Vec. |
| 77 | + PyBytes::new_with(ts, output_len, |output| { |
| 78 | + encode_into(input, output); |
| 79 | + Ok(()) |
| 80 | + }) |
206 | 81 | } |
207 | 82 |
|
208 | | -impl ModuleDef { |
209 | | - fn init_multi_phase(&'static self) -> *mut PyObject { |
210 | | - unsafe { PyModuleDef_Init(self.ffi.get()) } |
211 | | - } |
| 83 | +fn base64_exec<'py>(_ts: &ThreadState<'py>, _module: &Bound<'py, PyModule>) -> PyResult<()> { |
| 84 | + Ok(()) |
212 | 85 | } |
213 | 86 |
|
214 | | -unsafe impl Sync for ModuleDef {} |
215 | | - |
216 | | -pub static _BASE64_MODULE_METHODS: [PyMethodDef; 2] = { |
217 | | - [ |
218 | | - PyMethodDef { |
219 | | - ml_name: c"standard_b64encode".as_ptr() as *mut c_char, |
220 | | - ml_meth: PyMethodDefFuncPointer { |
221 | | - PyCFunctionFast: standard_b64encode, |
222 | | - }, |
223 | | - ml_flags: METH_FASTCALL, |
224 | | - ml_doc: c"Demo for the _base64 module".as_ptr() as *mut c_char, |
225 | | - }, |
226 | | - PyMethodDef::zeroed(), |
227 | | - ] |
228 | | -}; |
229 | | - |
230 | | -pub static _BASE64_MODULE: ModuleDef = { |
231 | | - ModuleDef { |
232 | | - ffi: UnsafeCell::new(PyModuleDef { |
233 | | - m_base: PyModuleDef_HEAD_INIT, |
234 | | - m_name: c"_base64".as_ptr() as *mut _, |
235 | | - m_doc: c"A test Rust module".as_ptr() as *mut _, |
236 | | - m_size: 0, |
237 | | - m_methods: &_BASE64_MODULE_METHODS as *const PyMethodDef as *mut _, |
238 | | - m_slots: std::ptr::null_mut(), |
239 | | - m_traverse: None, |
240 | | - m_clear: Some(_base64_clear), |
241 | | - m_free: Some(_base64_free), |
242 | | - }), |
243 | | - } |
244 | | -}; |
245 | | - |
246 | | -#[unsafe(no_mangle)] |
247 | | -pub extern "C" fn PyInit__base64() -> *mut PyObject { |
248 | | - _BASE64_MODULE.init_multi_phase() |
| 87 | +export_module! { |
| 88 | + name: _base64, |
| 89 | + doc: c"Base64 encoding implemented in Rust", |
| 90 | + state: Base64State, |
| 91 | + methods: [standard_b64encode], |
| 92 | + exec: base64_exec, |
249 | 93 | } |
0 commit comments