### Code
```Cargo.toml
[package]
name = "lcp"
version = "0.1.0"
edition = "2024"…
[dependencies]
async-channel = "2.5.0"
codec = "0.0.9"
embassy-futures = "0.1.2"
embassy-time = "0.5.1"
embedded-io-async = "0.7.0"
heapless = "0.9.3"
```
```Cargo.lock
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "async-channel"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"
dependencies = [
"concurrent-queue",
"event-listener-strategy",
"futures-core",
"pin-project-lite",
]
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "codec"
version = "0.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a947cf08398fd4d78b71a878580a78612f2ac5c3a2a69b63d042d37cf1140a8"
dependencies = [
"heapless",
]
[[package]]
name = "concurrent-queue"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "critical-section"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
[[package]]
name = "crossbeam-utils"
version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
[[package]]
name = "document-features"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
dependencies = [
"litrs",
]
[[package]]
name = "embassy-futures"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc2d050bdc5c21e0862a89256ed8029ae6c290a93aecefc73084b3002cdebb01"
[[package]]
name = "embassy-time"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "592b0c143ec626e821d4d90da51a2bd91d559d6c442b7c74a47d368c9e23d97a"
dependencies = [
"cfg-if",
"critical-section",
"document-features",
"embassy-time-driver",
"embedded-hal 0.2.7",
"embedded-hal 1.0.0",
"embedded-hal-async",
"futures-core",
]
[[package]]
name = "embassy-time-driver"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ee71af1b3a0deaa53eaf2d39252f83504c853646e472400b763060389b9fcc9"
dependencies = [
"document-features",
]
[[package]]
name = "embedded-hal"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff"
dependencies = [
"nb 0.1.3",
"void",
]
[[package]]
name = "embedded-hal"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89"
[[package]]
name = "embedded-hal-async"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884"
dependencies = [
"embedded-hal 1.0.0",
]
[[package]]
name = "embedded-io"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9eb1aa714776b75c7e67e1da744b81a129b3ff919c8712b5e1b32252c1f07cc7"
[[package]]
name = "embedded-io-async"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2564b9f813c544241430e147d8bc454815ef9ac998878d30cc3055449f7fd4c0"
dependencies = [
"embedded-io",
]
[[package]]
name = "event-listener"
version = "5.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
dependencies = [
"concurrent-queue",
"parking",
"pin-project-lite",
]
[[package]]
name = "event-listener-strategy"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
dependencies = [
"event-listener",
"pin-project-lite",
]
[[package]]
name = "futures-core"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
[[package]]
name = "hash32"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
dependencies = [
"byteorder",
]
[[package]]
name = "heapless"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25ba4bd83f9415b58b4ed8dc5714c76e626a105be4646c02630ad730ad3b5aa4"
dependencies = [
"hash32",
"stable_deref_trait",
]
[[package]]
name = "lcp"
version = "0.1.0"
dependencies = [
"async-channel",
"codec",
"embassy-futures",
"embassy-time",
"embedded-io-async",
"heapless",
]
[[package]]
name = "litrs"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
[[package]]
name = "nb"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f"
dependencies = [
"nb 1.1.0",
]
[[package]]
name = "nb"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d"
[[package]]
name = "parking"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
[[package]]
name = "pin-project-lite"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
```
```Rust
#![feature(generic_const_exprs)]
use core::cmp::min;
use async_channel::{Receiver, Sender, bounded};
use codec::{Codec, DecodeError, EncodeBuffer, EncodeError, SizeContext, StaticBuffer};
use embassy_futures::select::{Either, select};
use embassy_time::{Duration, Instant, Timer};
use embedded_io_async::{Read, Write};
use heapless::{Deque, LenType, Vec};
use codec::{DecodeBuffer};
pub trait GetStaticView<'a, View: 'a> {
fn get_static_view(&'a mut self) -> View;
}
impl<'a, const N: usize> GetStaticView<'a, StaticDequeView<'a, N>> for Deque<u8, N> {
fn get_static_view(&'a mut self) -> StaticDequeView<'a, N> {
StaticDequeView {
dq: self,
pointer: 0,
}
}
}
pub struct StaticDequeView<'a, const N: usize> {
dq: &'a mut Deque<u8, N>,
pointer: usize,
}
impl<'a, const N: usize> StaticDequeView<'a, N> {
pub fn new(source: &'a mut Deque<u8, N>) -> StaticDequeView<'a, N> {
StaticDequeView {
dq: source,
pointer: 0,
}
}
/// Commit the read bytes permanently.
pub fn commit(&mut self) {
for _ in 0..self.pointer {
self.dq.pop_front();
}
}
}
impl<'a, const N: usize> DecodeBuffer for StaticDequeView<'a, N> {
fn read(&mut self) -> Result<u8, codec::DecodeError> {
let data = self.dq.get(self.pointer).copied();
self.pointer += 1;
data.ok_or(DecodeError::Incomplete)
}
fn read_slice(&mut self, buf: &mut [u8]) -> Result<(), DecodeError> {
if self.dq.len() - self.pointer < buf.len() {
return Err(DecodeError::Incomplete);
}
for item in buf.iter_mut() {
// SAFETY: Checked len at start of function
*item = unsafe { *self.dq.get_unchecked(self.pointer) };
self.pointer += 1;
}
return Ok(());
}
fn get_read_len(&self) -> usize {
0
}
}
const MAGIC_ID: u8 = 0x97;
const PACKET_RETRY_TIME: u64 = 5; // milliseconds
// TODO: configuration parameters should be provided to user
// TODO: buffer sizes should be pre-computed... somehow.
enum FillState {
Empty,
Partial,
Full,
}
trait GetFillState {
fn fill_state(&self) -> FillState;
}
impl<T, const S: usize, L: LenType> GetFillState for Vec<T, S, L> {
fn fill_state(&self) -> FillState {
match (self.is_empty(), self.is_full()) {
(true,false) => FillState::Empty,
(false,true) => FillState::Full,
(false, false) => FillState::Partial,
(true,true) => panic!("Can't be empty and full"),
}
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Copy, Clone)]
pub struct SequenceId {
raw: u8,
}
impl SequenceId {
pub fn reset(&mut self) {
self.raw = 1;
}
pub fn init() -> SequenceId {
SequenceId { raw: 1 }
}
pub fn none() -> SequenceId {
SequenceId { raw: 0 }
}
pub fn is_none(self) -> bool {self.raw == 0}
pub fn is_some(self) -> bool {self.raw != 0}
pub fn get_next(&mut self) -> SequenceId {
// TODO: Verify branchless is faster.
// branchless programming for
// if none, stay none, else wrapping add 1->255->1
let original = self.raw;
let raw = self.raw;
let is_some: bool = raw != 0;
let new = raw.wrapping_add(1);
let new = new + ((new == 0) as u8);
// if started none, stay none
self.raw = new * (is_some as u8);
SequenceId { raw: original }
}
fn get_n_away(mut self, n: u8) -> SequenceId {
for _ in 0..n {
self.get_next();
}
self
}
pub fn is_within(self, last_received: SequenceId, within: u8) -> bool {
/*
let positions_ahead = SequenceId::distance(last_received, self);
println!("A->B: {positions_ahead}");
positions_ahead <= within*/
let end = last_received.get_n_away(within);
((self.raw <= end.raw) &
(self.raw >= last_received.raw) &
(last_received.raw <= end.raw))
|
((self.raw >= end.raw) &
(self.raw >= last_received.raw) &
(last_received.raw > end.raw))
|
((self.raw <= end.raw) &
(self.raw <= last_received.raw) &
(last_received.raw > end.raw))
}
}
impl Codec<()> for SequenceId {
fn encode(&self, buf: &mut impl EncodeBuffer, _: ()) -> Result<(), EncodeError> {
self.raw.encode(buf, ())
}
fn decode(buf: &mut impl codec::DecodeBuffer, _: ()) -> Result<Self, DecodeError> {
Ok(Self {raw: u8::decode(buf, ())?})
}
}
// TODO: Separate local max payload from transports max payload
pub fn build_transport<Reader: Read, Writer: Write, const MAX_PAYLOAD: usize>(
reader: Reader,
writer: Writer,
) -> (
TransportReader<Reader, MAX_PAYLOAD>,
TransportWriter<Writer, MAX_PAYLOAD>,
Sender<Vec<u8, MAX_PAYLOAD, u8>>,
Receiver<Vec<u8, MAX_PAYLOAD, u8>>,
)
where [(); HEADER_SIZE+MAX_PAYLOAD]: {
// TODO: Does received or ack channels need to be larger to prevent starving the
// serial read connection.
let (received, rx) = bounded(3);
let (tx, to_send) = bounded(3);
let (ack_tx, ack_rx) = bounded(3);
let tr = TransportReader::new(reader, ack_tx, received);
let tw = TransportWriter::new(writer, to_send, ack_rx);
(tr, tw, tx, rx)
}
const RECV_Q_SIZE: usize = 5;
const RESET_INVALID_BYTE_COUNT: Option<usize> = None;
const RESET_OUT_OF_RANGE_COUNT: usize = 100;
struct RecvErrorProcessor {
byte_errors: usize,
last_process_good: bool,
out_of_range_errs: usize,
}
enum RecvErrorCmd {
None,
Reset,
Request,
}
impl RecvErrorProcessor {
pub fn new() -> Self {
Self { byte_errors: 0, last_process_good: true, out_of_range_errs: 0 }
}
pub fn byte_error(&mut self) -> RecvErrorCmd {
self.byte_errors += 1;
if self.last_process_good {
self.last_process_good = false;
return RecvErrorCmd::Request;
}
if let Some(count) = RESET_INVALID_BYTE_COUNT {
if self.byte_errors >= count {
return RecvErrorCmd::Reset;
}
}
RecvErrorCmd::None
}
pub fn good_packet(&mut self) {
self.last_process_good = true;
self.byte_errors = 0;
}
pub fn good_packet_index(&mut self) {
self.out_of_range_errs = 0;
}
pub fn out_of_range(&mut self) -> RecvErrorCmd {
self.out_of_range_errs += 1;
if self.out_of_range_errs >= RESET_OUT_OF_RANGE_COUNT {
RecvErrorCmd::Reset
} else {
RecvErrorCmd::None
}
}
pub fn reset(&mut self) {
*self = RecvErrorProcessor::new();
}
}
enum RecvQCmd {
Ack{acked: SequenceId, to_ack: SequenceId},
SeqReq(SequenceId),
None,
}
struct ReceiveQueue<const DATA_SIZE: usize> {
next_seq_id: SequenceId,
q: Vec<Packet<DATA_SIZE>, RECV_Q_SIZE>
}
impl<const DATA_SIZE: usize> ReceiveQueue<DATA_SIZE> {
fn new() -> Self {
Self { next_seq_id: SequenceId::init(), q: Vec::new() }
}
fn reset(&mut self) {
self.q.clear();
self.next_seq_id = SequenceId::init();
}
fn process_recv(&mut self, pkt: Packet<DATA_SIZE>) -> RecvQCmd {
if pkt.sequence_id
.is_within(self.next_seq_id, self.q.capacity() as u8) {
let ack_id = pkt.ack_id;
let seq_id = pkt.sequence_id;
self.q.push(pkt).unwrap();
RecvQCmd::Ack { acked: ack_id, to_ack: seq_id }
} else if self.next_seq_id
.is_within(pkt.sequence_id, self.q.capacity() as u8) {
// Packet already received recently. The retransmit went out before
// we could acknowledge the first transmit. Ignore
RecvQCmd::None
}else {
// Request next sequence if we started getting values out of range.
RecvQCmd::SeqReq(self.next_seq_id)
}
}
fn get_ready_data(&mut self) -> Option<Packet<DATA_SIZE>> {
for index in 0..self.q.len() {
if self.q.get(index).is_some_and(|pkt| pkt.sequence_id == self.next_seq_id) {
self.next_seq_id.get_next();
return Some(self.q.swap_remove(index))
}
}
None
}
}
#[derive(PartialEq, Eq)]
enum RxData {
Ack{acked: SequenceId, to_ack: SequenceId},
WeRequest(SequenceId),
TheyRequested(SequenceId),
WeReset,
TheyReset,
Started,
}
pub struct TransportReader<Reader: Read, const DATA_SIZE: usize> {
reader: Reader,
ack_tx: Sender<RxData>,
received: Sender<Vec<u8, DATA_SIZE, u8>>,
recv_q: ReceiveQueue<DATA_SIZE>,
err_proc: RecvErrorProcessor,
}
impl<Reader: Read, const DATA_SIZE: usize> TransportReader<Reader, DATA_SIZE> {
fn new(
reader: Reader,
ack_tx: Sender<RxData>,
received: Sender<Vec<u8, DATA_SIZE, u8>>
) -> TransportReader<Reader, DATA_SIZE> {
TransportReader {
reader, ack_tx, received, recv_q: ReceiveQueue::new(),
err_proc: RecvErrorProcessor::new()
}
}
async fn fill_buf<const SIZE: usize>(&mut self, buf: &mut Deque<u8, SIZE>) {
// TODO: pre-calculate buffer size
let max_fill = buf.storage_capacity() - buf.len();
const WORKING_BUF_SIZE: usize = 50;
let mut working_buf = [0u8; WORKING_BUF_SIZE];
if let Ok(read) = self.reader.read(&mut working_buf[0..max_fill]).await {
for item in working_buf.iter().take(read) {
buf.push_back(*item);
}
}
}
fn reset(&mut self) {
self.recv_q.reset();
self.err_proc.reset();
}
async fn process_pkt(&mut self, packet: Packet<DATA_SIZE>) {
match packet.cmd {
PacketCmd::Reset => {
self.reset();
self.ack_tx.send(RxData::TheyReset).await.unwrap();
},
PacketCmd::Start => {
self.reset();
self.ack_tx.send(RxData::Started).await.unwrap();
}
PacketCmd::Request => {
self.ack_tx
.send(RxData::TheyRequested(packet.sequence_id))
.await
.unwrap();
}
PacketCmd::None => {
// Only use ack_id, since we don't ack none packets
self.ack_tx
.send(RxData::Ack { acked: packet.ack_id, to_ack: SequenceId::none() })
.await
.unwrap();
}
PacketCmd::Data => {
match self.recv_q.process_recv(packet) {
RecvQCmd::SeqReq(seq_id) => {
if let RecvErrorCmd::Reset = self.err_proc.out_of_range() {
self.reset();
self.ack_tx.send(RxData::WeReset).await.unwrap();
}
self.ack_tx
.send(RxData::WeRequest(seq_id))
.await
.unwrap();
}
RecvQCmd::Ack { acked, to_ack } => {
self.err_proc.good_packet_index();
self.ack_tx
.send(RxData::Ack { acked, to_ack })
.await
.unwrap();
}
RecvQCmd::None => {
// No action necessary
}
}
while let Some(pkt) = self.recv_q.get_ready_data() {
self.received.send(pkt.data).await.unwrap();
}
}
}
}
pub async fn run(&mut self) -> !
where [(); HEADER_SIZE + DATA_SIZE]: {
let mut buf = Deque::<_, { HEADER_SIZE + DATA_SIZE }>::new();
loop {
self.fill_buf(&mut buf).await;
let mut static_view = buf.get_static_view();
match Packet::decode(&mut static_view, ()) {
Ok(packet) => {
self.err_proc.good_packet();
static_view.commit();
self.process_pkt(packet).await;
},
Err(DecodeError::Incomplete) => continue,
Err(DecodeError::Invalid) => {
match self.err_proc.byte_error() {
RecvErrorCmd::None => (),
RecvErrorCmd::Reset => {
self.reset();
self.ack_tx.send(RxData::WeReset).await.unwrap();
},
RecvErrorCmd::Request => {
self.ack_tx.send(RxData::WeRequest(self.recv_q.next_seq_id)).await.unwrap();
},
}
buf.pop_front();
continue;
}
};
}
}
}
const SEND_Q_SIZE: usize = 5;
struct WriteQueue<const DATA_SIZE: usize> {
q: Vec<PacketInfo<DATA_SIZE>, SEND_Q_SIZE>,
}
impl<const DATA_SIZE: usize> WriteQueue<DATA_SIZE> {
fn new() -> Self {
Self { q: Vec::new() }
}
fn clear(&mut self) {
self.q.clear();
}
fn fill_state(&self) -> FillState {
self.q.fill_state()
}
fn ack(&mut self, ack_id: SequenceId) {
// Remove data which was confirmed as sent.
self.q.retain(|pkt| pkt.pkt.sequence_id != ack_id);
}
fn get_mut(&mut self, index: usize) -> Option<&mut PacketInfo<DATA_SIZE>> {
self.q.get_mut(index)
}
fn get_next_to_send(&mut self) -> Option<usize> {
self.q.iter_mut().enumerate().reduce(|a, b|{
if a.1.retry < b.1.retry {
a
} else {
b
}
}).map(|pair|pair.0)
}
fn add(&mut self, to_add: PacketInfo<DATA_SIZE>) -> usize {
self.q.push(to_add).expect("Fill state checked before calling");
self.q.len() - 1 // index of last element
}
fn index_from_id(&self, seq_id: SequenceId) -> Option<usize> {
for (index, item) in self.q.iter().enumerate() {
if item.pkt.sequence_id == seq_id {return Some(index)}
}
None
}
}
#[derive(Debug)]
struct PacketInfo<const DATA_SIZE: usize> {
pkt: Packet<DATA_SIZE>,
retry: Instant
}
pub struct TransportWriter<Writer: Write, const DATA_SIZE: usize> {
writer: Writer,
to_send: Receiver<Vec<u8, DATA_SIZE, u8>>,
ack_rx: Receiver<RxData>,
write_buf: Deque<u8, 100>,
tx_q: WriteQueue<DATA_SIZE>,
to_ack: Vec<SequenceId, 10>,
current_id: SequenceId,
}
impl<Writer: Write, const DATA_SIZE: usize> TransportWriter<Writer, DATA_SIZE>
where [(); HEADER_SIZE+DATA_SIZE]: {
fn new(
writer: Writer,
to_send: Receiver<Vec<u8, DATA_SIZE, u8>>,
ack_rx: Receiver<RxData>
) -> TransportWriter<Writer, DATA_SIZE> {
TransportWriter {
writer, write_buf: Deque::new(),
to_send, ack_rx,
tx_q: WriteQueue::new(),
to_ack: Vec::new(),
current_id: SequenceId::init()
}
}
async fn write_index(&mut self, index: usize) {
let mut working_buf = [0u8; HEADER_SIZE+DATA_SIZE];
let mut static_buf = StaticBuffer::new(&mut working_buf);
let pkt = &self.tx_q.get_mut(index).expect("Can only call write_tx with valid packet").pkt;
println!("Write index: {index} {pkt:?}");
pkt.encode(&mut static_buf, ())
.expect("Must have enough space, and be valid packet");
let len = static_buf.get_encoded_len();
self.writer
.write_all(&mut working_buf[0..len])
.await
.expect("Must be writeable")
}
async fn write(&mut self, pkt: &Packet<DATA_SIZE>) {
let mut working_buf = [0u8; HEADER_SIZE+DATA_SIZE];
let mut static_buf = StaticBuffer::new(&mut working_buf);
pkt.encode(&mut static_buf, ())
.expect("Must have enough space, and be valid packet");
let len = static_buf.get_encoded_len();
self.writer
.write_all(&mut working_buf[0..len])
.await
.expect("Must be writeable");
println!("Write: {:?}", pkt);
}
// Should only be called with self.rx_pkt as None
async fn process_rx_data(&mut self, mut pkt: RxData) {
if self.to_ack.is_full() {panic!("Must not be full before process rx is called")}
match &mut pkt {
RxData::Ack { acked, to_ack } => {
self.tx_q.ack(*acked);
if to_ack.is_some() {
self.to_ack.push(*to_ack).expect("Already confirmed size");
}
}
RxData::WeRequest(id) => {
println!("We didn't receive \'{:?}\' request to send again", id);
self.write(&Packet::request(*id)).await
}
RxData::TheyRequested(id) => {
println!("They didn't receive \'{:?}\' we are sending again", id);
if let Some(index) = self.tx_q.index_from_id(*id) {
// Resend if reported lost
self.write_index(index).await
} else if *id == self.current_id {
// They want the next ID we don't have yet
// ignore
}else {
println!("current_id: {:?}", self.current_id);
todo!("Can consistency be recovered?");
}
}
RxData::WeReset => {
println!("Reset Write");
self.current_id.reset();
self.to_ack.clear();
self.tx_q.clear();
self.process_reset();
}
RxData::Started => {
// Ignore. Processed in WeReset when needed
}
RxData::TheyReset => {
println!("Reset Write");
self.current_id.reset();
self.to_ack.clear();
self.tx_q.clear();
self.write(&Packet::start()).await;
}
}
}
async fn process_reset(&mut self) {
self.write(&Packet::reset()).await;
loop {
match select(
self.ack_rx.recv(),
Timer::after(Duration::from_millis(100))
).await {
Either::First(pkt) => {
let pkt = pkt.expect("Communication must be maintained");
if let RxData::TheyReset = pkt {
// If we are both in the reset state, ack their reset
self.write(&Packet::start()).await
}
if let RxData::Started = pkt {
// Reset Acked, we are now synced
return;
}
}
Either::Second(_) => {
self.write(&Packet::reset()).await;
}
}
}
while self.ack_rx.recv().await.unwrap() != RxData::Started {
// Wait for packet with Started. Discard everything else.
}
}
fn has_sized_room(&self) -> bool {
self.write_buf.capacity() - self.write_buf.len() >= DATA_SIZE
}
fn has_sendable_data(&self) -> bool {
! self.write_buf.is_empty()
}
fn create_packet(&mut self) -> usize {
let mut data = Vec::new();
for _ in 0..min(DATA_SIZE, self.write_buf.len()) {
data.push(
self.write_buf
.pop_front()
.expect("Data len must be within buffer")
).expect("Data must fit in packet")
}
let ack = match self.to_ack.pop() {
Some(ack) => ack,
None => SequenceId::none(),
};
let info = PacketInfo {
pkt: Packet {
sequence_id: self.current_id.get_next(),
ack_id: ack,
data, cmd: PacketCmd::Data
}, retry: Self::get_retry_instant()
};
self.tx_q.add(info)
}
fn get_retry_instant() -> Instant {
Instant::now() + Duration::from_millis(PACKET_RETRY_TIME)
}
pub async fn run(mut self) -> ! {
// TODO: Does rx packet get taken into account immediately.
// Sequence invalidated, must start from scratch
// TODO: Make actual state machine to more easily represent
// and communicate graph of behavior
self.process_reset().await;
loop {
match (
self.to_ack.fill_state(),
self.tx_q.fill_state(),
self.has_sized_room(),
self.has_sendable_data()
) {
(_, _, false, false) => {
panic!("Can't have no room, and also have no data");
}
(_, FillState::Empty | FillState::Partial, _, true) => {
// Send packet with possible ack as well
let new_index = self.create_packet();
self.write_index(new_index).await;
}
(FillState::Partial | FillState::Full, _, _, false) => {
// Send ack when no data ready
let pkt = Packet::ack(
self.to_ack.pop().expect("Can't be empty")
);
self.write(&pkt).await;
}
(FillState::Empty, FillState::Empty, true, false) => {
// wait for something to do
match select(self.ack_rx.recv(), self.to_send.recv()).await {
Either::First(rx) => {
self.process_rx_data(rx.expect("Must maintain communication")).await;
},
Either::Second(data) => {
let data = data.expect("Must maintain communication");
self.write_buf.extend(data);
},
}
}
(FillState::Empty, FillState::Partial | FillState::Full, _, _) => {
// Wait for packet retransmit or ack_rx
let retransmit_index = self.tx_q.get_next_to_send().unwrap();
let retransmit = self.tx_q.get_mut(retransmit_index).unwrap();
match select(Timer::at(retransmit.retry), self.ack_rx.recv()).await {
Either::First(_) => {
self.write_index(retransmit_index).await;
// TODO: Can't guarantee same index. Fix this
self.tx_q.get_mut(retransmit_index).unwrap().retry = Self::get_retry_instant();
},
Either::Second(rx_data) => {
self.process_rx_data(rx_data.expect("Must maintain communication")).await;
},
}
}
(FillState::Partial, FillState::Full, true, true) => {
todo!();
}
(FillState::Partial, FillState::Full, false, true) => {
todo!();
}
(FillState::Full, FillState::Full, _, _) => {
// TODO: Can we get stuck in this state? How would we get out?
// Can't do anything with full buffers besides wait to send again.
todo!("Temp");
/*Timer::at(pkt.retry).await;
self.write_index().await;
self.tx_q.as_mut().expect("matched on exists").retry = Self::get_retry_intant();*/
}
}
}
}
}
/// Packet(SIZE):
/// magic_id(u8): ?
/// cmd(u8)
/// sequence_id(u8)
/// size(u8?)
/// checksum(u16)
/// checksum(u16)
/// data[SIZE]
///
const HEADER_SIZE: usize = 8;
const CHECKSUM_SIZE: usize = 4;
#[derive(Debug)]
pub enum PacketCmd {
None, // Used for empty ack
Data,
Request,
Reset, // Got out of sync, and can't fix
Start,
}
impl Codec<()> for PacketCmd {
fn encode(&self, buf: &mut impl EncodeBuffer, _: ()) -> Result<(), EncodeError> {
match self {
Self::None => 0u8.encode(buf, ()),
Self::Data => 1u8.encode(buf, ()),
Self::Request => 2u8.encode(buf, ()),
Self::Reset => 3u8.encode(buf, ()),
Self::Start => 4u8.encode(buf, ()),
}
}
fn decode(buf: &mut impl codec::DecodeBuffer, _: ()) -> Result<Self, DecodeError> {
let cmd = u8::decode(buf, ())?;
match cmd {
0 => Ok(Self::None),
1 => Ok(Self::Data),
2 => Ok(Self::Request),
3 => Ok(Self::Reset),
4 => Ok(Self::Start),
_ => Err(DecodeError::Invalid),
}
}
}
#[derive(Debug)]
pub struct Packet<const DATA_SIZE: usize> {
sequence_id: SequenceId,
ack_id: SequenceId,
data: Vec<u8, DATA_SIZE, u8>,
cmd: PacketCmd,
}
impl<const DATA_SIZE: usize> Packet<DATA_SIZE> {
fn new(sequence_id: SequenceId, ack_id: SequenceId, raw: &[u8]) -> Result<Packet<DATA_SIZE>, ()> {
Ok(Packet {
sequence_id: sequence_id,
data: Vec::<u8, DATA_SIZE, u8>::from_slice(raw).map_err(|_| ())?,
cmd: PacketCmd::Data,
ack_id
})
}
pub fn ack(ack_id: SequenceId) -> Packet<DATA_SIZE> {
Packet {
sequence_id: SequenceId::none(),
data: Vec::from_slice(&[]).expect("Empty slice always succeeds"),
cmd: PacketCmd::None, ack_id
}
}
pub fn request(sequence_id: SequenceId) -> Packet<DATA_SIZE> {
Packet { sequence_id, data: Vec::new(), cmd: PacketCmd::Request, ack_id: SequenceId::none() }
}
pub fn reset() -> Packet<DATA_SIZE> {
Packet { sequence_id: SequenceId::none(), data: Vec::new(), cmd: PacketCmd::Reset, ack_id: SequenceId::none() }
}
pub fn start() -> Packet<DATA_SIZE> {
Packet {
sequence_id: SequenceId::none(), data: Vec::new(),
cmd: PacketCmd::Start, ack_id: SequenceId::none()
}
}
}
impl<const DATA_SIZE: usize> Codec<()> for Packet<DATA_SIZE> {
fn encode(&self, buf: &mut impl EncodeBuffer, _: ()) -> Result<(), EncodeError> {
MAGIC_ID.encode(buf, ())?;
self.cmd.encode(buf, ())?;
self.sequence_id.encode(buf, ())?;
self.ack_id.encode(buf, ())?;
self.data.encode(buf, SizeContext::U8Len)?;
// TODO: Checksum
Ok(())
}
fn decode(buf: &mut impl codec::DecodeBuffer, _: ()) -> Result<Self, DecodeError> {
if u8::decode(buf, ())? != MAGIC_ID {return Err(DecodeError::Invalid);}
let cmd = PacketCmd::decode(buf, ())?;
let sequence_id = SequenceId::decode(buf, ())?;
let ack_id = SequenceId::decode(buf, ())?;
let payload = Vec::<u8, DATA_SIZE, u8>::decode(buf, SizeContext::U8Len)?;
Ok(Packet {
sequence_id,
ack_id,
data: payload,
cmd,
})
}
}
```
### Meta
`rustc --version --verbose`:
```
rustc 1.99.0-nightly (1a833e165 2026-07-29)
binary: rustc
commit-hash: 1a833e16546c2eb012758ddd499964fd8afee29e
commit-date: 2026-07-29
host: x86_64-unknown-linux-gnu
release: 1.99.0-nightly
LLVM version: 22.1.8
```
### Error output
```
thread 'rustc' (37087) panicked at /rustc-dev/1a833e16546c2eb012758ddd499964fd8afee29e/compiler/rustc_type_ir/src/binder.rs:788:9:
type parameter `/#2` (/#2/2) out of range when instantiating, args=[Writer/#0, DATA_SIZE/#1]
stack backtrace:
0: 0x7b18730b64d6 - <<std[a31cbe3acb964aad]::sys::backtrace::BacktraceLock>::print::DisplayBacktrace as core[ed5d9bd1b3102e1a]::fmt::Display>::fmt
1: 0x7b187380ad52 - core[ed5d9bd1b3102e1a]::fmt::write
2: 0x7b18730cb61c - <std[a31cbe3acb964aad]::sys::stdio::unix::Stderr as core[ed5d9bd1b3102e1a]::io::write::Write>::write_fmt
3: 0x7b18730880aa - std[a31cbe3acb964aad]::panicking::default_hook::{closure#0}
4: 0x7b18730a8bb3 - std[a31cbe3acb964aad]::panicking::default_hook
5: 0x7b187201835b - std[a31cbe3acb964aad]::panicking::update_hook::<alloc[bd6e42b2068c47ab]::boxed::Box<rustc_driver_impl[6783d01fd94ba16f]::install_ice_hook::{closure#1}>>::{closure#0}
6: 0x7b18730a9052 - std[a31cbe3acb964aad]::panicking::panic_with_hook
7: 0x7b1873088162 - std[a31cbe3acb964aad]::panicking::panic_handler::{closure#0}
8: 0x7b187307fd79 - std[a31cbe3acb964aad]::sys::backtrace::__rust_end_short_backtrace::<std[a31cbe3acb964aad]::panicking::panic_handler::{closure#0}, !>
9: 0x7b1873089b5d - __rustc[1dc8cc5ee6929c6b]::rust_begin_unwind
10: 0x7b186fe1682c - core[ed5d9bd1b3102e1a]::panicking::panic_fmt
11: 0x7b1872622e35 - <rustc_type_ir[d2e4ec8bb1fc4f7f]::binder::ArgFolder<rustc_middle[8a4fff7d680873ae]::ty::context::TyCtxt>>::type_param_out_of_range
12: 0x7b187385265a - <rustc_type_ir[d2e4ec8bb1fc4f7f]::binder::ArgFolder<rustc_middle[8a4fff7d680873ae]::ty::context::TyCtxt> as rustc_type_ir[d2e4ec8bb1fc4f7f]::fold::TypeFolder<rustc_middle[8a4fff7d680873ae]::ty::context::TyCtxt>>::fold_ty
13: 0x7b18738540f4 - <&rustc_middle[8a4fff7d680873ae]::ty::list::RawList<(), rustc_middle[8a4fff7d680873ae]::ty::generic_args::GenericArg> as rustc_type_ir[d2e4ec8bb1fc4f7f]::fold::TypeFoldable<rustc_middle[8a4fff7d680873ae]::ty::context::TyCtxt>>::fold_with::<rustc_type_ir[d2e4ec8bb1fc4f7f]::binder::ArgFolder<rustc_middle[8a4fff7d680873ae]::ty::context::TyCtxt>>
14: 0x7b187384f2de - <rustc_type_ir[d2e4ec8bb1fc4f7f]::binder::ArgFolder<rustc_middle[8a4fff7d680873ae]::ty::context::TyCtxt> as rustc_type_ir[d2e4ec8bb1fc4f7f]::fold::TypeFolder<rustc_middle[8a4fff7d680873ae]::ty::context::TyCtxt>>::fold_const
15: 0x7b187384ff5a - <rustc_type_ir[d2e4ec8bb1fc4f7f]::binder::ArgFolder<rustc_middle[8a4fff7d680873ae]::ty::context::TyCtxt> as rustc_type_ir[d2e4ec8bb1fc4f7f]::fold::TypeFolder<rustc_middle[8a4fff7d680873ae]::ty::context::TyCtxt>>::fold_ty
16: 0x7b1873c6544f - <&rustc_middle[8a4fff7d680873ae]::ty::list::RawList<(), rustc_middle[8a4fff7d680873ae]::ty::Ty> as rustc_type_ir[d2e4ec8bb1fc4f7f]::fold::TypeFoldable<rustc_middle[8a4fff7d680873ae]::ty::context::TyCtxt>>::fold_with::<rustc_type_ir[d2e4ec8bb1fc4f7f]::binder::ArgFolder<rustc_middle[8a4fff7d680873ae]::ty::context::TyCtxt>>
17: 0x7b1872ddfd28 - <rustc_type_ir[d2e4ec8bb1fc4f7f]::binder::EarlyBinder<rustc_middle[8a4fff7d680873ae]::ty::context::TyCtxt, rustc_type_ir[d2e4ec8bb1fc4f7f]::binder::Binder<rustc_middle[8a4fff7d680873ae]::ty::context::TyCtxt, rustc_type_ir[d2e4ec8bb1fc4f7f]::ty_kind::CoroutineWitnessTypes<rustc_middle[8a4fff7d680873ae]::ty::context::TyCtxt>>>>::instantiate::<&rustc_middle[8a4fff7d680873ae]::ty::list::RawList<(), rustc_middle[8a4fff7d680873ae]::ty::generic_args::GenericArg>>
18: 0x7b187382dcf5 - <rustc_trait_selection[aba9d9ed9a7e610d]::traits::select::SelectionContext>::confirm_auto_impl_candidate::{closure#0}
19: 0x7b1874b18f0b - <rustc_trait_selection[aba9d9ed9a7e610d]::traits::select::SelectionContext>::confirm_candidate
20: 0x7b1873a93c06 - <rustc_trait_selection[aba9d9ed9a7e610d]::traits::select::SelectionContext>::evaluate_trait_predicate_recursively
21: 0x7b1873a8c7d1 - <rustc_trait_selection[aba9d9ed9a7e610d]::traits::select::SelectionContext>::evaluate_predicate_recursively
22: 0x7b1873a93d87 - <rustc_trait_selection[aba9d9ed9a7e610d]::traits::select::SelectionContext>::evaluate_trait_predicate_recursively
23: 0x7b1873a8c7d1 - <rustc_trait_selection[aba9d9ed9a7e610d]::traits::select::SelectionContext>::evaluate_predicate_recursively
24: 0x7b1873a93d87 - <rustc_trait_selection[aba9d9ed9a7e610d]::traits::select::SelectionContext>::evaluate_trait_predicate_recursively
25: 0x7b1873a8c7d1 - <rustc_trait_selection[aba9d9ed9a7e610d]::traits::select::SelectionContext>::evaluate_predicate_recursively
26: 0x7b1873a93d87 - <rustc_trait_selection[aba9d9ed9a7e610d]::traits::select::SelectionContext>::evaluate_trait_predicate_recursively
27: 0x7b1873a90d50 - <rustc_trait_selection[aba9d9ed9a7e610d]::traits::select::SelectionContext>::evaluate_root_obligation
28: 0x7b1873a8eeb5 - rustc_traits[d548f957c06040bd]::evaluate_obligation::evaluate_obligation
29: 0x7b1873a8e74f - rustc_query_impl[6a9183a651f07735]::query_impl::evaluate_obligation::invoke_provider_fn::__rust_begin_short_backtrace
30: 0x7b18745c1c06 - rustc_query_impl[6a9183a651f07735]::execution::try_execute_query::<rustc_middle[8a4fff7d680873ae]::query::caches::DefaultCache<rustc_type_ir[d2e4ec8bb1fc4f7f]::canonical::CanonicalQueryInput<rustc_middle[8a4fff7d680873ae]::ty::context::TyCtxt, rustc_middle[8a4fff7d680873ae]::ty::ParamEnvAnd<rustc_middle[8a4fff7d680873ae]::ty::predicate::Predicate>>, rustc_middle[8a4fff7d680873ae]::query::erase::ErasedData<[u8; 2usize]>>, true>
31: 0x7b18745c0e90 - rustc_query_impl[6a9183a651f07735]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace
32: 0x7b1874ca5214 - rustc_trait_selection[aba9d9ed9a7e610d]::traits::type_known_to_meet_bound_modulo_regions
33: 0x7b1873e90830 - rustc_ty_utils[6aa3e9345cd6b371]::common_traits::is_trait_raw
34: 0x7b1873e903de - rustc_query_impl[6a9183a651f07735]::query_impl::is_freeze_raw::invoke_provider_fn::__rust_begin_short_backtrace
35: 0x7b1873d18eb6 - rustc_query_impl[6a9183a651f07735]::execution::try_execute_query::<rustc_middle[8a4fff7d680873ae]::query::caches::DefaultCache<rustc_middle[8a4fff7d680873ae]::ty::PseudoCanonicalInput<rustc_middle[8a4fff7d680873ae]::ty::Ty>, rustc_middle[8a4fff7d680873ae]::query::erase::ErasedData<[u8; 1usize]>>, true>
36: 0x7b1873d1ce27 - rustc_query_impl[6a9183a651f07735]::query_impl::is_freeze_raw::execute_query_incr::__rust_end_short_backtrace
37: 0x7b1874173d6f - <rustc_middle[8a4fff7d680873ae]::ty::Ty>::is_freeze
38: 0x7b1874174fc0 - <rustc_mir_transform[20f807b108e81fbd]::ssa::SsaLocals>::new
39: 0x7b1870dcb554 - <rustc_mir_transform[20f807b108e81fbd]::copy_prop::CopyProp as rustc_mir_transform[20f807b108e81fbd]::pass_manager::MirPass>::run_pass
40: 0x7b187380912a - rustc_mir_transform[20f807b108e81fbd]::pass_manager::run_passes_inner
41: 0x7b1873afc112 - rustc_mir_transform[20f807b108e81fbd]::optimized_mir
42: 0x7b1873afb317 - rustc_query_impl[6a9183a651f07735]::query_impl::optimized_mir::invoke_provider_fn::__rust_begin_short_backtrace
43: 0x7b1873b6b66f - rustc_query_impl[6a9183a651f07735]::execution::try_execute_query::<rustc_middle[8a4fff7d680873ae]::query::caches::DefIdCache<rustc_middle[8a4fff7d680873ae]::query::erase::ErasedData<[u8; 8usize]>>, true>
44: 0x7b1873b5bc9d - rustc_query_impl[6a9183a651f07735]::query_impl::optimized_mir::execute_query_incr::__rust_end_short_backtrace
45: 0x7b1874b2fd7a - <rustc_middle[8a4fff7d680873ae]::ty::context::TyCtxt>::coroutine_layout
46: 0x7b187464f39e - rustc_ty_utils[6aa3e9345cd6b371]::layout::layout_of_uncached
47: 0x7b1874647878 - rustc_ty_utils[6aa3e9345cd6b371]::layout::layout_of
48: 0x7b1874647791 - rustc_query_impl[6a9183a651f07735]::query_impl::layout_of::invoke_provider_fn::__rust_begin_short_backtrace
49: 0x7b1873d49bbf - rustc_query_impl[6a9183a651f07735]::execution::try_execute_query::<rustc_middle[8a4fff7d680873ae]::query::caches::DefaultCache<rustc_middle[8a4fff7d680873ae]::ty::PseudoCanonicalInput<rustc_middle[8a4fff7d680873ae]::ty::Ty>, rustc_middle[8a4fff7d680873ae]::query::erase::ErasedData<[u8; 16usize]>>, true>
50: 0x7b1873d49131 - rustc_query_impl[6a9183a651f07735]::query_impl::layout_of::execute_query_incr::__rust_end_short_backtrace
51: 0x7b1873bfb901 - <rustc_middle[8a4fff7d680873ae]::ty::context::TyCtxt>::par_hir_body_owners::<rustc_interface[fe012aea17673c3c]::passes::run_required_analyses::{closure#2}::{closure#0}>::{closure#0}
52: 0x7b1873bfa510 - rustc_interface[fe012aea17673c3c]::passes::analysis
53: 0x7b1874a212d2 - rustc_query_impl[6a9183a651f07735]::execution::try_execute_query::<rustc_middle[8a4fff7d680873ae]::query::caches::SingleCache<rustc_middle[8a4fff7d680873ae]::query::erase::ErasedData<[u8; 0usize]>>, true>
54: 0x7b1874a20a63 - rustc_query_impl[6a9183a651f07735]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace
55: 0x7b18749cfa99 - rustc_interface[fe012aea17673c3c]::interface::run_compiler::<(), rustc_driver_impl[6783d01fd94ba16f]::run_compiler::{closure#0}>::{closure#2}
56: 0x7b1874a01f64 - std[a31cbe3acb964aad]::sys::backtrace::__rust_begin_short_backtrace::<rustc_interface[fe012aea17673c3c]::util::run_in_thread_with_globals<rustc_interface[fe012aea17673c3c]::util::run_in_thread_pool_with_globals<rustc_interface[fe012aea17673c3c]::interface::run_compiler<(), rustc_driver_impl[6783d01fd94ba16f]::run_compiler::{closure#0}>::{closure#2}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>
57: 0x7b1874a01d2d - <std[a31cbe3acb964aad]::thread::lifecycle::spawn_unchecked<rustc_interface[fe012aea17673c3c]::util::run_in_thread_with_globals<rustc_interface[fe012aea17673c3c]::util::run_in_thread_pool_with_globals<rustc_interface[fe012aea17673c3c]::interface::run_compiler<(), rustc_driver_impl[6783d01fd94ba16f]::run_compiler::{closure#0}>::{closure#2}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[ed5d9bd1b3102e1a]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0}
58: 0x7b1874a0e0fa - <std[a31cbe3acb964aad]::sys::thread::unix::Thread>::new::thread_start
59: 0x7b186de9cb84 - start_thread
at ./nptl/pthread_create.c:447:8
60: 0x7b186df29d6c - clone3
at ./misc/../sysdeps/unix/sysv/linux/x86_64/clone3.S:78:0
61: 0x0 - <unknown>
error: the compiler unexpectedly panicked. This is a bug
note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md
note: please make sure that you have updated to the latest nightly
note: please attach the file at `/home/owner/repos/compiler_error/lcp/rustc-ice-2026-07-31T01_24_04-37084.txt` to your bug report
note: rustc 1.99.0-nightly (1a833e165 2026-07-29) running on x86_64-unknown-linux-gnu
note: compiler flags: --crate-type lib -C embed-bitcode=no -C debuginfo=2 -C incremental=[REDACTED]
note: some of the compiler flags provided by cargo are hidden
query stack during panic:
#0 [evaluate_obligation] evaluating trait selection obligation `{async fn body of protocol::<impl at src/protocol.rs:472:1: 473:35>::process_reset()}: core::marker::Freeze`
#1 [is_freeze_raw] computing whether `{async fn body of protocol::<impl at src/protocol.rs:472:1: 473:35>::process_reset()}` is freeze
... and 3 other queries... use `env RUST_BACKTRACE=1` to see the full query stack
warning: `lcp` (lib) generated 2 warnings
error: could not compile `lcp` (lib); 2 warnings emitted
Caused by:
process didn't exit successfully: `/home/owner/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/rustc --crate-name lcp --edition=2024 src/lib.rs --error-format=json --json=diagnostic-rendered-ansi,artifacts,future-incompat --diagnostic-width=124 --crate-type lib --emit=dep-info,metadata,link -C embed-bitcode=no -C debuginfo=2 --check-cfg 'cfg(docsrs,test)' --check-cfg 'cfg(feature, values())' -C metadata=f215ee653b74a792 -C extra-filename=-ab80402c8131bde7 --out-dir /home/owner/repos/compiler_error/lcp/target/debug/deps -C incremental=/home/owner/repos/compiler_error/lcp/target/debug/incremental -L dependency=/home/owner/repos/compiler_error/lcp/target/debug/deps --extern async_channel=/home/owner/repos/compiler_error/lcp/target/debug/deps/libasync_channel-c6554ffbd9187735.rmeta --extern codec=/home/owner/repos/compiler_error/lcp/target/debug/deps/libcodec-11c76b29dba3f814.rmeta --extern embassy_futures=/home/owner/repos/compiler_error/lcp/target/debug/deps/libembassy_futures-3dcc6f905aaa095b.rmeta --extern embassy_time=/home/owner/repos/compiler_error/lcp/target/debug/deps/libembassy_time-e7290c3990b23b5e.rmeta --extern embedded_io_async=/home/owner/repos/compiler_error/lcp/target/debug/deps/libembedded_io_async-ec87c78bb722456f.rmeta --extern heapless=/home/owner/repos/compiler_error/lcp/target/debug/deps/libheapless-59f5e7b6bdbb98f9.rmeta` (exit status: 101)
```
<details><summary><strong>Backtrace</strong></summary>
<p>
```
thread 'rustc' (37269) panicked at /rustc-dev/1a833e16546c2eb012758ddd499964fd8afee29e/compiler/rustc_type_ir/src/binder.rs:788:9:
type parameter `/#2` (/#2/2) out of range when instantiating, args=[Writer/#0, DATA_SIZE/#1]
stack backtrace:
0: __rustc::rust_begin_unwind
1: core::panicking::panic_fmt
2: <rustc_type_ir::binder::ArgFolder<rustc_middle::ty::context::TyCtxt>>::type_param_out_of_range
3: <rustc_type_ir::binder::ArgFolder<rustc_middle::ty::context::TyCtxt> as rustc_type_ir::fold::TypeFolder<rustc_middle::ty::context::TyCtxt>>::fold_ty
4: <&rustc_middle::ty::list::RawList<(), rustc_middle::ty::generic_args::GenericArg> as rustc_type_ir::fold::TypeFoldable<rustc_middle::ty::context::TyCtxt>>::fold_with::<rustc_type_ir::binder::ArgFolder<rustc_middle::ty::context::TyCtxt>>
5: <rustc_type_ir::binder::ArgFolder<rustc_middle::ty::context::TyCtxt> as rustc_type_ir::fold::TypeFolder<rustc_middle::ty::context::TyCtxt>>::fold_const
6: <rustc_type_ir::binder::ArgFolder<rustc_middle::ty::context::TyCtxt> as rustc_type_ir::fold::TypeFolder<rustc_middle::ty::context::TyCtxt>>::fold_ty
7: <&rustc_middle::ty::list::RawList<(), rustc_middle::ty::Ty> as rustc_type_ir::fold::TypeFoldable<rustc_middle::ty::context::TyCtxt>>::fold_with::<rustc_type_ir::binder::ArgFolder<rustc_middle::ty::context::TyCtxt>>
8: <rustc_type_ir::binder::EarlyBinder<rustc_middle::ty::context::TyCtxt, rustc_type_ir::binder::Binder<rustc_middle::ty::context::TyCtxt, rustc_type_ir::ty_kind::CoroutineWitnessTypes<rustc_middle::ty::context::TyCtxt>>>>::instantiate::<&rustc_middle::ty::list::RawList<(), rustc_middle::ty::generic_args::GenericArg>>
9: <rustc_trait_selection::traits::select::SelectionContext>::confirm_auto_impl_candidate::{closure#0}
10: <rustc_trait_selection::traits::select::SelectionContext>::confirm_candidate
11: <rustc_trait_selection::traits::select::SelectionContext>::evaluate_trait_predicate_recursively
12: <rustc_trait_selection::traits::select::SelectionContext>::evaluate_predicate_recursively
13: <rustc_trait_selection::traits::select::SelectionContext>::evaluate_trait_predicate_recursively
14: <rustc_trait_selection::traits::select::SelectionContext>::evaluate_predicate_recursively
15: <rustc_trait_selection::traits::select::SelectionContext>::evaluate_trait_predicate_recursively
16: <rustc_trait_selection::traits::select::SelectionContext>::evaluate_predicate_recursively
17: <rustc_trait_selection::traits::select::SelectionContext>::evaluate_trait_predicate_recursively
18: <rustc_trait_selection::traits::select::SelectionContext>::evaluate_root_obligation
19: rustc_traits::evaluate_obligation::evaluate_obligation
[... omitted 1 frame ...]
20: rustc_trait_selection::traits::type_known_to_meet_bound_modulo_regions
21: rustc_ty_utils::common_traits::is_trait_raw
[... omitted 1 frame ...]
22: <rustc_middle::ty::Ty>::is_freeze
23: <rustc_mir_transform::ssa::SsaLocals>::new
24: <rustc_mir_transform::copy_prop::CopyProp as rustc_mir_transform::pass_manager::MirPass>::run_pass
25: rustc_mir_transform::pass_manager::run_passes_inner
26: rustc_mir_transform::optimized_mir
[... omitted 1 frame ...]
27: <rustc_middle::ty::context::TyCtxt>::coroutine_layout
28: rustc_ty_utils::layout::layout_of_uncached
29: rustc_ty_utils::layout::layout_of
[... omitted 1 frame ...]
30: <rustc_middle::ty::context::TyCtxt>::par_hir_body_owners::<rustc_interface::passes::run_required_analyses::{closure#2}::{closure#0}>::{closure#0}
31: rustc_interface::passes::analysis
32: rustc_query_impl::execution::try_execute_query::<rustc_middle::query::caches::SingleCache<rustc_middle::query::erase::ErasedData<[u8; 0]>>, true>
33: rustc_interface::interface::run_compiler::<(), rustc_driver_impl::run_compiler::{closure#0}>::{closure#2}
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
error: the compiler unexpectedly panicked. This is a bug
note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md
note: please make sure that you have updated to the latest nightly
note: please attach the file at `/home/owner/repos/compiler_error/lcp/rustc-ice-2026-07-31T01_25_30-37267.txt` to your bug report
note: rustc 1.99.0-nightly (1a833e165 2026-07-29) running on x86_64-unknown-linux-gnu
note: compiler flags: --crate-type lib -C embed-bitcode=no -C debuginfo=2 -C incremental=[REDACTED]
note: some of the compiler flags provided by cargo are hidden
query stack during panic:
#0 [evaluate_obligation] evaluating trait selection obligation `{async fn body of protocol::<impl at src/protocol.rs:472:1: 473:35>::process_reset()}: core::marker::Freeze`
#1 [is_freeze_raw] computing whether `{async fn body of protocol::<impl at src/protocol.rs:472:1: 473:35>::process_reset()}` is freeze
#2 [optimized_mir] optimizing MIR for `protocol::<impl at src/protocol.rs:472:1: 473:35>::process_rx_data::{closure#0}`
#3 [layout_of] computing layout of `{async fn body of protocol::<impl at src/protocol.rs:472:1: 473:35>::process_rx_data()}`
#4 [analysis] running analysis passes on crate `lcp`
end of query stack
warning: `lcp` (lib) generated 2 warnings
error: could not compile `lcp` (lib); 2 warnings emitted
Caused by:
process didn't exit successfully: `/home/owner/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/rustc --crate-name lcp --edition=2024 src/lib.rs --error-format=json --json=diagnostic-rendered-ansi,artifacts,future-incompat --diagnostic-width=124 --crate-type lib --emit=dep-info,metadata,link -C embed-bitcode=no -C debuginfo=2 --check-cfg 'cfg(docsrs,test)' --check-cfg 'cfg(feature, values())' -C metadata=f215ee653b74a792 -C extra-filename=-ab80402c8131bde7 --out-dir /home/owner/repos/compiler_error/lcp/target/debug/deps -C incremental=/home/owner/repos/compiler_error/lcp/target/debug/incremental -L dependency=/home/owner/repos/compiler_error/lcp/target/debug/deps --extern async_channel=/home/owner/repos/compiler_error/lcp/target/debug/deps/libasync_channel-c6554ffbd9187735.rmeta --extern codec=/home/owner/repos/compiler_error/lcp/target/debug/deps/libcodec-11c76b29dba3f814.rmeta --extern embassy_futures=/home/owner/repos/compiler_error/lcp/target/debug/deps/libembassy_futures-3dcc6f905aaa095b.rmeta --extern embassy_time=/home/owner/repos/compiler_error/lcp/target/debug/deps/libembassy_time-e7290c3990b23b5e.rmeta --extern embedded_io_async=/home/owner/repos/compiler_error/lcp/target/debug/deps/libembedded_io_async-ec87c78bb722456f.rmeta --extern heapless=/home/owner/repos/compiler_error/lcp/target/debug/deps/libheapless-59f5e7b6bdbb98f9.rmeta` (exit status: 101)
[rustc-ice-2026-07-31T01_26_39-37443.txt](https://github.com/user-attachments/files/30568248/rustc-ice-2026-07-31T01_26_39-37443.txt)
```
</p>
</details>