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)]
523pub extern "C" fn gbp_mls_export_state(h: i32) -> GbpBuffer {
524 clear_last_error();
525 let Some(ctx_arc) = mls().get(h) else {
526 set_last_error("invalid MLS handle");
527 return GbpBuffer::empty();
528 };
529 let ctx = ctx_arc.lock().unwrap();
530 match ctx.export_state() {
531 Ok(bytes) => GbpBuffer::from_vec(bytes),
532 Err(e) => {
533 set_last_error(e);
534 GbpBuffer::empty()
535 }
536 }
537}
538
539#[unsafe(no_mangle)]
546pub unsafe extern "C" fn gbp_mls_restore_state(ptr: *const u8, len: usize) -> i32 {
547 clear_last_error();
548 let bytes = unsafe { std::slice::from_raw_parts(ptr, len) };
549 match MlsContext::restore_state(bytes) {
550 Ok(ctx) => mls().insert(ctx),
551 Err(e) => {
552 set_last_error(e);
553 0
554 }
555 }
556}
557
558#[unsafe(no_mangle)]
567pub unsafe extern "C" fn gbp_node_create(member_id: u32, group_id_16: *const u8) -> i32 {
568 clear_last_error();
569 let mut gid = [0u8; 16];
570 unsafe { std::ptr::copy_nonoverlapping(group_id_16, gid.as_mut_ptr(), 16) };
571 nodes().insert(GroupNode::new(member_id, gid))
572}
573
574#[unsafe(no_mangle)]
576pub extern "C" fn gbp_node_destroy(h: i32) {
577 nodes().remove(h);
578}
579
580#[unsafe(no_mangle)]
582pub extern "C" fn gbp_node_bootstrap_creator(h: i32, epoch: u64) -> bool {
583 let Some(n_arc) = nodes().get(h) else {
584 return false;
585 };
586 n_arc.lock().unwrap().bootstrap_as_creator(epoch);
587 true
588}
589
590#[unsafe(no_mangle)]
595pub extern "C" fn gbp_node_bootstrap_joiner(h: i32, epoch: u64, expected_first_tid: u32) -> bool {
596 let Some(n_arc) = nodes().get(h) else {
597 return false;
598 };
599 n_arc
600 .lock()
601 .unwrap()
602 .bootstrap_as_joiner(epoch, expected_first_tid);
603 true
604}
605
606#[unsafe(no_mangle)]
608pub extern "C" fn gbp_node_state(h: i32) -> u32 {
609 nodes()
610 .get(h)
611 .map(|n| n.lock().unwrap().state as u32)
612 .unwrap_or(u32::MAX)
613}
614
615#[unsafe(no_mangle)]
617pub extern "C" fn gbp_node_epoch(h: i32) -> u64 {
618 nodes()
619 .get(h)
620 .map(|n| n.lock().unwrap().current_epoch)
621 .unwrap_or(0)
622}
623
624#[unsafe(no_mangle)]
626pub extern "C" fn gbp_node_last_transition_id(h: i32) -> u32 {
627 nodes()
628 .get(h)
629 .map(|n| n.lock().unwrap().last_transition_id)
630 .unwrap_or(0)
631}
632
633#[unsafe(no_mangle)]
636pub extern "C" fn gbp_node_set_epoch(h: i32, epoch: u64) -> bool {
637 let Some(n_arc) = nodes().get(h) else {
638 return false;
639 };
640 n_arc.lock().unwrap().current_epoch = epoch;
641 true
642}
643
644#[unsafe(no_mangle)]
646pub extern "C" fn gbp_node_apply_transition(h: i32, tid: u32) -> bool {
647 let Some(n_arc) = nodes().get(h) else {
648 return false;
649 };
650 n_arc.lock().unwrap().apply_transition(tid);
651 true
652}
653
654#[unsafe(no_mangle)]
661pub unsafe extern "C" fn gbp_node_send_control(
662 nh: i32,
663 mh: i32,
664 target: u32,
665 opcode: u16,
666 transition_id: u32,
667 request_id: u32,
668 args_ptr: *const u8,
669 args_len: usize,
670) -> GbpBuffer {
671 clear_last_error();
672 let op = match ControlOpcode::try_from(opcode) {
673 Ok(o) => o,
674 Err(_) => {
675 set_last_error(format!("bad opcode 0x{opcode:04X}"));
676 return GbpBuffer::empty();
677 }
678 };
679 let args = if args_len == 0 {
680 Vec::new()
681 } else {
682 unsafe { std::slice::from_raw_parts(args_ptr, args_len) }.to_vec()
683 };
684 let (n_arc, m_arc) = (nodes().get(nh), mls().get(mh));
685 let (Some(n_arc), Some(m_arc)) = (n_arc, m_arc) else {
686 set_last_error("bad node/mls handle");
687 return GbpBuffer::empty();
688 };
689 let mut n = n_arc.lock().unwrap();
690 let mut m = m_arc.lock().unwrap();
691 match n.send_control(&mut *m, target, op, transition_id, request_id, args) {
692 Ok(of) => outbound_to_buffer(of),
693 Err(e) => {
694 set_last_error(e.to_string());
695 GbpBuffer::empty()
696 }
697 }
698}
699
700#[unsafe(no_mangle)]
705pub unsafe extern "C" fn gbp_node_on_wire(
706 nh: i32,
707 mh: i32,
708 wire_ptr: *const u8,
709 wire_len: usize,
710) -> *mut c_char {
711 clear_last_error();
712 let wire = unsafe { std::slice::from_raw_parts(wire_ptr, wire_len) };
713 let (n_arc, m_arc) = (nodes().get(nh), mls().get(mh));
714 let (Some(n_arc), Some(m_arc)) = (n_arc, m_arc) else {
715 set_last_error("bad node/mls handle");
716 return alloc_cstring("[]");
717 };
718 let mut n = n_arc.lock().unwrap();
719 let mut m = m_arc.lock().unwrap();
720 let events = match n.on_wire(&mut *m, wire) {
721 Ok(e) => e,
722 Err(e) => {
723 set_last_error(e.to_string());
724 return alloc_cstring("[]");
725 }
726 };
727 alloc_cstring(&events_to_json(&events))
728}
729
730#[unsafe(no_mangle)]
732pub extern "C" fn gbp_node_drain_events(nh: i32) -> *mut c_char {
733 let Some(n_arc) = nodes().get(nh) else {
734 return alloc_cstring("[]");
735 };
736 alloc_cstring(&events_to_json(&n_arc.lock().unwrap().drain_events()))
737}
738
739fn outbound_to_buffer(of: OutboundFrame) -> GbpBuffer {
740 let mut out = Vec::with_capacity(4 + of.wire.len());
741 out.extend_from_slice(&of.to.to_le_bytes());
742 out.extend_from_slice(&of.wire);
743 GbpBuffer::from_vec(out)
744}
745
746#[unsafe(no_mangle)]
752pub extern "C" fn gtp_client_create() -> i32 {
753 gtps().insert(GtpClient::new())
754}
755
756#[unsafe(no_mangle)]
758pub extern "C" fn gtp_client_destroy(h: i32) {
759 gtps().remove(h);
760}
761
762#[unsafe(no_mangle)]
764pub extern "C" fn gtp_client_reset(h: i32) {
765 if let Some(c) = gtps().get(h) {
766 c.lock().unwrap().reset();
767 }
768}
769
770#[unsafe(no_mangle)]
778pub unsafe extern "C" fn gtp_client_send(
779 ch: i32,
780 nh: i32,
781 mh: i32,
782 target: u32,
783 message_id: u64,
784 text_ptr: *const u8,
785 text_len: usize,
786 codec: u8,
787) -> GbpBuffer {
788 clear_last_error();
789 let text = unsafe { std::slice::from_raw_parts(text_ptr, text_len) };
790 let text = match std::str::from_utf8(text) {
791 Ok(s) => s,
792 Err(e) => {
793 set_last_error(format!("utf8: {e}"));
794 return GbpBuffer::empty();
795 }
796 };
797 let codec = PayloadCodec::from_u8(codec).unwrap_or(PayloadCodec::Cbor);
798 let (c_arc, n_arc, m_arc) = (gtps().get(ch), nodes().get(nh), mls().get(mh));
799 let (Some(c_arc), Some(n_arc), Some(m_arc)) = (c_arc, n_arc, m_arc) else {
800 set_last_error("bad handle");
801 return GbpBuffer::empty();
802 };
803 let mut c = c_arc.lock().unwrap();
804 let mut n = n_arc.lock().unwrap();
805 let mut m = m_arc.lock().unwrap();
806 match c.send(&mut *n, &mut *m, target, message_id, text, codec) {
807 Ok(of) => outbound_to_buffer(of),
808 Err(e) => {
809 set_last_error(e.to_string());
810 GbpBuffer::empty()
811 }
812 }
813}
814
815#[unsafe(no_mangle)]
827pub unsafe extern "C" fn gtp_client_accept(
828 ch: i32,
829 current_epoch: u64,
830 pt_ptr: *const u8,
831 pt_len: usize,
832 codec: u8,
833) -> *mut c_char {
834 clear_last_error();
835 let pt = unsafe { std::slice::from_raw_parts(pt_ptr, pt_len) };
836 let codec = PayloadCodec::from_u8(codec).unwrap_or(PayloadCodec::Cbor);
837 let Some(c_arc) = gtps().get(ch) else {
838 return alloc_cstring(r#"{"status":"error","reason":"bad client"}"#);
839 };
840 let mut c = c_arc.lock().unwrap();
841 #[derive(Serialize)]
842 struct Out<'a> {
843 status: &'a str,
844 sender: Option<u32>,
845 message_id: Option<u64>,
846 text: Option<String>,
847 reason: Option<String>,
848 }
849 let out = match c.accept(pt, current_epoch, codec) {
850 Ok(GtpAccept::New(m)) => Out {
851 status: "new",
852 sender: Some(m.sender_id),
853 message_id: Some(m.message_id),
854 text: Some(m.text().unwrap_or("<binary>").to_string()),
855 reason: None,
856 },
857 Ok(GtpAccept::Duplicate(m)) => Out {
858 status: "duplicate",
859 sender: Some(m.sender_id),
860 message_id: Some(m.message_id),
861 text: Some(m.text().unwrap_or("<binary>").to_string()),
862 reason: None,
863 },
864 Err(e) => Out {
865 status: "error",
866 sender: None,
867 message_id: None,
868 text: None,
869 reason: Some(e.to_string()),
870 },
871 };
872 alloc_cstring(&serde_json::to_string(&out).unwrap_or_default())
873}
874
875#[unsafe(no_mangle)]
881pub extern "C" fn gap_client_create() -> i32 {
882 gaps().insert(GapClient::new())
883}
884
885#[unsafe(no_mangle)]
887pub extern "C" fn gap_client_destroy(h: i32) {
888 gaps().remove(h);
889}
890
891#[unsafe(no_mangle)]
893pub extern "C" fn gap_client_reset(h: i32) {
894 if let Some(c) = gaps().get(h) {
895 c.lock().unwrap().reset();
896 }
897}
898
899#[unsafe(no_mangle)]
907pub unsafe extern "C" fn gap_client_send(
908 ch: i32,
909 nh: i32,
910 mh: i32,
911 target: u32,
912 media_source_id: u32,
913 rtp_timestamp: u64,
914 opus_ptr: *const u8,
915 opus_len: usize,
916 codec: u8,
917) -> GbpBuffer {
918 clear_last_error();
919 let opus = unsafe { std::slice::from_raw_parts(opus_ptr, opus_len) }.to_vec();
920 let codec = PayloadCodec::from_u8(codec).unwrap_or(PayloadCodec::Cbor);
921 let (c_arc, n_arc, m_arc) = (gaps().get(ch), nodes().get(nh), mls().get(mh));
922 let (Some(c_arc), Some(n_arc), Some(m_arc)) = (c_arc, n_arc, m_arc) else {
923 set_last_error("bad handle");
924 return GbpBuffer::empty();
925 };
926 let mut c = c_arc.lock().unwrap();
927 let mut n = n_arc.lock().unwrap();
928 let mut m = m_arc.lock().unwrap();
929 match c.send(
930 &mut *n,
931 &mut *m,
932 target,
933 media_source_id,
934 rtp_timestamp,
935 opus,
936 codec,
937 ) {
938 Ok(of) => outbound_to_buffer(of),
939 Err(e) => {
940 set_last_error(e.to_string());
941 GbpBuffer::empty()
942 }
943 }
944}
945
946#[unsafe(no_mangle)]
954pub unsafe extern "C" fn gap_client_accept(
955 ch: i32,
956 current_epoch: u64,
957 pt_ptr: *const u8,
958 pt_len: usize,
959 codec: u8,
960) -> *mut c_char {
961 clear_last_error();
962 let pt = unsafe { std::slice::from_raw_parts(pt_ptr, pt_len) };
963 let codec = PayloadCodec::from_u8(codec).unwrap_or(PayloadCodec::Cbor);
964 let Some(c_arc) = gaps().get(ch) else {
965 return alloc_cstring(r#"{"status":"error","reason":"bad client"}"#);
966 };
967 let mut c = c_arc.lock().unwrap();
968 #[derive(Serialize)]
969 struct Out<'a> {
970 status: &'a str,
971 source: Option<u32>,
972 seq: Option<u32>,
973 bytes: Option<usize>,
974 reason: Option<String>,
975 }
976 let out = match c.accept(pt, current_epoch, codec) {
977 Ok(GapAccept::New(p)) => Out {
978 status: "new",
979 source: Some(p.media_source_id),
980 seq: Some(p.rtp_sequence),
981 bytes: Some(p.opus_frame.len()),
982 reason: None,
983 },
984 Ok(GapAccept::Late(p)) => Out {
985 status: "late",
986 source: Some(p.media_source_id),
987 seq: Some(p.rtp_sequence),
988 bytes: Some(p.opus_frame.len()),
989 reason: None,
990 },
991 Err(e) => Out {
992 status: "error",
993 source: None,
994 seq: None,
995 bytes: None,
996 reason: Some(e.to_string()),
997 },
998 };
999 alloc_cstring(&serde_json::to_string(&out).unwrap_or_default())
1000}
1001
1002#[unsafe(no_mangle)]
1008pub extern "C" fn gsp_client_create() -> i32 {
1009 gsps().insert(GspClient::new())
1010}
1011
1012#[unsafe(no_mangle)]
1014pub extern "C" fn gsp_client_destroy(h: i32) {
1015 gsps().remove(h);
1016}
1017
1018#[unsafe(no_mangle)]
1020pub extern "C" fn gsp_client_reset(h: i32) {
1021 if let Some(c) = gsps().get(h) {
1022 c.lock().unwrap().reset();
1023 }
1024}
1025
1026#[unsafe(no_mangle)]
1031pub extern "C" fn gsp_client_send(
1032 ch: i32,
1033 nh: i32,
1034 mh: i32,
1035 target: u32,
1036 signal_type: u32,
1037 role_claim: u32,
1038 request_id: u32,
1039 codec: u8,
1040) -> GbpBuffer {
1041 clear_last_error();
1042 let sig = match SignalType::try_from(signal_type) {
1043 Ok(s) => s,
1044 Err(_) => {
1045 set_last_error(format!("bad signal {signal_type}"));
1046 return GbpBuffer::empty();
1047 }
1048 };
1049 let codec = PayloadCodec::from_u8(codec).unwrap_or(PayloadCodec::Cbor);
1050 let (c_arc, n_arc, m_arc) = (gsps().get(ch), nodes().get(nh), mls().get(mh));
1051 let (Some(c_arc), Some(n_arc), Some(m_arc)) = (c_arc, n_arc, m_arc) else {
1052 set_last_error("bad handle");
1053 return GbpBuffer::empty();
1054 };
1055 let mut c = c_arc.lock().unwrap();
1056 let mut n = n_arc.lock().unwrap();
1057 let mut m = m_arc.lock().unwrap();
1058 match c.send(&mut *n, &mut *m, target, sig, role_claim, request_id, codec) {
1059 Ok(of) => outbound_to_buffer(of),
1060 Err(e) => {
1061 set_last_error(e.to_string());
1062 GbpBuffer::empty()
1063 }
1064 }
1065}
1066
1067#[unsafe(no_mangle)]
1077pub unsafe extern "C" fn gsp_client_send_with_args(
1078 ch: i32,
1079 nh: i32,
1080 mh: i32,
1081 target: u32,
1082 signal_type: u32,
1083 role_claim: u32,
1084 request_id: u32,
1085 args_ptr: *const u8,
1086 args_len: usize,
1087 codec: u8,
1088) -> GbpBuffer {
1089 clear_last_error();
1090 let args: &[u8] = if args_len == 0 || args_ptr.is_null() {
1091 &[]
1092 } else {
1093 unsafe { std::slice::from_raw_parts(args_ptr, args_len) }
1094 };
1095 let sig = match SignalType::try_from(signal_type) {
1096 Ok(s) => s,
1097 Err(_) => {
1098 set_last_error(format!("bad signal {signal_type}"));
1099 return GbpBuffer::empty();
1100 }
1101 };
1102 let codec = PayloadCodec::from_u8(codec).unwrap_or(PayloadCodec::Cbor);
1103 let (c_arc, n_arc, m_arc) = (gsps().get(ch), nodes().get(nh), mls().get(mh));
1104 let (Some(c_arc), Some(n_arc), Some(m_arc)) = (c_arc, n_arc, m_arc) else {
1105 set_last_error("bad handle");
1106 return GbpBuffer::empty();
1107 };
1108 let mut c = c_arc.lock().unwrap();
1109 let mut n = n_arc.lock().unwrap();
1110 let mut m = m_arc.lock().unwrap();
1111 match c.send_with_args(
1112 &mut *n, &mut *m, target, sig, role_claim, request_id, args, codec,
1113 ) {
1114 Ok(of) => outbound_to_buffer(of),
1115 Err(e) => {
1116 set_last_error(e.to_string());
1117 GbpBuffer::empty()
1118 }
1119 }
1120}
1121
1122#[unsafe(no_mangle)]
1132pub unsafe extern "C" fn gsp_client_accept(
1133 ch: i32,
1134 current_epoch: u64,
1135 pt_ptr: *const u8,
1136 pt_len: usize,
1137 codec: u8,
1138) -> *mut c_char {
1139 clear_last_error();
1140 let pt = unsafe { std::slice::from_raw_parts(pt_ptr, pt_len) };
1141 let codec = PayloadCodec::from_u8(codec).unwrap_or(PayloadCodec::Cbor);
1142 let Some(c_arc) = gsps().get(ch) else {
1143 return alloc_cstring(r#"{"status":"error","reason":"bad client"}"#);
1144 };
1145 let mut c = c_arc.lock().unwrap();
1146 #[derive(Serialize)]
1147 struct Out<'a> {
1148 status: &'a str,
1149 signal: Option<&'a str>,
1150 signal_code: Option<u32>,
1151 sender: Option<u32>,
1152 role_claim: Option<u32>,
1153 request_id: Option<u32>,
1154 reason: Option<String>,
1155 }
1156 let out = match c.accept(pt, current_epoch, codec) {
1157 Ok(GspAccept {
1158 signal,
1159 sender_id,
1160 role_claim,
1161 request_id,
1162 }) => Out {
1163 status: "new",
1164 signal: Some(signal.name()),
1165 signal_code: Some(signal as u32),
1166 sender: Some(sender_id),
1167 role_claim: Some(role_claim),
1168 request_id: Some(request_id),
1169 reason: None,
1170 },
1171 Err(gbp_stack::GspError::DuplicateRequest(rid)) => Out {
1172 status: "duplicate",
1173 signal: None,
1174 signal_code: None,
1175 sender: None,
1176 role_claim: None,
1177 request_id: Some(rid),
1178 reason: None,
1179 },
1180 Err(e) => Out {
1181 status: "error",
1182 signal: None,
1183 signal_code: None,
1184 sender: None,
1185 role_claim: None,
1186 request_id: None,
1187 reason: Some(e.to_string()),
1188 },
1189 };
1190 alloc_cstring(&serde_json::to_string(&out).unwrap_or_default())
1191}
1192
1193#[unsafe(no_mangle)]
1202pub unsafe extern "C" fn gbp_frame_encode_v(
1203 version: u8,
1204 group_id_16: *const u8,
1205 epoch: u64,
1206 transition_id: u32,
1207 stream_type: u32,
1208 stream_id: u32,
1209 flags: u16,
1210 sequence_no: u32,
1211 payload_ptr: *const u8,
1212 payload_len: usize,
1213) -> GbpBuffer {
1214 clear_last_error();
1215 let mut gid = [0u8; 16];
1216 unsafe { std::ptr::copy_nonoverlapping(group_id_16, gid.as_mut_ptr(), 16) };
1217 let st_u8 = StreamType::try_from(stream_type)
1218 .map(|s| s as u8)
1219 .unwrap_or(stream_type as u8);
1220 let payload: Vec<u8> = if payload_len == 0 || payload_ptr.is_null() {
1221 Vec::new()
1222 } else {
1223 unsafe { std::slice::from_raw_parts(payload_ptr, payload_len) }.to_vec()
1224 };
1225 let frame = gbp_stack::gbp::GbpFrame {
1226 version,
1227 group_id: serde_bytes::ByteBuf::from(gid.to_vec()),
1228 epoch,
1229 transition_id,
1230 stream_type: st_u8,
1231 stream_id,
1232 flags,
1233 sequence_no,
1234 payload_format: 0u8,
1235 payload_size: payload.len() as u32,
1236 encrypted_payload: serde_bytes::ByteBuf::from(payload),
1237 };
1238 GbpBuffer::from_vec(frame.to_cbor())
1239}
1240
1241#[unsafe(no_mangle)]
1243pub extern "C" fn gbp_error_lookup(code: u16) -> GbpBuffer {
1244 use gbp_stack::core::errors::ErrorSpec;
1245 match ErrorSpec::lookup(code) {
1246 Some(spec) => GbpBuffer::from_vec(ErrorObject::from_spec(spec, spec.name).to_cbor()),
1247 None => {
1248 set_last_error(format!("unknown error code 0x{code:04X}"));
1249 GbpBuffer::empty()
1250 }
1251 }
1252}
1253
1254#[allow(dead_code)]
1255fn _link(_f: &GbpFrame, _l: StreamLabel) {}
1256
1257#[derive(Serialize)]
1262#[serde(tag = "kind", rename_all = "snake_case")]
1263enum EventDto<'a> {
1264 StateChanged {
1265 from: String,
1266 to: String,
1267 },
1268 PayloadReceived {
1269 stream_type: &'a str,
1270 stream_type_code: u32,
1271 stream_id: u32,
1272 sequence_no: u32,
1273 flags: u16,
1274 codec: u8,
1275 plaintext_b64: String,
1276 },
1277 Control {
1278 from: u32,
1279 opcode: &'a str,
1280 opcode_code: u16,
1281 transition_id: u32,
1282 request_id: u32,
1283 args_b64: String,
1284 },
1285 Error {
1286 code: u16,
1287 code_hex: String,
1288 class: u8,
1289 retryable: bool,
1290 fatal: bool,
1291 reason: String,
1292 },
1293 EpochAdvanced {
1294 epoch: u64,
1295 transition_id: u32,
1296 },
1297 CoordinatorElectionNeeded {},
1298 BecameCoordinator {},
1299 CoordinatorClaim {
1300 claimant: u32,
1301 },
1302}
1303
1304fn b64(b: &[u8]) -> String {
1305 const A: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1306 let mut out = String::with_capacity(b.len().div_ceil(3) * 4);
1307 let mut i = 0;
1308 while i + 3 <= b.len() {
1309 let n = ((b[i] as u32) << 16) | ((b[i + 1] as u32) << 8) | (b[i + 2] as u32);
1310 out.push(A[(n >> 18) as usize & 0x3F] as char);
1311 out.push(A[(n >> 12) as usize & 0x3F] as char);
1312 out.push(A[(n >> 6) as usize & 0x3F] as char);
1313 out.push(A[n as usize & 0x3F] as char);
1314 i += 3;
1315 }
1316 let rem = b.len() - i;
1317 if rem == 1 {
1318 let n = (b[i] as u32) << 16;
1319 out.push(A[(n >> 18) as usize & 0x3F] as char);
1320 out.push(A[(n >> 12) as usize & 0x3F] as char);
1321 out.push('=');
1322 out.push('=');
1323 } else if rem == 2 {
1324 let n = ((b[i] as u32) << 16) | ((b[i + 1] as u32) << 8);
1325 out.push(A[(n >> 18) as usize & 0x3F] as char);
1326 out.push(A[(n >> 12) as usize & 0x3F] as char);
1327 out.push(A[(n >> 6) as usize & 0x3F] as char);
1328 out.push('=');
1329 }
1330 out
1331}
1332
1333fn dto<'a>(e: &'a Event) -> EventDto<'a> {
1334 match e {
1335 Event::StateChanged { from, to } => EventDto::StateChanged {
1336 from: from.to_string(),
1337 to: to.to_string(),
1338 },
1339 Event::PayloadReceived(DeliveredPayload {
1340 stream_type,
1341 stream_id,
1342 sequence_no,
1343 flags,
1344 plaintext,
1345 codec,
1346 }) => EventDto::PayloadReceived {
1347 stream_type: match stream_type {
1348 StreamType::Control => "control",
1349 StreamType::Audio => "audio",
1350 StreamType::Text => "text",
1351 StreamType::Signal => "signal",
1352 },
1353 stream_type_code: *stream_type as u32,
1354 stream_id: *stream_id,
1355 sequence_no: *sequence_no,
1356 flags: *flags,
1357 codec: codec.as_u8(),
1358 plaintext_b64: b64(plaintext),
1359 },
1360 Event::Control {
1361 from,
1362 opcode,
1363 transition_id,
1364 request_id,
1365 args,
1366 } => EventDto::Control {
1367 from: *from,
1368 opcode: opcode.name(),
1369 opcode_code: *opcode as u16,
1370 transition_id: *transition_id,
1371 request_id: *request_id,
1372 args_b64: b64(args),
1373 },
1374 Event::Error {
1375 code,
1376 class,
1377 retryable,
1378 fatal,
1379 reason,
1380 } => EventDto::Error {
1381 code: *code,
1382 code_hex: format!("0x{code:04X}"),
1383 class: *class as u8,
1384 retryable: *retryable,
1385 fatal: *fatal,
1386 reason: reason.clone(),
1387 },
1388 Event::EpochAdvanced {
1389 epoch,
1390 transition_id,
1391 } => EventDto::EpochAdvanced {
1392 epoch: *epoch,
1393 transition_id: *transition_id,
1394 },
1395 Event::CoordinatorElectionNeeded => EventDto::CoordinatorElectionNeeded {},
1396 Event::BecameCoordinator => EventDto::BecameCoordinator {},
1397 Event::CoordinatorClaim { claimant } => EventDto::CoordinatorClaim {
1398 claimant: *claimant,
1399 },
1400 }
1401}
1402
1403fn events_to_json(events: &[Event]) -> String {
1404 let dtos: Vec<EventDto> = events.iter().map(dto).collect();
1405 serde_json::to_string(&dtos).unwrap_or_else(|_| "[]".to_string())
1406}
1407
1408#[allow(dead_code)]
1409const _STATES: [NodeState; 7] = [
1410 NodeState::Idle,
1411 NodeState::Connecting,
1412 NodeState::EstablishingGroup,
1413 NodeState::Active,
1414 NodeState::Resyncing,
1415 NodeState::Failed,
1416 NodeState::Closed,
1417];
1418
1419#[unsafe(no_mangle)]
1440pub unsafe extern "C" fn gbp_sframe_session_create(
1441 mls_handle: i32,
1442 suite: u8,
1443 label_ptr: *const u8,
1444 label_len: usize,
1445) -> i32 {
1446 clear_last_error();
1447 let suite = match CipherSuite::from_u8(suite) {
1448 Some(s) => s,
1449 None => {
1450 set_last_error(format!("unknown ciphersuite {suite}"));
1451 return 0;
1452 }
1453 };
1454 let label = unsafe {
1455 match std::str::from_utf8(std::slice::from_raw_parts(label_ptr, label_len)) {
1456 Ok(s) => s,
1457 Err(e) => {
1458 set_last_error(e);
1459 return 0;
1460 }
1461 }
1462 };
1463 let Some(mls_arc) = mls().get(mls_handle) else {
1464 set_last_error("invalid MLS handle");
1465 return 0;
1466 };
1467 let mls = mls_arc.lock().unwrap();
1468 match SFrameSession::from_mls(&mls, label, suite) {
1469 Ok(session) => sframe_sessions().insert(session.decryptor()),
1470 Err(e) => {
1471 set_last_error(e);
1472 0
1473 }
1474 }
1475}
1476
1477#[unsafe(no_mangle)]
1479pub extern "C" fn gbp_sframe_session_free(handle: i32) {
1480 sframe_sessions().remove(handle);
1481}
1482
1483#[unsafe(no_mangle)]
1494pub unsafe extern "C" fn gbp_sframe_encryptor_create(
1495 mls_handle: i32,
1496 session_handle: i32,
1497 leaf_index: u32,
1498 suite: u8,
1499 label_ptr: *const u8,
1500 label_len: usize,
1501) -> i32 {
1502 clear_last_error();
1503 let suite = match CipherSuite::from_u8(suite) {
1504 Some(s) => s,
1505 None => {
1506 set_last_error(format!("unknown ciphersuite {suite}"));
1507 return 0;
1508 }
1509 };
1510 let label = unsafe {
1511 match std::str::from_utf8(std::slice::from_raw_parts(label_ptr, label_len)) {
1512 Ok(s) => s,
1513 Err(e) => {
1514 set_last_error(e);
1515 return 0;
1516 }
1517 }
1518 };
1519 if sframe_sessions().get(session_handle).is_none() {
1521 set_last_error("invalid session handle");
1522 return 0;
1523 }
1524 let Some(mls_arc) = mls().get(mls_handle) else {
1525 set_last_error("invalid MLS handle");
1526 return 0;
1527 };
1528 let mls = mls_arc.lock().unwrap();
1529 match SFrameSession::from_mls(&mls, label, suite) {
1530 Ok(session) => sframe_encryptors().insert(session.encryptor(leaf_index)),
1531 Err(e) => {
1532 set_last_error(e);
1533 0
1534 }
1535 }
1536}
1537
1538#[unsafe(no_mangle)]
1540pub extern "C" fn gbp_sframe_encryptor_free(handle: i32) {
1541 sframe_encryptors().remove(handle);
1542}
1543
1544#[unsafe(no_mangle)]
1557pub unsafe extern "C" fn gbp_sframe_encrypt(
1558 enc_handle: i32,
1559 plaintext_ptr: *const u8,
1560 plaintext_len: usize,
1561 aad_ptr: *const u8,
1562 aad_len: usize,
1563) -> GbpBuffer {
1564 clear_last_error();
1565 let Some(enc_arc) = sframe_encryptors().get(enc_handle) else {
1566 set_last_error("invalid encryptor handle");
1567 return GbpBuffer::empty();
1568 };
1569 let plaintext = unsafe { std::slice::from_raw_parts(plaintext_ptr, plaintext_len) };
1570 let aad = if aad_ptr.is_null() || aad_len == 0 {
1571 &[][..]
1572 } else {
1573 unsafe { std::slice::from_raw_parts(aad_ptr, aad_len) }
1574 };
1575 let mut enc = enc_arc.lock().unwrap();
1576 match enc.encrypt(plaintext, aad) {
1577 Ok(payload) => GbpBuffer::from_vec(payload),
1578 Err(e) => {
1579 set_last_error(e);
1580 GbpBuffer::empty()
1581 }
1582 }
1583}
1584
1585#[unsafe(no_mangle)]
1597pub unsafe extern "C" fn gbp_sframe_decrypt(
1598 session_handle: i32,
1599 payload_ptr: *const u8,
1600 payload_len: usize,
1601 aad_ptr: *const u8,
1602 aad_len: usize,
1603 sender_leaf_out: *mut u32,
1604) -> GbpBuffer {
1605 clear_last_error();
1606 let Some(session_arc) = sframe_sessions().get(session_handle) else {
1607 set_last_error("invalid session handle");
1608 return GbpBuffer::empty();
1609 };
1610 let payload = unsafe { std::slice::from_raw_parts(payload_ptr, payload_len) };
1611 let aad = if aad_ptr.is_null() || aad_len == 0 {
1612 &[][..]
1613 } else {
1614 unsafe { std::slice::from_raw_parts(aad_ptr, aad_len) }
1615 };
1616 let mut dec = session_arc.lock().unwrap();
1617 match dec.decrypt(payload, aad) {
1618 Ok((plaintext, leaf)) => {
1619 if !sender_leaf_out.is_null() {
1620 unsafe {
1621 *sender_leaf_out = leaf;
1622 }
1623 }
1624 GbpBuffer::from_vec(plaintext)
1625 }
1626 Err(e) => {
1627 set_last_error(e);
1628 GbpBuffer::empty()
1629 }
1630 }
1631}
1632
1633#[cfg(test)]
1634mod tests {
1635 use super::b64;
1636
1637 #[test]
1638 fn b64_empty() {
1639 assert_eq!(b64(b""), "");
1640 }
1641
1642 #[test]
1643 fn b64_single_byte() {
1644 let s = b64(b"f");
1651 assert_eq!(s, "Zg==");
1653 }
1654
1655 #[test]
1656 fn b64_two_bytes() {
1657 let s = b64(b"fo");
1658 assert_eq!(s, "Zm8=");
1662 }
1663
1664 #[test]
1665 fn b64_three_bytes() {
1666 let s = b64(b"foo");
1667 assert_eq!(s, "Zm9v");
1669 }
1670
1671 #[test]
1672 fn b64_known_vectors() {
1673 assert_eq!(b64(b""), "");
1675 assert_eq!(b64(b"f"), "Zg==");
1676 assert_eq!(b64(b"fo"), "Zm8=");
1677 assert_eq!(b64(b"foo"), "Zm9v");
1678 assert_eq!(b64(b"foob"), "Zm9vYg==");
1679 assert_eq!(b64(b"fooba"), "Zm9vYmE=");
1680 assert_eq!(b64(b"foobar"), "Zm9vYmFy");
1681 }
1682
1683 #[test]
1684 fn b64_padding_roundtrip() {
1685 for b in 0u8..=255 {
1687 let input = [b];
1688 let enc = b64(&input);
1689 assert_eq!(enc.len(), 4, "len mismatch for 0x{b:02X}: {enc}");
1691 assert!(enc.ends_with("=="), "missing padding for 0x{b:02X}: {enc}");
1693 }
1694 }
1695}