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