Skip to main content

usb_vsock/
lib.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4#![warn(missing_docs, unsafe_op_in_unsafe_fn)]
5//! A transport-agnostic library for implementing a vsock bridge over a usb bulk device.
6
7mod connection;
8mod packet;
9
10pub use connection::*;
11pub use packet::*;
12
13/// Protocol version. This can be extracted from the payload of the sync packet
14/// and will determine what features a connection supports.
15#[derive(Copy, Clone, PartialEq, Eq, Debug)]
16pub enum ProtocolVersion {
17    /// Protocol version 0
18    V0,
19    /// Protocol version 1
20    V1,
21    /// Protocol version 2 with a random 32-bit integer nonce
22    V2(u32),
23}
24
25impl ProtocolVersion {
26    /// The latest protocol version.
27    pub const LATEST: ProtocolVersion = ProtocolVersion::V2(0);
28
29    /// Magic sent in the sync packet of the USB protocol.
30    ///
31    /// The format is a byte string of the form `vsock:0`, where the 0 indicates
32    /// protocol version 0, and we expect the reply sync packet to have the
33    /// exact same contents. As we version the protocol this may increment.
34    ///
35    /// In version 2, the format is `vsock:2:<nonce>` where `<nonce>` is a random
36    /// 32-bit unsigned integer (in hexadecimal).
37    ///
38    /// To document the semantics, let's say this header were "vsock:3". The device
39    /// could reply with a lower number, say "vsock:1". This is the device
40    /// requesting a downgrade, and if we accept we send the final sync with
41    /// "vsock:1". Otherwise we hang up.
42    pub fn magic(&self) -> Vec<u8> {
43        match self {
44            ProtocolVersion::V0 => b"vsock:0".to_vec(),
45            ProtocolVersion::V1 => b"vsock:1".to_vec(),
46            ProtocolVersion::V2(nonce) => format!("vsock:2:{nonce:x}").into_bytes(),
47        }
48    }
49
50    /// Derive the protocol version from the magic sent in the sync packet.
51    pub fn from_magic(magic: &[u8]) -> Option<ProtocolVersion> {
52        if magic == b"vsock:0" {
53            Some(ProtocolVersion::V0)
54        } else if magic == b"vsock:1" {
55            Some(ProtocolVersion::V1)
56        } else if let Some(rest) = magic.strip_prefix(b"vsock:2:") {
57            let s = std::str::from_utf8(rest).ok()?;
58            let nonce = u32::from_str_radix(s, 16).ok()?;
59            Some(ProtocolVersion::V2(nonce))
60        } else {
61            None
62        }
63    }
64
65    /// Given `self` is the protocol version the target prefers and
66    /// `host_version` is the protocol version sent in the magic as the host
67    /// connects, find the protocol version that should be sent in the reply
68    /// magic and used for the connection. If `None`, negotiation has broken
69    /// down.
70    pub fn negotiate(&self, host_version: &ProtocolVersion) -> Option<ProtocolVersion> {
71        match (self, host_version) {
72            (ProtocolVersion::V2(_), ProtocolVersion::V2(nonce)) => {
73                Some(ProtocolVersion::V2(*nonce))
74            }
75            (ProtocolVersion::V2(_), ProtocolVersion::V1)
76            | (ProtocolVersion::V1, ProtocolVersion::V2(_))
77            | (ProtocolVersion::V1, ProtocolVersion::V1) => Some(ProtocolVersion::V1),
78            (ProtocolVersion::V0, _) | (_, ProtocolVersion::V0) => Some(ProtocolVersion::V0),
79        }
80    }
81
82    /// Whether we support the pause protocol message.
83    pub(crate) fn has_pause_packets(&self) -> bool {
84        matches!(self, ProtocolVersion::V1 | ProtocolVersion::V2(_))
85    }
86}
87
88impl std::fmt::Display for ProtocolVersion {
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        match self {
91            ProtocolVersion::V0 => write!(f, "0"),
92            ProtocolVersion::V1 => write!(f, "1"),
93            ProtocolVersion::V2(nonce) => write!(f, "2:{nonce:x}"),
94        }
95    }
96}
97
98/// A placeholder CID indicating "any" CID is acceptable.
99pub const CID_ANY: u32 = u32::MAX;
100
101/// CID of the host.
102pub const CID_HOST: u32 = 2;
103
104/// The loopback CID.
105pub const CID_LOOPBACK: u32 = 1;
106
107/// An address for a vsock packet transmitted over USB. Since this library does not implement
108/// policy decisions, it includes all four components of a vsock address pair even though some
109/// may not be appropriate for some situations.
110#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
111pub struct Address {
112    /// For Connect, Reset, Accept, and Data packets this represents the device side's address.
113    /// Usually this will be a special value representing either that it is simply "the device",
114    /// or zero along with the rest of the cid and port fields to indicate that it's a control stream
115    /// packet. Must be zero for any other packet type.
116    pub device_cid: u32,
117    /// For Connect, Reset, Accept, and Data packets this represents the host side's address.
118    /// Usually this will be a special value representing either that it is simply "the host",
119    /// or zero along with the rest of the cid and port fields to indicate that it's a control stream
120    /// packet. Must be zero for any other packet type.
121    pub host_cid: u32,
122    /// For Connect, Reset, Accept, and Data packets this represents the device side's port.
123    /// This must be a valid positive value for any of those packet types, unless all of the cid and
124    /// port fields are also zero, in which case it is a control stream packet. Must be zero for any
125    /// other packet type.
126    pub device_port: u32,
127    /// For Connect, Reset, Accept, and Data packets this represents the host side's port.
128    /// This must be a valid positive value for any of those packet types, unless all of the cid and
129    /// port fields are also zero, in which case it is a control stream packet. Must be zero for any
130    /// other packet type.
131    pub host_port: u32,
132}
133
134impl Address {
135    /// Returns true if all the fields of this address are zero (which usually means it's a control
136    /// packet of some sort).
137    pub fn is_zeros(&self) -> bool {
138        *self == Self::default()
139    }
140}
141
142impl From<&Header> for Address {
143    fn from(header: &Header) -> Self {
144        Self {
145            device_cid: header.device_cid.get(),
146            host_cid: header.host_cid.get(),
147            device_port: header.device_port.get(),
148            host_port: header.host_port.get(),
149        }
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn test_protocol_version_magic_roundtrip() {
159        let v0 = ProtocolVersion::V0;
160        assert_eq!(v0.magic(), b"vsock:0");
161        assert_eq!(ProtocolVersion::from_magic(b"vsock:0"), Some(v0));
162
163        let v1 = ProtocolVersion::V1;
164        assert_eq!(v1.magic(), b"vsock:1");
165        assert_eq!(ProtocolVersion::from_magic(b"vsock:1"), Some(v1));
166
167        let v2_zero = ProtocolVersion::V2(0);
168        assert_eq!(v2_zero.magic(), b"vsock:2:0");
169        assert_eq!(ProtocolVersion::from_magic(b"vsock:2:0"), Some(v2_zero));
170
171        let v2 = ProtocolVersion::V2(0xdeadbeef);
172        assert_eq!(v2.magic(), b"vsock:2:deadbeef");
173        assert_eq!(ProtocolVersion::from_magic(b"vsock:2:deadbeef"), Some(v2));
174
175        // Also test parsing with upper case / padded hex
176        assert_eq!(
177            ProtocolVersion::from_magic(b"vsock:2:DEADBEEF"),
178            Some(ProtocolVersion::V2(0xdeadbeef))
179        );
180        assert_eq!(ProtocolVersion::from_magic(b"vsock:2:00000001"), Some(ProtocolVersion::V2(1)));
181
182        // Invalid magics
183        assert_eq!(ProtocolVersion::from_magic(b""), None);
184        assert_eq!(ProtocolVersion::from_magic(b"vsock"), None);
185        assert_eq!(ProtocolVersion::from_magic(b"vsock:3"), None);
186        assert_eq!(ProtocolVersion::from_magic(b"vsock:2:invalid"), None);
187    }
188
189    #[test]
190    fn test_protocol_version_negotiate() {
191        let target_v2 = ProtocolVersion::V2(0);
192        let host_v2 = ProtocolVersion::V2(0x12345678);
193        assert_eq!(target_v2.negotiate(&host_v2), Some(ProtocolVersion::V2(0x12345678)));
194        assert_eq!(target_v2.negotiate(&ProtocolVersion::V1), Some(ProtocolVersion::V1));
195        assert_eq!(target_v2.negotiate(&ProtocolVersion::V0), Some(ProtocolVersion::V0));
196
197        let target_v1 = ProtocolVersion::V1;
198        assert_eq!(target_v1.negotiate(&host_v2), Some(ProtocolVersion::V1));
199        assert_eq!(target_v1.negotiate(&ProtocolVersion::V1), Some(ProtocolVersion::V1));
200        assert_eq!(target_v1.negotiate(&ProtocolVersion::V0), Some(ProtocolVersion::V0));
201
202        let target_v0 = ProtocolVersion::V0;
203        assert_eq!(target_v0.negotiate(&host_v2), Some(ProtocolVersion::V0));
204        assert_eq!(target_v0.negotiate(&ProtocolVersion::V1), Some(ProtocolVersion::V0));
205        assert_eq!(target_v0.negotiate(&ProtocolVersion::V0), Some(ProtocolVersion::V0));
206    }
207
208    #[test]
209    fn test_protocol_version_features() {
210        assert!(!ProtocolVersion::V0.has_pause_packets());
211        assert!(ProtocolVersion::V1.has_pause_packets());
212        assert!(ProtocolVersion::V2(0).has_pause_packets());
213        assert!(ProtocolVersion::V2(0xabcdef).has_pause_packets());
214    }
215
216    #[test]
217    fn test_protocol_version_display() {
218        assert_eq!(format!("{}", ProtocolVersion::V0), "0");
219        assert_eq!(format!("{}", ProtocolVersion::V1), "1");
220        assert_eq!(format!("{}", ProtocolVersion::V2(0x1a2b)), "2:1a2b");
221    }
222}