Skip to main content

zerodds_ts_wasm/
lib.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! Crate `zerodds-ts-wasm`. Safety classification: **STANDARD** (FFI-Boundary
5//! ueber wasm-bindgen; std erlaubt; kein direkter Hardware-/Syscall-Zugriff).
6//!
7//! WASM-Bindings fuer den ZeroDDS-XCDR-Codec. Im Gegensatz zur Node-Variante
8//! (`zerodds-ts-node`) kann WASM keine UDP-Sockets oder Threads benutzen —
9//! Live-DDS im Browser braucht eine WebSocket-Bridge
10//! (`crates/websocket-bridge`).
11//!
12//! Was hier exposed ist:
13//!   * XCDR1/XCDR2 Encoder + Decoder fuer Primitive + Strings + Bytes
14//!   * KeyHash-Berechnung (XTypes 1.3 §7.6.8)
15//!   * Endianness-Konstanten + Version-String
16//!
17//! Use-Cases:
18//!   * Browser-Frontend wandelt Form-Daten in XCDR um, schickt's per
19//!     WebSocket an einen DDS-Gateway
20//!   * Browser empfaengt XCDR-Bytes, decodiert clientseitig
21//!   * Schema-Validation + Type-Checks ohne Server-Roundtrip
22
23#![no_std]
24// FFI-Bindings ueber `#[wasm_bindgen]`-Makros — die generierten Wrapper
25// bekommen automatisch keine rustdoc und werden vom JS-Layer aus
26// dokumentiert. Allow das hier auf Crate-Ebene; eigene fns sind
27// trotzdem doc'd.
28#![allow(missing_docs)]
29
30extern crate alloc;
31
32use alloc::format;
33use alloc::string::{String, ToString};
34use alloc::vec::Vec;
35use wasm_bindgen::prelude::*;
36use zerodds_cdr::{BufferReader, BufferWriter, Endianness};
37
38#[wasm_bindgen(start)]
39pub fn init() {
40    #[cfg(feature = "console_error_panic_hook")]
41    console_error_panic_hook::set_once();
42}
43
44#[wasm_bindgen]
45pub fn version() -> String {
46    "zerodds-wasm 0.0.0".to_string()
47}
48
49/// Endianness-Tag fuer JS — 0 = little, 1 = big.
50#[wasm_bindgen(js_name = endiannessLittle)]
51pub fn endianness_little() -> u8 {
52    0
53}
54#[wasm_bindgen(js_name = endiannessBig)]
55pub fn endianness_big() -> u8 {
56    1
57}
58
59fn endianness_from_u8(value: u8) -> Result<Endianness, JsError> {
60    match value {
61        0 => Ok(Endianness::Little),
62        1 => Ok(Endianness::Big),
63        other => Err(JsError::new(&format!("invalid endianness {other}"))),
64    }
65}
66
67/// XCDR-Encoder. Buffert Bytes bis `finish()` aufgerufen wird.
68#[wasm_bindgen]
69pub struct CdrEncoder {
70    inner: BufferWriter,
71}
72
73#[wasm_bindgen]
74impl CdrEncoder {
75    #[wasm_bindgen(constructor)]
76    pub fn new(endianness: u8) -> Result<CdrEncoder, JsError> {
77        let endian = endianness_from_u8(endianness)?;
78        Ok(Self {
79            inner: BufferWriter::new(endian),
80        })
81    }
82
83    #[wasm_bindgen(js_name = writeU8)]
84    pub fn write_u8(&mut self, value: u8) -> Result<(), JsError> {
85        self.inner
86            .write_u8(value)
87            .map_err(|e| JsError::new(&format!("write_u8: {e:?}")))
88    }
89
90    #[wasm_bindgen(js_name = writeU16)]
91    pub fn write_u16(&mut self, value: u16) -> Result<(), JsError> {
92        self.inner
93            .write_u16(value)
94            .map_err(|e| JsError::new(&format!("write_u16: {e:?}")))
95    }
96
97    #[wasm_bindgen(js_name = writeU32)]
98    pub fn write_u32(&mut self, value: u32) -> Result<(), JsError> {
99        self.inner
100            .write_u32(value)
101            .map_err(|e| JsError::new(&format!("write_u32: {e:?}")))
102    }
103
104    #[wasm_bindgen(js_name = writeU64)]
105    pub fn write_u64(&mut self, value: u64) -> Result<(), JsError> {
106        self.inner
107            .write_u64(value)
108            .map_err(|e| JsError::new(&format!("write_u64: {e:?}")))
109    }
110
111    #[wasm_bindgen(js_name = writeString)]
112    pub fn write_string(&mut self, value: &str) -> Result<(), JsError> {
113        self.inner
114            .write_string(value)
115            .map_err(|e| JsError::new(&format!("write_string: {e:?}")))
116    }
117
118    #[wasm_bindgen(js_name = writeBytes)]
119    pub fn write_bytes(&mut self, data: &[u8]) -> Result<(), JsError> {
120        self.inner
121            .write_bytes(data)
122            .map_err(|e| JsError::new(&format!("write_bytes: {e:?}")))
123    }
124
125    pub fn align(&mut self, alignment: usize) {
126        self.inner.align(alignment);
127    }
128
129    pub fn position(&self) -> usize {
130        self.inner.position()
131    }
132
133    /// Schliesst den Encoder ab und returnt die akkumulierten Bytes.
134    /// Nach finish() ist der Encoder unbrauchbar.
135    pub fn finish(self) -> Vec<u8> {
136        self.inner.into_bytes()
137    }
138}
139
140/// XCDR-Decoder. Liest Bytes-Slice via Position-Pointer.
141#[wasm_bindgen]
142pub struct CdrDecoder {
143    bytes: Vec<u8>,
144    endianness: Endianness,
145    position: usize,
146}
147
148#[wasm_bindgen]
149impl CdrDecoder {
150    #[wasm_bindgen(constructor)]
151    pub fn new(bytes: Vec<u8>, endianness: u8) -> Result<CdrDecoder, JsError> {
152        let endian = endianness_from_u8(endianness)?;
153        Ok(Self {
154            bytes,
155            endianness: endian,
156            position: 0,
157        })
158    }
159
160    fn reader(&self) -> BufferReader<'_> {
161        let mut r = BufferReader::new(&self.bytes, self.endianness);
162        // Reader-Position ist intern in BufferReader — wir tracken sie
163        // separat und legen sie via skip() vor jedem Read an.
164        let _ = r.read_bytes(self.position);
165        r
166    }
167
168    #[wasm_bindgen(js_name = readU8)]
169    pub fn read_u8(&mut self) -> Result<u8, JsError> {
170        let mut r = self.reader();
171        let v = r
172            .read_u8()
173            .map_err(|e| JsError::new(&format!("read_u8: {e:?}")))?;
174        self.position = r.position();
175        Ok(v)
176    }
177
178    #[wasm_bindgen(js_name = readU16)]
179    pub fn read_u16(&mut self) -> Result<u16, JsError> {
180        let mut r = self.reader();
181        let v = r
182            .read_u16()
183            .map_err(|e| JsError::new(&format!("read_u16: {e:?}")))?;
184        self.position = r.position();
185        Ok(v)
186    }
187
188    #[wasm_bindgen(js_name = readU32)]
189    pub fn read_u32(&mut self) -> Result<u32, JsError> {
190        let mut r = self.reader();
191        let v = r
192            .read_u32()
193            .map_err(|e| JsError::new(&format!("read_u32: {e:?}")))?;
194        self.position = r.position();
195        Ok(v)
196    }
197
198    #[wasm_bindgen(js_name = readU64)]
199    pub fn read_u64(&mut self) -> Result<u64, JsError> {
200        let mut r = self.reader();
201        let v = r
202            .read_u64()
203            .map_err(|e| JsError::new(&format!("read_u64: {e:?}")))?;
204        self.position = r.position();
205        Ok(v)
206    }
207
208    #[wasm_bindgen(js_name = readString)]
209    pub fn read_string(&mut self) -> Result<String, JsError> {
210        let mut r = self.reader();
211        let v = r
212            .read_string()
213            .map_err(|e| JsError::new(&format!("read_string: {e:?}")))?;
214        self.position = r.position();
215        Ok(v)
216    }
217
218    pub fn position(&self) -> usize {
219        self.position
220    }
221
222    pub fn remaining(&self) -> usize {
223        self.bytes.len().saturating_sub(self.position)
224    }
225}