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_delta,
6    encode_nt_payload_filtered, encode_nt_payload_full, encode_nt_scalar_bitset,
7    encode_nt_scalar_full, encode_structure_desc, 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
884/// Encode a sparse monitor-data response containing only fields that changed
885/// between `prev_value` and `next_value` (projected onto `filtered_desc`).
886/// Returns `None` when nothing changed — the caller should suppress the send
887/// entirely and NOT decrement any pipeline credit.
888pub fn encode_monitor_data_response_delta(
889    ioid: u32,
890    subcmd: u8,
891    prev_value: &NtPayload,
892    next_value: &NtPayload,
893    filtered_desc: &StructureDesc,
894    version: u8,
895    is_be: bool,
896) -> Option<Vec<u8>> {
897    let (bitset, values) =
898        encode_nt_payload_delta(prev_value, next_value, filtered_desc, is_be)?;
899    let mut payload = Vec::new();
900    payload.extend_from_slice(&if is_be {
901        ioid.to_be_bytes()
902    } else {
903        ioid.to_le_bytes()
904    });
905    payload.push(subcmd);
906    if (subcmd & 0x10) != 0 {
907        payload.extend_from_slice(&encode_status_ok());
908    }
909    payload.extend_from_slice(&bitset);
910    payload.extend_from_slice(&values);
911    payload.extend_from_slice(&encode_size_pva(0, is_be));
912    let mut out = encode_header(true, is_be, false, version, 13, payload.len() as u32);
913    out.extend_from_slice(&payload);
914    Some(out)
915}
916
917pub fn encode_destroy_channel_response(sid: u32, cid: u32, version: u8, is_be: bool) -> Vec<u8> {
918    let mut payload = Vec::new();
919    payload.extend_from_slice(&if is_be {
920        sid.to_be_bytes()
921    } else {
922        sid.to_le_bytes()
923    });
924    payload.extend_from_slice(&if is_be {
925        cid.to_be_bytes()
926    } else {
927        cid.to_le_bytes()
928    });
929    let mut out = encode_header(true, is_be, false, version, 8, payload.len() as u32);
930    out.extend_from_slice(&payload);
931    out
932}
933
934pub fn encode_op_error(
935    command: u8,
936    subcmd: u8,
937    ioid: u32,
938    message: &str,
939    version: u8,
940    is_be: bool,
941) -> Vec<u8> {
942    let mut payload = Vec::new();
943    payload.extend_from_slice(&if is_be {
944        ioid.to_be_bytes()
945    } else {
946        ioid.to_le_bytes()
947    });
948    payload.push(subcmd);
949    payload.push(0x02); // ERROR
950    payload.extend_from_slice(&encode_string_pva(message, is_be));
951    payload.extend_from_slice(&encode_string_pva("", is_be));
952    let mut out = encode_header(true, is_be, false, version, command, payload.len() as u32);
953    out.extend_from_slice(&payload);
954    out
955}
956
957pub fn encode_beacon(
958    guid: [u8; 12],
959    seq: u8,
960    change_count: u16,
961    addr: [u8; 16],
962    port: u16,
963    protocol: &str,
964    version: u8,
965    is_be: bool,
966) -> Vec<u8> {
967    let mut payload = Vec::new();
968    payload.extend_from_slice(&guid);
969    payload.push(0x00); // flags
970    payload.push(seq);
971    payload.extend_from_slice(&if is_be {
972        change_count.to_be_bytes()
973    } else {
974        change_count.to_le_bytes()
975    });
976    payload.extend_from_slice(&addr);
977    payload.extend_from_slice(&if is_be {
978        port.to_be_bytes()
979    } else {
980        port.to_le_bytes()
981    });
982    payload.extend_from_slice(&encode_string_pva(protocol, is_be));
983    // serverStatus: NULL FieldDesc (0xFF) means "no server status".
984    // Writing a PVA string here instead would be misinterpreted as a TypeCode
985    // by compliant clients (e.g. Phoebus), causing a BufferUnderflowException.
986    payload.push(0xFF);
987    let mut out = encode_header(true, is_be, false, version, 0, payload.len() as u32);
988    out.extend_from_slice(&payload);
989    out
990}
991
992// ---------------------------------------------------------------------------
993// IP address ↔ 16-byte PVA wire-format conversion helpers
994// ---------------------------------------------------------------------------
995
996/// Convert an [`IpAddr`] to the 16-byte PVA wire representation.
997///
998/// IPv4 addresses are stored as IPv4-mapped IPv6 (`::ffff:a.b.c.d`).
999/// Native IPv6 addresses are stored as-is.
1000pub fn ip_to_bytes(ip: IpAddr) -> [u8; 16] {
1001    match ip {
1002        IpAddr::V4(v4) => {
1003            let mut out = [0u8; 16];
1004            out[10] = 0xFF;
1005            out[11] = 0xFF;
1006            out[12..16].copy_from_slice(&v4.octets());
1007            out
1008        }
1009        IpAddr::V6(v6) => v6.octets(),
1010    }
1011}
1012
1013/// Decode a 16-byte PVA address field to an [`IpAddr`].
1014///
1015/// Returns `None` for all-zeros (unspecified).
1016/// IPv4-mapped addresses (`::ffff:a.b.c.d`) are returned as [`IpAddr::V4`].
1017pub fn ip_from_bytes(addr: &[u8; 16]) -> Option<IpAddr> {
1018    if addr.iter().all(|&b| b == 0) {
1019        return None;
1020    }
1021    // IPv4-mapped IPv6 address ::ffff:a.b.c.d
1022    if addr[0..10].iter().all(|&b| b == 0) && addr[10] == 0xFF && addr[11] == 0xFF {
1023        return Some(IpAddr::V4(Ipv4Addr::new(
1024            addr[12], addr[13], addr[14], addr[15],
1025        )));
1026    }
1027    Some(IpAddr::V6(Ipv6Addr::from(*addr)))
1028}
1029
1030pub fn socket_addr_from_pva_bytes(addr: [u8; 16], port: u16) -> Option<SocketAddr> {
1031    ip_from_bytes(&addr).map(|ip| SocketAddr::new(ip, port))
1032}
1033
1034/// Format a 16-byte PVA address field as a human-readable IP string.
1035///
1036/// All-zeros → `"0.0.0.0"`, IPv4-mapped → dotted-quad, otherwise IPv6 notation.
1037pub fn format_pva_address(addr: &[u8; 16]) -> String {
1038    match ip_from_bytes(addr) {
1039        Some(ip) => ip.to_string(),
1040        None => "0.0.0.0".to_string(),
1041    }
1042}
1043
1044#[cfg(test)]
1045mod tests {
1046    use super::*;
1047    use crate::epics_decode::{PvaPacket, PvaPacketCommand};
1048
1049    #[test]
1050    fn encode_decode_connection_validation_roundtrip() {
1051        let msg = encode_connection_validation(4096, 2, &["anonymous", "ca"], 2, true);
1052        let mut pkt = PvaPacket::new(&msg);
1053        let cmd = pkt.decode_payload().expect("decoded");
1054        match cmd {
1055            PvaPacketCommand::ConnectionValidation(payload) => {
1056                assert!(payload.is_server);
1057                assert_eq!(payload.buffer_size, 4096);
1058                assert_eq!(payload.introspection_registry_size, 2);
1059                assert_eq!(payload.qos, 0); // server→client has no qos
1060                assert_eq!(payload.authz.as_deref(), Some("anonymous"));
1061            }
1062            other => panic!("unexpected decode: {:?}", other),
1063        }
1064    }
1065
1066    #[test]
1067    fn encode_decode_client_connection_validation_roundtrip() {
1068        let msg = encode_client_connection_validation(
1069            87_040, 32_767, 0, "ca", "alice", "host1", 2, false,
1070        );
1071        let mut pkt = PvaPacket::new(&msg);
1072        let cmd = pkt.decode_payload().expect("decoded");
1073        match cmd {
1074            PvaPacketCommand::ConnectionValidation(payload) => {
1075                assert!(!payload.is_server);
1076                assert_eq!(payload.buffer_size, 87_040);
1077                assert_eq!(payload.introspection_registry_size, 32_767);
1078                assert_eq!(payload.qos, 0);
1079            }
1080            other => panic!("unexpected decode: {:?}", other),
1081        }
1082    }
1083
1084    #[test]
1085    fn encode_decode_search_response_roundtrip() {
1086        let guid = [1u8; 12];
1087        let seq = 42;
1088        let addr = [0u8; 16];
1089        let port = 5075;
1090        let cids = vec![100u32, 101u32];
1091        let msg = encode_search_response(guid, seq, addr, port, "tcp", true, &cids, 2, false);
1092        let mut pkt = PvaPacket::new(&msg);
1093        let cmd = pkt.decode_payload().expect("decoded");
1094        match cmd {
1095            PvaPacketCommand::SearchResponse(payload) => {
1096                assert_eq!(payload.guid, guid);
1097                assert_eq!(payload.seq, seq);
1098                assert_eq!(payload.port, port);
1099                assert!(payload.found);
1100                assert_eq!(payload.cids, cids);
1101            }
1102            other => panic!("unexpected decode: {:?}", other),
1103        }
1104    }
1105
1106    #[test]
1107    fn encode_decode_connection_validated_roundtrip() {
1108        let msg = encode_connection_validated(true, 2, false);
1109        let mut pkt = PvaPacket::new(&msg);
1110        let cmd = pkt.decode_payload().expect("decoded");
1111        match cmd {
1112            PvaPacketCommand::ConnectionValidated(payload) => {
1113                // 0xFF means "OK" which decodes to None in our decoder.
1114                assert!(payload.status.is_none());
1115            }
1116            other => panic!("unexpected decode: {:?}", other),
1117        }
1118    }
1119
1120    #[test]
1121    fn get_data_response_includes_status() {
1122        let nt = NtScalar::from_value(spvirit_types::ScalarValue::F64(1.0));
1123        let msg = encode_op_get_data_response_payload(0x11223344, &NtPayload::Scalar(nt), 2, false);
1124        assert!(msg.len() > 13);
1125        let status_offset = 8 + 4 + 1;
1126        assert_eq!(msg[status_offset], 0xFF);
1127
1128        let mut pkt = PvaPacket::new(&msg);
1129        let cmd = pkt.decode_payload().expect("decoded");
1130        match cmd {
1131            PvaPacketCommand::Op(op) => {
1132                assert_eq!(op.command, 10);
1133                assert_eq!(op.subcmd, 0x00);
1134                assert!(!op.body.is_empty());
1135            }
1136            other => panic!("unexpected decode: {:?}", other),
1137        }
1138    }
1139
1140    #[test]
1141    fn put_get_init_includes_two_descriptors() {
1142        let nt = NtScalar::from_value(spvirit_types::ScalarValue::F64(1.0));
1143        let desc = crate::spvd_encode::nt_scalar_desc(&nt.value);
1144        let msg = encode_op_put_get_init_response(0x01020304, &desc, &desc, 2, false);
1145
1146        let payload = &msg[8..];
1147        assert!(payload.len() > 6);
1148        // ioid(4) + subcmd(1) + status(1)
1149        assert_eq!(payload[5], 0xFF);
1150        let rest = &payload[6..];
1151        let first = rest.first().copied().unwrap_or(0);
1152        assert_eq!(first, 0x80);
1153        let second_pos = rest.iter().skip(1).position(|b| *b == 0x80);
1154        assert!(second_pos.is_some(), "expected second descriptor marker");
1155    }
1156
1157    #[test]
1158    fn put_get_data_includes_status() {
1159        let nt = NtScalar::from_value(spvirit_types::ScalarValue::F64(2.0));
1160        let msg = encode_op_put_get_data_response(0x55667788, &nt, 2, false);
1161        assert!(msg.len() > 13);
1162        let status_offset = 8 + 4 + 1;
1163        assert_eq!(msg[status_offset], 0xFF);
1164    }
1165
1166    #[test]
1167    fn put_getput_response_encodes_subcmd_0x40() {
1168        let nt = NtScalar::from_value(spvirit_types::ScalarValue::F64(2.0));
1169        let msg = encode_op_put_getput_response(0x01020304, &nt, 2, false);
1170        assert!(msg.len() > 13);
1171        let status_offset = 8 + 4 + 1;
1172        assert_eq!(msg[status_offset], 0xFF);
1173        let mut pkt = PvaPacket::new(&msg);
1174        let cmd = pkt.decode_payload().expect("decoded");
1175        match cmd {
1176            PvaPacketCommand::Op(op) => {
1177                assert_eq!(op.command, 11);
1178                assert_eq!(op.subcmd, 0x40);
1179            }
1180            other => panic!("unexpected decode: {:?}", other),
1181        }
1182    }
1183
1184    #[test]
1185    fn encode_get_field_response_roundtrip() {
1186        let desc = StructureDesc {
1187            struct_id: Some("epics:nt/NTScalar:1.0".to_string()),
1188            fields: vec![crate::spvd_decode::FieldDesc {
1189                name: "value".to_string(),
1190                field_type: crate::spvd_decode::FieldType::Scalar(
1191                    crate::spvd_decode::TypeCode::String,
1192                ),
1193            }],
1194        };
1195        let msg = encode_get_field_response(11, &desc, 2, false);
1196        let mut pkt = PvaPacket::new(&msg);
1197        let cmd = pkt.decode_payload().expect("decoded");
1198        match cmd {
1199            PvaPacketCommand::GetField(payload) => {
1200                assert!(payload.is_server);
1201                assert_eq!(payload.cid, 11);
1202                assert!(payload.status.is_none());
1203                let intro = payload.introspection.expect("introspection");
1204                assert_eq!(intro.fields.len(), 1);
1205                assert_eq!(intro.fields[0].name, "value");
1206            }
1207            other => panic!("unexpected decode: {:?}", other),
1208        }
1209    }
1210
1211    #[test]
1212    fn encode_get_field_error_roundtrip() {
1213        let msg = encode_get_field_error(7, "listing disabled", 2, false);
1214        let mut pkt = PvaPacket::new(&msg);
1215        let cmd = pkt.decode_payload().expect("decoded");
1216        match cmd {
1217            PvaPacketCommand::GetField(payload) => {
1218                assert!(payload.is_server);
1219                assert_eq!(payload.cid, 7);
1220                let status = payload.status.expect("status");
1221                assert_eq!(status.code, 0x02);
1222                assert_eq!(status.message.as_deref(), Some("listing disabled"));
1223            }
1224            other => panic!("unexpected decode: {:?}", other),
1225        }
1226    }
1227
1228    #[test]
1229    fn encode_decode_create_channel_request_roundtrip() {
1230        let msg = encode_create_channel_request(7, "TEST:PV", 2, false);
1231        let mut pkt = PvaPacket::new(&msg);
1232        let cmd = pkt.decode_payload().expect("decoded");
1233        match cmd {
1234            PvaPacketCommand::CreateChannel(payload) => {
1235                assert!(!payload.is_server);
1236                assert_eq!(payload.channels, vec![(7, "TEST:PV".to_string())]);
1237            }
1238            other => panic!("unexpected decode: {:?}", other),
1239        }
1240    }
1241
1242    #[test]
1243    fn encode_decode_get_field_request_roundtrip() {
1244        let msg = encode_get_field_request(9, 1, Some("*"), 2, false);
1245        let mut pkt = PvaPacket::new(&msg);
1246        let cmd = pkt.decode_payload().expect("decoded");
1247        match cmd {
1248            PvaPacketCommand::GetField(payload) => {
1249                assert!(!payload.is_server);
1250                assert_eq!(payload.sid, Some(9));
1251                assert_eq!(payload.ioid, Some(1));
1252                assert_eq!(payload.field_name.as_deref(), Some("*"));
1253            }
1254            other => panic!("unexpected decode: {:?}", other),
1255        }
1256    }
1257
1258    #[test]
1259    fn encode_decode_get_request_roundtrip() {
1260        let msg = encode_get_request(1, 2, 0x08, &[0xfd, 0x02, 0x00], 2, false);
1261        let mut pkt = PvaPacket::new(&msg);
1262        let cmd = pkt.decode_payload().expect("decoded");
1263        match cmd {
1264            PvaPacketCommand::Op(op) => {
1265                assert_eq!(op.command, 10);
1266                assert_eq!(op.sid_or_cid, 1);
1267                assert_eq!(op.ioid, 2);
1268                assert_eq!(op.subcmd, 0x08);
1269            }
1270            other => panic!("unexpected decode: {:?}", other),
1271        }
1272    }
1273
1274    #[test]
1275    fn encode_decode_put_request_roundtrip() {
1276        let msg = encode_put_request(3, 4, 0x40, &[0xAA], 2, false);
1277        let mut pkt = PvaPacket::new(&msg);
1278        let cmd = pkt.decode_payload().expect("decoded");
1279        match cmd {
1280            PvaPacketCommand::Op(op) => {
1281                assert_eq!(op.command, 11);
1282                assert_eq!(op.sid_or_cid, 3);
1283                assert_eq!(op.ioid, 4);
1284                assert_eq!(op.subcmd, 0x40);
1285            }
1286            other => panic!("unexpected decode: {:?}", other),
1287        }
1288    }
1289
1290    #[test]
1291    fn encode_decode_monitor_request_roundtrip() {
1292        let msg = encode_monitor_request(5, 6, 0x44, &[], 2, false);
1293        let mut pkt = PvaPacket::new(&msg);
1294        let cmd = pkt.decode_payload().expect("decoded");
1295        match cmd {
1296            PvaPacketCommand::Op(op) => {
1297                assert_eq!(op.command, 13);
1298                assert_eq!(op.sid_or_cid, 5);
1299                assert_eq!(op.ioid, 6);
1300                assert_eq!(op.subcmd, 0x44);
1301            }
1302            other => panic!("unexpected decode: {:?}", other),
1303        }
1304    }
1305
1306    #[test]
1307    fn encode_decode_rpc_request_roundtrip() {
1308        let msg = encode_rpc_request(7, 8, 0x00, &[0x80, 0x00], 2, false);
1309        let mut pkt = PvaPacket::new(&msg);
1310        let cmd = pkt.decode_payload().expect("decoded");
1311        match cmd {
1312            PvaPacketCommand::Op(op) => {
1313                assert_eq!(op.command, 20);
1314                assert_eq!(op.sid_or_cid, 7);
1315                assert_eq!(op.ioid, 8);
1316                assert_eq!(op.subcmd, 0x00);
1317            }
1318            other => panic!("unexpected decode: {:?}", other),
1319        }
1320    }
1321
1322    #[test]
1323    fn encode_decode_search_request_roundtrip() {
1324        let seq = 1234;
1325        let cid = 42;
1326        let port = 5076;
1327        let reply_addr = ip_to_bytes(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 20)));
1328        let requests = [(cid, "TEST:PV")];
1329        let msg = encode_search_request(seq, 0x81, port, reply_addr, &requests, 2, false);
1330        let mut pkt = PvaPacket::new(&msg);
1331        let cmd = pkt.decode_payload().expect("decoded");
1332        match cmd {
1333            PvaPacketCommand::Search(payload) => {
1334                assert_eq!(payload.seq, seq);
1335                assert_eq!(payload.mask, 0x81);
1336                assert_eq!(payload.addr, reply_addr);
1337                assert_eq!(payload.port, port);
1338                assert_eq!(payload.protocols, vec!["tcp".to_string()]);
1339                assert_eq!(payload.pv_requests.len(), 1);
1340                assert_eq!(payload.pv_requests[0].0, cid);
1341                assert_eq!(payload.pv_requests[0].1, "TEST:PV");
1342            }
1343            other => panic!("unexpected decode: {:?}", other),
1344        }
1345    }
1346
1347    #[test]
1348    fn socket_addr_from_pva_bytes_decodes_ipv4_mapped() {
1349        let addr = ip_to_bytes(IpAddr::V4(Ipv4Addr::new(10, 20, 30, 40)));
1350        assert_eq!(
1351            socket_addr_from_pva_bytes(addr, 5075),
1352            Some("10.20.30.40:5075".parse().unwrap())
1353        );
1354    }
1355
1356    #[test]
1357    fn socket_addr_from_pva_bytes_decodes_ipv6() {
1358        let addr = ip_to_bytes(IpAddr::V6("2001:db8::1".parse().unwrap()));
1359        assert_eq!(
1360            socket_addr_from_pva_bytes(addr, 5075),
1361            Some("[2001:db8::1]:5075".parse().unwrap())
1362        );
1363    }
1364
1365    #[test]
1366    fn socket_addr_from_pva_bytes_returns_none_for_unspecified() {
1367        assert_eq!(socket_addr_from_pva_bytes([0u8; 16], 5075), None);
1368    }
1369}