1#![allow(dead_code)]
2
3use std::fmt::Display;
4
5#[derive(Debug)]
7pub struct NbpPacket {
8 pub operation: NbpOperation, pub transaction_id: u8, pub tuples: Vec<NbpTuple>, }
12
13#[derive(Debug)]
15#[repr(u8)]
16pub enum NbpOperation {
17 BroadcastRequest = 1,
18 Lookup = 2,
19 LookupReply = 3,
20 ForwardRequest = 4,
21 Unknown(u8),
22}
23
24impl NbpOperation {
25 fn from_u8(value: u8) -> Self {
27 match value {
28 1 => NbpOperation::BroadcastRequest,
29 2 => NbpOperation::Lookup,
30 3 => NbpOperation::LookupReply,
31 4 => NbpOperation::ForwardRequest,
32 _ => NbpOperation::Unknown(value),
33 }
34 }
35
36 fn to_u8(&self) -> u8 {
38 match self {
39 NbpOperation::BroadcastRequest => 1,
40 NbpOperation::Lookup => 2,
41 NbpOperation::LookupReply => 3,
42 NbpOperation::ForwardRequest => 4,
43 NbpOperation::Unknown(value) => *value,
44 }
45 }
46}
47
48#[derive(Debug)]
50pub struct NbpTuple {
51 pub network_number: u16, pub node_id: u8, pub socket_number: u8, pub enumerator: u8, pub entity_name: EntityName, }
57
58#[derive(Debug, Eq, PartialEq)]
60pub struct EntityName {
61 pub object: String, pub entity_type: String, pub zone: String, }
65
66impl NbpPacket {
67 pub fn from_bytes(data: &[u8]) -> Result<Self, String> {
69 if data.len() < 2 {
70 return Err("Packet too short to be valid".to_string());
71 }
72
73 let control_byte = data[0];
74 let operation = NbpOperation::from_u8(control_byte >> 4);
75 let tuple_count = control_byte & 0x0F;
76 let transaction_id = data[1];
77 let mut offset = 2;
78 let mut tuples = Vec::new();
79
80 for _ in 0..tuple_count {
81 if offset + 5 > data.len() {
82 return Err("Packet too short for declared tuple count".to_string());
83 }
84
85 let network_number = u16::from_be_bytes([data[offset], data[offset + 1]]);
86 let node_id = data[offset + 2];
87 let socket_number = data[offset + 3];
88 let enumerator = data[offset + 4];
89 offset += 5;
90
91 let (entity_name, name_length) = EntityName::from_bytes(&data[offset..])?;
92 offset += name_length;
93
94 tuples.push(NbpTuple {
95 network_number,
96 node_id,
97 socket_number,
98 enumerator,
99 entity_name,
100 });
101 }
102
103 Ok(NbpPacket {
104 operation,
105 transaction_id,
106 tuples,
107 })
108 }
109
110 pub fn to_bytes(&self, buffer: &mut [u8]) -> Result<usize, String> {
113 let mut offset = 0;
114
115 if buffer.len() < 2 {
116 return Err("Buffer too small to hold the header".to_string());
117 }
118
119 buffer[offset] = (self.operation.to_u8() << 4) | (self.tuples.len() as u8 & 0x0F);
121 offset += 1;
122
123 buffer[offset] = self.transaction_id;
125 offset += 1;
126
127 for tuple in &self.tuples {
129 if offset + 5 > buffer.len() {
130 return Err("Buffer too small to hold tuple data".to_string());
131 }
132
133 buffer[offset..offset + 2].copy_from_slice(&tuple.network_number.to_be_bytes());
134 offset += 2;
135
136 buffer[offset] = tuple.node_id;
137 offset += 1;
138
139 buffer[offset] = tuple.socket_number;
140 offset += 1;
141
142 buffer[offset] = tuple.enumerator;
143 offset += 1;
144
145 let entity_name_size = tuple.entity_name.to_bytes(&mut buffer[offset..])?;
146 offset += entity_name_size;
147 }
148
149 Ok(offset)
150 }
151}
152
153impl TryFrom<&str> for EntityName {
154 type Error = &'static str;
155
156 fn try_from(value: &str) -> Result<Self, Self::Error> {
157 let first_index = value
158 .find(":")
159 .ok_or("malformed entity name - missing : separator")?;
160 let second_index = value
161 .find("@")
162 .ok_or("malformed entity name - missing @ separator")?;
163
164 if first_index > second_index {
165 return Err("malformed entity name - : was found before @");
166 }
167
168 let object = &value[..first_index];
169 if object.is_empty() {
170 return Err("malformed entity name - object is empty");
171 }
172
173 let entity_type = &value[first_index + 1..second_index];
174 if entity_type.is_empty() {
175 return Err("malformed entity name - type is empty");
176 }
177
178 let zone = &value[second_index + 1..];
179 if zone.is_empty() {
180 return Err("malformed entity name - zone is empty");
181 }
182
183 Ok(EntityName {
184 object: object.into(),
185 entity_type: entity_type.into(),
186 zone: zone.into(),
187 })
188 }
189}
190
191impl Display for EntityName {
192 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193 write!(f, "{}:{}@{}", self.object, self.entity_type, self.zone)
194 }
195}
196
197impl EntityName {
198 pub fn from_bytes(data: &[u8]) -> Result<(Self, usize), String> {
200 let mut offset = 0;
201
202 let object_length = *data.get(offset).ok_or("Missing object length")? as usize;
203 offset += 1;
204
205 let (object_cow, _, _) =
206 encoding_rs::MACINTOSH.decode(&data[offset..offset + object_length]);
207 let object = object_cow.into_owned();
208 offset += object_length;
209
210 let type_length = *data.get(offset).ok_or("Missing type length")? as usize;
211 offset += 1;
212
213 let (type_cow, _, _) = encoding_rs::MACINTOSH.decode(&data[offset..offset + type_length]);
214 let entity_type = type_cow.into_owned();
215 offset += type_length;
216
217 let zone_length = *data.get(offset).ok_or("Missing zone length")? as usize;
218 offset += 1;
219 let (zone_cow, _, _) = encoding_rs::MACINTOSH.decode(&data[offset..offset + zone_length]);
220 let zone = zone_cow.into_owned();
221 offset += zone_length;
222
223 Ok((
224 EntityName {
225 object,
226 entity_type,
227 zone,
228 },
229 offset,
230 ))
231 }
232
233 pub fn to_bytes(&self, buffer: &mut [u8]) -> Result<usize, String> {
236 let mut offset = 0;
237
238 let (object_cow, _, _) = encoding_rs::MACINTOSH.encode(self.object.as_str());
239 let object = object_cow.into_owned();
240 let (type_cow, _, _) = encoding_rs::MACINTOSH.encode(self.entity_type.as_str());
241 let entity_type = type_cow.into_owned();
242 let (zone_cow, _, _) = encoding_rs::MACINTOSH.encode(self.zone.as_str());
243 let zone = zone_cow.into_owned();
244
245 let calc_size = 1 + object.len() + 1 + entity_type.len() + 1 + zone.len();
246 if buffer.len() < calc_size {
247 return Err(format!(
248 "Buffer too small to hold entity name. Buf is: {}, calc_size: {calc_size}",
249 buffer.len()
250 ));
251 }
252
253 buffer[offset] = object.len() as u8;
254 offset += 1;
255 buffer[offset..offset + object.len()].copy_from_slice(&object);
256 offset += object.len();
257
258 buffer[offset] = entity_type.len() as u8;
259 offset += 1;
260 buffer[offset..offset + entity_type.len()].copy_from_slice(&entity_type);
261 offset += entity_type.len();
262
263 buffer[offset] = zone.len() as u8;
264 offset += 1;
265 buffer[offset..offset + zone.len()].copy_from_slice(&zone);
266 offset += zone.len();
267
268 Ok(offset)
269 }
270
271 pub fn matches(&self, pattern: &EntityName) -> bool {
272 let match_part = |concrete: &str, pattern: &str| -> bool {
273 if pattern == "=" || pattern == "≈" || pattern == "*" {
274 return true;
275 }
276 concrete.eq_ignore_ascii_case(pattern)
277 };
278
279 match_part(&self.object, &pattern.object)
280 && match_part(&self.entity_type, &pattern.entity_type)
281 && match_part(&self.zone, &pattern.zone)
282 }
283
284 pub fn fully_qualified(&self) -> bool {
285 const LOOKUP_FLAGS: [char; 3] = ['*', '=', '≈'];
286
287 for flag in LOOKUP_FLAGS {
288 if self.object.contains(flag) || self.entity_type.contains(flag) || self.zone != "*" {
289 return false;
290 }
291 }
292
293 true
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300
301 #[test]
302 fn test_parse_nbp() {
303 const TEST_DATA: &[u8] = &[
304 0x21, 0x01, 0xff, 0x54, 0x44, 0xfe, 0x00, 0x20, 0x30, 0x41, 0x45, 0x30, 0x34, 0x39,
305 0x36, 0x30, 0x33, 0x30, 0x44, 0x42, 0x43, 0x34, 0x30, 0x34, 0x31, 0x38, 0x30, 0x30,
306 0x41, 0x44, 0x43, 0x44, 0x30, 0x34, 0x37, 0x40, 0x4d, 0x4f, 0x52, 0x4f, 0x1c, 0x4d,
307 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0xa8, 0x20, 0x57, 0x69, 0x6e, 0x64,
308 0x6f, 0x77, 0x73, 0x20, 0x32, 0x30, 0x30, 0x30, 0xaa, 0x20, 0x50, 0x72, 0x74, 0x01,
309 0x2a,
310 ];
311
312 let packet = NbpPacket::from_bytes(TEST_DATA).expect("failed to parse");
313 let mut buf = [0u8; TEST_DATA.len()];
314
315 packet.to_bytes(&mut buf).expect("failed to encode");
316
317 assert_eq!(TEST_DATA, buf);
318 }
319
320 #[test]
321 fn test_parse_entity() {
322 let example_name = "Judy:Mailbox@Bandley3";
323
324 let entity: EntityName = example_name.try_into().expect("failed to parse");
325
326 assert_eq!(entity.object, "Judy");
327 assert_eq!(entity.entity_type, "Mailbox");
328 assert_eq!(entity.zone, "Bandley3");
329 }
330
331 #[test]
332 fn test_malformed_entity() {
333 assert!(EntityName::try_from("").is_err());
334 assert!(EntityName::try_from(":@").is_err());
335 assert!(EntityName::try_from("pants@waffles:com").is_err());
336 assert!(EntityName::try_from("Pannenkoek:Waffles@").is_err());
337 assert!(EntityName::try_from("Pannenkoek:@Waffles").is_err());
338 assert!(EntityName::try_from("Pannenkoek:@@@:").is_err());
339 }
340 #[test]
341 fn test_matches() {
342 let name: EntityName = "Steve:Workstation@Twilight".try_into().unwrap();
343
344 assert!(name.matches(&"Steve:Workstation@Twilight".try_into().unwrap()));
346
347 assert!(name.matches(&"steve:workstation@twilight".try_into().unwrap()));
349
350 assert!(name.matches(&"=:=@*".try_into().unwrap()));
352
353 assert!(name.matches(&"≈:Workstation@*".try_into().unwrap()));
354
355 assert!(!name.matches(&"Bob:Workstation@Twilight".try_into().unwrap()));
357
358 assert!(!name.matches(&"Steve:Printer@Twilight".try_into().unwrap()));
359 }
360}