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: boolImplementations§
Source§impl<H> AteccChannel<'_, H>where
H: AteccHal,
impl<H> AteccChannel<'_, H>where
H: AteccHal,
Sourcepub async fn checkmac(
&mut self,
slot: Slot,
challenge: &[u8; 32],
client_resp: &[u8; 32],
other_data: &[u8; 13],
) -> Result<bool, AteccError<H::Error>>
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,
impl<H> AteccChannel<'_, H>where
H: AteccHal,
Sourcepub async fn counter_read(
&mut self,
counter: CounterId,
) -> Result<u32, AteccError<H::Error>>
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
Sourcepub async fn counter_increment(
&mut self,
counter: CounterId,
) -> Result<u32, AteccError<H::Error>>
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.
async fn counter_internal( &mut self, mode: u8, counter: CounterId, ) -> Result<u32, AteccError<H::Error>>
Source§impl<H> AteccChannel<'_, H>where
H: AteccHal,
impl<H> AteccChannel<'_, H>where
H: AteccHal,
Sourcepub async fn gendig(
&mut self,
zone: GenDigZone,
key_id: u16,
) -> Result<(), AteccError<H::Error>>
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
Source§impl<H> AteccChannel<'_, H>where
H: AteccHal,
impl<H> AteccChannel<'_, H>where
H: AteccHal,
Sourcepub async fn genkey_create(
&mut self,
slot: Slot,
) -> Result<[u8; 64], AteccError<H::Error>>
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.
Sourcepub async fn genkey_public(
&mut self,
slot: Slot,
) -> Result<[u8; 64], AteccError<H::Error>>
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
async fn genkey_internal( &mut self, mode: u8, slot: Slot, ) -> Result<[u8; 64], AteccError<H::Error>>
Source§impl<H> AteccChannel<'_, H>where
H: AteccHal,
impl<H> AteccChannel<'_, H>where
H: AteccHal,
Sourcepub async fn info_revision(&mut self) -> Result<[u8; 4], AteccError<H::Error>>
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
Source§impl<H: AteccHal> AteccChannel<'_, H>
impl<H: AteccHal> AteccChannel<'_, H>
Sourcepub async fn lock_config_zone(
&mut self,
expected_crc: u16,
) -> Result<(), AteccError<H::Error>>
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::ChipwithChipError::ExecutionErrorif the CRC does not match (zone stays unlocked).- Other
AteccErrorvariants for I2C or wake failures.
Sourcepub async fn lock_data_zone(&mut self) -> Result<(), AteccError<H::Error>>
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::Chipif the chip refuses the command (for example when the configuration zone is not yet locked).- Other
AteccErrorvariants for I2C or wake failures.
Sourcepub async fn lock_slot(
&mut self,
slot: Slot,
) -> Result<(), AteccError<H::Error>>
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::ChipwithChipError::ExecutionErrorif the slot is not lockable or already locked.- Other
AteccErrorvariants for I2C or wake failures.
Source§impl<H> AteccChannel<'_, H>where
H: AteccHal,
impl<H> AteccChannel<'_, H>where
H: AteccHal,
Sourcepub async fn nonce_random(
&mut self,
num_in: &[u8; 20],
) -> Result<[u8; 32], AteccError<H::Error>>
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
Sourcepub async fn nonce_passthrough(
&mut self,
target: NonceTarget,
value: &[u8; 32],
) -> Result<(), AteccError<H::Error>>
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
Source§impl<H> AteccChannel<'_, H>where
H: AteccHal,
impl<H> AteccChannel<'_, H>where
H: AteccHal,
Sourcepub async fn privwrite_cleartext(
&mut self,
slot: Slot,
private_key: &[u8; 32],
) -> Result<(), AteccError<H::Error>>
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,
impl<H> AteccChannel<'_, H>where
H: AteccHal,
Source§impl<H> AteccChannel<'_, H>where
H: AteccHal,
impl<H> AteccChannel<'_, H>where
H: AteccHal,
Sourcepub(crate) async fn read_4(
&mut self,
zone: Zone,
address: u16,
) -> Result<[u8; 4], AteccError<H::Error>>
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.
Sourcepub(crate) async fn read_32(
&mut self,
zone: Zone,
address: u16,
) -> Result<[u8; 32], AteccError<H::Error>>
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
Sourcepub async fn read_config_zone(
&mut self,
out: &mut [u8; 128],
) -> Result<(), AteccError<H::Error>>
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.
Sourcepub async fn read_slot_word(
&mut self,
slot: Slot,
block: u8,
offset_words: u8,
) -> Result<[u8; 4], AteccError<H::Error>>
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
Sourcepub async fn read_slot_block(
&mut self,
slot: Slot,
block: u8,
) -> Result<[u8; 32], AteccError<H::Error>>
pub async fn read_slot_block( &mut self, slot: Slot, block: u8, ) -> Result<[u8; 32], AteccError<H::Error>>
Sourcepub async fn write_4(
&mut self,
zone: Zone,
address: u16,
data: &[u8; 4],
) -> Result<(), AteccError<H::Error>>
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.
Sourcepub async fn write_32(
&mut self,
zone: Zone,
address: u16,
data: &[u8; 32],
) -> Result<(), AteccError<H::Error>>
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
Sourcepub async fn write_32_encrypted(
&mut self,
zone: Zone,
address: u16,
ciphertext_and_mac: &[u8; 64],
) -> Result<(), AteccError<H::Error>>
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
Sourcepub async fn write_slot_word(
&mut self,
slot: Slot,
block: u8,
offset_words: u8,
data: &[u8; 4],
) -> Result<(), AteccError<H::Error>>
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
Source§impl<H> AteccChannel<'_, H>where
H: AteccHal,
impl<H> AteccChannel<'_, H>where
H: AteccHal,
Sourcepub async fn sign_external(
&mut self,
slot: Slot,
) -> Result<[u8; 64], AteccError<H::Error>>
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,
impl<H> AteccChannel<'_, H>where
H: AteccHal,
Sourcepub async fn verify_external(
&mut self,
signature: &[u8; 64],
public_key: &[u8; 64],
) -> Result<bool, AteccError<H::Error>>
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,
impl<H> AteccChannel<'_, H>where
H: AteccHal,
Sourcepub async fn close(self) -> Result<(), AteccError<H::Error>>
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.
Sourcepub async fn close_to_sleep(self) -> Result<(), AteccError<H::Error>>
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.
Sourcepub async fn refresh(&mut self) -> Result<(), AteccError<H::Error>>
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.
Sourcepub(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>>
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.
Sourcepub(crate) async fn execute_command_status(
&mut self,
opcode: u8,
param1: u8,
param2: u16,
data: &[u8],
expected_exec_ms: u32,
) -> Result<(), AteccError<H::Error>>
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.
Sourceasync 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>>
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.
Sourceasync fn poll_for_response(
&mut self,
response_buf: &mut [u8],
) -> Result<usize, AteccError<H::Error>>
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,
impl<H> Drop for AteccChannel<'_, H>where
H: AteccHal,
Source§fn drop(&mut self)
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.