Skip to main content

spvirit_codec/
spvirit_encode.rs

1//! PVA message encoding helpers.
2
3use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
4use spvirit_types::{NtPayload, NtScalar};
5use crate::spvd_decode::StructureDesc;
6use crate::spvd_encode::{
7    encode_nt_payload_bitset, encode_nt_payload_bitset_parts, encode_nt_payload_full,
8    encode_nt_scalar_bitset, encode_nt_scalar_full, encode_structure_desc,
9    nt_payload_desc,
10};
11
12pub fn encode_size_pva(size: usize, is_be: bool) -> Vec<u8> {
13    crate::encode_common::encode_size(size, is_be)
14}
15
16pub fn encode_string_pva(value: &str, is_be: bool) -> Vec<u8> {
17    crate::encode_common::encode_string(value, is_be)
18}
19
20fn encode_status_ok() -> Vec<u8> {
21    vec![0xFF]
22}
23
24fn encode_status_error(message: &str, is_be: bool) -> Vec<u8> {
25    let mut out = Vec::new();
26    out.push(0x02);
27    out.extend_from_slice(&encode_string_pva(message, is_be));
28    out.extend_from_slice(&encode_string_pva("", is_be));
29    out
30}
31
32pub fn encode_message_error(message: &str, version: u8, is_be: bool) -> Vec<u8> {
33    let payload = encode_status_error(message, is_be);
34    let mut out = encode_header(true, is_be, false, version, 18, payload.len() as u32);
35    out.extend_from_slice(&payload);
36    out
37}
38
39pub fn encode_header(
40    is_server: bool,
41    is_be: bool,
42    is_control: bool,
43    version: u8,
44    command: u8,
45    payload_length: u32,
46) -> Vec<u8> {
47    let magic = 0xCA;
48    let mut flags = 0u8;
49    if is_control {
50        flags |= 0x01;
51    }
52    if is_server {
53        flags |= 0x40;
54    }
55    if is_be {
56        flags |= 0x80;
57    }
58    let mut out = vec![magic, version, flags, command];
59    let len_bytes = if is_be {
60        payload_length.to_be_bytes()
61    } else {
62        payload_length.to_le_bytes()
63    };
64    out.extend_from_slice(&len_bytes);
65    out
66}
67
68pub fn encode_search_response(
69    guid: [u8; 12],
70    seq: u32,
71    addr: [u8; 16],
72    port: u16,
73    protocol: &str,
74    found: bool,
75    cids: &[u32],
76    version: u8,
77    is_be: bool,
78) -> Vec<u8> {
79    let mut payload = Vec::new();
80    payload.extend_from_slice(&guid);
81    payload.extend_from_slice(&if is_be {
82        seq.to_be_bytes()
83    } else {
84        seq.to_le_bytes()
85    });
86    payload.extend_from_slice(&addr);
87    payload.extend_from_slice(&if is_be {
88        port.to_be_bytes()
89    } else {
90        port.to_le_bytes()
91    });
92    payload.extend_from_slice(&encode_string_pva(protocol, is_be));
93    payload.push(if found { 1 } else { 0 });
94    let count = cids.len() as u16;
95    payload.extend_from_slice(&if is_be {
96        count.to_be_bytes()
97    } else {
98        count.to_le_bytes()
99    });
100    for cid in cids {
101        payload.extend_from_slice(&if is_be {
102            cid.to_be_bytes()
103        } else {
104            cid.to_le_bytes()
105        });
106    }
107
108    let mut out = encode_header(true, is_be, false, version, 4, payload.len() as u32);
109    out.extend_from_slice(&payload);
110    out
111}
112
113pub fn encode_connection_validated(is_server: bool, version: u8, is_be: bool) -> Vec<u8> {
114    let payload = encode_status_ok();
115    let mut out = encode_header(is_server, is_be, false, version, 9, payload.len() as u32);
116    out.extend_from_slice(&payload);
117    out
118}
119
120pub fn encode_control_message(
121    is_server: bool,
122    is_be: bool,
123    version: u8,
124    command: u8,
125    data: u32,
126) -> Vec<u8> {
127    // Control messages: header only; size field carries data.
128    encode_header(is_server, is_be, true, version, command, data)
129}
130
131pub fn encode_connection_validation(
132    buffer_size: u32,
133    introspection_registry_size: u16,
134    qos: u16,
135    authz_name: &str,
136    version: u8,
137    is_be: bool,
138) -> Vec<u8> {
139    let mut payload = Vec::new();
140    payload.extend_from_slice(&if is_be {
141        buffer_size.to_be_bytes()
142    } else {
143        buffer_size.to_le_bytes()
144    });
145    payload.extend_from_slice(&if is_be {
146        introspection_registry_size.to_be_bytes()
147    } else {
148        introspection_registry_size.to_le_bytes()
149    });
150    payload.extend_from_slice(&if is_be {
151        qos.to_be_bytes()
152    } else {
153        qos.to_le_bytes()
154    });
155    payload.extend_from_slice(&encode_string_pva(authz_name, is_be));
156    let mut out = encode_header(true, is_be, false, version, 1, payload.len() as u32);
157    out.extend_from_slice(&payload);
158    out
159}
160
161pub fn encode_authnz_user_host(user: &str, host: &str, is_be: bool) -> Vec<u8> {
162    let mut out = Vec::new();
163    out.extend_from_slice(&[0xFD]);
164    if is_be {
165        out.extend_from_slice(&1u16.to_be_bytes());
166    } else {
167        out.extend_from_slice(&1u16.to_le_bytes());
168    }
169    out.extend_from_slice(&[0x80, 0x00]);
170    out.push(0x02);
171    out.push(0x04);
172    out.extend_from_slice(b"user");
173    out.push(0x60);
174    out.push(0x04);
175    out.extend_from_slice(b"host");
176    out.push(0x60);
177    out.extend_from_slice(&encode_string_pva(user, is_be));
178    out.extend_from_slice(&encode_string_pva(host, is_be));
179    out
180}
181
182pub fn encode_client_connection_validation(
183    buffer_size: u32,
184    introspection_registry_size: u16,
185    qos: u16,
186    authz: &str,
187    user: &str,
188    host: &str,
189    version: u8,
190    is_be: bool,
191) -> Vec<u8> {
192    let mut payload = Vec::new();
193    payload.extend_from_slice(&if is_be {
194        buffer_size.to_be_bytes()
195    } else {
196        buffer_size.to_le_bytes()
197    });
198    payload.extend_from_slice(&if is_be {
199        introspection_registry_size.to_be_bytes()
200    } else {
201        introspection_registry_size.to_le_bytes()
202    });
203    payload.extend_from_slice(&if is_be {
204        qos.to_be_bytes()
205    } else {
206        qos.to_le_bytes()
207    });
208    payload.extend_from_slice(&encode_string_pva(authz, is_be));
209    payload.extend_from_slice(&encode_authnz_user_host(user, host, is_be));
210    let mut out = encode_header(false, is_be, false, version, 1, payload.len() as u32);
211    out.extend_from_slice(&payload);
212    out
213}
214
215pub fn encode_create_channel_request(cid: u32, pv_name: &str, version: u8, is_be: bool) -> Vec<u8> {
216    let mut payload = Vec::new();
217    payload.extend_from_slice(&if is_be {
218        1u16.to_be_bytes()
219    } else {
220        1u16.to_le_bytes()
221    });
222    payload.extend_from_slice(&if is_be {
223        cid.to_be_bytes()
224    } else {
225        cid.to_le_bytes()
226    });
227    payload.extend_from_slice(&encode_string_pva(pv_name, is_be));
228    let mut out = encode_header(false, is_be, false, version, 7, payload.len() as u32);
229    out.extend_from_slice(&payload);
230    out
231}
232
233pub fn encode_get_field_request(
234    sid: u32,
235    ioid: u32,
236    sub_field: Option<&str>,
237    version: u8,
238    is_be: bool,
239) -> Vec<u8> {
240    let mut payload = Vec::new();
241    payload.extend_from_slice(&if is_be {
242        sid.to_be_bytes()
243    } else {
244        sid.to_le_bytes()
245    });
246    payload.extend_from_slice(&if is_be {
247        ioid.to_be_bytes()
248    } else {
249        ioid.to_le_bytes()
250    });
251    payload.extend_from_slice(&encode_string_pva(sub_field.unwrap_or(""), is_be));
252    let mut out = encode_header(false, is_be, false, version, 17, payload.len() as u32);
253    out.extend_from_slice(&payload);
254    out
255}
256
257pub fn encode_op_request(
258    command: u8,
259    sid: u32,
260    ioid: u32,
261    subcmd: u8,
262    extra: &[u8],
263    version: u8,
264    is_be: bool,
265) -> Vec<u8> {
266    let mut payload = Vec::new();
267    payload.extend_from_slice(&if is_be {
268        sid.to_be_bytes()
269    } else {
270        sid.to_le_bytes()
271    });
272    payload.extend_from_slice(&if is_be {
273        ioid.to_be_bytes()
274    } else {
275        ioid.to_le_bytes()
276    });
277    payload.push(subcmd);
278    payload.extend_from_slice(extra);
279    let mut out = encode_header(false, is_be, false, version, command, payload.len() as u32);
280    out.extend_from_slice(&payload);
281    out
282}
283
284pub fn encode_get_request(
285    sid: u32,
286    ioid: u32,
287    subcmd: u8,
288    extra: &[u8],
289    version: u8,
290    is_be: bool,
291) -> Vec<u8> {
292    encode_op_request(10, sid, ioid, subcmd, extra, version, is_be)
293}
294
295pub fn encode_put_request(
296    sid: u32,
297    ioid: u32,
298    subcmd: u8,
299    extra: &[u8],
300    version: u8,
301    is_be: bool,
302) -> Vec<u8> {
303    encode_op_request(11, sid, ioid, subcmd, extra, version, is_be)
304}
305
306pub fn encode_monitor_request(
307    sid: u32,
308    ioid: u32,
309    subcmd: u8,
310    extra: &[u8],
311    version: u8,
312    is_be: bool,
313) -> Vec<u8> {
314    encode_op_request(13, sid, ioid, subcmd, extra, version, is_be)
315}
316
317pub fn encode_rpc_request(
318    sid: u32,
319    ioid: u32,
320    subcmd: u8,
321    extra: &[u8],
322    version: u8,
323    is_be: bool,
324) -> Vec<u8> {
325    encode_op_request(20, sid, ioid, subcmd, extra, version, is_be)
326}
327
328pub fn encode_search_request(
329    seq: u32,
330    flags: u8,
331    port: u16,
332    reply_addr: [u8; 16],
333    pv_requests: &[(u32, &str)],
334    version: u8,
335    is_be: bool,
336) -> Vec<u8> {
337    let mut payload = Vec::new();
338    payload.extend_from_slice(&if is_be {
339        seq.to_be_bytes()
340    } else {
341        seq.to_le_bytes()
342    });
343    payload.push(flags);
344    payload.extend_from_slice(&[0u8; 3]);
345    payload.extend_from_slice(&reply_addr);
346    payload.extend_from_slice(&if is_be {
347        port.to_be_bytes()
348    } else {
349        port.to_le_bytes()
350    });
351    payload.extend_from_slice(&encode_size_pva(1, is_be));
352    payload.extend_from_slice(&encode_string_pva("tcp", is_be));
353    payload.extend_from_slice(&if is_be {
354        (pv_requests.len() as u16).to_be_bytes()
355    } else {
356        (pv_requests.len() as u16).to_le_bytes()
357    });
358    for (cid, pv_name) in pv_requests {
359        payload.extend_from_slice(&if is_be {
360            cid.to_be_bytes()
361        } else {
362            cid.to_le_bytes()
363        });
364        payload.extend_from_slice(&encode_string_pva(pv_name, is_be));
365    }
366
367    let mut out = encode_header(false, is_be, false, version, 3, payload.len() as u32);
368    out.extend_from_slice(&payload);
369    out
370}
371
372pub fn encode_create_channel_response(cid: u32, sid: u32, version: u8, is_be: bool) -> Vec<u8> {
373    let mut payload = Vec::new();
374    payload.extend_from_slice(&if is_be {
375        cid.to_be_bytes()
376    } else {
377        cid.to_le_bytes()
378    });
379    payload.extend_from_slice(&if is_be {
380        sid.to_be_bytes()
381    } else {
382        sid.to_le_bytes()
383    });
384    payload.extend_from_slice(&encode_status_ok());
385    let mut out = encode_header(true, is_be, false, version, 7, payload.len() as u32);
386    out.extend_from_slice(&payload);
387    out
388}
389
390pub fn encode_create_channel_error(cid: u32, message: &str, version: u8, is_be: bool) -> Vec<u8> {
391    let mut payload = Vec::new();
392    payload.extend_from_slice(&if is_be {
393        cid.to_be_bytes()
394    } else {
395        cid.to_le_bytes()
396    });
397    payload.extend_from_slice(&if is_be {
398        0u32.to_be_bytes()
399    } else {
400        0u32.to_le_bytes()
401    });
402    payload.push(0x01);
403    payload.extend_from_slice(&encode_string_pva(message, is_be));
404    payload.extend_from_slice(&encode_string_pva("", is_be));
405    let mut out = encode_header(true, is_be, false, version, 7, payload.len() as u32);
406    out.extend_from_slice(&payload);
407    out
408}
409
410pub fn encode_get_field_response(
411    request_id: u32,
412    desc: &StructureDesc,
413    version: u8,
414    is_be: bool,
415) -> Vec<u8> {
416    let mut payload = Vec::new();
417    payload.extend_from_slice(&if is_be {
418        request_id.to_be_bytes()
419    } else {
420        request_id.to_le_bytes()
421    });
422    payload.extend_from_slice(&encode_status_ok());
423    payload.push(0x80);
424    payload.extend_from_slice(&encode_structure_desc(desc, is_be));
425    let mut out = encode_header(true, is_be, false, version, 17, payload.len() as u32);
426    out.extend_from_slice(&payload);
427    out
428}
429
430pub fn encode_get_field_error(request_id: u32, message: &str, version: u8, is_be: bool) -> Vec<u8> {
431    let mut payload = Vec::new();
432    payload.extend_from_slice(&if is_be {
433        request_id.to_be_bytes()
434    } else {
435        request_id.to_le_bytes()
436    });
437    payload.extend_from_slice(&encode_status_error(message, is_be));
438    let mut out = encode_header(true, is_be, false, version, 17, payload.len() as u32);
439    out.extend_from_slice(&payload);
440    out
441}
442
443pub fn encode_op_init_response(
444    command: u8,
445    ioid: u32,
446    subcmd: u8,
447    desc: &StructureDesc,
448    nt: &NtScalar,
449    version: u8,
450    is_be: bool,
451) -> Vec<u8> {
452    let mut payload = Vec::new();
453    payload.extend_from_slice(&if is_be {
454        ioid.to_be_bytes()
455    } else {
456        ioid.to_le_bytes()
457    });
458    payload.push(subcmd);
459    payload.extend_from_slice(&encode_status_ok());
460    payload.push(0x80); // structure type for introspection
461    payload.extend_from_slice(&encode_structure_desc(desc, is_be));
462    payload.extend_from_slice(&encode_nt_scalar_full(nt, is_be));
463
464    let mut out = encode_header(true, is_be, false, version, command, payload.len() as u32);
465    out.extend_from_slice(&payload);
466    out
467}
468
469pub fn encode_op_init_response_desc(
470    command: u8,
471    ioid: u32,
472    subcmd: u8,
473    desc: &StructureDesc,
474    version: u8,
475    is_be: bool,
476) -> Vec<u8> {
477    let mut payload = Vec::new();
478    payload.extend_from_slice(&if is_be {
479        ioid.to_be_bytes()
480    } else {
481        ioid.to_le_bytes()
482    });
483    payload.push(subcmd);
484    payload.extend_from_slice(&encode_status_ok());
485    payload.push(0x80); // structure type for introspection
486    payload.extend_from_slice(&encode_structure_desc(desc, is_be));
487
488    let mut out = encode_header(true, is_be, false, version, command, payload.len() as u32);
489    out.extend_from_slice(&payload);
490    out
491}
492
493pub fn encode_op_data_response(
494    command: u8,
495    ioid: u32,
496    nt: &NtScalar,
497    version: u8,
498    is_be: bool,
499) -> Vec<u8> {
500    let mut payload = Vec::new();
501    payload.extend_from_slice(&if is_be {
502        ioid.to_be_bytes()
503    } else {
504        ioid.to_le_bytes()
505    });
506    payload.push(0x00);
507    payload.extend_from_slice(&encode_nt_scalar_bitset(nt, is_be));
508    let mut out = encode_header(true, is_be, false, version, command, payload.len() as u32);
509    out.extend_from_slice(&payload);
510    out
511}
512
513pub fn encode_op_get_data_response_payload(
514    ioid: u32,
515    payload_value: &NtPayload,
516    version: u8,
517    is_be: bool,
518) -> Vec<u8> {
519    encode_op_data_response_payload(10, ioid, payload_value, version, is_be)
520}
521
522pub fn encode_op_data_response_payload(
523    command: u8,
524    ioid: u32,
525    payload_value: &NtPayload,
526    version: u8,
527    is_be: bool,
528) -> Vec<u8> {
529    let mut payload = Vec::new();
530    payload.extend_from_slice(&if is_be {
531        ioid.to_be_bytes()
532    } else {
533        ioid.to_le_bytes()
534    });
535    payload.push(0x00);
536    payload.extend_from_slice(&encode_status_ok());
537    payload.extend_from_slice(&encode_nt_payload_bitset(payload_value, is_be));
538    let mut out = encode_header(true, is_be, false, version, command, payload.len() as u32);
539    out.extend_from_slice(&payload);
540    out
541}
542
543pub fn encode_op_status_response(
544    command: u8,
545    ioid: u32,
546    subcmd: u8,
547    version: u8,
548    is_be: bool,
549) -> Vec<u8> {
550    let mut payload = Vec::new();
551    payload.extend_from_slice(&if is_be {
552        ioid.to_be_bytes()
553    } else {
554        ioid.to_le_bytes()
555    });
556    payload.push(subcmd);
557    payload.extend_from_slice(&encode_status_ok());
558    let mut out = encode_header(true, is_be, false, version, command, payload.len() as u32);
559    out.extend_from_slice(&payload);
560    out
561}
562
563pub fn encode_op_status_error_response(
564    command: u8,
565    ioid: u32,
566    subcmd: u8,
567    message: &str,
568    version: u8,
569    is_be: bool,
570) -> Vec<u8> {
571    let mut payload = Vec::new();
572    payload.extend_from_slice(&if is_be {
573        ioid.to_be_bytes()
574    } else {
575        ioid.to_le_bytes()
576    });
577    payload.push(subcmd);
578    payload.extend_from_slice(&encode_status_error(message, is_be));
579    let mut out = encode_header(true, is_be, false, version, command, payload.len() as u32);
580    out.extend_from_slice(&payload);
581    out
582}
583
584pub fn encode_op_rpc_data_response_payload(
585    ioid: u32,
586    subcmd: u8,
587    payload_value: &NtPayload,
588    version: u8,
589    is_be: bool,
590) -> Vec<u8> {
591    let desc = nt_payload_desc(payload_value);
592    let mut payload = Vec::new();
593    payload.extend_from_slice(&if is_be {
594        ioid.to_be_bytes()
595    } else {
596        ioid.to_le_bytes()
597    });
598    payload.push(subcmd);
599    payload.extend_from_slice(&encode_status_ok());
600    payload.push(0x80);
601    payload.extend_from_slice(&encode_structure_desc(&desc, is_be));
602    payload.extend_from_slice(&encode_nt_payload_full(payload_value, is_be));
603    let mut out = encode_header(true, is_be, false, version, 20, payload.len() as u32);
604    out.extend_from_slice(&payload);
605    out
606}
607
608pub fn encode_op_put_get_init_response(
609    ioid: u32,
610    put_desc: &StructureDesc,
611    get_desc: &StructureDesc,
612    version: u8,
613    is_be: bool,
614) -> Vec<u8> {
615    let mut payload = Vec::new();
616    payload.extend_from_slice(&if is_be {
617        ioid.to_be_bytes()
618    } else {
619        ioid.to_le_bytes()
620    });
621    payload.push(0x08);
622    payload.extend_from_slice(&encode_status_ok());
623    payload.push(0x80);
624    payload.extend_from_slice(&encode_structure_desc(put_desc, is_be));
625    payload.push(0x80);
626    payload.extend_from_slice(&encode_structure_desc(get_desc, is_be));
627    let mut out = encode_header(true, is_be, false, version, 12, payload.len() as u32);
628    out.extend_from_slice(&payload);
629    out
630}
631
632pub fn encode_op_put_get_data_response(
633    ioid: u32,
634    nt: &NtScalar,
635    version: u8,
636    is_be: bool,
637) -> Vec<u8> {
638    encode_op_put_get_data_response_payload(ioid, &NtPayload::Scalar(nt.clone()), version, is_be)
639}
640
641pub fn encode_op_put_get_data_response_payload(
642    ioid: u32,
643    payload_value: &NtPayload,
644    version: u8,
645    is_be: bool,
646) -> Vec<u8> {
647    let mut payload = Vec::new();
648    payload.extend_from_slice(&if is_be {
649        ioid.to_be_bytes()
650    } else {
651        ioid.to_le_bytes()
652    });
653    payload.push(0x00);
654    payload.extend_from_slice(&encode_status_ok());
655    payload.extend_from_slice(&encode_nt_payload_bitset(payload_value, is_be));
656    let mut out = encode_header(true, is_be, false, version, 12, payload.len() as u32);
657    out.extend_from_slice(&payload);
658    out
659}
660
661pub fn encode_op_put_response(ioid: u32, version: u8, is_be: bool) -> Vec<u8> {
662    let mut payload = Vec::new();
663    payload.extend_from_slice(&if is_be {
664        ioid.to_be_bytes()
665    } else {
666        ioid.to_le_bytes()
667    });
668    payload.push(0x00);
669    payload.extend_from_slice(&encode_status_ok());
670    let mut out = encode_header(true, is_be, false, version, 11, payload.len() as u32);
671    out.extend_from_slice(&payload);
672    out
673}
674
675pub fn encode_op_put_status_response(
676    ioid: u32,
677    subcmd: u8,
678    message: &str,
679    version: u8,
680    is_be: bool,
681) -> Vec<u8> {
682    let mut payload = Vec::new();
683    payload.extend_from_slice(&if is_be {
684        ioid.to_be_bytes()
685    } else {
686        ioid.to_le_bytes()
687    });
688    payload.push(subcmd);
689    payload.extend_from_slice(&encode_status_error(message, is_be));
690    let mut out = encode_header(true, is_be, false, version, 11, payload.len() as u32);
691    out.extend_from_slice(&payload);
692    out
693}
694
695pub fn encode_op_put_getput_response(
696    ioid: u32,
697    nt: &NtScalar,
698    version: u8,
699    is_be: bool,
700) -> Vec<u8> {
701    encode_op_put_getput_response_payload(ioid, &NtPayload::Scalar(nt.clone()), version, is_be)
702}
703
704pub fn encode_op_put_getput_response_payload(
705    ioid: u32,
706    payload_value: &NtPayload,
707    version: u8,
708    is_be: bool,
709) -> Vec<u8> {
710    let mut payload = Vec::new();
711    payload.extend_from_slice(&if is_be {
712        ioid.to_be_bytes()
713    } else {
714        ioid.to_le_bytes()
715    });
716    payload.push(0x40);
717    payload.extend_from_slice(&encode_status_ok());
718    payload.extend_from_slice(&encode_nt_payload_bitset(payload_value, is_be));
719    let mut out = encode_header(true, is_be, false, version, 11, payload.len() as u32);
720    out.extend_from_slice(&payload);
721    out
722}
723
724pub fn encode_op_put_get_init_error_response(
725    ioid: u32,
726    message: &str,
727    version: u8,
728    is_be: bool,
729) -> Vec<u8> {
730    let mut payload = Vec::new();
731    payload.extend_from_slice(&if is_be {
732        ioid.to_be_bytes()
733    } else {
734        ioid.to_le_bytes()
735    });
736    payload.push(0x08);
737    payload.extend_from_slice(&encode_status_error(message, is_be));
738    let mut out = encode_header(true, is_be, false, version, 12, payload.len() as u32);
739    out.extend_from_slice(&payload);
740    out
741}
742
743pub fn encode_op_put_get_data_error_response(
744    ioid: u32,
745    message: &str,
746    version: u8,
747    is_be: bool,
748) -> Vec<u8> {
749    let mut payload = Vec::new();
750    payload.extend_from_slice(&if is_be {
751        ioid.to_be_bytes()
752    } else {
753        ioid.to_le_bytes()
754    });
755    payload.push(0x00);
756    payload.extend_from_slice(&encode_status_error(message, is_be));
757    let mut out = encode_header(true, is_be, false, version, 12, payload.len() as u32);
758    out.extend_from_slice(&payload);
759    out
760}
761
762pub fn encode_monitor_data_response(
763    ioid: u32,
764    subcmd: u8,
765    nt: &NtScalar,
766    version: u8,
767    is_be: bool,
768) -> Vec<u8> {
769    encode_monitor_data_response_payload(
770        ioid,
771        subcmd,
772        &NtPayload::Scalar(nt.clone()),
773        version,
774        is_be,
775    )
776}
777
778pub fn encode_monitor_data_response_payload(
779    ioid: u32,
780    subcmd: u8,
781    payload_value: &NtPayload,
782    version: u8,
783    is_be: bool,
784) -> Vec<u8> {
785    let (changed_bitset, values) = encode_nt_payload_bitset_parts(payload_value, is_be);
786    let mut payload = Vec::new();
787    payload.extend_from_slice(&if is_be {
788        ioid.to_be_bytes()
789    } else {
790        ioid.to_le_bytes()
791    });
792    payload.push(subcmd);
793    if (subcmd & 0x10) != 0 {
794        payload.extend_from_slice(&encode_status_ok());
795    }
796    payload.extend_from_slice(&changed_bitset);
797    payload.extend_from_slice(&values);
798    // overrun bitset: empty (after data per spec)
799    payload.extend_from_slice(&encode_size_pva(0, is_be));
800    let mut out = encode_header(true, is_be, false, version, 13, payload.len() as u32);
801    out.extend_from_slice(&payload);
802    out
803}
804
805pub fn encode_destroy_channel_response(sid: u32, cid: u32, version: u8, is_be: bool) -> Vec<u8> {
806    let mut payload = Vec::new();
807    payload.extend_from_slice(&if is_be {
808        sid.to_be_bytes()
809    } else {
810        sid.to_le_bytes()
811    });
812    payload.extend_from_slice(&if is_be {
813        cid.to_be_bytes()
814    } else {
815        cid.to_le_bytes()
816    });
817    let mut out = encode_header(true, is_be, false, version, 8, payload.len() as u32);
818    out.extend_from_slice(&payload);
819    out
820}
821
822pub fn encode_op_error(command: u8, ioid: u32, message: &str, version: u8, is_be: bool) -> Vec<u8> {
823    let mut payload = Vec::new();
824    payload.extend_from_slice(&if is_be {
825        ioid.to_be_bytes()
826    } else {
827        ioid.to_le_bytes()
828    });
829    payload.push(0x08);
830    payload.push(0x01); // error
831    payload.extend_from_slice(&encode_string_pva(message, is_be));
832    payload.extend_from_slice(&encode_string_pva("", is_be));
833    let mut out = encode_header(true, is_be, false, version, command, payload.len() as u32);
834    out.extend_from_slice(&payload);
835    out
836}
837
838pub fn encode_beacon(
839    guid: [u8; 12],
840    seq: u8,
841    change_count: u16,
842    addr: [u8; 16],
843    port: u16,
844    protocol: &str,
845    version: u8,
846    is_be: bool,
847) -> Vec<u8> {
848    let mut payload = Vec::new();
849    payload.extend_from_slice(&guid);
850    payload.push(0x00); // flags
851    payload.push(seq);
852    payload.extend_from_slice(&if is_be {
853        change_count.to_be_bytes()
854    } else {
855        change_count.to_le_bytes()
856    });
857    payload.extend_from_slice(&addr);
858    payload.extend_from_slice(&if is_be {
859        port.to_be_bytes()
860    } else {
861        port.to_le_bytes()
862    });
863    payload.extend_from_slice(&encode_string_pva(protocol, is_be));
864    // serverStatus: NULL FieldDesc (0xFF) means "no server status".
865    // Writing a PVA string here instead would be misinterpreted as a TypeCode
866    // by compliant clients (e.g. Phoebus), causing a BufferUnderflowException.
867    payload.push(0xFF);
868    let mut out = encode_header(true, is_be, false, version, 0, payload.len() as u32);
869    out.extend_from_slice(&payload);
870    out
871}
872
873// ---------------------------------------------------------------------------
874// IP address ↔ 16-byte PVA wire-format conversion helpers
875// ---------------------------------------------------------------------------
876
877/// Convert an [`IpAddr`] to the 16-byte PVA wire representation.
878///
879/// IPv4 addresses are stored as IPv4-mapped IPv6 (`::ffff:a.b.c.d`).
880/// Native IPv6 addresses are stored as-is.
881pub fn ip_to_bytes(ip: IpAddr) -> [u8; 16] {
882    match ip {
883        IpAddr::V4(v4) => {
884            let mut out = [0u8; 16];
885            out[10] = 0xFF;
886            out[11] = 0xFF;
887            out[12..16].copy_from_slice(&v4.octets());
888            out
889        }
890        IpAddr::V6(v6) => v6.octets(),
891    }
892}
893
894/// Decode a 16-byte PVA address field to an [`IpAddr`].
895///
896/// Returns `None` for all-zeros (unspecified).
897/// IPv4-mapped addresses (`::ffff:a.b.c.d`) are returned as [`IpAddr::V4`].
898pub fn ip_from_bytes(addr: &[u8; 16]) -> Option<IpAddr> {
899    if addr.iter().all(|&b| b == 0) {
900        return None;
901    }
902    // IPv4-mapped IPv6 address ::ffff:a.b.c.d
903    if addr[0..10].iter().all(|&b| b == 0) && addr[10] == 0xFF && addr[11] == 0xFF {
904        return Some(IpAddr::V4(Ipv4Addr::new(
905            addr[12], addr[13], addr[14], addr[15],
906        )));
907    }
908    Some(IpAddr::V6(Ipv6Addr::from(*addr)))
909}
910
911pub fn socket_addr_from_pva_bytes(addr: [u8; 16], port: u16) -> Option<SocketAddr> {
912    ip_from_bytes(&addr).map(|ip| SocketAddr::new(ip, port))
913}
914
915/// Format a 16-byte PVA address field as a human-readable IP string.
916///
917/// All-zeros → `"0.0.0.0"`, IPv4-mapped → dotted-quad, otherwise IPv6 notation.
918pub fn format_pva_address(addr: &[u8; 16]) -> String {
919    match ip_from_bytes(addr) {
920        Some(ip) => ip.to_string(),
921        None => "0.0.0.0".to_string(),
922    }
923}
924
925#[cfg(test)]
926mod tests {
927    use super::*;
928    use crate::epics_decode::{PvaPacket, PvaPacketCommand};
929
930    #[test]
931    fn encode_decode_connection_validation_roundtrip() {
932        let msg = encode_connection_validation(4096, 2, 0x10, "test", 2, true);
933        let mut pkt = PvaPacket::new(&msg);
934        let cmd = pkt.decode_payload().expect("decoded");
935        match cmd {
936            PvaPacketCommand::ConnectionValidation(payload) => {
937                assert_eq!(payload.buffer_size, 4096);
938                assert_eq!(payload.introspection_registry_size, 2);
939                assert_eq!(payload.qos, 0x10);
940                assert_eq!(payload.authz.as_deref(), Some("test"));
941            }
942            other => panic!("unexpected decode: {:?}", other),
943        }
944    }
945
946    #[test]
947    fn encode_decode_client_connection_validation_roundtrip() {
948        let msg =
949            encode_client_connection_validation(87_040, 32_767, 0, "ca", "alice", "host1", 2, false);
950        let mut pkt = PvaPacket::new(&msg);
951        let cmd = pkt.decode_payload().expect("decoded");
952        match cmd {
953            PvaPacketCommand::ConnectionValidation(payload) => {
954                assert!(!payload.is_server);
955                assert_eq!(payload.buffer_size, 87_040);
956                assert_eq!(payload.introspection_registry_size, 32_767);
957                assert_eq!(payload.qos, 0);
958            }
959            other => panic!("unexpected decode: {:?}", other),
960        }
961    }
962
963    #[test]
964    fn encode_decode_search_response_roundtrip() {
965        let guid = [1u8; 12];
966        let seq = 42;
967        let addr = [0u8; 16];
968        let port = 5075;
969        let cids = vec![100u32, 101u32];
970        let msg = encode_search_response(guid, seq, addr, port, "tcp", true, &cids, 2, false);
971        let mut pkt = PvaPacket::new(&msg);
972        let cmd = pkt.decode_payload().expect("decoded");
973        match cmd {
974            PvaPacketCommand::SearchResponse(payload) => {
975                assert_eq!(payload.guid, guid);
976                assert_eq!(payload.seq, seq);
977                assert_eq!(payload.port, port);
978                assert!(payload.found);
979                assert_eq!(payload.cids, cids);
980            }
981            other => panic!("unexpected decode: {:?}", other),
982        }
983    }
984
985    #[test]
986    fn encode_decode_connection_validated_roundtrip() {
987        let msg = encode_connection_validated(true, 2, false);
988        let mut pkt = PvaPacket::new(&msg);
989        let cmd = pkt.decode_payload().expect("decoded");
990        match cmd {
991            PvaPacketCommand::ConnectionValidated(payload) => {
992                // 0xFF means "OK" which decodes to None in our decoder.
993                assert!(payload.status.is_none());
994            }
995            other => panic!("unexpected decode: {:?}", other),
996        }
997    }
998
999    #[test]
1000    fn get_data_response_includes_status() {
1001        let nt = NtScalar::from_value(spvirit_types::ScalarValue::F64(1.0));
1002        let msg = encode_op_get_data_response_payload(
1003            0x11223344,
1004            &NtPayload::Scalar(nt),
1005            2,
1006            false,
1007        );
1008        assert!(msg.len() > 13);
1009        let status_offset = 8 + 4 + 1;
1010        assert_eq!(msg[status_offset], 0xFF);
1011
1012        let mut pkt = PvaPacket::new(&msg);
1013        let cmd = pkt.decode_payload().expect("decoded");
1014        match cmd {
1015            PvaPacketCommand::Op(op) => {
1016                assert_eq!(op.command, 10);
1017                assert_eq!(op.subcmd, 0x00);
1018                assert!(!op.body.is_empty());
1019            }
1020            other => panic!("unexpected decode: {:?}", other),
1021        }
1022    }
1023
1024    #[test]
1025    fn put_get_init_includes_two_descriptors() {
1026        let nt = NtScalar::from_value(spvirit_types::ScalarValue::F64(1.0));
1027        let desc = crate::spvd_encode::nt_scalar_desc(&nt.value);
1028        let msg = encode_op_put_get_init_response(0x01020304, &desc, &desc, 2, false);
1029
1030        let payload = &msg[8..];
1031        assert!(payload.len() > 6);
1032        // ioid(4) + subcmd(1) + status(1)
1033        assert_eq!(payload[5], 0xFF);
1034        let rest = &payload[6..];
1035        let first = rest.first().copied().unwrap_or(0);
1036        assert_eq!(first, 0x80);
1037        let second_pos = rest.iter().skip(1).position(|b| *b == 0x80);
1038        assert!(second_pos.is_some(), "expected second descriptor marker");
1039    }
1040
1041    #[test]
1042    fn put_get_data_includes_status() {
1043        let nt = NtScalar::from_value(spvirit_types::ScalarValue::F64(2.0));
1044        let msg = encode_op_put_get_data_response(0x55667788, &nt, 2, false);
1045        assert!(msg.len() > 13);
1046        let status_offset = 8 + 4 + 1;
1047        assert_eq!(msg[status_offset], 0xFF);
1048    }
1049
1050    #[test]
1051    fn put_getput_response_encodes_subcmd_0x40() {
1052        let nt = NtScalar::from_value(spvirit_types::ScalarValue::F64(2.0));
1053        let msg = encode_op_put_getput_response(0x01020304, &nt, 2, false);
1054        assert!(msg.len() > 13);
1055        let status_offset = 8 + 4 + 1;
1056        assert_eq!(msg[status_offset], 0xFF);
1057        let mut pkt = PvaPacket::new(&msg);
1058        let cmd = pkt.decode_payload().expect("decoded");
1059        match cmd {
1060            PvaPacketCommand::Op(op) => {
1061                assert_eq!(op.command, 11);
1062                assert_eq!(op.subcmd, 0x40);
1063            }
1064            other => panic!("unexpected decode: {:?}", other),
1065        }
1066    }
1067
1068    #[test]
1069    fn encode_get_field_response_roundtrip() {
1070        let desc = StructureDesc {
1071            struct_id: Some("epics:nt/NTScalar:1.0".to_string()),
1072            fields: vec![crate::spvd_decode::FieldDesc {
1073                name: "value".to_string(),
1074                field_type: crate::spvd_decode::FieldType::Scalar(
1075                    crate::spvd_decode::TypeCode::String,
1076                ),
1077            }],
1078        };
1079        let msg = encode_get_field_response(11, &desc, 2, false);
1080        let mut pkt = PvaPacket::new(&msg);
1081        let cmd = pkt.decode_payload().expect("decoded");
1082        match cmd {
1083            PvaPacketCommand::GetField(payload) => {
1084                assert!(payload.is_server);
1085                assert_eq!(payload.cid, 11);
1086                assert!(payload.status.is_none());
1087                let intro = payload.introspection.expect("introspection");
1088                assert_eq!(intro.fields.len(), 1);
1089                assert_eq!(intro.fields[0].name, "value");
1090            }
1091            other => panic!("unexpected decode: {:?}", other),
1092        }
1093    }
1094
1095    #[test]
1096    fn encode_get_field_error_roundtrip() {
1097        let msg = encode_get_field_error(7, "listing disabled", 2, false);
1098        let mut pkt = PvaPacket::new(&msg);
1099        let cmd = pkt.decode_payload().expect("decoded");
1100        match cmd {
1101            PvaPacketCommand::GetField(payload) => {
1102                assert!(payload.is_server);
1103                assert_eq!(payload.cid, 7);
1104                let status = payload.status.expect("status");
1105                assert_eq!(status.code, 0x02);
1106                assert_eq!(status.message.as_deref(), Some("listing disabled"));
1107            }
1108            other => panic!("unexpected decode: {:?}", other),
1109        }
1110    }
1111
1112    #[test]
1113    fn encode_decode_create_channel_request_roundtrip() {
1114        let msg = encode_create_channel_request(7, "TEST:PV", 2, false);
1115        let mut pkt = PvaPacket::new(&msg);
1116        let cmd = pkt.decode_payload().expect("decoded");
1117        match cmd {
1118            PvaPacketCommand::CreateChannel(payload) => {
1119                assert!(!payload.is_server);
1120                assert_eq!(payload.channels, vec![(7, "TEST:PV".to_string())]);
1121            }
1122            other => panic!("unexpected decode: {:?}", other),
1123        }
1124    }
1125
1126    #[test]
1127    fn encode_decode_get_field_request_roundtrip() {
1128        let msg = encode_get_field_request(9, 1, Some("*"), 2, false);
1129        let mut pkt = PvaPacket::new(&msg);
1130        let cmd = pkt.decode_payload().expect("decoded");
1131        match cmd {
1132            PvaPacketCommand::GetField(payload) => {
1133                assert!(!payload.is_server);
1134                assert_eq!(payload.sid, Some(9));
1135                assert_eq!(payload.ioid, Some(1));
1136                assert_eq!(payload.field_name.as_deref(), Some("*"));
1137            }
1138            other => panic!("unexpected decode: {:?}", other),
1139        }
1140    }
1141
1142    #[test]
1143    fn encode_decode_get_request_roundtrip() {
1144        let msg = encode_get_request(1, 2, 0x08, &[0xfd, 0x02, 0x00], 2, false);
1145        let mut pkt = PvaPacket::new(&msg);
1146        let cmd = pkt.decode_payload().expect("decoded");
1147        match cmd {
1148            PvaPacketCommand::Op(op) => {
1149                assert_eq!(op.command, 10);
1150                assert_eq!(op.sid_or_cid, 1);
1151                assert_eq!(op.ioid, 2);
1152                assert_eq!(op.subcmd, 0x08);
1153            }
1154            other => panic!("unexpected decode: {:?}", other),
1155        }
1156    }
1157
1158    #[test]
1159    fn encode_decode_put_request_roundtrip() {
1160        let msg = encode_put_request(3, 4, 0x40, &[0xAA], 2, false);
1161        let mut pkt = PvaPacket::new(&msg);
1162        let cmd = pkt.decode_payload().expect("decoded");
1163        match cmd {
1164            PvaPacketCommand::Op(op) => {
1165                assert_eq!(op.command, 11);
1166                assert_eq!(op.sid_or_cid, 3);
1167                assert_eq!(op.ioid, 4);
1168                assert_eq!(op.subcmd, 0x40);
1169            }
1170            other => panic!("unexpected decode: {:?}", other),
1171        }
1172    }
1173
1174    #[test]
1175    fn encode_decode_monitor_request_roundtrip() {
1176        let msg = encode_monitor_request(5, 6, 0x44, &[], 2, false);
1177        let mut pkt = PvaPacket::new(&msg);
1178        let cmd = pkt.decode_payload().expect("decoded");
1179        match cmd {
1180            PvaPacketCommand::Op(op) => {
1181                assert_eq!(op.command, 13);
1182                assert_eq!(op.sid_or_cid, 5);
1183                assert_eq!(op.ioid, 6);
1184                assert_eq!(op.subcmd, 0x44);
1185            }
1186            other => panic!("unexpected decode: {:?}", other),
1187        }
1188    }
1189
1190    #[test]
1191    fn encode_decode_rpc_request_roundtrip() {
1192        let msg = encode_rpc_request(7, 8, 0x00, &[0x80, 0x00], 2, false);
1193        let mut pkt = PvaPacket::new(&msg);
1194        let cmd = pkt.decode_payload().expect("decoded");
1195        match cmd {
1196            PvaPacketCommand::Op(op) => {
1197                assert_eq!(op.command, 20);
1198                assert_eq!(op.sid_or_cid, 7);
1199                assert_eq!(op.ioid, 8);
1200                assert_eq!(op.subcmd, 0x00);
1201            }
1202            other => panic!("unexpected decode: {:?}", other),
1203        }
1204    }
1205
1206    #[test]
1207    fn encode_decode_search_request_roundtrip() {
1208        let seq = 1234;
1209        let cid = 42;
1210        let port = 5076;
1211        let reply_addr = ip_to_bytes(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 20)));
1212        let requests = [(cid, "TEST:PV")];
1213        let msg = encode_search_request(seq, 0x81, port, reply_addr, &requests, 2, false);
1214        let mut pkt = PvaPacket::new(&msg);
1215        let cmd = pkt.decode_payload().expect("decoded");
1216        match cmd {
1217            PvaPacketCommand::Search(payload) => {
1218                assert_eq!(payload.seq, seq);
1219                assert_eq!(payload.mask, 0x81);
1220                assert_eq!(payload.addr, reply_addr);
1221                assert_eq!(payload.port, port);
1222                assert_eq!(payload.protocols, vec!["tcp".to_string()]);
1223                assert_eq!(payload.pv_requests.len(), 1);
1224                assert_eq!(payload.pv_requests[0].0, cid);
1225                assert_eq!(payload.pv_requests[0].1, "TEST:PV");
1226            }
1227            other => panic!("unexpected decode: {:?}", other),
1228        }
1229    }
1230
1231    #[test]
1232    fn socket_addr_from_pva_bytes_decodes_ipv4_mapped() {
1233        let addr = ip_to_bytes(IpAddr::V4(Ipv4Addr::new(10, 20, 30, 40)));
1234        assert_eq!(
1235            socket_addr_from_pva_bytes(addr, 5075),
1236            Some("10.20.30.40:5075".parse().unwrap())
1237        );
1238    }
1239
1240    #[test]
1241    fn socket_addr_from_pva_bytes_decodes_ipv6() {
1242        let addr = ip_to_bytes(IpAddr::V6("2001:db8::1".parse().unwrap()));
1243        assert_eq!(
1244            socket_addr_from_pva_bytes(addr, 5075),
1245            Some("[2001:db8::1]:5075".parse().unwrap())
1246        );
1247    }
1248
1249    #[test]
1250    fn socket_addr_from_pva_bytes_returns_none_for_unspecified() {
1251        assert_eq!(socket_addr_from_pva_bytes([0u8; 16], 5075), None);
1252    }
1253}