instrument_core/
address.rs1use crate::error::{Error, Result};
2use serde::{Deserialize, Serialize};
3use std::collections::hash_map::DefaultHasher;
4use std::hash::{Hash, Hasher};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
8#[non_exhaustive]
9pub enum InterfaceKind {
10 Usb,
11 Gpib,
12 Tcpip,
13 Serial,
14 Vxi,
15 Pxi,
16 #[default]
17 Unknown,
18}
19
20#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
22pub struct AddressParts {
23 pub board: Option<u32>,
24 pub primary_address: Option<u32>,
25 pub secondary_address: Option<u32>,
26 pub vid: Option<String>,
27 pub pid: Option<String>,
28 pub serial: Option<String>,
29 pub host: Option<String>,
30 pub port: Option<u16>,
31 pub lane: Option<String>,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
36pub struct ResourceAddress {
37 pub interface: InterfaceKind,
38 pub raw: String,
39 pub components: AddressParts,
40 dedup_key: u64,
41}
42
43impl ResourceAddress {
44 pub fn parse(raw: &str) -> Result<Self> {
46 let trimmed = raw.trim();
47 if trimmed.is_empty() {
48 return Err(Error::InvalidAddress("empty address".into()));
49 }
50
51 let upper = trimmed.to_uppercase();
52 let (interface, components) = if upper.starts_with("USB") {
53 parse_usb(trimmed)?
54 } else if upper.starts_with("GPIB") {
55 parse_gpib(trimmed)?
56 } else if upper.starts_with("TCPIP") || upper.starts_with("TCP") {
57 parse_tcpip(trimmed)?
58 } else if upper.starts_with("ASRL") {
59 parse_serial(trimmed)?
60 } else if upper.starts_with("VXI") {
61 (InterfaceKind::Vxi, AddressParts::default())
62 } else if upper.starts_with("PXI") {
63 (InterfaceKind::Pxi, AddressParts::default())
64 } else {
65 (InterfaceKind::Unknown, AddressParts::default())
66 };
67
68 let mut addr = Self {
69 interface,
70 raw: trimmed.to_string(),
71 components,
72 dedup_key: 0,
73 };
74 addr.dedup_key = addr.compute_dedup_key();
75 Ok(addr)
76 }
77
78 pub fn dedup_key(&self) -> u64 {
80 self.dedup_key
81 }
82
83 fn compute_dedup_key(&self) -> u64 {
84 let mut hasher = DefaultHasher::new();
85 self.interface.hash(&mut hasher);
86 self.raw.to_uppercase().hash(&mut hasher);
87 hasher.finish()
88 }
89}
90
91fn parse_usb(raw: &str) -> Result<(InterfaceKind, AddressParts)> {
92 let parts: Vec<&str> = raw.split("::").collect();
94 let vid = parts.get(1).map(|s| normalize_hex_id(s));
95 let pid = parts.get(2).map(|s| normalize_hex_id(s));
96 let serial = parts.get(3).map(|s| s.to_string());
97 Ok((
98 InterfaceKind::Usb,
99 AddressParts {
100 vid,
101 pid,
102 serial,
103 ..Default::default()
104 },
105 ))
106}
107
108fn parse_gpib(raw: &str) -> Result<(InterfaceKind, AddressParts)> {
109 let parts: Vec<&str> = raw.split("::").collect();
110 let board = parts
111 .first()
112 .and_then(|s| s.strip_prefix("GPIB"))
113 .and_then(|s| s.parse().ok());
114 let primary_address = parts.get(1).and_then(|s| s.parse().ok());
115 let secondary_address = parts.get(2).and_then(|s| s.parse().ok());
116 Ok((
117 InterfaceKind::Gpib,
118 AddressParts {
119 board,
120 primary_address,
121 secondary_address,
122 ..Default::default()
123 },
124 ))
125}
126
127fn parse_tcpip(raw: &str) -> Result<(InterfaceKind, AddressParts)> {
128 let parts: Vec<&str> = raw.split("::").collect();
129 let host = parts.get(1).map(|s| s.to_string());
130 let port = if parts.len() == 4 && parts[3].eq_ignore_ascii_case("SOCKET") {
131 parts[2].parse().ok()
132 } else {
133 None
134 };
135 Ok((
136 InterfaceKind::Tcpip,
137 AddressParts {
138 host,
139 port,
140 ..Default::default()
141 },
142 ))
143}
144
145fn parse_serial(raw: &str) -> Result<(InterfaceKind, AddressParts)> {
146 let board = raw
147 .split("::")
148 .next()
149 .and_then(|s| s.strip_prefix("ASRL"))
150 .and_then(|s| s.parse().ok());
151 Ok((
152 InterfaceKind::Serial,
153 AddressParts {
154 board,
155 ..Default::default()
156 },
157 ))
158}
159
160fn normalize_hex_id(s: &str) -> String {
161 s.trim()
162 .trim_start_matches("0x")
163 .trim_start_matches("0X")
164 .to_uppercase()
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[test]
172 fn parses_usb_address() {
173 let addr = ResourceAddress::parse("USB0::0x0957::0x0607::MY123::INSTR").unwrap();
174 assert_eq!(addr.interface, InterfaceKind::Usb);
175 assert_eq!(addr.components.vid.as_deref(), Some("0957"));
176 assert_eq!(addr.components.pid.as_deref(), Some("0607"));
177 }
178
179 #[test]
180 fn dedup_is_case_insensitive() {
181 let a = ResourceAddress::parse("usb0::0x0957::0x0607::SN::INSTR").unwrap();
182 let b = ResourceAddress::parse("USB0::0x0957::0x0607::SN::INSTR").unwrap();
183 assert_eq!(a.dedup_key(), b.dedup_key());
184 }
185}