Skip to main content

spvirit_codec/
spvirit_encode.rs

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