Skip to main content

scp2p_core/
peer.rs

1// Copyright (c) 2024-2026 Vanyo Vanev / Tech Art Ltd
2// SPDX-License-Identifier: MPL-2.0
3//
4// This Source Code Form is subject to the terms of the Mozilla Public
5// License, v. 2.0. If a copy of the MPL was not distributed with this
6// file, You can obtain one at https://mozilla.org/MPL/2.0/.
7use std::net::IpAddr;
8
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
12#[serde(rename_all = "lowercase")]
13pub enum TransportProtocol {
14    Quic,
15    Tcp,
16}
17
18/// Routing information for reaching a peer via a relay node.
19///
20/// When a firewalled node registers a relay slot on a public relay,
21/// it advertises itself with a `PeerAddr` containing `relay_via`.
22/// Connectors that encounter this field connect to the relay address
23/// and tunnel requests through the relay slot.
24#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
25pub struct RelayRoute {
26    /// Address of the relay node that holds the tunnel.
27    pub relay_addr: Box<PeerAddr>,
28    /// The relay slot ID assigned during `RelayRegister`.
29    pub slot_id: u64,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
33pub struct PeerAddr {
34    pub ip: IpAddr,
35    pub port: u16,
36    pub transport: TransportProtocol,
37    pub pubkey_hint: Option<[u8; 32]>,
38    /// If present, this peer is behind a firewall and can only be reached
39    /// by tunneling through the relay described here.
40    #[serde(default, skip_serializing_if = "Option::is_none")]
41    pub relay_via: Option<RelayRoute>,
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47
48    #[test]
49    fn peer_addr_cbor_roundtrip() {
50        let addr = PeerAddr {
51            ip: "127.0.0.1".parse().expect("valid ip"),
52            port: 7000,
53            transport: TransportProtocol::Quic,
54            pubkey_hint: Some([7u8; 32]),
55            relay_via: None,
56        };
57
58        let encoded = crate::cbor::to_vec(&addr).expect("encode peer addr");
59        let decoded: PeerAddr = crate::cbor::from_slice(&encoded).expect("decode peer addr");
60        assert_eq!(decoded, addr);
61    }
62
63    #[test]
64    fn peer_addr_with_relay_via_roundtrip() {
65        let relay = PeerAddr {
66            ip: "10.0.0.1".parse().expect("valid ip"),
67            port: 8000,
68            transport: TransportProtocol::Tcp,
69            pubkey_hint: Some([1u8; 32]),
70            relay_via: None,
71        };
72        let addr = PeerAddr {
73            ip: "192.168.1.5".parse().expect("valid ip"),
74            port: 7000,
75            transport: TransportProtocol::Tcp,
76            pubkey_hint: Some([7u8; 32]),
77            relay_via: Some(RelayRoute {
78                relay_addr: Box::new(relay.clone()),
79                slot_id: 42,
80            }),
81        };
82
83        let encoded = crate::cbor::to_vec(&addr).expect("encode peer addr with relay");
84        let decoded: PeerAddr =
85            crate::cbor::from_slice(&encoded).expect("decode peer addr with relay");
86        assert_eq!(decoded, addr);
87        let route = decoded.relay_via.unwrap();
88        assert_eq!(*route.relay_addr, relay);
89        assert_eq!(route.slot_id, 42);
90    }
91
92    #[test]
93    fn peer_addr_without_relay_via_is_backward_compatible() {
94        // Encode a PeerAddr _without_ the relay_via field by building
95        // the struct normally and then stripping `relay_via` from the
96        // resulting CBOR map.  This simulates old peers that were
97        // encoded before the field was added.
98        let original = PeerAddr {
99            ip: "10.0.0.5".parse().unwrap(),
100            port: 9000,
101            transport: TransportProtocol::Tcp,
102            pubkey_hint: None,
103            relay_via: None,
104        };
105        let full_bytes = crate::cbor::to_vec(&original).expect("encode");
106        let mut val: crate::cbor::Value =
107            crate::cbor::from_slice(&full_bytes).expect("decode value");
108        // Strip relay_via from the map to simulate legacy encoding.
109        if let crate::cbor::Value::Map(ref mut m) = val {
110            m.retain(|(k, _)| *k != crate::cbor::Value::Text("relay_via".into()));
111        }
112        let legacy = crate::cbor::to_vec(&val).expect("re-encode without relay_via");
113        let decoded: PeerAddr = crate::cbor::from_slice(&legacy).expect("decode legacy peer addr");
114        assert_eq!(decoded.relay_via, None);
115        assert_eq!(decoded.ip, original.ip);
116        assert_eq!(decoded.port, 9000);
117    }
118}