espflash/
command.rs

1//! Commands to work with a flasher stub running on a target device
2
3use std::{io::Write, mem::size_of, time::Duration};
4
5use bytemuck::{Pod, Zeroable, bytes_of};
6use serde::{Deserialize, Serialize};
7use strum::Display;
8
9use crate::{
10    Error,
11    flasher::{SpiAttachParams, SpiSetParams},
12};
13
14const DEFAULT_TIMEOUT: Duration = Duration::from_secs(3);
15const ERASE_REGION_TIMEOUT_PER_MB: Duration = Duration::from_secs(30);
16const ERASE_WRITE_TIMEOUT_PER_MB: Duration = Duration::from_secs(40);
17const ERASE_CHIP_TIMEOUT: Duration = Duration::from_secs(120);
18const MEM_END_TIMEOUT: Duration = Duration::from_millis(50);
19const SYNC_TIMEOUT: Duration = Duration::from_millis(100);
20const FLASH_DEFLATE_END_TIMEOUT: Duration = Duration::from_secs(10);
21const FLASH_MD5_TIMEOUT_PER_MB: Duration = Duration::from_secs(8);
22
23/// Input data for SYNC command (36 bytes: 0x07 0x07 0x12 0x20, followed by
24/// 32 x 0x55)
25const SYNC_FRAME: [u8; 36] = [
26    0x07, 0x07, 0x12, 0x20, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
27    0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
28    0x55, 0x55, 0x55, 0x55,
29];
30
31/// Types of commands that can be sent to a target device
32///
33/// <https://docs.espressif.com/projects/esptool/en/latest/esp32c3/advanced-topics/serial-protocol.html#supported-by-stub-loader-and-rom-loader>
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Display, Deserialize, Serialize)]
35#[non_exhaustive]
36#[repr(u8)]
37pub enum CommandType {
38    /// Unknown command type
39    Unknown = 0x00,
40
41    // Commands supported by the ESP32's bootloader
42    /// Begin flash download
43    FlashBegin = 0x02,
44    /// Flash download data
45    FlashData = 0x03,
46    /// Finish flash download
47    FlashEnd = 0x04,
48    /// Begin RAM download
49    MemBegin = 0x05,
50    /// RAM download data
51    MemEnd = 0x06,
52    /// Finish RAM download
53    MemData = 0x07,
54    /// Synchronize frame
55    Sync = 0x08,
56    /// Write 32-bit memory address
57    WriteReg = 0x09,
58    /// Read 32-bit memory address
59    ReadReg = 0x0A,
60
61    // Commands supported by the ESP32's bootloader
62    /// Configure SPI flash
63    SpiSetParams = 0x0B,
64    /// Attach SPI flash
65    SpiAttach = 0x0D,
66    /// Read flash
67    ///
68    /// ROM-code only, much slower than the stub's `READ_FLASH` command.
69    ReadFlashSlow = 0x0E,
70    /// Change baud rate
71    ChangeBaudrate = 0x0F,
72    /// Begin compressed flash download
73    FlashDeflBegin = 0x10,
74    /// Compressed flash download data
75    FlashDeflData = 0x11,
76    /// Finish compressed flash download
77    FlashDeflEnd = 0x12,
78    /// Calculate MD5 checksum of flash region
79    FlashMd5 = 0x13,
80    /// Read chip security info
81    GetSecurityInfo = 0x14,
82
83    // Stub-only commands
84    /// Erase entire flash chip
85    EraseFlash = 0xD0,
86    /// Erase flash region
87    EraseRegion = 0xD1,
88    /// Read flash
89    ReadFlash = 0xD2,
90    /// Exits loader and runs user code
91    RunUserCode = 0xD3,
92
93    // Not part of the protocol
94    /// Detect the ID of the connected flash
95    FlashDetect = 0x9F,
96}
97
98/// The value of a command response.
99#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
100pub enum CommandResponseValue {
101    /// A 32-bit value.
102    ValueU32(u32),
103    /// A 128-bit value.
104    ValueU128(u128),
105    /// A vector of bytes.
106    Vector(Vec<u8>),
107}
108
109impl TryInto<u32> for CommandResponseValue {
110    type Error = Error;
111
112    fn try_into(self) -> Result<u32, Self::Error> {
113        match self {
114            CommandResponseValue::ValueU32(value) => Ok(value),
115            CommandResponseValue::ValueU128(_) => Err(Error::InvalidResponse(
116                "expected `u32` but found `u128`".into(),
117            )),
118            CommandResponseValue::Vector(_) => Err(Error::InvalidResponse(
119                "expected `u32` but found `Vec`".into(),
120            )),
121        }
122    }
123}
124
125impl TryInto<u128> for CommandResponseValue {
126    type Error = Error;
127
128    fn try_into(self) -> Result<u128, Self::Error> {
129        match self {
130            CommandResponseValue::ValueU32(_) => Err(Error::InvalidResponse(
131                "expected `u128` but found `u32`".into(),
132            )),
133            CommandResponseValue::ValueU128(value) => Ok(value),
134            CommandResponseValue::Vector(_) => Err(Error::InvalidResponse(
135                "expected `u128` but found `Vec`".into(),
136            )),
137        }
138    }
139}
140
141impl TryInto<Vec<u8>> for CommandResponseValue {
142    type Error = Error;
143
144    fn try_into(self) -> Result<Vec<u8>, Self::Error> {
145        match self {
146            CommandResponseValue::ValueU32(_) => Err(Error::InvalidResponse(
147                "expected `Vec` but found `u32`".into(),
148            )),
149            CommandResponseValue::ValueU128(_) => Err(Error::InvalidResponse(
150                "expected `Vec` but found `u128`".into(),
151            )),
152            CommandResponseValue::Vector(value) => Ok(value),
153        }
154    }
155}
156
157/// A response from a target device following a command.
158#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
159pub struct CommandResponse {
160    /// The response byte.
161    pub resp: u8,
162    /// The return operation byte.
163    pub return_op: u8,
164    /// The length of the return value.
165    pub return_length: u16,
166    /// The value of the response.
167    pub value: CommandResponseValue,
168    /// The error byte.
169    pub error: u8,
170    /// The status byte.
171    pub status: u8,
172}
173
174impl CommandType {
175    /// Return the default timeout for the [`CommandType`] variant.
176    pub fn timeout(&self) -> Duration {
177        match self {
178            CommandType::MemEnd => MEM_END_TIMEOUT,
179            CommandType::Sync => SYNC_TIMEOUT,
180            CommandType::EraseFlash => ERASE_CHIP_TIMEOUT,
181            CommandType::FlashDeflEnd => FLASH_DEFLATE_END_TIMEOUT,
182            CommandType::FlashMd5 => {
183                log::warn!(
184                    "Using default timeout for {self}, this may not be sufficient for large flash regions. Consider using `timeout_for_size` instead."
185                );
186
187                DEFAULT_TIMEOUT
188            }
189            _ => DEFAULT_TIMEOUT,
190        }
191    }
192
193    /// Return a timeout for the command that scales with the amount of data
194    /// involved in the transfer.
195    pub fn timeout_for_size(&self, size: u32) -> Duration {
196        fn calc_timeout(timeout_per_mb: Duration, size: u32) -> Duration {
197            let mb = size as f64 / 1_000_000.0;
198            std::cmp::max(
199                FLASH_DEFLATE_END_TIMEOUT,
200                Duration::from_millis((timeout_per_mb.as_millis() as f64 * mb) as u64),
201            )
202        }
203        match self {
204            CommandType::FlashBegin | CommandType::FlashDeflBegin | CommandType::EraseRegion => {
205                calc_timeout(ERASE_REGION_TIMEOUT_PER_MB, size)
206            }
207            CommandType::FlashData | CommandType::FlashDeflData => {
208                calc_timeout(ERASE_WRITE_TIMEOUT_PER_MB, size)
209            }
210            CommandType::FlashMd5 => calc_timeout(FLASH_MD5_TIMEOUT_PER_MB, size),
211            _ => self.timeout(),
212        }
213    }
214}
215
216/// Available commands
217///
218/// See <https://docs.espressif.com/projects/esptool/en/latest/esp32c6/advanced-topics/serial-protocol.html#commands>
219#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)]
220#[non_exhaustive]
221pub enum Command<'a> {
222    /// Begin flash download
223    FlashBegin {
224        /// Size to erase
225        size: u32,
226        /// Number of data packets
227        blocks: u32,
228        /// Data size in one packet
229        block_size: u32,
230        /// Flash offset
231        offset: u32,
232        /// Supports encryption
233        supports_encryption: bool,
234    },
235    /// Flash download data
236    FlashData {
237        /// Data
238        data: &'a [u8],
239        /// Pad to
240        pad_to: usize,
241        /// Pad byte
242        pad_byte: u8,
243        /// Sequence number
244        sequence: u32,
245    },
246    /// Finish flash download
247    FlashEnd {
248        /// Reboot
249        ///
250        /// 0 to reboot, 1 to run user code. Not necessary to send this command
251        /// if you wish to stay in the loader.
252        reboot: bool,
253    },
254    /// Begin RAM download start
255    MemBegin {
256        /// Total size
257        size: u32,
258        /// Number of data packets
259        blocks: u32,
260        /// Data size in one packet
261        block_size: u32,
262        /// Memory offset
263        offset: u32,
264        /// Supports encryption
265        supports_encryption: bool,
266    },
267    /// Finish RAM download
268    MemEnd {
269        /// Execute flag
270        no_entry: bool,
271        /// Entry point address
272        entry: u32,
273    },
274    /// RAM download data
275    MemData {
276        /// Data size
277        data: &'a [u8],
278        /// Pad to
279        pad_to: usize,
280        /// Pad byte
281        pad_byte: u8,
282        /// Sequence number
283        sequence: u32,
284    },
285    /// Sync frame
286    ///
287    /// 36 bytes: `0x07 0x07 0x12 0x20`, followed by 32 x `0x55`.
288    Sync,
289    /// Write 32-bit memory address
290    WriteReg {
291        /// Address
292        address: u32,
293        /// Value
294        value: u32,
295        /// Mask
296        mask: Option<u32>,
297    },
298    /// Read 32-bit memory address
299    ReadReg {
300        /// Address
301        address: u32,
302    },
303    /// Configure SPI flash
304    SpiSetParams {
305        /// SPI attach parameters
306        spi_params: SpiSetParams,
307    },
308    /// Attach SPI flash
309    SpiAttach {
310        /// SPI attach parameters
311        spi_params: SpiAttachParams,
312    },
313    /// Attach SPI flash (stub)
314    SpiAttachStub {
315        /// SPI attach parameters
316        spi_params: SpiAttachParams,
317    },
318    /// Change Baud rate
319    ChangeBaudrate {
320        /// New baud rate
321        new_baud: u32,
322        /// Prior baud rate ('0' for ROM flasher)
323        prior_baud: u32,
324    },
325    /// Begin compressed flash download
326    FlashDeflBegin {
327        /// Uncompressed size
328        ///
329        /// With stub loader the uncompressed size is exact byte count to be
330        /// written, whereas on ROM bootloader it is rounded up to flash erase
331        /// block size.
332        size: u32,
333        /// Number of data packets
334        blocks: u32,
335        /// Data packet size
336        block_size: u32,
337        /// Flash offset
338        offset: u32,
339        /// Supports encryption
340        ///
341        /// ROM loader only: `1` to begin encrypted flash, `0` to not.
342        supports_encryption: bool,
343    },
344    /// Compressed flash download data
345    FlashDeflData {
346        /// Data size
347        data: &'a [u8],
348        /// Pad to
349        pad_to: usize,
350        /// Pad byte
351        pad_byte: u8,
352        /// Sequence number
353        sequence: u32,
354    },
355    /// End compressed flash download
356    FlashDeflEnd {
357        /// Reboot
358        ///
359        /// `0` to reboot, `1` to run user code. Not necessary to send this
360        /// command if you wish to stay in the loader.
361        reboot: bool,
362    },
363    /// Calculate MD5 of flash region
364    FlashMd5 {
365        /// Address
366        offset: u32,
367        /// Size
368        size: u32,
369    },
370    /// Erase entire flash chip
371    ///
372    /// Supported by stub loader only.
373    EraseFlash,
374    /// Erase flash region
375    ///
376    /// Supported by stub loader only.
377    EraseRegion {
378        /// Flash offset to erase
379        offset: u32,
380        /// Erase size in bytes
381        size: u32,
382    },
383    /// Read flash
384    ///
385    /// Supported by stub loader only.
386    ReadFlash {
387        /// Flash offset
388        offset: u32,
389        /// Read length
390        size: u32,
391        /// Flash sector size
392        block_size: u32,
393        /// Maximum number of un-acked packets
394        max_in_flight: u32,
395    },
396    /// Read flash (slow)
397    ///
398    /// Supported by ROM loader only.
399    ReadFlashSlow {
400        /// Offset in flash to start from
401        offset: u32,
402        /// Size of the region to read
403        size: u32,
404        /// Block size
405        block_size: u32,
406        /// Maximum number of in-flight bytes
407        max_in_flight: u32,
408    },
409    /// Exits loader and runs user code
410    RunUserCode,
411    /// Read SPI flash manufacturer and device ID
412    ///
413    /// Not part of the serial protocol.
414    FlashDetect,
415    /// Read chip security info
416    ///
417    /// Not supported by ESP32.
418    GetSecurityInfo,
419}
420
421impl Command<'_> {
422    /// Return the command type
423    pub fn command_type(&self) -> CommandType {
424        match self {
425            Command::FlashBegin { .. } => CommandType::FlashBegin,
426            Command::FlashData { .. } => CommandType::FlashData,
427            Command::FlashEnd { .. } => CommandType::FlashEnd,
428            Command::MemBegin { .. } => CommandType::MemBegin,
429            Command::MemData { .. } => CommandType::MemData,
430            Command::MemEnd { .. } => CommandType::MemEnd,
431            Command::Sync => CommandType::Sync,
432            Command::WriteReg { .. } => CommandType::WriteReg,
433            Command::ReadReg { .. } => CommandType::ReadReg,
434            Command::SpiSetParams { .. } => CommandType::SpiSetParams,
435            Command::SpiAttach { .. } => CommandType::SpiAttach,
436            Command::SpiAttachStub { .. } => CommandType::SpiAttach,
437            Command::ChangeBaudrate { .. } => CommandType::ChangeBaudrate,
438            Command::FlashDeflBegin { .. } => CommandType::FlashDeflBegin,
439            Command::FlashDeflData { .. } => CommandType::FlashDeflData,
440            Command::FlashDeflEnd { .. } => CommandType::FlashDeflEnd,
441            Command::FlashMd5 { .. } => CommandType::FlashMd5,
442            Command::EraseFlash { .. } => CommandType::EraseFlash,
443            Command::EraseRegion { .. } => CommandType::EraseRegion,
444            Command::ReadFlash { .. } => CommandType::ReadFlash,
445            Command::ReadFlashSlow { .. } => CommandType::ReadFlashSlow,
446            Command::RunUserCode { .. } => CommandType::RunUserCode,
447            Command::FlashDetect => CommandType::FlashDetect,
448            Command::GetSecurityInfo => CommandType::GetSecurityInfo,
449        }
450    }
451
452    /// Return a timeout based on the size
453    pub fn timeout_for_size(&self, size: u32) -> Duration {
454        self.command_type().timeout_for_size(size)
455    }
456
457    /// Write a command
458    pub fn write<W: Write>(&self, mut writer: W) -> std::io::Result<()> {
459        // Write the Direction and Command Identifier
460        writer.write_all(&[0, self.command_type() as u8])?;
461        match *self {
462            Command::FlashBegin {
463                size,
464                blocks,
465                block_size,
466                offset,
467                supports_encryption,
468            } => {
469                begin_command(
470                    writer,
471                    size,
472                    blocks,
473                    block_size,
474                    offset,
475                    supports_encryption,
476                )?;
477            }
478            Command::FlashData {
479                pad_to,
480                pad_byte,
481                data,
482                sequence,
483            } => {
484                data_command(writer, data, pad_to, pad_byte, sequence)?;
485            }
486            Command::FlashEnd { reboot } => {
487                write_basic(writer, &[u8::from(!reboot)], 0)?;
488            }
489            Command::MemBegin {
490                size,
491                blocks,
492                block_size,
493                offset,
494                supports_encryption,
495            } => {
496                begin_command(
497                    writer,
498                    size,
499                    blocks,
500                    block_size,
501                    offset,
502                    supports_encryption,
503                )?;
504            }
505            Command::MemData {
506                pad_to,
507                pad_byte,
508                data,
509                sequence,
510            } => {
511                data_command(writer, data, pad_to, pad_byte, sequence)?;
512            }
513            Command::MemEnd {
514                no_entry: reboot,
515                entry,
516            } => {
517                #[derive(Zeroable, Pod, Copy, Clone)]
518                #[repr(C)]
519                struct EntryParams {
520                    no_entry: u32,
521                    entry: u32,
522                }
523                let params = EntryParams {
524                    no_entry: u32::from(reboot),
525                    entry,
526                };
527                write_basic(writer, bytes_of(&params), 0)?;
528            }
529            Command::Sync => {
530                write_basic(writer, &SYNC_FRAME, 0)?;
531            }
532            Command::WriteReg {
533                address,
534                value,
535                mask,
536            } => {
537                #[derive(Zeroable, Pod, Copy, Clone, Debug)]
538                #[repr(C)]
539                struct WriteRegParams {
540                    address: u32,
541                    value: u32,
542                    mask: u32,
543                    delay_us: u32,
544                }
545                let params = WriteRegParams {
546                    address,
547                    value,
548                    mask: mask.unwrap_or(0xFFFFFFFF),
549                    delay_us: 0,
550                };
551                write_basic(writer, bytes_of(&params), 0)?;
552            }
553            Command::ReadReg { address } => {
554                write_basic(writer, &address.to_le_bytes(), 0)?;
555            }
556            Command::SpiSetParams { spi_params } => {
557                write_basic(writer, &spi_params.encode(), 0)?;
558            }
559            Command::SpiAttach { spi_params } => {
560                write_basic(writer, &spi_params.encode(false), 0)?;
561            }
562            Command::SpiAttachStub { spi_params } => {
563                write_basic(writer, &spi_params.encode(true), 0)?;
564            }
565            Command::ChangeBaudrate {
566                new_baud,
567                prior_baud,
568            } => {
569                // length
570                writer.write_all(&(8u16.to_le_bytes()))?;
571                // checksum
572                writer.write_all(&(0u32.to_le_bytes()))?;
573                // data
574                writer.write_all(&new_baud.to_le_bytes())?;
575                writer.write_all(&prior_baud.to_le_bytes())?;
576            }
577            Command::FlashDeflBegin {
578                size,
579                blocks,
580                block_size,
581                offset,
582                supports_encryption,
583            } => {
584                begin_command(
585                    writer,
586                    size,
587                    blocks,
588                    block_size,
589                    offset,
590                    supports_encryption,
591                )?;
592            }
593            Command::FlashDeflData {
594                pad_to,
595                pad_byte,
596                data,
597                sequence,
598            } => {
599                data_command(writer, data, pad_to, pad_byte, sequence)?;
600            }
601            Command::FlashDeflEnd { reboot } => {
602                // As per the logic here: https://github.com/espressif/esptool/blob/0a9caaf04cfde6fd97c785d4811f3fde09b1b71f/flasher_stub/stub_flasher.c#L402
603                // 0 means reboot, 1 means do nothing
604                write_basic(writer, &[u8::from(!reboot)], 0)?;
605            }
606            Command::FlashMd5 { offset, size } => {
607                // length
608                writer.write_all(&(16u16.to_le_bytes()))?;
609                // checksum
610                writer.write_all(&(0u32.to_le_bytes()))?;
611                // data
612                writer.write_all(&offset.to_le_bytes())?;
613                writer.write_all(&size.to_le_bytes())?;
614                writer.write_all(&(0u32.to_le_bytes()))?;
615                writer.write_all(&(0u32.to_le_bytes()))?;
616            }
617            Command::EraseFlash => {
618                write_basic(writer, &[], 0)?;
619            }
620            Command::EraseRegion { offset, size } => {
621                // length
622                writer.write_all(&(8u16.to_le_bytes()))?;
623                // checksum
624                writer.write_all(&(0u32.to_le_bytes()))?;
625                // data
626                writer.write_all(&offset.to_le_bytes())?;
627                writer.write_all(&size.to_le_bytes())?;
628            }
629            Command::ReadFlash {
630                offset,
631                size,
632                block_size,
633                max_in_flight,
634            } => {
635                // length
636                writer.write_all(&(16u16.to_le_bytes()))?;
637                // checksum
638                writer.write_all(&(0u32.to_le_bytes()))?;
639                // data
640                writer.write_all(&offset.to_le_bytes())?;
641                writer.write_all(&size.to_le_bytes())?;
642                writer.write_all(&block_size.to_le_bytes())?;
643                writer.write_all(&(max_in_flight.to_le_bytes()))?;
644            }
645            Command::ReadFlashSlow {
646                offset,
647                size,
648                block_size,
649                max_in_flight,
650            } => {
651                // length
652                writer.write_all(&(16u16.to_le_bytes()))?;
653                // checksum
654                writer.write_all(&(0u32.to_le_bytes()))?;
655                // data
656                writer.write_all(&offset.to_le_bytes())?;
657                writer.write_all(&size.to_le_bytes())?;
658                writer.write_all(&block_size.to_le_bytes())?;
659                writer.write_all(&(max_in_flight.to_le_bytes()))?;
660            }
661            Command::RunUserCode => {
662                write_basic(writer, &[], 0)?;
663            }
664            Command::FlashDetect => {
665                write_basic(writer, &[], 0)?;
666            }
667            Command::GetSecurityInfo => {
668                write_basic(writer, &[], 0)?;
669            }
670        };
671        Ok(())
672    }
673}
674
675/// Write a data array and its checksum to a writer
676fn write_basic<W: Write>(mut writer: W, data: &[u8], checksum: u32) -> std::io::Result<()> {
677    writer.write_all(&((data.len() as u16).to_le_bytes()))?;
678    writer.write_all(&(checksum.to_le_bytes()))?;
679    writer.write_all(data)?;
680    Ok(())
681}
682
683/// Write a Begin command to a writer
684fn begin_command<W: Write>(
685    writer: W,
686    size: u32,
687    blocks: u32,
688    block_size: u32,
689    offset: u32,
690    supports_encryption: bool,
691) -> std::io::Result<()> {
692    #[derive(Zeroable, Pod, Copy, Clone, Debug)]
693    #[repr(C)]
694    struct BeginParams {
695        size: u32,
696        blocks: u32,
697        block_size: u32,
698        offset: u32,
699        encrypted: u32,
700    }
701    let params = BeginParams {
702        size,
703        blocks,
704        block_size,
705        offset,
706        encrypted: 0,
707    };
708
709    let bytes = bytes_of(&params);
710    let data = if !supports_encryption {
711        // The ESP32 does not take the `encrypted` field, so truncate the last
712        // 4 bytes of the slice where it resides.
713        let end = bytes.len() - 4;
714        &bytes[0..end]
715    } else {
716        bytes
717    };
718    write_basic(writer, data, 0)
719}
720
721/// Write a Data command to a writer
722fn data_command<W: Write>(
723    mut writer: W,
724    block_data: &[u8],
725    pad_to: usize,
726    pad_byte: u8,
727    sequence: u32,
728) -> std::io::Result<()> {
729    #[derive(Zeroable, Pod, Copy, Clone, Debug)]
730    #[repr(C)]
731    struct BlockParams {
732        size: u32,
733        sequence: u32,
734        dummy1: u32,
735        dummy2: u32,
736    }
737
738    let pad_length = pad_to.saturating_sub(block_data.len());
739
740    let params = BlockParams {
741        size: (block_data.len() + pad_length) as u32,
742        sequence,
743        dummy1: 0,
744        dummy2: 0,
745    };
746
747    let mut check = checksum(block_data, CHECKSUM_INIT);
748
749    for _ in 0..pad_length {
750        check = checksum(&[pad_byte], check);
751    }
752
753    let total_length = size_of::<BlockParams>() + block_data.len() + pad_length;
754    writer.write_all(&((total_length as u16).to_le_bytes()))?;
755    writer.write_all(&((check as u32).to_le_bytes()))?;
756    writer.write_all(bytes_of(&params))?;
757    writer.write_all(block_data)?;
758    for _ in 0..pad_length {
759        writer.write_all(&[pad_byte])?;
760    }
761    Ok(())
762}
763
764const CHECKSUM_INIT: u8 = 0xEF;
765
766fn checksum(data: &[u8], mut checksum: u8) -> u8 {
767    for byte in data {
768        checksum ^= *byte;
769    }
770
771    checksum
772}