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::Serialize as TlsSerialize;
24use openmls::prelude::{KeyPackageIn, OpenMlsProvider, ProtocolVersion};
25use tls_codec::Deserialize as TlsDeserialize;
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(&obj, "streamType", &JsValue::from_f64(p.stream_type.as_u8() as f64));
49 set(&obj, "plaintext", &u8s(&p.plaintext));
50 set(&obj, "sequenceNo", &JsValue::from_f64(p.sequence_no as f64));
51 set(&obj, "codec", &JsValue::from_f64(p.codec as u8 as f64));
52 }
53 Event::StateChanged { from, to } => {
54 set(&obj, "kind", &"state_changed".into());
55 set(&obj, "from", &JsValue::from_str(&from.to_string()));
56 set(&obj, "to", &JsValue::from_str(&to.to_string()));
57 }
58 Event::EpochAdvanced { epoch, transition_id } => {
59 set(&obj, "kind", &"epoch_advanced".into());
60 set(&obj, "epoch", &js_sys::BigInt::from(epoch).into());
61 set(&obj, "transitionId", &JsValue::from_f64(transition_id as f64));
62 }
63 Event::Error { code, reason, fatal, retryable, .. } => {
64 set(&obj, "kind", &"error".into());
65 set(&obj, "code", &JsValue::from_f64(code as f64));
66 set(&obj, "reason", &JsValue::from_str(&reason));
67 set(&obj, "fatal", &JsValue::from_bool(fatal));
68 set(&obj, "retryable", &JsValue::from_bool(retryable));
69 }
70 Event::Control { from, opcode, transition_id, .. } => {
71 set(&obj, "kind", &"control".into());
72 set(&obj, "from", &JsValue::from_f64(from as f64));
73 set(&obj, "opcode", &JsValue::from_f64(opcode as u8 as f64));
74 set(&obj, "transitionId", &JsValue::from_f64(transition_id as f64));
75 }
76 _ => {
77 set(&obj, "kind", &"other".into());
78 }
79 }
80 obj.into()
81}
82
83fn codec_from(c: Option<u8>) -> gbp_core::PayloadCodec {
88 c.and_then(gbp_core::PayloadCodec::from_u8)
89 .unwrap_or(gbp_core::PayloadCodec::Cbor)
90}
91
92#[wasm_bindgen]
97#[derive(Clone, Copy, PartialEq, Eq, Debug)]
98pub enum PayloadCodec {
99 Cbor = 0,
100 Protobuf = 1,
101 FlatBuffers = 2,
102}
103
104#[wasm_bindgen]
106#[derive(Clone, Copy, PartialEq, Eq, Debug)]
107pub enum SignalType {
108 Join = 100,
109 Leave = 101,
110 RoleChange = 102,
111 Mute = 200,
112 Unmute = 201,
113 StreamStart = 300,
114 StreamStop = 301,
115 CodecUpdate = 400,
116}
117
118#[wasm_bindgen]
120#[derive(Clone, Copy, PartialEq, Eq, Debug)]
121pub enum ControlOpcode {
122 PrepareTransition = 0x0001,
123 ReadyForTransition = 0x0002,
124 ExecuteTransition = 0x0003,
125 AbortTransition = 0x0004,
126 GroupStateDigestRequest = 0x0005,
127 GroupStateDigestResponse = 0x0006,
128 ReportInvalidCommit = 0x0007,
129 CapabilitiesAdvertise = 0x0008,
130 Ack = 0x0009,
131 Nack = 0x000A,
132}
133
134#[wasm_bindgen]
136#[derive(Clone, Copy, PartialEq, Eq, Debug)]
137pub enum CipherSuite {
138 Aes128Gcm = 0,
139 Aes256Gcm = 1,
140}
141
142#[wasm_bindgen]
155pub struct MlsContext {
156 inner: RefCell<gbp_mls::MlsContext>,
157 kp_bytes: Vec<u8>,
158}
159
160#[wasm_bindgen]
161impl MlsContext {
162 #[wasm_bindgen(js_name = "create")]
167 pub fn create(user_id: &str) -> Result<MlsContext, JsValue> {
168 let (ctx, kpb) = gbp_mls::MlsContext::new_member(user_id.as_bytes())
169 .map_err(|e| js_err(e))?;
170 let kp_bytes = kpb.key_package()
171 .tls_serialize_detached()
172 .map_err(|e| js_err(format!("kp serialize: {e:?}")))?;
173 Ok(MlsContext { inner: RefCell::new(ctx), kp_bytes })
174 }
175
176 #[wasm_bindgen(getter, js_name = "keyPackage")]
179 pub fn key_package(&self) -> Uint8Array {
180 Uint8Array::from(self.kp_bytes.as_slice())
181 }
182
183 #[wasm_bindgen(getter)]
185 pub fn epoch(&self) -> u64 {
186 self.inner.borrow().epoch()
187 }
188
189 #[wasm_bindgen(getter, js_name = "groupId")]
191 pub fn group_id(&self) -> Uint8Array {
192 Uint8Array::from(self.inner.borrow().group_id_16().as_slice())
193 }
194
195 #[wasm_bindgen(js_name = "invite")]
202 pub fn invite(&self, key_package_bytes: &[u8]) -> Result<Uint8Array, JsValue> {
203 let mut ctx = self.inner.borrow_mut();
204 let kp_in = KeyPackageIn::tls_deserialize(&mut key_package_bytes.as_ref())
205 .map_err(|e| js_err(format!("kp parse: {e:?}")))?;
206 let kp = kp_in
207 .validate(ctx.provider.crypto(), ProtocolVersion::Mls10)
208 .map_err(|e| js_err(format!("kp validate: {e:?}")))?;
209 let welcome = ctx.invite(&[kp]).map_err(|e| js_err(e))?;
210 Ok(Uint8Array::from(welcome.as_slice()))
211 }
212
213 #[wasm_bindgen(js_name = "inviteMany")]
221 pub fn invite_many(&self, key_packages: Array) -> Result<Uint8Array, JsValue> {
222 let mut ctx = self.inner.borrow_mut();
223 let mut kps = Vec::with_capacity(key_packages.length() as usize);
224 for v in key_packages.iter() {
225 let bytes = Uint8Array::new(&v).to_vec();
226 let kp_in = KeyPackageIn::tls_deserialize(&mut bytes.as_slice())
227 .map_err(|e| js_err(format!("kp parse: {e:?}")))?;
228 let kp = kp_in
229 .validate(ctx.provider.crypto(), ProtocolVersion::Mls10)
230 .map_err(|e| js_err(format!("kp validate: {e:?}")))?;
231 kps.push(kp);
232 }
233 if kps.is_empty() {
234 return Err(js_err("inviteMany: no key packages".to_string()));
235 }
236 let welcome = ctx.invite(&kps).map_err(|e| js_err(e))?;
237 Ok(Uint8Array::from(welcome.as_slice()))
238 }
239
240 #[wasm_bindgen(js_name = "acceptWelcome")]
245 pub fn accept_welcome(&self, welcome_bytes: &[u8]) -> Result<(), JsValue> {
246 self.inner.borrow_mut()
247 .accept_welcome(welcome_bytes)
248 .map_err(|e| js_err(e))
249 }
250
251 #[wasm_bindgen(js_name = "inviteFull")]
258 pub fn invite_full(&self, key_package_bytes: &[u8]) -> Result<JsValue, JsValue> {
259 let mut ctx = self.inner.borrow_mut();
260 let kp_in = KeyPackageIn::tls_deserialize(&mut key_package_bytes.as_ref())
261 .map_err(|e| js_err(format!("kp parse: {e:?}")))?;
262 let kp = kp_in
263 .validate(ctx.provider.crypto(), ProtocolVersion::Mls10)
264 .map_err(|e| js_err(format!("kp validate: {e:?}")))?;
265 let (commit, welcome) = ctx.invite_full(&[kp]).map_err(|e| js_err(e))?;
266 let obj = Object::new();
267 set(&obj, "commit", &u8s(&commit));
268 set(&obj, "welcome", &u8s(&welcome));
269 Ok(obj.into())
270 }
271
272 #[wasm_bindgen(js_name = "removeMember")]
278 pub fn remove_member(&self, leaf_index: u32) -> Result<Uint8Array, JsValue> {
279 let mut ctx = self.inner.borrow_mut();
280 let commit = ctx.remove_members(&[leaf_index]).map_err(|e| js_err(e))?;
281 Ok(Uint8Array::from(commit.as_slice()))
282 }
283
284 #[wasm_bindgen(js_name = "processMessage")]
288 pub fn process_message(&self, msg_bytes: &[u8]) -> Result<String, JsValue> {
289 let mut ctx = self.inner.borrow_mut();
290 let kind = ctx.process_message(msg_bytes).map_err(|e| js_err(e))?;
291 Ok(match kind {
292 gbp_mls::ProcessedKind::Commit => "commit",
293 gbp_mls::ProcessedKind::Application => "application",
294 gbp_mls::ProcessedKind::Proposal => "proposal",
295 gbp_mls::ProcessedKind::External => "external",
296 }
297 .to_string())
298 }
299
300 #[wasm_bindgen(js_name = "finalizeCommit")]
303 pub fn finalize_commit(&self) -> Result<(), JsValue> {
304 self.inner.borrow_mut()
305 .finalize_pending_commit()
306 .map_err(|e| js_err(e))
307 }
308
309 #[wasm_bindgen(js_name = "clearPendingCommit")]
312 pub fn clear_pending_commit(&self) -> Result<(), JsValue> {
313 self.inner.borrow_mut()
314 .clear_pending_commit()
315 .map_err(|e| js_err(e))
316 }
317
318 #[wasm_bindgen(js_name = "exportState")]
323 pub fn export_state(&self) -> Result<Uint8Array, JsValue> {
324 let bytes = self.inner.borrow().export_state().map_err(|e| js_err(e))?;
325 Ok(Uint8Array::from(bytes.as_slice()))
326 }
327
328 #[wasm_bindgen(js_name = "restoreState")]
332 pub fn restore_state(blob: &[u8]) -> Result<MlsContext, JsValue> {
333 let ctx = gbp_mls::MlsContext::restore_state(blob).map_err(|e| js_err(e))?;
334 Ok(MlsContext { inner: RefCell::new(ctx), kp_bytes: Vec::new() })
335 }
336}
337
338#[wasm_bindgen]
349pub struct GroupNode {
350 inner: RefCell<RustGroupNode>,
351}
352
353#[wasm_bindgen]
354impl GroupNode {
355 #[wasm_bindgen(js_name = "create")]
357 pub fn create(leaf_index: u32, group_id_bytes: &[u8]) -> GroupNode {
358 let gid: [u8; 16] = group_id_bytes.try_into().unwrap_or([0u8; 16]);
359 GroupNode { inner: RefCell::new(RustGroupNode::new(leaf_index as MemberId, gid)) }
360 }
361
362 #[wasm_bindgen(js_name = "bootstrapAsCreator")]
364 pub fn bootstrap_as_creator(&self, epoch: u64) {
365 self.inner.borrow_mut().bootstrap_as_creator(epoch);
366 }
367
368 #[wasm_bindgen(js_name = "bootstrapAsJoiner")]
373 pub fn bootstrap_as_joiner(&self, epoch: u64, expected_first_tid: u32) {
374 self.inner.borrow_mut().bootstrap_as_joiner(epoch, expected_first_tid);
375 }
376
377 #[wasm_bindgen(js_name = "exportOutSeq")]
383 pub fn export_out_seq(&self) -> Uint8Array {
384 Uint8Array::from(self.inner.borrow().export_out_seq().as_slice())
385 }
386
387 #[wasm_bindgen(js_name = "restoreOutSeq")]
389 pub fn restore_out_seq(&self, bytes: &[u8]) {
390 self.inner.borrow_mut().restore_out_seq(bytes);
391 }
392
393 #[wasm_bindgen(js_name = "onWire")]
405 pub fn on_wire(&self, mls: &MlsContext, wire_bytes: &[u8]) -> Array {
406 let mut node = self.inner.borrow_mut();
407 let mut mls_inner = mls.inner.borrow_mut();
408 let events = node.on_wire(&mut *mls_inner, wire_bytes).unwrap_or_default();
409 let arr = Array::new();
410 for ev in events {
411 arr.push(&event_to_js(ev));
412 }
413 arr
414 }
415
416 #[wasm_bindgen(js_name = "checkTimeouts")]
418 pub fn check_timeouts(&self) -> Array {
419 let arr = Array::new();
420 for ev in self.inner.borrow_mut().check_timeouts() {
421 arr.push(&event_to_js(ev));
422 }
423 arr
424 }
425
426 #[wasm_bindgen(getter, js_name = "lastTransitionId")]
428 pub fn last_transition_id(&self) -> u32 {
429 self.inner.borrow().last_transition_id
430 }
431
432 #[wasm_bindgen(getter, js_name = "currentEpoch")]
434 pub fn current_epoch(&self) -> u64 {
435 self.inner.borrow().current_epoch
436 }
437
438 #[wasm_bindgen(getter, js_name = "memberId")]
440 pub fn member_id(&self) -> u32 {
441 self.inner.borrow().member_id
442 }
443
444 #[wasm_bindgen(js_name = "sendControl")]
450 pub fn send_control(
451 &self,
452 mls: &MlsContext,
453 target: u32,
454 opcode: u16,
455 transition_id: u32,
456 request_id: u32,
457 args: &[u8],
458 ) -> Result<JsValue, JsValue> {
459 let op = gbp_core::ControlOpcode::try_from(opcode)
460 .map_err(|_| js_err(format!("bad opcode 0x{opcode:04X}")))?;
461 let mut node = self.inner.borrow_mut();
462 let mut m = mls.inner.borrow_mut();
463 let of = node
464 .send_control(&mut *m, target as MemberId, op, transition_id, request_id, args.to_vec())
465 .map_err(|e| js_err(e))?;
466 let obj = Object::new();
467 set(&obj, "wire", &u8s(&of.wire));
468 set(&obj, "to", &JsValue::from_f64(of.to as f64));
469 Ok(obj.into())
470 }
471
472 #[wasm_bindgen(js_name = "applyTransition")]
475 pub fn apply_transition(&self, tid: u32) {
476 self.inner.borrow_mut().apply_transition(tid);
477 }
478
479 #[wasm_bindgen(js_name = "drainEvents")]
482 pub fn drain_events(&self) -> Array {
483 let arr = Array::new();
484 for ev in self.inner.borrow_mut().drain_events() {
485 arr.push(&event_to_js(ev));
486 }
487 arr
488 }
489}
490
491#[wasm_bindgen]
506pub struct GtpClient {
507 inner: RefCell<RustGtpClient>,
508}
509
510#[wasm_bindgen]
511impl GtpClient {
512 #[wasm_bindgen(js_name = "create")]
514 pub fn create() -> GtpClient {
515 GtpClient { inner: RefCell::new(RustGtpClient::new()) }
516 }
517
518 #[wasm_bindgen(js_name = "send")]
524 pub fn send(
525 &self,
526 node: &GroupNode,
527 mls: &MlsContext,
528 target: u32,
529 message_id: u64,
530 text: &str,
531 codec: Option<u8>,
532 ) -> JsValue {
533 let mut gtp = self.inner.borrow_mut();
534 let mut n = node.inner.borrow_mut();
535 let mut m = mls.inner.borrow_mut();
536 match gtp.send(&mut *n, &mut *m, target as MemberId, message_id, text, codec_from(codec)) {
537 Ok(frame) => {
538 let obj = Object::new();
539 set(&obj, "wire", &u8s(&frame.wire));
540 set(&obj, "to", &JsValue::from_f64(frame.to as f64));
541 obj.into()
542 }
543 Err(_) => JsValue::NULL,
544 }
545 }
546
547 #[wasm_bindgen(js_name = "accept")]
555 pub fn accept(&self, plaintext: &[u8], epoch: u64, codec: Option<u8>) -> JsValue {
556 let mut gtp = self.inner.borrow_mut();
557 match gtp.accept(plaintext, epoch, codec_from(codec)) {
558 Ok(result) => {
559 let (msg, status) = match result {
560 GtpAccept::New(m) => (m, "new"),
561 GtpAccept::Duplicate(m) => (m, "duplicate"),
562 };
563 let text = String::from_utf8_lossy(&msg.content).into_owned();
564 let obj = Object::new();
565 set(&obj, "text", &JsValue::from_str(&text));
566 set(&obj, "messageId", &js_sys::BigInt::from(msg.message_id).into());
567 set(&obj, "senderId", &JsValue::from_f64(msg.sender_id as f64));
568 set(&obj, "status", &JsValue::from_str(status));
569 obj.into()
570 }
571 Err(_) => JsValue::NULL,
572 }
573 }
574
575 #[wasm_bindgen(js_name = "reset")]
577 pub fn reset(&self) {
578 self.inner.borrow_mut().reset();
579 }
580}
581
582fn outbound_to_js(of: gbp_node::OutboundFrame) -> JsValue {
586 let obj = Object::new();
587 set(&obj, "wire", &u8s(&of.wire));
588 set(&obj, "to", &JsValue::from_f64(of.to as f64));
589 obj.into()
590}
591
592fn gap_payload_to_js(status: &str, p: gap::GapPayload) -> JsValue {
593 let obj = Object::new();
594 set(&obj, "status", &JsValue::from_str(status));
595 set(&obj, "source", &JsValue::from_f64(p.media_source_id as f64));
596 set(&obj, "seq", &JsValue::from_f64(p.rtp_sequence as f64));
597 set(&obj, "rtpTimestamp", &js_sys::BigInt::from(p.rtp_timestamp).into());
598 set(&obj, "opus", &u8s(&p.opus_frame.into_vec()));
599 obj.into()
600}
601
602fn cipher_suite_from(v: u8) -> Result<RustCipherSuite, JsValue> {
603 RustCipherSuite::from_u8(v).ok_or_else(|| js_err(format!("unknown ciphersuite {v}")))
604}
605
606#[wasm_bindgen]
621pub struct GapClient {
622 inner: RefCell<RustGapClient>,
623}
624
625#[wasm_bindgen]
626impl GapClient {
627 #[wasm_bindgen(js_name = "create")]
629 pub fn create() -> GapClient {
630 GapClient { inner: RefCell::new(RustGapClient::new()) }
631 }
632
633 #[wasm_bindgen(js_name = "send")]
637 pub fn send(
638 &self,
639 node: &GroupNode,
640 mls: &MlsContext,
641 target: u32,
642 media_source_id: u32,
643 rtp_timestamp: u64,
644 opus: &[u8],
645 codec: Option<u8>,
646 ) -> JsValue {
647 let mut gap = self.inner.borrow_mut();
648 let mut n = node.inner.borrow_mut();
649 let mut m = mls.inner.borrow_mut();
650 match gap.send(
651 &mut *n,
652 &mut *m,
653 target as MemberId,
654 media_source_id,
655 rtp_timestamp,
656 opus.to_vec(),
657 codec_from(codec),
658 ) {
659 Ok(of) => outbound_to_js(of),
660 Err(_) => JsValue::NULL,
661 }
662 }
663
664 #[wasm_bindgen(js_name = "accept")]
669 pub fn accept(&self, plaintext: &[u8], epoch: u64, codec: Option<u8>) -> JsValue {
670 let mut gap = self.inner.borrow_mut();
671 match gap.accept(plaintext, epoch, codec_from(codec)) {
672 Ok(GapAccept::New(p)) => gap_payload_to_js("new", p),
673 Ok(GapAccept::Late(p)) => gap_payload_to_js("late", p),
674 Err(_) => JsValue::NULL,
675 }
676 }
677
678 #[wasm_bindgen(js_name = "reset")]
680 pub fn reset(&self) {
681 self.inner.borrow_mut().reset();
682 }
683}
684
685#[wasm_bindgen]
699pub struct GspClient {
700 inner: RefCell<RustGspClient>,
701}
702
703#[wasm_bindgen]
704impl GspClient {
705 #[wasm_bindgen(js_name = "create")]
707 pub fn create() -> GspClient {
708 GspClient { inner: RefCell::new(RustGspClient::new()) }
709 }
710
711 #[wasm_bindgen(js_name = "send")]
715 pub fn send(
716 &self,
717 node: &GroupNode,
718 mls: &MlsContext,
719 target: u32,
720 signal_type: u32,
721 role_claim: u32,
722 request_id: u32,
723 codec: Option<u8>,
724 ) -> Result<JsValue, JsValue> {
725 let sig = gbp_core::SignalType::try_from(signal_type)
726 .map_err(|_| js_err(format!("bad signal {signal_type}")))?;
727 let mut gsp = self.inner.borrow_mut();
728 let mut n = node.inner.borrow_mut();
729 let mut m = mls.inner.borrow_mut();
730 gsp.send(&mut *n, &mut *m, target as MemberId, sig, role_claim, request_id, codec_from(codec))
731 .map(outbound_to_js)
732 .map_err(|e| js_err(e))
733 }
734
735 #[wasm_bindgen(js_name = "sendWithArgs")]
738 pub fn send_with_args(
739 &self,
740 node: &GroupNode,
741 mls: &MlsContext,
742 target: u32,
743 signal_type: u32,
744 role_claim: u32,
745 request_id: u32,
746 args: &[u8],
747 codec: Option<u8>,
748 ) -> Result<JsValue, JsValue> {
749 let sig = gbp_core::SignalType::try_from(signal_type)
750 .map_err(|_| js_err(format!("bad signal {signal_type}")))?;
751 let mut gsp = self.inner.borrow_mut();
752 let mut n = node.inner.borrow_mut();
753 let mut m = mls.inner.borrow_mut();
754 gsp.send_with_args(&mut *n, &mut *m, target as MemberId, sig, role_claim, request_id, args, codec_from(codec))
755 .map(outbound_to_js)
756 .map_err(|e| js_err(e))
757 }
758
759 #[wasm_bindgen(js_name = "accept")]
764 pub fn accept(&self, plaintext: &[u8], epoch: u64, codec: Option<u8>) -> Result<JsValue, JsValue> {
765 let mut gsp = self.inner.borrow_mut();
766 match gsp.accept(plaintext, epoch, codec_from(codec)) {
767 Ok(GspAccept { signal, sender_id, role_claim, request_id }) => {
768 let obj = Object::new();
769 set(&obj, "status", &JsValue::from_str("new"));
770 set(&obj, "signal", &JsValue::from_str(signal.name()));
771 set(&obj, "signalCode", &JsValue::from_f64(signal as u32 as f64));
772 set(&obj, "sender", &JsValue::from_f64(sender_id as f64));
773 set(&obj, "roleClaim", &JsValue::from_f64(role_claim as f64));
774 set(&obj, "requestId", &JsValue::from_f64(request_id as f64));
775 Ok(obj.into())
776 }
777 Err(GspError::DuplicateRequest(rid)) => {
778 let obj = Object::new();
779 set(&obj, "status", &JsValue::from_str("duplicate"));
780 set(&obj, "requestId", &JsValue::from_f64(rid as f64));
781 Ok(obj.into())
782 }
783 Err(e) => Err(js_err(e)),
784 }
785 }
786
787 #[wasm_bindgen(js_name = "reset")]
789 pub fn reset(&self) {
790 self.inner.borrow_mut().reset();
791 }
792}
793
794#[wasm_bindgen]
809pub struct SFrameSession {
810 inner: RefCell<RustSFrameDecryptor>,
811}
812
813#[wasm_bindgen]
814impl SFrameSession {
815 #[wasm_bindgen(js_name = "create")]
819 pub fn create(mls: &MlsContext, label: &str, suite: u8) -> Result<SFrameSession, JsValue> {
820 let suite = cipher_suite_from(suite)?;
821 let m = mls.inner.borrow();
822 let session = RustSFrameSession::from_mls(&m, label, suite).map_err(|e| js_err(e))?;
823 Ok(SFrameSession { inner: RefCell::new(session.decryptor()) })
824 }
825
826 #[wasm_bindgen(js_name = "createEncryptor")]
830 pub fn create_encryptor(
831 &self,
832 mls: &MlsContext,
833 leaf_index: u32,
834 label: &str,
835 suite: u8,
836 ) -> Result<SFrameEncryptor, JsValue> {
837 let suite = cipher_suite_from(suite)?;
838 let m = mls.inner.borrow();
839 let session = RustSFrameSession::from_mls(&m, label, suite).map_err(|e| js_err(e))?;
840 Ok(SFrameEncryptor { inner: RefCell::new(session.encryptor(leaf_index)) })
841 }
842
843 #[wasm_bindgen(js_name = "decrypt")]
847 pub fn decrypt(&self, payload: &[u8], aad: &[u8]) -> Result<JsValue, JsValue> {
848 let mut dec = self.inner.borrow_mut();
849 match dec.decrypt(payload, aad) {
850 Ok((plaintext, leaf)) => {
851 let obj = Object::new();
852 set(&obj, "plaintext", &u8s(&plaintext));
853 set(&obj, "senderLeaf", &JsValue::from_f64(leaf as f64));
854 Ok(obj.into())
855 }
856 Err(e) => Err(js_err(e)),
857 }
858 }
859}
860
861#[wasm_bindgen]
864pub struct SFrameEncryptor {
865 inner: RefCell<RustSFrameEncryptor>,
866}
867
868#[wasm_bindgen]
869impl SFrameEncryptor {
870 #[wasm_bindgen(js_name = "encrypt")]
874 pub fn encrypt(&self, plaintext: &[u8], aad: &[u8]) -> Result<Uint8Array, JsValue> {
875 let mut enc = self.inner.borrow_mut();
876 let ct = enc.encrypt(plaintext, aad).map_err(|e| js_err(e))?;
877 Ok(Uint8Array::from(ct.as_slice()))
878 }
879}
880
881#[cfg(test)]
884mod tests;