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