1#![cfg(target_arch = "wasm32")]
12
13use gap::{GapAccept, GapClient as RustGapClient};
14use gbp_core::MemberId;
15use gbp_node::{Event, GroupNode as RustGroupNode};
16use gbp_sframe::{
17 CipherSuite as RustCipherSuite, SFrameDecryptor as RustSFrameDecryptor,
18 SFrameEncryptor as RustSFrameEncryptor, SFrameSession as RustSFrameSession,
19};
20use gsp::{GspAccept, GspClient as RustGspClient, GspError};
21use gtp::{GtpAccept, GtpClient as RustGtpClient};
22use js_sys::{Array, Object, Reflect, Uint8Array};
23use openmls::prelude::tls_codec::Deserialize as TlsDeserialize;
24use openmls::prelude::tls_codec::Serialize as TlsSerialize;
25use openmls::prelude::{KeyPackageIn, OpenMlsProvider, ProtocolVersion};
26use std::cell::RefCell;
27use std::collections::HashMap;
28use wasm_bindgen::prelude::*;
29
30fn set(obj: &Object, key: &str, val: &JsValue) {
33 Reflect::set(obj, &JsValue::from_str(key), val).unwrap_throw();
34}
35
36fn u8s(bytes: &[u8]) -> JsValue {
37 Uint8Array::from(bytes).into()
38}
39
40fn js_err(msg: impl std::fmt::Display) -> JsValue {
41 JsValue::from_str(&msg.to_string())
42}
43
44fn event_to_js(ev: Event) -> JsValue {
45 let obj = Object::new();
46 match ev {
47 Event::PayloadReceived(p) => {
48 set(&obj, "kind", &"payload_received".into());
49 set(
50 &obj,
51 "streamType",
52 &JsValue::from_f64(p.stream_type.as_u8() as f64),
53 );
54 set(&obj, "plaintext", &u8s(&p.plaintext));
55 set(&obj, "sequenceNo", &JsValue::from_f64(p.sequence_no as f64));
56 set(&obj, "codec", &JsValue::from_f64(p.codec as u8 as f64));
57 }
58 Event::StateChanged { from, to } => {
59 set(&obj, "kind", &"state_changed".into());
60 set(&obj, "from", &JsValue::from_str(&from.to_string()));
61 set(&obj, "to", &JsValue::from_str(&to.to_string()));
62 }
63 Event::EpochAdvanced {
64 epoch,
65 transition_id,
66 } => {
67 set(&obj, "kind", &"epoch_advanced".into());
68 set(&obj, "epoch", &js_sys::BigInt::from(epoch).into());
69 set(
70 &obj,
71 "transitionId",
72 &JsValue::from_f64(transition_id as f64),
73 );
74 }
75 Event::Error {
76 code,
77 reason,
78 fatal,
79 retryable,
80 ..
81 } => {
82 set(&obj, "kind", &"error".into());
83 set(&obj, "code", &JsValue::from_f64(code as f64));
84 set(&obj, "reason", &JsValue::from_str(&reason));
85 set(&obj, "fatal", &JsValue::from_bool(fatal));
86 set(&obj, "retryable", &JsValue::from_bool(retryable));
87 }
88 Event::Control {
89 from,
90 opcode,
91 transition_id,
92 ..
93 } => {
94 set(&obj, "kind", &"control".into());
95 set(&obj, "from", &JsValue::from_f64(from as f64));
96 set(&obj, "opcode", &JsValue::from_f64(opcode as u8 as f64));
97 set(
98 &obj,
99 "transitionId",
100 &JsValue::from_f64(transition_id as f64),
101 );
102 }
103 _ => {
104 set(&obj, "kind", &"other".into());
105 }
106 }
107 obj.into()
108}
109
110fn codec_from(c: Option<u8>) -> gbp_core::PayloadCodec {
115 c.and_then(gbp_core::PayloadCodec::from_u8)
116 .unwrap_or(gbp_core::PayloadCodec::Cbor)
117}
118
119#[wasm_bindgen]
124#[derive(Clone, Copy, PartialEq, Eq, Debug)]
125pub enum PayloadCodec {
126 Cbor = 0,
127 Protobuf = 1,
128 FlatBuffers = 2,
129}
130
131#[wasm_bindgen]
133#[derive(Clone, Copy, PartialEq, Eq, Debug)]
134pub enum SignalType {
135 Join = 100,
136 Leave = 101,
137 RoleChange = 102,
138 Mute = 200,
139 Unmute = 201,
140 StreamStart = 300,
141 StreamStop = 301,
142 CodecUpdate = 400,
143}
144
145#[wasm_bindgen]
147#[derive(Clone, Copy, PartialEq, Eq, Debug)]
148pub enum ControlOpcode {
149 PrepareTransition = 0x0001,
150 ReadyForTransition = 0x0002,
151 ExecuteTransition = 0x0003,
152 AbortTransition = 0x0004,
153 GroupStateDigestRequest = 0x0005,
154 GroupStateDigestResponse = 0x0006,
155 ReportInvalidCommit = 0x0007,
156 CapabilitiesAdvertise = 0x0008,
157 Ack = 0x0009,
158 Nack = 0x000A,
159}
160
161#[wasm_bindgen]
163#[derive(Clone, Copy, PartialEq, Eq, Debug)]
164pub enum CipherSuite {
165 Aes128Gcm = 0,
166 Aes256Gcm = 1,
167}
168
169#[wasm_bindgen]
182pub struct MlsContext {
183 inner: RefCell<gbp_mls::MlsContext>,
184 kp_bytes: Vec<u8>,
185}
186
187#[wasm_bindgen]
188impl MlsContext {
189 #[wasm_bindgen(js_name = "create")]
194 pub fn create(user_id: &str) -> Result<MlsContext, JsValue> {
195 let (ctx, kpb) = gbp_mls::MlsContext::new_member(user_id.as_bytes()).map_err(js_err)?;
196 let kp_bytes = kpb
197 .key_package()
198 .tls_serialize_detached()
199 .map_err(|e| js_err(format!("kp serialize: {e:?}")))?;
200 Ok(MlsContext {
201 inner: RefCell::new(ctx),
202 kp_bytes,
203 })
204 }
205
206 #[wasm_bindgen(getter, js_name = "keyPackage")]
209 pub fn key_package(&self) -> Uint8Array {
210 Uint8Array::from(self.kp_bytes.as_slice())
211 }
212
213 #[wasm_bindgen(getter)]
215 pub fn epoch(&self) -> u64 {
216 self.inner.borrow().epoch()
217 }
218
219 #[wasm_bindgen(getter, js_name = "groupId")]
221 pub fn group_id(&self) -> Uint8Array {
222 Uint8Array::from(self.inner.borrow().group_id_16().as_slice())
223 }
224
225 #[wasm_bindgen(js_name = "invite")]
232 pub fn invite(&self, mut key_package_bytes: &[u8]) -> Result<Uint8Array, JsValue> {
233 let mut ctx = self.inner.borrow_mut();
234 let kp_in = KeyPackageIn::tls_deserialize(&mut key_package_bytes)
235 .map_err(|e| js_err(format!("kp parse: {e:?}")))?;
236 let kp = kp_in
237 .validate(ctx.provider.crypto(), ProtocolVersion::Mls10)
238 .map_err(|e| js_err(format!("kp validate: {e:?}")))?;
239 let welcome = ctx.invite(&[kp]).map_err(js_err)?;
240 Ok(Uint8Array::from(welcome.as_slice()))
241 }
242
243 #[wasm_bindgen(js_name = "inviteMany")]
251 pub fn invite_many(&self, key_packages: Array) -> Result<Uint8Array, JsValue> {
252 let mut ctx = self.inner.borrow_mut();
253 let mut kps = Vec::with_capacity(key_packages.length() as usize);
254 for v in key_packages.iter() {
255 let bytes = Uint8Array::new(&v).to_vec();
256 let kp_in = KeyPackageIn::tls_deserialize(&mut bytes.as_slice())
257 .map_err(|e| js_err(format!("kp parse: {e:?}")))?;
258 let kp = kp_in
259 .validate(ctx.provider.crypto(), ProtocolVersion::Mls10)
260 .map_err(|e| js_err(format!("kp validate: {e:?}")))?;
261 kps.push(kp);
262 }
263 if kps.is_empty() {
264 return Err(js_err("inviteMany: no key packages".to_string()));
265 }
266 let welcome = ctx.invite(&kps).map_err(js_err)?;
267 Ok(Uint8Array::from(welcome.as_slice()))
268 }
269
270 #[wasm_bindgen(js_name = "acceptWelcome")]
275 pub fn accept_welcome(&self, welcome_bytes: &[u8]) -> Result<(), JsValue> {
276 self.inner
277 .borrow_mut()
278 .accept_welcome(welcome_bytes)
279 .map_err(js_err)
280 }
281
282 #[wasm_bindgen(js_name = "inviteFull")]
289 pub fn invite_full(&self, mut key_package_bytes: &[u8]) -> Result<JsValue, JsValue> {
290 let mut ctx = self.inner.borrow_mut();
291 let kp_in = KeyPackageIn::tls_deserialize(&mut key_package_bytes)
292 .map_err(|e| js_err(format!("kp parse: {e:?}")))?;
293 let kp = kp_in
294 .validate(ctx.provider.crypto(), ProtocolVersion::Mls10)
295 .map_err(|e| js_err(format!("kp validate: {e:?}")))?;
296 let (commit, welcome) = ctx.invite_full(&[kp]).map_err(js_err)?;
297 let obj = Object::new();
298 set(&obj, "commit", &u8s(&commit));
299 set(&obj, "welcome", &u8s(&welcome));
300 Ok(obj.into())
301 }
302
303 #[wasm_bindgen(js_name = "removeMember")]
309 pub fn remove_member(&self, leaf_index: u32) -> Result<Uint8Array, JsValue> {
310 let mut ctx = self.inner.borrow_mut();
311 let commit = ctx.remove_members(&[leaf_index]).map_err(js_err)?;
312 Ok(Uint8Array::from(commit.as_slice()))
313 }
314
315 #[wasm_bindgen(js_name = "processMessage")]
319 pub fn process_message(&self, msg_bytes: &[u8]) -> Result<String, JsValue> {
320 let mut ctx = self.inner.borrow_mut();
321 let kind = ctx.process_message(msg_bytes).map_err(js_err)?;
322 Ok(match kind {
323 gbp_mls::ProcessedKind::Commit => "commit",
324 gbp_mls::ProcessedKind::Application => "application",
325 gbp_mls::ProcessedKind::Proposal => "proposal",
326 gbp_mls::ProcessedKind::External => "external",
327 }
328 .to_string())
329 }
330
331 #[wasm_bindgen(js_name = "finalizeCommit")]
334 pub fn finalize_commit(&self) -> Result<(), JsValue> {
335 self.inner
336 .borrow_mut()
337 .finalize_pending_commit()
338 .map_err(js_err)
339 }
340
341 #[wasm_bindgen(js_name = "clearPendingCommit")]
344 pub fn clear_pending_commit(&self) -> Result<(), JsValue> {
345 self.inner
346 .borrow_mut()
347 .clear_pending_commit()
348 .map_err(js_err)
349 }
350
351 #[wasm_bindgen(js_name = "exportState")]
356 pub fn export_state(&self) -> Result<Uint8Array, JsValue> {
357 let bytes = self.inner.borrow().export_state().map_err(js_err)?;
358 Ok(Uint8Array::from(bytes.as_slice()))
359 }
360
361 #[wasm_bindgen(js_name = "restoreState")]
365 pub fn restore_state(blob: &[u8]) -> Result<MlsContext, JsValue> {
366 let ctx = gbp_mls::MlsContext::restore_state(blob).map_err(js_err)?;
367 Ok(MlsContext {
368 inner: RefCell::new(ctx),
369 kp_bytes: Vec::new(),
370 })
371 }
372}
373
374#[wasm_bindgen]
385pub struct GroupNode {
386 inner: RefCell<RustGroupNode>,
387}
388
389#[wasm_bindgen]
390impl GroupNode {
391 #[wasm_bindgen(js_name = "create")]
393 pub fn create(leaf_index: u32, group_id_bytes: &[u8]) -> GroupNode {
394 let gid: [u8; 16] = group_id_bytes.try_into().unwrap_or([0u8; 16]);
395 GroupNode {
396 inner: RefCell::new(RustGroupNode::new(leaf_index as MemberId, gid)),
397 }
398 }
399
400 #[wasm_bindgen(js_name = "bootstrapAsCreator")]
402 pub fn bootstrap_as_creator(&self, epoch: u64) {
403 self.inner.borrow_mut().bootstrap_as_creator(epoch);
404 }
405
406 #[wasm_bindgen(js_name = "bootstrapAsJoiner")]
411 pub fn bootstrap_as_joiner(&self, epoch: u64, expected_first_tid: u32) {
412 self.inner
413 .borrow_mut()
414 .bootstrap_as_joiner(epoch, expected_first_tid);
415 }
416
417 #[wasm_bindgen(js_name = "exportOutSeq")]
423 pub fn export_out_seq(&self) -> Uint8Array {
424 Uint8Array::from(self.inner.borrow().export_out_seq().as_slice())
425 }
426
427 #[wasm_bindgen(js_name = "restoreOutSeq")]
429 pub fn restore_out_seq(&self, bytes: &[u8]) {
430 self.inner.borrow_mut().restore_out_seq(bytes);
431 }
432
433 #[wasm_bindgen(js_name = "onWire")]
445 pub fn on_wire(&self, mls: &MlsContext, wire_bytes: &[u8]) -> Array {
446 let mut node = self.inner.borrow_mut();
447 let mut mls_inner = mls.inner.borrow_mut();
448 let events = node
449 .on_wire(&mut *mls_inner, wire_bytes)
450 .unwrap_or_default();
451 let arr = Array::new();
452 for ev in events {
453 arr.push(&event_to_js(ev));
454 }
455 arr
456 }
457
458 #[wasm_bindgen(js_name = "checkTimeouts")]
460 pub fn check_timeouts(&self) -> Array {
461 let arr = Array::new();
462 for ev in self.inner.borrow_mut().check_timeouts() {
463 arr.push(&event_to_js(ev));
464 }
465 arr
466 }
467
468 #[wasm_bindgen(getter, js_name = "lastTransitionId")]
470 pub fn last_transition_id(&self) -> u32 {
471 self.inner.borrow().last_transition_id
472 }
473
474 #[wasm_bindgen(getter, js_name = "currentEpoch")]
476 pub fn current_epoch(&self) -> u64 {
477 self.inner.borrow().current_epoch
478 }
479
480 #[wasm_bindgen(getter, js_name = "memberId")]
482 pub fn member_id(&self) -> u32 {
483 self.inner.borrow().member_id
484 }
485
486 #[wasm_bindgen(js_name = "sendControl")]
492 pub fn send_control(
493 &self,
494 mls: &MlsContext,
495 target: u32,
496 opcode: u16,
497 transition_id: u32,
498 request_id: u32,
499 args: &[u8],
500 ) -> Result<JsValue, JsValue> {
501 let op = gbp_core::ControlOpcode::try_from(opcode)
502 .map_err(|_| js_err(format!("bad opcode 0x{opcode:04X}")))?;
503 let mut node = self.inner.borrow_mut();
504 let mut m = mls.inner.borrow_mut();
505 let of = node
506 .send_control(
507 &mut *m,
508 target as MemberId,
509 op,
510 transition_id,
511 request_id,
512 args.to_vec(),
513 )
514 .map_err(js_err)?;
515 let obj = Object::new();
516 set(&obj, "wire", &u8s(&of.wire));
517 set(&obj, "to", &JsValue::from_f64(of.to as f64));
518 Ok(obj.into())
519 }
520
521 #[wasm_bindgen(js_name = "applyTransition")]
524 pub fn apply_transition(&self, tid: u32) {
525 self.inner.borrow_mut().apply_transition(tid);
526 }
527
528 #[wasm_bindgen(js_name = "drainEvents")]
531 pub fn drain_events(&self) -> Array {
532 let arr = Array::new();
533 for ev in self.inner.borrow_mut().drain_events() {
534 arr.push(&event_to_js(ev));
535 }
536 arr
537 }
538}
539
540#[wasm_bindgen]
555pub struct GtpClient {
556 inner: RefCell<RustGtpClient>,
557}
558
559#[wasm_bindgen]
560impl GtpClient {
561 #[wasm_bindgen(js_name = "create")]
563 pub fn create() -> GtpClient {
564 GtpClient {
565 inner: RefCell::new(RustGtpClient::new()),
566 }
567 }
568
569 #[allow(clippy::explicit_auto_deref)]
578 #[wasm_bindgen(js_name = "send")]
579 pub fn send(
580 &self,
581 node: &GroupNode,
582 mls: &MlsContext,
583 target: u32,
584 message_id: u64,
585 text: &str,
586 codec: Option<u8>,
587 ) -> JsValue {
588 let mut gtp = self.inner.borrow_mut();
589 let mut n = node.inner.borrow_mut();
590 let mut m = mls.inner.borrow_mut();
591 match gtp.send(
592 &mut n,
593 &mut *m,
594 target as MemberId,
595 message_id,
596 text,
597 codec_from(codec),
598 ) {
599 Ok(frame) => {
600 let obj = Object::new();
601 set(&obj, "wire", &u8s(&frame.wire));
602 set(&obj, "to", &JsValue::from_f64(frame.to as f64));
603 obj.into()
604 }
605 Err(_) => JsValue::NULL,
606 }
607 }
608
609 #[wasm_bindgen(js_name = "accept")]
617 pub fn accept(&self, plaintext: &[u8], epoch: u64, codec: Option<u8>) -> JsValue {
618 let mut gtp = self.inner.borrow_mut();
619 match gtp.accept(plaintext, epoch, codec_from(codec)) {
620 Ok(result) => {
621 let (msg, status) = match result {
622 GtpAccept::New(m) => (m, "new"),
623 GtpAccept::Duplicate(m) => (m, "duplicate"),
624 };
625 let text = String::from_utf8_lossy(&msg.content).into_owned();
626 let obj = Object::new();
627 set(&obj, "text", &JsValue::from_str(&text));
628 set(
629 &obj,
630 "messageId",
631 &js_sys::BigInt::from(msg.message_id).into(),
632 );
633 set(&obj, "senderId", &JsValue::from_f64(msg.sender_id as f64));
634 set(&obj, "status", &JsValue::from_str(status));
635 obj.into()
636 }
637 Err(_) => JsValue::NULL,
638 }
639 }
640
641 #[wasm_bindgen(js_name = "reset")]
643 pub fn reset(&self) {
644 self.inner.borrow_mut().reset();
645 }
646}
647
648fn outbound_to_js(of: gbp_node::OutboundFrame) -> JsValue {
652 let obj = Object::new();
653 set(&obj, "wire", &u8s(&of.wire));
654 set(&obj, "to", &JsValue::from_f64(of.to as f64));
655 obj.into()
656}
657
658fn gap_payload_to_js(status: &str, p: gap::GapPayload) -> JsValue {
659 let obj = Object::new();
660 set(&obj, "status", &JsValue::from_str(status));
661 set(&obj, "source", &JsValue::from_f64(p.media_source_id as f64));
662 set(&obj, "seq", &JsValue::from_f64(p.rtp_sequence as f64));
663 set(
664 &obj,
665 "rtpTimestamp",
666 &js_sys::BigInt::from(p.rtp_timestamp).into(),
667 );
668 set(&obj, "opus", &u8s(&p.opus_frame.into_vec()));
669 obj.into()
670}
671
672fn cipher_suite_from(v: u8) -> Result<RustCipherSuite, JsValue> {
673 RustCipherSuite::from_u8(v).ok_or_else(|| js_err(format!("unknown ciphersuite {v}")))
674}
675
676#[wasm_bindgen]
691pub struct GapClient {
692 inner: RefCell<RustGapClient>,
693}
694
695#[wasm_bindgen]
696impl GapClient {
697 #[wasm_bindgen(js_name = "create")]
699 pub fn create() -> GapClient {
700 GapClient {
701 inner: RefCell::new(RustGapClient::new()),
702 }
703 }
704
705 #[allow(clippy::too_many_arguments, clippy::explicit_auto_deref)]
713 #[wasm_bindgen(js_name = "send")]
714 pub fn send(
715 &self,
716 node: &GroupNode,
717 mls: &MlsContext,
718 target: u32,
719 media_source_id: u32,
720 rtp_timestamp: u64,
721 opus: &[u8],
722 codec: Option<u8>,
723 ) -> JsValue {
724 let mut gap = self.inner.borrow_mut();
725 let mut n = node.inner.borrow_mut();
726 let mut m = mls.inner.borrow_mut();
727 match gap.send(
728 &mut n,
729 &mut *m,
730 target as MemberId,
731 media_source_id,
732 rtp_timestamp,
733 opus.to_vec(),
734 codec_from(codec),
735 ) {
736 Ok(of) => outbound_to_js(of),
737 Err(_) => JsValue::NULL,
738 }
739 }
740
741 #[wasm_bindgen(js_name = "accept")]
746 pub fn accept(&self, plaintext: &[u8], epoch: u64, codec: Option<u8>) -> JsValue {
747 let mut gap = self.inner.borrow_mut();
748 match gap.accept(plaintext, epoch, codec_from(codec)) {
749 Ok(GapAccept::New(p)) => gap_payload_to_js("new", p),
750 Ok(GapAccept::Late(p)) => gap_payload_to_js("late", p),
751 Err(_) => JsValue::NULL,
752 }
753 }
754
755 #[wasm_bindgen(js_name = "reset")]
757 pub fn reset(&self) {
758 self.inner.borrow_mut().reset();
759 }
760}
761
762#[wasm_bindgen]
776pub struct GspClient {
777 inner: RefCell<RustGspClient>,
778}
779
780#[wasm_bindgen]
781impl GspClient {
782 #[wasm_bindgen(js_name = "create")]
784 pub fn create() -> GspClient {
785 GspClient {
786 inner: RefCell::new(RustGspClient::new()),
787 }
788 }
789
790 #[allow(clippy::too_many_arguments, clippy::explicit_auto_deref)]
797 #[wasm_bindgen(js_name = "send")]
798 pub fn send(
799 &self,
800 node: &GroupNode,
801 mls: &MlsContext,
802 target: u32,
803 signal_type: u32,
804 role_claim: u32,
805 request_id: u32,
806 codec: Option<u8>,
807 ) -> Result<JsValue, JsValue> {
808 let sig = gbp_core::SignalType::try_from(signal_type)
809 .map_err(|_| js_err(format!("bad signal {signal_type}")))?;
810 let mut gsp = self.inner.borrow_mut();
811 let mut n = node.inner.borrow_mut();
812 let mut m = mls.inner.borrow_mut();
813 gsp.send(
814 &mut n,
815 &mut *m,
816 target as MemberId,
817 sig,
818 role_claim,
819 request_id,
820 codec_from(codec),
821 )
822 .map(outbound_to_js)
823 .map_err(js_err)
824 }
825
826 #[allow(clippy::too_many_arguments, clippy::explicit_auto_deref)]
832 #[wasm_bindgen(js_name = "sendWithArgs")]
833 pub fn send_with_args(
834 &self,
835 node: &GroupNode,
836 mls: &MlsContext,
837 target: u32,
838 signal_type: u32,
839 role_claim: u32,
840 request_id: u32,
841 args: &[u8],
842 codec: Option<u8>,
843 ) -> Result<JsValue, JsValue> {
844 let sig = gbp_core::SignalType::try_from(signal_type)
845 .map_err(|_| js_err(format!("bad signal {signal_type}")))?;
846 let mut gsp = self.inner.borrow_mut();
847 let mut n = node.inner.borrow_mut();
848 let mut m = mls.inner.borrow_mut();
849 gsp.send_with_args(
850 &mut n,
851 &mut *m,
852 target as MemberId,
853 sig,
854 role_claim,
855 request_id,
856 args,
857 codec_from(codec),
858 )
859 .map(outbound_to_js)
860 .map_err(js_err)
861 }
862
863 #[wasm_bindgen(js_name = "accept")]
868 pub fn accept(
869 &self,
870 plaintext: &[u8],
871 epoch: u64,
872 codec: Option<u8>,
873 ) -> Result<JsValue, JsValue> {
874 let mut gsp = self.inner.borrow_mut();
875 match gsp.accept(plaintext, epoch, codec_from(codec)) {
876 Ok(GspAccept {
877 signal,
878 sender_id,
879 role_claim,
880 request_id,
881 }) => {
882 let obj = Object::new();
883 set(&obj, "status", &JsValue::from_str("new"));
884 set(&obj, "signal", &JsValue::from_str(signal.name()));
885 set(&obj, "signalCode", &JsValue::from_f64(signal as u32 as f64));
886 set(&obj, "sender", &JsValue::from_f64(sender_id as f64));
887 set(&obj, "roleClaim", &JsValue::from_f64(role_claim as f64));
888 set(&obj, "requestId", &JsValue::from_f64(request_id as f64));
889 Ok(obj.into())
890 }
891 Err(GspError::DuplicateRequest(rid)) => {
892 let obj = Object::new();
893 set(&obj, "status", &JsValue::from_str("duplicate"));
894 set(&obj, "requestId", &JsValue::from_f64(rid as f64));
895 Ok(obj.into())
896 }
897 Err(e) => Err(js_err(e)),
898 }
899 }
900
901 #[wasm_bindgen(js_name = "reset")]
903 pub fn reset(&self) {
904 self.inner.borrow_mut().reset();
905 }
906}
907
908#[wasm_bindgen]
923pub struct SFrameSession {
924 inner: RefCell<RustSFrameDecryptor>,
925 encryptors: RefCell<HashMap<u32, RustSFrameEncryptor>>,
932}
933
934#[wasm_bindgen]
935impl SFrameSession {
936 #[wasm_bindgen(js_name = "create")]
940 pub fn create(mls: &MlsContext, label: &str, suite: u8) -> Result<SFrameSession, JsValue> {
941 let suite = cipher_suite_from(suite)?;
942 let m = mls.inner.borrow();
943 let session = RustSFrameSession::from_mls(&m, label, suite).map_err(js_err)?;
944 Ok(SFrameSession {
945 inner: RefCell::new(session.decryptor()),
946 encryptors: RefCell::new(HashMap::new()),
947 })
948 }
949
950 #[wasm_bindgen(js_name = "createEncryptor")]
955 pub fn create_encryptor(
956 &self,
957 mls: &MlsContext,
958 leaf_index: u32,
959 label: &str,
960 suite: u8,
961 ) -> Result<SFrameEncryptor, JsValue> {
962 if let Some(existing) = self.encryptors.borrow().get(&leaf_index) {
963 return Ok(SFrameEncryptor {
964 inner: RefCell::new(existing.clone()),
965 });
966 }
967
968 let suite = cipher_suite_from(suite)?;
969 let m = mls.inner.borrow();
970 let session = RustSFrameSession::from_mls(&m, label, suite).map_err(js_err)?;
971 let encryptor = session.encryptor(leaf_index);
972 self.encryptors
973 .borrow_mut()
974 .insert(leaf_index, encryptor.clone());
975 Ok(SFrameEncryptor {
976 inner: RefCell::new(encryptor),
977 })
978 }
979
980 #[wasm_bindgen(js_name = "decrypt")]
984 pub fn decrypt(&self, payload: &[u8], aad: &[u8]) -> Result<JsValue, JsValue> {
985 let mut dec = self.inner.borrow_mut();
986 match dec.decrypt(payload, aad) {
987 Ok((plaintext, leaf)) => {
988 let obj = Object::new();
989 set(&obj, "plaintext", &u8s(&plaintext));
990 set(&obj, "senderLeaf", &JsValue::from_f64(leaf as f64));
991 Ok(obj.into())
992 }
993 Err(e) => Err(js_err(e)),
994 }
995 }
996}
997
998#[wasm_bindgen]
1001pub struct SFrameEncryptor {
1002 inner: RefCell<RustSFrameEncryptor>,
1003}
1004
1005#[wasm_bindgen]
1006impl SFrameEncryptor {
1007 #[wasm_bindgen(js_name = "encrypt")]
1011 pub fn encrypt(&self, plaintext: &[u8], aad: &[u8]) -> Result<Uint8Array, JsValue> {
1012 let mut enc = self.inner.borrow_mut();
1013 let ct = enc.encrypt(plaintext, aad).map_err(js_err)?;
1014 Ok(Uint8Array::from(ct.as_slice()))
1015 }
1016}
1017
1018#[cfg(test)]
1021mod tests;