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