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