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 wasm_bindgen::prelude::*;
28
29fn set(obj: &Object, key: &str, val: &JsValue) {
32 Reflect::set(obj, &JsValue::from_str(key), val).unwrap_throw();
33}
34
35fn u8s(bytes: &[u8]) -> JsValue {
36 Uint8Array::from(bytes).into()
37}
38
39fn js_err(msg: impl std::fmt::Display) -> JsValue {
40 JsValue::from_str(&msg.to_string())
41}
42
43fn event_to_js(ev: Event) -> JsValue {
44 let obj = Object::new();
45 match ev {
46 Event::PayloadReceived(p) => {
47 set(&obj, "kind", &"payload_received".into());
48 set(
49 &obj,
50 "streamType",
51 &JsValue::from_f64(p.stream_type.as_u8() as f64),
52 );
53 set(&obj, "plaintext", &u8s(&p.plaintext));
54 set(&obj, "sequenceNo", &JsValue::from_f64(p.sequence_no as f64));
55 set(&obj, "codec", &JsValue::from_f64(p.codec as u8 as f64));
56 }
57 Event::StateChanged { from, to } => {
58 set(&obj, "kind", &"state_changed".into());
59 set(&obj, "from", &JsValue::from_str(&from.to_string()));
60 set(&obj, "to", &JsValue::from_str(&to.to_string()));
61 }
62 Event::EpochAdvanced {
63 epoch,
64 transition_id,
65 } => {
66 set(&obj, "kind", &"epoch_advanced".into());
67 set(&obj, "epoch", &js_sys::BigInt::from(epoch).into());
68 set(
69 &obj,
70 "transitionId",
71 &JsValue::from_f64(transition_id as f64),
72 );
73 }
74 Event::Error {
75 code,
76 reason,
77 fatal,
78 retryable,
79 ..
80 } => {
81 set(&obj, "kind", &"error".into());
82 set(&obj, "code", &JsValue::from_f64(code as f64));
83 set(&obj, "reason", &JsValue::from_str(&reason));
84 set(&obj, "fatal", &JsValue::from_bool(fatal));
85 set(&obj, "retryable", &JsValue::from_bool(retryable));
86 }
87 Event::Control {
88 from,
89 opcode,
90 transition_id,
91 ..
92 } => {
93 set(&obj, "kind", &"control".into());
94 set(&obj, "from", &JsValue::from_f64(from as f64));
95 set(&obj, "opcode", &JsValue::from_f64(opcode as u8 as f64));
96 set(
97 &obj,
98 "transitionId",
99 &JsValue::from_f64(transition_id as f64),
100 );
101 }
102 _ => {
103 set(&obj, "kind", &"other".into());
104 }
105 }
106 obj.into()
107}
108
109fn codec_from(c: Option<u8>) -> gbp_core::PayloadCodec {
114 c.and_then(gbp_core::PayloadCodec::from_u8)
115 .unwrap_or(gbp_core::PayloadCodec::Cbor)
116}
117
118#[wasm_bindgen]
123#[derive(Clone, Copy, PartialEq, Eq, Debug)]
124pub enum PayloadCodec {
125 Cbor = 0,
126 Protobuf = 1,
127 FlatBuffers = 2,
128}
129
130#[wasm_bindgen]
132#[derive(Clone, Copy, PartialEq, Eq, Debug)]
133pub enum SignalType {
134 Join = 100,
135 Leave = 101,
136 RoleChange = 102,
137 Mute = 200,
138 Unmute = 201,
139 StreamStart = 300,
140 StreamStop = 301,
141 CodecUpdate = 400,
142}
143
144#[wasm_bindgen]
146#[derive(Clone, Copy, PartialEq, Eq, Debug)]
147pub enum ControlOpcode {
148 PrepareTransition = 0x0001,
149 ReadyForTransition = 0x0002,
150 ExecuteTransition = 0x0003,
151 AbortTransition = 0x0004,
152 GroupStateDigestRequest = 0x0005,
153 GroupStateDigestResponse = 0x0006,
154 ReportInvalidCommit = 0x0007,
155 CapabilitiesAdvertise = 0x0008,
156 Ack = 0x0009,
157 Nack = 0x000A,
158}
159
160#[wasm_bindgen]
162#[derive(Clone, Copy, PartialEq, Eq, Debug)]
163pub enum CipherSuite {
164 Aes128Gcm = 0,
165 Aes256Gcm = 1,
166}
167
168#[wasm_bindgen]
181pub struct MlsContext {
182 inner: RefCell<gbp_mls::MlsContext>,
183 kp_bytes: Vec<u8>,
184}
185
186#[wasm_bindgen]
187impl MlsContext {
188 #[wasm_bindgen(js_name = "create")]
193 pub fn create(user_id: &str) -> Result<MlsContext, JsValue> {
194 let (ctx, kpb) =
195 gbp_mls::MlsContext::new_member(user_id.as_bytes()).map_err(|e| js_err(e))?;
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, 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.as_ref())
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(|e| js_err(e))?;
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(|e| js_err(e))?;
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(|e| js_err(e))
280 }
281
282 #[wasm_bindgen(js_name = "inviteFull")]
289 pub fn invite_full(&self, 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.as_ref())
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(|e| js_err(e))?;
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(|e| js_err(e))?;
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(|e| js_err(e))?;
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(|e| js_err(e))
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(|e| js_err(e))
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(|e| js_err(e))?;
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(|e| js_err(e))?;
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(|e| js_err(e))?;
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 #[wasm_bindgen(js_name = "send")]
575 pub fn send(
576 &self,
577 node: &GroupNode,
578 mls: &MlsContext,
579 target: u32,
580 message_id: u64,
581 text: &str,
582 codec: Option<u8>,
583 ) -> JsValue {
584 let mut gtp = self.inner.borrow_mut();
585 let mut n = node.inner.borrow_mut();
586 let mut m = mls.inner.borrow_mut();
587 match gtp.send(
588 &mut *n,
589 &mut *m,
590 target as MemberId,
591 message_id,
592 text,
593 codec_from(codec),
594 ) {
595 Ok(frame) => {
596 let obj = Object::new();
597 set(&obj, "wire", &u8s(&frame.wire));
598 set(&obj, "to", &JsValue::from_f64(frame.to as f64));
599 obj.into()
600 }
601 Err(_) => JsValue::NULL,
602 }
603 }
604
605 #[wasm_bindgen(js_name = "accept")]
613 pub fn accept(&self, plaintext: &[u8], epoch: u64, codec: Option<u8>) -> JsValue {
614 let mut gtp = self.inner.borrow_mut();
615 match gtp.accept(plaintext, epoch, codec_from(codec)) {
616 Ok(result) => {
617 let (msg, status) = match result {
618 GtpAccept::New(m) => (m, "new"),
619 GtpAccept::Duplicate(m) => (m, "duplicate"),
620 };
621 let text = String::from_utf8_lossy(&msg.content).into_owned();
622 let obj = Object::new();
623 set(&obj, "text", &JsValue::from_str(&text));
624 set(
625 &obj,
626 "messageId",
627 &js_sys::BigInt::from(msg.message_id).into(),
628 );
629 set(&obj, "senderId", &JsValue::from_f64(msg.sender_id as f64));
630 set(&obj, "status", &JsValue::from_str(status));
631 obj.into()
632 }
633 Err(_) => JsValue::NULL,
634 }
635 }
636
637 #[wasm_bindgen(js_name = "reset")]
639 pub fn reset(&self) {
640 self.inner.borrow_mut().reset();
641 }
642}
643
644fn outbound_to_js(of: gbp_node::OutboundFrame) -> JsValue {
648 let obj = Object::new();
649 set(&obj, "wire", &u8s(&of.wire));
650 set(&obj, "to", &JsValue::from_f64(of.to as f64));
651 obj.into()
652}
653
654fn gap_payload_to_js(status: &str, p: gap::GapPayload) -> JsValue {
655 let obj = Object::new();
656 set(&obj, "status", &JsValue::from_str(status));
657 set(&obj, "source", &JsValue::from_f64(p.media_source_id as f64));
658 set(&obj, "seq", &JsValue::from_f64(p.rtp_sequence as f64));
659 set(
660 &obj,
661 "rtpTimestamp",
662 &js_sys::BigInt::from(p.rtp_timestamp).into(),
663 );
664 set(&obj, "opus", &u8s(&p.opus_frame.into_vec()));
665 obj.into()
666}
667
668fn cipher_suite_from(v: u8) -> Result<RustCipherSuite, JsValue> {
669 RustCipherSuite::from_u8(v).ok_or_else(|| js_err(format!("unknown ciphersuite {v}")))
670}
671
672#[wasm_bindgen]
687pub struct GapClient {
688 inner: RefCell<RustGapClient>,
689}
690
691#[wasm_bindgen]
692impl GapClient {
693 #[wasm_bindgen(js_name = "create")]
695 pub fn create() -> GapClient {
696 GapClient {
697 inner: RefCell::new(RustGapClient::new()),
698 }
699 }
700
701 #[wasm_bindgen(js_name = "send")]
705 pub fn send(
706 &self,
707 node: &GroupNode,
708 mls: &MlsContext,
709 target: u32,
710 media_source_id: u32,
711 rtp_timestamp: u64,
712 opus: &[u8],
713 codec: Option<u8>,
714 ) -> JsValue {
715 let mut gap = self.inner.borrow_mut();
716 let mut n = node.inner.borrow_mut();
717 let mut m = mls.inner.borrow_mut();
718 match gap.send(
719 &mut *n,
720 &mut *m,
721 target as MemberId,
722 media_source_id,
723 rtp_timestamp,
724 opus.to_vec(),
725 codec_from(codec),
726 ) {
727 Ok(of) => outbound_to_js(of),
728 Err(_) => JsValue::NULL,
729 }
730 }
731
732 #[wasm_bindgen(js_name = "accept")]
737 pub fn accept(&self, plaintext: &[u8], epoch: u64, codec: Option<u8>) -> JsValue {
738 let mut gap = self.inner.borrow_mut();
739 match gap.accept(plaintext, epoch, codec_from(codec)) {
740 Ok(GapAccept::New(p)) => gap_payload_to_js("new", p),
741 Ok(GapAccept::Late(p)) => gap_payload_to_js("late", p),
742 Err(_) => JsValue::NULL,
743 }
744 }
745
746 #[wasm_bindgen(js_name = "reset")]
748 pub fn reset(&self) {
749 self.inner.borrow_mut().reset();
750 }
751}
752
753#[wasm_bindgen]
767pub struct GspClient {
768 inner: RefCell<RustGspClient>,
769}
770
771#[wasm_bindgen]
772impl GspClient {
773 #[wasm_bindgen(js_name = "create")]
775 pub fn create() -> GspClient {
776 GspClient {
777 inner: RefCell::new(RustGspClient::new()),
778 }
779 }
780
781 #[wasm_bindgen(js_name = "send")]
785 pub fn send(
786 &self,
787 node: &GroupNode,
788 mls: &MlsContext,
789 target: u32,
790 signal_type: u32,
791 role_claim: u32,
792 request_id: u32,
793 codec: Option<u8>,
794 ) -> Result<JsValue, JsValue> {
795 let sig = gbp_core::SignalType::try_from(signal_type)
796 .map_err(|_| js_err(format!("bad signal {signal_type}")))?;
797 let mut gsp = self.inner.borrow_mut();
798 let mut n = node.inner.borrow_mut();
799 let mut m = mls.inner.borrow_mut();
800 gsp.send(
801 &mut *n,
802 &mut *m,
803 target as MemberId,
804 sig,
805 role_claim,
806 request_id,
807 codec_from(codec),
808 )
809 .map(outbound_to_js)
810 .map_err(|e| js_err(e))
811 }
812
813 #[wasm_bindgen(js_name = "sendWithArgs")]
816 pub fn send_with_args(
817 &self,
818 node: &GroupNode,
819 mls: &MlsContext,
820 target: u32,
821 signal_type: u32,
822 role_claim: u32,
823 request_id: u32,
824 args: &[u8],
825 codec: Option<u8>,
826 ) -> Result<JsValue, JsValue> {
827 let sig = gbp_core::SignalType::try_from(signal_type)
828 .map_err(|_| js_err(format!("bad signal {signal_type}")))?;
829 let mut gsp = self.inner.borrow_mut();
830 let mut n = node.inner.borrow_mut();
831 let mut m = mls.inner.borrow_mut();
832 gsp.send_with_args(
833 &mut *n,
834 &mut *m,
835 target as MemberId,
836 sig,
837 role_claim,
838 request_id,
839 args,
840 codec_from(codec),
841 )
842 .map(outbound_to_js)
843 .map_err(|e| js_err(e))
844 }
845
846 #[wasm_bindgen(js_name = "accept")]
851 pub fn accept(
852 &self,
853 plaintext: &[u8],
854 epoch: u64,
855 codec: Option<u8>,
856 ) -> Result<JsValue, JsValue> {
857 let mut gsp = self.inner.borrow_mut();
858 match gsp.accept(plaintext, epoch, codec_from(codec)) {
859 Ok(GspAccept {
860 signal,
861 sender_id,
862 role_claim,
863 request_id,
864 }) => {
865 let obj = Object::new();
866 set(&obj, "status", &JsValue::from_str("new"));
867 set(&obj, "signal", &JsValue::from_str(signal.name()));
868 set(&obj, "signalCode", &JsValue::from_f64(signal as u32 as f64));
869 set(&obj, "sender", &JsValue::from_f64(sender_id as f64));
870 set(&obj, "roleClaim", &JsValue::from_f64(role_claim as f64));
871 set(&obj, "requestId", &JsValue::from_f64(request_id as f64));
872 Ok(obj.into())
873 }
874 Err(GspError::DuplicateRequest(rid)) => {
875 let obj = Object::new();
876 set(&obj, "status", &JsValue::from_str("duplicate"));
877 set(&obj, "requestId", &JsValue::from_f64(rid as f64));
878 Ok(obj.into())
879 }
880 Err(e) => Err(js_err(e)),
881 }
882 }
883
884 #[wasm_bindgen(js_name = "reset")]
886 pub fn reset(&self) {
887 self.inner.borrow_mut().reset();
888 }
889}
890
891#[wasm_bindgen]
906pub struct SFrameSession {
907 inner: RefCell<RustSFrameDecryptor>,
908}
909
910#[wasm_bindgen]
911impl SFrameSession {
912 #[wasm_bindgen(js_name = "create")]
916 pub fn create(mls: &MlsContext, label: &str, suite: u8) -> Result<SFrameSession, JsValue> {
917 let suite = cipher_suite_from(suite)?;
918 let m = mls.inner.borrow();
919 let session = RustSFrameSession::from_mls(&m, label, suite).map_err(|e| js_err(e))?;
920 Ok(SFrameSession {
921 inner: RefCell::new(session.decryptor()),
922 })
923 }
924
925 #[wasm_bindgen(js_name = "createEncryptor")]
929 pub fn create_encryptor(
930 &self,
931 mls: &MlsContext,
932 leaf_index: u32,
933 label: &str,
934 suite: u8,
935 ) -> Result<SFrameEncryptor, JsValue> {
936 let suite = cipher_suite_from(suite)?;
937 let m = mls.inner.borrow();
938 let session = RustSFrameSession::from_mls(&m, label, suite).map_err(|e| js_err(e))?;
939 Ok(SFrameEncryptor {
940 inner: RefCell::new(session.encryptor(leaf_index)),
941 })
942 }
943
944 #[wasm_bindgen(js_name = "decrypt")]
948 pub fn decrypt(&self, payload: &[u8], aad: &[u8]) -> Result<JsValue, JsValue> {
949 let mut dec = self.inner.borrow_mut();
950 match dec.decrypt(payload, aad) {
951 Ok((plaintext, leaf)) => {
952 let obj = Object::new();
953 set(&obj, "plaintext", &u8s(&plaintext));
954 set(&obj, "senderLeaf", &JsValue::from_f64(leaf as f64));
955 Ok(obj.into())
956 }
957 Err(e) => Err(js_err(e)),
958 }
959 }
960}
961
962#[wasm_bindgen]
965pub struct SFrameEncryptor {
966 inner: RefCell<RustSFrameEncryptor>,
967}
968
969#[wasm_bindgen]
970impl SFrameEncryptor {
971 #[wasm_bindgen(js_name = "encrypt")]
975 pub fn encrypt(&self, plaintext: &[u8], aad: &[u8]) -> Result<Uint8Array, JsValue> {
976 let mut enc = self.inner.borrow_mut();
977 let ct = enc.encrypt(plaintext, aad).map_err(|e| js_err(e))?;
978 Ok(Uint8Array::from(ct.as_slice()))
979 }
980}
981
982#[cfg(test)]
985mod tests;