1use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
3
4use blake2::digest::{Update, VariableOutput};
5use blake2::Blake2sVar;
6use yip_membership::Record;
7
8const DOMAIN: &[u8] = b"yip-rdv-v1";
10
11pub type NodeId = [u8; 16];
14
15pub fn node_id(pubkey: &[u8; 32]) -> NodeId {
17 let mut h = Blake2sVar::new(16).expect("16 is a valid blake2s output len");
18 h.update(DOMAIN);
19 h.update(pubkey);
20 let mut out = [0u8; 16];
21 h.finalize_variable(&mut out).expect("output len matches");
22 out
23}
24
25const DOMAIN_REG_AUTH: &[u8] = b"yip-rendezvous-v1-reg-auth";
26
27pub fn compute_register_mac(obf_key: &[u8; 16], node: &NodeId, counter: u64) -> [u8; 16] {
29 let mut h = Blake2sVar::new(16).expect("16 is a valid blake2s output len");
30 h.update(DOMAIN_REG_AUTH);
31 h.update(obf_key);
32 h.update(node);
33 h.update(&counter.to_be_bytes());
34 let mut out = [0u8; 16];
35 h.finalize_variable(&mut out).expect("len matches");
36 out
37}
38
39pub fn verify_register_mac(
41 obf_key: &[u8; 16],
42 node: &NodeId,
43 counter: u64,
44 mac: &[u8; 16],
45) -> bool {
46 use subtle::ConstantTimeEq;
47
48 let expected = compute_register_mac(obf_key, node, counter);
49 expected.ct_eq(mac).into()
50}
51
52#[repr(u8)]
54enum Tag {
55 Register = 0,
56 Lookup = 1,
57 PeerInfo = 2,
58 NotFound = 3,
59 PunchHint = 4,
60 RelaySend = 5,
61 RelayDeliver = 6,
62 RegisterSigned = 7,
63 RegisterAuth = 8,
64}
65
66#[derive(Debug, Clone, PartialEq, Eq)]
68pub enum Message {
69 Register {
70 node: NodeId,
71 counter: u64,
75 },
76 Lookup {
77 node: NodeId,
78 },
79 PeerInfo {
80 node: NodeId,
81 reflexive: SocketAddr,
82 record: Option<Record>,
86 },
87 NotFound {
88 node: NodeId,
89 },
90 PunchHint {
91 node: NodeId,
92 reflexive: SocketAddr,
93 },
94 RelaySend {
95 src: NodeId,
96 dst: NodeId,
97 payload: Vec<u8>,
98 },
99 RelayDeliver {
100 src: NodeId,
101 payload: Vec<u8>,
102 },
103 RegisterSigned {
106 record: Record,
107 },
108 RegisterAuth {
111 node: NodeId,
112 counter: u64,
113 mac: [u8; 16],
114 },
115}
116
117fn put_record(out: &mut Vec<u8>, record: &Record) {
123 let mut bytes = Vec::new();
124 record.encode(&mut bytes);
125 let len = u16::try_from(bytes.len()).expect("record fits in u16");
126 out.extend_from_slice(&len.to_be_bytes());
127 out.extend_from_slice(&bytes);
128}
129
130fn take_record(buf: &[u8]) -> Option<Record> {
134 let len = usize::from(u16::from_be_bytes(buf.get(..2)?.try_into().ok()?));
135 let record_bytes = buf.get(2..2 + len)?;
136 Record::decode(record_bytes)
137}
138
139fn put_addr(out: &mut Vec<u8>, addr: &SocketAddr) {
140 match addr.ip() {
141 IpAddr::V4(ip) => {
142 out.push(4);
143 out.extend_from_slice(&ip.octets());
144 }
145 IpAddr::V6(ip) => {
146 out.push(6);
147 out.extend_from_slice(&ip.octets());
148 }
149 }
150 out.extend_from_slice(&addr.port().to_be_bytes());
151}
152
153fn take_addr(buf: &[u8]) -> Option<(SocketAddr, usize)> {
154 let (&fam, rest) = buf.split_first()?;
155 let (ip, used): (IpAddr, usize) = match fam {
156 4 => {
157 let o: [u8; 4] = rest.get(..4)?.try_into().ok()?;
158 (IpAddr::V4(Ipv4Addr::from(o)), 4)
159 }
160 6 => {
161 let o: [u8; 16] = rest.get(..16)?.try_into().ok()?;
162 (IpAddr::V6(Ipv6Addr::from(o)), 16)
163 }
164 _ => return None,
165 };
166 let port_bytes: [u8; 2] = rest.get(used..used + 2)?.try_into().ok()?;
167 let port = u16::from_be_bytes(port_bytes);
168 Some((SocketAddr::new(ip, port), 1 + used + 2))
169}
170
171pub fn encode(msg: &Message, out: &mut Vec<u8>) {
173 match msg {
174 Message::Register { node, counter } => {
175 out.push(Tag::Register as u8);
176 out.extend_from_slice(node);
177 out.extend_from_slice(&counter.to_be_bytes());
178 }
179 Message::Lookup { node } => {
180 out.push(Tag::Lookup as u8);
181 out.extend_from_slice(node);
182 }
183 Message::PeerInfo {
184 node,
185 reflexive,
186 record,
187 } => {
188 out.push(Tag::PeerInfo as u8);
189 out.extend_from_slice(node);
190 put_addr(out, reflexive);
191 match record {
192 Some(r) => {
193 out.push(1);
194 put_record(out, r);
195 }
196 None => out.push(0),
197 }
198 }
199 Message::NotFound { node } => {
200 out.push(Tag::NotFound as u8);
201 out.extend_from_slice(node);
202 }
203 Message::PunchHint { node, reflexive } => {
204 out.push(Tag::PunchHint as u8);
205 out.extend_from_slice(node);
206 put_addr(out, reflexive);
207 }
208 Message::RelaySend { src, dst, payload } => {
209 out.push(Tag::RelaySend as u8);
210 out.extend_from_slice(src);
211 out.extend_from_slice(dst);
212 out.extend_from_slice(payload);
213 }
214 Message::RelayDeliver { src, payload } => {
215 out.push(Tag::RelayDeliver as u8);
216 out.extend_from_slice(src);
217 out.extend_from_slice(payload);
218 }
219 Message::RegisterSigned { record } => {
220 out.push(Tag::RegisterSigned as u8);
221 put_record(out, record);
222 }
223 Message::RegisterAuth { node, counter, mac } => {
224 out.push(Tag::RegisterAuth as u8);
225 out.extend_from_slice(node);
226 out.extend_from_slice(&counter.to_be_bytes());
227 out.extend_from_slice(mac);
228 }
229 }
230}
231
232pub fn decode(buf: &[u8]) -> Option<Message> {
234 let (&tag, rest) = buf.split_first()?;
235 let node16 = |b: &[u8]| -> Option<NodeId> { b.get(..16)?.try_into().ok() };
236 match tag {
237 t if t == Tag::Register as u8 => {
238 let node = node16(rest)?;
239 let counter = u64::from_be_bytes(rest.get(16..24)?.try_into().ok()?);
240 Some(Message::Register { node, counter })
241 }
242 t if t == Tag::Lookup as u8 => Some(Message::Lookup {
243 node: node16(rest)?,
244 }),
245 t if t == Tag::NotFound as u8 => Some(Message::NotFound {
246 node: node16(rest)?,
247 }),
248 t if t == Tag::PeerInfo as u8 => {
249 let node = node16(rest)?;
250 let (reflexive, used) = take_addr(rest.get(16..)?)?;
251 let after_addr = rest.get(16 + used..)?;
252 let record = match after_addr.first() {
253 Some(0) => None,
254 Some(1) => {
255 let record = take_record(after_addr.get(1..)?)?;
256 Some(record)
257 }
258 Some(_) => return None,
259 None => None,
262 };
263 Some(Message::PeerInfo {
264 node,
265 reflexive,
266 record,
267 })
268 }
269 t if t == Tag::PunchHint as u8 => {
270 let node = node16(rest)?;
271 let (reflexive, _) = take_addr(rest.get(16..)?)?;
272 Some(Message::PunchHint { node, reflexive })
273 }
274 t if t == Tag::RelaySend as u8 => {
275 let src = node16(rest)?;
276 let dst = node16(rest.get(16..)?)?;
277 Some(Message::RelaySend {
278 src,
279 dst,
280 payload: rest.get(32..)?.to_vec(),
281 })
282 }
283 t if t == Tag::RelayDeliver as u8 => {
284 let src = node16(rest)?;
285 Some(Message::RelayDeliver {
286 src,
287 payload: rest.get(16..)?.to_vec(),
288 })
289 }
290 t if t == Tag::RegisterSigned as u8 => {
291 let record = take_record(rest)?;
292 Some(Message::RegisterSigned { record })
293 }
294 t if t == Tag::RegisterAuth as u8 => {
295 if rest.len() != 40 {
296 return None;
297 }
298 let node = node16(rest)?;
299 let counter = u64::from_be_bytes(rest.get(16..24)?.try_into().ok()?);
300 let mac: [u8; 16] = rest.get(24..40)?.try_into().ok()?;
301 Some(Message::RegisterAuth { node, counter, mac })
302 }
303 _ => None,
304 }
305}
306
307#[cfg(test)]
308mod tests {
309 use super::*;
310 use std::net::SocketAddr;
311
312 #[test]
313 fn node_id_is_deterministic_and_16_bytes() {
314 let pk = [7u8; 32];
315 let a = node_id(&pk);
316 assert_eq!(a.len(), 16);
317 assert_eq!(node_id(&pk), a);
318 assert_ne!(node_id(&pk), node_id(&[8u8; 32]));
319 }
320
321 fn roundtrip(msg: Message) {
322 let mut buf = Vec::new();
323 encode(&msg, &mut buf);
324 assert_eq!(decode(&buf), Some(msg));
325 }
326
327 #[test]
328 fn decode_rejects_invalid_address_family() {
329 let mut buf = vec![Tag::PeerInfo as u8];
332 buf.extend_from_slice(&[7u8; 16]); buf.push(99); buf.extend_from_slice(&[0u8; 4]);
335 assert_eq!(decode(&buf), None);
336 }
337
338 #[test]
339 fn all_messages_roundtrip() {
340 let n = [1u8; 16];
341 let v4: SocketAddr = "203.0.113.9:5000".parse().unwrap();
342 let v6: SocketAddr = "[2001:db8::1]:5000".parse().unwrap();
343 roundtrip(Message::Register {
344 node: n,
345 counter: 1,
346 });
347 roundtrip(Message::Lookup { node: n });
348 roundtrip(Message::PeerInfo {
349 node: n,
350 reflexive: v4,
351 record: None,
352 });
353 roundtrip(Message::PeerInfo {
354 node: n,
355 reflexive: v6,
356 record: None,
357 });
358 roundtrip(Message::NotFound { node: n });
359 roundtrip(Message::PunchHint {
360 node: n,
361 reflexive: v4,
362 });
363 roundtrip(Message::RelaySend {
364 src: [3u8; 16],
365 dst: n,
366 payload: vec![9, 8, 7],
367 });
368 roundtrip(Message::RelayDeliver {
369 src: n,
370 payload: vec![1, 2, 3, 4],
371 });
372 roundtrip(Message::RegisterAuth {
373 node: n,
374 counter: 42,
375 mac: [0xaa; 16],
376 });
377 }
378
379 #[test]
380 fn decode_rejects_garbage_and_truncation() {
381 assert_eq!(decode(&[]), None);
382 assert_eq!(decode(&[0xFF]), None); let mut buf = Vec::new();
384 encode(
385 &Message::PeerInfo {
386 node: [2u8; 16],
387 reflexive: "1.2.3.4:5".parse().unwrap(),
388 record: None,
389 },
390 &mut buf,
391 );
392 buf.truncate(buf.len() - 2);
397 assert_eq!(decode(&buf), None); }
399
400 #[test]
401 fn register_roundtrips_with_counter() {
402 let n = node_id(&[7u8; 32]);
403 let msg = Message::Register {
404 node: n,
405 counter: 0x0102_0304_0506_0708,
406 };
407 let mut buf = Vec::new();
408 encode(&msg, &mut buf);
409 assert_eq!(decode(&buf), Some(msg));
410 }
411
412 #[test]
413 fn register_truncated_counter_is_none() {
414 let mut buf = vec![0u8]; buf.extend_from_slice(&[9u8; 16]);
417 buf.extend_from_slice(&[0u8; 4]);
418 assert_eq!(decode(&buf), None);
419 }
420
421 fn addr(s: &str) -> SocketAddr {
422 s.parse().unwrap()
423 }
424
425 fn sample_record() -> Record {
430 use ed25519_dalek::{Signer, SigningKey};
431 use getrandom::SysRng;
432 use rand_core::UnwrapErr;
433 use yip_membership::cert::cert_signing_body;
434 use yip_membership::record::{record_signing_body, sign};
435 use yip_membership::{node_id, Cert};
436
437 let ca = SigningKey::generate(&mut UnwrapErr(SysRng));
438 let member_pubkey = [1u8; 32];
439 let member_sign_key = SigningKey::generate(&mut UnwrapErr(SysRng));
440 let member_sign_pubkey = member_sign_key.verifying_key().to_bytes();
441 let network_id = [7u8; 16];
442
443 let mut cert = Cert {
444 version: 1,
445 member_pubkey,
446 member_sign_pubkey,
447 network_id,
448 not_before: 100,
449 not_after: 200,
450 tags: vec![],
451 ca_sig: [0u8; 64],
452 };
453 cert.ca_sig = ca.sign(&cert_signing_body(&cert)).to_bytes();
454
455 let mut record = Record {
456 node_id: node_id(&member_pubkey),
457 cert,
458 endpoints: vec![addr("192.0.2.1:8080")],
459 seq: 1,
460 sig: [0u8; 64],
461 };
462 let body = record_signing_body(&record);
463 record.sig = sign(
464 &body,
465 member_sign_key.to_bytes().as_ref().try_into().unwrap(),
466 );
467 record
468 }
469
470 #[test]
471 fn register_signed_roundtrips() {
472 let rec = sample_record();
473 roundtrip(Message::RegisterSigned { record: rec });
474 }
475
476 #[test]
477 fn peerinfo_with_record_roundtrips() {
478 roundtrip(Message::PeerInfo {
479 node: [7u8; 16],
480 reflexive: addr("198.51.100.7:41000"),
481 record: Some(sample_record()),
482 });
483 }
484
485 #[test]
486 fn peerinfo_without_record_roundtrips_and_is_backward_compatible() {
487 roundtrip(Message::PeerInfo {
488 node: [7u8; 16],
489 reflexive: addr("198.51.100.7:41000"),
490 record: None,
491 });
492 }
493
494 #[test]
495 fn peerinfo_legacy_datagram_with_no_presence_byte_decodes_as_none() {
496 let mut buf = vec![Tag::PeerInfo as u8];
500 buf.extend_from_slice(&[7u8; 16]); put_addr(&mut buf, &addr("198.51.100.7:41000")); assert_eq!(
503 decode(&buf),
504 Some(Message::PeerInfo {
505 node: [7u8; 16],
506 reflexive: addr("198.51.100.7:41000"),
507 record: None,
508 })
509 );
510 }
511
512 #[test]
513 fn register_signed_length_prefix_exceeding_buffer_is_none() {
514 let mut buf = vec![Tag::RegisterSigned as u8];
517 buf.extend_from_slice(&0xFFFFu16.to_be_bytes()); buf.extend_from_slice(&[0u8; 4]); assert_eq!(decode(&buf), None);
520 }
521
522 #[test]
523 fn peerinfo_record_length_prefix_exceeding_buffer_is_none() {
524 let mut buf = vec![Tag::PeerInfo as u8];
525 buf.extend_from_slice(&[7u8; 16]); put_addr(&mut buf, &addr("198.51.100.7:41000"));
527 buf.push(1); buf.extend_from_slice(&0xFFFFu16.to_be_bytes()); buf.extend_from_slice(&[0u8; 4]); assert_eq!(decode(&buf), None);
531 }
532
533 #[test]
534 fn test_compute_register_mac_kat_and_verify() {
535 let key = [0x42u8; 16];
536 let node = node_id(&[0x11u8; 32]);
537 let counter = 12345u64;
538 let mac = compute_register_mac(&key, &node, counter);
539 assert_ne!(mac, [0u8; 16]);
540 assert!(verify_register_mac(&key, &node, counter, &mac));
541
542 let bad_key = [0x43u8; 16];
544 assert!(!verify_register_mac(&bad_key, &node, counter, &mac));
545
546 let bad_node = node_id(&[0x22u8; 32]);
548 assert!(!verify_register_mac(&key, &bad_node, counter, &mac));
549
550 assert!(!verify_register_mac(&key, &node, counter + 1, &mac));
552
553 let mut bad_mac = mac;
555 bad_mac[0] ^= 1;
556 assert!(!verify_register_mac(&key, &node, counter, &bad_mac));
557 }
558
559 #[test]
560 fn test_register_auth_encode_decode_roundtrip() {
561 let node = node_id(&[0x55u8; 32]);
562 let counter = 9999u64;
563 let mac = [0x77u8; 16];
564 let msg = Message::RegisterAuth { node, counter, mac };
565 let mut buf = Vec::new();
566 encode(&msg, &mut buf);
567 assert_eq!(buf.len(), 1 + 16 + 8 + 16);
568 assert_eq!(buf[0], 8);
569 let decoded = decode(&buf).expect("decodes RegisterAuth");
570 assert_eq!(decoded, msg);
571 }
572
573 #[test]
574 fn register_auth_rejects_trailing_garbage_or_truncation() {
575 let node = node_id(&[0x55u8; 32]);
576 let counter = 9999u64;
577 let mac = [0x77u8; 16];
578 let msg = Message::RegisterAuth { node, counter, mac };
579 let mut buf = Vec::new();
580 encode(&msg, &mut buf);
581 assert_eq!(decode(&buf), Some(msg));
582
583 let mut with_trailing = buf.clone();
585 with_trailing.push(0x00);
586 assert_eq!(
587 decode(&with_trailing),
588 None,
589 "trailing garbage must be rejected"
590 );
591
592 let mut truncated = buf;
594 truncated.pop();
595 assert_eq!(
596 decode(&truncated),
597 None,
598 "truncated datagram must be rejected"
599 );
600 }
601
602 #[test]
603 fn decode_unknown_tag_returns_none() {
604 let mut buf = vec![99u8];
605 buf.extend_from_slice(&[0u8; 40]);
606 assert_eq!(decode(&buf), None);
607 }
608}