Skip to content

Commit

Permalink
Merge pull request #4 from awslabs/uuid
Browse files Browse the repository at this point in the history
UUID and crypto random impl
  • Loading branch information
richarddavison authored Nov 2, 2023
2 parents 62b618b + 7bc25a0 commit 90af552
Show file tree
Hide file tree
Showing 15 changed files with 529 additions and 95 deletions.
23 changes: 23 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ Everything else inherited from [Uint8Array](https://developer.mozilla.org/en-US/

[randomBytes](https://nodejs.org/api/crypto.html#cryptorandombytessize-callback)

[randomFill](https://nodejs.org/api/crypto.html#cryptorandomfillbuffer-offset-size-callback)

[randomFillSync](https://nodejs.org/api/crypto.html#cryptorandomfillsyncbuffer-offset-size)

[randomUUID](https://nodejs.org/api/crypto.html#cryptorandomuuidoptions)

## events

[EventEmitter](https://nodejs.org/api/events.html#class-eventemitter)
Expand Down Expand Up @@ -132,7 +138,24 @@ _supports only `decode`_
## uuid

```typescript
export const NIL:string
export function v1():string
export function v3(name:string, namespace:Array|Uint8Array|String):string
export function v4():string
export function v5(name:string, namespace:Array|Uint8Array|String):string
export function parse(value:string):Uint8Array
export function stringify(arr:Array|Uint8Array):string
export function validate(arr:string):boolean
export function version(arr:Array|Uint8Array):number
```

## xml
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ The test runner also has support for filters. Using filters is as simple as addi

## Compatibility matrix

_LLRT does not support all Node.js APIs. It is not a drop in replacement for Node.js, nor will it ever be. Below is a high level overview of supported APIs and modules. For more details consult the [API](API) documentation_
_LLRT does not support all Node.js APIs. It is not a drop in replacement for Node.js, nor will it ever be. Below is a high level overview of supported APIs and modules. For more details consult the [API](API.md) documentation_

| | Node.js | LLRT |
| ------------- | ---------------------------------------- | ----- |
Expand Down Expand Up @@ -72,7 +72,7 @@ LLRT can work with any bundler of your choice. Below are some configurations for

### ESBuild

esbuild index.js --platform=node --target=es2020 --format=esm --bundle --minify --external:@aws-sdk
esbuild index.js --platform=node --target=es2020 --format=esm --bundle --minify --external:@aws-sdk --external:uuid

### Rollup

Expand All @@ -94,7 +94,7 @@ export default {
commonjs(),
terser(),
],
external: ["@aws-sdk"],
external: ["@aws-sdk","uuid"],
};
```

Expand All @@ -116,7 +116,7 @@ export default {
resolve: {
extensions: ['.js'],
},
externals: [nodeExternals(),"@aws-sdk"],
externals: [nodeExternals(),"@aws-sdk","uuid"],
optimization: {
minimize: true,
minimizer: [
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@aws-sdk/lib-dynamodb": "3.438.0",
"@types/jest": "29.5.6",
"@types/readable-stream": "4.0.4",
"@types/uuid": "9.0.6",
"assert": "2.1.0",
"esbuild": "0.19.5",
"esbuild-plugins-node-modules-polyfill": "1.6.1",
Expand All @@ -39,6 +40,6 @@
"stream-browserify": "3.0.0",
"typescript": "5.2.2"
},
"packageManager": "[email protected].0",
"packageManager": "[email protected].1",
"nodeLinker": "pnpm"
}
22 changes: 15 additions & 7 deletions src/console.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,13 +286,21 @@ fn stringify_value<'js>(
if let Some(class_name) = class_name {
result.push_str(&class_name);
result.push(' ');
is_typed_array = match class_name.as_str() {
"Int8Array" | "Uint8Array" | "Uint8ClampedArray"
| "Int16Array" | "Uint16Array" | "Int32Array"
| "Uint32Array" | "Float32Array" | "Float64Array"
| "Buffer" => true,
_ => false,
}
is_typed_array = matches!(
class_name.as_str(),
"Int8Array"
| "Uint8Array"
| "Uint8ClampedArray"
| "Int16Array"
| "Uint16Array"
| "Int32Array"
| "Uint32Array"
| "Int64Array"
| "Uint64Array"
| "Float32Array"
| "Float64Array"
| "Buffer"
)
}

let obj = value.as_object().unwrap();
Expand Down
91 changes: 83 additions & 8 deletions src/crypto.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
use std::slice;

use once_cell::sync::Lazy;
use ring::{
digest::{self, Context as DigestContext},
hmac::{self, Context as HmacContext},
rand::{self, SecureRandom},
rand::{SecureRandom, SystemRandom},
};
use rquickjs::{
function::{Constructor, Opt},
module::{Declarations, Exports, ModuleDef},
prelude::{Func, This},
Class, Ctx, Exception, IntoJs, Result, TypedArray, Value,
prelude::{Func, Rest, This},
Class, Ctx, Error, Exception, Function, IntoJs, Null, Object, Result, TypedArray, Value,
};

use crate::{
encoding::encoder::{bytes_to_b64_string, bytes_to_hex_string},
util::{bytes_to_typed_array, export_default, get_bytes},
util::{
bytes_to_typed_array, export_default, get_bytes, get_checked_len, obj_to_array_buffer,
ResultExt,
},
uuid::uuidv4,
vm::{CtxExtension, ErrorExtensions},
};

pub static SYSTEM_RANDOM: Lazy<SystemRandom> = Lazy::new(SystemRandom::new);

fn encoded_bytes<'js>(ctx: Ctx<'js>, bytes: &[u8], encoding: &str) -> Result<Value<'js>> {
match encoding {
"hex" => {
Expand Down Expand Up @@ -231,16 +241,75 @@ impl ShaHash {
}
}

fn get_random_bytes(ctx: Ctx, length: usize) -> Result<Value> {
#[inline]
pub fn random_byte_array(length: usize) -> Vec<u8> {
let mut vec = vec![0; length];
SYSTEM_RANDOM.fill(&mut vec).unwrap();
vec
}

let rng = rand::SystemRandom::new();
rng.fill(&mut vec).unwrap();
fn get_random_bytes(ctx: Ctx, length: usize) -> Result<Value> {
let random_bytes = random_byte_array(length);

let array_buffer = TypedArray::new(ctx.clone(), vec)?;
let array_buffer = TypedArray::new(ctx.clone(), random_bytes)?;
array_buffer.into_js(&ctx)
}

fn random_fill<'js>(ctx: Ctx<'js>, obj: Object<'js>, args: Rest<Value<'js>>) -> Result<()> {
let args_iter = args.0.into_iter();
let mut args_iter = args_iter.rev();

let callback: Function = args_iter
.next()
.and_then(|v| v.into_function())
.or_throw_msg(&ctx, "Callback required")?;
let size = args_iter
.next()
.and_then(|arg| arg.as_int())
.map(|i| i as usize);
let offset = args_iter
.next()
.and_then(|arg| arg.as_int())
.map(|i| i as usize);

ctx.clone().spawn_exit(async move {
if let Err(err) = random_fill_sync(ctx.clone(), obj.clone(), Opt(offset), Opt(size)) {
let err = err.into_value(&ctx)?;
callback.call((err,))?;

return Ok(());
}
callback.call((Null.into_js(&ctx), obj))?;
Ok::<_, Error>(())
})?;
Ok(())
}

fn random_fill_sync<'js>(
ctx: Ctx<'js>,
obj: Object<'js>,
offset: Opt<usize>,
size: Opt<usize>,
) -> Result<Object<'js>> {
let offset = offset.unwrap_or(0);

if let Some(array_buffer) = obj_to_array_buffer(obj.clone())? {
let checked_len = get_checked_len(array_buffer.len(), size.0, offset);

let raw = array_buffer
.as_raw()
.ok_or("ArrayBuffer is detached")
.or_throw(&ctx)?;
let bytes = unsafe { slice::from_raw_parts_mut(raw.ptr.as_ptr(), raw.len) };

SYSTEM_RANDOM
.fill(&mut bytes[offset..offset + checked_len])
.unwrap();
}

Ok(obj)
}

pub struct CryptoModule;

impl ModuleDef for CryptoModule {
Expand All @@ -250,6 +319,9 @@ impl ModuleDef for CryptoModule {
declare.declare("Crc32")?;
declare.declare("Crc32c")?;
declare.declare("randomBytes")?;
declare.declare("randomUUID")?;
declare.declare("randomFillSync")?;
declare.declare("randomFill")?;

for sha_algorithm in ShaAlgorithm::iterate() {
let class_name = sha_algorithm.class_name();
Expand Down Expand Up @@ -282,6 +354,9 @@ impl ModuleDef for CryptoModule {
default.set("createHash", Func::from(Hash::new))?;
default.set("createHmac", Func::from(Hmac::new))?;
default.set("randomBytes", Func::from(get_random_bytes))?;
default.set("randomUUID", Func::from(uuidv4))?;
default.set("randomFillSync", Func::from(random_fill_sync))?;
default.set("randomFill", Func::from(random_fill))?;
Ok(())
})?;

Expand Down
9 changes: 5 additions & 4 deletions src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,12 @@ where
let proto = Class::<Self>::prototype(ctx.clone())
.or_throw_msg(ctx, "Prototype for EventEmitter not found")?;

let on = Function::new(ctx.clone(), Self::on)?;
let off = Function::new(ctx.clone(), Self::remove_event_listener)?;

proto.set("once", Func::from(Self::once))?;

proto.set("on", Func::from(Self::on))?;
proto.set("on", on.clone())?;

proto.set("emit", Func::from(Self::emit))?;

Expand All @@ -122,14 +125,12 @@ where
Func::from(Self::prepend_once_listener),
)?;

proto.set("off", Func::from(Self::remove_event_listener))?;
proto.set("off", off.clone())?;

proto.set("eventNames", Func::from(Self::event_names))?;

let on: Function = proto.get("on")?;
proto.set("addListener", on)?;

let off: Function = proto.get("off")?;
proto.set("removeListener", off)?;

Ok(())
Expand Down
16 changes: 8 additions & 8 deletions src/net/socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -494,20 +494,20 @@ impl<'js> Socket<'js> {
impl<'js> Server<'js> {
#[qjs(constructor)]
pub fn new(ctx: Ctx<'js>, args: Rest<Value<'js>>) -> Result<Class<'js, Self>> {
let mut iter = args.0.into_iter();
let mut args_iter = args.0.into_iter();

let mut connection_listener = None;
let mut allow_half_open = false;

if let Some(first) = iter.next() {
if let Some(first) = args_iter.next() {
if let Some(connection_listener_arg) = first.as_function() {
connection_listener = Some(connection_listener_arg.clone());
}
if let Some(opts_arg) = first.as_object() {
allow_half_open = opts_arg.get_optional("allowHalfOpen")?.unwrap_or_default();
}
}
if let Some(next) = iter.next() {
if let Some(next) = args_iter.next() {
connection_listener = next.into_function();
}

Expand Down Expand Up @@ -547,7 +547,7 @@ impl<'js> Server<'js> {
ctx: Ctx<'js>,
args: Rest<Value<'js>>,
) -> Result<()> {
let mut iter = args.0.into_iter();
let mut args_iter = args.0.into_iter();
let mut port = None;
let mut path = None;
let mut host = None;
Expand All @@ -559,7 +559,7 @@ impl<'js> Server<'js> {
let allow_half_open = borrow.allow_half_open;
drop(borrow);

if let Some(first) = iter.next() {
if let Some(first) = args_iter.next() {
if let Some(callback_arg) = first.as_function() {
callback = Some(callback_arg.clone());
} else {
Expand All @@ -578,7 +578,7 @@ impl<'js> Server<'js> {

let path = first.into_string();

if let Some(second) = iter.next() {
if let Some(second) = args_iter.next() {
if let Some(callback_arg) = second.as_function() {
callback = Some(callback_arg.clone());
}
Expand All @@ -590,15 +590,15 @@ impl<'js> Server<'js> {
backlog = Some(backlog_arg);
}
}
if let Some(third) = iter.next() {
if let Some(third) = args_iter.next() {
if let Some(callback_arg) = third.as_function() {
callback = Some(callback_arg.clone());
}
if port.is_some() {
if let Some(backlog_arg) = third.as_int() {
backlog = Some(backlog_arg);

callback = iter.next().and_then(|v| v.into_function());
callback = args_iter.next().and_then(|v| v.into_function());
}
}
}
Expand Down
Loading

0 comments on commit 90af552

Please sign in to comment.