1#![allow(unsafe_op_in_unsafe_fn)]
25
26use gbp_stack::core::{ControlOpcode, NodeState, SignalType, StreamType};
27use gbp_stack::{
28 DeliveredPayload, ErrorObject, Event, GapAccept, GapClient, GbpFrame, GroupNode, GspAccept,
29 GspClient, GtpAccept, GtpClient, MlsContext, OutboundFrame, ProcessedKind, StreamLabel,
30};
31use openmls::prelude::tls_codec::Serialize as _;
32use openmls::prelude::*;
33use serde::Serialize;
34use std::cell::RefCell;
35use std::collections::HashMap;
36use std::ffi::{CString, c_char};
37use std::sync::{Arc, Mutex};
38use std::sync::atomic::{AtomicI32, Ordering};
39
40#[repr(C)]
47pub struct GbpBuffer {
48 pub ptr: *mut u8,
50 pub len: usize,
52 pub cap: usize,
54}
55
56impl GbpBuffer {
57 fn empty() -> Self {
58 Self { ptr: std::ptr::null_mut(), len: 0, cap: 0 }
59 }
60 fn from_vec(mut v: Vec<u8>) -> Self {
61 let ptr = v.as_mut_ptr();
62 let len = v.len();
63 let cap = v.capacity();
64 std::mem::forget(v);
65 Self { ptr, len, cap }
66 }
67}
68
69#[unsafe(no_mangle)]
75pub unsafe extern "C" fn gbp_buffer_free(buf: GbpBuffer) {
76 if buf.ptr.is_null() {
77 return;
78 }
79 unsafe {
80 let _ = Vec::from_raw_parts(buf.ptr, buf.len, buf.cap);
81 }
82}
83
84#[unsafe(no_mangle)]
89pub unsafe extern "C" fn gbp_string_free(ptr: *mut c_char) {
90 if ptr.is_null() {
91 return;
92 }
93 unsafe {
94 let _ = CString::from_raw(ptr);
95 }
96}
97
98fn alloc_cstring(s: &str) -> *mut c_char {
99 CString::new(s.as_bytes())
100 .unwrap_or_else(|_| CString::new(s.replace('\0', "?")).unwrap())
101 .into_raw()
102}
103
104thread_local! {
109 static LAST_ERROR: RefCell<String> = const { RefCell::new(String::new()) };
110}
111
112fn set_last_error(e: impl ToString) {
113 LAST_ERROR.with(|s| *s.borrow_mut() = e.to_string());
114}
115
116fn clear_last_error() {
117 LAST_ERROR.with(|s| s.borrow_mut().clear());
118}
119
120#[unsafe(no_mangle)]
122pub extern "C" fn gbp_last_error() -> *mut c_char {
123 LAST_ERROR.with(|s| alloc_cstring(&s.borrow()))
124}
125
126macro_rules! registry {
131 ($vis:vis $name:ident<$t:ty>) => {
132 $vis struct $name {
133 next: AtomicI32,
134 map: Mutex<HashMap<i32, Arc<Mutex<$t>>>>,
135 }
136 impl $name {
137 fn new() -> Self {
138 Self { next: AtomicI32::new(1), map: Mutex::new(HashMap::new()) }
139 }
140 fn insert(&self, v: $t) -> i32 {
141 let id = self.next.fetch_add(1, Ordering::Relaxed);
142 self.map.lock().unwrap().insert(id, Arc::new(Mutex::new(v)));
143 id
144 }
145 fn remove(&self, id: i32) {
146 self.map.lock().unwrap().remove(&id);
147 }
148 fn get(&self, id: i32) -> Option<Arc<Mutex<$t>>> {
149 self.map.lock().unwrap().get(&id).cloned()
150 }
151 }
152 };
153}
154
155registry!(MlsRegistry<MlsContext>);
156registry!(NodeRegistry<GroupNode>);
157registry!(GtpRegistry<GtpClient>);
158registry!(GapRegistry<GapClient>);
159registry!(GspRegistry<GspClient>);
160
161struct MlsBundles {
162 map: Mutex<HashMap<i32, KeyPackageBundle>>,
163}
164impl MlsBundles {
165 fn new() -> Self {
166 Self { map: Mutex::new(HashMap::new()) }
167 }
168}
169
170fn mls() -> &'static MlsRegistry {
171 use std::sync::OnceLock;
172 static R: OnceLock<MlsRegistry> = OnceLock::new();
173 R.get_or_init(MlsRegistry::new)
174}
175fn mls_bundles() -> &'static MlsBundles {
176 use std::sync::OnceLock;
177 static R: OnceLock<MlsBundles> = OnceLock::new();
178 R.get_or_init(MlsBundles::new)
179}
180fn nodes() -> &'static NodeRegistry {
181 use std::sync::OnceLock;
182 static R: OnceLock<NodeRegistry> = OnceLock::new();
183 R.get_or_init(NodeRegistry::new)
184}
185fn gtps() -> &'static GtpRegistry {
186 use std::sync::OnceLock;
187 static R: OnceLock<GtpRegistry> = OnceLock::new();
188 R.get_or_init(GtpRegistry::new)
189}
190fn gaps() -> &'static GapRegistry {
191 use std::sync::OnceLock;
192 static R: OnceLock<GapRegistry> = OnceLock::new();
193 R.get_or_init(GapRegistry::new)
194}
195fn gsps() -> &'static GspRegistry {
196 use std::sync::OnceLock;
197 static R: OnceLock<GspRegistry> = OnceLock::new();
198 R.get_or_init(GspRegistry::new)
199}
200
201#[unsafe(no_mangle)]
207pub extern "C" fn gbp_version() -> *mut c_char {
208 alloc_cstring(&format!(
209 "group-protocol-stack {} (gbp + gtp + gap + gsp)",
210 env!("CARGO_PKG_VERSION")
211 ))
212}
213
214#[unsafe(no_mangle)]
223pub unsafe extern "C" fn gbp_mls_create(identity_ptr: *const u8, identity_len: usize) -> i32 {
224 clear_last_error();
225 let ident = unsafe { std::slice::from_raw_parts(identity_ptr, identity_len) };
226 match MlsContext::new_member(ident) {
227 Ok((ctx, kp)) => {
228 let id = mls().insert(ctx);
229 mls_bundles().map.lock().unwrap().insert(id, kp);
230 id
231 }
232 Err(e) => {
233 set_last_error(e);
234 0
235 }
236 }
237}
238
239#[unsafe(no_mangle)]
241pub extern "C" fn gbp_mls_destroy(h: i32) {
242 mls().remove(h);
243 mls_bundles().map.lock().unwrap().remove(&h);
244}
245
246#[unsafe(no_mangle)]
248pub extern "C" fn gbp_mls_epoch(h: i32) -> u64 {
249 mls().get(h).map(|c| c.lock().unwrap().epoch()).unwrap_or(0)
250}
251
252#[unsafe(no_mangle)]
257pub unsafe extern "C" fn gbp_mls_group_id(h: i32, out16: *mut u8) -> bool {
258 clear_last_error();
259 let Some(ctx_arc) = mls().get(h) else {
260 set_last_error("invalid MLS handle");
261 return false;
262 };
263 let ctx = ctx_arc.lock().unwrap();
264 let gid = ctx.group_id_16();
265 unsafe { std::ptr::copy_nonoverlapping(gid.as_ptr(), out16, 16) };
266 true
267}
268
269#[unsafe(no_mangle)]
272pub extern "C" fn gbp_mls_export_key_package(h: i32) -> GbpBuffer {
273 clear_last_error();
274 let bundles = mls_bundles().map.lock().unwrap();
275 let Some(b) = bundles.get(&h) else {
276 set_last_error("invalid MLS handle");
277 return GbpBuffer::empty();
278 };
279 match b.key_package().tls_serialize_detached() {
280 Ok(b) => GbpBuffer::from_vec(b),
281 Err(e) => {
282 set_last_error(format!("kp serialize: {e:?}"));
283 GbpBuffer::empty()
284 }
285 }
286}
287
288#[unsafe(no_mangle)]
295pub unsafe extern "C" fn gbp_mls_invite(h: i32, kp_ptr: *const u8, kp_len: usize) -> GbpBuffer {
296 clear_last_error();
297 let bytes = unsafe { std::slice::from_raw_parts(kp_ptr, kp_len) };
298 let Some(ctx_arc) = mls().get(h) else {
299 set_last_error("invalid MLS handle");
300 return GbpBuffer::empty();
301 };
302 let mut ctx = ctx_arc.lock().unwrap();
303 let kp_in = match KeyPackageIn::tls_deserialize_exact_bytes(bytes) {
304 Ok(v) => v,
305 Err(e) => {
306 set_last_error(format!("kp parse: {e:?}"));
307 return GbpBuffer::empty();
308 }
309 };
310 let validated = match kp_in.validate(ctx.provider.crypto(), ProtocolVersion::Mls10) {
311 Ok(v) => v,
312 Err(e) => {
313 set_last_error(format!("kp validate: {e:?}"));
314 return GbpBuffer::empty();
315 }
316 };
317 match ctx.invite(&[validated]) {
318 Ok(welcome) => GbpBuffer::from_vec(welcome),
319 Err(e) => {
320 set_last_error(e);
321 GbpBuffer::empty()
322 }
323 }
324}
325
326#[unsafe(no_mangle)]
336pub unsafe extern "C" fn gbp_mls_invite_full(
337 h: i32,
338 kp_ptr: *const u8,
339 kp_len: usize,
340) -> GbpBuffer {
341 clear_last_error();
342 let bytes = unsafe { std::slice::from_raw_parts(kp_ptr, kp_len) };
343 let Some(ctx_arc) = mls().get(h) else {
344 set_last_error("invalid MLS handle");
345 return GbpBuffer::empty();
346 };
347 let mut ctx = ctx_arc.lock().unwrap();
348 let kp_in = match KeyPackageIn::tls_deserialize_exact_bytes(bytes) {
349 Ok(v) => v,
350 Err(e) => {
351 set_last_error(format!("kp parse: {e:?}"));
352 return GbpBuffer::empty();
353 }
354 };
355 let validated = match kp_in.validate(ctx.provider.crypto(), ProtocolVersion::Mls10) {
356 Ok(v) => v,
357 Err(e) => {
358 set_last_error(format!("kp validate: {e:?}"));
359 return GbpBuffer::empty();
360 }
361 };
362 match ctx.invite_full(&[validated]) {
363 Ok((commit, welcome)) => {
364 let mut out = Vec::with_capacity(4 + commit.len() + welcome.len());
365 out.extend_from_slice(&(commit.len() as u32).to_le_bytes());
366 out.extend_from_slice(&commit);
367 out.extend_from_slice(&welcome);
368 GbpBuffer::from_vec(out)
369 }
370 Err(e) => {
371 set_last_error(e);
372 GbpBuffer::empty()
373 }
374 }
375}
376
377#[unsafe(no_mangle)]
381pub extern "C" fn gbp_mls_remove(h: i32, leaf_index: u32) -> GbpBuffer {
382 clear_last_error();
383 let Some(ctx_arc) = mls().get(h) else {
384 set_last_error("invalid MLS handle");
385 return GbpBuffer::empty();
386 };
387 let mut ctx = ctx_arc.lock().unwrap();
388 match ctx.remove_members(&[leaf_index]) {
389 Ok(commit) => GbpBuffer::from_vec(commit),
390 Err(e) => {
391 set_last_error(e);
392 GbpBuffer::empty()
393 }
394 }
395}
396
397#[unsafe(no_mangle)]
408pub unsafe extern "C" fn gbp_mls_process_message(
409 h: i32,
410 msg_ptr: *const u8,
411 msg_len: usize,
412) -> u32 {
413 clear_last_error();
414 let bytes = unsafe { std::slice::from_raw_parts(msg_ptr, msg_len) };
415 let Some(ctx_arc) = mls().get(h) else {
416 set_last_error("invalid MLS handle");
417 return 0;
418 };
419 let mut ctx = ctx_arc.lock().unwrap();
420 match ctx.process_message(bytes) {
421 Ok(ProcessedKind::Commit) => 1,
422 Ok(ProcessedKind::Application) => 2,
423 Ok(ProcessedKind::Proposal) => 3,
424 Ok(ProcessedKind::External) => 4,
425 Err(e) => {
426 set_last_error(e);
427 0
428 }
429 }
430}
431
432#[unsafe(no_mangle)]
435pub extern "C" fn gbp_mls_finalize_commit(h: i32) -> bool {
436 clear_last_error();
437 let Some(ctx_arc) = mls().get(h) else {
438 set_last_error("invalid MLS handle");
439 return false;
440 };
441 let mut ctx = ctx_arc.lock().unwrap();
442 match ctx.finalize_pending_commit() {
443 Ok(()) => true,
444 Err(e) => {
445 set_last_error(e);
446 false
447 }
448 }
449}
450
451#[unsafe(no_mangle)]
454pub extern "C" fn gbp_mls_clear_pending_commit(h: i32) -> bool {
455 clear_last_error();
456 let Some(ctx_arc) = mls().get(h) else {
457 set_last_error("invalid MLS handle");
458 return false;
459 };
460 let mut ctx = ctx_arc.lock().unwrap();
461 match ctx.clear_pending_commit() {
462 Ok(()) => true,
463 Err(e) => {
464 set_last_error(e);
465 false
466 }
467 }
468}
469
470#[unsafe(no_mangle)]
475pub unsafe extern "C" fn gbp_mls_accept_welcome(
476 h: i32,
477 welcome_ptr: *const u8,
478 welcome_len: usize,
479) -> bool {
480 clear_last_error();
481 let bytes = unsafe { std::slice::from_raw_parts(welcome_ptr, welcome_len) };
482 let Some(ctx_arc) = mls().get(h) else {
483 set_last_error("invalid MLS handle");
484 return false;
485 };
486 let mut ctx = ctx_arc.lock().unwrap();
487 match ctx.accept_welcome(bytes) {
488 Ok(()) => true,
489 Err(e) => {
490 set_last_error(e);
491 false
492 }
493 }
494}
495
496#[unsafe(no_mangle)]
505pub unsafe extern "C" fn gbp_node_create(member_id: u32, group_id_16: *const u8) -> i32 {
506 clear_last_error();
507 let mut gid = [0u8; 16];
508 unsafe { std::ptr::copy_nonoverlapping(group_id_16, gid.as_mut_ptr(), 16) };
509 nodes().insert(GroupNode::new(member_id, gid))
510}
511
512#[unsafe(no_mangle)]
514pub extern "C" fn gbp_node_destroy(h: i32) {
515 nodes().remove(h);
516}
517
518#[unsafe(no_mangle)]
520pub extern "C" fn gbp_node_bootstrap_creator(h: i32, epoch: u64) -> bool {
521 let Some(n_arc) = nodes().get(h) else { return false };
522 n_arc.lock().unwrap().bootstrap_as_creator(epoch);
523 true
524}
525
526#[unsafe(no_mangle)]
531pub extern "C" fn gbp_node_bootstrap_joiner(h: i32, epoch: u64, expected_first_tid: u32) -> bool {
532 let Some(n_arc) = nodes().get(h) else { return false };
533 n_arc.lock().unwrap().bootstrap_as_joiner(epoch, expected_first_tid);
534 true
535}
536
537#[unsafe(no_mangle)]
539pub extern "C" fn gbp_node_state(h: i32) -> u32 {
540 nodes()
541 .get(h)
542 .map(|n| n.lock().unwrap().state as u32)
543 .unwrap_or(u32::MAX)
544}
545
546#[unsafe(no_mangle)]
548pub extern "C" fn gbp_node_epoch(h: i32) -> u64 {
549 nodes().get(h).map(|n| n.lock().unwrap().current_epoch).unwrap_or(0)
550}
551
552#[unsafe(no_mangle)]
554pub extern "C" fn gbp_node_last_transition_id(h: i32) -> u32 {
555 nodes()
556 .get(h)
557 .map(|n| n.lock().unwrap().last_transition_id)
558 .unwrap_or(0)
559}
560
561#[unsafe(no_mangle)]
564pub extern "C" fn gbp_node_set_epoch(h: i32, epoch: u64) -> bool {
565 let Some(n_arc) = nodes().get(h) else { return false };
566 n_arc.lock().unwrap().current_epoch = epoch;
567 true
568}
569
570#[unsafe(no_mangle)]
572pub extern "C" fn gbp_node_apply_transition(h: i32, tid: u32) -> bool {
573 let Some(n_arc) = nodes().get(h) else { return false };
574 n_arc.lock().unwrap().apply_transition(tid);
575 true
576}
577
578#[unsafe(no_mangle)]
585pub unsafe extern "C" fn gbp_node_send_control(
586 nh: i32,
587 mh: i32,
588 target: u32,
589 opcode: u16,
590 transition_id: u32,
591 request_id: u32,
592 args_ptr: *const u8,
593 args_len: usize,
594) -> GbpBuffer {
595 clear_last_error();
596 let op = match ControlOpcode::try_from(opcode) {
597 Ok(o) => o,
598 Err(_) => {
599 set_last_error(format!("bad opcode 0x{opcode:04X}"));
600 return GbpBuffer::empty();
601 }
602 };
603 let args = if args_len == 0 {
604 Vec::new()
605 } else {
606 unsafe { std::slice::from_raw_parts(args_ptr, args_len) }.to_vec()
607 };
608 let (n_arc, m_arc) = (nodes().get(nh), mls().get(mh));
609 let (Some(n_arc), Some(m_arc)) = (n_arc, m_arc) else {
610 set_last_error("bad node/mls handle");
611 return GbpBuffer::empty();
612 };
613 let mut n = n_arc.lock().unwrap();
614 let mut m = m_arc.lock().unwrap();
615 match n.send_control(&mut *m, target, op, transition_id, request_id, args) {
616 Ok(of) => outbound_to_buffer(of),
617 Err(e) => {
618 set_last_error(e.to_string());
619 GbpBuffer::empty()
620 }
621 }
622}
623
624#[unsafe(no_mangle)]
629pub unsafe extern "C" fn gbp_node_on_wire(
630 nh: i32,
631 mh: i32,
632 wire_ptr: *const u8,
633 wire_len: usize,
634) -> *mut c_char {
635 clear_last_error();
636 let wire = unsafe { std::slice::from_raw_parts(wire_ptr, wire_len) };
637 let (n_arc, m_arc) = (nodes().get(nh), mls().get(mh));
638 let (Some(n_arc), Some(m_arc)) = (n_arc, m_arc) else {
639 set_last_error("bad node/mls handle");
640 return alloc_cstring("[]");
641 };
642 let mut n = n_arc.lock().unwrap();
643 let mut m = m_arc.lock().unwrap();
644 let events = match n.on_wire(&mut *m, wire) {
645 Ok(e) => e,
646 Err(e) => {
647 set_last_error(e.to_string());
648 return alloc_cstring("[]");
649 }
650 };
651 alloc_cstring(&events_to_json(&events))
652}
653
654#[unsafe(no_mangle)]
656pub extern "C" fn gbp_node_drain_events(nh: i32) -> *mut c_char {
657 let Some(n_arc) = nodes().get(nh) else {
658 return alloc_cstring("[]");
659 };
660 alloc_cstring(&events_to_json(&n_arc.lock().unwrap().drain_events()))
661}
662
663fn outbound_to_buffer(of: OutboundFrame) -> GbpBuffer {
664 let mut out = Vec::with_capacity(4 + of.wire.len());
665 out.extend_from_slice(&of.to.to_le_bytes());
666 out.extend_from_slice(&of.wire);
667 GbpBuffer::from_vec(out)
668}
669
670#[unsafe(no_mangle)]
676pub extern "C" fn gtp_client_create() -> i32 {
677 gtps().insert(GtpClient::new())
678}
679
680#[unsafe(no_mangle)]
682pub extern "C" fn gtp_client_destroy(h: i32) {
683 gtps().remove(h);
684}
685
686#[unsafe(no_mangle)]
688pub extern "C" fn gtp_client_reset(h: i32) {
689 if let Some(c) = gtps().get(h) {
690 c.lock().unwrap().reset();
691 }
692}
693
694#[unsafe(no_mangle)]
699pub unsafe extern "C" fn gtp_client_send(
700 ch: i32,
701 nh: i32,
702 mh: i32,
703 target: u32,
704 message_id: u64,
705 text_ptr: *const u8,
706 text_len: usize,
707) -> GbpBuffer {
708 clear_last_error();
709 let text = unsafe { std::slice::from_raw_parts(text_ptr, text_len) };
710 let text = match std::str::from_utf8(text) {
711 Ok(s) => s,
712 Err(e) => {
713 set_last_error(format!("utf8: {e}"));
714 return GbpBuffer::empty();
715 }
716 };
717 let (c_arc, n_arc, m_arc) = (gtps().get(ch), nodes().get(nh), mls().get(mh));
718 let (Some(c_arc), Some(n_arc), Some(m_arc)) = (c_arc, n_arc, m_arc) else {
719 set_last_error("bad handle");
720 return GbpBuffer::empty();
721 };
722 let mut c = c_arc.lock().unwrap();
723 let mut n = n_arc.lock().unwrap();
724 let mut m = m_arc.lock().unwrap();
725 match c.send(&mut *n, &mut *m, target, message_id, text) {
726 Ok(of) => outbound_to_buffer(of),
727 Err(e) => {
728 set_last_error(e.to_string());
729 GbpBuffer::empty()
730 }
731 }
732}
733
734#[unsafe(no_mangle)]
744pub unsafe extern "C" fn gtp_client_accept(
745 ch: i32,
746 current_epoch: u64,
747 pt_ptr: *const u8,
748 pt_len: usize,
749) -> *mut c_char {
750 clear_last_error();
751 let pt = unsafe { std::slice::from_raw_parts(pt_ptr, pt_len) };
752 let Some(c_arc) = gtps().get(ch) else {
753 return alloc_cstring(r#"{"status":"error","reason":"bad client"}"#);
754 };
755 let mut c = c_arc.lock().unwrap();
756 #[derive(Serialize)]
757 struct Out<'a> {
758 status: &'a str,
759 sender: Option<u32>,
760 message_id: Option<u64>,
761 text: Option<String>,
762 reason: Option<String>,
763 }
764 let out = match c.accept(pt, current_epoch) {
765 Ok(GtpAccept::New(m)) => Out {
766 status: "new",
767 sender: Some(m.sender_id),
768 message_id: Some(m.message_id),
769 text: Some(m.text().unwrap_or("<binary>").to_string()),
770 reason: None,
771 },
772 Ok(GtpAccept::Duplicate(m)) => Out {
773 status: "duplicate",
774 sender: Some(m.sender_id),
775 message_id: Some(m.message_id),
776 text: Some(m.text().unwrap_or("<binary>").to_string()),
777 reason: None,
778 },
779 Err(e) => Out {
780 status: "error",
781 sender: None,
782 message_id: None,
783 text: None,
784 reason: Some(e.to_string()),
785 },
786 };
787 alloc_cstring(&serde_json::to_string(&out).unwrap_or_default())
788}
789
790#[unsafe(no_mangle)]
796pub extern "C" fn gap_client_create() -> i32 {
797 gaps().insert(GapClient::new())
798}
799
800#[unsafe(no_mangle)]
802pub extern "C" fn gap_client_destroy(h: i32) {
803 gaps().remove(h);
804}
805
806#[unsafe(no_mangle)]
808pub extern "C" fn gap_client_reset(h: i32) {
809 if let Some(c) = gaps().get(h) {
810 c.lock().unwrap().reset();
811 }
812}
813
814#[unsafe(no_mangle)]
819pub unsafe extern "C" fn gap_client_send(
820 ch: i32,
821 nh: i32,
822 mh: i32,
823 target: u32,
824 media_source_id: u32,
825 rtp_timestamp: u64,
826 opus_ptr: *const u8,
827 opus_len: usize,
828) -> GbpBuffer {
829 clear_last_error();
830 let opus = unsafe { std::slice::from_raw_parts(opus_ptr, opus_len) }.to_vec();
831 let (c_arc, n_arc, m_arc) = (gaps().get(ch), nodes().get(nh), mls().get(mh));
832 let (Some(c_arc), Some(n_arc), Some(m_arc)) = (c_arc, n_arc, m_arc) else {
833 set_last_error("bad handle");
834 return GbpBuffer::empty();
835 };
836 let mut c = c_arc.lock().unwrap();
837 let mut n = n_arc.lock().unwrap();
838 let mut m = m_arc.lock().unwrap();
839 match c.send(&mut *n, &mut *m, target, media_source_id, rtp_timestamp, opus) {
840 Ok(of) => outbound_to_buffer(of),
841 Err(e) => {
842 set_last_error(e.to_string());
843 GbpBuffer::empty()
844 }
845 }
846}
847
848#[unsafe(no_mangle)]
853pub unsafe extern "C" fn gap_client_accept(
854 ch: i32,
855 current_epoch: u64,
856 pt_ptr: *const u8,
857 pt_len: usize,
858) -> *mut c_char {
859 clear_last_error();
860 let pt = unsafe { std::slice::from_raw_parts(pt_ptr, pt_len) };
861 let Some(c_arc) = gaps().get(ch) else {
862 return alloc_cstring(r#"{"status":"error","reason":"bad client"}"#);
863 };
864 let mut c = c_arc.lock().unwrap();
865 #[derive(Serialize)]
866 struct Out<'a> {
867 status: &'a str,
868 source: Option<u32>,
869 seq: Option<u32>,
870 bytes: Option<usize>,
871 reason: Option<String>,
872 }
873 let out = match c.accept(pt, current_epoch) {
874 Ok(GapAccept::New(p)) => Out {
875 status: "new",
876 source: Some(p.media_source_id),
877 seq: Some(p.rtp_sequence),
878 bytes: Some(p.opus_frame.len()),
879 reason: None,
880 },
881 Ok(GapAccept::Late(p)) => Out {
882 status: "late",
883 source: Some(p.media_source_id),
884 seq: Some(p.rtp_sequence),
885 bytes: Some(p.opus_frame.len()),
886 reason: None,
887 },
888 Err(e) => Out {
889 status: "error",
890 source: None,
891 seq: None,
892 bytes: None,
893 reason: Some(e.to_string()),
894 },
895 };
896 alloc_cstring(&serde_json::to_string(&out).unwrap_or_default())
897}
898
899#[unsafe(no_mangle)]
905pub extern "C" fn gsp_client_create() -> i32 {
906 gsps().insert(GspClient::new())
907}
908
909#[unsafe(no_mangle)]
911pub extern "C" fn gsp_client_destroy(h: i32) {
912 gsps().remove(h);
913}
914
915#[unsafe(no_mangle)]
917pub extern "C" fn gsp_client_reset(h: i32) {
918 if let Some(c) = gsps().get(h) {
919 c.lock().unwrap().reset();
920 }
921}
922
923#[unsafe(no_mangle)]
925pub extern "C" fn gsp_client_send(
926 ch: i32,
927 nh: i32,
928 mh: i32,
929 target: u32,
930 signal_type: u32,
931 role_claim: u32,
932 request_id: u32,
933) -> GbpBuffer {
934 clear_last_error();
935 let sig = match SignalType::try_from(signal_type) {
936 Ok(s) => s,
937 Err(_) => {
938 set_last_error(format!("bad signal {signal_type}"));
939 return GbpBuffer::empty();
940 }
941 };
942 let (c_arc, n_arc, m_arc) = (gsps().get(ch), nodes().get(nh), mls().get(mh));
943 let (Some(c_arc), Some(n_arc), Some(m_arc)) = (c_arc, n_arc, m_arc) else {
944 set_last_error("bad handle");
945 return GbpBuffer::empty();
946 };
947 let mut c = c_arc.lock().unwrap();
948 let mut n = n_arc.lock().unwrap();
949 let mut m = m_arc.lock().unwrap();
950 match c.send(&mut *n, &mut *m, target, sig, role_claim, request_id) {
951 Ok(of) => outbound_to_buffer(of),
952 Err(e) => {
953 set_last_error(e.to_string());
954 GbpBuffer::empty()
955 }
956 }
957}
958
959#[unsafe(no_mangle)]
967pub unsafe extern "C" fn gsp_client_accept(
968 ch: i32,
969 current_epoch: u64,
970 pt_ptr: *const u8,
971 pt_len: usize,
972) -> *mut c_char {
973 clear_last_error();
974 let pt = unsafe { std::slice::from_raw_parts(pt_ptr, pt_len) };
975 let Some(c_arc) = gsps().get(ch) else {
976 return alloc_cstring(r#"{"status":"error","reason":"bad client"}"#);
977 };
978 let mut c = c_arc.lock().unwrap();
979 #[derive(Serialize)]
980 struct Out<'a> {
981 status: &'a str,
982 signal: Option<&'a str>,
983 signal_code: Option<u32>,
984 sender: Option<u32>,
985 role_claim: Option<u32>,
986 request_id: Option<u32>,
987 reason: Option<String>,
988 }
989 let out = match c.accept(pt, current_epoch) {
990 Ok(GspAccept { signal, sender_id, role_claim, request_id }) => Out {
991 status: "new",
992 signal: Some(signal.name()),
993 signal_code: Some(signal as u32),
994 sender: Some(sender_id),
995 role_claim: Some(role_claim),
996 request_id: Some(request_id),
997 reason: None,
998 },
999 Err(e) => Out {
1000 status: "error",
1001 signal: None,
1002 signal_code: None,
1003 sender: None,
1004 role_claim: None,
1005 request_id: None,
1006 reason: Some(e.to_string()),
1007 },
1008 };
1009 alloc_cstring(&serde_json::to_string(&out).unwrap_or_default())
1010}
1011
1012#[unsafe(no_mangle)]
1021pub unsafe extern "C" fn gbp_frame_encode_v(
1022 version: u8,
1023 group_id_16: *const u8,
1024 epoch: u64,
1025 transition_id: u32,
1026 stream_type: u32,
1027 stream_id: u32,
1028 flags: u16,
1029 sequence_no: u32,
1030 payload_ptr: *const u8,
1031 payload_len: usize,
1032) -> GbpBuffer {
1033 clear_last_error();
1034 let mut gid = [0u8; 16];
1035 unsafe { std::ptr::copy_nonoverlapping(group_id_16, gid.as_mut_ptr(), 16) };
1036 let st_u8 = StreamType::try_from(stream_type).map(|s| s as u8).unwrap_or(stream_type as u8);
1037 let payload = unsafe { std::slice::from_raw_parts(payload_ptr, payload_len) }.to_vec();
1038 let frame = gbp_stack::gbp::GbpFrame {
1039 version,
1040 group_id: serde_bytes::ByteBuf::from(gid.to_vec()),
1041 epoch,
1042 transition_id,
1043 stream_type: st_u8,
1044 stream_id,
1045 flags,
1046 sequence_no,
1047 payload_size: payload.len() as u32,
1048 encrypted_payload: serde_bytes::ByteBuf::from(payload),
1049 };
1050 GbpBuffer::from_vec(frame.to_cbor())
1051}
1052
1053#[unsafe(no_mangle)]
1055pub extern "C" fn gbp_error_lookup(code: u16) -> GbpBuffer {
1056 use gbp_stack::core::errors::ErrorSpec;
1057 match ErrorSpec::lookup(code) {
1058 Some(spec) => GbpBuffer::from_vec(ErrorObject::from_spec(spec, spec.name).to_cbor()),
1059 None => {
1060 set_last_error(format!("unknown error code 0x{code:04X}"));
1061 GbpBuffer::empty()
1062 }
1063 }
1064}
1065
1066#[allow(dead_code)]
1067fn _link(_f: &GbpFrame, _l: StreamLabel) {}
1068
1069#[derive(Serialize)]
1074#[serde(tag = "kind", rename_all = "snake_case")]
1075enum EventDto<'a> {
1076 StateChanged {
1077 from: String,
1078 to: String,
1079 },
1080 PayloadReceived {
1081 stream_type: &'a str,
1082 stream_type_code: u32,
1083 stream_id: u32,
1084 sequence_no: u32,
1085 flags: u16,
1086 plaintext_b64: String,
1087 },
1088 Control {
1089 from: u32,
1090 opcode: &'a str,
1091 opcode_code: u16,
1092 transition_id: u32,
1093 request_id: u32,
1094 args_b64: String,
1095 },
1096 Error {
1097 code: u16,
1098 code_hex: String,
1099 class: u8,
1100 retryable: bool,
1101 fatal: bool,
1102 reason: String,
1103 },
1104 EpochAdvanced {
1105 epoch: u64,
1106 transition_id: u32,
1107 },
1108}
1109
1110fn b64(b: &[u8]) -> String {
1111 const A: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1112 let mut out = String::with_capacity(b.len().div_ceil(3) * 4);
1113 let mut i = 0;
1114 while i + 3 <= b.len() {
1115 let n = ((b[i] as u32) << 16) | ((b[i + 1] as u32) << 8) | (b[i + 2] as u32);
1116 out.push(A[(n >> 18) as usize & 0x3F] as char);
1117 out.push(A[(n >> 12) as usize & 0x3F] as char);
1118 out.push(A[(n >> 6) as usize & 0x3F] as char);
1119 out.push(A[n as usize & 0x3F] as char);
1120 i += 3;
1121 }
1122 let rem = b.len() - i;
1123 if rem == 1 {
1124 let n = (b[i] as u32) << 16;
1125 out.push(A[(n >> 18) as usize & 0x3F] as char);
1126 out.push(A[(n >> 12) as usize & 0x3F] as char);
1127 out.push('=');
1128 out.push('=');
1129 } else if rem == 2 {
1130 let n = ((b[i] as u32) << 16) | ((b[i + 1] as u32) << 8);
1131 out.push(A[(n >> 18) as usize & 0x3F] as char);
1132 out.push(A[(n >> 12) as usize & 0x3F] as char);
1133 out.push(A[(n >> 6) as usize & 0x3F] as char);
1134 out.push('=');
1135 }
1136 out
1137}
1138
1139fn dto<'a>(e: &'a Event) -> EventDto<'a> {
1140 match e {
1141 Event::StateChanged { from, to } => EventDto::StateChanged {
1142 from: from.to_string(),
1143 to: to.to_string(),
1144 },
1145 Event::PayloadReceived(DeliveredPayload {
1146 stream_type,
1147 stream_id,
1148 sequence_no,
1149 flags,
1150 plaintext,
1151 }) => EventDto::PayloadReceived {
1152 stream_type: match stream_type {
1153 StreamType::Control => "control",
1154 StreamType::Audio => "audio",
1155 StreamType::Text => "text",
1156 StreamType::Signal => "signal",
1157 },
1158 stream_type_code: *stream_type as u32,
1159 stream_id: *stream_id,
1160 sequence_no: *sequence_no,
1161 flags: *flags,
1162 plaintext_b64: b64(plaintext),
1163 },
1164 Event::Control { from, opcode, transition_id, request_id, args } => EventDto::Control {
1165 from: *from,
1166 opcode: opcode.name(),
1167 opcode_code: *opcode as u16,
1168 transition_id: *transition_id,
1169 request_id: *request_id,
1170 args_b64: b64(args),
1171 },
1172 Event::Error { code, class, retryable, fatal, reason } => EventDto::Error {
1173 code: *code,
1174 code_hex: format!("0x{code:04X}"),
1175 class: *class as u8,
1176 retryable: *retryable,
1177 fatal: *fatal,
1178 reason: reason.clone(),
1179 },
1180 Event::EpochAdvanced { epoch, transition_id } => EventDto::EpochAdvanced {
1181 epoch: *epoch,
1182 transition_id: *transition_id,
1183 },
1184 }
1185}
1186
1187fn events_to_json(events: &[Event]) -> String {
1188 let dtos: Vec<EventDto> = events.iter().map(dto).collect();
1189 serde_json::to_string(&dtos).unwrap_or_else(|_| "[]".to_string())
1190}
1191
1192#[allow(dead_code)]
1193const _STATES: [NodeState; 7] = [
1194 NodeState::Idle,
1195 NodeState::Connecting,
1196 NodeState::EstablishingGroup,
1197 NodeState::Active,
1198 NodeState::Resyncing,
1199 NodeState::Failed,
1200 NodeState::Closed,
1201];
1202
1203#[cfg(test)]
1204mod tests {
1205 use super::b64;
1206
1207 #[test]
1208 fn b64_empty() {
1209 assert_eq!(b64(b""), "");
1210 }
1211
1212 #[test]
1213 fn b64_single_byte() {
1214 let s = b64(b"f");
1221 assert_eq!(s, "Zg==");
1223 }
1224
1225 #[test]
1226 fn b64_two_bytes() {
1227 let s = b64(b"fo");
1228 assert_eq!(s, "Zm8=");
1232 }
1233
1234 #[test]
1235 fn b64_three_bytes() {
1236 let s = b64(b"foo");
1237 assert_eq!(s, "Zm9v");
1239 }
1240
1241 #[test]
1242 fn b64_known_vectors() {
1243 assert_eq!(b64(b""), "");
1245 assert_eq!(b64(b"f"), "Zg==");
1246 assert_eq!(b64(b"fo"), "Zm8=");
1247 assert_eq!(b64(b"foo"), "Zm9v");
1248 assert_eq!(b64(b"foob"), "Zm9vYg==");
1249 assert_eq!(b64(b"fooba"), "Zm9vYmE=");
1250 assert_eq!(b64(b"foobar"), "Zm9vYmFy");
1251 }
1252
1253 #[test]
1254 fn b64_padding_roundtrip() {
1255 for b in 0u8..=255 {
1257 let input = [b];
1258 let enc = b64(&input);
1259 assert_eq!(enc.len(), 4, "len mismatch for 0x{b:02X}: {enc}");
1261 assert!(enc.ends_with("=="), "missing padding for 0x{b:02X}: {enc}");
1263 }
1264 }
1265}