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(&mut *n, &mut *m, target, sig, role_claim, request_id, args, codec) {
1112 Ok(of) => outbound_to_buffer(of),
1113 Err(e) => {
1114 set_last_error(e.to_string());
1115 GbpBuffer::empty()
1116 }
1117 }
1118}
1119
1120#[unsafe(no_mangle)]
1130pub unsafe extern "C" fn gsp_client_accept(
1131 ch: i32,
1132 current_epoch: u64,
1133 pt_ptr: *const u8,
1134 pt_len: usize,
1135 codec: u8,
1136) -> *mut c_char {
1137 clear_last_error();
1138 let pt = unsafe { std::slice::from_raw_parts(pt_ptr, pt_len) };
1139 let codec = PayloadCodec::from_u8(codec).unwrap_or(PayloadCodec::Cbor);
1140 let Some(c_arc) = gsps().get(ch) else {
1141 return alloc_cstring(r#"{"status":"error","reason":"bad client"}"#);
1142 };
1143 let mut c = c_arc.lock().unwrap();
1144 #[derive(Serialize)]
1145 struct Out<'a> {
1146 status: &'a str,
1147 signal: Option<&'a str>,
1148 signal_code: Option<u32>,
1149 sender: Option<u32>,
1150 role_claim: Option<u32>,
1151 request_id: Option<u32>,
1152 reason: Option<String>,
1153 }
1154 let out = match c.accept(pt, current_epoch, codec) {
1155 Ok(GspAccept {
1156 signal,
1157 sender_id,
1158 role_claim,
1159 request_id,
1160 }) => Out {
1161 status: "new",
1162 signal: Some(signal.name()),
1163 signal_code: Some(signal as u32),
1164 sender: Some(sender_id),
1165 role_claim: Some(role_claim),
1166 request_id: Some(request_id),
1167 reason: None,
1168 },
1169 Err(gbp_stack::GspError::DuplicateRequest(rid)) => Out {
1170 status: "duplicate",
1171 signal: None,
1172 signal_code: None,
1173 sender: None,
1174 role_claim: None,
1175 request_id: Some(rid),
1176 reason: None,
1177 },
1178 Err(e) => Out {
1179 status: "error",
1180 signal: None,
1181 signal_code: None,
1182 sender: None,
1183 role_claim: None,
1184 request_id: None,
1185 reason: Some(e.to_string()),
1186 },
1187 };
1188 alloc_cstring(&serde_json::to_string(&out).unwrap_or_default())
1189}
1190
1191#[unsafe(no_mangle)]
1200pub unsafe extern "C" fn gbp_frame_encode_v(
1201 version: u8,
1202 group_id_16: *const u8,
1203 epoch: u64,
1204 transition_id: u32,
1205 stream_type: u32,
1206 stream_id: u32,
1207 flags: u16,
1208 sequence_no: u32,
1209 payload_ptr: *const u8,
1210 payload_len: usize,
1211) -> GbpBuffer {
1212 clear_last_error();
1213 let mut gid = [0u8; 16];
1214 unsafe { std::ptr::copy_nonoverlapping(group_id_16, gid.as_mut_ptr(), 16) };
1215 let st_u8 = StreamType::try_from(stream_type)
1216 .map(|s| s as u8)
1217 .unwrap_or(stream_type as u8);
1218 let payload: Vec<u8> = if payload_len == 0 || payload_ptr.is_null() {
1219 Vec::new()
1220 } else {
1221 unsafe { std::slice::from_raw_parts(payload_ptr, payload_len) }.to_vec()
1222 };
1223 let frame = gbp_stack::gbp::GbpFrame {
1224 version,
1225 group_id: serde_bytes::ByteBuf::from(gid.to_vec()),
1226 epoch,
1227 transition_id,
1228 stream_type: st_u8,
1229 stream_id,
1230 flags,
1231 sequence_no,
1232 payload_format: 0u8,
1233 payload_size: payload.len() as u32,
1234 encrypted_payload: serde_bytes::ByteBuf::from(payload),
1235 };
1236 GbpBuffer::from_vec(frame.to_cbor())
1237}
1238
1239#[unsafe(no_mangle)]
1241pub extern "C" fn gbp_error_lookup(code: u16) -> GbpBuffer {
1242 use gbp_stack::core::errors::ErrorSpec;
1243 match ErrorSpec::lookup(code) {
1244 Some(spec) => GbpBuffer::from_vec(ErrorObject::from_spec(spec, spec.name).to_cbor()),
1245 None => {
1246 set_last_error(format!("unknown error code 0x{code:04X}"));
1247 GbpBuffer::empty()
1248 }
1249 }
1250}
1251
1252#[allow(dead_code)]
1253fn _link(_f: &GbpFrame, _l: StreamLabel) {}
1254
1255#[derive(Serialize)]
1260#[serde(tag = "kind", rename_all = "snake_case")]
1261enum EventDto<'a> {
1262 StateChanged {
1263 from: String,
1264 to: String,
1265 },
1266 PayloadReceived {
1267 stream_type: &'a str,
1268 stream_type_code: u32,
1269 stream_id: u32,
1270 sequence_no: u32,
1271 flags: u16,
1272 codec: u8,
1273 plaintext_b64: String,
1274 },
1275 Control {
1276 from: u32,
1277 opcode: &'a str,
1278 opcode_code: u16,
1279 transition_id: u32,
1280 request_id: u32,
1281 args_b64: String,
1282 },
1283 Error {
1284 code: u16,
1285 code_hex: String,
1286 class: u8,
1287 retryable: bool,
1288 fatal: bool,
1289 reason: String,
1290 },
1291 EpochAdvanced {
1292 epoch: u64,
1293 transition_id: u32,
1294 },
1295 CoordinatorElectionNeeded {},
1296 BecameCoordinator {},
1297 CoordinatorClaim {
1298 claimant: u32,
1299 },
1300}
1301
1302fn b64(b: &[u8]) -> String {
1303 const A: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1304 let mut out = String::with_capacity(b.len().div_ceil(3) * 4);
1305 let mut i = 0;
1306 while i + 3 <= b.len() {
1307 let n = ((b[i] as u32) << 16) | ((b[i + 1] as u32) << 8) | (b[i + 2] as u32);
1308 out.push(A[(n >> 18) as usize & 0x3F] as char);
1309 out.push(A[(n >> 12) as usize & 0x3F] as char);
1310 out.push(A[(n >> 6) as usize & 0x3F] as char);
1311 out.push(A[n as usize & 0x3F] as char);
1312 i += 3;
1313 }
1314 let rem = b.len() - i;
1315 if rem == 1 {
1316 let n = (b[i] as u32) << 16;
1317 out.push(A[(n >> 18) as usize & 0x3F] as char);
1318 out.push(A[(n >> 12) as usize & 0x3F] as char);
1319 out.push('=');
1320 out.push('=');
1321 } else if rem == 2 {
1322 let n = ((b[i] as u32) << 16) | ((b[i + 1] as u32) << 8);
1323 out.push(A[(n >> 18) as usize & 0x3F] as char);
1324 out.push(A[(n >> 12) as usize & 0x3F] as char);
1325 out.push(A[(n >> 6) as usize & 0x3F] as char);
1326 out.push('=');
1327 }
1328 out
1329}
1330
1331fn dto<'a>(e: &'a Event) -> EventDto<'a> {
1332 match e {
1333 Event::StateChanged { from, to } => EventDto::StateChanged {
1334 from: from.to_string(),
1335 to: to.to_string(),
1336 },
1337 Event::PayloadReceived(DeliveredPayload {
1338 stream_type,
1339 stream_id,
1340 sequence_no,
1341 flags,
1342 plaintext,
1343 codec,
1344 }) => EventDto::PayloadReceived {
1345 stream_type: match stream_type {
1346 StreamType::Control => "control",
1347 StreamType::Audio => "audio",
1348 StreamType::Text => "text",
1349 StreamType::Signal => "signal",
1350 },
1351 stream_type_code: *stream_type as u32,
1352 stream_id: *stream_id,
1353 sequence_no: *sequence_no,
1354 flags: *flags,
1355 codec: codec.as_u8(),
1356 plaintext_b64: b64(plaintext),
1357 },
1358 Event::Control {
1359 from,
1360 opcode,
1361 transition_id,
1362 request_id,
1363 args,
1364 } => EventDto::Control {
1365 from: *from,
1366 opcode: opcode.name(),
1367 opcode_code: *opcode as u16,
1368 transition_id: *transition_id,
1369 request_id: *request_id,
1370 args_b64: b64(args),
1371 },
1372 Event::Error {
1373 code,
1374 class,
1375 retryable,
1376 fatal,
1377 reason,
1378 } => EventDto::Error {
1379 code: *code,
1380 code_hex: format!("0x{code:04X}"),
1381 class: *class as u8,
1382 retryable: *retryable,
1383 fatal: *fatal,
1384 reason: reason.clone(),
1385 },
1386 Event::EpochAdvanced {
1387 epoch,
1388 transition_id,
1389 } => EventDto::EpochAdvanced {
1390 epoch: *epoch,
1391 transition_id: *transition_id,
1392 },
1393 Event::CoordinatorElectionNeeded => EventDto::CoordinatorElectionNeeded {},
1394 Event::BecameCoordinator => EventDto::BecameCoordinator {},
1395 Event::CoordinatorClaim { claimant } => EventDto::CoordinatorClaim {
1396 claimant: *claimant,
1397 },
1398 }
1399}
1400
1401fn events_to_json(events: &[Event]) -> String {
1402 let dtos: Vec<EventDto> = events.iter().map(dto).collect();
1403 serde_json::to_string(&dtos).unwrap_or_else(|_| "[]".to_string())
1404}
1405
1406#[allow(dead_code)]
1407const _STATES: [NodeState; 7] = [
1408 NodeState::Idle,
1409 NodeState::Connecting,
1410 NodeState::EstablishingGroup,
1411 NodeState::Active,
1412 NodeState::Resyncing,
1413 NodeState::Failed,
1414 NodeState::Closed,
1415];
1416
1417#[unsafe(no_mangle)]
1438pub unsafe extern "C" fn gbp_sframe_session_create(
1439 mls_handle: i32,
1440 suite: u8,
1441 label_ptr: *const u8,
1442 label_len: usize,
1443) -> i32 {
1444 clear_last_error();
1445 let suite = match CipherSuite::from_u8(suite) {
1446 Some(s) => s,
1447 None => {
1448 set_last_error(format!("unknown ciphersuite {suite}"));
1449 return 0;
1450 }
1451 };
1452 let label = unsafe {
1453 match std::str::from_utf8(std::slice::from_raw_parts(label_ptr, label_len)) {
1454 Ok(s) => s,
1455 Err(e) => {
1456 set_last_error(e);
1457 return 0;
1458 }
1459 }
1460 };
1461 let Some(mls_arc) = mls().get(mls_handle) else {
1462 set_last_error("invalid MLS handle");
1463 return 0;
1464 };
1465 let mls = mls_arc.lock().unwrap();
1466 match SFrameSession::from_mls(&mls, label, suite) {
1467 Ok(session) => sframe_sessions().insert(session.decryptor()),
1468 Err(e) => {
1469 set_last_error(e);
1470 0
1471 }
1472 }
1473}
1474
1475#[unsafe(no_mangle)]
1477pub extern "C" fn gbp_sframe_session_free(handle: i32) {
1478 sframe_sessions().remove(handle);
1479}
1480
1481#[unsafe(no_mangle)]
1492pub unsafe extern "C" fn gbp_sframe_encryptor_create(
1493 mls_handle: i32,
1494 session_handle: i32,
1495 leaf_index: u32,
1496 suite: u8,
1497 label_ptr: *const u8,
1498 label_len: usize,
1499) -> i32 {
1500 clear_last_error();
1501 let suite = match CipherSuite::from_u8(suite) {
1502 Some(s) => s,
1503 None => {
1504 set_last_error(format!("unknown ciphersuite {suite}"));
1505 return 0;
1506 }
1507 };
1508 let label = unsafe {
1509 match std::str::from_utf8(std::slice::from_raw_parts(label_ptr, label_len)) {
1510 Ok(s) => s,
1511 Err(e) => {
1512 set_last_error(e);
1513 return 0;
1514 }
1515 }
1516 };
1517 if sframe_sessions().get(session_handle).is_none() {
1519 set_last_error("invalid session handle");
1520 return 0;
1521 }
1522 let Some(mls_arc) = mls().get(mls_handle) else {
1523 set_last_error("invalid MLS handle");
1524 return 0;
1525 };
1526 let mls = mls_arc.lock().unwrap();
1527 match SFrameSession::from_mls(&mls, label, suite) {
1528 Ok(session) => sframe_encryptors().insert(session.encryptor(leaf_index)),
1529 Err(e) => {
1530 set_last_error(e);
1531 0
1532 }
1533 }
1534}
1535
1536#[unsafe(no_mangle)]
1538pub extern "C" fn gbp_sframe_encryptor_free(handle: i32) {
1539 sframe_encryptors().remove(handle);
1540}
1541
1542#[unsafe(no_mangle)]
1555pub unsafe extern "C" fn gbp_sframe_encrypt(
1556 enc_handle: i32,
1557 plaintext_ptr: *const u8,
1558 plaintext_len: usize,
1559 aad_ptr: *const u8,
1560 aad_len: usize,
1561) -> GbpBuffer {
1562 clear_last_error();
1563 let Some(enc_arc) = sframe_encryptors().get(enc_handle) else {
1564 set_last_error("invalid encryptor handle");
1565 return GbpBuffer::empty();
1566 };
1567 let plaintext = unsafe { std::slice::from_raw_parts(plaintext_ptr, plaintext_len) };
1568 let aad = if aad_ptr.is_null() || aad_len == 0 {
1569 &[][..]
1570 } else {
1571 unsafe { std::slice::from_raw_parts(aad_ptr, aad_len) }
1572 };
1573 let mut enc = enc_arc.lock().unwrap();
1574 match enc.encrypt(plaintext, aad) {
1575 Ok(payload) => GbpBuffer::from_vec(payload),
1576 Err(e) => {
1577 set_last_error(e);
1578 GbpBuffer::empty()
1579 }
1580 }
1581}
1582
1583#[unsafe(no_mangle)]
1595pub unsafe extern "C" fn gbp_sframe_decrypt(
1596 session_handle: i32,
1597 payload_ptr: *const u8,
1598 payload_len: usize,
1599 aad_ptr: *const u8,
1600 aad_len: usize,
1601 sender_leaf_out: *mut u32,
1602) -> GbpBuffer {
1603 clear_last_error();
1604 let Some(session_arc) = sframe_sessions().get(session_handle) else {
1605 set_last_error("invalid session handle");
1606 return GbpBuffer::empty();
1607 };
1608 let payload = unsafe { std::slice::from_raw_parts(payload_ptr, payload_len) };
1609 let aad = if aad_ptr.is_null() || aad_len == 0 {
1610 &[][..]
1611 } else {
1612 unsafe { std::slice::from_raw_parts(aad_ptr, aad_len) }
1613 };
1614 let mut dec = session_arc.lock().unwrap();
1615 match dec.decrypt(payload, aad) {
1616 Ok((plaintext, leaf)) => {
1617 if !sender_leaf_out.is_null() {
1618 unsafe {
1619 *sender_leaf_out = leaf;
1620 }
1621 }
1622 GbpBuffer::from_vec(plaintext)
1623 }
1624 Err(e) => {
1625 set_last_error(e);
1626 GbpBuffer::empty()
1627 }
1628 }
1629}
1630
1631#[cfg(test)]
1632mod tests {
1633 use super::b64;
1634
1635 #[test]
1636 fn b64_empty() {
1637 assert_eq!(b64(b""), "");
1638 }
1639
1640 #[test]
1641 fn b64_single_byte() {
1642 let s = b64(b"f");
1649 assert_eq!(s, "Zg==");
1651 }
1652
1653 #[test]
1654 fn b64_two_bytes() {
1655 let s = b64(b"fo");
1656 assert_eq!(s, "Zm8=");
1660 }
1661
1662 #[test]
1663 fn b64_three_bytes() {
1664 let s = b64(b"foo");
1665 assert_eq!(s, "Zm9v");
1667 }
1668
1669 #[test]
1670 fn b64_known_vectors() {
1671 assert_eq!(b64(b""), "");
1673 assert_eq!(b64(b"f"), "Zg==");
1674 assert_eq!(b64(b"fo"), "Zm8=");
1675 assert_eq!(b64(b"foo"), "Zm9v");
1676 assert_eq!(b64(b"foob"), "Zm9vYg==");
1677 assert_eq!(b64(b"fooba"), "Zm9vYmE=");
1678 assert_eq!(b64(b"foobar"), "Zm9vYmFy");
1679 }
1680
1681 #[test]
1682 fn b64_padding_roundtrip() {
1683 for b in 0u8..=255 {
1685 let input = [b];
1686 let enc = b64(&input);
1687 assert_eq!(enc.len(), 4, "len mismatch for 0x{b:02X}: {enc}");
1689 assert!(enc.ends_with("=="), "missing padding for 0x{b:02X}: {enc}");
1691 }
1692 }
1693}