Skip to content

Commit 9d33bce

Browse files
committed
feat(crypto): Improve crypto.getRandomValues() compatibility
1 parent 952fd20 commit 9d33bce

11 files changed

Lines changed: 312 additions & 33 deletions

File tree

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

libs/llrt_utils/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ fs = ["tokio/fs"]
1414
bytearray-buffer = ["tokio/sync"]
1515

1616
[dependencies]
17+
half = { version = "2", default-features= false }
1718
memchr = { version = "2", default-features = false, features = ["std"] }
18-
rquickjs = { version = "0.12", features = ["macro"], default-features = false }
19+
rquickjs = { version = "0.12", features = ["half", "macro"], default-features = false }
1920
simdutf8 = { version = "0.1", default-features = false, features = ["std", "aarch64_neon"] }
2021
tokio = { version = "1", features = ["sync"], default-features = false }
2122
tracing = { version = "0.1", default-features = false }

libs/llrt_utils/src/bytes.rs

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0
33
use std::{rc::Rc, slice};
44

5+
use half::f16;
56
use rquickjs::{
67
atom::PredefinedAtom,
78
class::{Trace, Tracer},
@@ -228,9 +229,11 @@ pub enum ObjectBytes<'js> {
228229
I32Array(TypedArray<'js, i32>),
229230
U64Array(TypedArray<'js, u64>),
230231
I64Array(TypedArray<'js, i64>),
232+
F16Array(TypedArray<'js, f16>),
231233
F32Array(TypedArray<'js, f32>),
232234
F64Array(TypedArray<'js, f64>),
233-
DataView(ArrayBuffer<'js>, usize, usize), // buffer, offset, length
235+
U8ClampedArray(ArrayBuffer<'js>, usize, usize), // buffer, offset, length
236+
DataView(ArrayBuffer<'js>, usize, usize), // buffer, offset, length
234237
Vec(Vec<u8>),
235238
}
236239

@@ -250,9 +253,11 @@ impl<'js> Trace<'js> for ObjectBytes<'js> {
250253
ObjectBytes::I32Array(a) => a.trace(tracer),
251254
ObjectBytes::U64Array(a) => a.trace(tracer),
252255
ObjectBytes::I64Array(a) => a.trace(tracer),
256+
ObjectBytes::F16Array(a) => a.trace(tracer),
253257
ObjectBytes::F32Array(a) => a.trace(tracer),
254258
ObjectBytes::F64Array(a) => a.trace(tracer),
255-
ObjectBytes::DataView(d, _, _) => d.trace(tracer),
259+
ObjectBytes::U8ClampedArray(ab, _, _) => ab.trace(tracer),
260+
ObjectBytes::DataView(ab, _, _) => ab.trace(tracer),
256261
ObjectBytes::Vec(v) => v.trace(tracer),
257262
}
258263
}
@@ -269,11 +274,16 @@ impl<'js> IntoJs<'js> for ObjectBytes<'js> {
269274
ObjectBytes::I32Array(a) => a.into_js(ctx),
270275
ObjectBytes::U64Array(a) => a.into_js(ctx),
271276
ObjectBytes::I64Array(a) => a.into_js(ctx),
277+
ObjectBytes::F16Array(a) => a.into_js(ctx),
272278
ObjectBytes::F32Array(a) => a.into_js(ctx),
273279
ObjectBytes::F64Array(a) => a.into_js(ctx),
274-
ObjectBytes::DataView(d, _, _) => {
280+
ObjectBytes::U8ClampedArray(ab, _, _) => {
281+
let ctor: Constructor = ctx.globals().get(PredefinedAtom::Uint8ClampedArray)?;
282+
ctor.construct((ab,))
283+
},
284+
ObjectBytes::DataView(ab, _, _) => {
275285
let ctor: Constructor = ctx.globals().get(PredefinedAtom::DataView)?;
276-
ctor.construct((d,))
286+
ctor.construct((ab,))
277287
},
278288
ObjectBytes::Vec(v) => v.into_js(ctx),
279289
}
@@ -359,11 +369,15 @@ impl<'js> ObjectBytes<'js> {
359369
ObjectBytes::I32Array(array) => array.as_bytes(),
360370
ObjectBytes::U64Array(array) => array.as_bytes(),
361371
ObjectBytes::I64Array(array) => array.as_bytes(),
372+
ObjectBytes::F16Array(array) => array.as_bytes(),
362373
ObjectBytes::F32Array(array) => array.as_bytes(),
363374
ObjectBytes::F64Array(array) => array.as_bytes(),
364-
ObjectBytes::DataView(array_buffer, offset, length) => array_buffer
365-
.as_bytes()
366-
.map(|b| &b[*offset..*offset + *length]),
375+
ObjectBytes::U8ClampedArray(ab, offset, length) => {
376+
ab.as_bytes().map(|b| &b[*offset..*offset + *length])
377+
},
378+
ObjectBytes::DataView(ab, offset, length) => {
379+
ab.as_bytes().map(|b| &b[*offset..*offset + *length])
380+
},
367381
ObjectBytes::Vec(bytes) => Some(bytes.as_ref()),
368382
}
369383
.ok_or(ERROR_MSG_ARRAY_BUFFER_DETACHED.into())
@@ -419,6 +433,10 @@ impl<'js> ObjectBytes<'js> {
419433
return Ok(Some(ObjectBytes::I64Array(typed_array)));
420434
}
421435

436+
if let Ok(typed_array) = TypedArray::<f16>::from_object(obj.clone()) {
437+
return Ok(Some(ObjectBytes::F16Array(typed_array)));
438+
}
439+
422440
if let Ok(typed_array) = TypedArray::<f32>::from_object(obj.clone()) {
423441
return Ok(Some(ObjectBytes::F32Array(typed_array)));
424442
}
@@ -427,24 +445,23 @@ impl<'js> ObjectBytes<'js> {
427445
return Ok(Some(ObjectBytes::F64Array(typed_array)));
428446
}
429447

430-
if let Ok(array_buffer) = obj.get::<_, ArrayBuffer>("buffer") {
431-
let byte_offset: usize = obj.get("byteOffset").unwrap_or(0);
432-
let byte_length: usize = obj.get("byteLength").unwrap_or_else(|_| array_buffer.len());
433-
return Ok(Some(ObjectBytes::DataView(
434-
array_buffer,
435-
byte_offset,
436-
byte_length,
437-
)));
448+
if let Ok(ab) = obj.get::<_, ArrayBuffer>("buffer") {
449+
let tag: String = obj
450+
.get(PredefinedAtom::SymbolToStringTag)
451+
.unwrap_or_default();
452+
let offset: usize = obj.get("byteOffset").unwrap_or(0);
453+
let length: usize = obj.get("byteLength").unwrap_or_else(|_| ab.len());
454+
return Ok(Some(match tag.as_str() {
455+
"Uint8ClampedArray" => ObjectBytes::U8ClampedArray(ab, offset, length),
456+
_ => ObjectBytes::DataView(ab, offset, length),
457+
}));
438458
}
439459

440460
Ok(None)
441461
}
442462

443463
pub fn get_array_buffer(&self) -> Result<Option<(ArrayBuffer<'js>, usize, usize)>> {
444464
let buffer = match self {
445-
ObjectBytes::DataView(array_buffer, offset, length) => {
446-
(array_buffer.clone(), *length, *offset)
447-
},
448465
ObjectBytes::U8Array(typed_array) => {
449466
let byte_length = typed_array.len();
450467
(
@@ -509,6 +526,14 @@ impl<'js> ObjectBytes<'js> {
509526
typed_array.get("byteOffset")?,
510527
)
511528
},
529+
ObjectBytes::F16Array(typed_array) => {
530+
let byte_length = typed_array.len() * 2;
531+
(
532+
typed_array.arraybuffer()?,
533+
byte_length,
534+
typed_array.get("byteOffset")?,
535+
)
536+
},
512537
ObjectBytes::F32Array(typed_array) => {
513538
let byte_length = typed_array.len() * 4;
514539
(
@@ -525,6 +550,12 @@ impl<'js> ObjectBytes<'js> {
525550
typed_array.get("byteOffset")?,
526551
)
527552
},
553+
ObjectBytes::U8ClampedArray(buffer, offset, length) => {
554+
(buffer.clone(), *length, *offset)
555+
},
556+
ObjectBytes::DataView(array_buffer, offset, length) => {
557+
(array_buffer.clone(), *length, *offset)
558+
},
528559
_ => return Ok(None),
529560
};
530561

modules/llrt_crypto/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ crc32fast = { version = "1", default-features = false }
5252
llrt_buffer = { version = "0.8.1-beta", path = "../llrt_buffer" }
5353
llrt_context = { version = "0.8.1-beta", path = "../../libs/llrt_context" }
5454
llrt_encoding = { version = "0.8.1-beta", path = "../../libs/llrt_encoding" }
55+
llrt_exceptions = { version = "0.8.1-beta", path = "../llrt_exceptions" }
5556
llrt_utils = { version = "0.8.1-beta", path = "../../libs/llrt_utils", default-features = false }
5657
md-5 = { version = "0.11", default-features = false }
5758
once_cell = { version = "1", features = ["std"], default-features = false }

modules/llrt_crypto/src/lib.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use std::slice;
3131
use llrt_buffer::Buffer;
3232
use llrt_context::CtxExtension;
3333
use llrt_encoding::{bytes_to_b64_string, bytes_to_hex_string};
34+
use llrt_exceptions::{DOMException, QuotaExceededError};
3435
use llrt_utils::{
3536
bytes::{get_start_end_indexes, ObjectBytes},
3637
error::ErrorExtensions,
@@ -162,9 +163,15 @@ fn get_random_values<'js>(ctx: Ctx<'js>, obj: Object<'js>) -> Result<Object<'js>
162163
if let Some(object_bytes) = ObjectBytes::from_array_buffer(&obj)? {
163164
if matches!(
164165
object_bytes,
165-
ObjectBytes::F64Array(_) | ObjectBytes::F32Array(_)
166+
ObjectBytes::F64Array(_)
167+
| ObjectBytes::F32Array(_)
168+
| ObjectBytes::F16Array(_)
169+
| ObjectBytes::DataView(_, _, _)
166170
) {
167-
return Err(Exception::throw_message(&ctx, "Unsupported TypedArray"));
171+
return Err(DOMException::type_mismatch_error(
172+
&ctx,
173+
"getRandomValues requires an integer TypedArray",
174+
));
168175
}
169176

170177
let (array_buffer, source_length, source_offset) = object_bytes
@@ -176,9 +183,9 @@ fn get_random_values<'js>(ctx: Ctx<'js>, obj: Object<'js>) -> Result<Object<'js>
176183
.or_throw(&ctx)?;
177184

178185
if source_length > 0x10000 {
179-
return Err(Exception::throw_message(
186+
return Err(QuotaExceededError::quota_exceeded_error(
180187
&ctx,
181-
"QuotaExceededError: The requested length exceeds 65,536 bytes",
188+
"The requested length exceeds 65,536 bytes",
182189
));
183190
}
184191

modules/llrt_exceptions/src/lib.rs

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
3+
mod quota_exceeded_error;
4+
5+
use core::fmt;
6+
use std::fmt::Debug;
7+
38
use llrt_utils::{
49
option::Undefined,
510
primordials::{BasePrimordials, Primordial},
@@ -8,22 +13,35 @@ use rquickjs::{
813
atom::PredefinedAtom,
914
class::{
1015
impl_::{CloneTrait, CloneWrapper},
11-
JsClass,
16+
JsClass, Trace,
1217
},
13-
function::{Constructor, Func, Opt},
18+
function::{Constructor, Opt},
1419
object::Property,
15-
prelude::This,
16-
Class, Coerced, Ctx, Exception, FromJs, IntoJs, Object, Result, Value,
20+
prelude::{Func, This},
21+
qjs, Class, Coerced, Ctx, Error, Exception, FromJs, IntoJs, JsLifetime, Object, Result, Value,
1722
};
1823

19-
#[derive(rquickjs::class::Trace, rquickjs::JsLifetime)]
24+
pub use crate::quota_exceeded_error::QuotaExceededError;
25+
use crate::DOMExceptionName::{NotSupportedError, OperationError, TypeMismatchError};
26+
27+
#[derive(Trace, JsLifetime, Debug)]
2028
pub struct DOMException {
2129
name: String,
2230
message: String,
2331
stack: String,
2432
code: u8,
2533
}
2634

35+
impl fmt::Display for DOMException {
36+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37+
f.debug_struct("DOMException")
38+
.field("name", &self.name())
39+
.field("message", &self.message())
40+
.field("stack", &self.stack())
41+
.finish()
42+
}
43+
}
44+
2745
fn add_constants(obj: &Object<'_>) -> Result<()> {
2846
const CONSTANTS: [(&str, u8); 25] = [
2947
("INDEX_SIZE_ERR", 1),
@@ -175,6 +193,43 @@ impl DOMException {
175193
}
176194
}
177195

196+
impl<'js> DOMException {
197+
fn create(
198+
ctx: &Ctx<'js>,
199+
name: DOMExceptionName,
200+
message: impl Into<String>,
201+
) -> Result<Value<'js>> {
202+
let exception = Self::new_with_name(ctx, name, message.into())?;
203+
let ctor: Constructor = ctx.globals().get("DOMException")?;
204+
ctor.construct((exception.message, exception.name))
205+
}
206+
207+
fn throw_value(ctx: &Ctx<'js>, value: Value<'js>) -> Error {
208+
unsafe {
209+
let dup = qjs::JS_DupValue(ctx.as_raw().as_ptr(), value.as_raw());
210+
qjs::JS_Throw(ctx.as_raw().as_ptr(), dup);
211+
}
212+
Error::Exception
213+
}
214+
215+
fn create_error(ctx: &Ctx<'js>, name: DOMExceptionName, message: impl Into<String>) -> Error {
216+
let value = Self::create(ctx, name, message).expect("failed to create DOMException");
217+
Self::throw_value(ctx, value)
218+
}
219+
220+
pub fn not_supported_error(ctx: &Ctx<'js>, message: impl Into<String>) -> Error {
221+
Self::create_error(ctx, NotSupportedError, message)
222+
}
223+
224+
pub fn type_mismatch_error(ctx: &Ctx<'js>, message: impl Into<String>) -> Error {
225+
Self::create_error(ctx, TypeMismatchError, message)
226+
}
227+
228+
pub fn operation_error(ctx: &Ctx<'js>, message: impl Into<String>) -> Error {
229+
Self::create_error(ctx, OperationError, message)
230+
}
231+
}
232+
178233
macro_rules! create_dom_exception {
179234
($name:ident, $($variant:ident),+ $(,)?) => {
180235
#[derive(Debug)]
@@ -294,6 +349,17 @@ pub fn init(ctx: &Ctx<'_>) -> Result<()> {
294349
let primordials = BasePrimordials::get(ctx)?;
295350
dom_ex_proto.set_prototype(Some(&primordials.prototype_error))?;
296351

352+
if let Some(constructor) = Class::<QuotaExceededError>::create_constructor(ctx)? {
353+
// the wpt tests expect this particular property descriptor
354+
globals.prop(
355+
QuotaExceededError::NAME,
356+
Property::from(constructor).writable().configurable(),
357+
)?;
358+
}
359+
360+
let qee_ex_proto = Class::<QuotaExceededError>::prototype(ctx)?.unwrap();
361+
qee_ex_proto.set_prototype(Some(&primordials.prototype_error))?;
362+
297363
// `Error.isError(v)` only returns `true` for objects with QuickJS's
298364
// `[[ErrorData]]` internal slot (class id `JS_CLASS_ERROR`). There is
299365
// no public rquickjs API to tag a class-derived instance with that
@@ -314,5 +380,9 @@ fn is_error<'js>(ctx: Ctx<'js>, value: Value<'js>) -> Result<bool> {
314380
return Ok(false);
315381
};
316382
let dom_exception: Value = ctx.globals().get(DOMException::NAME)?;
317-
Ok(obj.is_instance_of(&dom_exception))
383+
if obj.is_instance_of(&dom_exception) {
384+
return Ok(true);
385+
}
386+
let quota_exceeded_error: Value = ctx.globals().get(QuotaExceededError::NAME)?;
387+
Ok(obj.is_instance_of(&quota_exceeded_error))
318388
}

0 commit comments

Comments
 (0)