// Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; #[wasm_bindgen(typescript_custom_section)] const TS_DEFS: &'static str = r#" export interface BinaryMessage { kind: number, payload: Uint8Array; headers: string, } export interface StringMessage { kind: number, payload: string; } "#; #[wasm_bindgen] extern "C" { #[wasm_bindgen(typescript_type = "BinaryMessage")] pub type IBinaryMessage; #[wasm_bindgen(typescript_type = "StringMessage")] pub type IStringMessage; } #[derive(Serialize, Deserialize)] pub struct BinaryMessage { pub kind: u8, pub payload: Vec, pub headers: String, } #[derive(Serialize, Deserialize)] pub struct StringMessage { pub kind: u8, pub payload: String, } /// Create a new binary message with a user-specified `kind`. #[wasm_bindgen] pub fn create_binary_message(kind: u8, payload: Vec) -> Vec { create_binary_message_with_headers(kind, payload, "".to_string()) } /// Create a new message with a UTF-8 encoded string `payload` and a user-specified `kind`. #[wasm_bindgen] pub fn create_binary_message_from_string(kind: u8, payload: String) -> Vec { create_binary_message_with_headers(kind, payload.as_bytes().to_vec(), "".to_string()) } /// Create a new binary message with a user-specified `kind`, and `headers` as a string. #[wasm_bindgen] pub fn create_binary_message_with_headers(kind: u8, payload: Vec, headers: String) -> Vec { let headers = headers.as_bytes().to_vec(); let size = (headers.len() as u64).to_be_bytes().to_vec(); vec![vec![kind], size, headers, payload].concat() } /// Parse the `kind` and byte array `payload` from a byte array #[wasm_bindgen] pub async fn parse_binary_message(message: Vec) -> Result { if message.len() < 2 { return Err(JsError::new( "Could not parse message, as less than 2 bytes long", )); } let (kind, _headers, payload) = parse_binary_payload(&message); Ok(serde_wasm_bindgen::to_value(&BinaryMessage { kind, payload: payload.to_vec(), headers: "".to_string(), }) .unwrap() .unchecked_into::()) } /// Parse the `kind` and byte array `payload` from a byte array with headers #[wasm_bindgen] pub async fn parse_binary_message_with_headers( message: Vec, ) -> Result { if message.len() < 2 { return Err(JsError::new( "Could not parse message, as less than 2 bytes long", )); } let (kind, headers, payload) = parse_binary_payload(&message); Ok(serde_wasm_bindgen::to_value(&BinaryMessage { kind, payload: payload.to_vec(), headers, }) .unwrap() .unchecked_into::()) } /// Parse the `kind` and UTF-8 string `payload` from a byte array with headers #[wasm_bindgen] pub async fn parse_string_message_with_headers( message: Vec, ) -> Result { if message.len() < 2 { return Err(JsError::new( "Could not parse message, as less than 2 bytes long", )); } let (kind, _headers, payload) = parse_binary_payload(&message); let payload = String::from_utf8_lossy(payload).into_owned(); Ok( serde_wasm_bindgen::to_value(&StringMessage { kind, payload }) .unwrap() .unchecked_into::(), ) } pub(crate) fn parse_binary_payload(message: &[u8]) -> (u8, String, &[u8]) { // 1st byte is the kind let kind = message[0]; // then the size as u64 big endian let mut size = [0u8; 8]; size.clone_from_slice(&message[1..9]); let size = u64::from_be_bytes(size) as usize; // then the headers let headers = String::from_utf8_lossy(&message[9..9 + size]).into_owned(); // finally the payload let payload = &message[9 + size..]; (kind, headers, payload) } /// Parse the `kind` and UTF-8 string `payload` from a byte array #[wasm_bindgen] pub async fn parse_string_message(message: Vec) -> Result { if message.len() < 2 { return Err(JsError::new( "Could not parse message, as less than 2 bytes long", )); } let kind = message[0]; let payload = String::from_utf8_lossy(&message[1..]).into_owned(); Ok( serde_wasm_bindgen::to_value(&StringMessage { kind, payload }) .unwrap() .unchecked_into::(), ) } #[cfg(test)] mod tests { use super::{create_binary_message_with_headers, parse_binary_payload}; use wasm_bindgen_test::*; #[wasm_bindgen_test] fn test_binary_with_headers() { let message_as_bytes = create_binary_message_with_headers( 42u8, vec![0u8, 1u8, 2u8], "test headers".to_string(), ); // calculate header size let headers = "test headers".as_bytes().to_vec(); let size = headers.len(); // the expected size let expected_size = 12; assert_eq!(size, expected_size); assert_eq!(message_as_bytes[0], 42u8); assert_eq!(message_as_bytes[1..9], 12u64.to_be_bytes()); assert_eq!( message_as_bytes[9 + expected_size..9 + expected_size + 3], vec![0u8, 1u8, 2u8] ); let res = parse_binary_payload(&message_as_bytes); assert_eq!(res.0, 42u8); assert_eq!(res.1, "test headers".to_string()); assert_eq!(res.2, vec![0u8, 1u8, 2u8]); } #[wasm_bindgen_test] fn test_binary_with_empty_headers() { let message_as_bytes = create_binary_message_with_headers(42u8, vec![0u8, 1u8, 2u8], "".to_string()); let expected_size = 0; assert_eq!(message_as_bytes[0], 42u8); assert_eq!(message_as_bytes[1..9], 0u64.to_be_bytes()); assert_eq!( message_as_bytes[9 + expected_size..9 + expected_size + 3], vec![0u8, 1u8, 2u8] ); let res = parse_binary_payload(&message_as_bytes); assert_eq!(res.0, 42u8); assert_eq!(res.1, "".to_string()); assert_eq!(res.2, vec![0u8, 1u8, 2u8]); } }