rqjs_ext/modules/
buffer.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3use rquickjs::{
4    atom::PredefinedAtom,
5    cstr,
6    function::{Constructor, Opt},
7    module::{Declarations, Exports, ModuleDef},
8    prelude::{Func, This},
9    Array, ArrayBuffer, Ctx, Exception, IntoJs, Object, Result, TypedArray, Value,
10};
11
12use crate::{
13    // module_builder::ModuleInfo,
14    modules::{encoding::encoder::Encoder, module::export_default},
15    utils::{
16        object::{
17            get_array_buffer_bytes, get_array_bytes, get_bytes, get_coerced_string_bytes,
18            get_start_end_indexes, get_string_bytes, obj_to_array_buffer,
19        },
20        result::ResultExt,
21    },
22};
23
24pub struct Buffer(pub Vec<u8>);
25
26impl<'js> IntoJs<'js> for Buffer {
27    fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
28        let array_buffer = ArrayBuffer::new(ctx.clone(), self.0)?;
29        Self::from_array_buffer(ctx, array_buffer)
30    }
31}
32
33impl<'js> Buffer {
34    pub fn to_string(&self, ctx: &Ctx<'js>, encoding: &str) -> Result<String> {
35        Encoder::from_str(encoding)
36            .and_then(|enc| enc.encode_to_string(self.0.as_ref()))
37            .or_throw(ctx)
38    }
39
40    fn from_array_buffer(ctx: &Ctx<'js>, buffer: ArrayBuffer<'js>) -> Result<Value<'js>> {
41        let constructor: Constructor = ctx.globals().get(stringify!(Buffer))?;
42        constructor.construct((buffer,))
43    }
44
45    fn from_array_buffer_offset_length(
46        ctx: &Ctx<'js>,
47        array_buffer: ArrayBuffer<'js>,
48        offset: usize,
49        length: usize,
50    ) -> Result<Value<'js>> {
51        let constructor: Constructor = ctx.globals().get(stringify!(Buffer))?;
52        constructor.construct((array_buffer, offset, length))
53    }
54
55    fn from_encoding(
56        ctx: &Ctx<'js>,
57        mut bytes: Vec<u8>,
58        encoding: Option<String>,
59    ) -> Result<Value<'js>> {
60        if let Some(encoding) = encoding {
61            let encoder = Encoder::from_str(&encoding).or_throw(ctx)?;
62            bytes = encoder.decode(bytes).or_throw(ctx)?;
63        }
64        Buffer(bytes).into_js(ctx)
65    }
66}
67
68fn byte_length<'js>(ctx: Ctx<'js>, value: Value<'js>, encoding: Opt<String>) -> Result<usize> {
69    //slow path
70    if let Some(encoding) = encoding.0 {
71        let encoder = Encoder::from_str(&encoding).or_throw(&ctx)?;
72        let bytes = get_bytes(&ctx, value)?;
73        return Ok(encoder.decode(bytes).or_throw(&ctx)?.len());
74    }
75    //fast path
76    if let Some(val) = value.as_string() {
77        return Ok(val.to_string()?.len());
78    }
79
80    if value.is_array() {
81        let array = value.as_array().unwrap();
82
83        for val in array.iter::<u8>() {
84            val.or_throw_msg(&ctx, "array value is not u8")?;
85        }
86
87        return Ok(array.len());
88    }
89
90    if let Some(obj) = value.as_object() {
91        if let Some((_, source_length, _)) = obj_to_array_buffer(obj)? {
92            return Ok(source_length);
93        }
94    }
95
96    Err(Exception::throw_message(
97        &ctx,
98        "value must be typed DataView, Buffer, ArrayBuffer, Uint8Array or string",
99    ))
100}
101
102fn to_string(this: This<Object<'_>>, ctx: Ctx, encoding: Opt<String>) -> Result<String> {
103    let typed_array = TypedArray::<u8>::from_object(this.0)?;
104    let bytes: &[u8] = typed_array.as_ref();
105    let encoding = encoding.0.unwrap_or_else(|| String::from("utf-8"));
106    let encoder = Encoder::from_str(&encoding).or_throw(&ctx)?;
107    encoder.encode_to_string(bytes).or_throw(&ctx)
108}
109
110fn alloc<'js>(
111    ctx: Ctx<'js>,
112    length: usize,
113    fill: Opt<Value<'js>>,
114    encoding: Opt<String>,
115) -> Result<Value<'js>> {
116    if let Some(value) = fill.0 {
117        if let Some(value) = value.as_string() {
118            let string = value.to_string()?;
119
120            if let Some(encoding) = encoding.0 {
121                let encoder = Encoder::from_str(&encoding).or_throw(&ctx)?;
122                let bytes = encoder.decode_from_string(string).or_throw(&ctx)?;
123                return alloc_byte_ref(&ctx, &bytes, length);
124            }
125
126            let byte_ref = string.as_bytes();
127
128            return alloc_byte_ref(&ctx, byte_ref, length);
129        }
130        if let Some(value) = value.as_int() {
131            let bytes = vec![value as u8; length];
132            return Buffer(bytes).into_js(&ctx);
133        }
134        if let Some(obj) = value.as_object() {
135            if let Some((array_buffer, source_length, offset)) = obj_to_array_buffer(obj)? {
136                let bytes: &[u8] = array_buffer.as_ref();
137                return alloc_byte_ref(&ctx, &bytes[offset..offset + source_length], length);
138            }
139        }
140    }
141
142    Buffer(vec![0; length]).into_js(&ctx)
143}
144
145fn alloc_byte_ref<'js>(ctx: &Ctx<'js>, byte_ref: &[u8], length: usize) -> Result<Value<'js>> {
146    let mut bytes = vec![0; length];
147    let byte_ref_length = byte_ref.len();
148    for i in 0..length {
149        bytes[i] = byte_ref[i % byte_ref_length];
150    }
151    Buffer(bytes).into_js(ctx)
152}
153
154fn concat<'js>(ctx: Ctx<'js>, list: Array<'js>, max_length: Opt<usize>) -> Result<Value<'js>> {
155    let mut bytes = Vec::new();
156    let mut total_length = 0;
157    let mut length;
158    for value in list.iter::<Object>() {
159        let typed_array = TypedArray::<u8>::from_object(value?)?;
160        let bytes_ref: &[u8] = typed_array.as_ref();
161
162        length = bytes_ref.len();
163
164        if length == 0 {
165            continue;
166        }
167
168        if let Some(max_length) = max_length.0 {
169            total_length += length;
170            if total_length > max_length {
171                let diff = max_length - (total_length - length);
172                bytes.extend_from_slice(&bytes_ref[0..diff]);
173                break;
174            }
175        }
176        bytes.extend_from_slice(bytes_ref);
177    }
178
179    Buffer(bytes).into_js(&ctx)
180}
181
182fn from<'js>(
183    ctx: Ctx<'js>,
184    value: Value<'js>,
185    offset_or_encoding: Opt<Value<'js>>,
186    length: Opt<usize>,
187) -> Result<Value<'js>> {
188    let mut encoding: Option<String> = None;
189    let mut offset = 0;
190
191    if let Some(offset_or_encoding) = offset_or_encoding.0 {
192        if offset_or_encoding.is_string() {
193            encoding = Some(offset_or_encoding.get()?);
194        } else if offset_or_encoding.is_number() {
195            offset = offset_or_encoding.get()?;
196        }
197    }
198
199    println!("encoding {:?}", encoding);
200
201    if let Some(bytes) = get_string_bytes(&value, offset, length.0)? {
202        return Buffer::from_encoding(&ctx, bytes, encoding)?.into_js(&ctx);
203    }
204    if let Some(bytes) = get_array_bytes(&ctx, &value, offset, length.0)? {
205        return Buffer::from_encoding(&ctx, bytes, encoding)?.into_js(&ctx);
206    }
207
208    if let Some(obj) = value.as_object() {
209        if let Some((array_buffer, source_length, source_offset)) = obj_to_array_buffer(obj)? {
210            let (start, end) = get_start_end_indexes(source_length, length.0, offset);
211
212            //buffers from buffer should be copied
213            if obj
214                .get::<_, Option<String>>(PredefinedAtom::Meta)?
215                .as_deref()
216                == Some(stringify!(Buffer))
217                || encoding.is_some()
218            {
219                let bytes = get_array_buffer_bytes(
220                    array_buffer,
221                    start + source_offset,
222                    end - source_offset,
223                );
224                return Buffer::from_encoding(&ctx, bytes, encoding)?.into_js(&ctx);
225            } else {
226                return Buffer::from_array_buffer_offset_length(
227                    &ctx,
228                    array_buffer,
229                    start + source_offset,
230                    end - start,
231                );
232            }
233        }
234    }
235
236    if let Some(bytes) = get_coerced_string_bytes(&value, offset, length.0) {
237        return Buffer::from_encoding(&ctx, bytes, encoding)?.into_js(&ctx);
238    }
239
240    Err(Exception::throw_message(
241        &ctx,
242        "value must be typed DataView, Buffer, ArrayBuffer, Uint8Array or interpretable as string",
243    ))
244}
245
246fn set_prototype<'js>(ctx: &Ctx<'js>, constructor: Object<'js>) -> Result<()> {
247    let _ = &constructor.set(PredefinedAtom::From, Func::from(from))?;
248    let _ = &constructor.set(stringify!(alloc), Func::from(alloc))?;
249    let _ = &constructor.set(stringify!(concat), Func::from(concat))?;
250    let _ = &constructor.set("byteLength", Func::from(byte_length))?;
251
252    let prototype: &Object = &constructor.get(PredefinedAtom::Prototype)?;
253    prototype.set(PredefinedAtom::ToString, Func::from(to_string))?;
254    //not assessable from js
255    prototype.prop(PredefinedAtom::Meta, stringify!(Buffer))?;
256
257    ctx.globals().set(stringify!(Buffer), constructor)?;
258
259    Ok(())
260}
261
262pub fn init<'js>(ctx: &Ctx<'js>) -> Result<()> {
263    let buffer = ctx.eval::<Object<'js>, &str>(&format!(
264        "class {0} extends Uint8Array {{}}\n{0}",
265        stringify!(Buffer)
266    ))?;
267    set_prototype(ctx, buffer)
268}
269
270pub struct BufferModule;
271
272impl ModuleDef for BufferModule {
273    fn declare(declare: &Declarations<'_>) -> Result<()> {
274        declare.declare(stringify!(Buffer))?;
275        declare.declare(cstr!("default").to_bytes())?;
276
277        Ok(())
278    }
279
280    fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> {
281        let globals = ctx.globals();
282        let buf: Constructor = globals.get(stringify!(Buffer))?;
283
284        export_default(ctx, exports, |default| {
285            default.set(stringify!(Buffer), buf)?;
286            Ok(())
287        })?;
288
289        Ok(())
290    }
291}
292
293// impl From<BufferModule> for ModuleInfo<BufferModule> {
294//     fn from(val: BufferModule) -> Self {
295//         ModuleInfo {
296//             name: "buffer",
297//             module: val,
298//         }
299//     }
300// }