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 #[allow(clippy::explicit_auto_deref)]
810 match c.send(&mut n, &mut *m, target, message_id, text, codec) {
811 Ok(of) => outbound_to_buffer(of),
812 Err(e) => {
813 set_last_error(e.to_string());
814 GbpBuffer::empty()
815 }
816 }
817}
818
819#[unsafe(no_mangle)]
831pub unsafe extern "C" fn gtp_client_accept(
832 ch: i32,
833 current_epoch: u64,
834 pt_ptr: *const u8,
835 pt_len: usize,
836 codec: u8,
837) -> *mut c_char {
838 clear_last_error();
839 let pt = unsafe { std::slice::from_raw_parts(pt_ptr, pt_len) };
840 let codec = PayloadCodec::from_u8(codec).unwrap_or(PayloadCodec::Cbor);
841 let Some(c_arc) = gtps().get(ch) else {
842 return alloc_cstring(r#"{"status":"error","reason":"bad client"}"#);
843 };
844 let mut c = c_arc.lock().unwrap();
845 #[derive(Serialize)]
846 struct Out<'a> {
847 status: &'a str,
848 sender: Option<u32>,
849 message_id: Option<u64>,
850 text: Option<String>,
851 reason: Option<String>,
852 }
853 let out = match c.accept(pt, current_epoch, codec) {
854 Ok(GtpAccept::New(m)) => Out {
855 status: "new",
856 sender: Some(m.sender_id),
857 message_id: Some(m.message_id),
858 text: Some(m.text().unwrap_or("<binary>").to_string()),
859 reason: None,
860 },
861 Ok(GtpAccept::Duplicate(m)) => Out {
862 status: "duplicate",
863 sender: Some(m.sender_id),
864 message_id: Some(m.message_id),
865 text: Some(m.text().unwrap_or("<binary>").to_string()),
866 reason: None,
867 },
868 Err(e) => Out {
869 status: "error",
870 sender: None,
871 message_id: None,
872 text: None,
873 reason: Some(e.to_string()),
874 },
875 };
876 alloc_cstring(&serde_json::to_string(&out).unwrap_or_default())
877}
878
879#[unsafe(no_mangle)]
885pub extern "C" fn gap_client_create() -> i32 {
886 gaps().insert(GapClient::new())
887}
888
889#[unsafe(no_mangle)]
891pub extern "C" fn gap_client_destroy(h: i32) {
892 gaps().remove(h);
893}
894
895#[unsafe(no_mangle)]
897pub extern "C" fn gap_client_reset(h: i32) {
898 if let Some(c) = gaps().get(h) {
899 c.lock().unwrap().reset();
900 }
901}
902
903#[unsafe(no_mangle)]
911pub unsafe extern "C" fn gap_client_send(
912 ch: i32,
913 nh: i32,
914 mh: i32,
915 target: u32,
916 media_source_id: u32,
917 rtp_timestamp: u64,
918 opus_ptr: *const u8,
919 opus_len: usize,
920 codec: u8,
921) -> GbpBuffer {
922 clear_last_error();
923 let opus = unsafe { std::slice::from_raw_parts(opus_ptr, opus_len) }.to_vec();
924 let codec = PayloadCodec::from_u8(codec).unwrap_or(PayloadCodec::Cbor);
925 let (c_arc, n_arc, m_arc) = (gaps().get(ch), nodes().get(nh), mls().get(mh));
926 let (Some(c_arc), Some(n_arc), Some(m_arc)) = (c_arc, n_arc, m_arc) else {
927 set_last_error("bad handle");
928 return GbpBuffer::empty();
929 };
930 let mut c = c_arc.lock().unwrap();
931 let mut n = n_arc.lock().unwrap();
932 let mut m = m_arc.lock().unwrap();
933 #[allow(clippy::explicit_auto_deref)]
937 match c.send(
938 &mut n,
939 &mut *m,
940 target,
941 media_source_id,
942 rtp_timestamp,
943 opus,
944 codec,
945 ) {
946 Ok(of) => outbound_to_buffer(of),
947 Err(e) => {
948 set_last_error(e.to_string());
949 GbpBuffer::empty()
950 }
951 }
952}
953
954#[unsafe(no_mangle)]
962pub unsafe extern "C" fn gap_client_accept(
963 ch: i32,
964 current_epoch: u64,
965 pt_ptr: *const u8,
966 pt_len: usize,
967 codec: u8,
968) -> *mut c_char {
969 clear_last_error();
970 let pt = unsafe { std::slice::from_raw_parts(pt_ptr, pt_len) };
971 let codec = PayloadCodec::from_u8(codec).unwrap_or(PayloadCodec::Cbor);
972 let Some(c_arc) = gaps().get(ch) else {
973 return alloc_cstring(r#"{"status":"error","reason":"bad client"}"#);
974 };
975 let mut c = c_arc.lock().unwrap();
976 #[derive(Serialize)]
977 struct Out<'a> {
978 status: &'a str,
979 source: Option<u32>,
980 seq: Option<u32>,
981 bytes: Option<usize>,
982 reason: Option<String>,
983 }
984 let out = match c.accept(pt, current_epoch, codec) {
985 Ok(GapAccept::New(p)) => Out {
986 status: "new",
987 source: Some(p.media_source_id),
988 seq: Some(p.rtp_sequence),
989 bytes: Some(p.opus_frame.len()),
990 reason: None,
991 },
992 Ok(GapAccept::Late(p)) => Out {
993 status: "late",
994 source: Some(p.media_source_id),
995 seq: Some(p.rtp_sequence),
996 bytes: Some(p.opus_frame.len()),
997 reason: None,
998 },
999 Err(e) => Out {
1000 status: "error",
1001 source: None,
1002 seq: None,
1003 bytes: None,
1004 reason: Some(e.to_string()),
1005 },
1006 };
1007 alloc_cstring(&serde_json::to_string(&out).unwrap_or_default())
1008}
1009
1010#[unsafe(no_mangle)]
1016pub extern "C" fn gsp_client_create() -> i32 {
1017 gsps().insert(GspClient::new())
1018}
1019
1020#[unsafe(no_mangle)]
1022pub extern "C" fn gsp_client_destroy(h: i32) {
1023 gsps().remove(h);
1024}
1025
1026#[unsafe(no_mangle)]
1028pub extern "C" fn gsp_client_reset(h: i32) {
1029 if let Some(c) = gsps().get(h) {
1030 c.lock().unwrap().reset();
1031 }
1032}
1033
1034#[unsafe(no_mangle)]
1039pub extern "C" fn gsp_client_send(
1040 ch: i32,
1041 nh: i32,
1042 mh: i32,
1043 target: u32,
1044 signal_type: u32,
1045 role_claim: u32,
1046 request_id: u32,
1047 codec: u8,
1048) -> GbpBuffer {
1049 clear_last_error();
1050 let sig = match SignalType::try_from(signal_type) {
1051 Ok(s) => s,
1052 Err(_) => {
1053 set_last_error(format!("bad signal {signal_type}"));
1054 return GbpBuffer::empty();
1055 }
1056 };
1057 let codec = PayloadCodec::from_u8(codec).unwrap_or(PayloadCodec::Cbor);
1058 let (c_arc, n_arc, m_arc) = (gsps().get(ch), nodes().get(nh), mls().get(mh));
1059 let (Some(c_arc), Some(n_arc), Some(m_arc)) = (c_arc, n_arc, m_arc) else {
1060 set_last_error("bad handle");
1061 return GbpBuffer::empty();
1062 };
1063 let mut c = c_arc.lock().unwrap();
1064 let mut n = n_arc.lock().unwrap();
1065 let mut m = m_arc.lock().unwrap();
1066 #[allow(clippy::explicit_auto_deref)]
1070 match c.send(&mut n, &mut *m, target, sig, role_claim, request_id, codec) {
1071 Ok(of) => outbound_to_buffer(of),
1072 Err(e) => {
1073 set_last_error(e.to_string());
1074 GbpBuffer::empty()
1075 }
1076 }
1077}
1078
1079#[unsafe(no_mangle)]
1089pub unsafe extern "C" fn gsp_client_send_with_args(
1090 ch: i32,
1091 nh: i32,
1092 mh: i32,
1093 target: u32,
1094 signal_type: u32,
1095 role_claim: u32,
1096 request_id: u32,
1097 args_ptr: *const u8,
1098 args_len: usize,
1099 codec: u8,
1100) -> GbpBuffer {
1101 clear_last_error();
1102 let args: &[u8] = if args_len == 0 || args_ptr.is_null() {
1103 &[]
1104 } else {
1105 unsafe { std::slice::from_raw_parts(args_ptr, args_len) }
1106 };
1107 let sig = match SignalType::try_from(signal_type) {
1108 Ok(s) => s,
1109 Err(_) => {
1110 set_last_error(format!("bad signal {signal_type}"));
1111 return GbpBuffer::empty();
1112 }
1113 };
1114 let codec = PayloadCodec::from_u8(codec).unwrap_or(PayloadCodec::Cbor);
1115 let (c_arc, n_arc, m_arc) = (gsps().get(ch), nodes().get(nh), mls().get(mh));
1116 let (Some(c_arc), Some(n_arc), Some(m_arc)) = (c_arc, n_arc, m_arc) else {
1117 set_last_error("bad handle");
1118 return GbpBuffer::empty();
1119 };
1120 let mut c = c_arc.lock().unwrap();
1121 let mut n = n_arc.lock().unwrap();
1122 let mut m = m_arc.lock().unwrap();
1123 #[allow(clippy::explicit_auto_deref)]
1127 match c.send_with_args(
1128 &mut n, &mut *m, target, sig, role_claim, request_id, args, codec,
1129 ) {
1130 Ok(of) => outbound_to_buffer(of),
1131 Err(e) => {
1132 set_last_error(e.to_string());
1133 GbpBuffer::empty()
1134 }
1135 }
1136}
1137
1138#[unsafe(no_mangle)]
1148pub unsafe extern "C" fn gsp_client_accept(
1149 ch: i32,
1150 current_epoch: u64,
1151 pt_ptr: *const u8,
1152 pt_len: usize,
1153 codec: u8,
1154) -> *mut c_char {
1155 clear_last_error();
1156 let pt = unsafe { std::slice::from_raw_parts(pt_ptr, pt_len) };
1157 let codec = PayloadCodec::from_u8(codec).unwrap_or(PayloadCodec::Cbor);
1158 let Some(c_arc) = gsps().get(ch) else {
1159 return alloc_cstring(r#"{"status":"error","reason":"bad client"}"#);
1160 };
1161 let mut c = c_arc.lock().unwrap();
1162 #[derive(Serialize)]
1163 struct Out<'a> {
1164 status: &'a str,
1165 signal: Option<&'a str>,
1166 signal_code: Option<u32>,
1167 sender: Option<u32>,
1168 role_claim: Option<u32>,
1169 request_id: Option<u32>,
1170 reason: Option<String>,
1171 }
1172 let out = match c.accept(pt, current_epoch, codec) {
1173 Ok(GspAccept {
1174 signal,
1175 sender_id,
1176 role_claim,
1177 request_id,
1178 }) => Out {
1179 status: "new",
1180 signal: Some(signal.name()),
1181 signal_code: Some(signal as u32),
1182 sender: Some(sender_id),
1183 role_claim: Some(role_claim),
1184 request_id: Some(request_id),
1185 reason: None,
1186 },
1187 Err(gbp_stack::GspError::DuplicateRequest(rid)) => Out {
1188 status: "duplicate",
1189 signal: None,
1190 signal_code: None,
1191 sender: None,
1192 role_claim: None,
1193 request_id: Some(rid),
1194 reason: None,
1195 },
1196 Err(e) => Out {
1197 status: "error",
1198 signal: None,
1199 signal_code: None,
1200 sender: None,
1201 role_claim: None,
1202 request_id: None,
1203 reason: Some(e.to_string()),
1204 },
1205 };
1206 alloc_cstring(&serde_json::to_string(&out).unwrap_or_default())
1207}
1208
1209#[unsafe(no_mangle)]
1218pub unsafe extern "C" fn gbp_frame_encode_v(
1219 version: u8,
1220 group_id_16: *const u8,
1221 epoch: u64,
1222 transition_id: u32,
1223 stream_type: u32,
1224 stream_id: u32,
1225 flags: u16,
1226 sequence_no: u32,
1227 payload_ptr: *const u8,
1228 payload_len: usize,
1229) -> GbpBuffer {
1230 clear_last_error();
1231 let mut gid = [0u8; 16];
1232 unsafe { std::ptr::copy_nonoverlapping(group_id_16, gid.as_mut_ptr(), 16) };
1233 let st_u8 = StreamType::try_from(stream_type)
1234 .map(|s| s as u8)
1235 .unwrap_or(stream_type as u8);
1236 let payload: Vec<u8> = if payload_len == 0 || payload_ptr.is_null() {
1237 Vec::new()
1238 } else {
1239 unsafe { std::slice::from_raw_parts(payload_ptr, payload_len) }.to_vec()
1240 };
1241 let frame = gbp_stack::gbp::GbpFrame {
1242 version,
1243 group_id: serde_bytes::ByteBuf::from(gid.to_vec()),
1244 epoch,
1245 transition_id,
1246 stream_type: st_u8,
1247 stream_id,
1248 flags,
1249 sequence_no,
1250 payload_format: 0u8,
1251 payload_size: payload.len() as u32,
1252 encrypted_payload: serde_bytes::ByteBuf::from(payload),
1253 };
1254 GbpBuffer::from_vec(frame.to_cbor())
1255}
1256
1257#[unsafe(no_mangle)]
1259pub extern "C" fn gbp_error_lookup(code: u16) -> GbpBuffer {
1260 use gbp_stack::core::errors::ErrorSpec;
1261 match ErrorSpec::lookup(code) {
1262 Some(spec) => GbpBuffer::from_vec(ErrorObject::from_spec(spec, spec.name).to_cbor()),
1263 None => {
1264 set_last_error(format!("unknown error code 0x{code:04X}"));
1265 GbpBuffer::empty()
1266 }
1267 }
1268}
1269
1270#[allow(dead_code)]
1271fn _link(_f: &GbpFrame, _l: StreamLabel) {}
1272
1273#[derive(Serialize)]
1278#[serde(tag = "kind", rename_all = "snake_case")]
1279enum EventDto<'a> {
1280 StateChanged {
1281 from: String,
1282 to: String,
1283 },
1284 PayloadReceived {
1285 stream_type: &'a str,
1286 stream_type_code: u32,
1287 stream_id: u32,
1288 sequence_no: u32,
1289 flags: u16,
1290 codec: u8,
1291 plaintext_b64: String,
1292 },
1293 Control {
1294 from: u32,
1295 opcode: &'a str,
1296 opcode_code: u16,
1297 transition_id: u32,
1298 request_id: u32,
1299 args_b64: String,
1300 },
1301 Error {
1302 code: u16,
1303 code_hex: String,
1304 class: u8,
1305 retryable: bool,
1306 fatal: bool,
1307 reason: String,
1308 },
1309 EpochAdvanced {
1310 epoch: u64,
1311 transition_id: u32,
1312 },
1313 CoordinatorElectionNeeded {},
1314 BecameCoordinator {},
1315 CoordinatorClaim {
1316 claimant: u32,
1317 },
1318}
1319
1320fn b64(b: &[u8]) -> String {
1321 const A: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1322 let mut out = String::with_capacity(b.len().div_ceil(3) * 4);
1323 let mut i = 0;
1324 while i + 3 <= b.len() {
1325 let n = ((b[i] as u32) << 16) | ((b[i + 1] as u32) << 8) | (b[i + 2] as u32);
1326 out.push(A[(n >> 18) as usize & 0x3F] as char);
1327 out.push(A[(n >> 12) as usize & 0x3F] as char);
1328 out.push(A[(n >> 6) as usize & 0x3F] as char);
1329 out.push(A[n as usize & 0x3F] as char);
1330 i += 3;
1331 }
1332 let rem = b.len() - i;
1333 if rem == 1 {
1334 let n = (b[i] as u32) << 16;
1335 out.push(A[(n >> 18) as usize & 0x3F] as char);
1336 out.push(A[(n >> 12) as usize & 0x3F] as char);
1337 out.push('=');
1338 out.push('=');
1339 } else if rem == 2 {
1340 let n = ((b[i] as u32) << 16) | ((b[i + 1] as u32) << 8);
1341 out.push(A[(n >> 18) as usize & 0x3F] as char);
1342 out.push(A[(n >> 12) as usize & 0x3F] as char);
1343 out.push(A[(n >> 6) as usize & 0x3F] as char);
1344 out.push('=');
1345 }
1346 out
1347}
1348
1349fn dto<'a>(e: &'a Event) -> EventDto<'a> {
1350 match e {
1351 Event::StateChanged { from, to } => EventDto::StateChanged {
1352 from: from.to_string(),
1353 to: to.to_string(),
1354 },
1355 Event::PayloadReceived(DeliveredPayload {
1356 stream_type,
1357 stream_id,
1358 sequence_no,
1359 flags,
1360 plaintext,
1361 codec,
1362 }) => EventDto::PayloadReceived {
1363 stream_type: match stream_type {
1364 StreamType::Control => "control",
1365 StreamType::Audio => "audio",
1366 StreamType::Text => "text",
1367 StreamType::Signal => "signal",
1368 },
1369 stream_type_code: *stream_type as u32,
1370 stream_id: *stream_id,
1371 sequence_no: *sequence_no,
1372 flags: *flags,
1373 codec: codec.as_u8(),
1374 plaintext_b64: b64(plaintext),
1375 },
1376 Event::Control {
1377 from,
1378 opcode,
1379 transition_id,
1380 request_id,
1381 args,
1382 } => EventDto::Control {
1383 from: *from,
1384 opcode: opcode.name(),
1385 opcode_code: *opcode as u16,
1386 transition_id: *transition_id,
1387 request_id: *request_id,
1388 args_b64: b64(args),
1389 },
1390 Event::Error {
1391 code,
1392 class,
1393 retryable,
1394 fatal,
1395 reason,
1396 } => EventDto::Error {
1397 code: *code,
1398 code_hex: format!("0x{code:04X}"),
1399 class: *class as u8,
1400 retryable: *retryable,
1401 fatal: *fatal,
1402 reason: reason.clone(),
1403 },
1404 Event::EpochAdvanced {
1405 epoch,
1406 transition_id,
1407 } => EventDto::EpochAdvanced {
1408 epoch: *epoch,
1409 transition_id: *transition_id,
1410 },
1411 Event::CoordinatorElectionNeeded => EventDto::CoordinatorElectionNeeded {},
1412 Event::BecameCoordinator => EventDto::BecameCoordinator {},
1413 Event::CoordinatorClaim { claimant } => EventDto::CoordinatorClaim {
1414 claimant: *claimant,
1415 },
1416 }
1417}
1418
1419fn events_to_json(events: &[Event]) -> String {
1420 let dtos: Vec<EventDto> = events.iter().map(dto).collect();
1421 serde_json::to_string(&dtos).unwrap_or_else(|_| "[]".to_string())
1422}
1423
1424#[allow(dead_code)]
1425const _STATES: [NodeState; 7] = [
1426 NodeState::Idle,
1427 NodeState::Connecting,
1428 NodeState::EstablishingGroup,
1429 NodeState::Active,
1430 NodeState::Resyncing,
1431 NodeState::Failed,
1432 NodeState::Closed,
1433];
1434
1435#[unsafe(no_mangle)]
1456pub unsafe extern "C" fn gbp_sframe_session_create(
1457 mls_handle: i32,
1458 suite: u8,
1459 label_ptr: *const u8,
1460 label_len: usize,
1461) -> i32 {
1462 clear_last_error();
1463 let suite = match CipherSuite::from_u8(suite) {
1464 Some(s) => s,
1465 None => {
1466 set_last_error(format!("unknown ciphersuite {suite}"));
1467 return 0;
1468 }
1469 };
1470 let label = unsafe {
1471 match std::str::from_utf8(std::slice::from_raw_parts(label_ptr, label_len)) {
1472 Ok(s) => s,
1473 Err(e) => {
1474 set_last_error(e);
1475 return 0;
1476 }
1477 }
1478 };
1479 let Some(mls_arc) = mls().get(mls_handle) else {
1480 set_last_error("invalid MLS handle");
1481 return 0;
1482 };
1483 let mls = mls_arc.lock().unwrap();
1484 match SFrameSession::from_mls(&mls, label, suite) {
1485 Ok(session) => sframe_sessions().insert(session.decryptor()),
1486 Err(e) => {
1487 set_last_error(e);
1488 0
1489 }
1490 }
1491}
1492
1493#[unsafe(no_mangle)]
1495pub extern "C" fn gbp_sframe_session_free(handle: i32) {
1496 sframe_sessions().remove(handle);
1497}
1498
1499#[unsafe(no_mangle)]
1510pub unsafe extern "C" fn gbp_sframe_encryptor_create(
1511 mls_handle: i32,
1512 session_handle: i32,
1513 leaf_index: u32,
1514 suite: u8,
1515 label_ptr: *const u8,
1516 label_len: usize,
1517) -> i32 {
1518 clear_last_error();
1519 let suite = match CipherSuite::from_u8(suite) {
1520 Some(s) => s,
1521 None => {
1522 set_last_error(format!("unknown ciphersuite {suite}"));
1523 return 0;
1524 }
1525 };
1526 let label = unsafe {
1527 match std::str::from_utf8(std::slice::from_raw_parts(label_ptr, label_len)) {
1528 Ok(s) => s,
1529 Err(e) => {
1530 set_last_error(e);
1531 return 0;
1532 }
1533 }
1534 };
1535 if sframe_sessions().get(session_handle).is_none() {
1537 set_last_error("invalid session handle");
1538 return 0;
1539 }
1540 let Some(mls_arc) = mls().get(mls_handle) else {
1541 set_last_error("invalid MLS handle");
1542 return 0;
1543 };
1544 let mls = mls_arc.lock().unwrap();
1545 match SFrameSession::from_mls(&mls, label, suite) {
1546 Ok(session) => sframe_encryptors().insert(session.encryptor(leaf_index)),
1547 Err(e) => {
1548 set_last_error(e);
1549 0
1550 }
1551 }
1552}
1553
1554#[unsafe(no_mangle)]
1556pub extern "C" fn gbp_sframe_encryptor_free(handle: i32) {
1557 sframe_encryptors().remove(handle);
1558}
1559
1560#[unsafe(no_mangle)]
1573pub unsafe extern "C" fn gbp_sframe_encrypt(
1574 enc_handle: i32,
1575 plaintext_ptr: *const u8,
1576 plaintext_len: usize,
1577 aad_ptr: *const u8,
1578 aad_len: usize,
1579) -> GbpBuffer {
1580 clear_last_error();
1581 let Some(enc_arc) = sframe_encryptors().get(enc_handle) else {
1582 set_last_error("invalid encryptor handle");
1583 return GbpBuffer::empty();
1584 };
1585 let plaintext = unsafe { std::slice::from_raw_parts(plaintext_ptr, plaintext_len) };
1586 let aad = if aad_ptr.is_null() || aad_len == 0 {
1587 &[][..]
1588 } else {
1589 unsafe { std::slice::from_raw_parts(aad_ptr, aad_len) }
1590 };
1591 let mut enc = enc_arc.lock().unwrap();
1592 match enc.encrypt(plaintext, aad) {
1593 Ok(payload) => GbpBuffer::from_vec(payload),
1594 Err(e) => {
1595 set_last_error(e);
1596 GbpBuffer::empty()
1597 }
1598 }
1599}
1600
1601#[unsafe(no_mangle)]
1613pub unsafe extern "C" fn gbp_sframe_decrypt(
1614 session_handle: i32,
1615 payload_ptr: *const u8,
1616 payload_len: usize,
1617 aad_ptr: *const u8,
1618 aad_len: usize,
1619 sender_leaf_out: *mut u32,
1620) -> GbpBuffer {
1621 clear_last_error();
1622 let Some(session_arc) = sframe_sessions().get(session_handle) else {
1623 set_last_error("invalid session handle");
1624 return GbpBuffer::empty();
1625 };
1626 let payload = unsafe { std::slice::from_raw_parts(payload_ptr, payload_len) };
1627 let aad = if aad_ptr.is_null() || aad_len == 0 {
1628 &[][..]
1629 } else {
1630 unsafe { std::slice::from_raw_parts(aad_ptr, aad_len) }
1631 };
1632 let mut dec = session_arc.lock().unwrap();
1633 match dec.decrypt(payload, aad) {
1634 Ok((plaintext, leaf)) => {
1635 if !sender_leaf_out.is_null() {
1636 unsafe {
1637 *sender_leaf_out = leaf;
1638 }
1639 }
1640 GbpBuffer::from_vec(plaintext)
1641 }
1642 Err(e) => {
1643 set_last_error(e);
1644 GbpBuffer::empty()
1645 }
1646 }
1647}
1648
1649#[cfg(test)]
1650mod tests {
1651 use super::b64;
1652
1653 #[test]
1654 fn b64_empty() {
1655 assert_eq!(b64(b""), "");
1656 }
1657
1658 #[test]
1659 fn b64_single_byte() {
1660 let s = b64(b"f");
1667 assert_eq!(s, "Zg==");
1669 }
1670
1671 #[test]
1672 fn b64_two_bytes() {
1673 let s = b64(b"fo");
1674 assert_eq!(s, "Zm8=");
1678 }
1679
1680 #[test]
1681 fn b64_three_bytes() {
1682 let s = b64(b"foo");
1683 assert_eq!(s, "Zm9v");
1685 }
1686
1687 #[test]
1688 fn b64_known_vectors() {
1689 assert_eq!(b64(b""), "");
1691 assert_eq!(b64(b"f"), "Zg==");
1692 assert_eq!(b64(b"fo"), "Zm8=");
1693 assert_eq!(b64(b"foo"), "Zm9v");
1694 assert_eq!(b64(b"foob"), "Zm9vYg==");
1695 assert_eq!(b64(b"fooba"), "Zm9vYmE=");
1696 assert_eq!(b64(b"foobar"), "Zm9vYmFy");
1697 }
1698
1699 #[test]
1700 fn b64_padding_roundtrip() {
1701 for b in 0u8..=255 {
1703 let input = [b];
1704 let enc = b64(&input);
1705 assert_eq!(enc.len(), 4, "len mismatch for 0x{b:02X}: {enc}");
1707 assert!(enc.ends_with("=="), "missing padding for 0x{b:02X}: {enc}");
1709 }
1710 }
1711}