1#![allow(unsafe_op_in_unsafe_fn)]
26
27use gbp_stack::core::{ControlOpcode, NodeState, PayloadCodec, SignalType, StreamType};
28use gbp_stack::{
29 CipherSuite, DeliveredPayload, ErrorObject, Event, GapAccept, GapClient, GbpFrame, GroupNode,
30 GspAccept, GspClient, GtpAccept, GtpClient, MlsContext, OutboundFrame, ProcessedKind,
31 SFrameDecryptor, SFrameEncryptor, SFrameSession, StreamLabel,
32};
33use openmls::prelude::tls_codec::Serialize as _;
34use openmls::prelude::*;
35use serde::Serialize;
36use std::cell::RefCell;
37use std::collections::HashMap;
38use std::ffi::{CString, c_char};
39use std::sync::atomic::{AtomicI32, Ordering};
40use std::sync::{Arc, Mutex};
41
42#[repr(C)]
49pub struct GbpBuffer {
50 pub ptr: *mut u8,
52 pub len: usize,
54 pub cap: usize,
56}
57
58impl GbpBuffer {
59 fn empty() -> Self {
60 Self {
61 ptr: std::ptr::null_mut(),
62 len: 0,
63 cap: 0,
64 }
65 }
66 fn from_vec(mut v: Vec<u8>) -> Self {
67 let ptr = v.as_mut_ptr();
68 let len = v.len();
69 let cap = v.capacity();
70 std::mem::forget(v);
71 Self { ptr, len, cap }
72 }
73}
74
75#[unsafe(no_mangle)]
81pub unsafe extern "C" fn gbp_buffer_free(buf: GbpBuffer) {
82 if buf.ptr.is_null() {
83 return;
84 }
85 unsafe {
86 let _ = Vec::from_raw_parts(buf.ptr, buf.len, buf.cap);
87 }
88}
89
90#[unsafe(no_mangle)]
95pub unsafe extern "C" fn gbp_string_free(ptr: *mut c_char) {
96 if ptr.is_null() {
97 return;
98 }
99 unsafe {
100 let _ = CString::from_raw(ptr);
101 }
102}
103
104fn alloc_cstring(s: &str) -> *mut c_char {
105 CString::new(s.as_bytes())
106 .unwrap_or_else(|_| CString::new(s.replace('\0', "?")).unwrap())
107 .into_raw()
108}
109
110thread_local! {
115 static LAST_ERROR: RefCell<String> = const { RefCell::new(String::new()) };
116}
117
118fn set_last_error(e: impl ToString) {
119 LAST_ERROR.with(|s| *s.borrow_mut() = e.to_string());
120}
121
122fn clear_last_error() {
123 LAST_ERROR.with(|s| s.borrow_mut().clear());
124}
125
126#[unsafe(no_mangle)]
128pub extern "C" fn gbp_last_error() -> *mut c_char {
129 LAST_ERROR.with(|s| alloc_cstring(&s.borrow()))
130}
131
132macro_rules! registry {
137 ($vis:vis $name:ident<$t:ty>) => {
138 $vis struct $name {
139 next: AtomicI32,
140 map: Mutex<HashMap<i32, Arc<Mutex<$t>>>>,
141 }
142 impl $name {
143 fn new() -> Self {
144 Self { next: AtomicI32::new(1), map: Mutex::new(HashMap::new()) }
145 }
146 fn insert(&self, v: $t) -> i32 {
147 let id = self.next.fetch_add(1, Ordering::Relaxed);
148 self.map.lock().unwrap().insert(id, Arc::new(Mutex::new(v)));
149 id
150 }
151 fn remove(&self, id: i32) {
152 self.map.lock().unwrap().remove(&id);
153 }
154 fn get(&self, id: i32) -> Option<Arc<Mutex<$t>>> {
155 self.map.lock().unwrap().get(&id).cloned()
156 }
157 }
158 };
159}
160
161registry!(MlsRegistry<MlsContext>);
162registry!(NodeRegistry<GroupNode>);
163registry!(GtpRegistry<GtpClient>);
164registry!(GapRegistry<GapClient>);
165registry!(GspRegistry<GspClient>);
166registry!(SFrameSessionRegistry<SFrameDecryptor>);
167registry!(SFrameEncryptorRegistry<SFrameEncryptor>);
168
169struct MlsBundles {
170 map: Mutex<HashMap<i32, KeyPackageBundle>>,
171}
172impl MlsBundles {
173 fn new() -> Self {
174 Self {
175 map: Mutex::new(HashMap::new()),
176 }
177 }
178}
179
180fn mls() -> &'static MlsRegistry {
181 use std::sync::OnceLock;
182 static R: OnceLock<MlsRegistry> = OnceLock::new();
183 R.get_or_init(MlsRegistry::new)
184}
185fn mls_bundles() -> &'static MlsBundles {
186 use std::sync::OnceLock;
187 static R: OnceLock<MlsBundles> = OnceLock::new();
188 R.get_or_init(MlsBundles::new)
189}
190fn nodes() -> &'static NodeRegistry {
191 use std::sync::OnceLock;
192 static R: OnceLock<NodeRegistry> = OnceLock::new();
193 R.get_or_init(NodeRegistry::new)
194}
195fn gtps() -> &'static GtpRegistry {
196 use std::sync::OnceLock;
197 static R: OnceLock<GtpRegistry> = OnceLock::new();
198 R.get_or_init(GtpRegistry::new)
199}
200fn gaps() -> &'static GapRegistry {
201 use std::sync::OnceLock;
202 static R: OnceLock<GapRegistry> = OnceLock::new();
203 R.get_or_init(GapRegistry::new)
204}
205fn gsps() -> &'static GspRegistry {
206 use std::sync::OnceLock;
207 static R: OnceLock<GspRegistry> = OnceLock::new();
208 R.get_or_init(GspRegistry::new)
209}
210fn sframe_sessions() -> &'static SFrameSessionRegistry {
211 use std::sync::OnceLock;
212 static R: OnceLock<SFrameSessionRegistry> = OnceLock::new();
213 R.get_or_init(SFrameSessionRegistry::new)
214}
215fn sframe_encryptors() -> &'static SFrameEncryptorRegistry {
216 use std::sync::OnceLock;
217 static R: OnceLock<SFrameEncryptorRegistry> = OnceLock::new();
218 R.get_or_init(SFrameEncryptorRegistry::new)
219}
220
221#[unsafe(no_mangle)]
227pub extern "C" fn gbp_version() -> *mut c_char {
228 alloc_cstring(&format!(
229 "group-protocol-stack {} (gbp + gtp + gap + gsp)",
230 env!("CARGO_PKG_VERSION")
231 ))
232}
233
234#[unsafe(no_mangle)]
243pub unsafe extern "C" fn gbp_mls_create(identity_ptr: *const u8, identity_len: usize) -> i32 {
244 clear_last_error();
245 let ident = unsafe { std::slice::from_raw_parts(identity_ptr, identity_len) };
246 match MlsContext::new_member(ident) {
247 Ok((ctx, kp)) => {
248 let id = mls().insert(ctx);
249 mls_bundles().map.lock().unwrap().insert(id, kp);
250 id
251 }
252 Err(e) => {
253 set_last_error(e);
254 0
255 }
256 }
257}
258
259#[unsafe(no_mangle)]
261pub extern "C" fn gbp_mls_destroy(h: i32) {
262 mls().remove(h);
263 mls_bundles().map.lock().unwrap().remove(&h);
264}
265
266#[unsafe(no_mangle)]
268pub extern "C" fn gbp_mls_epoch(h: i32) -> u64 {
269 mls().get(h).map(|c| c.lock().unwrap().epoch()).unwrap_or(0)
270}
271
272#[unsafe(no_mangle)]
277pub unsafe extern "C" fn gbp_mls_group_id(h: i32, out16: *mut u8) -> bool {
278 clear_last_error();
279 let Some(ctx_arc) = mls().get(h) else {
280 set_last_error("invalid MLS handle");
281 return false;
282 };
283 let ctx = ctx_arc.lock().unwrap();
284 let gid = ctx.group_id_16();
285 unsafe { std::ptr::copy_nonoverlapping(gid.as_ptr(), out16, 16) };
286 true
287}
288
289#[unsafe(no_mangle)]
292pub extern "C" fn gbp_mls_export_key_package(h: i32) -> GbpBuffer {
293 clear_last_error();
294 let bundles = mls_bundles().map.lock().unwrap();
295 let Some(b) = bundles.get(&h) else {
296 set_last_error("invalid MLS handle");
297 return GbpBuffer::empty();
298 };
299 match b.key_package().tls_serialize_detached() {
300 Ok(b) => GbpBuffer::from_vec(b),
301 Err(e) => {
302 set_last_error(format!("kp serialize: {e:?}"));
303 GbpBuffer::empty()
304 }
305 }
306}
307
308#[unsafe(no_mangle)]
315pub unsafe extern "C" fn gbp_mls_invite(h: i32, kp_ptr: *const u8, kp_len: usize) -> GbpBuffer {
316 clear_last_error();
317 let bytes = unsafe { std::slice::from_raw_parts(kp_ptr, kp_len) };
318 let Some(ctx_arc) = mls().get(h) else {
319 set_last_error("invalid MLS handle");
320 return GbpBuffer::empty();
321 };
322 let mut ctx = ctx_arc.lock().unwrap();
323 let kp_in = match KeyPackageIn::tls_deserialize_exact_bytes(bytes) {
324 Ok(v) => v,
325 Err(e) => {
326 set_last_error(format!("kp parse: {e:?}"));
327 return GbpBuffer::empty();
328 }
329 };
330 let validated = match kp_in.validate(ctx.provider.crypto(), ProtocolVersion::Mls10) {
331 Ok(v) => v,
332 Err(e) => {
333 set_last_error(format!("kp validate: {e:?}"));
334 return GbpBuffer::empty();
335 }
336 };
337 match ctx.invite(&[validated]) {
338 Ok(welcome) => GbpBuffer::from_vec(welcome),
339 Err(e) => {
340 set_last_error(e);
341 GbpBuffer::empty()
342 }
343 }
344}
345
346#[unsafe(no_mangle)]
356pub unsafe extern "C" fn gbp_mls_invite_full(
357 h: i32,
358 kp_ptr: *const u8,
359 kp_len: usize,
360) -> GbpBuffer {
361 clear_last_error();
362 let bytes = unsafe { std::slice::from_raw_parts(kp_ptr, kp_len) };
363 let Some(ctx_arc) = mls().get(h) else {
364 set_last_error("invalid MLS handle");
365 return GbpBuffer::empty();
366 };
367 let mut ctx = ctx_arc.lock().unwrap();
368 let kp_in = match KeyPackageIn::tls_deserialize_exact_bytes(bytes) {
369 Ok(v) => v,
370 Err(e) => {
371 set_last_error(format!("kp parse: {e:?}"));
372 return GbpBuffer::empty();
373 }
374 };
375 let validated = match kp_in.validate(ctx.provider.crypto(), ProtocolVersion::Mls10) {
376 Ok(v) => v,
377 Err(e) => {
378 set_last_error(format!("kp validate: {e:?}"));
379 return GbpBuffer::empty();
380 }
381 };
382 match ctx.invite_full(&[validated]) {
383 Ok((commit, welcome)) => {
384 let mut out = Vec::with_capacity(4 + commit.len() + welcome.len());
385 out.extend_from_slice(&(commit.len() as u32).to_le_bytes());
386 out.extend_from_slice(&commit);
387 out.extend_from_slice(&welcome);
388 GbpBuffer::from_vec(out)
389 }
390 Err(e) => {
391 set_last_error(e);
392 GbpBuffer::empty()
393 }
394 }
395}
396
397#[unsafe(no_mangle)]
401pub extern "C" fn gbp_mls_remove(h: i32, leaf_index: u32) -> GbpBuffer {
402 clear_last_error();
403 let Some(ctx_arc) = mls().get(h) else {
404 set_last_error("invalid MLS handle");
405 return GbpBuffer::empty();
406 };
407 let mut ctx = ctx_arc.lock().unwrap();
408 match ctx.remove_members(&[leaf_index]) {
409 Ok(commit) => GbpBuffer::from_vec(commit),
410 Err(e) => {
411 set_last_error(e);
412 GbpBuffer::empty()
413 }
414 }
415}
416
417#[unsafe(no_mangle)]
428pub unsafe extern "C" fn gbp_mls_process_message(
429 h: i32,
430 msg_ptr: *const u8,
431 msg_len: usize,
432) -> u32 {
433 clear_last_error();
434 let bytes = unsafe { std::slice::from_raw_parts(msg_ptr, msg_len) };
435 let Some(ctx_arc) = mls().get(h) else {
436 set_last_error("invalid MLS handle");
437 return 0;
438 };
439 let mut ctx = ctx_arc.lock().unwrap();
440 match ctx.process_message(bytes) {
441 Ok(ProcessedKind::Commit) => 1,
442 Ok(ProcessedKind::Application) => 2,
443 Ok(ProcessedKind::Proposal) => 3,
444 Ok(ProcessedKind::External) => 4,
445 Err(e) => {
446 set_last_error(e);
447 0
448 }
449 }
450}
451
452#[unsafe(no_mangle)]
455pub extern "C" fn gbp_mls_finalize_commit(h: i32) -> bool {
456 clear_last_error();
457 let Some(ctx_arc) = mls().get(h) else {
458 set_last_error("invalid MLS handle");
459 return false;
460 };
461 let mut ctx = ctx_arc.lock().unwrap();
462 match ctx.finalize_pending_commit() {
463 Ok(()) => true,
464 Err(e) => {
465 set_last_error(e);
466 false
467 }
468 }
469}
470
471#[unsafe(no_mangle)]
474pub extern "C" fn gbp_mls_clear_pending_commit(h: i32) -> bool {
475 clear_last_error();
476 let Some(ctx_arc) = mls().get(h) else {
477 set_last_error("invalid MLS handle");
478 return false;
479 };
480 let mut ctx = ctx_arc.lock().unwrap();
481 match ctx.clear_pending_commit() {
482 Ok(()) => true,
483 Err(e) => {
484 set_last_error(e);
485 false
486 }
487 }
488}
489
490#[unsafe(no_mangle)]
495pub unsafe extern "C" fn gbp_mls_accept_welcome(
496 h: i32,
497 welcome_ptr: *const u8,
498 welcome_len: usize,
499) -> bool {
500 clear_last_error();
501 let bytes = unsafe { std::slice::from_raw_parts(welcome_ptr, welcome_len) };
502 let Some(ctx_arc) = mls().get(h) else {
503 set_last_error("invalid MLS handle");
504 return false;
505 };
506 let mut ctx = ctx_arc.lock().unwrap();
507 match ctx.accept_welcome(bytes) {
508 Ok(()) => true,
509 Err(e) => {
510 set_last_error(e);
511 false
512 }
513 }
514}
515
516#[unsafe(no_mangle)]
525pub unsafe extern "C" fn gbp_node_create(member_id: u32, group_id_16: *const u8) -> i32 {
526 clear_last_error();
527 let mut gid = [0u8; 16];
528 unsafe { std::ptr::copy_nonoverlapping(group_id_16, gid.as_mut_ptr(), 16) };
529 nodes().insert(GroupNode::new(member_id, gid))
530}
531
532#[unsafe(no_mangle)]
534pub extern "C" fn gbp_node_destroy(h: i32) {
535 nodes().remove(h);
536}
537
538#[unsafe(no_mangle)]
540pub extern "C" fn gbp_node_bootstrap_creator(h: i32, epoch: u64) -> bool {
541 let Some(n_arc) = nodes().get(h) else {
542 return false;
543 };
544 n_arc.lock().unwrap().bootstrap_as_creator(epoch);
545 true
546}
547
548#[unsafe(no_mangle)]
553pub extern "C" fn gbp_node_bootstrap_joiner(h: i32, epoch: u64, expected_first_tid: u32) -> bool {
554 let Some(n_arc) = nodes().get(h) else {
555 return false;
556 };
557 n_arc
558 .lock()
559 .unwrap()
560 .bootstrap_as_joiner(epoch, expected_first_tid);
561 true
562}
563
564#[unsafe(no_mangle)]
566pub extern "C" fn gbp_node_state(h: i32) -> u32 {
567 nodes()
568 .get(h)
569 .map(|n| n.lock().unwrap().state as u32)
570 .unwrap_or(u32::MAX)
571}
572
573#[unsafe(no_mangle)]
575pub extern "C" fn gbp_node_epoch(h: i32) -> u64 {
576 nodes()
577 .get(h)
578 .map(|n| n.lock().unwrap().current_epoch)
579 .unwrap_or(0)
580}
581
582#[unsafe(no_mangle)]
584pub extern "C" fn gbp_node_last_transition_id(h: i32) -> u32 {
585 nodes()
586 .get(h)
587 .map(|n| n.lock().unwrap().last_transition_id)
588 .unwrap_or(0)
589}
590
591#[unsafe(no_mangle)]
594pub extern "C" fn gbp_node_set_epoch(h: i32, epoch: u64) -> bool {
595 let Some(n_arc) = nodes().get(h) else {
596 return false;
597 };
598 n_arc.lock().unwrap().current_epoch = epoch;
599 true
600}
601
602#[unsafe(no_mangle)]
604pub extern "C" fn gbp_node_apply_transition(h: i32, tid: u32) -> bool {
605 let Some(n_arc) = nodes().get(h) else {
606 return false;
607 };
608 n_arc.lock().unwrap().apply_transition(tid);
609 true
610}
611
612#[unsafe(no_mangle)]
619pub unsafe extern "C" fn gbp_node_send_control(
620 nh: i32,
621 mh: i32,
622 target: u32,
623 opcode: u16,
624 transition_id: u32,
625 request_id: u32,
626 args_ptr: *const u8,
627 args_len: usize,
628) -> GbpBuffer {
629 clear_last_error();
630 let op = match ControlOpcode::try_from(opcode) {
631 Ok(o) => o,
632 Err(_) => {
633 set_last_error(format!("bad opcode 0x{opcode:04X}"));
634 return GbpBuffer::empty();
635 }
636 };
637 let args = if args_len == 0 {
638 Vec::new()
639 } else {
640 unsafe { std::slice::from_raw_parts(args_ptr, args_len) }.to_vec()
641 };
642 let (n_arc, m_arc) = (nodes().get(nh), mls().get(mh));
643 let (Some(n_arc), Some(m_arc)) = (n_arc, m_arc) else {
644 set_last_error("bad node/mls handle");
645 return GbpBuffer::empty();
646 };
647 let mut n = n_arc.lock().unwrap();
648 let mut m = m_arc.lock().unwrap();
649 match n.send_control(&mut *m, target, op, transition_id, request_id, args) {
650 Ok(of) => outbound_to_buffer(of),
651 Err(e) => {
652 set_last_error(e.to_string());
653 GbpBuffer::empty()
654 }
655 }
656}
657
658#[unsafe(no_mangle)]
663pub unsafe extern "C" fn gbp_node_on_wire(
664 nh: i32,
665 mh: i32,
666 wire_ptr: *const u8,
667 wire_len: usize,
668) -> *mut c_char {
669 clear_last_error();
670 let wire = unsafe { std::slice::from_raw_parts(wire_ptr, wire_len) };
671 let (n_arc, m_arc) = (nodes().get(nh), mls().get(mh));
672 let (Some(n_arc), Some(m_arc)) = (n_arc, m_arc) else {
673 set_last_error("bad node/mls handle");
674 return alloc_cstring("[]");
675 };
676 let mut n = n_arc.lock().unwrap();
677 let mut m = m_arc.lock().unwrap();
678 let events = match n.on_wire(&mut *m, wire) {
679 Ok(e) => e,
680 Err(e) => {
681 set_last_error(e.to_string());
682 return alloc_cstring("[]");
683 }
684 };
685 alloc_cstring(&events_to_json(&events))
686}
687
688#[unsafe(no_mangle)]
690pub extern "C" fn gbp_node_drain_events(nh: i32) -> *mut c_char {
691 let Some(n_arc) = nodes().get(nh) else {
692 return alloc_cstring("[]");
693 };
694 alloc_cstring(&events_to_json(&n_arc.lock().unwrap().drain_events()))
695}
696
697fn outbound_to_buffer(of: OutboundFrame) -> GbpBuffer {
698 let mut out = Vec::with_capacity(4 + of.wire.len());
699 out.extend_from_slice(&of.to.to_le_bytes());
700 out.extend_from_slice(&of.wire);
701 GbpBuffer::from_vec(out)
702}
703
704#[unsafe(no_mangle)]
710pub extern "C" fn gtp_client_create() -> i32 {
711 gtps().insert(GtpClient::new())
712}
713
714#[unsafe(no_mangle)]
716pub extern "C" fn gtp_client_destroy(h: i32) {
717 gtps().remove(h);
718}
719
720#[unsafe(no_mangle)]
722pub extern "C" fn gtp_client_reset(h: i32) {
723 if let Some(c) = gtps().get(h) {
724 c.lock().unwrap().reset();
725 }
726}
727
728#[unsafe(no_mangle)]
736pub unsafe extern "C" fn gtp_client_send(
737 ch: i32,
738 nh: i32,
739 mh: i32,
740 target: u32,
741 message_id: u64,
742 text_ptr: *const u8,
743 text_len: usize,
744 codec: u8,
745) -> GbpBuffer {
746 clear_last_error();
747 let text = unsafe { std::slice::from_raw_parts(text_ptr, text_len) };
748 let text = match std::str::from_utf8(text) {
749 Ok(s) => s,
750 Err(e) => {
751 set_last_error(format!("utf8: {e}"));
752 return GbpBuffer::empty();
753 }
754 };
755 let codec = PayloadCodec::from_u8(codec).unwrap_or(PayloadCodec::Cbor);
756 let (c_arc, n_arc, m_arc) = (gtps().get(ch), nodes().get(nh), mls().get(mh));
757 let (Some(c_arc), Some(n_arc), Some(m_arc)) = (c_arc, n_arc, m_arc) else {
758 set_last_error("bad handle");
759 return GbpBuffer::empty();
760 };
761 let mut c = c_arc.lock().unwrap();
762 let mut n = n_arc.lock().unwrap();
763 let mut m = m_arc.lock().unwrap();
764 match c.send(&mut *n, &mut *m, target, message_id, text, codec) {
765 Ok(of) => outbound_to_buffer(of),
766 Err(e) => {
767 set_last_error(e.to_string());
768 GbpBuffer::empty()
769 }
770 }
771}
772
773#[unsafe(no_mangle)]
785pub unsafe extern "C" fn gtp_client_accept(
786 ch: i32,
787 current_epoch: u64,
788 pt_ptr: *const u8,
789 pt_len: usize,
790 codec: u8,
791) -> *mut c_char {
792 clear_last_error();
793 let pt = unsafe { std::slice::from_raw_parts(pt_ptr, pt_len) };
794 let codec = PayloadCodec::from_u8(codec).unwrap_or(PayloadCodec::Cbor);
795 let Some(c_arc) = gtps().get(ch) else {
796 return alloc_cstring(r#"{"status":"error","reason":"bad client"}"#);
797 };
798 let mut c = c_arc.lock().unwrap();
799 #[derive(Serialize)]
800 struct Out<'a> {
801 status: &'a str,
802 sender: Option<u32>,
803 message_id: Option<u64>,
804 text: Option<String>,
805 reason: Option<String>,
806 }
807 let out = match c.accept(pt, current_epoch, codec) {
808 Ok(GtpAccept::New(m)) => Out {
809 status: "new",
810 sender: Some(m.sender_id),
811 message_id: Some(m.message_id),
812 text: Some(m.text().unwrap_or("<binary>").to_string()),
813 reason: None,
814 },
815 Ok(GtpAccept::Duplicate(m)) => Out {
816 status: "duplicate",
817 sender: Some(m.sender_id),
818 message_id: Some(m.message_id),
819 text: Some(m.text().unwrap_or("<binary>").to_string()),
820 reason: None,
821 },
822 Err(e) => Out {
823 status: "error",
824 sender: None,
825 message_id: None,
826 text: None,
827 reason: Some(e.to_string()),
828 },
829 };
830 alloc_cstring(&serde_json::to_string(&out).unwrap_or_default())
831}
832
833#[unsafe(no_mangle)]
839pub extern "C" fn gap_client_create() -> i32 {
840 gaps().insert(GapClient::new())
841}
842
843#[unsafe(no_mangle)]
845pub extern "C" fn gap_client_destroy(h: i32) {
846 gaps().remove(h);
847}
848
849#[unsafe(no_mangle)]
851pub extern "C" fn gap_client_reset(h: i32) {
852 if let Some(c) = gaps().get(h) {
853 c.lock().unwrap().reset();
854 }
855}
856
857#[unsafe(no_mangle)]
865pub unsafe extern "C" fn gap_client_send(
866 ch: i32,
867 nh: i32,
868 mh: i32,
869 target: u32,
870 media_source_id: u32,
871 rtp_timestamp: u64,
872 opus_ptr: *const u8,
873 opus_len: usize,
874 codec: u8,
875) -> GbpBuffer {
876 clear_last_error();
877 let opus = unsafe { std::slice::from_raw_parts(opus_ptr, opus_len) }.to_vec();
878 let codec = PayloadCodec::from_u8(codec).unwrap_or(PayloadCodec::Cbor);
879 let (c_arc, n_arc, m_arc) = (gaps().get(ch), nodes().get(nh), mls().get(mh));
880 let (Some(c_arc), Some(n_arc), Some(m_arc)) = (c_arc, n_arc, m_arc) else {
881 set_last_error("bad handle");
882 return GbpBuffer::empty();
883 };
884 let mut c = c_arc.lock().unwrap();
885 let mut n = n_arc.lock().unwrap();
886 let mut m = m_arc.lock().unwrap();
887 match c.send(
888 &mut *n,
889 &mut *m,
890 target,
891 media_source_id,
892 rtp_timestamp,
893 opus,
894 codec,
895 ) {
896 Ok(of) => outbound_to_buffer(of),
897 Err(e) => {
898 set_last_error(e.to_string());
899 GbpBuffer::empty()
900 }
901 }
902}
903
904#[unsafe(no_mangle)]
912pub unsafe extern "C" fn gap_client_accept(
913 ch: i32,
914 current_epoch: u64,
915 pt_ptr: *const u8,
916 pt_len: usize,
917 codec: u8,
918) -> *mut c_char {
919 clear_last_error();
920 let pt = unsafe { std::slice::from_raw_parts(pt_ptr, pt_len) };
921 let codec = PayloadCodec::from_u8(codec).unwrap_or(PayloadCodec::Cbor);
922 let Some(c_arc) = gaps().get(ch) else {
923 return alloc_cstring(r#"{"status":"error","reason":"bad client"}"#);
924 };
925 let mut c = c_arc.lock().unwrap();
926 #[derive(Serialize)]
927 struct Out<'a> {
928 status: &'a str,
929 source: Option<u32>,
930 seq: Option<u32>,
931 bytes: Option<usize>,
932 reason: Option<String>,
933 }
934 let out = match c.accept(pt, current_epoch, codec) {
935 Ok(GapAccept::New(p)) => Out {
936 status: "new",
937 source: Some(p.media_source_id),
938 seq: Some(p.rtp_sequence),
939 bytes: Some(p.opus_frame.len()),
940 reason: None,
941 },
942 Ok(GapAccept::Late(p)) => Out {
943 status: "late",
944 source: Some(p.media_source_id),
945 seq: Some(p.rtp_sequence),
946 bytes: Some(p.opus_frame.len()),
947 reason: None,
948 },
949 Err(e) => Out {
950 status: "error",
951 source: None,
952 seq: None,
953 bytes: None,
954 reason: Some(e.to_string()),
955 },
956 };
957 alloc_cstring(&serde_json::to_string(&out).unwrap_or_default())
958}
959
960#[unsafe(no_mangle)]
966pub extern "C" fn gsp_client_create() -> i32 {
967 gsps().insert(GspClient::new())
968}
969
970#[unsafe(no_mangle)]
972pub extern "C" fn gsp_client_destroy(h: i32) {
973 gsps().remove(h);
974}
975
976#[unsafe(no_mangle)]
978pub extern "C" fn gsp_client_reset(h: i32) {
979 if let Some(c) = gsps().get(h) {
980 c.lock().unwrap().reset();
981 }
982}
983
984#[unsafe(no_mangle)]
989pub extern "C" fn gsp_client_send(
990 ch: i32,
991 nh: i32,
992 mh: i32,
993 target: u32,
994 signal_type: u32,
995 role_claim: u32,
996 request_id: u32,
997 codec: u8,
998) -> GbpBuffer {
999 clear_last_error();
1000 let sig = match SignalType::try_from(signal_type) {
1001 Ok(s) => s,
1002 Err(_) => {
1003 set_last_error(format!("bad signal {signal_type}"));
1004 return GbpBuffer::empty();
1005 }
1006 };
1007 let codec = PayloadCodec::from_u8(codec).unwrap_or(PayloadCodec::Cbor);
1008 let (c_arc, n_arc, m_arc) = (gsps().get(ch), nodes().get(nh), mls().get(mh));
1009 let (Some(c_arc), Some(n_arc), Some(m_arc)) = (c_arc, n_arc, m_arc) else {
1010 set_last_error("bad handle");
1011 return GbpBuffer::empty();
1012 };
1013 let mut c = c_arc.lock().unwrap();
1014 let mut n = n_arc.lock().unwrap();
1015 let mut m = m_arc.lock().unwrap();
1016 match c.send(&mut *n, &mut *m, target, sig, role_claim, request_id, codec) {
1017 Ok(of) => outbound_to_buffer(of),
1018 Err(e) => {
1019 set_last_error(e.to_string());
1020 GbpBuffer::empty()
1021 }
1022 }
1023}
1024
1025#[unsafe(no_mangle)]
1035pub unsafe extern "C" fn gsp_client_send_with_args(
1036 ch: i32,
1037 nh: i32,
1038 mh: i32,
1039 target: u32,
1040 signal_type: u32,
1041 role_claim: u32,
1042 request_id: u32,
1043 args_ptr: *const u8,
1044 args_len: usize,
1045 codec: u8,
1046) -> GbpBuffer {
1047 clear_last_error();
1048 let args: &[u8] = if args_len == 0 || args_ptr.is_null() {
1049 &[]
1050 } else {
1051 unsafe { std::slice::from_raw_parts(args_ptr, args_len) }
1052 };
1053 let sig = match SignalType::try_from(signal_type) {
1054 Ok(s) => s,
1055 Err(_) => {
1056 set_last_error(format!("bad signal {signal_type}"));
1057 return GbpBuffer::empty();
1058 }
1059 };
1060 let codec = PayloadCodec::from_u8(codec).unwrap_or(PayloadCodec::Cbor);
1061 let (c_arc, n_arc, m_arc) = (gsps().get(ch), nodes().get(nh), mls().get(mh));
1062 let (Some(c_arc), Some(n_arc), Some(m_arc)) = (c_arc, n_arc, m_arc) else {
1063 set_last_error("bad handle");
1064 return GbpBuffer::empty();
1065 };
1066 let mut c = c_arc.lock().unwrap();
1067 let mut n = n_arc.lock().unwrap();
1068 let mut m = m_arc.lock().unwrap();
1069 match c.send_with_args(&mut *n, &mut *m, target, sig, role_claim, request_id, args, codec) {
1070 Ok(of) => outbound_to_buffer(of),
1071 Err(e) => {
1072 set_last_error(e.to_string());
1073 GbpBuffer::empty()
1074 }
1075 }
1076}
1077
1078#[unsafe(no_mangle)]
1088pub unsafe extern "C" fn gsp_client_accept(
1089 ch: i32,
1090 current_epoch: u64,
1091 pt_ptr: *const u8,
1092 pt_len: usize,
1093 codec: u8,
1094) -> *mut c_char {
1095 clear_last_error();
1096 let pt = unsafe { std::slice::from_raw_parts(pt_ptr, pt_len) };
1097 let codec = PayloadCodec::from_u8(codec).unwrap_or(PayloadCodec::Cbor);
1098 let Some(c_arc) = gsps().get(ch) else {
1099 return alloc_cstring(r#"{"status":"error","reason":"bad client"}"#);
1100 };
1101 let mut c = c_arc.lock().unwrap();
1102 #[derive(Serialize)]
1103 struct Out<'a> {
1104 status: &'a str,
1105 signal: Option<&'a str>,
1106 signal_code: Option<u32>,
1107 sender: Option<u32>,
1108 role_claim: Option<u32>,
1109 request_id: Option<u32>,
1110 reason: Option<String>,
1111 }
1112 let out = match c.accept(pt, current_epoch, codec) {
1113 Ok(GspAccept {
1114 signal,
1115 sender_id,
1116 role_claim,
1117 request_id,
1118 }) => Out {
1119 status: "new",
1120 signal: Some(signal.name()),
1121 signal_code: Some(signal as u32),
1122 sender: Some(sender_id),
1123 role_claim: Some(role_claim),
1124 request_id: Some(request_id),
1125 reason: None,
1126 },
1127 Err(gbp_stack::GspError::DuplicateRequest(rid)) => Out {
1128 status: "duplicate",
1129 signal: None,
1130 signal_code: None,
1131 sender: None,
1132 role_claim: None,
1133 request_id: Some(rid),
1134 reason: None,
1135 },
1136 Err(e) => Out {
1137 status: "error",
1138 signal: None,
1139 signal_code: None,
1140 sender: None,
1141 role_claim: None,
1142 request_id: None,
1143 reason: Some(e.to_string()),
1144 },
1145 };
1146 alloc_cstring(&serde_json::to_string(&out).unwrap_or_default())
1147}
1148
1149#[unsafe(no_mangle)]
1158pub unsafe extern "C" fn gbp_frame_encode_v(
1159 version: u8,
1160 group_id_16: *const u8,
1161 epoch: u64,
1162 transition_id: u32,
1163 stream_type: u32,
1164 stream_id: u32,
1165 flags: u16,
1166 sequence_no: u32,
1167 payload_ptr: *const u8,
1168 payload_len: usize,
1169) -> GbpBuffer {
1170 clear_last_error();
1171 let mut gid = [0u8; 16];
1172 unsafe { std::ptr::copy_nonoverlapping(group_id_16, gid.as_mut_ptr(), 16) };
1173 let st_u8 = StreamType::try_from(stream_type)
1174 .map(|s| s as u8)
1175 .unwrap_or(stream_type as u8);
1176 let payload: Vec<u8> = if payload_len == 0 || payload_ptr.is_null() {
1177 Vec::new()
1178 } else {
1179 unsafe { std::slice::from_raw_parts(payload_ptr, payload_len) }.to_vec()
1180 };
1181 let frame = gbp_stack::gbp::GbpFrame {
1182 version,
1183 group_id: serde_bytes::ByteBuf::from(gid.to_vec()),
1184 epoch,
1185 transition_id,
1186 stream_type: st_u8,
1187 stream_id,
1188 flags,
1189 sequence_no,
1190 payload_format: 0u8,
1191 payload_size: payload.len() as u32,
1192 encrypted_payload: serde_bytes::ByteBuf::from(payload),
1193 };
1194 GbpBuffer::from_vec(frame.to_cbor())
1195}
1196
1197#[unsafe(no_mangle)]
1199pub extern "C" fn gbp_error_lookup(code: u16) -> GbpBuffer {
1200 use gbp_stack::core::errors::ErrorSpec;
1201 match ErrorSpec::lookup(code) {
1202 Some(spec) => GbpBuffer::from_vec(ErrorObject::from_spec(spec, spec.name).to_cbor()),
1203 None => {
1204 set_last_error(format!("unknown error code 0x{code:04X}"));
1205 GbpBuffer::empty()
1206 }
1207 }
1208}
1209
1210#[allow(dead_code)]
1211fn _link(_f: &GbpFrame, _l: StreamLabel) {}
1212
1213#[derive(Serialize)]
1218#[serde(tag = "kind", rename_all = "snake_case")]
1219enum EventDto<'a> {
1220 StateChanged {
1221 from: String,
1222 to: String,
1223 },
1224 PayloadReceived {
1225 stream_type: &'a str,
1226 stream_type_code: u32,
1227 stream_id: u32,
1228 sequence_no: u32,
1229 flags: u16,
1230 codec: u8,
1231 plaintext_b64: String,
1232 },
1233 Control {
1234 from: u32,
1235 opcode: &'a str,
1236 opcode_code: u16,
1237 transition_id: u32,
1238 request_id: u32,
1239 args_b64: String,
1240 },
1241 Error {
1242 code: u16,
1243 code_hex: String,
1244 class: u8,
1245 retryable: bool,
1246 fatal: bool,
1247 reason: String,
1248 },
1249 EpochAdvanced {
1250 epoch: u64,
1251 transition_id: u32,
1252 },
1253 CoordinatorElectionNeeded {},
1254 BecameCoordinator {},
1255 CoordinatorClaim {
1256 claimant: u32,
1257 },
1258}
1259
1260fn b64(b: &[u8]) -> String {
1261 const A: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1262 let mut out = String::with_capacity(b.len().div_ceil(3) * 4);
1263 let mut i = 0;
1264 while i + 3 <= b.len() {
1265 let n = ((b[i] as u32) << 16) | ((b[i + 1] as u32) << 8) | (b[i + 2] as u32);
1266 out.push(A[(n >> 18) as usize & 0x3F] as char);
1267 out.push(A[(n >> 12) as usize & 0x3F] as char);
1268 out.push(A[(n >> 6) as usize & 0x3F] as char);
1269 out.push(A[n as usize & 0x3F] as char);
1270 i += 3;
1271 }
1272 let rem = b.len() - i;
1273 if rem == 1 {
1274 let n = (b[i] as u32) << 16;
1275 out.push(A[(n >> 18) as usize & 0x3F] as char);
1276 out.push(A[(n >> 12) as usize & 0x3F] as char);
1277 out.push('=');
1278 out.push('=');
1279 } else if rem == 2 {
1280 let n = ((b[i] as u32) << 16) | ((b[i + 1] as u32) << 8);
1281 out.push(A[(n >> 18) as usize & 0x3F] as char);
1282 out.push(A[(n >> 12) as usize & 0x3F] as char);
1283 out.push(A[(n >> 6) as usize & 0x3F] as char);
1284 out.push('=');
1285 }
1286 out
1287}
1288
1289fn dto<'a>(e: &'a Event) -> EventDto<'a> {
1290 match e {
1291 Event::StateChanged { from, to } => EventDto::StateChanged {
1292 from: from.to_string(),
1293 to: to.to_string(),
1294 },
1295 Event::PayloadReceived(DeliveredPayload {
1296 stream_type,
1297 stream_id,
1298 sequence_no,
1299 flags,
1300 plaintext,
1301 codec,
1302 }) => EventDto::PayloadReceived {
1303 stream_type: match stream_type {
1304 StreamType::Control => "control",
1305 StreamType::Audio => "audio",
1306 StreamType::Text => "text",
1307 StreamType::Signal => "signal",
1308 },
1309 stream_type_code: *stream_type as u32,
1310 stream_id: *stream_id,
1311 sequence_no: *sequence_no,
1312 flags: *flags,
1313 codec: codec.as_u8(),
1314 plaintext_b64: b64(plaintext),
1315 },
1316 Event::Control {
1317 from,
1318 opcode,
1319 transition_id,
1320 request_id,
1321 args,
1322 } => EventDto::Control {
1323 from: *from,
1324 opcode: opcode.name(),
1325 opcode_code: *opcode as u16,
1326 transition_id: *transition_id,
1327 request_id: *request_id,
1328 args_b64: b64(args),
1329 },
1330 Event::Error {
1331 code,
1332 class,
1333 retryable,
1334 fatal,
1335 reason,
1336 } => EventDto::Error {
1337 code: *code,
1338 code_hex: format!("0x{code:04X}"),
1339 class: *class as u8,
1340 retryable: *retryable,
1341 fatal: *fatal,
1342 reason: reason.clone(),
1343 },
1344 Event::EpochAdvanced {
1345 epoch,
1346 transition_id,
1347 } => EventDto::EpochAdvanced {
1348 epoch: *epoch,
1349 transition_id: *transition_id,
1350 },
1351 Event::CoordinatorElectionNeeded => EventDto::CoordinatorElectionNeeded {},
1352 Event::BecameCoordinator => EventDto::BecameCoordinator {},
1353 Event::CoordinatorClaim { claimant } => EventDto::CoordinatorClaim {
1354 claimant: *claimant,
1355 },
1356 }
1357}
1358
1359fn events_to_json(events: &[Event]) -> String {
1360 let dtos: Vec<EventDto> = events.iter().map(dto).collect();
1361 serde_json::to_string(&dtos).unwrap_or_else(|_| "[]".to_string())
1362}
1363
1364#[allow(dead_code)]
1365const _STATES: [NodeState; 7] = [
1366 NodeState::Idle,
1367 NodeState::Connecting,
1368 NodeState::EstablishingGroup,
1369 NodeState::Active,
1370 NodeState::Resyncing,
1371 NodeState::Failed,
1372 NodeState::Closed,
1373];
1374
1375#[unsafe(no_mangle)]
1396pub unsafe extern "C" fn gbp_sframe_session_create(
1397 mls_handle: i32,
1398 suite: u8,
1399 label_ptr: *const u8,
1400 label_len: usize,
1401) -> i32 {
1402 clear_last_error();
1403 let suite = match CipherSuite::from_u8(suite) {
1404 Some(s) => s,
1405 None => {
1406 set_last_error(format!("unknown ciphersuite {suite}"));
1407 return 0;
1408 }
1409 };
1410 let label = unsafe {
1411 match std::str::from_utf8(std::slice::from_raw_parts(label_ptr, label_len)) {
1412 Ok(s) => s,
1413 Err(e) => {
1414 set_last_error(e);
1415 return 0;
1416 }
1417 }
1418 };
1419 let Some(mls_arc) = mls().get(mls_handle) else {
1420 set_last_error("invalid MLS handle");
1421 return 0;
1422 };
1423 let mls = mls_arc.lock().unwrap();
1424 match SFrameSession::from_mls(&mls, label, suite) {
1425 Ok(session) => sframe_sessions().insert(session.decryptor()),
1426 Err(e) => {
1427 set_last_error(e);
1428 0
1429 }
1430 }
1431}
1432
1433#[unsafe(no_mangle)]
1435pub extern "C" fn gbp_sframe_session_free(handle: i32) {
1436 sframe_sessions().remove(handle);
1437}
1438
1439#[unsafe(no_mangle)]
1450pub unsafe extern "C" fn gbp_sframe_encryptor_create(
1451 mls_handle: i32,
1452 session_handle: i32,
1453 leaf_index: u32,
1454 suite: u8,
1455 label_ptr: *const u8,
1456 label_len: usize,
1457) -> i32 {
1458 clear_last_error();
1459 let suite = match CipherSuite::from_u8(suite) {
1460 Some(s) => s,
1461 None => {
1462 set_last_error(format!("unknown ciphersuite {suite}"));
1463 return 0;
1464 }
1465 };
1466 let label = unsafe {
1467 match std::str::from_utf8(std::slice::from_raw_parts(label_ptr, label_len)) {
1468 Ok(s) => s,
1469 Err(e) => {
1470 set_last_error(e);
1471 return 0;
1472 }
1473 }
1474 };
1475 if sframe_sessions().get(session_handle).is_none() {
1477 set_last_error("invalid session handle");
1478 return 0;
1479 }
1480 let Some(mls_arc) = mls().get(mls_handle) else {
1481 set_last_error("invalid MLS handle");
1482 return 0;
1483 };
1484 let mls = mls_arc.lock().unwrap();
1485 match SFrameSession::from_mls(&mls, label, suite) {
1486 Ok(session) => sframe_encryptors().insert(session.encryptor(leaf_index)),
1487 Err(e) => {
1488 set_last_error(e);
1489 0
1490 }
1491 }
1492}
1493
1494#[unsafe(no_mangle)]
1496pub extern "C" fn gbp_sframe_encryptor_free(handle: i32) {
1497 sframe_encryptors().remove(handle);
1498}
1499
1500#[unsafe(no_mangle)]
1513pub unsafe extern "C" fn gbp_sframe_encrypt(
1514 enc_handle: i32,
1515 plaintext_ptr: *const u8,
1516 plaintext_len: usize,
1517 aad_ptr: *const u8,
1518 aad_len: usize,
1519) -> GbpBuffer {
1520 clear_last_error();
1521 let Some(enc_arc) = sframe_encryptors().get(enc_handle) else {
1522 set_last_error("invalid encryptor handle");
1523 return GbpBuffer::empty();
1524 };
1525 let plaintext = unsafe { std::slice::from_raw_parts(plaintext_ptr, plaintext_len) };
1526 let aad = if aad_ptr.is_null() || aad_len == 0 {
1527 &[][..]
1528 } else {
1529 unsafe { std::slice::from_raw_parts(aad_ptr, aad_len) }
1530 };
1531 let mut enc = enc_arc.lock().unwrap();
1532 match enc.encrypt(plaintext, aad) {
1533 Ok(payload) => GbpBuffer::from_vec(payload),
1534 Err(e) => {
1535 set_last_error(e);
1536 GbpBuffer::empty()
1537 }
1538 }
1539}
1540
1541#[unsafe(no_mangle)]
1553pub unsafe extern "C" fn gbp_sframe_decrypt(
1554 session_handle: i32,
1555 payload_ptr: *const u8,
1556 payload_len: usize,
1557 aad_ptr: *const u8,
1558 aad_len: usize,
1559 sender_leaf_out: *mut u32,
1560) -> GbpBuffer {
1561 clear_last_error();
1562 let Some(session_arc) = sframe_sessions().get(session_handle) else {
1563 set_last_error("invalid session handle");
1564 return GbpBuffer::empty();
1565 };
1566 let payload = unsafe { std::slice::from_raw_parts(payload_ptr, payload_len) };
1567 let aad = if aad_ptr.is_null() || aad_len == 0 {
1568 &[][..]
1569 } else {
1570 unsafe { std::slice::from_raw_parts(aad_ptr, aad_len) }
1571 };
1572 let mut dec = session_arc.lock().unwrap();
1573 match dec.decrypt(payload, aad) {
1574 Ok((plaintext, leaf)) => {
1575 if !sender_leaf_out.is_null() {
1576 unsafe {
1577 *sender_leaf_out = leaf;
1578 }
1579 }
1580 GbpBuffer::from_vec(plaintext)
1581 }
1582 Err(e) => {
1583 set_last_error(e);
1584 GbpBuffer::empty()
1585 }
1586 }
1587}
1588
1589#[cfg(test)]
1590mod tests {
1591 use super::b64;
1592
1593 #[test]
1594 fn b64_empty() {
1595 assert_eq!(b64(b""), "");
1596 }
1597
1598 #[test]
1599 fn b64_single_byte() {
1600 let s = b64(b"f");
1607 assert_eq!(s, "Zg==");
1609 }
1610
1611 #[test]
1612 fn b64_two_bytes() {
1613 let s = b64(b"fo");
1614 assert_eq!(s, "Zm8=");
1618 }
1619
1620 #[test]
1621 fn b64_three_bytes() {
1622 let s = b64(b"foo");
1623 assert_eq!(s, "Zm9v");
1625 }
1626
1627 #[test]
1628 fn b64_known_vectors() {
1629 assert_eq!(b64(b""), "");
1631 assert_eq!(b64(b"f"), "Zg==");
1632 assert_eq!(b64(b"fo"), "Zm8=");
1633 assert_eq!(b64(b"foo"), "Zm9v");
1634 assert_eq!(b64(b"foob"), "Zm9vYg==");
1635 assert_eq!(b64(b"fooba"), "Zm9vYmE=");
1636 assert_eq!(b64(b"foobar"), "Zm9vYmFy");
1637 }
1638
1639 #[test]
1640 fn b64_padding_roundtrip() {
1641 for b in 0u8..=255 {
1643 let input = [b];
1644 let enc = b64(&input);
1645 assert_eq!(enc.len(), 4, "len mismatch for 0x{b:02X}: {enc}");
1647 assert!(enc.ends_with("=="), "missing padding for 0x{b:02X}: {enc}");
1649 }
1650 }
1651}