diff --git a/vm-migration/src/protocol.rs b/vm-migration/src/protocol.rs index 7c14ec7bf..e3f899759 100644 --- a/vm-migration/src/protocol.rs +++ b/vm-migration/src/protocol.rs @@ -73,8 +73,22 @@ //! Source->>Destination: Complete //! Destination-->>Source: OK //! ``` +//! +//! ## Protocol Versioning +//! +//! `Start` carries the sender's migration protocol version. +//! A zeroed version field is treated as legacy protocol `v0`. +//! +//! The destination validates that version and replies with a plain `OK` or +//! `Error`. +//! +//! Only the current and immediately previous protocol versions are +//! supported. Compatibility is one-way, from older protocol versions +//! to newer ones. use std::io::{Read, Write}; +use std::mem::size_of; +use std::ops::RangeInclusive; use std::{mem, slice}; use itertools::Itertools; @@ -127,18 +141,33 @@ pub enum Command { CompletePaused = 8, } +/// Newest migration protocol version sent by this implementation. +pub const CURRENT_PROTOCOL_VERSION: u16 = 0; + +/// Returns the current migration protocol version and the previous version, if any. +pub fn supported_protocol_versions() -> RangeInclusive { + CURRENT_PROTOCOL_VERSION.saturating_sub(1)..=CURRENT_PROTOCOL_VERSION +} + #[repr(C)] #[derive(Default, Copy, Clone)] pub struct Request { command: Command, - padding: [u8; 6], - length: u64, // Length of payload for command excluding the Request struct + command_headers: [u8; 6], + /// Length of payload for command excluding the Request struct + length: u64, } // SAFETY: Request contains a series of integers with no implicit padding unsafe impl ByteValued for Request {} impl Request { + fn encode_sender_version(version: u16) -> [u8; 6] { + let mut command_headers = [0; 6]; + command_headers[..size_of::()].copy_from_slice(&version.to_le_bytes()); + command_headers + } + pub fn new(command: Command, length: u64) -> Self { Self { command, @@ -148,7 +177,11 @@ impl Request { } pub fn start() -> Self { - Self::new(Command::Start, 0) + Self { + command: Command::Start, + command_headers: Self::encode_sender_version(CURRENT_PROTOCOL_VERSION), + length: 0, + } } pub fn state(length: u64) -> Self { @@ -189,6 +222,10 @@ impl Request { self.length } + pub fn command_headers(&self) -> &[u8; 6] { + &self.command_headers + } + pub fn read_from(fd: &mut dyn Read) -> Result { let mut request = Request::default(); fd.read_exact(Self::as_mut_slice(&mut request)) @@ -485,7 +522,21 @@ impl MemoryRangeTable { #[cfg(test)] mod unit_tests { - use crate::protocol::{MemoryRange, MemoryRangeTable}; + use crate::protocol::{Command, MemoryRange, MemoryRangeTable, Request}; + + #[test] + fn test_start_request_ignores_residual_command_headers_bytes() { + let request = Request { + command: Command::Start, + command_headers: [1, 0, 0xaa, 0xbb, 0xcc, 0xdd], + length: 0, + }; + + assert_eq!( + u16::from_le_bytes([request.command_headers()[0], request.command_headers()[1]]), + 1 + ); + } #[test] fn test_memory_range_table_from_dirty_ranges_iter() {