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)]
16pub enum ProtocolVersion {
17 /// Protocol version 0
18 V0,
19 /// Protocol version 1
20 V1,
21}
22
23impl ProtocolVersion {
24 /// The latest protocol version.
25 pub const LATEST: ProtocolVersion = ProtocolVersion::V1;
26
27 /// Magic sent in the sync packet of the USB protocol.
28 ///
29 /// The format is a byte string of the form `vsock:0`, where the 0 indicates
30 /// protocol version 0, and we expect the reply sync packet to have the
31 /// exact same contents. As we version the protocol this may increment.
32 ///
33 /// To document the semantics, let's say this header were "vsock:3". The device
34 /// could reply with a lower number, say "vsock:1". This is the device
35 /// requesting a downgrade, and if we accept we send the final sync with
36 /// "vsock:1". Otherwise we hang up.
37 pub fn magic(&self) -> &[u8] {
38 match self {
39 ProtocolVersion::V0 => b"vsock:0",
40 ProtocolVersion::V1 => b"vsock:1",
41 }
42 }
43
44 /// Derive the protocol version from the magic sent in the sync packet.
45 pub fn from_magic(magic: &[u8]) -> Option<ProtocolVersion> {
46 if magic == b"vsock:0" {
47 Some(ProtocolVersion::V0)
48 } else if magic == b"vsock:1" {
49 Some(ProtocolVersion::V1)
50 } else {
51 None
52 }
53 }
54
55 /// Given `self` is the protocol version the target prefers and
56 /// `host_version` is the protocol version sent in the magic as the host
57 /// connects, find the protocol version that should be sent in the reply
58 /// magic and used for the connection. If `None`, negotiation has broken
59 /// down.
60 pub fn negotiate(&self, host_version: &ProtocolVersion) -> Option<ProtocolVersion> {
61 match (self, host_version) {
62 (ProtocolVersion::V1, ProtocolVersion::V1) => Some(ProtocolVersion::V1),
63 (ProtocolVersion::V0, _) | (_, ProtocolVersion::V0) => Some(ProtocolVersion::V0),
64 }
65 }
66
67 /// Whether we support the pause protocol message.
68 pub(crate) fn has_pause_packets(&self) -> bool {
69 matches!(self, ProtocolVersion::V1)
70 }
71}
72
73impl std::fmt::Display for ProtocolVersion {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 match self {
76 ProtocolVersion::V0 => write!(f, "0"),
77 ProtocolVersion::V1 => write!(f, "1"),
78 }
79 }
80}
81
82/// A placeholder CID indicating "any" CID is acceptable.
83pub const CID_ANY: u32 = u32::MAX;
84
85/// CID of the host.
86pub const CID_HOST: u32 = 2;
87
88/// The loopback CID.
89pub const CID_LOOPBACK: u32 = 1;
90
91/// An address for a vsock packet transmitted over USB. Since this library does not implement
92/// policy decisions, it includes all four components of a vsock address pair even though some
93/// may not be appropriate for some situations.
94#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
95pub struct Address {
96 /// For Connect, Reset, Accept, and Data packets this represents the device side's address.
97 /// Usually this will be a special value representing either that it is simply "the device",
98 /// or zero along with the rest of the cid and port fields to indicate that it's a control stream
99 /// packet. Must be zero for any other packet type.
100 pub device_cid: u32,
101 /// For Connect, Reset, Accept, and Data packets this represents the host side's address.
102 /// Usually this will be a special value representing either that it is simply "the host",
103 /// or zero along with the rest of the cid and port fields to indicate that it's a control stream
104 /// packet. Must be zero for any other packet type.
105 pub host_cid: u32,
106 /// For Connect, Reset, Accept, and Data packets this represents the device side's port.
107 /// This must be a valid positive value for any of those packet types, unless all of the cid and
108 /// port fields are also zero, in which case it is a control stream packet. Must be zero for any
109 /// other packet type.
110 pub device_port: u32,
111 /// For Connect, Reset, Accept, and Data packets this represents the host side's port.
112 /// This must be a valid positive value for any of those packet types, unless all of the cid and
113 /// port fields are also zero, in which case it is a control stream packet. Must be zero for any
114 /// other packet type.
115 pub host_port: u32,
116}
117
118impl Address {
119 /// Returns true if all the fields of this address are zero (which usually means it's a control
120 /// packet of some sort).
121 pub fn is_zeros(&self) -> bool {
122 *self == Self::default()
123 }
124}
125
126impl From<&Header> for Address {
127 fn from(header: &Header) -> Self {
128 Self {
129 device_cid: header.device_cid.get(),
130 host_cid: header.host_cid.get(),
131 device_port: header.device_port.get(),
132 host_port: header.host_port.get(),
133 }
134 }
135}