proxmox-backup/proxmox-protocol/src/protocol.rs

314 lines
8.7 KiB
Rust
Raw Normal View History

use std::mem;
use endian_trait::Endian;
// There's no reason to have more than that in a single packet...
pub const MAX_PACKET_SIZE: u32 = 16 * 1024 * 1024;
// Each packet has a transaction ID (eg. when uploading multiple disks each
// upload is a separate stream).
#[derive(Endian)]
#[repr(C, packed)]
pub struct Packet {
pub id: u8, // request/command id
pub pkttype: u8, // packet type
pub length: u32, // data length before the next packet
// content is attached directly afterwards
}
impl Packet {
pub fn builder(id: u8, pkttype: PacketType) -> PacketBuilder {
PacketBuilder::new(id, pkttype)
}
pub fn simple(id: u8, pkttype: PacketType) -> Vec<u8> {
Self::builder(id, pkttype).finish()
}
}
#[derive(Endian, Clone, Copy)]
#[repr(u8)]
pub enum ErrorId {
Generic,
Busy,
}
#[repr(u8)]
#[derive(Clone, Copy)]
pub enum PacketType {
/// First packet sent by the server.
Hello,
/// Generic acknowledgement.
Ok,
/// Error packet sent by the server, this cancels the request for which it is produced.
Error,
/// The client wants the list of available hashes in order to know which ones require an
/// upload.
///
/// The body should contain a backup file name of which to retrieve the hash list.
///
/// Server responds with a sequence of ``HashListPart`` packets.
GetHashList,
/// Array of hashes. The number of hashes in a packet is calculated from the packet length as
/// provided in the ``Packet`` struct. An empty packet indicates the end of the list. We send a
/// sequence of such packets because we don't know whether the server will be keeping the list
/// in memory yet, so it might not know the number in advance and may be iterating through
/// directories until it hits an end. It can produce the network packets asynchronously while
/// walking the chunk dir.
HashListPart,
/// Client requests to download chunks via a hash list from the server. The number of chunks
/// can be derived from the length of this request, so it works similar to ``HashListPart``,
/// but there's only 1 chunk list per request ID.
///
/// The server responds with a sequence of ``Chunk`` packets or ``Error``.
DownloadChunks,
/// The response to ``DownloadChunks``. One packet per requested chunk.
Chunk,
/// The upload of a chunk can happen independent from the ongoing backup
/// streams. Server responds with an ``OK``.
UploadChunk,
/// Create a file in a new or existing backup. Contains all the metadata of
/// a file.
///
/// The server responds with ``BackupCreated`` or ``Error``. On ``BackupCreated`` the client
/// may proceed to send as many ``BackupData...`` packets as necessary to fill the file.
/// The sequence is finished by the client with a ``BackupFinished``.
CreateBackup,
/// Successful from the server to a client's ``CreateBackup`` packet. Contains the server side
/// path relative to the store.
BackupCreated,
/// This packet contains an array of references to fixed sized chunks. Clients should upload
/// chunks via ``UploadChunk`` packets before using them in this type of packet. A non-existent
/// chunk is an error.
///
/// The server produces an ``Error`` packet in case of an error.
BackupDataFixed,
/// This packet contains an array of references to dynamic sized chunks. Clients should upload
/// chunks via ``UploadChunk`` packets before using them in this type of packet. A non-existent
/// chunk is an error.
///
/// The server produces an ``Error`` packet in case of an error.
BackupDataDynamic,
/// This ends a backup file. The server responds with an ``OK`` or an ``Error`` packet.
BackupFinished,
}
// Nightly has a std::convert::TryFrom, actually...
impl PacketType {
pub fn try_from(v: u8) -> Option<Self> {
if v <= PacketType::BackupFinished as u8 {
Some(unsafe { std::mem::transmute(v) })
} else {
None
}
}
}
// Not using bitflags! for Endian derive...
pub mod backup_flags {
pub type Type = u8;
/// The backup must not exist yet.
pub const EXCL: Type = 0x00000001;
/// The data represents a raw file
pub const RAW: Type = 0x00000002;
/// The data uses dynamically sized chunks (catar file)
pub const DYNAMIC_CHUNKS: Type = 0x00000004;
}
pub mod backup_type {
pub type Type = u8;
pub const VM: Type = 0;
pub const CT: Type = 1;
pub const HOST: Type = 2;
use failure::{bail, Error};
pub fn id_to_name(id: Type) -> Result<&'static str, Error> {
Ok(match id {
VM => "vm",
CT => "ct",
HOST => "host",
n => bail!("unknown backup type id: {}", n),
})
}
pub fn name_to_id(id: &str) -> Result<Type, Error> {
Ok(match id {
"vm" => VM,
"ct" => CT,
"host" => HOST,
n => bail!("unknown backup type name: {}", n),
})
}
}
#[repr(C, packed)]
pub struct DynamicChunk {
pub offset: u64,
pub digest: [u8; 32],
}
pub mod server {
use endian_trait::Endian;
pub const PROTOCOL_VERSION: u32 = 1;
#[derive(Eq, PartialEq)]
#[repr(C, packed)]
pub struct HelloMagic([u8; 8]);
pub const HELLO_MAGIC: HelloMagic = HelloMagic(*b"PMXBCKUP");
pub const HELLO_VERSION: u32 = 1; // the current version
#[derive(Endian)]
#[repr(C, packed)]
pub struct Hello {
pub magic: HelloMagic,
pub version: u32,
}
#[derive(Endian)]
#[repr(C, packed)]
pub struct Error {
pub id: u8,
}
#[derive(Endian)]
#[repr(C, packed)]
pub struct Chunk {
pub hash: super::DynamicChunk,
// Data follows here...
}
impl Endian for HelloMagic {
fn to_be(self) -> Self {
self
}
fn to_le(self) -> Self {
self
}
fn from_be(self) -> Self {
self
}
fn from_le(self) -> Self {
self
}
}
#[derive(Endian)]
#[repr(C, packed)]
pub struct BackupCreated {
pub path_length: u16,
// path follows here
}
}
pub mod client {
use endian_trait::Endian;
#[derive(Endian)]
#[repr(C, packed)]
pub struct UploadChunk {
pub hash: crate::FixedChunk,
}
#[derive(Endian)]
#[repr(C, packed)]
pub struct CreateBackup {
pub backup_type: super::backup_type::Type,
pub id_length: u8, // length of the ID string
pub timestamp: u64, // seconds since the epoch
pub flags: super::backup_flags::Type,
pub name_length: u8, // file name length
pub chunk_size: u32, // average or "fixed" chunk size
pub file_size: u64, // size for fixed size files (must be 0 if DYNAMIC_CHUNKS is set)
// ``id_length`` bytes of ID follow
// ``name_length`` bytes of file name follow
// Further packets contain the data or chunks
}
#[derive(Endian)]
#[repr(C, packed)]
pub struct GetHashList {
pub name_length: u16,
// name follows as payload
}
}
impl Endian for DynamicChunk {
fn to_be(self) -> Self {
self
}
fn to_le(self) -> Self {
self
}
fn from_be(self) -> Self {
self
}
fn from_le(self) -> Self {
self
}
}
pub struct PacketBuilder {
data: Vec<u8>,
}
impl PacketBuilder {
pub fn new(id: u8, pkttype: PacketType) -> Self {
let data = Vec::with_capacity(mem::size_of::<Packet>());
let mut me = Self { data };
me.write_data(
Packet {
id,
pkttype: pkttype as _,
length: 0,
}
.to_le(),
);
me
}
pub fn reserve(&mut self, more: usize) -> &mut Self {
self.data.reserve(more);
self
}
pub fn write_buf(&mut self, buf: &[u8]) -> &mut Self {
self.data.extend_from_slice(buf);
self
}
pub fn write_data<T: Endian>(&mut self, data: T) -> &mut Self {
self.write_data_noswap(&data.to_le())
}
pub fn write_data_noswap<T>(&mut self, data: &T) -> &mut Self {
self.write_buf(unsafe {
std::slice::from_raw_parts(data as *const T as *const u8, mem::size_of::<T>())
})
}
pub fn finish(mut self) -> Vec<u8> {
let length = self.data.len();
assert!(length >= mem::size_of::<Packet>());
unsafe {
let head = self.data.as_mut_ptr() as *mut Packet;
std::ptr::write_unaligned((&mut (*head).length) as *mut u32, (length as u32).to_le());
}
self.data
}
}