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) = gbp_mls::MlsContext::new_member(user_id.as_bytes()).map_err(js_err)?;
195 let kp_bytes = kpb
196 .key_package()
197 .tls_serialize_detached()
198 .map_err(|e| js_err(format!("kp serialize: {e:?}")))?;
199 Ok(MlsContext {
200 inner: RefCell::new(ctx),
201 kp_bytes,
202 })
203 }
204
205 #[wasm_bindgen(getter, js_name = "keyPackage")]
208 pub fn key_package(&self) -> Uint8Array {
209 Uint8Array::from(self.kp_bytes.as_slice())
210 }
211
212 #[wasm_bindgen(getter)]
214 pub fn epoch(&self) -> u64 {
215 self.inner.borrow().epoch()
216 }
217
218 #[wasm_bindgen(getter, js_name = "groupId")]
220 pub fn group_id(&self) -> Uint8Array {
221 Uint8Array::from(self.inner.borrow().group_id_16().as_slice())
222 }
223
224 #[wasm_bindgen(js_name = "invite")]
231 pub fn invite(&self, mut key_package_bytes: &[u8]) -> Result<Uint8Array, JsValue> {
232 let mut ctx = self.inner.borrow_mut();
233 let kp_in = KeyPackageIn::tls_deserialize(&mut key_package_bytes)
234 .map_err(|e| js_err(format!("kp parse: {e:?}")))?;
235 let kp = kp_in
236 .validate(ctx.provider.crypto(), ProtocolVersion::Mls10)
237 .map_err(|e| js_err(format!("kp validate: {e:?}")))?;
238 let welcome = ctx.invite(&[kp]).map_err(js_err)?;
239 Ok(Uint8Array::from(welcome.as_slice()))
240 }
241
242 #[wasm_bindgen(js_name = "inviteMany")]
250 pub fn invite_many(&self, key_packages: Array) -> Result<Uint8Array, JsValue> {
251 let mut ctx = self.inner.borrow_mut();
252 let mut kps = Vec::with_capacity(key_packages.length() as usize);
253 for v in key_packages.iter() {
254 let bytes = Uint8Array::new(&v).to_vec();
255 let kp_in = KeyPackageIn::tls_deserialize(&mut bytes.as_slice())
256 .map_err(|e| js_err(format!("kp parse: {e:?}")))?;
257 let kp = kp_in
258 .validate(ctx.provider.crypto(), ProtocolVersion::Mls10)
259 .map_err(|e| js_err(format!("kp validate: {e:?}")))?;
260 kps.push(kp);
261 }
262 if kps.is_empty() {
263 return Err(js_err("inviteMany: no key packages".to_string()));
264 }
265 let welcome = ctx.invite(&kps).map_err(js_err)?;
266 Ok(Uint8Array::from(welcome.as_slice()))
267 }
268
269 #[wasm_bindgen(js_name = "acceptWelcome")]
274 pub fn accept_welcome(&self, welcome_bytes: &[u8]) -> Result<(), JsValue> {
275 self.inner
276 .borrow_mut()
277 .accept_welcome(welcome_bytes)
278 .map_err(js_err)
279 }
280
281 #[wasm_bindgen(js_name = "inviteFull")]
288 pub fn invite_full(&self, mut key_package_bytes: &[u8]) -> Result<JsValue, JsValue> {
289 let mut ctx = self.inner.borrow_mut();
290 let kp_in = KeyPackageIn::tls_deserialize(&mut key_package_bytes)
291 .map_err(|e| js_err(format!("kp parse: {e:?}")))?;
292 let kp = kp_in
293 .validate(ctx.provider.crypto(), ProtocolVersion::Mls10)
294 .map_err(|e| js_err(format!("kp validate: {e:?}")))?;
295 let (commit, welcome) = ctx.invite_full(&[kp]).map_err(js_err)?;
296 let obj = Object::new();
297 set(&obj, "commit", &u8s(&commit));
298 set(&obj, "welcome", &u8s(&welcome));
299 Ok(obj.into())
300 }
301
302 #[wasm_bindgen(js_name = "removeMember")]
308 pub fn remove_member(&self, leaf_index: u32) -> Result<Uint8Array, JsValue> {
309 let mut ctx = self.inner.borrow_mut();
310 let commit = ctx.remove_members(&[leaf_index]).map_err(js_err)?;
311 Ok(Uint8Array::from(commit.as_slice()))
312 }
313
314 #[wasm_bindgen(js_name = "processMessage")]
318 pub fn process_message(&self, msg_bytes: &[u8]) -> Result<String, JsValue> {
319 let mut ctx = self.inner.borrow_mut();
320 let kind = ctx.process_message(msg_bytes).map_err(js_err)?;
321 Ok(match kind {
322 gbp_mls::ProcessedKind::Commit => "commit",
323 gbp_mls::ProcessedKind::Application => "application",
324 gbp_mls::ProcessedKind::Proposal => "proposal",
325 gbp_mls::ProcessedKind::External => "external",
326 }
327 .to_string())
328 }
329
330 #[wasm_bindgen(js_name = "finalizeCommit")]
333 pub fn finalize_commit(&self) -> Result<(), JsValue> {
334 self.inner
335 .borrow_mut()
336 .finalize_pending_commit()
337 .map_err(js_err)
338 }
339
340 #[wasm_bindgen(js_name = "clearPendingCommit")]
343 pub fn clear_pending_commit(&self) -> Result<(), JsValue> {
344 self.inner
345 .borrow_mut()
346 .clear_pending_commit()
347 .map_err(js_err)
348 }
349
350 #[wasm_bindgen(js_name = "exportState")]
355 pub fn export_state(&self) -> Result<Uint8Array, JsValue> {
356 let bytes = self.inner.borrow().export_state().map_err(js_err)?;
357 Ok(Uint8Array::from(bytes.as_slice()))
358 }
359
360 #[wasm_bindgen(js_name = "restoreState")]
364 pub fn restore_state(blob: &[u8]) -> Result<MlsContext, JsValue> {
365 let ctx = gbp_mls::MlsContext::restore_state(blob).map_err(js_err)?;
366 Ok(MlsContext {
367 inner: RefCell::new(ctx),
368 kp_bytes: Vec::new(),
369 })
370 }
371}
372
373#[wasm_bindgen]
384pub struct GroupNode {
385 inner: RefCell<RustGroupNode>,
386}
387
388#[wasm_bindgen]
389impl GroupNode {
390 #[wasm_bindgen(js_name = "create")]
392 pub fn create(leaf_index: u32, group_id_bytes: &[u8]) -> GroupNode {
393 let gid: [u8; 16] = group_id_bytes.try_into().unwrap_or([0u8; 16]);
394 GroupNode {
395 inner: RefCell::new(RustGroupNode::new(leaf_index as MemberId, gid)),
396 }
397 }
398
399 #[wasm_bindgen(js_name = "bootstrapAsCreator")]
401 pub fn bootstrap_as_creator(&self, epoch: u64) {
402 self.inner.borrow_mut().bootstrap_as_creator(epoch);
403 }
404
405 #[wasm_bindgen(js_name = "bootstrapAsJoiner")]
410 pub fn bootstrap_as_joiner(&self, epoch: u64, expected_first_tid: u32) {
411 self.inner
412 .borrow_mut()
413 .bootstrap_as_joiner(epoch, expected_first_tid);
414 }
415
416 #[wasm_bindgen(js_name = "exportOutSeq")]
422 pub fn export_out_seq(&self) -> Uint8Array {
423 Uint8Array::from(self.inner.borrow().export_out_seq().as_slice())
424 }
425
426 #[wasm_bindgen(js_name = "restoreOutSeq")]
428 pub fn restore_out_seq(&self, bytes: &[u8]) {
429 self.inner.borrow_mut().restore_out_seq(bytes);
430 }
431
432 #[wasm_bindgen(js_name = "onWire")]
444 pub fn on_wire(&self, mls: &MlsContext, wire_bytes: &[u8]) -> Array {
445 let mut node = self.inner.borrow_mut();
446 let mut mls_inner = mls.inner.borrow_mut();
447 let events = node
448 .on_wire(&mut *mls_inner, wire_bytes)
449 .unwrap_or_default();
450 let arr = Array::new();
451 for ev in events {
452 arr.push(&event_to_js(ev));
453 }
454 arr
455 }
456
457 #[wasm_bindgen(js_name = "checkTimeouts")]
459 pub fn check_timeouts(&self) -> Array {
460 let arr = Array::new();
461 for ev in self.inner.borrow_mut().check_timeouts() {
462 arr.push(&event_to_js(ev));
463 }
464 arr
465 }
466
467 #[wasm_bindgen(getter, js_name = "lastTransitionId")]
469 pub fn last_transition_id(&self) -> u32 {
470 self.inner.borrow().last_transition_id
471 }
472
473 #[wasm_bindgen(getter, js_name = "currentEpoch")]
475 pub fn current_epoch(&self) -> u64 {
476 self.inner.borrow().current_epoch
477 }
478
479 #[wasm_bindgen(getter, js_name = "memberId")]
481 pub fn member_id(&self) -> u32 {
482 self.inner.borrow().member_id
483 }
484
485 #[wasm_bindgen(js_name = "sendControl")]
491 pub fn send_control(
492 &self,
493 mls: &MlsContext,
494 target: u32,
495 opcode: u16,
496 transition_id: u32,
497 request_id: u32,
498 args: &[u8],
499 ) -> Result<JsValue, JsValue> {
500 let op = gbp_core::ControlOpcode::try_from(opcode)
501 .map_err(|_| js_err(format!("bad opcode 0x{opcode:04X}")))?;
502 let mut node = self.inner.borrow_mut();
503 let mut m = mls.inner.borrow_mut();
504 let of = node
505 .send_control(
506 &mut *m,
507 target as MemberId,
508 op,
509 transition_id,
510 request_id,
511 args.to_vec(),
512 )
513 .map_err(js_err)?;
514 let obj = Object::new();
515 set(&obj, "wire", &u8s(&of.wire));
516 set(&obj, "to", &JsValue::from_f64(of.to as f64));
517 Ok(obj.into())
518 }
519
520 #[wasm_bindgen(js_name = "applyTransition")]
523 pub fn apply_transition(&self, tid: u32) {
524 self.inner.borrow_mut().apply_transition(tid);
525 }
526
527 #[wasm_bindgen(js_name = "drainEvents")]
530 pub fn drain_events(&self) -> Array {
531 let arr = Array::new();
532 for ev in self.inner.borrow_mut().drain_events() {
533 arr.push(&event_to_js(ev));
534 }
535 arr
536 }
537}
538
539#[wasm_bindgen]
554pub struct GtpClient {
555 inner: RefCell<RustGtpClient>,
556}
557
558#[wasm_bindgen]
559impl GtpClient {
560 #[wasm_bindgen(js_name = "create")]
562 pub fn create() -> GtpClient {
563 GtpClient {
564 inner: RefCell::new(RustGtpClient::new()),
565 }
566 }
567
568 #[allow(clippy::explicit_auto_deref)]
577 #[wasm_bindgen(js_name = "send")]
578 pub fn send(
579 &self,
580 node: &GroupNode,
581 mls: &MlsContext,
582 target: u32,
583 message_id: u64,
584 text: &str,
585 codec: Option<u8>,
586 ) -> JsValue {
587 let mut gtp = self.inner.borrow_mut();
588 let mut n = node.inner.borrow_mut();
589 let mut m = mls.inner.borrow_mut();
590 match gtp.send(
591 &mut n,
592 &mut *m,
593 target as MemberId,
594 message_id,
595 text,
596 codec_from(codec),
597 ) {
598 Ok(frame) => {
599 let obj = Object::new();
600 set(&obj, "wire", &u8s(&frame.wire));
601 set(&obj, "to", &JsValue::from_f64(frame.to as f64));
602 obj.into()
603 }
604 Err(_) => JsValue::NULL,
605 }
606 }
607
608 #[wasm_bindgen(js_name = "accept")]
616 pub fn accept(&self, plaintext: &[u8], epoch: u64, codec: Option<u8>) -> JsValue {
617 let mut gtp = self.inner.borrow_mut();
618 match gtp.accept(plaintext, epoch, codec_from(codec)) {
619 Ok(result) => {
620 let (msg, status) = match result {
621 GtpAccept::New(m) => (m, "new"),
622 GtpAccept::Duplicate(m) => (m, "duplicate"),
623 };
624 let text = String::from_utf8_lossy(&msg.content).into_owned();
625 let obj = Object::new();
626 set(&obj, "text", &JsValue::from_str(&text));
627 set(
628 &obj,
629 "messageId",
630 &js_sys::BigInt::from(msg.message_id).into(),
631 );
632 set(&obj, "senderId", &JsValue::from_f64(msg.sender_id as f64));
633 set(&obj, "status", &JsValue::from_str(status));
634 obj.into()
635 }
636 Err(_) => JsValue::NULL,
637 }
638 }
639
640 #[wasm_bindgen(js_name = "reset")]
642 pub fn reset(&self) {
643 self.inner.borrow_mut().reset();
644 }
645}
646
647fn outbound_to_js(of: gbp_node::OutboundFrame) -> JsValue {
651 let obj = Object::new();
652 set(&obj, "wire", &u8s(&of.wire));
653 set(&obj, "to", &JsValue::from_f64(of.to as f64));
654 obj.into()
655}
656
657fn gap_payload_to_js(status: &str, p: gap::GapPayload) -> JsValue {
658 let obj = Object::new();
659 set(&obj, "status", &JsValue::from_str(status));
660 set(&obj, "source", &JsValue::from_f64(p.media_source_id as f64));
661 set(&obj, "seq", &JsValue::from_f64(p.rtp_sequence as f64));
662 set(
663 &obj,
664 "rtpTimestamp",
665 &js_sys::BigInt::from(p.rtp_timestamp).into(),
666 );
667 set(&obj, "opus", &u8s(&p.opus_frame.into_vec()));
668 obj.into()
669}
670
671fn cipher_suite_from(v: u8) -> Result<RustCipherSuite, JsValue> {
672 RustCipherSuite::from_u8(v).ok_or_else(|| js_err(format!("unknown ciphersuite {v}")))
673}
674
675#[wasm_bindgen]
690pub struct GapClient {
691 inner: RefCell<RustGapClient>,
692}
693
694#[wasm_bindgen]
695impl GapClient {
696 #[wasm_bindgen(js_name = "create")]
698 pub fn create() -> GapClient {
699 GapClient {
700 inner: RefCell::new(RustGapClient::new()),
701 }
702 }
703
704 #[allow(clippy::too_many_arguments, clippy::explicit_auto_deref)]
712 #[wasm_bindgen(js_name = "send")]
713 pub fn send(
714 &self,
715 node: &GroupNode,
716 mls: &MlsContext,
717 target: u32,
718 media_source_id: u32,
719 rtp_timestamp: u64,
720 opus: &[u8],
721 codec: Option<u8>,
722 ) -> JsValue {
723 let mut gap = self.inner.borrow_mut();
724 let mut n = node.inner.borrow_mut();
725 let mut m = mls.inner.borrow_mut();
726 match gap.send(
727 &mut n,
728 &mut *m,
729 target as MemberId,
730 media_source_id,
731 rtp_timestamp,
732 opus.to_vec(),
733 codec_from(codec),
734 ) {
735 Ok(of) => outbound_to_js(of),
736 Err(_) => JsValue::NULL,
737 }
738 }
739
740 #[wasm_bindgen(js_name = "accept")]
745 pub fn accept(&self, plaintext: &[u8], epoch: u64, codec: Option<u8>) -> JsValue {
746 let mut gap = self.inner.borrow_mut();
747 match gap.accept(plaintext, epoch, codec_from(codec)) {
748 Ok(GapAccept::New(p)) => gap_payload_to_js("new", p),
749 Ok(GapAccept::Late(p)) => gap_payload_to_js("late", p),
750 Err(_) => JsValue::NULL,
751 }
752 }
753
754 #[wasm_bindgen(js_name = "reset")]
756 pub fn reset(&self) {
757 self.inner.borrow_mut().reset();
758 }
759}
760
761#[wasm_bindgen]
775pub struct GspClient {
776 inner: RefCell<RustGspClient>,
777}
778
779#[wasm_bindgen]
780impl GspClient {
781 #[wasm_bindgen(js_name = "create")]
783 pub fn create() -> GspClient {
784 GspClient {
785 inner: RefCell::new(RustGspClient::new()),
786 }
787 }
788
789 #[allow(clippy::too_many_arguments, clippy::explicit_auto_deref)]
796 #[wasm_bindgen(js_name = "send")]
797 pub fn send(
798 &self,
799 node: &GroupNode,
800 mls: &MlsContext,
801 target: u32,
802 signal_type: u32,
803 role_claim: u32,
804 request_id: u32,
805 codec: Option<u8>,
806 ) -> Result<JsValue, JsValue> {
807 let sig = gbp_core::SignalType::try_from(signal_type)
808 .map_err(|_| js_err(format!("bad signal {signal_type}")))?;
809 let mut gsp = self.inner.borrow_mut();
810 let mut n = node.inner.borrow_mut();
811 let mut m = mls.inner.borrow_mut();
812 gsp.send(
813 &mut n,
814 &mut *m,
815 target as MemberId,
816 sig,
817 role_claim,
818 request_id,
819 codec_from(codec),
820 )
821 .map(outbound_to_js)
822 .map_err(js_err)
823 }
824
825 #[allow(clippy::too_many_arguments, clippy::explicit_auto_deref)]
831 #[wasm_bindgen(js_name = "sendWithArgs")]
832 pub fn send_with_args(
833 &self,
834 node: &GroupNode,
835 mls: &MlsContext,
836 target: u32,
837 signal_type: u32,
838 role_claim: u32,
839 request_id: u32,
840 args: &[u8],
841 codec: Option<u8>,
842 ) -> Result<JsValue, JsValue> {
843 let sig = gbp_core::SignalType::try_from(signal_type)
844 .map_err(|_| js_err(format!("bad signal {signal_type}")))?;
845 let mut gsp = self.inner.borrow_mut();
846 let mut n = node.inner.borrow_mut();
847 let mut m = mls.inner.borrow_mut();
848 gsp.send_with_args(
849 &mut n,
850 &mut *m,
851 target as MemberId,
852 sig,
853 role_claim,
854 request_id,
855 args,
856 codec_from(codec),
857 )
858 .map(outbound_to_js)
859 .map_err(js_err)
860 }
861
862 #[wasm_bindgen(js_name = "accept")]
867 pub fn accept(
868 &self,
869 plaintext: &[u8],
870 epoch: u64,
871 codec: Option<u8>,
872 ) -> Result<JsValue, JsValue> {
873 let mut gsp = self.inner.borrow_mut();
874 match gsp.accept(plaintext, epoch, codec_from(codec)) {
875 Ok(GspAccept {
876 signal,
877 sender_id,
878 role_claim,
879 request_id,
880 }) => {
881 let obj = Object::new();
882 set(&obj, "status", &JsValue::from_str("new"));
883 set(&obj, "signal", &JsValue::from_str(signal.name()));
884 set(&obj, "signalCode", &JsValue::from_f64(signal as u32 as f64));
885 set(&obj, "sender", &JsValue::from_f64(sender_id as f64));
886 set(&obj, "roleClaim", &JsValue::from_f64(role_claim as f64));
887 set(&obj, "requestId", &JsValue::from_f64(request_id as f64));
888 Ok(obj.into())
889 }
890 Err(GspError::DuplicateRequest(rid)) => {
891 let obj = Object::new();
892 set(&obj, "status", &JsValue::from_str("duplicate"));
893 set(&obj, "requestId", &JsValue::from_f64(rid as f64));
894 Ok(obj.into())
895 }
896 Err(e) => Err(js_err(e)),
897 }
898 }
899
900 #[wasm_bindgen(js_name = "reset")]
902 pub fn reset(&self) {
903 self.inner.borrow_mut().reset();
904 }
905}
906
907#[wasm_bindgen]
922pub struct SFrameSession {
923 inner: RefCell<RustSFrameDecryptor>,
924}
925
926#[wasm_bindgen]
927impl SFrameSession {
928 #[wasm_bindgen(js_name = "create")]
932 pub fn create(mls: &MlsContext, label: &str, suite: u8) -> Result<SFrameSession, JsValue> {
933 let suite = cipher_suite_from(suite)?;
934 let m = mls.inner.borrow();
935 let session = RustSFrameSession::from_mls(&m, label, suite).map_err(js_err)?;
936 Ok(SFrameSession {
937 inner: RefCell::new(session.decryptor()),
938 })
939 }
940
941 #[wasm_bindgen(js_name = "createEncryptor")]
945 pub fn create_encryptor(
946 &self,
947 mls: &MlsContext,
948 leaf_index: u32,
949 label: &str,
950 suite: u8,
951 ) -> Result<SFrameEncryptor, JsValue> {
952 let suite = cipher_suite_from(suite)?;
953 let m = mls.inner.borrow();
954 let session = RustSFrameSession::from_mls(&m, label, suite).map_err(js_err)?;
955 Ok(SFrameEncryptor {
956 inner: RefCell::new(session.encryptor(leaf_index)),
957 })
958 }
959
960 #[wasm_bindgen(js_name = "decrypt")]
964 pub fn decrypt(&self, payload: &[u8], aad: &[u8]) -> Result<JsValue, JsValue> {
965 let mut dec = self.inner.borrow_mut();
966 match dec.decrypt(payload, aad) {
967 Ok((plaintext, leaf)) => {
968 let obj = Object::new();
969 set(&obj, "plaintext", &u8s(&plaintext));
970 set(&obj, "senderLeaf", &JsValue::from_f64(leaf as f64));
971 Ok(obj.into())
972 }
973 Err(e) => Err(js_err(e)),
974 }
975 }
976}
977
978#[wasm_bindgen]
981pub struct SFrameEncryptor {
982 inner: RefCell<RustSFrameEncryptor>,
983}
984
985#[wasm_bindgen]
986impl SFrameEncryptor {
987 #[wasm_bindgen(js_name = "encrypt")]
991 pub fn encrypt(&self, plaintext: &[u8], aad: &[u8]) -> Result<Uint8Array, JsValue> {
992 let mut enc = self.inner.borrow_mut();
993 let ct = enc.encrypt(plaintext, aad).map_err(js_err)?;
994 Ok(Uint8Array::from(ct.as_slice()))
995 }
996}
997
998#[cfg(test)]
1001mod tests;