1#![allow(unsafe_op_in_unsafe_fn)]
26
27use gbp_stack::core::{ControlOpcode, NodeState, SignalType, StreamType};
28use gbp_stack::{
29 CipherSuite, DeliveredPayload, ErrorObject, Event, GapAccept, GapClient, GbpFrame, GroupNode,
30 GspAccept, GspClient, GtpAccept, GtpClient, MlsContext, OutboundFrame, ProcessedKind,
31 SFrameDecryptor, SFrameEncryptor, SFrameSession, StreamLabel,
32};
33use openmls::prelude::tls_codec::Serialize as _;
34use openmls::prelude::*;
35use serde::Serialize;
36use std::cell::RefCell;
37use std::collections::HashMap;
38use std::ffi::{CString, c_char};
39use std::sync::{Arc, Mutex};
40use std::sync::atomic::{AtomicI32, Ordering};
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 { ptr: std::ptr::null_mut(), len: 0, cap: 0 }
61 }
62 fn from_vec(mut v: Vec<u8>) -> Self {
63 let ptr = v.as_mut_ptr();
64 let len = v.len();
65 let cap = v.capacity();
66 std::mem::forget(v);
67 Self { ptr, len, cap }
68 }
69}
70
71#[unsafe(no_mangle)]
77pub unsafe extern "C" fn gbp_buffer_free(buf: GbpBuffer) {
78 if buf.ptr.is_null() {
79 return;
80 }
81 unsafe {
82 let _ = Vec::from_raw_parts(buf.ptr, buf.len, buf.cap);
83 }
84}
85
86#[unsafe(no_mangle)]
91pub unsafe extern "C" fn gbp_string_free(ptr: *mut c_char) {
92 if ptr.is_null() {
93 return;
94 }
95 unsafe {
96 let _ = CString::from_raw(ptr);
97 }
98}
99
100fn alloc_cstring(s: &str) -> *mut c_char {
101 CString::new(s.as_bytes())
102 .unwrap_or_else(|_| CString::new(s.replace('\0', "?")).unwrap())
103 .into_raw()
104}
105
106thread_local! {
111 static LAST_ERROR: RefCell<String> = const { RefCell::new(String::new()) };
112}
113
114fn set_last_error(e: impl ToString) {
115 LAST_ERROR.with(|s| *s.borrow_mut() = e.to_string());
116}
117
118fn clear_last_error() {
119 LAST_ERROR.with(|s| s.borrow_mut().clear());
120}
121
122#[unsafe(no_mangle)]
124pub extern "C" fn gbp_last_error() -> *mut c_char {
125 LAST_ERROR.with(|s| alloc_cstring(&s.borrow()))
126}
127
128macro_rules! registry {
133 ($vis:vis $name:ident<$t:ty>) => {
134 $vis struct $name {
135 next: AtomicI32,
136 map: Mutex<HashMap<i32, Arc<Mutex<$t>>>>,
137 }
138 impl $name {
139 fn new() -> Self {
140 Self { next: AtomicI32::new(1), map: Mutex::new(HashMap::new()) }
141 }
142 fn insert(&self, v: $t) -> i32 {
143 let id = self.next.fetch_add(1, Ordering::Relaxed);
144 self.map.lock().unwrap().insert(id, Arc::new(Mutex::new(v)));
145 id
146 }
147 fn remove(&self, id: i32) {
148 self.map.lock().unwrap().remove(&id);
149 }
150 fn get(&self, id: i32) -> Option<Arc<Mutex<$t>>> {
151 self.map.lock().unwrap().get(&id).cloned()
152 }
153 }
154 };
155}
156
157registry!(MlsRegistry<MlsContext>);
158registry!(NodeRegistry<GroupNode>);
159registry!(GtpRegistry<GtpClient>);
160registry!(GapRegistry<GapClient>);
161registry!(GspRegistry<GspClient>);
162registry!(SFrameSessionRegistry<SFrameDecryptor>);
163registry!(SFrameEncryptorRegistry<SFrameEncryptor>);
164
165struct MlsBundles {
166 map: Mutex<HashMap<i32, KeyPackageBundle>>,
167}
168impl MlsBundles {
169 fn new() -> Self {
170 Self { map: Mutex::new(HashMap::new()) }
171 }
172}
173
174fn mls() -> &'static MlsRegistry {
175 use std::sync::OnceLock;
176 static R: OnceLock<MlsRegistry> = OnceLock::new();
177 R.get_or_init(MlsRegistry::new)
178}
179fn mls_bundles() -> &'static MlsBundles {
180 use std::sync::OnceLock;
181 static R: OnceLock<MlsBundles> = OnceLock::new();
182 R.get_or_init(MlsBundles::new)
183}
184fn nodes() -> &'static NodeRegistry {
185 use std::sync::OnceLock;
186 static R: OnceLock<NodeRegistry> = OnceLock::new();
187 R.get_or_init(NodeRegistry::new)
188}
189fn gtps() -> &'static GtpRegistry {
190 use std::sync::OnceLock;
191 static R: OnceLock<GtpRegistry> = OnceLock::new();
192 R.get_or_init(GtpRegistry::new)
193}
194fn gaps() -> &'static GapRegistry {
195 use std::sync::OnceLock;
196 static R: OnceLock<GapRegistry> = OnceLock::new();
197 R.get_or_init(GapRegistry::new)
198}
199fn gsps() -> &'static GspRegistry {
200 use std::sync::OnceLock;
201 static R: OnceLock<GspRegistry> = OnceLock::new();
202 R.get_or_init(GspRegistry::new)
203}
204fn sframe_sessions() -> &'static SFrameSessionRegistry {
205 use std::sync::OnceLock;
206 static R: OnceLock<SFrameSessionRegistry> = OnceLock::new();
207 R.get_or_init(SFrameSessionRegistry::new)
208}
209fn sframe_encryptors() -> &'static SFrameEncryptorRegistry {
210 use std::sync::OnceLock;
211 static R: OnceLock<SFrameEncryptorRegistry> = OnceLock::new();
212 R.get_or_init(SFrameEncryptorRegistry::new)
213}
214
215#[unsafe(no_mangle)]
221pub extern "C" fn gbp_version() -> *mut c_char {
222 alloc_cstring(&format!(
223 "group-protocol-stack {} (gbp + gtp + gap + gsp)",
224 env!("CARGO_PKG_VERSION")
225 ))
226}
227
228#[unsafe(no_mangle)]
237pub unsafe extern "C" fn gbp_mls_create(identity_ptr: *const u8, identity_len: usize) -> i32 {
238 clear_last_error();
239 let ident = unsafe { std::slice::from_raw_parts(identity_ptr, identity_len) };
240 match MlsContext::new_member(ident) {
241 Ok((ctx, kp)) => {
242 let id = mls().insert(ctx);
243 mls_bundles().map.lock().unwrap().insert(id, kp);
244 id
245 }
246 Err(e) => {
247 set_last_error(e);
248 0
249 }
250 }
251}
252
253#[unsafe(no_mangle)]
255pub extern "C" fn gbp_mls_destroy(h: i32) {
256 mls().remove(h);
257 mls_bundles().map.lock().unwrap().remove(&h);
258}
259
260#[unsafe(no_mangle)]
262pub extern "C" fn gbp_mls_epoch(h: i32) -> u64 {
263 mls().get(h).map(|c| c.lock().unwrap().epoch()).unwrap_or(0)
264}
265
266#[unsafe(no_mangle)]
271pub unsafe extern "C" fn gbp_mls_group_id(h: i32, out16: *mut u8) -> bool {
272 clear_last_error();
273 let Some(ctx_arc) = mls().get(h) else {
274 set_last_error("invalid MLS handle");
275 return false;
276 };
277 let ctx = ctx_arc.lock().unwrap();
278 let gid = ctx.group_id_16();
279 unsafe { std::ptr::copy_nonoverlapping(gid.as_ptr(), out16, 16) };
280 true
281}
282
283#[unsafe(no_mangle)]
286pub extern "C" fn gbp_mls_export_key_package(h: i32) -> GbpBuffer {
287 clear_last_error();
288 let bundles = mls_bundles().map.lock().unwrap();
289 let Some(b) = bundles.get(&h) else {
290 set_last_error("invalid MLS handle");
291 return GbpBuffer::empty();
292 };
293 match b.key_package().tls_serialize_detached() {
294 Ok(b) => GbpBuffer::from_vec(b),
295 Err(e) => {
296 set_last_error(format!("kp serialize: {e:?}"));
297 GbpBuffer::empty()
298 }
299 }
300}
301
302#[unsafe(no_mangle)]
309pub unsafe extern "C" fn gbp_mls_invite(h: i32, kp_ptr: *const u8, kp_len: usize) -> GbpBuffer {
310 clear_last_error();
311 let bytes = unsafe { std::slice::from_raw_parts(kp_ptr, kp_len) };
312 let Some(ctx_arc) = mls().get(h) else {
313 set_last_error("invalid MLS handle");
314 return GbpBuffer::empty();
315 };
316 let mut ctx = ctx_arc.lock().unwrap();
317 let kp_in = match KeyPackageIn::tls_deserialize_exact_bytes(bytes) {
318 Ok(v) => v,
319 Err(e) => {
320 set_last_error(format!("kp parse: {e:?}"));
321 return GbpBuffer::empty();
322 }
323 };
324 let validated = match kp_in.validate(ctx.provider.crypto(), ProtocolVersion::Mls10) {
325 Ok(v) => v,
326 Err(e) => {
327 set_last_error(format!("kp validate: {e:?}"));
328 return GbpBuffer::empty();
329 }
330 };
331 match ctx.invite(&[validated]) {
332 Ok(welcome) => GbpBuffer::from_vec(welcome),
333 Err(e) => {
334 set_last_error(e);
335 GbpBuffer::empty()
336 }
337 }
338}
339
340#[unsafe(no_mangle)]
350pub unsafe extern "C" fn gbp_mls_invite_full(
351 h: i32,
352 kp_ptr: *const u8,
353 kp_len: usize,
354) -> GbpBuffer {
355 clear_last_error();
356 let bytes = unsafe { std::slice::from_raw_parts(kp_ptr, kp_len) };
357 let Some(ctx_arc) = mls().get(h) else {
358 set_last_error("invalid MLS handle");
359 return GbpBuffer::empty();
360 };
361 let mut ctx = ctx_arc.lock().unwrap();
362 let kp_in = match KeyPackageIn::tls_deserialize_exact_bytes(bytes) {
363 Ok(v) => v,
364 Err(e) => {
365 set_last_error(format!("kp parse: {e:?}"));
366 return GbpBuffer::empty();
367 }
368 };
369 let validated = match kp_in.validate(ctx.provider.crypto(), ProtocolVersion::Mls10) {
370 Ok(v) => v,
371 Err(e) => {
372 set_last_error(format!("kp validate: {e:?}"));
373 return GbpBuffer::empty();
374 }
375 };
376 match ctx.invite_full(&[validated]) {
377 Ok((commit, welcome)) => {
378 let mut out = Vec::with_capacity(4 + commit.len() + welcome.len());
379 out.extend_from_slice(&(commit.len() as u32).to_le_bytes());
380 out.extend_from_slice(&commit);
381 out.extend_from_slice(&welcome);
382 GbpBuffer::from_vec(out)
383 }
384 Err(e) => {
385 set_last_error(e);
386 GbpBuffer::empty()
387 }
388 }
389}
390
391#[unsafe(no_mangle)]
395pub extern "C" fn gbp_mls_remove(h: i32, leaf_index: u32) -> GbpBuffer {
396 clear_last_error();
397 let Some(ctx_arc) = mls().get(h) else {
398 set_last_error("invalid MLS handle");
399 return GbpBuffer::empty();
400 };
401 let mut ctx = ctx_arc.lock().unwrap();
402 match ctx.remove_members(&[leaf_index]) {
403 Ok(commit) => GbpBuffer::from_vec(commit),
404 Err(e) => {
405 set_last_error(e);
406 GbpBuffer::empty()
407 }
408 }
409}
410
411#[unsafe(no_mangle)]
422pub unsafe extern "C" fn gbp_mls_process_message(
423 h: i32,
424 msg_ptr: *const u8,
425 msg_len: usize,
426) -> u32 {
427 clear_last_error();
428 let bytes = unsafe { std::slice::from_raw_parts(msg_ptr, msg_len) };
429 let Some(ctx_arc) = mls().get(h) else {
430 set_last_error("invalid MLS handle");
431 return 0;
432 };
433 let mut ctx = ctx_arc.lock().unwrap();
434 match ctx.process_message(bytes) {
435 Ok(ProcessedKind::Commit) => 1,
436 Ok(ProcessedKind::Application) => 2,
437 Ok(ProcessedKind::Proposal) => 3,
438 Ok(ProcessedKind::External) => 4,
439 Err(e) => {
440 set_last_error(e);
441 0
442 }
443 }
444}
445
446#[unsafe(no_mangle)]
449pub extern "C" fn gbp_mls_finalize_commit(h: i32) -> bool {
450 clear_last_error();
451 let Some(ctx_arc) = mls().get(h) else {
452 set_last_error("invalid MLS handle");
453 return false;
454 };
455 let mut ctx = ctx_arc.lock().unwrap();
456 match ctx.finalize_pending_commit() {
457 Ok(()) => true,
458 Err(e) => {
459 set_last_error(e);
460 false
461 }
462 }
463}
464
465#[unsafe(no_mangle)]
468pub extern "C" fn gbp_mls_clear_pending_commit(h: i32) -> bool {
469 clear_last_error();
470 let Some(ctx_arc) = mls().get(h) else {
471 set_last_error("invalid MLS handle");
472 return false;
473 };
474 let mut ctx = ctx_arc.lock().unwrap();
475 match ctx.clear_pending_commit() {
476 Ok(()) => true,
477 Err(e) => {
478 set_last_error(e);
479 false
480 }
481 }
482}
483
484#[unsafe(no_mangle)]
489pub unsafe extern "C" fn gbp_mls_accept_welcome(
490 h: i32,
491 welcome_ptr: *const u8,
492 welcome_len: usize,
493) -> bool {
494 clear_last_error();
495 let bytes = unsafe { std::slice::from_raw_parts(welcome_ptr, welcome_len) };
496 let Some(ctx_arc) = mls().get(h) else {
497 set_last_error("invalid MLS handle");
498 return false;
499 };
500 let mut ctx = ctx_arc.lock().unwrap();
501 match ctx.accept_welcome(bytes) {
502 Ok(()) => true,
503 Err(e) => {
504 set_last_error(e);
505 false
506 }
507 }
508}
509
510#[unsafe(no_mangle)]
519pub unsafe extern "C" fn gbp_node_create(member_id: u32, group_id_16: *const u8) -> i32 {
520 clear_last_error();
521 let mut gid = [0u8; 16];
522 unsafe { std::ptr::copy_nonoverlapping(group_id_16, gid.as_mut_ptr(), 16) };
523 nodes().insert(GroupNode::new(member_id, gid))
524}
525
526#[unsafe(no_mangle)]
528pub extern "C" fn gbp_node_destroy(h: i32) {
529 nodes().remove(h);
530}
531
532#[unsafe(no_mangle)]
534pub extern "C" fn gbp_node_bootstrap_creator(h: i32, epoch: u64) -> bool {
535 let Some(n_arc) = nodes().get(h) else { return false };
536 n_arc.lock().unwrap().bootstrap_as_creator(epoch);
537 true
538}
539
540#[unsafe(no_mangle)]
545pub extern "C" fn gbp_node_bootstrap_joiner(h: i32, epoch: u64, expected_first_tid: u32) -> bool {
546 let Some(n_arc) = nodes().get(h) else { return false };
547 n_arc.lock().unwrap().bootstrap_as_joiner(epoch, expected_first_tid);
548 true
549}
550
551#[unsafe(no_mangle)]
553pub extern "C" fn gbp_node_state(h: i32) -> u32 {
554 nodes()
555 .get(h)
556 .map(|n| n.lock().unwrap().state as u32)
557 .unwrap_or(u32::MAX)
558}
559
560#[unsafe(no_mangle)]
562pub extern "C" fn gbp_node_epoch(h: i32) -> u64 {
563 nodes().get(h).map(|n| n.lock().unwrap().current_epoch).unwrap_or(0)
564}
565
566#[unsafe(no_mangle)]
568pub extern "C" fn gbp_node_last_transition_id(h: i32) -> u32 {
569 nodes()
570 .get(h)
571 .map(|n| n.lock().unwrap().last_transition_id)
572 .unwrap_or(0)
573}
574
575#[unsafe(no_mangle)]
578pub extern "C" fn gbp_node_set_epoch(h: i32, epoch: u64) -> bool {
579 let Some(n_arc) = nodes().get(h) else { return false };
580 n_arc.lock().unwrap().current_epoch = epoch;
581 true
582}
583
584#[unsafe(no_mangle)]
586pub extern "C" fn gbp_node_apply_transition(h: i32, tid: u32) -> bool {
587 let Some(n_arc) = nodes().get(h) else { return false };
588 n_arc.lock().unwrap().apply_transition(tid);
589 true
590}
591
592#[unsafe(no_mangle)]
599pub unsafe extern "C" fn gbp_node_send_control(
600 nh: i32,
601 mh: i32,
602 target: u32,
603 opcode: u16,
604 transition_id: u32,
605 request_id: u32,
606 args_ptr: *const u8,
607 args_len: usize,
608) -> GbpBuffer {
609 clear_last_error();
610 let op = match ControlOpcode::try_from(opcode) {
611 Ok(o) => o,
612 Err(_) => {
613 set_last_error(format!("bad opcode 0x{opcode:04X}"));
614 return GbpBuffer::empty();
615 }
616 };
617 let args = if args_len == 0 {
618 Vec::new()
619 } else {
620 unsafe { std::slice::from_raw_parts(args_ptr, args_len) }.to_vec()
621 };
622 let (n_arc, m_arc) = (nodes().get(nh), mls().get(mh));
623 let (Some(n_arc), Some(m_arc)) = (n_arc, m_arc) else {
624 set_last_error("bad node/mls handle");
625 return GbpBuffer::empty();
626 };
627 let mut n = n_arc.lock().unwrap();
628 let mut m = m_arc.lock().unwrap();
629 match n.send_control(&mut *m, target, op, transition_id, request_id, args) {
630 Ok(of) => outbound_to_buffer(of),
631 Err(e) => {
632 set_last_error(e.to_string());
633 GbpBuffer::empty()
634 }
635 }
636}
637
638#[unsafe(no_mangle)]
643pub unsafe extern "C" fn gbp_node_on_wire(
644 nh: i32,
645 mh: i32,
646 wire_ptr: *const u8,
647 wire_len: usize,
648) -> *mut c_char {
649 clear_last_error();
650 let wire = unsafe { std::slice::from_raw_parts(wire_ptr, wire_len) };
651 let (n_arc, m_arc) = (nodes().get(nh), mls().get(mh));
652 let (Some(n_arc), Some(m_arc)) = (n_arc, m_arc) else {
653 set_last_error("bad node/mls handle");
654 return alloc_cstring("[]");
655 };
656 let mut n = n_arc.lock().unwrap();
657 let mut m = m_arc.lock().unwrap();
658 let events = match n.on_wire(&mut *m, wire) {
659 Ok(e) => e,
660 Err(e) => {
661 set_last_error(e.to_string());
662 return alloc_cstring("[]");
663 }
664 };
665 alloc_cstring(&events_to_json(&events))
666}
667
668#[unsafe(no_mangle)]
670pub extern "C" fn gbp_node_drain_events(nh: i32) -> *mut c_char {
671 let Some(n_arc) = nodes().get(nh) else {
672 return alloc_cstring("[]");
673 };
674 alloc_cstring(&events_to_json(&n_arc.lock().unwrap().drain_events()))
675}
676
677fn outbound_to_buffer(of: OutboundFrame) -> GbpBuffer {
678 let mut out = Vec::with_capacity(4 + of.wire.len());
679 out.extend_from_slice(&of.to.to_le_bytes());
680 out.extend_from_slice(&of.wire);
681 GbpBuffer::from_vec(out)
682}
683
684#[unsafe(no_mangle)]
690pub extern "C" fn gtp_client_create() -> i32 {
691 gtps().insert(GtpClient::new())
692}
693
694#[unsafe(no_mangle)]
696pub extern "C" fn gtp_client_destroy(h: i32) {
697 gtps().remove(h);
698}
699
700#[unsafe(no_mangle)]
702pub extern "C" fn gtp_client_reset(h: i32) {
703 if let Some(c) = gtps().get(h) {
704 c.lock().unwrap().reset();
705 }
706}
707
708#[unsafe(no_mangle)]
713pub unsafe extern "C" fn gtp_client_send(
714 ch: i32,
715 nh: i32,
716 mh: i32,
717 target: u32,
718 message_id: u64,
719 text_ptr: *const u8,
720 text_len: usize,
721) -> GbpBuffer {
722 clear_last_error();
723 let text = unsafe { std::slice::from_raw_parts(text_ptr, text_len) };
724 let text = match std::str::from_utf8(text) {
725 Ok(s) => s,
726 Err(e) => {
727 set_last_error(format!("utf8: {e}"));
728 return GbpBuffer::empty();
729 }
730 };
731 let (c_arc, n_arc, m_arc) = (gtps().get(ch), nodes().get(nh), mls().get(mh));
732 let (Some(c_arc), Some(n_arc), Some(m_arc)) = (c_arc, n_arc, m_arc) else {
733 set_last_error("bad handle");
734 return GbpBuffer::empty();
735 };
736 let mut c = c_arc.lock().unwrap();
737 let mut n = n_arc.lock().unwrap();
738 let mut m = m_arc.lock().unwrap();
739 match c.send(&mut *n, &mut *m, target, message_id, text) {
740 Ok(of) => outbound_to_buffer(of),
741 Err(e) => {
742 set_last_error(e.to_string());
743 GbpBuffer::empty()
744 }
745 }
746}
747
748#[unsafe(no_mangle)]
758pub unsafe extern "C" fn gtp_client_accept(
759 ch: i32,
760 current_epoch: u64,
761 pt_ptr: *const u8,
762 pt_len: usize,
763) -> *mut c_char {
764 clear_last_error();
765 let pt = unsafe { std::slice::from_raw_parts(pt_ptr, pt_len) };
766 let Some(c_arc) = gtps().get(ch) else {
767 return alloc_cstring(r#"{"status":"error","reason":"bad client"}"#);
768 };
769 let mut c = c_arc.lock().unwrap();
770 #[derive(Serialize)]
771 struct Out<'a> {
772 status: &'a str,
773 sender: Option<u32>,
774 message_id: Option<u64>,
775 text: Option<String>,
776 reason: Option<String>,
777 }
778 let out = match c.accept(pt, current_epoch) {
779 Ok(GtpAccept::New(m)) => Out {
780 status: "new",
781 sender: Some(m.sender_id),
782 message_id: Some(m.message_id),
783 text: Some(m.text().unwrap_or("<binary>").to_string()),
784 reason: None,
785 },
786 Ok(GtpAccept::Duplicate(m)) => Out {
787 status: "duplicate",
788 sender: Some(m.sender_id),
789 message_id: Some(m.message_id),
790 text: Some(m.text().unwrap_or("<binary>").to_string()),
791 reason: None,
792 },
793 Err(e) => Out {
794 status: "error",
795 sender: None,
796 message_id: None,
797 text: None,
798 reason: Some(e.to_string()),
799 },
800 };
801 alloc_cstring(&serde_json::to_string(&out).unwrap_or_default())
802}
803
804#[unsafe(no_mangle)]
810pub extern "C" fn gap_client_create() -> i32 {
811 gaps().insert(GapClient::new())
812}
813
814#[unsafe(no_mangle)]
816pub extern "C" fn gap_client_destroy(h: i32) {
817 gaps().remove(h);
818}
819
820#[unsafe(no_mangle)]
822pub extern "C" fn gap_client_reset(h: i32) {
823 if let Some(c) = gaps().get(h) {
824 c.lock().unwrap().reset();
825 }
826}
827
828#[unsafe(no_mangle)]
833pub unsafe extern "C" fn gap_client_send(
834 ch: i32,
835 nh: i32,
836 mh: i32,
837 target: u32,
838 media_source_id: u32,
839 rtp_timestamp: u64,
840 opus_ptr: *const u8,
841 opus_len: usize,
842) -> GbpBuffer {
843 clear_last_error();
844 let opus = unsafe { std::slice::from_raw_parts(opus_ptr, opus_len) }.to_vec();
845 let (c_arc, n_arc, m_arc) = (gaps().get(ch), nodes().get(nh), mls().get(mh));
846 let (Some(c_arc), Some(n_arc), Some(m_arc)) = (c_arc, n_arc, m_arc) else {
847 set_last_error("bad handle");
848 return GbpBuffer::empty();
849 };
850 let mut c = c_arc.lock().unwrap();
851 let mut n = n_arc.lock().unwrap();
852 let mut m = m_arc.lock().unwrap();
853 match c.send(&mut *n, &mut *m, target, media_source_id, rtp_timestamp, opus) {
854 Ok(of) => outbound_to_buffer(of),
855 Err(e) => {
856 set_last_error(e.to_string());
857 GbpBuffer::empty()
858 }
859 }
860}
861
862#[unsafe(no_mangle)]
867pub unsafe extern "C" fn gap_client_accept(
868 ch: i32,
869 current_epoch: u64,
870 pt_ptr: *const u8,
871 pt_len: usize,
872) -> *mut c_char {
873 clear_last_error();
874 let pt = unsafe { std::slice::from_raw_parts(pt_ptr, pt_len) };
875 let Some(c_arc) = gaps().get(ch) else {
876 return alloc_cstring(r#"{"status":"error","reason":"bad client"}"#);
877 };
878 let mut c = c_arc.lock().unwrap();
879 #[derive(Serialize)]
880 struct Out<'a> {
881 status: &'a str,
882 source: Option<u32>,
883 seq: Option<u32>,
884 bytes: Option<usize>,
885 reason: Option<String>,
886 }
887 let out = match c.accept(pt, current_epoch) {
888 Ok(GapAccept::New(p)) => Out {
889 status: "new",
890 source: Some(p.media_source_id),
891 seq: Some(p.rtp_sequence),
892 bytes: Some(p.opus_frame.len()),
893 reason: None,
894 },
895 Ok(GapAccept::Late(p)) => Out {
896 status: "late",
897 source: Some(p.media_source_id),
898 seq: Some(p.rtp_sequence),
899 bytes: Some(p.opus_frame.len()),
900 reason: None,
901 },
902 Err(e) => Out {
903 status: "error",
904 source: None,
905 seq: None,
906 bytes: None,
907 reason: Some(e.to_string()),
908 },
909 };
910 alloc_cstring(&serde_json::to_string(&out).unwrap_or_default())
911}
912
913#[unsafe(no_mangle)]
919pub extern "C" fn gsp_client_create() -> i32 {
920 gsps().insert(GspClient::new())
921}
922
923#[unsafe(no_mangle)]
925pub extern "C" fn gsp_client_destroy(h: i32) {
926 gsps().remove(h);
927}
928
929#[unsafe(no_mangle)]
931pub extern "C" fn gsp_client_reset(h: i32) {
932 if let Some(c) = gsps().get(h) {
933 c.lock().unwrap().reset();
934 }
935}
936
937#[unsafe(no_mangle)]
939pub extern "C" fn gsp_client_send(
940 ch: i32,
941 nh: i32,
942 mh: i32,
943 target: u32,
944 signal_type: u32,
945 role_claim: u32,
946 request_id: u32,
947) -> GbpBuffer {
948 clear_last_error();
949 let sig = match SignalType::try_from(signal_type) {
950 Ok(s) => s,
951 Err(_) => {
952 set_last_error(format!("bad signal {signal_type}"));
953 return GbpBuffer::empty();
954 }
955 };
956 let (c_arc, n_arc, m_arc) = (gsps().get(ch), nodes().get(nh), mls().get(mh));
957 let (Some(c_arc), Some(n_arc), Some(m_arc)) = (c_arc, n_arc, m_arc) else {
958 set_last_error("bad handle");
959 return GbpBuffer::empty();
960 };
961 let mut c = c_arc.lock().unwrap();
962 let mut n = n_arc.lock().unwrap();
963 let mut m = m_arc.lock().unwrap();
964 match c.send(&mut *n, &mut *m, target, sig, role_claim, request_id) {
965 Ok(of) => outbound_to_buffer(of),
966 Err(e) => {
967 set_last_error(e.to_string());
968 GbpBuffer::empty()
969 }
970 }
971}
972
973#[unsafe(no_mangle)]
981pub unsafe extern "C" fn gsp_client_accept(
982 ch: i32,
983 current_epoch: u64,
984 pt_ptr: *const u8,
985 pt_len: usize,
986) -> *mut c_char {
987 clear_last_error();
988 let pt = unsafe { std::slice::from_raw_parts(pt_ptr, pt_len) };
989 let Some(c_arc) = gsps().get(ch) else {
990 return alloc_cstring(r#"{"status":"error","reason":"bad client"}"#);
991 };
992 let mut c = c_arc.lock().unwrap();
993 #[derive(Serialize)]
994 struct Out<'a> {
995 status: &'a str,
996 signal: Option<&'a str>,
997 signal_code: Option<u32>,
998 sender: Option<u32>,
999 role_claim: Option<u32>,
1000 request_id: Option<u32>,
1001 reason: Option<String>,
1002 }
1003 let out = match c.accept(pt, current_epoch) {
1004 Ok(GspAccept { signal, sender_id, role_claim, request_id }) => Out {
1005 status: "new",
1006 signal: Some(signal.name()),
1007 signal_code: Some(signal as u32),
1008 sender: Some(sender_id),
1009 role_claim: Some(role_claim),
1010 request_id: Some(request_id),
1011 reason: None,
1012 },
1013 Err(e) => Out {
1014 status: "error",
1015 signal: None,
1016 signal_code: None,
1017 sender: None,
1018 role_claim: None,
1019 request_id: None,
1020 reason: Some(e.to_string()),
1021 },
1022 };
1023 alloc_cstring(&serde_json::to_string(&out).unwrap_or_default())
1024}
1025
1026#[unsafe(no_mangle)]
1035pub unsafe extern "C" fn gbp_frame_encode_v(
1036 version: u8,
1037 group_id_16: *const u8,
1038 epoch: u64,
1039 transition_id: u32,
1040 stream_type: u32,
1041 stream_id: u32,
1042 flags: u16,
1043 sequence_no: u32,
1044 payload_ptr: *const u8,
1045 payload_len: usize,
1046) -> GbpBuffer {
1047 clear_last_error();
1048 let mut gid = [0u8; 16];
1049 unsafe { std::ptr::copy_nonoverlapping(group_id_16, gid.as_mut_ptr(), 16) };
1050 let st_u8 = StreamType::try_from(stream_type).map(|s| s as u8).unwrap_or(stream_type as u8);
1051 let payload = unsafe { std::slice::from_raw_parts(payload_ptr, payload_len) }.to_vec();
1052 let frame = gbp_stack::gbp::GbpFrame {
1053 version,
1054 group_id: serde_bytes::ByteBuf::from(gid.to_vec()),
1055 epoch,
1056 transition_id,
1057 stream_type: st_u8,
1058 stream_id,
1059 flags,
1060 sequence_no,
1061 payload_size: payload.len() as u32,
1062 encrypted_payload: serde_bytes::ByteBuf::from(payload),
1063 };
1064 GbpBuffer::from_vec(frame.to_cbor())
1065}
1066
1067#[unsafe(no_mangle)]
1069pub extern "C" fn gbp_error_lookup(code: u16) -> GbpBuffer {
1070 use gbp_stack::core::errors::ErrorSpec;
1071 match ErrorSpec::lookup(code) {
1072 Some(spec) => GbpBuffer::from_vec(ErrorObject::from_spec(spec, spec.name).to_cbor()),
1073 None => {
1074 set_last_error(format!("unknown error code 0x{code:04X}"));
1075 GbpBuffer::empty()
1076 }
1077 }
1078}
1079
1080#[allow(dead_code)]
1081fn _link(_f: &GbpFrame, _l: StreamLabel) {}
1082
1083#[derive(Serialize)]
1088#[serde(tag = "kind", rename_all = "snake_case")]
1089enum EventDto<'a> {
1090 StateChanged {
1091 from: String,
1092 to: String,
1093 },
1094 PayloadReceived {
1095 stream_type: &'a str,
1096 stream_type_code: u32,
1097 stream_id: u32,
1098 sequence_no: u32,
1099 flags: u16,
1100 plaintext_b64: String,
1101 },
1102 Control {
1103 from: u32,
1104 opcode: &'a str,
1105 opcode_code: u16,
1106 transition_id: u32,
1107 request_id: u32,
1108 args_b64: String,
1109 },
1110 Error {
1111 code: u16,
1112 code_hex: String,
1113 class: u8,
1114 retryable: bool,
1115 fatal: bool,
1116 reason: String,
1117 },
1118 EpochAdvanced {
1119 epoch: u64,
1120 transition_id: u32,
1121 },
1122}
1123
1124fn b64(b: &[u8]) -> String {
1125 const A: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1126 let mut out = String::with_capacity(b.len().div_ceil(3) * 4);
1127 let mut i = 0;
1128 while i + 3 <= b.len() {
1129 let n = ((b[i] as u32) << 16) | ((b[i + 1] as u32) << 8) | (b[i + 2] as u32);
1130 out.push(A[(n >> 18) as usize & 0x3F] as char);
1131 out.push(A[(n >> 12) as usize & 0x3F] as char);
1132 out.push(A[(n >> 6) as usize & 0x3F] as char);
1133 out.push(A[n as usize & 0x3F] as char);
1134 i += 3;
1135 }
1136 let rem = b.len() - i;
1137 if rem == 1 {
1138 let n = (b[i] as u32) << 16;
1139 out.push(A[(n >> 18) as usize & 0x3F] as char);
1140 out.push(A[(n >> 12) as usize & 0x3F] as char);
1141 out.push('=');
1142 out.push('=');
1143 } else if rem == 2 {
1144 let n = ((b[i] as u32) << 16) | ((b[i + 1] as u32) << 8);
1145 out.push(A[(n >> 18) as usize & 0x3F] as char);
1146 out.push(A[(n >> 12) as usize & 0x3F] as char);
1147 out.push(A[(n >> 6) as usize & 0x3F] as char);
1148 out.push('=');
1149 }
1150 out
1151}
1152
1153fn dto<'a>(e: &'a Event) -> EventDto<'a> {
1154 match e {
1155 Event::StateChanged { from, to } => EventDto::StateChanged {
1156 from: from.to_string(),
1157 to: to.to_string(),
1158 },
1159 Event::PayloadReceived(DeliveredPayload {
1160 stream_type,
1161 stream_id,
1162 sequence_no,
1163 flags,
1164 plaintext,
1165 }) => EventDto::PayloadReceived {
1166 stream_type: match stream_type {
1167 StreamType::Control => "control",
1168 StreamType::Audio => "audio",
1169 StreamType::Text => "text",
1170 StreamType::Signal => "signal",
1171 },
1172 stream_type_code: *stream_type as u32,
1173 stream_id: *stream_id,
1174 sequence_no: *sequence_no,
1175 flags: *flags,
1176 plaintext_b64: b64(plaintext),
1177 },
1178 Event::Control { from, opcode, transition_id, request_id, args } => EventDto::Control {
1179 from: *from,
1180 opcode: opcode.name(),
1181 opcode_code: *opcode as u16,
1182 transition_id: *transition_id,
1183 request_id: *request_id,
1184 args_b64: b64(args),
1185 },
1186 Event::Error { code, class, retryable, fatal, reason } => EventDto::Error {
1187 code: *code,
1188 code_hex: format!("0x{code:04X}"),
1189 class: *class as u8,
1190 retryable: *retryable,
1191 fatal: *fatal,
1192 reason: reason.clone(),
1193 },
1194 Event::EpochAdvanced { epoch, transition_id } => EventDto::EpochAdvanced {
1195 epoch: *epoch,
1196 transition_id: *transition_id,
1197 },
1198 }
1199}
1200
1201fn events_to_json(events: &[Event]) -> String {
1202 let dtos: Vec<EventDto> = events.iter().map(dto).collect();
1203 serde_json::to_string(&dtos).unwrap_or_else(|_| "[]".to_string())
1204}
1205
1206#[allow(dead_code)]
1207const _STATES: [NodeState; 7] = [
1208 NodeState::Idle,
1209 NodeState::Connecting,
1210 NodeState::EstablishingGroup,
1211 NodeState::Active,
1212 NodeState::Resyncing,
1213 NodeState::Failed,
1214 NodeState::Closed,
1215];
1216
1217#[unsafe(no_mangle)]
1238pub unsafe extern "C" fn gbp_sframe_session_create(
1239 mls_handle: i32,
1240 suite: u8,
1241 label_ptr: *const u8,
1242 label_len: usize,
1243) -> i32 {
1244 clear_last_error();
1245 let suite = match CipherSuite::from_u8(suite) {
1246 Some(s) => s,
1247 None => {
1248 set_last_error(format!("unknown ciphersuite {suite}"));
1249 return 0;
1250 }
1251 };
1252 let label = unsafe {
1253 match std::str::from_utf8(std::slice::from_raw_parts(label_ptr, label_len)) {
1254 Ok(s) => s,
1255 Err(e) => { set_last_error(e); return 0; }
1256 }
1257 };
1258 let Some(mls_arc) = mls().get(mls_handle) else {
1259 set_last_error("invalid MLS handle");
1260 return 0;
1261 };
1262 let mls = mls_arc.lock().unwrap();
1263 match SFrameSession::from_mls(&mls, label, suite) {
1264 Ok(session) => sframe_sessions().insert(session.decryptor()),
1265 Err(e) => { set_last_error(e); 0 }
1266 }
1267}
1268
1269#[unsafe(no_mangle)]
1271pub extern "C" fn gbp_sframe_session_free(handle: i32) {
1272 sframe_sessions().remove(handle);
1273}
1274
1275#[unsafe(no_mangle)]
1286pub unsafe extern "C" fn gbp_sframe_encryptor_create(
1287 mls_handle: i32,
1288 session_handle: i32,
1289 leaf_index: u32,
1290 suite: u8,
1291 label_ptr: *const u8,
1292 label_len: usize,
1293) -> i32 {
1294 clear_last_error();
1295 let suite = match CipherSuite::from_u8(suite) {
1296 Some(s) => s,
1297 None => {
1298 set_last_error(format!("unknown ciphersuite {suite}"));
1299 return 0;
1300 }
1301 };
1302 let label = unsafe {
1303 match std::str::from_utf8(std::slice::from_raw_parts(label_ptr, label_len)) {
1304 Ok(s) => s,
1305 Err(e) => { set_last_error(e); return 0; }
1306 }
1307 };
1308 if sframe_sessions().get(session_handle).is_none() {
1310 set_last_error("invalid session handle");
1311 return 0;
1312 }
1313 let Some(mls_arc) = mls().get(mls_handle) else {
1314 set_last_error("invalid MLS handle");
1315 return 0;
1316 };
1317 let mls = mls_arc.lock().unwrap();
1318 match SFrameSession::from_mls(&mls, label, suite) {
1319 Ok(session) => sframe_encryptors().insert(session.encryptor(leaf_index)),
1320 Err(e) => { set_last_error(e); 0 }
1321 }
1322}
1323
1324#[unsafe(no_mangle)]
1326pub extern "C" fn gbp_sframe_encryptor_free(handle: i32) {
1327 sframe_encryptors().remove(handle);
1328}
1329
1330#[unsafe(no_mangle)]
1343pub unsafe extern "C" fn gbp_sframe_encrypt(
1344 enc_handle: i32,
1345 plaintext_ptr: *const u8,
1346 plaintext_len: usize,
1347 aad_ptr: *const u8,
1348 aad_len: usize,
1349) -> GbpBuffer {
1350 clear_last_error();
1351 let Some(enc_arc) = sframe_encryptors().get(enc_handle) else {
1352 set_last_error("invalid encryptor handle");
1353 return GbpBuffer::empty();
1354 };
1355 let plaintext = unsafe { std::slice::from_raw_parts(plaintext_ptr, plaintext_len) };
1356 let aad = if aad_ptr.is_null() || aad_len == 0 {
1357 &[][..]
1358 } else {
1359 unsafe { std::slice::from_raw_parts(aad_ptr, aad_len) }
1360 };
1361 let mut enc = enc_arc.lock().unwrap();
1362 match enc.encrypt(plaintext, aad) {
1363 Ok(payload) => GbpBuffer::from_vec(payload),
1364 Err(e) => { set_last_error(e); GbpBuffer::empty() }
1365 }
1366}
1367
1368#[unsafe(no_mangle)]
1380pub unsafe extern "C" fn gbp_sframe_decrypt(
1381 session_handle: i32,
1382 payload_ptr: *const u8,
1383 payload_len: usize,
1384 aad_ptr: *const u8,
1385 aad_len: usize,
1386 sender_leaf_out: *mut u32,
1387) -> GbpBuffer {
1388 clear_last_error();
1389 let Some(session_arc) = sframe_sessions().get(session_handle) else {
1390 set_last_error("invalid session handle");
1391 return GbpBuffer::empty();
1392 };
1393 let payload = unsafe { std::slice::from_raw_parts(payload_ptr, payload_len) };
1394 let aad = if aad_ptr.is_null() || aad_len == 0 {
1395 &[][..]
1396 } else {
1397 unsafe { std::slice::from_raw_parts(aad_ptr, aad_len) }
1398 };
1399 let mut dec = session_arc.lock().unwrap();
1400 match dec.decrypt(payload, aad) {
1401 Ok((plaintext, leaf)) => {
1402 if !sender_leaf_out.is_null() {
1403 unsafe { *sender_leaf_out = leaf; }
1404 }
1405 GbpBuffer::from_vec(plaintext)
1406 }
1407 Err(e) => { set_last_error(e); GbpBuffer::empty() }
1408 }
1409}
1410
1411#[cfg(test)]
1412mod tests {
1413 use super::b64;
1414
1415 #[test]
1416 fn b64_empty() {
1417 assert_eq!(b64(b""), "");
1418 }
1419
1420 #[test]
1421 fn b64_single_byte() {
1422 let s = b64(b"f");
1429 assert_eq!(s, "Zg==");
1431 }
1432
1433 #[test]
1434 fn b64_two_bytes() {
1435 let s = b64(b"fo");
1436 assert_eq!(s, "Zm8=");
1440 }
1441
1442 #[test]
1443 fn b64_three_bytes() {
1444 let s = b64(b"foo");
1445 assert_eq!(s, "Zm9v");
1447 }
1448
1449 #[test]
1450 fn b64_known_vectors() {
1451 assert_eq!(b64(b""), "");
1453 assert_eq!(b64(b"f"), "Zg==");
1454 assert_eq!(b64(b"fo"), "Zm8=");
1455 assert_eq!(b64(b"foo"), "Zm9v");
1456 assert_eq!(b64(b"foob"), "Zm9vYg==");
1457 assert_eq!(b64(b"fooba"), "Zm9vYmE=");
1458 assert_eq!(b64(b"foobar"), "Zm9vYmFy");
1459 }
1460
1461 #[test]
1462 fn b64_padding_roundtrip() {
1463 for b in 0u8..=255 {
1465 let input = [b];
1466 let enc = b64(&input);
1467 assert_eq!(enc.len(), 4, "len mismatch for 0x{b:02X}: {enc}");
1469 assert!(enc.ends_with("=="), "missing padding for 0x{b:02X}: {enc}");
1471 }
1472 }
1473}