├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── examples ├── auther │ ├── Cargo.toml │ └── src │ │ └── main.rs └── bootloader │ ├── Cargo.toml │ └── src │ └── main.rs └── rust-toolchain.toml /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "autocfg" 7 | version = "1.1.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 10 | 11 | [[package]] 12 | name = "pin-project-lite" 13 | version = "0.2.9" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 16 | 17 | [[package]] 18 | name = "proc-macro2" 19 | version = "1.0.56" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" 22 | dependencies = [ 23 | "unicode-ident", 24 | ] 25 | 26 | [[package]] 27 | name = "quote" 28 | version = "1.0.27" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" 31 | dependencies = [ 32 | "proc-macro2", 33 | ] 34 | 35 | [[package]] 36 | name = "syn" 37 | version = "2.0.16" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" 40 | dependencies = [ 41 | "proc-macro2", 42 | "quote", 43 | "unicode-ident", 44 | ] 45 | 46 | [[package]] 47 | name = "tokio" 48 | version = "1.28.1" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105" 51 | dependencies = [ 52 | "autocfg", 53 | "pin-project-lite", 54 | "tokio-macros", 55 | "windows-sys", 56 | ] 57 | 58 | [[package]] 59 | name = "tokio-macros" 60 | version = "2.1.0" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" 63 | dependencies = [ 64 | "proc-macro2", 65 | "quote", 66 | "syn", 67 | ] 68 | 69 | [[package]] 70 | name = "trait-machine" 71 | version = "0.1.0" 72 | dependencies = [ 73 | "tokio", 74 | ] 75 | 76 | [[package]] 77 | name = "trait-machine-auther" 78 | version = "0.1.0" 79 | dependencies = [ 80 | "tokio", 81 | ] 82 | 83 | [[package]] 84 | name = "unicode-ident" 85 | version = "1.0.8" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" 88 | 89 | [[package]] 90 | name = "windows-sys" 91 | version = "0.48.0" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 94 | dependencies = [ 95 | "windows-targets", 96 | ] 97 | 98 | [[package]] 99 | name = "windows-targets" 100 | version = "0.48.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" 103 | dependencies = [ 104 | "windows_aarch64_gnullvm", 105 | "windows_aarch64_msvc", 106 | "windows_i686_gnu", 107 | "windows_i686_msvc", 108 | "windows_x86_64_gnu", 109 | "windows_x86_64_gnullvm", 110 | "windows_x86_64_msvc", 111 | ] 112 | 113 | [[package]] 114 | name = "windows_aarch64_gnullvm" 115 | version = "0.48.0" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" 118 | 119 | [[package]] 120 | name = "windows_aarch64_msvc" 121 | version = "0.48.0" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" 124 | 125 | [[package]] 126 | name = "windows_i686_gnu" 127 | version = "0.48.0" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" 130 | 131 | [[package]] 132 | name = "windows_i686_msvc" 133 | version = "0.48.0" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" 136 | 137 | [[package]] 138 | name = "windows_x86_64_gnu" 139 | version = "0.48.0" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" 142 | 143 | [[package]] 144 | name = "windows_x86_64_gnullvm" 145 | version = "0.48.0" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" 148 | 149 | [[package]] 150 | name = "windows_x86_64_msvc" 151 | version = "0.48.0" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" 154 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "examples/*", 4 | ] 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Trait Machine 2 | 3 | A proof of concept of declaring a state machine that exists simultaneously on two separate devices. 4 | 5 | Basically, this seems useful when the "state machine" is phase locked across two devices: they always move in lockstep with each other, but might do different things at the transitions. 6 | 7 | I dunno if this is practically useful, but I think it is very neat. 8 | -------------------------------------------------------------------------------- /examples/auther/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "trait-machine-auther" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | tokio = { version = "1.28.1", features = ["time", "macros", "rt", "sync"] } 8 | -------------------------------------------------------------------------------- /examples/auther/src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(incomplete_features)] 2 | #![feature(async_fn_in_trait)] 3 | 4 | // State machine definition 5 | 6 | use tokio::sync::mpsc::{Sender, Receiver, channel}; 7 | use core::fmt::Debug; 8 | use std::time::Duration; 9 | 10 | #[derive(Clone, Debug)] 11 | pub struct Token(u64); 12 | #[derive(Clone, Debug)] 13 | pub struct Challenge(String); 14 | #[derive(PartialEq, Debug)] 15 | pub struct Response(usize); 16 | #[derive(Debug)] 17 | pub struct Error; 18 | 19 | #[derive(Clone, Debug)] 20 | pub enum Offer { 21 | Authenticated(Token), 22 | Challenge(Challenge), 23 | } 24 | 25 | pub trait Auther { 26 | async fn check_creds(&mut self) -> Result; 27 | async fn challenge_response(&mut self, challenge: &Challenge) -> Result; 28 | async fn abort(&mut self); 29 | } 30 | 31 | pub async fn two_factor(tm: &mut TM) -> Result { 32 | match two_factor_inner(tm).await { 33 | Ok(t) => Ok(t), 34 | Err(e) => { 35 | tm.abort().await; 36 | Err(e) 37 | } 38 | } 39 | } 40 | 41 | async fn two_factor_inner(tm: &mut TM) -> Result { 42 | let outcome = tm.check_creds().await?; 43 | match outcome { 44 | Offer::Authenticated(token) => return Ok(token), 45 | Offer::Challenge(challenge) => tm.challenge_response(&challenge).await, 46 | } 47 | } 48 | 49 | // Wire types 50 | #[derive(Debug)] 51 | pub enum Client2Host { 52 | Authenticate { username: String, password: String }, 53 | ChallengeResponse { 54 | response: Response, 55 | }, 56 | ErrorReset, 57 | } 58 | 59 | #[derive(Debug)] 60 | pub enum Host2Client { 61 | Offer(Offer), 62 | Token(Token), 63 | ErrorReset, 64 | } 65 | 66 | // Client impl 67 | 68 | pub struct Client { 69 | comms: C, 70 | username: String, 71 | password: String, 72 | } 73 | 74 | impl Auther for Client 75 | where 76 | C: TxRx, 77 | { 78 | async fn check_creds(&mut self) -> Result { 79 | self.comms 80 | .send(Client2Host::Authenticate { 81 | username: self.username.clone(), 82 | password: self.password.clone(), 83 | }) 84 | .await?; 85 | match self.comms.receive().await? { 86 | Host2Client::Offer(o) => Ok(o), 87 | _ => Err(Error) 88 | } 89 | } 90 | 91 | async fn challenge_response(&mut self, challenge: &Challenge) -> Result { 92 | let resp = challenge_responder(challenge); 93 | self.comms 94 | .send(Client2Host::ChallengeResponse { response: resp }) 95 | .await?; 96 | match self.comms.receive().await? { 97 | Host2Client::Token(t) => Ok(t), 98 | _ => Err(Error) 99 | } 100 | } 101 | 102 | async fn abort(&mut self) { 103 | let _ = self.comms.send(Client2Host::ErrorReset).await; 104 | } 105 | } 106 | 107 | // Host impl 108 | 109 | pub struct Host { 110 | comms: C, 111 | } 112 | 113 | impl Auther for Host 114 | where 115 | C: TxRx, 116 | { 117 | async fn check_creds(&mut self) -> Result { 118 | match self.comms.receive().await? { 119 | Client2Host::Authenticate { username, password } => { 120 | let offer = if username == "root" && password == "hunter2" { 121 | Offer::Authenticated(Token(5678)) 122 | } else if username == "tryme" && password == "tryme" { 123 | Offer::Challenge(Challenge("butts".to_string())) 124 | } else { 125 | return Err(Error); 126 | }; 127 | self.comms.send(Host2Client::Offer(offer.clone())).await?; 128 | Ok(offer) 129 | }, 130 | _ => Err(Error), 131 | } 132 | } 133 | 134 | async fn challenge_response(&mut self, our_challenge: &Challenge) -> Result { 135 | match self.comms.receive().await? { 136 | Client2Host::ChallengeResponse { response } if response == challenge_responder(our_challenge) => { 137 | let token = Token(1234); 138 | self.comms.send(Host2Client::Token(token.clone())).await?; 139 | Ok(token) 140 | } 141 | _ => Err(Error) 142 | } 143 | } 144 | 145 | async fn abort(&mut self) { 146 | let _ = self.comms.send(Host2Client::ErrorReset).await; 147 | } 148 | } 149 | 150 | pub trait TxRx { 151 | type Tx: 'static; 152 | type Rx: 'static; 153 | 154 | async fn send(&mut self, t: Self::Tx) -> Result<(), Error>; 155 | async fn receive(&mut self) -> Result; 156 | } 157 | 158 | // Helper channel type 159 | struct Bidir { 160 | to: Sender, 161 | from: Receiver, 162 | } 163 | 164 | impl TxRx for Bidir { 165 | type Tx = TO; 166 | type Rx = FROM; 167 | 168 | async fn send(&mut self, to: TO) -> Result<(), Error> { 169 | // println!("sending: {to:?}"); 170 | self.to.send(to).await.map_err(|_| Error) 171 | } 172 | 173 | async fn receive(&mut self) -> Result { 174 | self.from.recv().await.ok_or(Error) 175 | } 176 | } 177 | 178 | 179 | #[tokio::main(flavor = "current_thread")] 180 | pub async fn main() { 181 | let h2c = channel(4); 182 | let c2h = channel(4); 183 | 184 | let host = Host { 185 | comms: Bidir { 186 | to: h2c.0, 187 | from: c2h.1, 188 | }, 189 | }; 190 | let client = Client { 191 | username: "tryme".into(), 192 | password: "tryme".into(), 193 | comms: Bidir { 194 | to: c2h.0, 195 | from: h2c.1, 196 | }, 197 | }; 198 | 199 | let ctask = tokio::task::spawn(async move { 200 | let mut client = client; 201 | let tok = two_factor(&mut client).await.unwrap(); 202 | println!("Client Done! - Got token: {tok:?}"); 203 | tokio::time::sleep(Duration::from_millis(10)).await; 204 | client 205 | }); 206 | 207 | let htask = tokio::task::spawn(async move { 208 | let mut host = host; 209 | let tok = two_factor(&mut host).await.unwrap(); 210 | println!("Host Done! - Sent token: {tok:?}"); 211 | tokio::time::sleep(Duration::from_millis(10)).await; 212 | host 213 | }); 214 | 215 | let _client = ctask.await.unwrap(); 216 | let _host = htask.await.unwrap(); 217 | } 218 | 219 | 220 | fn challenge_responder(c: &Challenge) -> Response { 221 | // lol 222 | Response(c.0.len()) 223 | } 224 | -------------------------------------------------------------------------------- /examples/bootloader/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "trait-machine" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | tokio = { version = "1.28.1", features = ["time", "macros", "rt", "sync"] } 8 | -------------------------------------------------------------------------------- /examples/bootloader/src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(incomplete_features)] 2 | #![feature(async_fn_in_trait)] 3 | 4 | use core::fmt::Debug; 5 | use std::{any::type_name, time::Duration}; 6 | use tokio::sync::mpsc::{channel, Receiver, Sender}; 7 | 8 | // 9 | // The trait specifies all the "state transitions" of both devices 10 | // 11 | 12 | trait TraitMachine { 13 | const SECTOR_SIZE: usize; 14 | const CHUNK_SIZE: usize; 15 | 16 | fn next_sector(&mut self) -> Option; 17 | 18 | // These are all the joint state transitions, basically? 19 | async fn start(&mut self) -> Result; 20 | async fn erase_sector(&mut self, start: usize, len: usize) -> Result<(), ()>; 21 | async fn write_next_chunk(&mut self) -> Result; 22 | async fn boot(&mut self) -> Result<(), ()>; 23 | async fn abort(&mut self) -> Result<(), ()>; 24 | } 25 | 26 | // 27 | // These functions are the actual "state logic", shared by both the host and 28 | // the client. 29 | // 30 | 31 | async fn bootload(tm: &mut TM) -> Result<(), ()> { 32 | // Silly top level function just so we can guarantee an abort message is sent 33 | // whenever there is an inner error. 34 | match bootload_inner(tm).await { 35 | Ok(()) => Ok(()), 36 | Err(()) => { 37 | let _ = tm.abort().await?; 38 | Err(()) 39 | } 40 | } 41 | } 42 | 43 | async fn bootload_inner(tm: &mut TM) -> Result<(), ()> { 44 | let name = type_name::(); 45 | println!("{name} STARTING"); 46 | let image_end = tm.start().await?; 47 | 48 | while let Some(start) = tm.next_sector() { 49 | println!("{name} ERASING"); 50 | tm.erase_sector(start, TM::SECTOR_SIZE).await?; 51 | let sector_end = start + TM::SECTOR_SIZE; 52 | let mut now = start; 53 | while (now < sector_end) && (now < image_end) { 54 | println!("{name} WRITING"); 55 | now += tm.write_next_chunk().await?; 56 | } 57 | } 58 | 59 | println!("{name} BOOTING"); 60 | tm.boot().await?; 61 | 62 | Ok(()) 63 | } 64 | 65 | // 66 | // These are the "wire types" for H->C and C->H comms 67 | // 68 | 69 | #[derive(Debug)] 70 | enum Host2Client { 71 | Start { total_size: usize }, 72 | EraseSector { addr: usize, len: usize }, 73 | WriteData { addr: usize, data: Vec }, 74 | Boot, 75 | Abort, 76 | } 77 | 78 | #[derive(Debug)] 79 | enum Client2Host { 80 | ErrorReset, 81 | Starting, 82 | ChunkWritten, 83 | SectorErased, 84 | Booting, 85 | } 86 | 87 | // 88 | // This is the host - it is driving the state machine. 89 | // 90 | // This is a vaguely RPC-like construct. 91 | // 92 | 93 | struct Host { 94 | image: Vec, 95 | channel: Bidir, 96 | position: usize, 97 | } 98 | 99 | impl TraitMachine for Host { 100 | const SECTOR_SIZE: usize = 4096; 101 | const CHUNK_SIZE: usize = 256; 102 | 103 | fn next_sector(&mut self) -> Option { 104 | if self.position < self.image.len() { 105 | let val = self.position; 106 | Some(val) 107 | } else { 108 | None 109 | } 110 | } 111 | 112 | async fn start(&mut self) -> Result { 113 | self.channel 114 | .send(Host2Client::Start { 115 | total_size: self.image.len(), 116 | }) 117 | .await?; 118 | match self.channel.recv().await? { 119 | Client2Host::Starting => Ok(self.image.len()), 120 | _ => Err(()), 121 | } 122 | } 123 | 124 | async fn erase_sector(&mut self, start: usize, len: usize) -> Result<(), ()> { 125 | assert_eq!(len, Self::SECTOR_SIZE); 126 | self.channel 127 | .send(Host2Client::EraseSector { addr: start, len }) 128 | .await?; 129 | match self.channel.recv().await? { 130 | Client2Host::SectorErased => Ok(()), 131 | _ => Err(()), 132 | } 133 | } 134 | 135 | async fn write_next_chunk(&mut self) -> Result { 136 | if self.image.len() <= self.position { 137 | self.position += Self::CHUNK_SIZE; 138 | Ok(Self::CHUNK_SIZE) 139 | } else { 140 | let remain = &self.image[self.position..][..Self::CHUNK_SIZE]; 141 | let data = remain.iter().copied().collect(); 142 | self.channel 143 | .send(Host2Client::WriteData { 144 | addr: self.position, 145 | data, 146 | }) 147 | .await?; 148 | self.position += Self::CHUNK_SIZE; 149 | match self.channel.recv().await? { 150 | Client2Host::ChunkWritten => Ok(Self::CHUNK_SIZE), 151 | _ => Err(()), 152 | } 153 | } 154 | } 155 | 156 | async fn boot(&mut self) -> Result<(), ()> { 157 | self.channel.send(Host2Client::Boot).await?; 158 | match self.channel.recv().await? { 159 | Client2Host::Booting => Ok(()), 160 | _ => Err(()), 161 | } 162 | } 163 | 164 | async fn abort(&mut self) -> Result<(), ()> { 165 | self.channel.send(Host2Client::Abort).await?; 166 | Ok(()) 167 | } 168 | } 169 | 170 | // 171 | // This is the client. It is being commanded by the host 172 | // 173 | 174 | struct Client { 175 | position: usize, 176 | image_len: Option, 177 | flash: Vec, 178 | channel: Bidir, 179 | } 180 | 181 | impl TraitMachine for Client { 182 | const SECTOR_SIZE: usize = 4096; 183 | const CHUNK_SIZE: usize = 256; 184 | 185 | fn next_sector(&mut self) -> Option { 186 | let in_ttl_range = self.position < Self::TOTAL_SIZE; 187 | let in_img_range = self.image_len.map(|len| self.position < len)?; 188 | 189 | if in_ttl_range && in_img_range { 190 | let val = self.position; 191 | Some(val) 192 | } else { 193 | None 194 | } 195 | } 196 | 197 | async fn start(&mut self) -> Result { 198 | match self.channel.recv().await? { 199 | Host2Client::Start { total_size } if total_size <= Self::TOTAL_SIZE => { 200 | self.image_len = Some(total_size); 201 | self.channel.send(Client2Host::Starting).await?; 202 | Ok(total_size) 203 | } 204 | _ => Err(()), 205 | } 206 | } 207 | 208 | async fn erase_sector(&mut self, sector_start: usize, sector_len: usize) -> Result<(), ()> { 209 | match self.channel.recv().await? { 210 | Host2Client::EraseSector { addr, len } => { 211 | let exp_pos = addr == sector_start; 212 | let exp_len = len == sector_len; 213 | if !(exp_pos && exp_len) { 214 | return Err(()); 215 | } 216 | } 217 | _ => return Err(()), 218 | } 219 | 220 | self.sector_erase(sector_start, sector_len).await?; 221 | self.channel.send(Client2Host::SectorErased).await?; 222 | Ok(()) 223 | } 224 | 225 | async fn write_next_chunk(&mut self) -> Result { 226 | match self.channel.recv().await? { 227 | Host2Client::WriteData { addr, data } => { 228 | if addr != self.position { 229 | return Err(()); 230 | } 231 | if data.len() != Self::CHUNK_SIZE { 232 | return Err(()); 233 | } 234 | self.chunk_write(addr, &data).await?; 235 | self.position += Self::CHUNK_SIZE; 236 | self.channel.send(Client2Host::ChunkWritten).await?; 237 | Ok(Self::CHUNK_SIZE) 238 | } 239 | _ => return Err(()), 240 | } 241 | } 242 | 243 | async fn boot(&mut self) -> Result<(), ()> { 244 | match self.channel.recv().await? { 245 | Host2Client::Boot => {} 246 | _ => return Err(()), 247 | } 248 | self.channel.send(Client2Host::Booting).await?; 249 | println!("Client Booted :)"); 250 | Ok(()) 251 | } 252 | 253 | async fn abort(&mut self) -> Result<(), ()> { 254 | let _ = self.channel.send(Client2Host::ErrorReset).await; 255 | Ok(()) 256 | } 257 | } 258 | 259 | // 260 | // These are fake "inherent methods" that would normally exist, like erasing/writing 261 | // the flash memory locally. 262 | // 263 | // Not part of the state machine directly, but are the "side effects" of state transitions 264 | // that are useful. 265 | impl Client { 266 | const TOTAL_SIZE: usize = 32 * 1024; 267 | 268 | async fn sector_erase(&mut self, start: usize, len: usize) -> Result<(), ()> { 269 | println!("Totally erasing real flash..."); 270 | self.flash 271 | .get_mut(start..(start + len)) 272 | .ok_or(())? 273 | .iter_mut() 274 | .for_each(|b| *b = 0xFF); 275 | Ok(()) 276 | } 277 | 278 | async fn chunk_write(&mut self, start: usize, data: &[u8]) -> Result<(), ()> { 279 | let len = data.len(); 280 | self.flash 281 | .get_mut(start..(start + len)) 282 | .ok_or(())? 283 | .iter_mut() 284 | .zip(data.iter()) 285 | .for_each(|(b, d)| { 286 | assert_eq!(*b, 0xFF, "WRITING NON ERASED FLASH"); 287 | *b = *d; 288 | }); 289 | Ok(()) 290 | } 291 | } 292 | 293 | // 294 | // Demonstration function 295 | // 296 | 297 | #[tokio::main(flavor = "current_thread")] 298 | pub async fn main() { 299 | let image = vec![0x42; 15 * 1024]; 300 | let flash = vec![0x00; Client::TOTAL_SIZE]; 301 | 302 | let h2c = channel(4); 303 | let c2h = channel(4); 304 | 305 | let host = Host { 306 | image, 307 | channel: Bidir { 308 | to: h2c.0, 309 | from: c2h.1, 310 | }, 311 | position: 0, 312 | }; 313 | let client = Client { 314 | flash, 315 | channel: Bidir { 316 | to: c2h.0, 317 | from: h2c.1, 318 | }, 319 | position: 0, 320 | image_len: None, 321 | }; 322 | 323 | let ctask = tokio::task::spawn(async move { 324 | let mut client = client; 325 | bootload(&mut client).await.unwrap(); 326 | println!("Client Done!"); 327 | tokio::time::sleep(Duration::from_millis(10)).await; 328 | client 329 | }); 330 | 331 | let htask = tokio::task::spawn(async move { 332 | let mut host = host; 333 | bootload(&mut host).await.unwrap(); 334 | println!("Host Done!"); 335 | tokio::time::sleep(Duration::from_millis(10)).await; 336 | host 337 | }); 338 | 339 | let client = ctask.await.unwrap(); 340 | let host = htask.await.unwrap(); 341 | 342 | assert_eq!(&host.image, &client.flash[..host.image.len()]); 343 | println!("Image check passed :)"); 344 | } 345 | 346 | // Helper channel type 347 | struct Bidir { 348 | to: Sender, 349 | from: Receiver, 350 | } 351 | 352 | impl Bidir { 353 | async fn send(&mut self, to: TO) -> Result<(), ()> { 354 | // println!("sending: {to:?}"); 355 | self.to.send(to).await.map_err(drop) 356 | } 357 | 358 | async fn recv(&mut self) -> Result { 359 | self.from.recv().await.ok_or(()) 360 | } 361 | } 362 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" 3 | --------------------------------------------------------------------------------