Skip to main content

vmbus_core/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#![expect(missing_docs)]
5#![forbid(unsafe_code)]
6
7pub mod protocol;
8
9use futures::StreamExt;
10use guid::Guid;
11use inspect::Inspect;
12use protocol::HEADER_SIZE;
13use protocol::MAX_MESSAGE_SIZE;
14use protocol::MessageHeader;
15use protocol::VmbusMessage;
16use std::str::FromStr;
17use std::task::Poll;
18use thiserror::Error;
19use zerocopy::Immutable;
20use zerocopy::IntoBytes;
21use zerocopy::KnownLayout;
22
23/// The standard, non-redirected synthetic interrupt used by VMBus.
24pub const VMBUS_SINT: u8 = 2;
25
26#[derive(Debug)]
27pub struct TaggedStream<T, S>(Option<T>, S);
28
29impl<T: Clone, S: futures::Stream + Unpin> TaggedStream<T, S> {
30    pub fn new(t: T, s: S) -> Self {
31        Self(Some(t), s)
32    }
33
34    pub fn value(&self) -> Option<&T> {
35        self.0.as_ref()
36    }
37}
38
39impl<T: Clone, S: futures::Stream + Unpin> futures::Stream for TaggedStream<T, S>
40where
41    Self: Unpin,
42{
43    type Item = (T, Option<S::Item>);
44
45    fn poll_next(
46        self: std::pin::Pin<&mut Self>,
47        cx: &mut std::task::Context<'_>,
48    ) -> Poll<Option<Self::Item>> {
49        let this = self.get_mut();
50        if let Some(t) = this.0.clone() {
51            let v = std::task::ready!(this.1.poll_next_unpin(cx));
52            if v.is_none() {
53                // Return `None` next time poll_next is called.
54                this.0 = None;
55            }
56            Poll::Ready(Some((t, v)))
57        } else {
58            Poll::Ready(None)
59        }
60    }
61}
62
63/// Represents information about a negotiated version.
64#[derive(Copy, Clone, Debug, PartialEq, Eq, Inspect)]
65pub struct VersionInfo {
66    pub version: protocol::Version,
67    pub feature_flags: protocol::FeatureFlags,
68}
69
70/// Represents a constraint on the version or features allowed.
71#[derive(Copy, Clone, Debug)]
72pub struct MaxVersionInfo {
73    pub version: u32,
74    pub feature_flags: protocol::FeatureFlags,
75}
76
77impl MaxVersionInfo {
78    pub fn new(version: u32) -> Self {
79        Self {
80            version,
81            feature_flags: protocol::FeatureFlags::new(),
82        }
83    }
84}
85
86impl From<VersionInfo> for MaxVersionInfo {
87    fn from(info: VersionInfo) -> Self {
88        Self {
89            version: info.version as u32,
90            feature_flags: info.feature_flags,
91        }
92    }
93}
94
95/// Parses a string of the form "major.minor" (e.g "5.3") into a vmbus version number.
96///
97/// N.B. This doesn't check whether the specified version actually exists.
98pub fn parse_vmbus_version(value: &str) -> Result<u32, String> {
99    || -> Option<u32> {
100        let (major, minor) = value.split_once('.')?;
101        let major = u16::from_str(major).ok()?;
102        let minor = u16::from_str(minor).ok()?;
103        Some(protocol::make_version(major, minor))
104    }()
105    .ok_or_else(|| format!("invalid vmbus version '{}'", value))
106}
107
108#[derive(Clone, Debug)]
109pub struct OutgoingMessage {
110    data: [u8; MAX_MESSAGE_SIZE],
111    len: u8,
112}
113
114/// Represents a vmbus message to be sent using the synic.
115impl OutgoingMessage {
116    /// Creates a new `OutgoingMessage` for the specified protocol message.
117    pub fn new<T: IntoBytes + Immutable + KnownLayout + VmbusMessage>(message: &T) -> Self {
118        let mut data = [0; MAX_MESSAGE_SIZE];
119        let header = MessageHeader::new(T::MESSAGE_TYPE);
120        let message_bytes = message.as_bytes();
121        let len = HEADER_SIZE + message_bytes.len();
122        data[..HEADER_SIZE].copy_from_slice(header.as_bytes());
123        data[HEADER_SIZE..len].copy_from_slice(message_bytes);
124        Self {
125            data,
126            len: len as u8,
127        }
128    }
129
130    /// Creates a new `OutgoingMessage` for the specified protocol message, including additional
131    /// data at the end of the message.
132    pub fn with_data<T: IntoBytes + Immutable + KnownLayout + VmbusMessage>(
133        message: &T,
134        data: &[u8],
135    ) -> Self {
136        let mut message = OutgoingMessage::new(message);
137        let old_len = message.len as usize;
138        let len = old_len + data.len();
139        message.data[old_len..len].copy_from_slice(data);
140        message.len = len as u8;
141        message
142    }
143
144    /// Converts an existing binary message to an `OutgoingMessage`. The slice
145    /// is assumed to contain a valid message.
146    pub fn from_message(message: &[u8]) -> Result<Self, MessageTooLarge> {
147        if message.len() > MAX_MESSAGE_SIZE {
148            return Err(MessageTooLarge);
149        }
150        let mut data = [0; MAX_MESSAGE_SIZE];
151        data[0..message.len()].copy_from_slice(message);
152        Ok(Self {
153            data,
154            len: message.len() as u8,
155        })
156    }
157
158    /// Gets the binary representation of the message.
159    pub fn data(&self) -> &[u8] {
160        &self.data[..self.len as usize]
161    }
162}
163
164impl PartialEq for OutgoingMessage {
165    fn eq(&self, other: &Self) -> bool {
166        self.len == other.len && self.data[..self.len as usize] == other.data[..self.len as usize]
167    }
168}
169
170#[derive(Debug, Error)]
171#[error("a synic message exceeds the maximum length")]
172pub struct MessageTooLarge;
173
174/// A request from the guest to connect to the specified hvsocket endpoint.
175#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Inspect)]
176pub struct HvsockConnectRequest {
177    pub service_id: Guid,
178    pub endpoint_id: Guid,
179    pub silo_id: Guid,
180    pub hosted_silo_unaware: bool,
181}
182
183impl HvsockConnectRequest {
184    pub fn from_message(value: protocol::TlConnectRequest2, hosted_silo_unaware: bool) -> Self {
185        Self {
186            service_id: value.base.service_id,
187            endpoint_id: value.base.endpoint_id,
188            silo_id: value.silo_id,
189            hosted_silo_unaware,
190        }
191    }
192}
193
194impl From<HvsockConnectRequest> for protocol::TlConnectRequest2 {
195    fn from(value: HvsockConnectRequest) -> Self {
196        Self {
197            base: protocol::TlConnectRequest {
198                endpoint_id: value.endpoint_id,
199                service_id: value.service_id,
200            },
201            silo_id: value.silo_id,
202        }
203    }
204}
205
206/// A notification from the host that a connection request has been handled.
207#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
208pub struct HvsockConnectResult {
209    pub service_id: Guid,
210    pub endpoint_id: Guid,
211    pub success: bool,
212}
213
214impl HvsockConnectResult {
215    /// Create a new result using the service and endpoint ID from the specified request.
216    pub fn from_request(request: &HvsockConnectRequest, success: bool) -> Self {
217        Self {
218            service_id: request.service_id,
219            endpoint_id: request.endpoint_id,
220            success,
221        }
222    }
223}
224
225impl From<protocol::TlConnectResult> for HvsockConnectResult {
226    fn from(value: protocol::TlConnectResult) -> Self {
227        Self {
228            service_id: value.service_id,
229            endpoint_id: value.endpoint_id,
230            success: value.status == protocol::STATUS_SUCCESS,
231        }
232    }
233}
234
235#[cfg(test)]
236mod tests {
237    use super::*;
238    use crate::protocol::ChannelId;
239    use crate::protocol::GpadlId;
240
241    #[test]
242    fn test_outgoing_message() {
243        let message = OutgoingMessage::new(&protocol::CloseChannel {
244            channel_id: ChannelId(5),
245        });
246
247        assert_eq!(&[0x7, 0, 0, 0, 0, 0, 0, 0, 0x5, 0, 0, 0], message.data())
248    }
249
250    #[test]
251    fn test_outgoing_message_empty() {
252        let message = OutgoingMessage::new(&protocol::Unload {});
253
254        assert_eq!(&[0x10, 0, 0, 0, 0, 0, 0, 0], message.data())
255    }
256
257    #[test]
258    fn test_outgoing_message_with_data() {
259        let message = OutgoingMessage::with_data(
260            &protocol::GpadlHeader {
261                channel_id: ChannelId(5),
262                gpadl_id: GpadlId(1),
263                len: 7,
264                count: 6,
265            },
266            &[0xa, 0xb, 0xc, 0xd],
267        );
268
269        assert_eq!(
270            &[
271                0x8, 0, 0, 0, 0, 0, 0, 0, 0x5, 0, 0, 0, 0x1, 0, 0, 0, 0x7, 0, 0x6, 0, 0xa, 0xb,
272                0xc, 0xd
273            ],
274            message.data()
275        )
276    }
277}