1#![allow(unsafe_op_in_unsafe_fn)]
26
27use gbp_stack::core::{ControlOpcode, NodeState, 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)]
733pub unsafe extern "C" fn gtp_client_send(
734 ch: i32,
735 nh: i32,
736 mh: i32,
737 target: u32,
738 message_id: u64,
739 text_ptr: *const u8,
740 text_len: usize,
741) -> GbpBuffer {
742 clear_last_error();
743 let text = unsafe { std::slice::from_raw_parts(text_ptr, text_len) };
744 let text = match std::str::from_utf8(text) {
745 Ok(s) => s,
746 Err(e) => {
747 set_last_error(format!("utf8: {e}"));
748 return GbpBuffer::empty();
749 }
750 };
751 let (c_arc, n_arc, m_arc) = (gtps().get(ch), nodes().get(nh), mls().get(mh));
752 let (Some(c_arc), Some(n_arc), Some(m_arc)) = (c_arc, n_arc, m_arc) else {
753 set_last_error("bad handle");
754 return GbpBuffer::empty();
755 };
756 let mut c = c_arc.lock().unwrap();
757 let mut n = n_arc.lock().unwrap();
758 let mut m = m_arc.lock().unwrap();
759 match c.send(&mut *n, &mut *m, target, message_id, text) {
760 Ok(of) => outbound_to_buffer(of),
761 Err(e) => {
762 set_last_error(e.to_string());
763 GbpBuffer::empty()
764 }
765 }
766}
767
768#[unsafe(no_mangle)]
778pub unsafe extern "C" fn gtp_client_accept(
779 ch: i32,
780 current_epoch: u64,
781 pt_ptr: *const u8,
782 pt_len: usize,
783) -> *mut c_char {
784 clear_last_error();
785 let pt = unsafe { std::slice::from_raw_parts(pt_ptr, pt_len) };
786 let Some(c_arc) = gtps().get(ch) else {
787 return alloc_cstring(r#"{"status":"error","reason":"bad client"}"#);
788 };
789 let mut c = c_arc.lock().unwrap();
790 #[derive(Serialize)]
791 struct Out<'a> {
792 status: &'a str,
793 sender: Option<u32>,
794 message_id: Option<u64>,
795 text: Option<String>,
796 reason: Option<String>,
797 }
798 let out = match c.accept(pt, current_epoch) {
799 Ok(GtpAccept::New(m)) => Out {
800 status: "new",
801 sender: Some(m.sender_id),
802 message_id: Some(m.message_id),
803 text: Some(m.text().unwrap_or("<binary>").to_string()),
804 reason: None,
805 },
806 Ok(GtpAccept::Duplicate(m)) => Out {
807 status: "duplicate",
808 sender: Some(m.sender_id),
809 message_id: Some(m.message_id),
810 text: Some(m.text().unwrap_or("<binary>").to_string()),
811 reason: None,
812 },
813 Err(e) => Out {
814 status: "error",
815 sender: None,
816 message_id: None,
817 text: None,
818 reason: Some(e.to_string()),
819 },
820 };
821 alloc_cstring(&serde_json::to_string(&out).unwrap_or_default())
822}
823
824#[unsafe(no_mangle)]
830pub extern "C" fn gap_client_create() -> i32 {
831 gaps().insert(GapClient::new())
832}
833
834#[unsafe(no_mangle)]
836pub extern "C" fn gap_client_destroy(h: i32) {
837 gaps().remove(h);
838}
839
840#[unsafe(no_mangle)]
842pub extern "C" fn gap_client_reset(h: i32) {
843 if let Some(c) = gaps().get(h) {
844 c.lock().unwrap().reset();
845 }
846}
847
848#[unsafe(no_mangle)]
853pub unsafe extern "C" fn gap_client_send(
854 ch: i32,
855 nh: i32,
856 mh: i32,
857 target: u32,
858 media_source_id: u32,
859 rtp_timestamp: u64,
860 opus_ptr: *const u8,
861 opus_len: usize,
862) -> GbpBuffer {
863 clear_last_error();
864 let opus = unsafe { std::slice::from_raw_parts(opus_ptr, opus_len) }.to_vec();
865 let (c_arc, n_arc, m_arc) = (gaps().get(ch), nodes().get(nh), mls().get(mh));
866 let (Some(c_arc), Some(n_arc), Some(m_arc)) = (c_arc, n_arc, m_arc) else {
867 set_last_error("bad handle");
868 return GbpBuffer::empty();
869 };
870 let mut c = c_arc.lock().unwrap();
871 let mut n = n_arc.lock().unwrap();
872 let mut m = m_arc.lock().unwrap();
873 match c.send(
874 &mut *n,
875 &mut *m,
876 target,
877 media_source_id,
878 rtp_timestamp,
879 opus,
880 ) {
881 Ok(of) => outbound_to_buffer(of),
882 Err(e) => {
883 set_last_error(e.to_string());
884 GbpBuffer::empty()
885 }
886 }
887}
888
889#[unsafe(no_mangle)]
894pub unsafe extern "C" fn gap_client_accept(
895 ch: i32,
896 current_epoch: u64,
897 pt_ptr: *const u8,
898 pt_len: usize,
899) -> *mut c_char {
900 clear_last_error();
901 let pt = unsafe { std::slice::from_raw_parts(pt_ptr, pt_len) };
902 let Some(c_arc) = gaps().get(ch) else {
903 return alloc_cstring(r#"{"status":"error","reason":"bad client"}"#);
904 };
905 let mut c = c_arc.lock().unwrap();
906 #[derive(Serialize)]
907 struct Out<'a> {
908 status: &'a str,
909 source: Option<u32>,
910 seq: Option<u32>,
911 bytes: Option<usize>,
912 reason: Option<String>,
913 }
914 let out = match c.accept(pt, current_epoch) {
915 Ok(GapAccept::New(p)) => Out {
916 status: "new",
917 source: Some(p.media_source_id),
918 seq: Some(p.rtp_sequence),
919 bytes: Some(p.opus_frame.len()),
920 reason: None,
921 },
922 Ok(GapAccept::Late(p)) => Out {
923 status: "late",
924 source: Some(p.media_source_id),
925 seq: Some(p.rtp_sequence),
926 bytes: Some(p.opus_frame.len()),
927 reason: None,
928 },
929 Err(e) => Out {
930 status: "error",
931 source: None,
932 seq: None,
933 bytes: None,
934 reason: Some(e.to_string()),
935 },
936 };
937 alloc_cstring(&serde_json::to_string(&out).unwrap_or_default())
938}
939
940#[unsafe(no_mangle)]
946pub extern "C" fn gsp_client_create() -> i32 {
947 gsps().insert(GspClient::new())
948}
949
950#[unsafe(no_mangle)]
952pub extern "C" fn gsp_client_destroy(h: i32) {
953 gsps().remove(h);
954}
955
956#[unsafe(no_mangle)]
958pub extern "C" fn gsp_client_reset(h: i32) {
959 if let Some(c) = gsps().get(h) {
960 c.lock().unwrap().reset();
961 }
962}
963
964#[unsafe(no_mangle)]
966pub extern "C" fn gsp_client_send(
967 ch: i32,
968 nh: i32,
969 mh: i32,
970 target: u32,
971 signal_type: u32,
972 role_claim: u32,
973 request_id: u32,
974) -> GbpBuffer {
975 clear_last_error();
976 let sig = match SignalType::try_from(signal_type) {
977 Ok(s) => s,
978 Err(_) => {
979 set_last_error(format!("bad signal {signal_type}"));
980 return GbpBuffer::empty();
981 }
982 };
983 let (c_arc, n_arc, m_arc) = (gsps().get(ch), nodes().get(nh), mls().get(mh));
984 let (Some(c_arc), Some(n_arc), Some(m_arc)) = (c_arc, n_arc, m_arc) else {
985 set_last_error("bad handle");
986 return GbpBuffer::empty();
987 };
988 let mut c = c_arc.lock().unwrap();
989 let mut n = n_arc.lock().unwrap();
990 let mut m = m_arc.lock().unwrap();
991 match c.send(&mut *n, &mut *m, target, sig, role_claim, request_id) {
992 Ok(of) => outbound_to_buffer(of),
993 Err(e) => {
994 set_last_error(e.to_string());
995 GbpBuffer::empty()
996 }
997 }
998}
999
1000#[unsafe(no_mangle)]
1007pub unsafe extern "C" fn gsp_client_send_with_args(
1008 ch: i32,
1009 nh: i32,
1010 mh: i32,
1011 target: u32,
1012 signal_type: u32,
1013 role_claim: u32,
1014 request_id: u32,
1015 args_ptr: *const u8,
1016 args_len: usize,
1017) -> GbpBuffer {
1018 clear_last_error();
1019 let args: &[u8] = if args_len == 0 || args_ptr.is_null() {
1020 &[]
1021 } else {
1022 unsafe { std::slice::from_raw_parts(args_ptr, args_len) }
1023 };
1024 let sig = match SignalType::try_from(signal_type) {
1025 Ok(s) => s,
1026 Err(_) => {
1027 set_last_error(format!("bad signal {signal_type}"));
1028 return GbpBuffer::empty();
1029 }
1030 };
1031 let (c_arc, n_arc, m_arc) = (gsps().get(ch), nodes().get(nh), mls().get(mh));
1032 let (Some(c_arc), Some(n_arc), Some(m_arc)) = (c_arc, n_arc, m_arc) else {
1033 set_last_error("bad handle");
1034 return GbpBuffer::empty();
1035 };
1036 let mut c = c_arc.lock().unwrap();
1037 let mut n = n_arc.lock().unwrap();
1038 let mut m = m_arc.lock().unwrap();
1039 match c.send_with_args(&mut *n, &mut *m, target, sig, role_claim, request_id, args) {
1040 Ok(of) => outbound_to_buffer(of),
1041 Err(e) => {
1042 set_last_error(e.to_string());
1043 GbpBuffer::empty()
1044 }
1045 }
1046}
1047
1048#[unsafe(no_mangle)]
1056pub unsafe extern "C" fn gsp_client_accept(
1057 ch: i32,
1058 current_epoch: u64,
1059 pt_ptr: *const u8,
1060 pt_len: usize,
1061) -> *mut c_char {
1062 clear_last_error();
1063 let pt = unsafe { std::slice::from_raw_parts(pt_ptr, pt_len) };
1064 let Some(c_arc) = gsps().get(ch) else {
1065 return alloc_cstring(r#"{"status":"error","reason":"bad client"}"#);
1066 };
1067 let mut c = c_arc.lock().unwrap();
1068 #[derive(Serialize)]
1069 struct Out<'a> {
1070 status: &'a str,
1071 signal: Option<&'a str>,
1072 signal_code: Option<u32>,
1073 sender: Option<u32>,
1074 role_claim: Option<u32>,
1075 request_id: Option<u32>,
1076 reason: Option<String>,
1077 }
1078 let out = match c.accept(pt, current_epoch) {
1079 Ok(GspAccept {
1080 signal,
1081 sender_id,
1082 role_claim,
1083 request_id,
1084 }) => Out {
1085 status: "new",
1086 signal: Some(signal.name()),
1087 signal_code: Some(signal as u32),
1088 sender: Some(sender_id),
1089 role_claim: Some(role_claim),
1090 request_id: Some(request_id),
1091 reason: None,
1092 },
1093 Err(gbp_stack::GspError::DuplicateRequest(rid)) => Out {
1094 status: "duplicate",
1095 signal: None,
1096 signal_code: None,
1097 sender: None,
1098 role_claim: None,
1099 request_id: Some(rid),
1100 reason: None,
1101 },
1102 Err(e) => Out {
1103 status: "error",
1104 signal: None,
1105 signal_code: None,
1106 sender: None,
1107 role_claim: None,
1108 request_id: None,
1109 reason: Some(e.to_string()),
1110 },
1111 };
1112 alloc_cstring(&serde_json::to_string(&out).unwrap_or_default())
1113}
1114
1115#[unsafe(no_mangle)]
1124pub unsafe extern "C" fn gbp_frame_encode_v(
1125 version: u8,
1126 group_id_16: *const u8,
1127 epoch: u64,
1128 transition_id: u32,
1129 stream_type: u32,
1130 stream_id: u32,
1131 flags: u16,
1132 sequence_no: u32,
1133 payload_ptr: *const u8,
1134 payload_len: usize,
1135) -> GbpBuffer {
1136 clear_last_error();
1137 let mut gid = [0u8; 16];
1138 unsafe { std::ptr::copy_nonoverlapping(group_id_16, gid.as_mut_ptr(), 16) };
1139 let st_u8 = StreamType::try_from(stream_type)
1140 .map(|s| s as u8)
1141 .unwrap_or(stream_type as u8);
1142 let payload: Vec<u8> = if payload_len == 0 || payload_ptr.is_null() {
1143 Vec::new()
1144 } else {
1145 unsafe { std::slice::from_raw_parts(payload_ptr, payload_len) }.to_vec()
1146 };
1147 let frame = gbp_stack::gbp::GbpFrame {
1148 version,
1149 group_id: serde_bytes::ByteBuf::from(gid.to_vec()),
1150 epoch,
1151 transition_id,
1152 stream_type: st_u8,
1153 stream_id,
1154 flags,
1155 sequence_no,
1156 payload_size: payload.len() as u32,
1157 encrypted_payload: serde_bytes::ByteBuf::from(payload),
1158 };
1159 GbpBuffer::from_vec(frame.to_cbor())
1160}
1161
1162#[unsafe(no_mangle)]
1164pub extern "C" fn gbp_error_lookup(code: u16) -> GbpBuffer {
1165 use gbp_stack::core::errors::ErrorSpec;
1166 match ErrorSpec::lookup(code) {
1167 Some(spec) => GbpBuffer::from_vec(ErrorObject::from_spec(spec, spec.name).to_cbor()),
1168 None => {
1169 set_last_error(format!("unknown error code 0x{code:04X}"));
1170 GbpBuffer::empty()
1171 }
1172 }
1173}
1174
1175#[allow(dead_code)]
1176fn _link(_f: &GbpFrame, _l: StreamLabel) {}
1177
1178#[derive(Serialize)]
1183#[serde(tag = "kind", rename_all = "snake_case")]
1184enum EventDto<'a> {
1185 StateChanged {
1186 from: String,
1187 to: String,
1188 },
1189 PayloadReceived {
1190 stream_type: &'a str,
1191 stream_type_code: u32,
1192 stream_id: u32,
1193 sequence_no: u32,
1194 flags: u16,
1195 plaintext_b64: String,
1196 },
1197 Control {
1198 from: u32,
1199 opcode: &'a str,
1200 opcode_code: u16,
1201 transition_id: u32,
1202 request_id: u32,
1203 args_b64: String,
1204 },
1205 Error {
1206 code: u16,
1207 code_hex: String,
1208 class: u8,
1209 retryable: bool,
1210 fatal: bool,
1211 reason: String,
1212 },
1213 EpochAdvanced {
1214 epoch: u64,
1215 transition_id: u32,
1216 },
1217 CoordinatorElectionNeeded {},
1218 BecameCoordinator {},
1219 CoordinatorClaim {
1220 claimant: u32,
1221 },
1222}
1223
1224fn b64(b: &[u8]) -> String {
1225 const A: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1226 let mut out = String::with_capacity(b.len().div_ceil(3) * 4);
1227 let mut i = 0;
1228 while i + 3 <= b.len() {
1229 let n = ((b[i] as u32) << 16) | ((b[i + 1] as u32) << 8) | (b[i + 2] as u32);
1230 out.push(A[(n >> 18) as usize & 0x3F] as char);
1231 out.push(A[(n >> 12) as usize & 0x3F] as char);
1232 out.push(A[(n >> 6) as usize & 0x3F] as char);
1233 out.push(A[n as usize & 0x3F] as char);
1234 i += 3;
1235 }
1236 let rem = b.len() - i;
1237 if rem == 1 {
1238 let n = (b[i] as u32) << 16;
1239 out.push(A[(n >> 18) as usize & 0x3F] as char);
1240 out.push(A[(n >> 12) as usize & 0x3F] as char);
1241 out.push('=');
1242 out.push('=');
1243 } else if rem == 2 {
1244 let n = ((b[i] as u32) << 16) | ((b[i + 1] as u32) << 8);
1245 out.push(A[(n >> 18) as usize & 0x3F] as char);
1246 out.push(A[(n >> 12) as usize & 0x3F] as char);
1247 out.push(A[(n >> 6) as usize & 0x3F] as char);
1248 out.push('=');
1249 }
1250 out
1251}
1252
1253fn dto<'a>(e: &'a Event) -> EventDto<'a> {
1254 match e {
1255 Event::StateChanged { from, to } => EventDto::StateChanged {
1256 from: from.to_string(),
1257 to: to.to_string(),
1258 },
1259 Event::PayloadReceived(DeliveredPayload {
1260 stream_type,
1261 stream_id,
1262 sequence_no,
1263 flags,
1264 plaintext,
1265 }) => EventDto::PayloadReceived {
1266 stream_type: match stream_type {
1267 StreamType::Control => "control",
1268 StreamType::Audio => "audio",
1269 StreamType::Text => "text",
1270 StreamType::Signal => "signal",
1271 },
1272 stream_type_code: *stream_type as u32,
1273 stream_id: *stream_id,
1274 sequence_no: *sequence_no,
1275 flags: *flags,
1276 plaintext_b64: b64(plaintext),
1277 },
1278 Event::Control {
1279 from,
1280 opcode,
1281 transition_id,
1282 request_id,
1283 args,
1284 } => EventDto::Control {
1285 from: *from,
1286 opcode: opcode.name(),
1287 opcode_code: *opcode as u16,
1288 transition_id: *transition_id,
1289 request_id: *request_id,
1290 args_b64: b64(args),
1291 },
1292 Event::Error {
1293 code,
1294 class,
1295 retryable,
1296 fatal,
1297 reason,
1298 } => EventDto::Error {
1299 code: *code,
1300 code_hex: format!("0x{code:04X}"),
1301 class: *class as u8,
1302 retryable: *retryable,
1303 fatal: *fatal,
1304 reason: reason.clone(),
1305 },
1306 Event::EpochAdvanced {
1307 epoch,
1308 transition_id,
1309 } => EventDto::EpochAdvanced {
1310 epoch: *epoch,
1311 transition_id: *transition_id,
1312 },
1313 Event::CoordinatorElectionNeeded => EventDto::CoordinatorElectionNeeded {},
1314 Event::BecameCoordinator => EventDto::BecameCoordinator {},
1315 Event::CoordinatorClaim { claimant } => EventDto::CoordinatorClaim {
1316 claimant: *claimant,
1317 },
1318 }
1319}
1320
1321fn events_to_json(events: &[Event]) -> String {
1322 let dtos: Vec<EventDto> = events.iter().map(dto).collect();
1323 serde_json::to_string(&dtos).unwrap_or_else(|_| "[]".to_string())
1324}
1325
1326#[allow(dead_code)]
1327const _STATES: [NodeState; 7] = [
1328 NodeState::Idle,
1329 NodeState::Connecting,
1330 NodeState::EstablishingGroup,
1331 NodeState::Active,
1332 NodeState::Resyncing,
1333 NodeState::Failed,
1334 NodeState::Closed,
1335];
1336
1337#[unsafe(no_mangle)]
1358pub unsafe extern "C" fn gbp_sframe_session_create(
1359 mls_handle: i32,
1360 suite: u8,
1361 label_ptr: *const u8,
1362 label_len: usize,
1363) -> i32 {
1364 clear_last_error();
1365 let suite = match CipherSuite::from_u8(suite) {
1366 Some(s) => s,
1367 None => {
1368 set_last_error(format!("unknown ciphersuite {suite}"));
1369 return 0;
1370 }
1371 };
1372 let label = unsafe {
1373 match std::str::from_utf8(std::slice::from_raw_parts(label_ptr, label_len)) {
1374 Ok(s) => s,
1375 Err(e) => {
1376 set_last_error(e);
1377 return 0;
1378 }
1379 }
1380 };
1381 let Some(mls_arc) = mls().get(mls_handle) else {
1382 set_last_error("invalid MLS handle");
1383 return 0;
1384 };
1385 let mls = mls_arc.lock().unwrap();
1386 match SFrameSession::from_mls(&mls, label, suite) {
1387 Ok(session) => sframe_sessions().insert(session.decryptor()),
1388 Err(e) => {
1389 set_last_error(e);
1390 0
1391 }
1392 }
1393}
1394
1395#[unsafe(no_mangle)]
1397pub extern "C" fn gbp_sframe_session_free(handle: i32) {
1398 sframe_sessions().remove(handle);
1399}
1400
1401#[unsafe(no_mangle)]
1412pub unsafe extern "C" fn gbp_sframe_encryptor_create(
1413 mls_handle: i32,
1414 session_handle: i32,
1415 leaf_index: u32,
1416 suite: u8,
1417 label_ptr: *const u8,
1418 label_len: usize,
1419) -> i32 {
1420 clear_last_error();
1421 let suite = match CipherSuite::from_u8(suite) {
1422 Some(s) => s,
1423 None => {
1424 set_last_error(format!("unknown ciphersuite {suite}"));
1425 return 0;
1426 }
1427 };
1428 let label = unsafe {
1429 match std::str::from_utf8(std::slice::from_raw_parts(label_ptr, label_len)) {
1430 Ok(s) => s,
1431 Err(e) => {
1432 set_last_error(e);
1433 return 0;
1434 }
1435 }
1436 };
1437 if sframe_sessions().get(session_handle).is_none() {
1439 set_last_error("invalid session handle");
1440 return 0;
1441 }
1442 let Some(mls_arc) = mls().get(mls_handle) else {
1443 set_last_error("invalid MLS handle");
1444 return 0;
1445 };
1446 let mls = mls_arc.lock().unwrap();
1447 match SFrameSession::from_mls(&mls, label, suite) {
1448 Ok(session) => sframe_encryptors().insert(session.encryptor(leaf_index)),
1449 Err(e) => {
1450 set_last_error(e);
1451 0
1452 }
1453 }
1454}
1455
1456#[unsafe(no_mangle)]
1458pub extern "C" fn gbp_sframe_encryptor_free(handle: i32) {
1459 sframe_encryptors().remove(handle);
1460}
1461
1462#[unsafe(no_mangle)]
1475pub unsafe extern "C" fn gbp_sframe_encrypt(
1476 enc_handle: i32,
1477 plaintext_ptr: *const u8,
1478 plaintext_len: usize,
1479 aad_ptr: *const u8,
1480 aad_len: usize,
1481) -> GbpBuffer {
1482 clear_last_error();
1483 let Some(enc_arc) = sframe_encryptors().get(enc_handle) else {
1484 set_last_error("invalid encryptor handle");
1485 return GbpBuffer::empty();
1486 };
1487 let plaintext = unsafe { std::slice::from_raw_parts(plaintext_ptr, plaintext_len) };
1488 let aad = if aad_ptr.is_null() || aad_len == 0 {
1489 &[][..]
1490 } else {
1491 unsafe { std::slice::from_raw_parts(aad_ptr, aad_len) }
1492 };
1493 let mut enc = enc_arc.lock().unwrap();
1494 match enc.encrypt(plaintext, aad) {
1495 Ok(payload) => GbpBuffer::from_vec(payload),
1496 Err(e) => {
1497 set_last_error(e);
1498 GbpBuffer::empty()
1499 }
1500 }
1501}
1502
1503#[unsafe(no_mangle)]
1515pub unsafe extern "C" fn gbp_sframe_decrypt(
1516 session_handle: i32,
1517 payload_ptr: *const u8,
1518 payload_len: usize,
1519 aad_ptr: *const u8,
1520 aad_len: usize,
1521 sender_leaf_out: *mut u32,
1522) -> GbpBuffer {
1523 clear_last_error();
1524 let Some(session_arc) = sframe_sessions().get(session_handle) else {
1525 set_last_error("invalid session handle");
1526 return GbpBuffer::empty();
1527 };
1528 let payload = unsafe { std::slice::from_raw_parts(payload_ptr, payload_len) };
1529 let aad = if aad_ptr.is_null() || aad_len == 0 {
1530 &[][..]
1531 } else {
1532 unsafe { std::slice::from_raw_parts(aad_ptr, aad_len) }
1533 };
1534 let mut dec = session_arc.lock().unwrap();
1535 match dec.decrypt(payload, aad) {
1536 Ok((plaintext, leaf)) => {
1537 if !sender_leaf_out.is_null() {
1538 unsafe {
1539 *sender_leaf_out = leaf;
1540 }
1541 }
1542 GbpBuffer::from_vec(plaintext)
1543 }
1544 Err(e) => {
1545 set_last_error(e);
1546 GbpBuffer::empty()
1547 }
1548 }
1549}
1550
1551#[cfg(test)]
1552mod tests {
1553 use super::b64;
1554
1555 #[test]
1556 fn b64_empty() {
1557 assert_eq!(b64(b""), "");
1558 }
1559
1560 #[test]
1561 fn b64_single_byte() {
1562 let s = b64(b"f");
1569 assert_eq!(s, "Zg==");
1571 }
1572
1573 #[test]
1574 fn b64_two_bytes() {
1575 let s = b64(b"fo");
1576 assert_eq!(s, "Zm8=");
1580 }
1581
1582 #[test]
1583 fn b64_three_bytes() {
1584 let s = b64(b"foo");
1585 assert_eq!(s, "Zm9v");
1587 }
1588
1589 #[test]
1590 fn b64_known_vectors() {
1591 assert_eq!(b64(b""), "");
1593 assert_eq!(b64(b"f"), "Zg==");
1594 assert_eq!(b64(b"fo"), "Zm8=");
1595 assert_eq!(b64(b"foo"), "Zm9v");
1596 assert_eq!(b64(b"foob"), "Zm9vYg==");
1597 assert_eq!(b64(b"fooba"), "Zm9vYmE=");
1598 assert_eq!(b64(b"foobar"), "Zm9vYmFy");
1599 }
1600
1601 #[test]
1602 fn b64_padding_roundtrip() {
1603 for b in 0u8..=255 {
1605 let input = [b];
1606 let enc = b64(&input);
1607 assert_eq!(enc.len(), 4, "len mismatch for 0x{b:02X}: {enc}");
1609 assert!(enc.ends_with("=="), "missing padding for 0x{b:02X}: {enc}");
1611 }
1612 }
1613}