neotron_bmc_protocol/
lib.rs

1#![doc = include_str!("../README.md")]
2#![cfg_attr(not(test), no_std)]
3
4// ============================================================================
5// Modules and Imports
6// ============================================================================
7
8#[cfg(feature = "defmt")]
9use defmt::Format;
10
11mod crc;
12
13// ============================================================================
14// Traits
15// ============================================================================
16
17/// Marks an object as being sendable over a byte-oriented communications link.
18pub trait Sendable {
19	/// Convert to bytes for transmission.
20	///
21	/// Copies into the given buffer, giving an error if it isn't large enough.
22	fn render_to_buffer(&self, buffer: &mut [u8]) -> Result<usize, Error>;
23}
24
25/// Marks an object as being receivable over a byte-oriented communications link.
26pub trait Receivable<'a>: Sized {
27	/// Convert from received bytes.
28	///
29	/// You get `Err` if `data` is not long enough, or if there was a CRC error.
30	fn from_bytes(data: &'a [u8]) -> Result<Self, Error> {
31		let crc = calculate_crc(data);
32		Self::from_bytes_with_crc(data, crc)
33	}
34
35	/// Convert from received bytes and a pre-calculated CRC.
36	///
37	/// You get `Err` if `data` is not long enough, or if there was a CRC error.
38	fn from_bytes_with_crc(data: &'a [u8], calc_crc: u8) -> Result<Self, Error>;
39}
40
41// ============================================================================
42// Enums
43// ============================================================================
44
45/// The ways this API can fail
46#[derive(Debug, Copy, Clone, PartialEq, Eq)]
47#[cfg_attr(feature = "defmt", derive(Format))]
48pub enum Error {
49	BadCrc,
50	BadLength,
51	BadRequestType,
52	BufferTooSmall,
53	BadResponseResult,
54}
55
56/// The kinds of [`Request`] the *Host* can make to the NBMC
57#[derive(
58	Debug, Copy, Clone, PartialEq, Eq, num_enum::IntoPrimitive, num_enum::TryFromPrimitive,
59)]
60#[cfg_attr(feature = "defmt", derive(Format))]
61#[repr(u8)]
62pub enum RequestType {
63	Read = 0xC0,
64	ReadAlt = 0xC1,
65	ShortWrite = 0xC2,
66	ShortWriteAlt = 0xC3,
67	LongWrite = 0xC4,
68	LongWriteAlt = 0xC5,
69}
70
71/// The NBMC returns this code to indicate whether the previous [`Request`] was
72/// succesful or not.
73#[derive(
74	Debug, Copy, Clone, PartialEq, Eq, num_enum::IntoPrimitive, num_enum::TryFromPrimitive,
75)]
76#[cfg_attr(feature = "defmt", derive(Format))]
77#[repr(u8)]
78pub enum ResponseResult {
79	/// The [`Request`] was correctly understood and actioned.
80	Ok = 0xA0,
81	/// The [`Request`] was not correctly understood because the CRC did not match.
82	///
83	/// The message may have been corrupted in-flight (e.g. a byte dropped, or a bit flipped).
84	CrcFailure = 0xA1,
85	/// The [`Request`] was received correctly but the Request Type was not known.
86	///
87	/// Did you check the Protocol Version was supported?
88	BadRequestType = 0xA2,
89	/// The [`Request`] was received correctly but the requested Register was not known.
90	///
91	/// Did you check the Protocol Version was supported?
92	BadRegister = 0xA3,
93	/// The [`Request`] was received correctly but the given number of bytes
94	/// could not be read from or written to the given Register..
95	///
96	/// Did you check the Protocol Version was supported?
97	BadLength = 0xA4,
98}
99
100// ============================================================================
101// Structs
102// ============================================================================
103
104/// A *Request* made by the *Host* to the *NBMC*
105#[derive(Debug, Clone, PartialEq, Eq)]
106#[cfg_attr(feature = "defmt", derive(Format))]
107pub struct Request {
108	pub request_type: RequestType,
109	pub register: u8,
110	pub length_or_data: u8,
111	crc: u8,
112}
113
114/// A *Response* sent by the *NBMC* in reply to a [`Request`] from a *Host*
115#[derive(Debug, Clone, PartialEq, Eq)]
116#[cfg_attr(feature = "defmt", derive(Format))]
117pub struct Response<'a> {
118	pub result: ResponseResult,
119	pub data: &'a [u8],
120	crc: u8,
121}
122
123/// Describes the [semantic version](https://semver.org) of this implementation
124/// of the NBMC interface.
125#[derive(Debug, Copy, Clone, PartialEq, Eq)]
126#[cfg_attr(feature = "defmt", derive(Format))]
127pub struct ProtocolVersion {
128	major: u8,
129	minor: u8,
130	patch: u8,
131}
132
133// ============================================================================
134// Impls
135// ============================================================================
136
137impl RequestType {
138	/// Converts an 'alt' command into a regular command.
139	///
140	/// Once you've checked for duplicates, you don't care which you've got.
141	pub fn flatten(self) -> Self {
142		match self {
143			RequestType::LongWrite | RequestType::LongWriteAlt => RequestType::LongWrite,
144			RequestType::ShortWrite | RequestType::ShortWriteAlt => RequestType::ShortWrite,
145			RequestType::Read | RequestType::ReadAlt => RequestType::Read,
146		}
147	}
148}
149
150impl Request {
151	/// Make a new Read Request, requesting the given register and number of
152	/// bytes.
153	///
154	/// Setting `use_alt` to true will use the alternate Request Type. You
155	/// should flip this for every successive call so that duplicate reads can
156	/// be detected.
157	pub fn new_read(use_alt: bool, register: u8, length: u8) -> Request {
158		let mut req = Request {
159			request_type: if use_alt {
160				RequestType::ReadAlt
161			} else {
162				RequestType::Read
163			},
164			register,
165			length_or_data: length,
166			crc: 0x00,
167		};
168		let bytes = req.as_bytes();
169		req.crc = calculate_crc(&bytes[0..=2]);
170		req
171	}
172
173	/// Make a new Short Write Request, writing the given byte to the given register.
174	///
175	/// Setting `use_alt` to true will use the alternate Request Type. You
176	/// should flip this for every successive call so that duplicate reads can
177	/// be detected.
178	pub fn new_short_write(use_alt: bool, register: u8, data: u8) -> Request {
179		let mut req = Request {
180			request_type: if use_alt {
181				RequestType::ShortWriteAlt
182			} else {
183				RequestType::ShortWrite
184			},
185			register,
186			length_or_data: data,
187			crc: 0x00,
188		};
189		let bytes = req.as_bytes();
190		req.crc = calculate_crc(&bytes[0..=2]);
191		req
192	}
193
194	/// Make a new Long Write Request, asking for a number of bytes to be
195	/// written to the given register.
196	///
197	/// Setting `use_alt` to true will use the alternate Request Type. You
198	/// should flip this for every successive call so that duplicate reads can
199	/// be detected.
200	pub fn new_long_write(use_alt: bool, register: u8, length: u8) -> Request {
201		let mut req = Request {
202			request_type: if use_alt {
203				RequestType::LongWriteAlt
204			} else {
205				RequestType::LongWrite
206			},
207			register,
208			length_or_data: length,
209			crc: 0x00,
210		};
211		let bytes = req.as_bytes();
212		req.crc = calculate_crc(&bytes[0..=2]);
213		req
214	}
215
216	/// Convert to bytes for transmission.
217	///
218	/// Produces a fixed sized buffer.
219	pub const fn as_bytes(&self) -> [u8; 4] {
220		[
221			self.request_type as u8,
222			self.register,
223			self.length_or_data,
224			self.crc,
225		]
226	}
227}
228
229impl Sendable for Request {
230	/// Convert to bytes for transmission.
231	///
232	/// Copies into the given buffer, giving an error if it isn't large enough.
233	fn render_to_buffer(&self, buffer: &mut [u8]) -> Result<usize, Error> {
234		let bytes = self.as_bytes();
235		if buffer.len() < bytes.len() {
236			return Err(Error::BufferTooSmall);
237		}
238		for (src, dest) in bytes.iter().zip(buffer.iter_mut()) {
239			*dest = *src;
240		}
241		Ok(bytes.len())
242	}
243}
244
245impl<'a> Receivable<'a> for Request {
246	/// Convert from received bytes.
247	///
248	/// You get `Err` if the bytes could not be decoded.
249	///
250	/// ```
251	/// # use neotron_bmc_protocol::{Request, Receivable};
252	/// let bytes = [0xC0, 0x11, 0x03, 0xC6];
253	/// let req = Request::from_bytes(&bytes).unwrap();
254	/// ```
255	fn from_bytes(data: &'a [u8]) -> Result<Request, Error> {
256		if data.len() < 4 {
257			return Err(Error::BadLength);
258		}
259		Request::from_bytes_with_crc(data, calculate_crc(&data[0..=3]))
260	}
261
262	/// Convert from received bytes, when the CRC is pre-calculated.
263	///
264	/// Use this if you were calculating the CRC on-the-fly, e.g. with a
265	/// hardware CRC calculator.
266	///
267	/// You get `Err` if the bytes could not be decoded.
268	///
269	/// ```
270	/// # use neotron_bmc_protocol::{Request, Receivable};
271	/// let bytes = [0xC0, 0x11, 0x03, 0xC6];
272	/// let req = Request::from_bytes(&bytes).unwrap();
273	/// ```
274	fn from_bytes_with_crc(data: &'a [u8], calc_crc: u8) -> Result<Request, Error> {
275		if data.len() < 4 {
276			return Err(Error::BadLength);
277		}
278		if calc_crc != 0 {
279			// It's a quirk of CRC-8 that including the CRC always produces a
280			// result of zero.
281			return Err(Error::BadCrc);
282		}
283		Ok(Request {
284			request_type: data[0].try_into().map_err(|_| Error::BadRequestType)?,
285			register: data[1],
286			length_or_data: data[2],
287			crc: data[3],
288		})
289	}
290}
291
292impl<'a> Response<'a> {
293	/// Make a new OK response, with some optional data
294	pub fn new_ok_with_data(data: &'a [u8]) -> Response<'a> {
295		Response {
296			result: ResponseResult::Ok,
297			data,
298			crc: {
299				let mut crc = crc::init();
300				crc = crc::update(crc, &[ResponseResult::Ok as u8]);
301				crc = crc::update(crc, data);
302				crc::finalize(crc)
303			},
304		}
305	}
306
307	/// Make a new error response
308	pub fn new_without_data(result: ResponseResult) -> Response<'a> {
309		Response {
310			result,
311			data: &[],
312			crc: calculate_crc(&[result as u8]),
313		}
314	}
315}
316
317impl<'a> Sendable for Response<'a> {
318	/// Convert to bytes for transmission.
319	///
320	/// Copies into the given buffer, giving an error if it isn't large enough.
321	///
322	/// ```
323	/// # use neotron_bmc_protocol::{Response, ResponseResult, Sendable};
324	/// let mut buffer = [0u8; 5];
325	///
326	/// let req = Response::new_ok_with_data(&[]);
327	/// assert_eq!(req.render_to_buffer(&mut buffer).unwrap(), 2);
328	/// assert_eq!(&buffer[0..=1], [0xA0, 0x69]);
329	///
330	/// let req = Response::new_ok_with_data(&[0x00, 0x01]);
331	/// assert_eq!(req.render_to_buffer(&mut buffer).unwrap(), 4);
332	/// assert_eq!(&buffer[0..=3], [0xA0, 0x00, 0x01, 0x4F]);
333	///
334	/// let req = Response::new_without_data(ResponseResult::BadRequestType);
335	/// assert_eq!(req.render_to_buffer(&mut buffer).unwrap(), 2);
336	/// assert_eq!(&buffer[0..=1], [0xA2, 0x67]);
337	/// ```
338	fn render_to_buffer(&self, buffer: &mut [u8]) -> Result<usize, Error> {
339		let len = 1 + self.data.len() + 1;
340		if buffer.len() < len {
341			return Err(Error::BufferTooSmall);
342		}
343		buffer[0] = self.result as u8;
344		for (src, dest) in self.data.iter().zip(buffer[1..].iter_mut()) {
345			*dest = *src;
346		}
347		buffer[len - 1] = self.crc;
348		Ok(len)
349	}
350}
351
352impl<'a> Receivable<'a> for Response<'a> {
353	/// Convert from received bytes.
354	///
355	/// You get `Err` if the bytes could not be decoded.
356	///
357	/// ```
358	/// # use neotron_bmc_protocol::{Response, Receivable};
359	/// let bytes = [0xA0, 0x00, 0x01, 0x4F];
360	/// let req = Response::from_bytes(&bytes).unwrap();
361	///
362	/// ```
363	fn from_bytes_with_crc(data: &'a [u8], calc_crc: u8) -> Result<Response<'a>, Error> {
364		if calc_crc != 0 {
365			// It's a quirk of CRC-8 that including the CRC always produces a
366			// result of zero.
367			return Err(Error::BadCrc);
368		}
369		Ok(Response {
370			result: data[0].try_into().map_err(|_| Error::BadResponseResult)?,
371			data: &data[1..=(data.len() - 2)],
372			crc: data[data.len() - 1],
373		})
374	}
375}
376
377impl ProtocolVersion {
378	/// Construct a new [`ProtocolVersion`].
379	///
380	/// This isn't a message but instead can form part of a message. For
381	/// example, you should have a register address which provides the version
382	/// of the NBMC protocol implemented.
383	///
384	/// Pass in the major version, the minor version and the patch version.
385	pub const fn new(major: u8, minor: u8, patch: u8) -> ProtocolVersion {
386		ProtocolVersion {
387			major,
388			minor,
389			patch,
390		}
391	}
392
393	/// Check if this [`ProtocolVersion`] is compatible with `my_version`.
394	///
395	/// ```
396	/// # use neotron_bmc_protocol::ProtocolVersion;
397	/// let my_version = ProtocolVersion::new(1, 1, 0);
398	///
399	/// // This is compatible.
400	/// let bmc_a = ProtocolVersion::new(1, 1, 1);
401	/// assert!(bmc_a.is_compatible_with(&my_version));
402	///
403	/// // This is incompatible - patch is too low.
404	/// let bmc_b = ProtocolVersion::new(1, 0, 0);
405	/// assert!(!bmc_b.is_compatible_with(&my_version));
406	///
407	/// // This is incompatible - major is too high.
408	/// let bmc_c = ProtocolVersion::new(2, 0, 0);
409	/// assert!(!bmc_c.is_compatible_with(&my_version));
410	///
411	/// // This is incompatible - major is too low.
412	/// let bmc_d = ProtocolVersion::new(0, 1, 0);
413	/// assert!(!bmc_d.is_compatible_with(&my_version));
414	/// ```
415	pub const fn is_compatible_with(&self, my_version: &ProtocolVersion) -> bool {
416		if self.major == my_version.major {
417			if self.minor > my_version.minor {
418				true
419			} else if self.minor == my_version.minor {
420				self.patch >= my_version.patch
421			} else {
422				false
423			}
424		} else {
425			false
426		}
427	}
428
429	/// Convert to bytes for transmission.
430	pub const fn as_bytes(&self) -> [u8; 3] {
431		[self.major, self.minor, self.patch]
432	}
433}
434
435impl Sendable for ProtocolVersion {
436	fn render_to_buffer(&self, buffer: &mut [u8]) -> Result<usize, Error> {
437		let bytes = self.as_bytes();
438		if buffer.len() < bytes.len() {
439			return Err(Error::BufferTooSmall);
440		}
441		for (src, dest) in bytes.iter().zip(buffer.iter_mut()) {
442			*dest = *src;
443		}
444		Ok(bytes.len())
445	}
446}
447
448// ============================================================================
449// Functions
450// ============================================================================
451
452/// An object for calculating CRC8 values on-the-fly.
453pub struct CrcCalc(u8);
454
455impl CrcCalc {
456	/// Make a new CRC calculator
457	pub const fn new() -> CrcCalc {
458		CrcCalc(crc::init())
459	}
460
461	/// Reset the CRC calculator
462	pub fn reset(&mut self) {
463		self.0 = crc::init();
464	}
465
466	/// Add one byte to the CRC calculator
467	pub fn add(&mut self, byte: u8) {
468		self.0 = crc::update(self.0, &[byte]);
469	}
470
471	/// Add several bytes to the CRC calculator
472	pub fn add_buffer(&mut self, bytes: &[u8]) {
473		self.0 = crc::update(self.0, bytes);
474	}
475
476	/// Get the CRC
477	pub fn get(&self) -> u8 {
478		crc::finalize(self.0)
479	}
480}
481
482/// Calculates the CRC-8 of the given bytes.
483///
484/// ```
485/// # use neotron_bmc_protocol::calculate_crc;
486/// assert_eq!(calculate_crc(&[0xC0, 0x11, 0x03]), 0xC6);
487/// assert_eq!(calculate_crc(&[0xA0]), 0x69);
488/// assert_eq!(calculate_crc(&[0xA0, 0x69]), 0x00);
489/// ```
490pub fn calculate_crc(data: &[u8]) -> u8 {
491	let mut crc_calc = CrcCalc::new();
492	crc_calc.add_buffer(data);
493	crc_calc.get()
494}
495
496// ============================================================================
497// Tests
498// ============================================================================
499
500#[cfg(test)]
501mod test {
502	use super::*;
503
504	#[test]
505	fn read_request() {
506		let req = Request::new_read(false, 0x10, 0x20);
507		let bytes = req.as_bytes();
508		assert_eq!(bytes, [0xC0, 0x10, 0x20, 0x3A]);
509		let decoded_req = Request::from_bytes(&bytes).unwrap();
510		assert_eq!(req, decoded_req);
511	}
512
513	#[test]
514	fn read_request_alt() {
515		let req = Request::new_read(true, 0x10, 0x20);
516		let bytes = req.as_bytes();
517		assert_eq!(bytes, [0xC1, 0x10, 0x20, 0x51]);
518		let decoded_req = Request::from_bytes(&bytes).unwrap();
519		assert_eq!(req, decoded_req);
520	}
521
522	#[test]
523	fn short_write_request() {
524		let req = Request::new_short_write(false, 0x11, 0x22);
525		let bytes = req.as_bytes();
526		assert_eq!(bytes, [0xC2, 0x11, 0x22, 0xF7]);
527		let decoded_req = Request::from_bytes(&bytes).unwrap();
528		assert_eq!(req, decoded_req);
529	}
530
531	#[test]
532	fn short_write_request_alt() {
533		let req = Request::new_short_write(true, 0x11, 0x22);
534		let bytes = req.as_bytes();
535		assert_eq!(bytes, [0xC3, 0x11, 0x22, 0x9C]);
536		let decoded_req = Request::from_bytes(&bytes).unwrap();
537		assert_eq!(req, decoded_req);
538	}
539
540	#[test]
541	fn long_write_request() {
542		let req = Request::new_long_write(false, 0x0F, 0x50);
543		let bytes = req.as_bytes();
544		assert_eq!(bytes, [0xC4, 0x0F, 0x50, 0x52]);
545		let decoded_req = Request::from_bytes(&bytes).unwrap();
546		assert_eq!(req, decoded_req);
547	}
548
549	#[test]
550	fn long_write_request_alt() {
551		let req = Request::new_long_write(true, 0x0F, 0x50);
552		let bytes = req.as_bytes();
553		assert_eq!(bytes, [0xC5, 0x0F, 0x50, 0x39]);
554		let decoded_req = Request::from_bytes(&bytes).unwrap();
555		assert_eq!(req, decoded_req);
556	}
557}
558
559// ============================================================================
560// End of File
561// ============================================================================