1use crate::copyterm::TermBuf;
24use crate::machine::Machine;
25use crate::render::RenderedSolution;
26use std::io::{self, Write};
27
28pub struct Envelope<'a> {
31 pub count: usize,
32 pub exhausted: bool,
33 pub solutions: &'a [RenderedSolution],
34 pub program_output: Option<&'a str>,
38 pub atoms: Option<&'a [String]>,
43}
44
45impl<'a> Envelope<'a> {
46 pub fn from_machine(m: &'a Machine, exhausted: bool) -> Self {
50 Self {
51 count: m.solutions.len(),
52 exhausted,
53 solutions: &m.solutions,
54 program_output: m.captured_output(),
55 atoms: None,
56 }
57 }
58}
59
60pub enum WireError {
67 Parse(String),
68 Runtime(String),
69}
70
71#[repr(C)]
78pub struct EncoderDesc {
79 pub name: &'static str,
81 pub write_envelope: fn(&mut dyn Write, &Machine, &Envelope) -> io::Result<()>,
82 pub write_error: fn(&mut dyn Write, &WireError) -> io::Result<()>,
83 pub can_stream: fn() -> bool,
86}
87
88impl EncoderDesc {
89 pub unsafe fn find(
95 caps: *const *const EncoderDesc,
96 len: usize,
97 name: &str,
98 ) -> Option<&'static EncoderDesc> {
99 let slice = unsafe { std::slice::from_raw_parts(caps, len) };
100 for &p in slice {
101 let d = unsafe { &*p };
102 if d.name == name {
103 return Some(d);
104 }
105 }
106 None
107 }
108}
109
110fn text_write_envelope(w: &mut dyn Write, _m: &Machine, e: &Envelope) -> io::Result<()> {
120 if e.solutions.is_empty() {
121 return w.write_all(b"false.\n");
122 }
123 for sol in e.solutions {
124 if sol.bindings.is_empty() {
125 w.write_all(b"true.\n")?;
126 continue;
127 }
128 for b in &sol.bindings {
129 writeln!(w, "{} = {}", b.name, b.text)?;
130 }
131 }
132 Ok(())
133}
134
135fn text_write_error(w: &mut dyn Write, err: &WireError) -> io::Result<()> {
136 let msg = match err {
137 WireError::Parse(m) | WireError::Runtime(m) => m,
138 };
139 writeln!(w, "error: {msg}")
140}
141
142const fn text_can_stream() -> bool {
143 true
144}
145
146#[unsafe(no_mangle)]
148pub static PLG_ENC_TEXT: EncoderDesc = EncoderDesc {
149 name: "text",
150 write_envelope: text_write_envelope,
151 write_error: text_write_error,
152 can_stream: text_can_stream,
153};
154
155fn serialize_termbuf(tb: &TermBuf) -> Vec<u8> {
184 let mut out = Vec::with_capacity(13 + tb.cells.len() * 8);
185 out.push(0x01); out.extend_from_slice(&(tb.cells.len() as u32).to_le_bytes());
187 out.extend_from_slice(&tb.root.to_le_bytes());
188 for c in &tb.cells {
189 out.extend_from_slice(&c.to_le_bytes());
190 }
191 out
192}
193
194const T_STRING: u8 = 0x02;
196const T_DOCUMENT: u8 = 0x03;
197const T_ARRAY: u8 = 0x04;
198const T_BINARY: u8 = 0x05;
199const T_BOOL: u8 = 0x08;
200const T_INT32: u8 = 0x10;
201const T_INT64: u8 = 0x12;
202
203fn bson_cstring(buf: &mut Vec<u8>, s: &str) {
204 buf.extend_from_slice(s.as_bytes());
205 buf.push(0x00);
206}
207
208fn bson_doc_begin(buf: &mut Vec<u8>) -> usize {
209 let start = buf.len();
210 buf.extend_from_slice(&[0; 4]); start
212}
213
214fn bson_doc_end(buf: &mut Vec<u8>, start: usize) {
215 buf.push(0x00); let len = i32::try_from(buf.len() - start).expect("bson doc < 2GB");
217 buf[start..start + 4].copy_from_slice(&len.to_le_bytes());
218}
219
220fn bson_atoms_array(buf: &mut Vec<u8>, names: &[String]) {
223 buf.push(T_ARRAY);
224 bson_cstring(buf, "atoms");
225 let arr = bson_doc_begin(buf);
226 for (i, name) in names.iter().enumerate() {
227 buf.push(T_STRING);
228 bson_cstring(buf, &i.to_string());
229 let len = i32::try_from(name.len() + 1).expect("atom name < 2GB");
230 buf.extend_from_slice(&len.to_le_bytes());
231 buf.extend_from_slice(name.as_bytes());
232 buf.push(0x00);
233 }
234 bson_doc_end(buf, arr);
235}
236
237pub fn write_atom_map_bson<W: Write>(w: &mut W, m: &Machine) -> io::Result<()> {
242 let names: Vec<String> = (0..m.atoms.len())
243 .map(|i| {
244 m.atoms
245 .try_resolve(i as u32)
246 .unwrap_or_default()
247 .to_string()
248 })
249 .collect();
250 let mut buf = Vec::new();
251 let doc = bson_doc_begin(&mut buf);
252 buf.push(T_INT32);
253 bson_cstring(&mut buf, "count");
254 buf.extend_from_slice(&(names.len().min(i32::MAX as usize) as i32).to_le_bytes());
255 bson_atoms_array(&mut buf, &names);
256 bson_doc_end(&mut buf, doc);
257 w.write_all(&buf)
258}
259
260pub fn write_atom_map_text<W: Write>(w: &mut W, m: &Machine) -> io::Result<()> {
262 for i in 0..m.atoms.len() {
263 let name = m.atoms.try_resolve(i as u32).unwrap_or_default();
264 writeln!(w, "{i}\t{name}")?;
265 }
266 Ok(())
267}
268
269fn bson_write_envelope(w: &mut dyn Write, _m: &Machine, e: &Envelope) -> io::Result<()> {
270 let mut buf = Vec::new();
271 let doc = bson_doc_begin(&mut buf);
272
273 buf.push(T_INT32);
274 bson_cstring(&mut buf, "count");
275 buf.extend_from_slice(&(e.count.min(i32::MAX as usize) as i32).to_le_bytes());
276
277 buf.push(T_BOOL);
278 bson_cstring(&mut buf, "exhausted");
279 buf.push(if e.exhausted { 0x01 } else { 0x00 });
280
281 if let Some(out) = e.program_output {
282 buf.push(T_STRING);
283 bson_cstring(&mut buf, "output");
284 let len = i32::try_from(out.len() + 1).expect("output string < 2GB");
285 buf.extend_from_slice(&len.to_le_bytes());
286 buf.extend_from_slice(out.as_bytes());
287 buf.push(0x00);
288 }
289
290 if let Some(names) = e.atoms {
294 bson_atoms_array(&mut buf, names);
295 }
296
297 buf.push(T_ARRAY);
298 bson_cstring(&mut buf, "solutions");
299 let arr = bson_doc_begin(&mut buf);
300 for (i, sol) in e.solutions.iter().enumerate() {
301 buf.push(T_DOCUMENT);
302 bson_cstring(&mut buf, &i.to_string());
303 let sdoc = bson_doc_begin(&mut buf);
304 for b in &sol.bindings {
305 let payload = serialize_termbuf(&b.buf);
308 buf.push(T_BINARY);
309 bson_cstring(&mut buf, &b.name);
310 let len = i32::try_from(payload.len()).expect("termbuf < 2GB");
311 buf.extend_from_slice(&len.to_le_bytes());
312 buf.push(0x00); buf.extend_from_slice(&payload);
314 }
315 bson_doc_end(&mut buf, sdoc);
316 }
317 bson_doc_end(&mut buf, arr);
318
319 bson_doc_end(&mut buf, doc);
320 w.write_all(&buf)
321}
322
323fn bson_write_error(w: &mut dyn Write, err: &WireError) -> io::Result<()> {
324 let msg = match err {
325 WireError::Parse(m) | WireError::Runtime(m) => m,
326 };
327 let mut buf = Vec::new();
328 let doc = bson_doc_begin(&mut buf);
329 buf.push(T_STRING);
330 bson_cstring(&mut buf, "error");
331 let len = i32::try_from(msg.len() + 1).expect("error message < 2GB");
332 buf.extend_from_slice(&len.to_le_bytes());
333 buf.extend_from_slice(msg.as_bytes());
334 buf.push(0x00);
335 bson_doc_end(&mut buf, doc);
336 w.write_all(&buf)
337}
338
339const fn bson_can_stream() -> bool {
340 false
341}
342
343#[unsafe(no_mangle)]
344pub static PLG_ENC_BSON: EncoderDesc = EncoderDesc {
345 name: "bson",
346 write_envelope: bson_write_envelope,
347 write_error: bson_write_error,
348 can_stream: bson_can_stream,
349};
350
351#[derive(Debug)]
363pub struct ParsedRequest {
364 pub query: String,
365 pub limit: Option<usize>,
366}
367
368pub fn parse_bson_request(buf: &[u8]) -> Result<ParsedRequest, String> {
373 if buf.len() < 5 {
374 return Err("bson request too short".to_string());
375 }
376 let total = i32::from_le_bytes(buf[0..4].try_into().unwrap()) as usize;
377 if total < 5 || total > buf.len() {
378 return Err(format!(
379 "bson request length mismatch: declared {total}, have {}",
380 buf.len()
381 ));
382 }
383 let body = &buf[..total];
384 let mut off = 4; let end = total - 1; let mut query = None;
387 let mut limit = None;
388 while off < end {
389 let ty = body[off];
390 off += 1;
391 let (key, after_key) = read_cstring(body, off)?;
392 off = after_key;
393 match (ty, key.as_str()) {
394 (T_STRING, "query") => {
395 let (s, next) = read_string(body, off)?;
396 query = Some(s);
397 off = next;
398 }
399 (T_INT32, "limit") => {
400 let n = read_i32(body, off)?;
401 limit = Some(n.max(0) as usize);
402 off += 4;
403 }
404 (T_INT64, "limit") => {
405 let n = read_i64(body, off)?;
406 limit = Some(n.max(0) as usize);
407 off += 8;
408 }
409 _ => {
410 off = skip_value(body, off, ty)?;
411 }
412 }
413 }
414 let query = query.ok_or_else(|| "bson request missing required 'query' string".to_string())?;
415 Ok(ParsedRequest { query, limit })
416}
417
418fn read_cstring(buf: &[u8], mut off: usize) -> Result<(String, usize), String> {
419 let end = buf[off..]
420 .iter()
421 .position(|&b| b == 0)
422 .ok_or_else(|| "bson key not null-terminated".to_string())?;
423 let s = std::str::from_utf8(&buf[off..off + end])
424 .map_err(|_| "bson key not utf-8".to_string())?
425 .to_string();
426 off += end + 1;
427 Ok((s, off))
428}
429
430fn read_string(buf: &[u8], off: usize) -> Result<(String, usize), String> {
431 let n = read_i32(buf, off)? as usize;
432 if n == 0 || off + 4 + n > buf.len() {
433 return Err("bson string length out of range".to_string());
434 }
435 let s = std::str::from_utf8(&buf[off + 4..off + 4 + n - 1])
436 .map_err(|_| "bson string not utf-8".to_string())?
437 .to_string();
438 Ok((s, off + 4 + n))
439}
440
441fn read_i32(buf: &[u8], off: usize) -> Result<i32, String> {
442 buf[off..]
443 .get(..4)
444 .map(|b| i32::from_le_bytes(b.try_into().unwrap()))
445 .ok_or_else(|| "bson int32 truncated".to_string())
446}
447
448fn read_i64(buf: &[u8], off: usize) -> Result<i64, String> {
449 buf[off..]
450 .get(..8)
451 .map(|b| i64::from_le_bytes(b.try_into().unwrap()))
452 .ok_or_else(|| "bson int64 truncated".to_string())
453}
454
455fn skip_value(buf: &[u8], off: usize, ty: u8) -> Result<usize, String> {
457 match ty {
458 0x01 => Ok(off + 8), T_STRING => Ok(read_string(buf, off)?.1), T_DOCUMENT | T_ARRAY => {
461 let n = read_i32(buf, off)? as usize;
462 Ok(off + n)
463 }
464 T_BINARY => {
465 let n = read_i32(buf, off)? as usize;
466 Ok(off + 4 + 1 + n)
467 }
468 T_BOOL => Ok(off + 1),
469 0x0A => Ok(off), T_INT32 => Ok(off + 4),
471 T_INT64 => Ok(off + 8),
472 _ => Err(format!("bson: cannot skip unknown element type {ty:#x}")),
473 }
474}
475
476#[cfg(test)]
477mod tests {
478 use super::*;
479 use crate::cell::{TAG_LST, TAG_STR, make, make_atom, make_int, pack_functor, payload, tag_of};
480 use plg_shared::StringInterner;
481 use plg_shared::atom::ATOM_NIL;
482
483 fn machine() -> Box<Machine> {
484 Machine::new(StringInterner::new(), Vec::new())
485 }
486
487 fn bytes(f: impl FnOnce(&mut Vec<u8>) -> io::Result<()>) -> Vec<u8> {
488 let mut buf = Vec::new();
489 f(&mut buf).unwrap();
490 buf
491 }
492
493 fn env_with(bindings: Vec<(&str, &str)>) -> Vec<RenderedSolution> {
496 bindings
497 .into_iter()
498 .map(|(n, t)| RenderedSolution {
499 bindings: vec![crate::render::Binding {
500 name: n.to_string(),
501 text: t.to_string(),
502 buf: TermBuf {
503 cells: Vec::new(),
504 root: make_atom(0),
505 },
506 }],
507 })
508 .collect()
509 }
510
511 #[test]
512 fn text_empty_is_false() {
513 let e = Envelope {
514 count: 0,
515 exhausted: true,
516 solutions: &[],
517 program_output: None,
518 atoms: None,
519 };
520 assert_eq!(
521 String::from_utf8(bytes(|w| (PLG_ENC_TEXT.write_envelope)(w, &machine(), &e))).unwrap(),
522 "false.\n"
523 );
524 }
525
526 #[test]
527 fn text_renders_bindings_and_true() {
528 let sols = env_with(vec![("X", "auth")]);
529 let empty_sols = vec![RenderedSolution { bindings: vec![] }];
530 let e1 = Envelope {
532 count: 1,
533 exhausted: false,
534 solutions: &sols,
535 program_output: None,
536 atoms: None,
537 };
538 assert_eq!(
539 String::from_utf8(bytes(|w| (PLG_ENC_TEXT.write_envelope)(w, &machine(), &e1)))
540 .unwrap(),
541 "X = auth\n"
542 );
543 let e2 = Envelope {
544 count: 1,
545 exhausted: true,
546 solutions: &empty_sols,
547 program_output: None,
548 atoms: None,
549 };
550 assert_eq!(
551 String::from_utf8(bytes(|w| (PLG_ENC_TEXT.write_envelope)(w, &machine(), &e2)))
552 .unwrap(),
553 "true.\n"
554 );
555 }
556
557 #[test]
558 fn descriptors_named_and_streaming() {
559 assert_eq!(PLG_ENC_TEXT.name, "text");
560 assert_eq!(PLG_ENC_BSON.name, "bson");
561 assert!((PLG_ENC_TEXT.can_stream)());
562 assert!(!(PLG_ENC_BSON.can_stream)());
563 }
564
565 #[test]
566 fn find_locates_advertised_encoders() {
567 let caps: [*const EncoderDesc; 2] = [&PLG_ENC_TEXT, &PLG_ENC_BSON];
568 assert_eq!(
569 unsafe { EncoderDesc::find(caps.as_ptr(), 2, "text") }
570 .unwrap()
571 .name,
572 "text"
573 );
574 assert_eq!(
575 unsafe { EncoderDesc::find(caps.as_ptr(), 2, "bson") }
576 .unwrap()
577 .name,
578 "bson"
579 );
580 assert!(unsafe { EncoderDesc::find(caps.as_ptr(), 2, "json") }.is_none());
581 }
582
583 #[test]
584 fn find_omitted_encoder_is_none() {
585 let caps: [*const EncoderDesc; 1] = [&PLG_ENC_TEXT];
586 assert!(unsafe { EncoderDesc::find(caps.as_ptr(), 1, "bson") }.is_none());
587 }
588
589 fn bson_doc_len(buf: &[u8]) -> i32 {
592 i32::from_le_bytes(buf[0..4].try_into().unwrap())
593 }
594
595 fn assert_valid_bson_doc(buf: &[u8]) {
596 assert_eq!(
597 bson_doc_len(buf) as usize,
598 buf.len(),
599 "bson doc self-delimits"
600 );
601 assert_eq!(
602 *buf.last().unwrap(),
603 0x00,
604 "bson doc ends in null terminator"
605 );
606 }
607
608 #[test]
609 fn bson_empty_envelope_self_delimits() {
610 let m = machine();
611 let e = Envelope {
612 count: 0,
613 exhausted: true,
614 solutions: &[],
615 program_output: None,
616 atoms: None,
617 };
618 let buf = bytes(|w| (PLG_ENC_BSON.write_envelope)(w, &m, &e));
619 assert_valid_bson_doc(&buf);
620 assert!(contains_key(&buf, b"count"));
621 assert!(contains_key(&buf, b"exhausted"));
622 assert!(contains_key(&buf, b"solutions"));
623 }
624
625 #[test]
626 fn bson_error_document_valid() {
627 let buf = bytes(|w| (PLG_ENC_BSON.write_error)(w, &WireError::Runtime("boom".into())));
628 assert_valid_bson_doc(&buf);
629 assert!(contains_key(&buf, b"error"));
630 }
631
632 fn contains_key(buf: &[u8], key: &[u8]) -> bool {
633 let mut needle = key.to_vec();
634 needle.push(0x00);
635 buf.windows(needle.len()).any(|w| w == needle.as_slice())
636 }
637
638 fn deserialize_termbuf(data: &[u8]) -> TermBuf {
641 assert_eq!(data[0], 0x01, "format version");
642 let n = u32::from_le_bytes(data[1..5].try_into().unwrap()) as usize;
643 let root = u64::from_le_bytes(data[5..13].try_into().unwrap());
644 let mut cells = Vec::with_capacity(n);
645 for i in 0..n {
646 let off = 13 + i * 8;
647 cells.push(u64::from_le_bytes(data[off..off + 8].try_into().unwrap()));
648 }
649 TermBuf { cells, root }
650 }
651
652 #[test]
653 fn termbuf_framing_roundtrips_scalar_and_cycle() {
654 let m = machine();
655 let a = make_atom(7);
656 let tb = crate::copyterm::copy_to_buf(&m, a);
657 assert!(tb.cells.is_empty());
658 let rt = deserialize_termbuf(&serialize_termbuf(&tb));
659 assert_eq!(rt.root, a);
660
661 let mut m = machine();
663 let x = m.new_var();
664 let s = {
665 let i = m.heap.len();
666 m.heap.push(pack_functor(3, 1));
667 m.heap.push(x);
668 make(TAG_STR, i as u64)
669 };
670 m.bind(payload(x) as usize, s);
671 let tb = crate::copyterm::copy_to_buf(&m, s);
672 let rt = deserialize_termbuf(&serialize_termbuf(&tb));
673 let restored = crate::copyterm::restore_from_buf(&mut m, &rt);
674 assert_eq!(tag_of(restored), TAG_STR);
675 let ri = payload(restored) as usize;
676 assert_eq!(
677 m.deref(m.heap[ri + 1]),
678 restored,
679 "f(X) arg is the term itself"
680 );
681 }
682
683 #[test]
689 fn bson_solutions_do_not_alias_across_heap_reuse() {
690 let mut m = machine();
692 let x = m.new_var();
693 m.query_vars.push(("X".to_string(), payload(x) as usize));
694 let build = |m: &mut Machine, a: i64, b: i64| {
695 let inner = {
696 let i = m.heap.len();
697 m.heap.push(make_int(b));
698 m.heap.push(make_atom(ATOM_NIL));
699 make(TAG_LST, i as u64)
700 };
701 let i = m.heap.len();
702 m.heap.push(make_int(a));
703 m.heap.push(inner);
704 make(TAG_LST, i as u64)
705 };
706 let mark = m.heap.len();
707 let l12 = build(&mut m, 1, 2);
708 m.bind(payload(x) as usize, l12);
709 let sol1 = crate::render::capture_solution(&m);
710
711 m.heap.truncate(mark);
714 m.heap[payload(x) as usize] = x; let l34 = build(&mut m, 3, 4);
716 m.bind(payload(x) as usize, l34);
717 let sol2 = crate::render::capture_solution(&m);
718
719 let sols = [sol1, sol2];
720 let e = Envelope {
721 count: 2,
722 exhausted: true,
723 solutions: &sols,
724 program_output: None,
725 atoms: None,
726 };
727 let buf = bytes(|w| (PLG_ENC_BSON.write_envelope)(w, &machine(), &e));
728
729 let mut payloads: Vec<Vec<u8>> = Vec::new();
732 for i in 0..buf.len().saturating_sub(8) {
733 if buf[i] == T_BINARY && buf[i + 1..].starts_with(b"X\0") {
734 let len = i32::from_le_bytes(buf[i + 3..i + 7].try_into().unwrap()) as usize;
735 payloads.push(buf[i + 8..i + 8 + len].to_vec());
736 }
737 }
738 assert_eq!(payloads.len(), 2, "one X binding per solution");
739
740 let mut m2 = machine();
741 let render = |m2: &mut Machine, p: &[u8]| {
742 let tb = deserialize_termbuf(p);
743 let w = crate::copyterm::restore_from_buf(m2, &tb);
744 crate::render::term_to_string(m2, w)
745 };
746 assert_eq!(render(&mut m2, &payloads[0]), "[1, 2]");
747 assert_eq!(render(&mut m2, &payloads[1]), "[3, 4]");
748 }
749
750 fn req_doc(fields: &[(u8, &str, &[u8])]) -> Vec<u8> {
753 let mut buf = Vec::new();
754 let start = bson_doc_begin(&mut buf);
755 for (ty, key, val) in fields {
756 buf.push(*ty);
757 bson_cstring(&mut buf, key);
758 buf.extend_from_slice(val);
759 }
760 bson_doc_end(&mut buf, start);
761 buf
762 }
763 fn bson_int32(n: i32) -> Vec<u8> {
764 n.to_le_bytes().to_vec()
765 }
766 fn bson_str(s: &str) -> Vec<u8> {
767 let mut v = (s.len() as i32 + 1).to_le_bytes().to_vec();
768 v.extend_from_slice(s.as_bytes());
769 v.push(0x00);
770 v
771 }
772
773 #[test]
774 fn parses_query_and_int32_limit() {
775 let doc = req_doc(&[
776 (T_STRING, "query", &bson_str("p(X)")),
777 (T_INT32, "limit", &bson_int32(5)),
778 ]);
779 let r = parse_bson_request(&doc).unwrap();
780 assert_eq!(r.query, "p(X)");
781 assert_eq!(r.limit, Some(5));
782 }
783
784 #[test]
785 fn ignores_unknown_fields() {
786 let doc = req_doc(&[
787 (T_STRING, "caller", &bson_str("x")),
788 (T_STRING, "query", &bson_str("ok")),
789 ]);
790 assert_eq!(parse_bson_request(&doc).unwrap().query, "ok");
791 }
792
793 #[test]
794 fn missing_query_is_an_error() {
795 let doc = req_doc(&[(T_INT32, "limit", &bson_int32(3))]);
796 assert!(
797 parse_bson_request(&doc)
798 .unwrap_err()
799 .contains("missing required 'query'")
800 );
801 }
802
803 #[test]
805 fn _keep_imports_used() {
806 let _ = make_int(1);
807 let _ = ATOM_NIL;
808 }
809}