1use virtio_accel_core::{ArtifactFormat, BackendError, ByteSource, TargetIdentity};
8
9const MAGIC: [u8; 4] = *b"VAMK";
10const VERSION_MAJOR: u8 = 1;
11const VERSION_MINOR: u8 = 0;
12const BINDING_ABI_VERSION: u8 = 1;
13const UNUSED_SLOT: u32 = u32::MAX;
14
15const OP_BARRIER: u8 = 0;
16const OP_COPY: u8 = 1;
17const OP_FILL: u8 = 2;
18const OP_XOR: u8 = 3;
19
20pub const ARTIFACT_FORMAT: ArtifactFormat = match ArtifactFormat::new(0x5641_4d4b) {
22 Some(format) => format,
23 None => panic!("reference artifact format must be nonzero"),
24};
25
26pub const TARGET_IDENTITY: TargetIdentity = TargetIdentity([
28 0x5641_4d4b,
29 0x0001_0000,
30 0x5445_5354,
31 0x4f4e_4c59,
32 0,
33 0,
34 0,
35 0,
36 0,
37 0,
38 0,
39 0,
40]);
41
42pub const ARTIFACT_BYTES: usize = 24;
44
45pub const RESIDENT_BYTES: u64 = 64;
47
48#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50pub(crate) enum Operation {
51 Barrier { slot: u32 },
52 Copy { source_slot: u32, target_slot: u32 },
53 Fill { target_slot: u32, value: u8 },
54 Xor { target_slot: u32, value: u8 },
55}
56
57#[derive(Clone, Copy, Debug, PartialEq, Eq)]
59pub struct ReferenceArtifact {
60 bytes: [u8; ARTIFACT_BYTES],
61}
62
63impl ReferenceArtifact {
64 pub fn barrier(slot: u32) -> Self {
68 Self::new(OP_BARRIER, slot, UNUSED_SLOT, 0)
69 }
70
71 pub fn copy(source_slot: u32, target_slot: u32) -> Result<Self, BackendError> {
73 if source_slot == target_slot {
74 return Err(BackendError::InvalidArgument);
75 }
76 Ok(Self::new(OP_COPY, source_slot, target_slot, 0))
77 }
78
79 pub fn fill(target_slot: u32, value: u8) -> Self {
81 Self::new(OP_FILL, target_slot, UNUSED_SLOT, value)
82 }
83
84 pub fn xor(target_slot: u32, value: u8) -> Self {
86 Self::new(OP_XOR, target_slot, UNUSED_SLOT, value)
87 }
88
89 pub const fn as_bytes(&self) -> &[u8; ARTIFACT_BYTES] {
90 &self.bytes
91 }
92
93 fn new(opcode: u8, first_slot: u32, second_slot: u32, operand: u8) -> Self {
94 let mut bytes = [0; ARTIFACT_BYTES];
95 bytes[0..4].copy_from_slice(&MAGIC);
96 bytes[4] = VERSION_MAJOR;
97 bytes[5] = VERSION_MINOR;
98 bytes[6] = opcode;
99 bytes[7] = BINDING_ABI_VERSION;
100 bytes[8..12].copy_from_slice(&first_slot.to_le_bytes());
101 bytes[12..16].copy_from_slice(&second_slot.to_le_bytes());
102 bytes[16] = operand;
103 Self { bytes }
104 }
105}
106
107impl AsRef<[u8]> for ReferenceArtifact {
108 fn as_ref(&self) -> &[u8] {
109 self.as_bytes()
110 }
111}
112
113pub(crate) fn decode(payload: &dyn ByteSource) -> Result<Operation, BackendError> {
114 if payload.len() != ARTIFACT_BYTES as u64 {
115 return Err(BackendError::InvalidArgument);
116 }
117
118 let mut bytes = [0; ARTIFACT_BYTES];
119 payload.read_at(0, &mut bytes)?;
120 if bytes[0..4] != MAGIC {
121 return Err(BackendError::InvalidArgument);
122 }
123 if bytes[4] != VERSION_MAJOR || bytes[5] != VERSION_MINOR || bytes[7] != BINDING_ABI_VERSION {
124 return Err(BackendError::Incompatible);
125 }
126 if bytes[17..].iter().any(|byte| *byte != 0) {
127 return Err(BackendError::InvalidArgument);
128 }
129
130 let first_slot = u32::from_le_bytes(bytes[8..12].try_into().unwrap());
131 let second_slot = u32::from_le_bytes(bytes[12..16].try_into().unwrap());
132 let operand = bytes[16];
133 match bytes[6] {
134 OP_BARRIER if second_slot == UNUSED_SLOT && operand == 0 => {
135 Ok(Operation::Barrier { slot: first_slot })
136 }
137 OP_COPY if first_slot != second_slot && operand == 0 => Ok(Operation::Copy {
138 source_slot: first_slot,
139 target_slot: second_slot,
140 }),
141 OP_FILL if second_slot == UNUSED_SLOT => Ok(Operation::Fill {
142 target_slot: first_slot,
143 value: operand,
144 }),
145 OP_XOR if second_slot == UNUSED_SLOT => Ok(Operation::Xor {
146 target_slot: first_slot,
147 value: operand,
148 }),
149 OP_BARRIER | OP_COPY | OP_FILL | OP_XOR => Err(BackendError::InvalidArgument),
150 _ => Err(BackendError::Unsupported),
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn builders_round_trip_through_the_fixed_envelope() {
160 for (artifact, expected) in [
161 (
162 ReferenceArtifact::barrier(9),
163 Operation::Barrier { slot: 9 },
164 ),
165 (
166 ReferenceArtifact::copy(2, 7).unwrap(),
167 Operation::Copy {
168 source_slot: 2,
169 target_slot: 7,
170 },
171 ),
172 (
173 ReferenceArtifact::fill(3, 0xa5),
174 Operation::Fill {
175 target_slot: 3,
176 value: 0xa5,
177 },
178 ),
179 (
180 ReferenceArtifact::xor(4, 0x5a),
181 Operation::Xor {
182 target_slot: 4,
183 value: 0x5a,
184 },
185 ),
186 ] {
187 assert_eq!(decode(artifact.as_bytes()), Ok(expected));
188 }
189 assert_eq!(
190 ReferenceArtifact::copy(2, 2),
191 Err(BackendError::InvalidArgument)
192 );
193 }
194
195 #[test]
196 fn malformed_and_incompatible_envelopes_are_distinguished() {
197 let mut malformed = ReferenceArtifact::barrier(0).bytes;
198 malformed[17] = 1;
199 assert_eq!(decode(&malformed), Err(BackendError::InvalidArgument));
200
201 let mut incompatible = ReferenceArtifact::barrier(0).bytes;
202 incompatible[4] += 1;
203 assert_eq!(decode(&incompatible), Err(BackendError::Incompatible));
204
205 let mut unsupported = ReferenceArtifact::barrier(0).bytes;
206 unsupported[6] = 0xff;
207 assert_eq!(decode(&unsupported), Err(BackendError::Unsupported));
208 }
209}