Skip to main content

AteccChannel

Struct AteccChannel 

Source
pub struct AteccChannel<'a, H>
where H: AteccHal,
{ driver: &'a mut Atecc<H>, closed: bool, }
Expand description

An open communication channel with a woken chip.

Holds a mutable borrow of the parent Atecc for the duration of the channel. All high-level chip commands are exposed as methods on this type (see the crate::command modules: info, random, sign, etc.).

§Closing

The channel must be closed via Self::close when commands are done. Closing sends the idle token, which preserves volatile state (TempKey, RNG seed) and resets the chip’s watchdog. After close, the parent Atecc becomes usable again for a new channel.

To put the chip into its lowest-power mode and clear volatile state instead of just idling, use Self::close_to_sleep.

If the channel is dropped without close being called, the chip is left awake. Its watchdog will eventually time it out to sleep, and the next Atecc::open_channel call will wake it normally. The Drop impl emits a defmt::warn! to flag the protocol violation in development; nothing breaks, but it indicates a bug to fix.

Fields§

§driver: &'a mut Atecc<H>§closed: bool

Implementations§

Source§

impl<H> AteccChannel<'_, H>
where H: AteccHal,

Source

pub async fn checkmac( &mut self, slot: Slot, challenge: &[u8; 32], client_resp: &[u8; 32], other_data: &[u8; 13], ) -> Result<bool, AteccError<H::Error>>

Verify a host-computed MAC against the contents of slot.

challenge is the random nonce used as input. client_resp is the MAC the host computed. other_data lays out the metadata fields the chip will hash into its own MAC.

Returns Ok(true) on match (chip status 0x00), Ok(false) on miscompare (chip status 0x01). Any other error condition surfaces as AteccError.

§Counter side-effect

If the target slot has a LimitedUse counter (slots 5 and 6 in this project), each CheckMac call bumps the counter regardless of the outcome. Reaching the threshold permanently blocks the slot.

§Errors

See AteccChannel::execute_command_status. A miscompare (0x01) is returned as Ok(false).

Source§

impl<H> AteccChannel<'_, H>
where H: AteccHal,

Source

pub async fn counter_read( &mut self, counter: CounterId, ) -> Result<u32, AteccError<H::Error>>

Read the current value of a counter without modifying it.

§Errors

See AteccChannel::execute_command.

Source

pub async fn counter_increment( &mut self, counter: CounterId, ) -> Result<u32, AteccError<H::Error>>

Increment a counter by 1 and return its new value.

§Errors

See AteccChannel::execute_command. The chip returns crate::error::ChipError::ExecutionError when the counter has reached its maximum value of 2^21 - 1.

Source

async fn counter_internal( &mut self, mode: u8, counter: CounterId, ) -> Result<u32, AteccError<H::Error>>

Source§

impl<H> AteccChannel<'_, H>
where H: AteccHal,

Source

pub async fn gendig( &mut self, zone: GenDigZone, key_id: u16, ) -> Result<(), AteccError<H::Error>>

Run a basic GenDig against a zone and key id, with no extra data.

Mostly used to derive a shared digest from the I/O Protection Key (slot 8 in this project) for subsequent encrypted writes.

The chip responds with a status-only frame on success.

§Errors

See AteccChannel::execute_command_status.

Source§

impl<H> AteccChannel<'_, H>
where H: AteccHal,

Source

pub async fn genkey_create( &mut self, slot: Slot, ) -> Result<[u8; 64], AteccError<H::Error>>

Generate a new P-256 private key inside the target slot.

The private key is created and stored entirely on-chip. The 64-byte public key (uncompressed X || Y) is returned.

§Errors

See AteccChannel::execute_command. Common chip errors include attempting to write a slot configured KeyConfig.Private = 0 or attempting to regenerate a slot whose SlotConfig.WriteConfig forbids it after data zone lock.

Source

pub async fn genkey_public( &mut self, slot: Slot, ) -> Result<[u8; 64], AteccError<H::Error>>

Compute and return the public key for the private key already stored in the target slot. Does not modify chip state.

§Errors

See AteccChannel::execute_command.

Source

async fn genkey_internal( &mut self, mode: u8, slot: Slot, ) -> Result<[u8; 64], AteccError<H::Error>>

Source§

impl<H> AteccChannel<'_, H>
where H: AteccHal,

Source

pub async fn info_revision(&mut self) -> Result<[u8; 4], AteccError<H::Error>>

Read the chip’s revision bytes.

Returns the 4-byte revision code. The first two bytes are reserved and always zero. The third byte is the device family (0x60 for ATECC608B). The fourth byte distinguishes the clock divider variant (0x02 for M0, 0x03 for M1, 0x04 for M2).

§Errors

See AteccChannel::execute_command.

Source§

impl<H: AteccHal> AteccChannel<'_, H>

Source

pub async fn lock_config_zone( &mut self, expected_crc: u16, ) -> Result<(), AteccError<H::Error>>

Permanently lock the configuration zone.

Irreversible. After this call, every byte in the configuration zone is read-only forever. Slot policies, key types, the chip’s I2C address, and counter initial values become immutable.

expected_crc is the CRC-16/CCITT of the current configuration zone as the host believes it to be. The chip recomputes the CRC of its own configuration and compares. If it differs, the chip rejects the command with ATCA_EXECUTION_ERROR and the zone stays unlocked.

The caller must have verified, by reading the chip and computing the CRC, that expected_crc matches what’s actually on the chip, and that the configuration is the intended one. The chip’s CRC check is a backstop, not a substitute.

§Errors
  • AteccError::Chip with ChipError::ExecutionError if the CRC does not match (zone stays unlocked).
  • Other AteccError variants for I2C or wake failures.
Source

pub async fn lock_data_zone(&mut self) -> Result<(), AteccError<H::Error>>

Permanently lock the data + OTP zones.

Irreversible. After this call, slots can no longer be written in cleartext. Writes must go through the encrypted-write protocol via the I/O Protection Key, and even those are subject to per-slot EncryptWrite policy.

Unlike Self::lock_config_zone, this call does not ask the chip to verify a CRC of the data zone before locking. Every secret-bearing slot on this project has IsSecret=1, so the host cannot read the current slot contents back to compute a meaningful CRC. The safety guard is the magic-word check in the firmware plus the interactive double confirmation in the host CLI.

§Errors
  • AteccError::Chip if the chip refuses the command (for example when the configuration zone is not yet locked).
  • Other AteccError variants for I2C or wake failures.
Source

pub async fn lock_slot( &mut self, slot: Slot, ) -> Result<(), AteccError<H::Error>>

Permanently lock an individual data slot.

Irreversible. After this call, the slot’s contents are frozen forever. Write(slot) and GenKey(slot) on that slot return chip errors. The slot’s policy in the configuration zone must have its Lockable bit set, or the chip rejects this command.

§Errors
  • AteccError::Chip with ChipError::ExecutionError if the slot is not lockable or already locked.
  • Other AteccError variants for I2C or wake failures.
Source§

impl<H> AteccChannel<'_, H>
where H: AteccHal,

Source

pub async fn nonce_random( &mut self, num_in: &[u8; 20], ) -> Result<[u8; 32], AteccError<H::Error>>

Issue a random Nonce (mode 0).

num_in is 20 bytes of host-provided entropy that the chip mixes with its TRNG before hashing the combined block into TempKey.

On success returns the 32-byte NumOut (the TRNG portion mixed in). The host can compute the resulting TempKey value as SHA256(NumOut || NumIn || OpCode || Mode || LSB || 0..0) per the CryptoAuthLib reference, but the driver does not do that derivation: it is a service-layer concern.

§Errors

See AteccChannel::execute_command.

Source

pub async fn nonce_passthrough( &mut self, target: NonceTarget, value: &[u8; 32], ) -> Result<(), AteccError<H::Error>>

Issue a passthrough Nonce (mode 3).

value is stored verbatim in the target register (no hashing, no RNG mixing). Mainly used to load a message digest into TempKey ahead of a Sign call.

§Errors

See AteccChannel::execute_command_status.

Source§

impl<H> AteccChannel<'_, H>
where H: AteccHal,

Source

pub async fn privwrite_cleartext( &mut self, slot: Slot, private_key: &[u8; 32], ) -> Result<(), AteccError<H::Error>>

Write a 32-byte P-256 private scalar into slot in cleartext.

Only valid while the data zone is unlocked, which on this project means before the irreversible data-zone Lock has been performed. Calling this after lock returns a chip error.

Not for the user identity key. The user identity key is created on-chip via genkey_create. This entry point exists for bring-up helpers (loading a known test key into a scratch slot) and for the attestation slot if used.

private_key is the raw 32-byte scalar in big-endian form, the natural P-256 byte order matching the output of standard libraries (p256, OpenSSL, etc.).

§Errors

See AteccChannel::execute_command_status. Returns a chip error if the data zone is already locked.

Source§

impl<H> AteccChannel<'_, H>
where H: AteccHal,

Source

pub async fn random(&mut self) -> Result<[u8; 32], AteccError<H::Error>>

Request 32 random bytes from the chip.

The chip reseeds its internal RNG before producing the output.

§Errors

See AteccChannel::execute_command.

Source§

impl<H> AteccChannel<'_, H>
where H: AteccHal,

Source

pub(crate) async fn read_4( &mut self, zone: Zone, address: u16, ) -> Result<[u8; 4], AteccError<H::Error>>

Read a 4-byte word from the given zone at the given address.

address is the raw 16-bit value encoded in param2. Callers generally build it with config_or_otp_address or data_address.

§Errors

See AteccChannel::execute_command. In particular, reads of locked or permission-restricted regions surface as AteccError::Chip with the relevant status byte.

Source

pub(crate) async fn read_32( &mut self, zone: Zone, address: u16, ) -> Result<[u8; 32], AteccError<H::Error>>

Read a 32-byte block from the given zone at the given address.

address is the raw 16-bit value encoded in param2. The offset bits must be zero, the chip rejects 32-byte transfers otherwise.

§Errors

See AteccChannel::execute_command.

Source

pub async fn read_config_zone( &mut self, out: &mut [u8; 128], ) -> Result<(), AteccError<H::Error>>

Read the entire 128-byte config zone into out.

Internally performs four 32-byte reads, one per block. The channel stays open between the reads.

§Errors

See AteccChannel::execute_command. The first failing block aborts the whole operation.

Source

pub async fn read_slot_word( &mut self, slot: Slot, block: u8, offset_words: u8, ) -> Result<[u8; 4], AteccError<H::Error>>

Read a 4-byte word from a data slot.

block and offset_words are interpreted per the ATECC608B address layout for the data zone.

§Errors

See AteccChannel::execute_command.

Source

pub async fn read_slot_block( &mut self, slot: Slot, block: u8, ) -> Result<[u8; 32], AteccError<H::Error>>

Read a 32-byte block from a data slot.

§Errors

See AteccChannel::execute_command.

Source

pub async fn write_4( &mut self, zone: Zone, address: u16, data: &[u8; 4], ) -> Result<(), AteccError<H::Error>>

Write a 4-byte word to the given zone at the given address.

Cleartext only: encrypted writes are not supported in 4-byte mode (this is a chip limitation, not a driver one).

§Errors

See AteccChannel::execute_command_status. Writes to locked regions or to slots whose SlotConfig.WriteConfig forbids cleartext writes surface as AteccError::Chip.

Source

pub async fn write_32( &mut self, zone: Zone, address: u16, data: &[u8; 32], ) -> Result<(), AteccError<H::Error>>

Write a 32-byte block to the given zone in cleartext.

address must have its offset bits set to zero. The block index is the upper bits per the zone layout.

§Errors

See AteccChannel::execute_command_status.

Source

pub async fn write_32_encrypted( &mut self, zone: Zone, address: u16, ciphertext_and_mac: &[u8; 64], ) -> Result<(), AteccError<H::Error>>

Write a 32-byte block to a data slot in encrypted mode.

The caller must supply the ciphertext and the precomputed MAC. The derivation of both is the responsibility of the higher-level hsm-crypto-service (see its provisioning module).

This entry point exists in the driver so that the encrypted-write path can be exercised against the mock HAL. It assumes the chip is already loaded with a fresh GenDig-derived TempKey for the I/O protection slot. Calling this without that prior step yields a chip error.

§Errors

See AteccChannel::execute_command_status.

Source

pub async fn write_slot_word( &mut self, slot: Slot, block: u8, offset_words: u8, data: &[u8; 4], ) -> Result<(), AteccError<H::Error>>

Write a 4-byte word into a data slot in cleartext.

§Errors

See AteccChannel::execute_command_status.

Source

pub async fn write_slot_block( &mut self, slot: Slot, block: u8, data: &[u8; 32], ) -> Result<(), AteccError<H::Error>>

Write a 32-byte block into a data slot in cleartext.

§Errors

See AteccChannel::execute_command_status.

Source§

impl<H> AteccChannel<'_, H>
where H: AteccHal,

Source

pub async fn sign_external( &mut self, slot: Slot, ) -> Result<[u8; 64], AteccError<H::Error>>

Sign the 32-byte digest currently loaded in the Message Digest Buffer with the private key in slot.

Callers must load the digest via AteccChannel::nonce_passthrough with target crate::command::nonce::NonceTarget::MsgDigBuf immediately before this call. Any intervening command that overwrites MsgDigBuf invalidates the operation.

Returns the 64-byte raw signature R || S, both big-endian. R then S, each 32 bytes; this is the on-the-wire layout the chip returns and matches the convention used by every standard ECDSA verifier when the signature components are passed separately.

§Errors

See AteccChannel::execute_command. The chip rejects this command if the target slot has ReqAuth=1 and no authenticated session is active, or if MsgDigBuf was not properly seeded by a preceding Nonce(passthrough, target=MsgDigBuf).

Source§

impl<H> AteccChannel<'_, H>
where H: AteccHal,

Source

pub async fn verify_external( &mut self, signature: &[u8; 64], public_key: &[u8; 64], ) -> Result<bool, AteccError<H::Error>>

Verify an ECDSA P-256 signature against the digest currently loaded in TempKey.

Callers must load the digest via AteccChannel::nonce_passthrough with target crate::command::nonce::NonceTarget::TempKey immediately before this call.

signature is the 64-byte raw R || S returned by Sign. public_key is the 64-byte raw X || Y returned by GenKey.

On success returns Ok(true) when the signature matches the digest under the given public key, and Ok(false) when the chip cleanly rejects the signature. Any other error condition surfaces as AteccError.

§Errors

See AteccChannel::execute_command_status. A signature mismatch surfaces here as Ok(false), not as an error: the chip uses crate::error::ChipError::CheckMacOrVerifyFailed (status 0x01) specifically to flag this case, and the driver maps it back to a boolean for ergonomics.

Source§

impl<H> AteccChannel<'_, H>
where H: AteccHal,

Source

pub async fn close(self) -> Result<(), AteccError<H::Error>>

Close the channel by sending the idle token to the chip and consuming the channel handle.

Idle preserves volatile chip state (TempKey, RNG seed) and resets the chip’s watchdog. To clear volatile state and put the chip into its lowest-power mode instead, use Self::close_to_sleep.

§Errors

Forwards AteccError::Hal from the I2C layer.

Source

pub async fn close_to_sleep(self) -> Result<(), AteccError<H::Error>>

Close the channel by sending the sleep token to the chip.

Unlike Self::close, sleep clears volatile state (TempKey, RNG seed) and brings the chip to its low power consumption level. The next Atecc::open_channel will re-wake the chip from a clean state.

§Errors

Forwards AteccError::Hal from the I2C layer.

Source

pub async fn refresh(&mut self) -> Result<(), AteccError<H::Error>>

Force a fresh wake mid-channel.

Useful after an HAL-level error suggests the chip’s state has become uncertain (a NACK during a command, for example). Equivalent to closing and reopening the channel, but cheaper because it does not idle first.

§Errors

Forwards every variant from crate::wake::wake.

Source

pub(crate) async fn execute_command<'r>( &mut self, opcode: u8, param1: u8, param2: u16, data: &[u8], expected_exec_ms: u32, response_buf: &'r mut [u8], ) -> Result<&'r [u8], AteccError<H::Error>>

Execute one full command round-trip and return the data payload.

Use this overload for commands that return data (Info, Random, Read, GenKey, Sign, Verify, ECDH). The chip responds with count >= 5 bytes (count + payload + CRC).

For commands that only signal success or failure via a 4-byte status frame (Write, Lock, Counter set), use Self::execute_command_status. This method does not accept the 0x00 success byte because a data-returning command never emits a bare 0x00. If it appears here, the frame is malformed.

data is the command-specific payload, expected_exec_ms is the typical execution time for that opcode (consult the EXEC_TIME_* constants in crate::opcodes), and response_buf is filled with the raw response frame (count byte and CRC included).

On success returns a &[u8] slice over the payload section of response_buf (excluding count and CRC).

This method does not idle the chip on its own. Callers that have finished their command sequence should drop the channel via Self::close. Callers chaining multiple commands that share volatile state (Nonce followed by Sign for example) keep the channel open between calls.

§Errors

Every variant of AteccError is reachable. See its documentation.

Source

pub(crate) async fn execute_command_status( &mut self, opcode: u8, param1: u8, param2: u16, data: &[u8], expected_exec_ms: u32, ) -> Result<(), AteccError<H::Error>>

Execute one full command round-trip and expect a status-only response.

Use this overload for commands that signal completion with a 4-byte status frame (Write, Lock, Counter set, Nonce mode 0x03). A status byte of 0x00 is the success indicator. Any non-zero status maps to an AteccError::Chip variant.

A response longer than 4 bytes here means the chip returned data when none was expected. This is treated as a malformed response.

§Errors

Every variant of AteccError is reachable. See its documentation.

Source

async fn run_command( &mut self, opcode: u8, param1: u8, param2: u16, data: &[u8], expected_exec_ms: u32, response_buf: &mut [u8], ) -> Result<usize, AteccError<H::Error>>

Send the command frame and poll for the raw response.

Returns the total number of bytes written into response_buf (count byte included). Parsing of the response frame is the caller’s responsibility, so this helper can be shared between the data-payload and status-only entry points above.

This helper does NOT idle the chip after the response: the channel model means idling is the explicit job of Self::close. That keeps multi-step workflows (Nonce + Sign, Nonce + GenDig + Write) working naturally inside a single channel.

Source

async fn poll_for_response( &mut self, response_buf: &mut [u8], ) -> Result<usize, AteccError<H::Error>>

Poll the chip’s response register until a frame is available or the global timeout elapses.

The ATECC608B signals “I am ready” by responding to the read with the frame proper. While it is still busy it NACKs the read, which surfaces as an HAL error. We treat any HAL error during this phase as “not yet ready” and retry after POLLING_PERIOD_MS.

On success returns the number of bytes written into response_buf.

Trait Implementations§

Source§

impl<H> Drop for AteccChannel<'_, H>
where H: AteccHal,

Source§

fn drop(&mut self)

On drop without an explicit close, emit a defmt::warn! to flag the protocol violation.

The chip is left awake; its watchdog (~1.3 s) will eventually idle it, but the next channel may observe a transient state for that duration. The drop itself cannot send the idle token because Drop is synchronous and the HAL is async; the warn-on-drop is the only signal we can emit without a block_on of unknown safety.

Auto Trait Implementations§

§

impl<'a, H> Freeze for AteccChannel<'a, H>

§

impl<'a, H> RefUnwindSafe for AteccChannel<'a, H>
where H: RefUnwindSafe,

§

impl<'a, H> Send for AteccChannel<'a, H>
where H: Send,

§

impl<'a, H> Sync for AteccChannel<'a, H>
where H: Sync,

§

impl<'a, H> Unpin for AteccChannel<'a, H>

§

impl<'a, H> UnsafeUnpin for AteccChannel<'a, H>

§

impl<'a, H> !UnwindSafe for AteccChannel<'a, H>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.