Compare commits
	
		
			No commits in common. "4396e6a43d5075dc774ab52ea0cd6f5379cffce1" and "e6974cffb2476cc97f691ae850831833f5c140a3" have entirely different histories. 
		
	
	
		
			4396e6a43d
			...
			e6974cffb2
		
	
		| 
						 | 
					@ -5,7 +5,7 @@ version = 3
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "arduino-hal"
 | 
					name = "arduino-hal"
 | 
				
			||||||
version = "0.1.0"
 | 
					version = "0.1.0"
 | 
				
			||||||
source = "git+https://github.com/rahix/avr-hal#e29a7be5b1fa9d490ded11e539d345af041ccef6"
 | 
					source = "git+https://github.com/rahix/avr-hal#fb609ab0d14c5a0a44e2dff3e5e514cb612a529a"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "atmega-hal",
 | 
					 "atmega-hal",
 | 
				
			||||||
 "avr-device",
 | 
					 "avr-device",
 | 
				
			||||||
| 
						 | 
					@ -19,7 +19,7 @@ dependencies = [
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "atmega-hal"
 | 
					name = "atmega-hal"
 | 
				
			||||||
version = "0.1.0"
 | 
					version = "0.1.0"
 | 
				
			||||||
source = "git+https://github.com/rahix/avr-hal#e29a7be5b1fa9d490ded11e539d345af041ccef6"
 | 
					source = "git+https://github.com/rahix/avr-hal#fb609ab0d14c5a0a44e2dff3e5e514cb612a529a"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "avr-device",
 | 
					 "avr-device",
 | 
				
			||||||
 "avr-hal-generic",
 | 
					 "avr-hal-generic",
 | 
				
			||||||
| 
						 | 
					@ -33,9 +33,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "avr-device"
 | 
					name = "avr-device"
 | 
				
			||||||
version = "0.5.1"
 | 
					version = "0.5.0"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "644690f9699830f0efce38ca9d41c7ad51a6ec1829364dd272638938c953673a"
 | 
					checksum = "deb45711b7227cb3f799c40c4e0e8beea1765acff8d06a14bb3479c1a549d8b0"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "avr-device-macros",
 | 
					 "avr-device-macros",
 | 
				
			||||||
 "bare-metal",
 | 
					 "bare-metal",
 | 
				
			||||||
| 
						 | 
					@ -45,9 +45,9 @@ dependencies = [
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "avr-device-macros"
 | 
					name = "avr-device-macros"
 | 
				
			||||||
version = "0.5.1"
 | 
					version = "0.5.0"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "ba8b49941a5148cec432d5180d205871f0132f9f15b7225a9f3ad9e2bae026cb"
 | 
					checksum = "87c428a2384981d8460668c48c766ff18e7e91d2d467f7b103151984ee952d38"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "proc-macro2",
 | 
					 "proc-macro2",
 | 
				
			||||||
 "quote",
 | 
					 "quote",
 | 
				
			||||||
| 
						 | 
					@ -57,7 +57,7 @@ dependencies = [
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "avr-hal-generic"
 | 
					name = "avr-hal-generic"
 | 
				
			||||||
version = "0.1.0"
 | 
					version = "0.1.0"
 | 
				
			||||||
source = "git+https://github.com/rahix/avr-hal#e29a7be5b1fa9d490ded11e539d345af041ccef6"
 | 
					source = "git+https://github.com/rahix/avr-hal#fb609ab0d14c5a0a44e2dff3e5e514cb612a529a"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "avr-device",
 | 
					 "avr-device",
 | 
				
			||||||
 "cfg-if 0.1.10",
 | 
					 "cfg-if 0.1.10",
 | 
				
			||||||
| 
						 | 
					@ -76,6 +76,12 @@ version = "1.0.0"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603"
 | 
					checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "binascii"
 | 
				
			||||||
 | 
					version = "0.1.4"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "cfg-if"
 | 
					name = "cfg-if"
 | 
				
			||||||
version = "0.1.10"
 | 
					version = "0.1.10"
 | 
				
			||||||
| 
						 | 
					@ -101,7 +107,8 @@ dependencies = [
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "ds323x"
 | 
					name = "ds323x"
 | 
				
			||||||
version = "0.5.0"
 | 
					version = "0.5.0"
 | 
				
			||||||
source = "git+https://github.com/kirbylife/ds323x-rs?branch=fix-set-day-format#e204b77494f921cfc276c9a66e6e9ba95dc4e142"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "057e63d03a3beb83d3c9b7a19b799c60e203b0c53f1bbec6a715804907f73a2b"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "embedded-hal",
 | 
					 "embedded-hal",
 | 
				
			||||||
 "rtcc",
 | 
					 "rtcc",
 | 
				
			||||||
| 
						 | 
					@ -123,11 +130,6 @@ version = "0.2.0"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "723dce4e9f25b6e6c5f35628e144794e5b459216ed7da97b7c4b66cdb3fa82ca"
 | 
					checksum = "723dce4e9f25b6e6c5f35628e144794e5b459216ed7da97b7c4b66cdb3fa82ca"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					 | 
				
			||||||
name = "hmac-sha1-compact"
 | 
					 | 
				
			||||||
version = "1.1.3"
 | 
					 | 
				
			||||||
source = "git+https://github.com/kirbylife/rust-hmac-sha1#a3c4116837efb8a850fb0d03985831ce4dc2bc12"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "nb"
 | 
					name = "nb"
 | 
				
			||||||
version = "0.1.3"
 | 
					version = "0.1.3"
 | 
				
			||||||
| 
						 | 
					@ -182,9 +184,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "proc-macro2"
 | 
					name = "proc-macro2"
 | 
				
			||||||
version = "1.0.56"
 | 
					version = "1.0.54"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
 | 
					checksum = "e472a104799c74b514a57226160104aa483546de37e839ec50e3c2e41dd87534"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "unicode-ident",
 | 
					 "unicode-ident",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
| 
						 | 
					@ -198,12 +200,6 @@ dependencies = [
 | 
				
			||||||
 "proc-macro2",
 | 
					 "proc-macro2",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					 | 
				
			||||||
name = "randomize"
 | 
					 | 
				
			||||||
version = "3.0.1"
 | 
					 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					 | 
				
			||||||
checksum = "88c38c99b51f33c9fcc655252bf02ac8048eb70f35244e4697b0de9c473e940a"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "rtcc"
 | 
					name = "rtcc"
 | 
				
			||||||
version = "0.3.0"
 | 
					version = "0.3.0"
 | 
				
			||||||
| 
						 | 
					@ -224,15 +220,19 @@ name = "rustytoken"
 | 
				
			||||||
version = "0.1.0"
 | 
					version = "0.1.0"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "arduino-hal",
 | 
					 "arduino-hal",
 | 
				
			||||||
 | 
					 "binascii",
 | 
				
			||||||
 "ds323x",
 | 
					 "ds323x",
 | 
				
			||||||
 "embedded-hal",
 | 
					 | 
				
			||||||
 "hmac-sha1-compact",
 | 
					 | 
				
			||||||
 "nb 1.1.0",
 | 
					 | 
				
			||||||
 "panic-halt",
 | 
					 "panic-halt",
 | 
				
			||||||
 "randomize",
 | 
					 "sha1_smol",
 | 
				
			||||||
 "ufmt 0.2.0",
 | 
					 "ufmt 0.2.0",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "sha1_smol"
 | 
				
			||||||
 | 
					version = "1.0.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "syn"
 | 
					name = "syn"
 | 
				
			||||||
version = "1.0.109"
 | 
					version = "1.0.109"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										11
									
								
								Cargo.toml
								
								
								
								
							
							
						
						
									
										11
									
								
								Cargo.toml
								
								
								
								
							| 
						 | 
					@ -5,17 +5,16 @@ edition = "2021"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[dependencies]
 | 
					[dependencies]
 | 
				
			||||||
arduino-hal = {  git = "https://github.com/rahix/avr-hal", features = ["arduino-uno"] }
 | 
					arduino-hal = {  git = "https://github.com/rahix/avr-hal", features = ["arduino-uno"] }
 | 
				
			||||||
ds323x = { git = "https://github.com/kirbylife/ds323x-rs", branch = "fix-set-day-format" }
 | 
					 | 
				
			||||||
hmac-sha1-compact = { git = "https://github.com/kirbylife/rust-hmac-sha1" }
 | 
					 | 
				
			||||||
panic-halt = "0.2.0"
 | 
					panic-halt = "0.2.0"
 | 
				
			||||||
embedded-hal = "0.2.7"
 | 
					sha1_smol = "1.0.0"
 | 
				
			||||||
nb = "1.1.0"
 | 
					binascii = { version = "0.1", default-features = false, features = ["decode"] }
 | 
				
			||||||
randomize = "3.0.1"
 | 
					ds323x = "0.5.0"
 | 
				
			||||||
ufmt = "0.2.0"
 | 
					ufmt = "0.2.0"
 | 
				
			||||||
 | 
					embedded-hal = "0.2.7"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[profile.release]
 | 
					[profile.release]
 | 
				
			||||||
lto = true
 | 
					lto = true
 | 
				
			||||||
panic = "abort"
 | 
					panic = "abort"
 | 
				
			||||||
strip = true
 | 
					strip = true
 | 
				
			||||||
opt-level = "s"
 | 
					opt-level = "z"
 | 
				
			||||||
codegen-units = 1
 | 
					codegen-units = 1
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,63 +0,0 @@
 | 
				
			||||||
use embedded_hal::digital::v2::InputPin;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub struct Button<'a, P> {
 | 
					 | 
				
			||||||
    pin: &'a P,
 | 
					 | 
				
			||||||
    state: bool,
 | 
					 | 
				
			||||||
    is_pullup: bool,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(PartialEq)]
 | 
					 | 
				
			||||||
pub enum Event {
 | 
					 | 
				
			||||||
    PressDown,
 | 
					 | 
				
			||||||
    PressUp,
 | 
					 | 
				
			||||||
    Pressed,
 | 
					 | 
				
			||||||
    None,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl<'a, P: InputPin> Button<'a, P> {
 | 
					 | 
				
			||||||
    pub fn new(pin: &'a P, is_pullup: bool) -> Self {
 | 
					 | 
				
			||||||
        let mut state = pin.is_high().map_err(|_| ()).unwrap();
 | 
					 | 
				
			||||||
        if is_pullup {
 | 
					 | 
				
			||||||
            state = !state;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Button {
 | 
					 | 
				
			||||||
            pin,
 | 
					 | 
				
			||||||
            state,
 | 
					 | 
				
			||||||
            is_pullup,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn read_pin(&self) -> bool {
 | 
					 | 
				
			||||||
        let mut inc = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for _ in 0..100 {
 | 
					 | 
				
			||||||
            let mut state = self.pin.is_high().map_err(|_| ()).unwrap();
 | 
					 | 
				
			||||||
            if self.is_pullup {
 | 
					 | 
				
			||||||
                state = !state;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if state {
 | 
					 | 
				
			||||||
                inc += 1;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        inc > 70
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn update(&mut self) -> Event {
 | 
					 | 
				
			||||||
        let actual_state = self.read_pin();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        match (self.state, actual_state) {
 | 
					 | 
				
			||||||
            (false, false) => Event::None,
 | 
					 | 
				
			||||||
            (true, false) => {
 | 
					 | 
				
			||||||
                self.state = !self.state;
 | 
					 | 
				
			||||||
                Event::PressUp
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            (false, true) => {
 | 
					 | 
				
			||||||
                self.state = !self.state;
 | 
					 | 
				
			||||||
                Event::PressDown
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            (true, true) => Event::Pressed,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,16 +1,40 @@
 | 
				
			||||||
use hmac_sha1_compact::HMAC;
 | 
					use sha1_smol::Sha1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const INTERVAL: u64 = 30;
 | 
					const BLOCK_SIZE: usize = 64;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn generate_otp_token(private_key: &[u8], actual_time: u64) -> u32 {
 | 
					pub fn hmac_sha1(key: &[u8], message: &[u8], output: &mut [u8]) {
 | 
				
			||||||
    let interval = actual_time / INTERVAL;
 | 
					    // Preprocess the key.
 | 
				
			||||||
    let msg = interval.to_be_bytes();
 | 
					    let mut key_padded = [0u8; BLOCK_SIZE];
 | 
				
			||||||
 | 
					    if key.len() > BLOCK_SIZE {
 | 
				
			||||||
 | 
					        let mut hash = Sha1::new();
 | 
				
			||||||
 | 
					        hash.update(key);
 | 
				
			||||||
 | 
					        let key_digest = hash.digest().bytes();
 | 
				
			||||||
 | 
					        key_padded[..20].copy_from_slice(&key_digest);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        key_padded[..key.len()].copy_from_slice(key);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    for byte in &mut key_padded {
 | 
				
			||||||
 | 
					        *byte ^= 0x36;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let hmac_digest = HMAC::mac(&msg, private_key);
 | 
					    // Hash the inner padded key and the message.
 | 
				
			||||||
 | 
					    let mut hash = Sha1::new();
 | 
				
			||||||
 | 
					    hash.update(&key_padded[..]);
 | 
				
			||||||
 | 
					    hash.update(message);
 | 
				
			||||||
 | 
					    let mut inner_hash = [0u8; 20];
 | 
				
			||||||
 | 
					    inner_hash.copy_from_slice(&hash.digest().bytes());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let start = (hmac_digest[19] & 0xF) as usize;
 | 
					    // Preprocess the key again.
 | 
				
			||||||
    let bytes: [u8; 4] = hmac_digest[start..start + 4].try_into().unwrap();
 | 
					    for byte in &mut key_padded {
 | 
				
			||||||
 | 
					        *byte ^= 0x36 ^ 0x5c;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let raw_token = u32::from_be_bytes(bytes);
 | 
					    // Hash the outer padded key and the inner hash.
 | 
				
			||||||
    ((raw_token & 0x7F_FF_FF_FF) % 1_000_000) as u32
 | 
					    let mut hash = Sha1::new();
 | 
				
			||||||
 | 
					    hash.update(&key_padded[..]);
 | 
				
			||||||
 | 
					    hash.update(&inner_hash[..]);
 | 
				
			||||||
 | 
					    let hmac_digest = hash.digest().bytes();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Copy the result into the output buffer.
 | 
				
			||||||
 | 
					    output.copy_from_slice(&hmac_digest);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,9 +11,17 @@ pub struct Datetime {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Datetime {
 | 
					impl Datetime {
 | 
				
			||||||
    pub fn unix_epoch(&self) -> u64 {
 | 
					    pub fn unix_epoch(&self) -> u64 {
 | 
				
			||||||
        let year: i64 = self.year as i64 - 1900;
 | 
					        let mut year: i64 = self.year as i64 - 1900;
 | 
				
			||||||
        let month: i64 = self.month as i64 - 1;
 | 
					        let mut month: i64 = self.month as i64 - 1;
 | 
				
			||||||
 | 
					        if month >= 12 || month < 0 {
 | 
				
			||||||
 | 
					            let mut adj = month / 12;
 | 
				
			||||||
 | 
					            month %= 12;
 | 
				
			||||||
 | 
					            if month < 0 {
 | 
				
			||||||
 | 
					                adj -= 1;
 | 
				
			||||||
 | 
					                month += 12;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            year += adj as i64;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        let (mut t, is_leap) = year_to_secs(year);
 | 
					        let (mut t, is_leap) = year_to_secs(year);
 | 
				
			||||||
        t += month_to_secs(month, is_leap);
 | 
					        t += month_to_secs(month, is_leap);
 | 
				
			||||||
        t += 86400 * (self.day as i64 - 1);
 | 
					        t += 86400 * (self.day as i64 - 1);
 | 
				
			||||||
| 
						 | 
					@ -27,21 +35,21 @@ impl Datetime {
 | 
				
			||||||
        let year = rtc.year().map_err(|_| ()).unwrap();
 | 
					        let year = rtc.year().map_err(|_| ()).unwrap();
 | 
				
			||||||
        let month = rtc.month().map_err(|_| ()).unwrap();
 | 
					        let month = rtc.month().map_err(|_| ()).unwrap();
 | 
				
			||||||
        let day = rtc.day().map_err(|_| ()).unwrap();
 | 
					        let day = rtc.day().map_err(|_| ()).unwrap();
 | 
				
			||||||
        let hours = match rtc.hours().map_err(|_| ()).unwrap() {
 | 
					        let hour = match rtc.hours().map_err(|_| ()).unwrap() {
 | 
				
			||||||
            ds323x::Hours::AM(val) => val,
 | 
					            ds323x::Hours::AM(val) => val,
 | 
				
			||||||
            ds323x::Hours::PM(val) => val + 12,
 | 
					            ds323x::Hours::PM(val) => val + 12,
 | 
				
			||||||
            ds323x::Hours::H24(val) => val,
 | 
					            ds323x::Hours::H24(val) => val,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        let minutes = rtc.minutes().map_err(|_| ()).unwrap();
 | 
					        let minute = rtc.minutes().map_err(|_| ()).unwrap();
 | 
				
			||||||
        let seconds = rtc.seconds().map_err(|_| ()).unwrap();
 | 
					        let second = rtc.seconds().map_err(|_| ()).unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Datetime {
 | 
					        Datetime {
 | 
				
			||||||
            year,
 | 
					            year: year,
 | 
				
			||||||
            month,
 | 
					            month: month,
 | 
				
			||||||
            day,
 | 
					            day: day,
 | 
				
			||||||
            hours,
 | 
					            hours: hour,
 | 
				
			||||||
            minutes,
 | 
					            minutes: minute,
 | 
				
			||||||
            seconds,
 | 
					            seconds: second,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -50,6 +58,17 @@ fn year_to_secs(year: i64) -> (i64, bool) {
 | 
				
			||||||
    let is_leap: bool;
 | 
					    let is_leap: bool;
 | 
				
			||||||
    let res: i64;
 | 
					    let res: i64;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if year - 2 <= 136 {
 | 
				
			||||||
 | 
					        let y = year as i32;
 | 
				
			||||||
 | 
					        let mut leaps = (y - 68) >> 2;
 | 
				
			||||||
 | 
					        if !((y - 68) & 3 != 0) {
 | 
				
			||||||
 | 
					            leaps -= 1;
 | 
				
			||||||
 | 
					            is_leap = true;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            is_leap = false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        res = 31536000 * (y - 70) as i64 + 86400 * leaps as i64;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
        let mut cycles: i64;
 | 
					        let mut cycles: i64;
 | 
				
			||||||
        let centuries: i64;
 | 
					        let centuries: i64;
 | 
				
			||||||
        let mut leaps: i64;
 | 
					        let mut leaps: i64;
 | 
				
			||||||
| 
						 | 
					@ -95,6 +114,7 @@ fn year_to_secs(year: i64) -> (i64, bool) {
 | 
				
			||||||
        leaps += 97 * cycles + 24 * centuries - (is_leap as i64);
 | 
					        leaps += 97 * cycles + 24 * centuries - (is_leap as i64);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        res = (year - 100) * 31536000 + leaps * 86400 + 946684800 + 86400;
 | 
					        res = (year - 100) * 31536000 + leaps * 86400 + 946684800 + 86400;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    (res, is_leap)
 | 
					    (res, is_leap)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										228
									
								
								src/main.rs
								
								
								
								
							
							
						
						
									
										228
									
								
								src/main.rs
								
								
								
								
							| 
						 | 
					@ -1,34 +1,60 @@
 | 
				
			||||||
#![no_std]
 | 
					#![no_std]
 | 
				
			||||||
#![no_main]
 | 
					#![no_main]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mod button;
 | 
					 | 
				
			||||||
mod crypto;
 | 
					mod crypto;
 | 
				
			||||||
mod datetime;
 | 
					mod datetime;
 | 
				
			||||||
mod screen;
 | 
					 | 
				
			||||||
mod storage;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
use arduino_hal::default_serial;
 | 
					use arduino_hal::default_serial;
 | 
				
			||||||
use arduino_hal::delay_ms;
 | 
					use arduino_hal::delay_ms;
 | 
				
			||||||
// use arduino_hal::delay_ms;
 | 
					use crypto::hmac_sha1;
 | 
				
			||||||
use arduino_hal::prelude::*;
 | 
					 | 
				
			||||||
use crypto::generate_otp_token;
 | 
					 | 
				
			||||||
use crypto::INTERVAL;
 | 
					 | 
				
			||||||
use ds323x::Rtcc;
 | 
					 | 
				
			||||||
use nb::block;
 | 
					 | 
				
			||||||
use panic_halt as _;
 | 
					 | 
				
			||||||
use storage::SECRET_KEY_MAX_LEN;
 | 
					 | 
				
			||||||
use storage::SECRET_KEY_NAME_LEN;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ENDL: u8 = 0;
 | 
					use panic_halt as _;
 | 
				
			||||||
const OK: u8 = 1;
 | 
					
 | 
				
			||||||
const ERROR: u8 = 2;
 | 
					const INTERVAL: u64 = 30;
 | 
				
			||||||
const HANDSHAKE: u8 = 3;
 | 
					const SECRET_KEY_MAX_LEN: usize = 32;
 | 
				
			||||||
const SET_TIMESTAMP: u8 = 10;
 | 
					
 | 
				
			||||||
const ADD_TOKEN: u8 = 20;
 | 
					fn process_secret<'a>(secret: &str, output: &mut [u8]) -> usize {
 | 
				
			||||||
const DELETE_TOKEN: u8 = 30;
 | 
					    // RFC 6238 Doesn't specify a max length for the secret keys, So I chose 32
 | 
				
			||||||
const GET_TOKENS: u8 = 40;
 | 
					    // You can change the max length by modifying the const
 | 
				
			||||||
const WIPE_TOKENS: u8 = 50;
 | 
					    let mut result = [0u8; SECRET_KEY_MAX_LEN];
 | 
				
			||||||
const EXIT: u8 = 254;
 | 
					    let mut inc = 0;
 | 
				
			||||||
 | 
					    let offset = match 8 - (secret.len() % 8) {
 | 
				
			||||||
 | 
					        8 => 0,
 | 
				
			||||||
 | 
					        n => n,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for c in secret.chars() {
 | 
				
			||||||
 | 
					        result[inc] = c.to_ascii_uppercase() as u8;
 | 
				
			||||||
 | 
					        inc += 1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    for _ in 0..offset {
 | 
				
			||||||
 | 
					        result[inc] = '=' as u8;
 | 
				
			||||||
 | 
					        inc += 1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let decoded = match binascii::b32decode(&result[..inc], output).ok() {
 | 
				
			||||||
 | 
					        Some(bytes) => bytes,
 | 
				
			||||||
 | 
					        None => "".as_bytes(), // TODO: Implement a way to display errors
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    decoded.len()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn generate_otp_token(private_key: &str, actual_time: u64) -> u32 {
 | 
				
			||||||
 | 
					    let mut output = [0u8; 32];
 | 
				
			||||||
 | 
					    let private_key_len = process_secret(private_key, &mut output);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let interval = actual_time / INTERVAL;
 | 
				
			||||||
 | 
					    let msg = interval.to_be_bytes();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut hmac_digest = [0u8; 20];
 | 
				
			||||||
 | 
					    hmac_sha1(&output[..private_key_len], &msg, &mut hmac_digest);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let start = (hmac_digest[19] & 0xF) as usize;
 | 
				
			||||||
 | 
					    let bytes: [u8; 4] = hmac_digest[start..start + 4].try_into().unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let raw_token = u32::from_be_bytes(bytes);
 | 
				
			||||||
 | 
					    ((raw_token & 0x7F_FF_FF_FF) % 1_000_000) as u32
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[arduino_hal::entry]
 | 
					#[arduino_hal::entry]
 | 
				
			||||||
fn main() -> ! {
 | 
					fn main() -> ! {
 | 
				
			||||||
| 
						 | 
					@ -46,159 +72,21 @@ fn main() -> ! {
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    let mut rtc = ds323x::Ds323x::new_ds3231(i2c_clock);
 | 
					    let mut rtc = ds323x::Ds323x::new_ds3231(i2c_clock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut rs = pins.d12.into_output();
 | 
					 | 
				
			||||||
    let mut en = pins.d11.into_output();
 | 
					 | 
				
			||||||
    let mut d4 = pins.d5.into_output();
 | 
					 | 
				
			||||||
    let mut d5 = pins.d4.into_output();
 | 
					 | 
				
			||||||
    let mut d6 = pins.d3.into_output();
 | 
					 | 
				
			||||||
    let mut d7 = pins.d2.into_output();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut display = screen::StnScreen::new(&mut rs, &mut en, &mut d4, &mut d5, &mut d6, &mut d7);
 | 
					 | 
				
			||||||
    display.clear();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let datetime = datetime::Datetime::from_ds3231(&mut rtc);
 | 
					    let datetime = datetime::Datetime::from_ds3231(&mut rtc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut eeprom = arduino_hal::Eeprom::new(dp.EEPROM);
 | 
					    let timestamp = datetime.unix_epoch();
 | 
				
			||||||
    let mut tokens = storage::Tokens::new(&mut eeprom, datetime.unix_epoch());
 | 
					    ufmt::uwriteln!(&mut serial, "Timestamp: {}", timestamp).unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let up = pins.d6.into_pull_up_input();
 | 
					    let token = generate_otp_token("FS7J22EHLLSOGKUVJKV2XE7FTIX24JAJ", 1680155315);
 | 
				
			||||||
    let mut button = button::Button::new(&up, true);
 | 
					    ufmt::uwriteln!(&mut serial, "token: {}", token).unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    button.update();
 | 
					    delay_ms(5000);
 | 
				
			||||||
    if button.update() == button::Event::Pressed {
 | 
					    let token = generate_otp_token("FS7J22EHLLSOGKUVJKV2XE7FTIX24JAJ", 1680155315);
 | 
				
			||||||
        display.write_str("Connected to");
 | 
					    ufmt::uwriteln!(&mut serial, "token: {}", token).unwrap();
 | 
				
			||||||
        display.set_cursor(0, 1);
 | 
					 | 
				
			||||||
        display.write_str("USB...");
 | 
					 | 
				
			||||||
        loop {
 | 
					 | 
				
			||||||
            // Waiting from a command since the tool
 | 
					 | 
				
			||||||
            let cmd = block!(serial.read()).unwrap_or(u8::MAX);
 | 
					 | 
				
			||||||
            display.set_cursor(8, 1);
 | 
					 | 
				
			||||||
            display.write_str("---");
 | 
					 | 
				
			||||||
            display.set_cursor(8, 1);
 | 
					 | 
				
			||||||
            display.write_u32(cmd as u32);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            match cmd {
 | 
					    delay_ms(5000);
 | 
				
			||||||
                EXIT => {}
 | 
					    let token = generate_otp_token("FS7J22EHLLSOGKUVJKV2XE7FTIX24JAJ", 1680155315);
 | 
				
			||||||
                HANDSHAKE => serial.write(OK).unwrap(),
 | 
					    ufmt::uwriteln!(&mut serial, "token: {}", token).unwrap();
 | 
				
			||||||
                SET_TIMESTAMP => {
 | 
					 | 
				
			||||||
                    serial.write(OK).unwrap();
 | 
					 | 
				
			||||||
                    let year = loop {
 | 
					 | 
				
			||||||
                        let y = block!(serial.read()).unwrap();
 | 
					 | 
				
			||||||
                        serial.write(y).unwrap();
 | 
					 | 
				
			||||||
                        if block!(serial.read()).unwrap() == OK {
 | 
					 | 
				
			||||||
                            break y;
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    };
 | 
					 | 
				
			||||||
                    let month = loop {
 | 
					 | 
				
			||||||
                        let m = block!(serial.read()).unwrap();
 | 
					 | 
				
			||||||
                        serial.write(m).unwrap();
 | 
					 | 
				
			||||||
                        if block!(serial.read()).unwrap() == OK {
 | 
					 | 
				
			||||||
                            break m;
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    };
 | 
					 | 
				
			||||||
                    let day = loop {
 | 
					 | 
				
			||||||
                        let d = block!(serial.read()).unwrap();
 | 
					 | 
				
			||||||
                        serial.write(d).unwrap();
 | 
					 | 
				
			||||||
                        if block!(serial.read()).unwrap() == OK {
 | 
					 | 
				
			||||||
                            break d;
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    };
 | 
					 | 
				
			||||||
                    let hours = loop {
 | 
					 | 
				
			||||||
                        let h = block!(serial.read()).unwrap();
 | 
					 | 
				
			||||||
                        serial.write(h).unwrap();
 | 
					 | 
				
			||||||
                        if block!(serial.read()).unwrap() == OK {
 | 
					 | 
				
			||||||
                            break h;
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    };
 | 
					 | 
				
			||||||
                    let minutes = loop {
 | 
					 | 
				
			||||||
                        let m = block!(serial.read()).unwrap();
 | 
					 | 
				
			||||||
                        serial.write(m).unwrap();
 | 
					 | 
				
			||||||
                        if block!(serial.read()).unwrap() == OK {
 | 
					 | 
				
			||||||
                            break m;
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    };
 | 
					 | 
				
			||||||
                    let seconds = loop {
 | 
					 | 
				
			||||||
                        let s = block!(serial.read()).unwrap();
 | 
					 | 
				
			||||||
                        serial.write(s).unwrap();
 | 
					 | 
				
			||||||
                        if block!(serial.read()).unwrap() == OK {
 | 
					 | 
				
			||||||
                            break s;
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    };
 | 
					 | 
				
			||||||
                    rtc.set_year(year as u16 + 2000).unwrap();
 | 
					 | 
				
			||||||
                    rtc.set_month(month).unwrap();
 | 
					 | 
				
			||||||
                    rtc.set_day(day).unwrap();
 | 
					 | 
				
			||||||
                    rtc.set_hours(ds323x::Hours::H24(hours)).unwrap();
 | 
					 | 
				
			||||||
                    rtc.set_minutes(minutes).unwrap();
 | 
					 | 
				
			||||||
                    rtc.set_seconds(seconds).unwrap();
 | 
					 | 
				
			||||||
                    serial.write(OK).unwrap();
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                ADD_TOKEN => match tokens.search_free() {
 | 
					 | 
				
			||||||
                    Some(index) => {
 | 
					 | 
				
			||||||
                        serial.write(OK).unwrap();
 | 
					 | 
				
			||||||
                        let mut name_buff = [0u8; SECRET_KEY_NAME_LEN as usize];
 | 
					 | 
				
			||||||
                        for i in 0..name_buff.len() {
 | 
					 | 
				
			||||||
                            let ch = block!(serial.read()).unwrap();
 | 
					 | 
				
			||||||
                            if ch == ENDL {
 | 
					 | 
				
			||||||
                                break;
 | 
					 | 
				
			||||||
                            } else {
 | 
					 | 
				
			||||||
                                name_buff[i] = ch;
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        let mut key_buff = [0u8; SECRET_KEY_MAX_LEN as usize];
 | 
					 | 
				
			||||||
                        for i in 0..key_buff.len() {
 | 
					 | 
				
			||||||
                            let ch = block!(serial.read()).unwrap();
 | 
					 | 
				
			||||||
                            if ch == ENDL {
 | 
					 | 
				
			||||||
                                break;
 | 
					 | 
				
			||||||
                            } else {
 | 
					 | 
				
			||||||
                                key_buff[i] = ch;
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        match tokens.write(index, &name_buff, &key_buff) {
 | 
					 | 
				
			||||||
                            Some(_) => serial.write(OK).unwrap(),
 | 
					 | 
				
			||||||
                            None => serial.write(ERROR).unwrap(),
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    None => serial.write(ERROR).unwrap(),
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                WIPE_TOKENS => {
 | 
					 | 
				
			||||||
                    serial.write(OK).unwrap();
 | 
					 | 
				
			||||||
                    tokens.wipe_all_tokens();
 | 
					 | 
				
			||||||
                    serial.write(OK).unwrap();
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                _ => {}
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    match tokens.current {
 | 
					 | 
				
			||||||
        Some(index) => {
 | 
					 | 
				
			||||||
            let mut buff_name = [0u8; SECRET_KEY_NAME_LEN as usize];
 | 
					 | 
				
			||||||
            let mut buff_key = [0u8; SECRET_KEY_MAX_LEN as usize];
 | 
					 | 
				
			||||||
            match tokens.read(index, &mut buff_name, &mut buff_key) {
 | 
					 | 
				
			||||||
                Some((len_name, len_key)) => {
 | 
					 | 
				
			||||||
                    let name = core::str::from_utf8(&buff_name[..len_name]).unwrap();
 | 
					 | 
				
			||||||
                    let key = &buff_key[..len_key];
 | 
					 | 
				
			||||||
                    let dt = datetime::Datetime::from_ds3231(&mut rtc);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    let timestamp = dt.unix_epoch();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    let token = generate_otp_token(key, timestamp);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    display.set_cursor(0, 0);
 | 
					 | 
				
			||||||
                    display.write_str(name);
 | 
					 | 
				
			||||||
                    display.set_cursor(0, 1);
 | 
					 | 
				
			||||||
                    display.write_u32(token);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                _ => {
 | 
					 | 
				
			||||||
                    // display.clear();
 | 
					 | 
				
			||||||
                    // display.set_cursor(0, 0);
 | 
					 | 
				
			||||||
                    // display.write_str("ERROR");
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        _ => {}
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    loop {
 | 
					    loop {
 | 
				
			||||||
        led.toggle();
 | 
					        led.toggle();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,23 +2,11 @@ use arduino_hal::delay_ms;
 | 
				
			||||||
use arduino_hal::delay_us;
 | 
					use arduino_hal::delay_us;
 | 
				
			||||||
use embedded_hal::digital::v2::OutputPin;
 | 
					use embedded_hal::digital::v2::OutputPin;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// The CMDs codes are documented here:
 | 
					 | 
				
			||||||
// https://www.openhacks.com/uploadsproductos/eone-1602a1.pdf
 | 
					 | 
				
			||||||
const INIT_CMD: u8 = 0x03; // 0b00000011
 | 
					const INIT_CMD: u8 = 0x03; // 0b00000011
 | 
				
			||||||
const MODE_4_BITS_CMD: u8 = 0x02; // 0b00000010
 | 
					const MODE_4_BITS_CMD: u8 = 0x02; // 0b00000010
 | 
				
			||||||
const DISPLAY_MODE_16X2_CMD: u8 = 0x28; // 0b00101000
 | 
					const DISPLAY_MODE_CMD: u8 = 0x28; // 0b00101000
 | 
				
			||||||
const ENABLE_SCREEN_CMD: u8 = 0x0C; // 0b00001100
 | 
					const ENABLE_SCREEN_CMD: u8 = 0x0C; // 0b00001100
 | 
				
			||||||
const CLEAN_SCREEN_CMD: u8 = 0x01; // 0b00000001
 | 
					const CLEAN_SCREEN_CMD: u8 = 0x01; // 0b00000001
 | 
				
			||||||
const SHIFT_LEFT_CMD: u8 = 0x10; // 0b10000
 | 
					 | 
				
			||||||
const SHIFT_RIGHT_CMD: u8 = 0x14; // 0b10100
 | 
					 | 
				
			||||||
const MOVE_CURSOR_CMD: u8 = 0x80; // 0b10000000
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const ROW_OFFSETS: [u8; 2] = [0x00, 0x40];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub enum Direction {
 | 
					 | 
				
			||||||
    Left = SHIFT_LEFT_CMD as isize,
 | 
					 | 
				
			||||||
    Right = SHIFT_RIGHT_CMD as isize,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct StnScreen<'a, RS, EN, D4, D5, D6, D7> {
 | 
					pub struct StnScreen<'a, RS, EN, D4, D5, D6, D7> {
 | 
				
			||||||
    rs: &'a mut RS,
 | 
					    rs: &'a mut RS,
 | 
				
			||||||
| 
						 | 
					@ -67,9 +55,8 @@ impl<
 | 
				
			||||||
        delay_us(100);
 | 
					        delay_us(100);
 | 
				
			||||||
        display.write_nibble(MODE_4_BITS_CMD, false);
 | 
					        display.write_nibble(MODE_4_BITS_CMD, false);
 | 
				
			||||||
        delay_us(100);
 | 
					        delay_us(100);
 | 
				
			||||||
        display.send_cmd(DISPLAY_MODE_16X2_CMD, false);
 | 
					        display.send_cmd(DISPLAY_MODE_CMD, false);
 | 
				
			||||||
        display.send_cmd(ENABLE_SCREEN_CMD, false);
 | 
					        display.send_cmd(ENABLE_SCREEN_CMD, false);
 | 
				
			||||||
        display.clear();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        display
 | 
					        display
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -98,19 +85,8 @@ impl<
 | 
				
			||||||
        self.pulse_enable();
 | 
					        self.pulse_enable();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn clear(&mut self) {
 | 
					    fn clear(&mut self) {
 | 
				
			||||||
        self.send_cmd(CLEAN_SCREEN_CMD, false);
 | 
					        display.send_cmd(CLEAN_SCREEN_CMD, false);
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn shift(&mut self, dir: Direction) {
 | 
					 | 
				
			||||||
        let cmd = dir as u8;
 | 
					 | 
				
			||||||
        self.send_cmd(cmd, false);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn set_cursor(&mut self, col: u8, row: u8) {
 | 
					 | 
				
			||||||
        let row_offset = ROW_OFFSETS[row as usize];
 | 
					 | 
				
			||||||
        let address_position = row_offset | col;
 | 
					 | 
				
			||||||
        self.send_cmd(MOVE_CURSOR_CMD | address_position, false);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn write_char(&mut self, c: char) {
 | 
					    pub fn write_char(&mut self, c: char) {
 | 
				
			||||||
| 
						 | 
					@ -126,36 +102,8 @@ impl<
 | 
				
			||||||
            self.write_char(c);
 | 
					            self.write_char(c);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn write_u32(&mut self, num: u32) {
 | 
					 | 
				
			||||||
        // The max number in an u32 is: 4,294,967,295
 | 
					 | 
				
			||||||
        let mut buff = [0; 10];
 | 
					 | 
				
			||||||
        let len = num_chars(num, &mut buff);
 | 
					 | 
				
			||||||
        self.write_str(&core::str::from_utf8(&buff[..len]).unwrap());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn last_bit(n: u8) -> bool {
 | 
					fn last_bit(n: u8) -> bool {
 | 
				
			||||||
    (n & 1) != 0
 | 
					    (n & 1) != 0
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
fn num_rev(mut num: u32) -> u32 {
 | 
					 | 
				
			||||||
    let mut rev_num = 0;
 | 
					 | 
				
			||||||
    while num > 0 {
 | 
					 | 
				
			||||||
        let digit = num % 10;
 | 
					 | 
				
			||||||
        rev_num = rev_num * 10 + digit;
 | 
					 | 
				
			||||||
        num /= 10;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    rev_num
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn num_chars(n: u32, output: &mut [u8]) -> usize {
 | 
					 | 
				
			||||||
    let mut n = num_rev(n);
 | 
					 | 
				
			||||||
    let mut i = 0;
 | 
					 | 
				
			||||||
    while n > 0 {
 | 
					 | 
				
			||||||
        output[i] = (n % 10) as u8 + '0' as u8;
 | 
					 | 
				
			||||||
        n = n / 10;
 | 
					 | 
				
			||||||
        i += 1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    i
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										131
									
								
								src/storage.rs
								
								
								
								
							
							
						
						
									
										131
									
								
								src/storage.rs
								
								
								
								
							| 
						 | 
					@ -1,131 +0,0 @@
 | 
				
			||||||
use arduino_hal::eeprom::Eeprom;
 | 
					 | 
				
			||||||
use randomize::PCG32;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const SECRET_KEY_MAX_LEN: u16 = 32;
 | 
					 | 
				
			||||||
pub const SECRET_KEY_NAME_LEN: u16 = 16;
 | 
					 | 
				
			||||||
pub const SECRET_KEY_FULL_LEN: u16 = SECRET_KEY_MAX_LEN + SECRET_KEY_NAME_LEN;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub struct Tokens<'a> {
 | 
					 | 
				
			||||||
    mem: &'a mut Eeprom,
 | 
					 | 
				
			||||||
    pub current: Option<u16>,
 | 
					 | 
				
			||||||
    capacity: u16,
 | 
					 | 
				
			||||||
    rand: PCG32,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl<'a> Tokens<'a> {
 | 
					 | 
				
			||||||
    pub fn new(mem: &'a mut Eeprom, rand_seed: u64) -> Self {
 | 
					 | 
				
			||||||
        let capacity = mem.capacity() / SECRET_KEY_FULL_LEN;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let mut tokens = Tokens {
 | 
					 | 
				
			||||||
            mem,
 | 
					 | 
				
			||||||
            capacity,
 | 
					 | 
				
			||||||
            rand: PCG32::seed(rand_seed, 1),
 | 
					 | 
				
			||||||
            current: None,
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        tokens.current = tokens.first();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        tokens
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn search_free(&self) -> Option<u16> {
 | 
					 | 
				
			||||||
        for n in 0..self.capacity {
 | 
					 | 
				
			||||||
            let index = SECRET_KEY_FULL_LEN * n;
 | 
					 | 
				
			||||||
            if self.mem.read_byte(index) == 255 {
 | 
					 | 
				
			||||||
                return Some(n);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        None
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn first(&self) -> Option<u16> {
 | 
					 | 
				
			||||||
        for n in 0..self.capacity {
 | 
					 | 
				
			||||||
            let index = SECRET_KEY_FULL_LEN * n;
 | 
					 | 
				
			||||||
            if self.mem.read_byte(index) != 255 {
 | 
					 | 
				
			||||||
                return Some(n);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return None;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn next(&mut self) -> Option<u16> {
 | 
					 | 
				
			||||||
        let mut index = self.current.unwrap();
 | 
					 | 
				
			||||||
        for _ in 0..self.capacity {
 | 
					 | 
				
			||||||
            index = (index + 1) % self.capacity;
 | 
					 | 
				
			||||||
            if self.mem.read_byte(index * SECRET_KEY_FULL_LEN) != 255 {
 | 
					 | 
				
			||||||
                self.current = Some(index);
 | 
					 | 
				
			||||||
                return Some(index);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        None
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn prev(&mut self) -> Option<u16> {
 | 
					 | 
				
			||||||
        todo!();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn read(&self, index: u16, name: &mut [u8], key: &mut [u8]) -> Option<(usize, usize)> {
 | 
					 | 
				
			||||||
        name.fill(0);
 | 
					 | 
				
			||||||
        key.fill(0);
 | 
					 | 
				
			||||||
        let index_name = index * SECRET_KEY_FULL_LEN;
 | 
					 | 
				
			||||||
        let index_key = (index * SECRET_KEY_FULL_LEN) + SECRET_KEY_NAME_LEN;
 | 
					 | 
				
			||||||
        match self.mem.read(index_name, name) {
 | 
					 | 
				
			||||||
            Ok(_) => {}
 | 
					 | 
				
			||||||
            Err(_) => return None,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        match self.mem.read(index_key, key) {
 | 
					 | 
				
			||||||
            Ok(_) => {}
 | 
					 | 
				
			||||||
            Err(_) => return None,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        let len_name = name
 | 
					 | 
				
			||||||
            .iter()
 | 
					 | 
				
			||||||
            .position(|&n| n == 0)
 | 
					 | 
				
			||||||
            .unwrap_or(SECRET_KEY_NAME_LEN as usize);
 | 
					 | 
				
			||||||
        let len_key = key
 | 
					 | 
				
			||||||
            .iter()
 | 
					 | 
				
			||||||
            .position(|&n| n == 0)
 | 
					 | 
				
			||||||
            .unwrap_or(SECRET_KEY_MAX_LEN as usize);
 | 
					 | 
				
			||||||
        Some((len_name, len_key))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn write(&mut self, index: u16, name: &[u8], key: &[u8]) -> Option<u16> {
 | 
					 | 
				
			||||||
        let index_name = index * SECRET_KEY_FULL_LEN;
 | 
					 | 
				
			||||||
        let index_key = (index * SECRET_KEY_FULL_LEN) + SECRET_KEY_NAME_LEN;
 | 
					 | 
				
			||||||
        match self.mem.write(index_name, name) {
 | 
					 | 
				
			||||||
            Ok(_) => {}
 | 
					 | 
				
			||||||
            Err(_) => return None,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        match self.mem.write(index_key, key) {
 | 
					 | 
				
			||||||
            Ok(_) => {}
 | 
					 | 
				
			||||||
            Err(_) => return None,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        Some(index)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn delete(&mut self, index: u16) -> Option<u16> {
 | 
					 | 
				
			||||||
        // The Arduino's EEPROM memory has a maximum number of write cycles.
 | 
					 | 
				
			||||||
        // To keep writes to a minimum, only the first byte of the token name is set to 0
 | 
					 | 
				
			||||||
        // and a byte of the key is randomly chosen to be overwritten with
 | 
					 | 
				
			||||||
        // another random value, so that it's unrecoverable.
 | 
					 | 
				
			||||||
        let index_name = index * SECRET_KEY_FULL_LEN;
 | 
					 | 
				
			||||||
        let index_key = (index * SECRET_KEY_FULL_LEN) + SECRET_KEY_NAME_LEN;
 | 
					 | 
				
			||||||
        let index_key = index_key + (self.rand.next_u32() % SECRET_KEY_MAX_LEN as u32) as u16;
 | 
					 | 
				
			||||||
        let rand_byte = (self.rand.next_u32() % 255) as u8;
 | 
					 | 
				
			||||||
        self.mem.write_byte(index_name, 255);
 | 
					 | 
				
			||||||
        self.mem.write_byte(index_key, rand_byte);
 | 
					 | 
				
			||||||
        Some(index)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn wipe_all_tokens(&mut self) -> u8 {
 | 
					 | 
				
			||||||
        let mut inc = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for n in 0..self.capacity {
 | 
					 | 
				
			||||||
            let index = SECRET_KEY_FULL_LEN * n;
 | 
					 | 
				
			||||||
            if self.mem.read_byte(index) != 255 {
 | 
					 | 
				
			||||||
                self.delete(index);
 | 
					 | 
				
			||||||
                inc += 1;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        inc
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										157
									
								
								tools/menu.py
								
								
								
								
							
							
						
						
									
										157
									
								
								tools/menu.py
								
								
								
								
							| 
						 | 
					@ -1,157 +0,0 @@
 | 
				
			||||||
from datetime import datetime
 | 
					 | 
				
			||||||
from time import sleep
 | 
					 | 
				
			||||||
from typing import Any
 | 
					 | 
				
			||||||
import base64
 | 
					 | 
				
			||||||
import serial
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
ENDL = bytes([0])
 | 
					 | 
				
			||||||
OK = bytes([1])
 | 
					 | 
				
			||||||
ERROR = bytes([2])
 | 
					 | 
				
			||||||
HANDSHAKE = bytes([3])
 | 
					 | 
				
			||||||
SET_TIMESTAMP = bytes([10])
 | 
					 | 
				
			||||||
ADD_TOKEN = bytes([20])
 | 
					 | 
				
			||||||
DELETE_TOKEN = bytes([30])
 | 
					 | 
				
			||||||
GET_TOKENS = bytes([40])
 | 
					 | 
				
			||||||
WIPE_TOKENS = bytes([50])
 | 
					 | 
				
			||||||
EXIT = bytes([254])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def loop_input(msg: str, valid_values: Any) -> str:
 | 
					 | 
				
			||||||
    valid_values = list(map(str, valid_values))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    while True:
 | 
					 | 
				
			||||||
        data = input(msg)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if data not in valid_values:
 | 
					 | 
				
			||||||
            print(f"'{data}' isn't a valid value, please enter a value in {valid_values}")
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            break
 | 
					 | 
				
			||||||
    return data
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def b(n: int) -> bytes:
 | 
					 | 
				
			||||||
    return bytes([n])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def process_secret(secret: str) -> bytes:
 | 
					 | 
				
			||||||
    offset = 8 - (len(secret) % 8)
 | 
					 | 
				
			||||||
    if offset != 8:
 | 
					 | 
				
			||||||
        secret += "=" * offset
 | 
					 | 
				
			||||||
    return base64.b32decode(secret, casefold=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def send_and_validate(value: bytes, conn):
 | 
					 | 
				
			||||||
    while True:
 | 
					 | 
				
			||||||
        print(f"Send {list(value)}")
 | 
					 | 
				
			||||||
        conn.write(value)
 | 
					 | 
				
			||||||
        resp = conn.read()
 | 
					 | 
				
			||||||
        print(f"Received {list(resp)}")
 | 
					 | 
				
			||||||
        if resp == value:
 | 
					 | 
				
			||||||
            conn.write(OK)
 | 
					 | 
				
			||||||
            break
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            conn.write(ERROR)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def get_datetime_items() -> dict[str, bytes]:
 | 
					 | 
				
			||||||
    now = datetime.utcnow()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return {
 | 
					 | 
				
			||||||
        "year": b(now.year - 2000),
 | 
					 | 
				
			||||||
        "month": b(now.month),
 | 
					 | 
				
			||||||
        "day": b(now.day),
 | 
					 | 
				
			||||||
        "hours": b(now.hour),
 | 
					 | 
				
			||||||
        "minutes": b(now.minute),
 | 
					 | 
				
			||||||
        "seconds": b(now.second)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main(argv: list[str]):
 | 
					 | 
				
			||||||
    port = argv[-1]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    conn = serial.Serial(port=port, baudrate=9600)
 | 
					 | 
				
			||||||
    print("UP + Reset to enable the USB connection")
 | 
					 | 
				
			||||||
    input("Press Enter if the device in the USB mode...")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    conn.write(HANDSHAKE)
 | 
					 | 
				
			||||||
    sleep(0.1)
 | 
					 | 
				
			||||||
    res = conn.read()
 | 
					 | 
				
			||||||
    if res != OK:
 | 
					 | 
				
			||||||
        print("A handshake could not be performed")
 | 
					 | 
				
			||||||
        print("Check the connection with your Arduino")
 | 
					 | 
				
			||||||
        return 1
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        print("Handshake successfully performed")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    while True:
 | 
					 | 
				
			||||||
        print("What do you want to do?")
 | 
					 | 
				
			||||||
        print("1) Update timestamp")
 | 
					 | 
				
			||||||
        print("2) Add a new token")
 | 
					 | 
				
			||||||
        print("3) Remove a token")
 | 
					 | 
				
			||||||
        print("4) WIPE ALL THE TOKENS")
 | 
					 | 
				
			||||||
        print("5) EXIT")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        opt = loop_input(">>> ", range(1, 6))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Update Timestamp
 | 
					 | 
				
			||||||
        if opt == "1":
 | 
					 | 
				
			||||||
            conn.write(SET_TIMESTAMP)
 | 
					 | 
				
			||||||
            sleep(0.1)
 | 
					 | 
				
			||||||
            resp = conn.read()
 | 
					 | 
				
			||||||
            sleep(0.1)
 | 
					 | 
				
			||||||
            if resp == ERROR:
 | 
					 | 
				
			||||||
                print(f"Error in the communication: Error {resp}")
 | 
					 | 
				
			||||||
                continue
 | 
					 | 
				
			||||||
            date_items = get_datetime_items();
 | 
					 | 
				
			||||||
            send_and_validate(date_items["year"], conn)
 | 
					 | 
				
			||||||
            send_and_validate(date_items["month"], conn)
 | 
					 | 
				
			||||||
            send_and_validate(date_items["day"], conn)
 | 
					 | 
				
			||||||
            send_and_validate(date_items["hours"], conn)
 | 
					 | 
				
			||||||
            send_and_validate(date_items["minutes"], conn)
 | 
					 | 
				
			||||||
            send_and_validate(date_items["seconds"], conn)
 | 
					 | 
				
			||||||
            resp = conn.read()
 | 
					 | 
				
			||||||
            if resp != OK:
 | 
					 | 
				
			||||||
                print(f"Error in the communication: Error {resp}")
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                print("Timestamp updated successfully!")
 | 
					 | 
				
			||||||
        # Add token
 | 
					 | 
				
			||||||
        elif opt == "2":
 | 
					 | 
				
			||||||
            conn.write(ADD_TOKEN)
 | 
					 | 
				
			||||||
            sleep(0.1)
 | 
					 | 
				
			||||||
            resp = conn.read()
 | 
					 | 
				
			||||||
            if resp == ERROR:
 | 
					 | 
				
			||||||
                print("The memory of the device is full")
 | 
					 | 
				
			||||||
                continue
 | 
					 | 
				
			||||||
            name = input("Enter the name of the new token (16 chars max):\n>>> ")
 | 
					 | 
				
			||||||
            name = name.strip()[:16].encode("ascii")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            key = input("Enter the OTP secret key (32 chars max):\n>>> ")
 | 
					 | 
				
			||||||
            key = process_secret(key.strip()[:32])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            for ch in name:
 | 
					 | 
				
			||||||
                conn.write(b(ch))
 | 
					 | 
				
			||||||
            conn.write(ENDL)
 | 
					 | 
				
			||||||
            for ch in key:
 | 
					 | 
				
			||||||
                conn.write(b(ch))
 | 
					 | 
				
			||||||
            conn.write(ENDL)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            resp = conn.read()
 | 
					 | 
				
			||||||
            if resp == ERROR:
 | 
					 | 
				
			||||||
                print("Error trying to add the token, try again")
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                print("Token added successfully!")
 | 
					 | 
				
			||||||
        # Wipe tokens
 | 
					 | 
				
			||||||
        elif opt == "4":
 | 
					 | 
				
			||||||
            conn.write(WIPE_TOKENS)
 | 
					 | 
				
			||||||
            sleep(0.1)
 | 
					 | 
				
			||||||
            _ = conn.read()
 | 
					 | 
				
			||||||
            resp = conn.read()
 | 
					 | 
				
			||||||
            if resp == OK:
 | 
					 | 
				
			||||||
                print("All the tokens wipped successfully!")
 | 
					 | 
				
			||||||
        elif opt == "5":
 | 
					 | 
				
			||||||
            return 0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if __name__ == "__main__":
 | 
					 | 
				
			||||||
    attrs = sys.argv
 | 
					 | 
				
			||||||
    if len(attrs) == 1:
 | 
					 | 
				
			||||||
        print("You need to specify the Arduino's serial port")
 | 
					 | 
				
			||||||
        print("Example:")
 | 
					 | 
				
			||||||
        print(f"python {attrs[0]} /dev/ttyUSB0")
 | 
					 | 
				
			||||||
        sys.exit(1)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    sys.exit(main(attrs[1:]))
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,2 +1 @@
 | 
				
			||||||
# avrdude -p atmega328p -c arduino -P /dev/ttyACM0 -U flash:w:target/avr-atmega328p/release/rustytoken.elf:e
 | 
					avrdude -p atmega328p -c arduino -P /dev/ttyACM0 -U flash:w:target/avr-atmega328p/release/rustytoken.elf:e
 | 
				
			||||||
avrdude -p atmega328p -c arduino -P /dev/ttyUSB0 -U flash:w:target/avr-atmega328p/release/rustytoken.elf:e
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue