From 8775601f494c07f11b13676245ff7d06324d5d13 Mon Sep 17 00:00:00 2001 From: Mitchell M Date: Tue, 13 Oct 2026 20:17:34 -0500 Subject: [PATCH] Update README and environment configuration; enhance code1 functionality and add assembly code --- README.md | 20 +++++++---- dev.env | 1 + public/challenge3.txt | 25 +++++++++++++- public/challenge3_annotated.txt | 24 +++++++++++++ src/codes/code1.rs | 60 +++++++++++++++++++++++++++++---- src/manage.rs | 16 ++++++--- src/secrets.rs | 13 +++++++ 7 files changed, 140 insertions(+), 19 deletions(-) create mode 100644 public/challenge3_annotated.txt diff --git a/README.md b/README.md index bb736cb..2402977 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,10 @@ Copy-Item dev.env .env You can also provide `DEV_SECRET_KEY` through the process environment. Do not commit secrets or expose them in client-side code. +Set `PROD=true` to make the server start in the live state. Truthy values are +`1`, `true`, `yes`, and `on` (case-insensitive); an absent or other value starts +the server offline. + Code 1 sends an SMS through TextBee when the server is live. Set `TEXTBEE_API_KEY`, `RECIPIENT_SELF`, `RECIPIENT_1`, and `RECIPIENT_2` (each a single E.164 number) in `.env`; `TEXTBEE_DEVICE_ID` is optional and selects a @@ -65,16 +69,18 @@ the notification instead while offline). The result page displays step when ready. The robot submits code 1 with `POST /event/buzz` and a JSON body containing -`delta_ms` (a signed integer) and `secret` (the value of `CODE1_SECRET`). +`delay_ms` (a non-negative integer) and `secret` (the value of `CODE1_SECRET`). +The server subtracts the target delay of 6430 ms from `delay_ms` to calculate +the signed buzz delta. Set `CODE1_SECRET` to a unique random secret of at least 32 bytes in `.env`. For example, with the server running locally: ```sh -curl.exe -X POST http://127.0.0.1:3000/event/buzz -H "Content-Type: application/json" -d '{"delta_ms":-42,"secret":""}' +curl.exe -X POST http://127.0.0.1:3000/event/buzz -H "Content-Type: application/json" -d '{"delay_ms":6388,"secret":""}' ``` -The integer `delta_ms` value is passed through to the congratulations message; -a valid request returns HTTP `202`. +The server calculates `6388 - 6430 = -42` ms for the congratulations message. +A valid request returns HTTP `202`. ### Test code1 with Postman @@ -82,7 +88,7 @@ Create a `POST` request to `http://127.0.0.1:3000/event/buzz`, select **Body > raw > JSON**, and send: ```json { - "delta_ms": -42, + "delay_ms": 6388, "secret": "" } ``` @@ -94,14 +100,14 @@ single test event: ```javascript async function main() { - const deltaMs = -42; + const delayMs = 6388; const secret = process.env.CODE1_SECRET; if (!secret) throw new Error("CODE1_SECRET is not set"); const response = await fetch("http://127.0.0.1:3000/event/buzz", { method: "POST", headers: { "content-type": "application/json" }, - body: JSON.stringify({ delta_ms: deltaMs, secret }), + body: JSON.stringify({ delay_ms: delayMs, secret }), }); console.log(response.status, await response.text()); diff --git a/dev.env b/dev.env index 0e116d1..da59775 100644 --- a/dev.env +++ b/dev.env @@ -5,3 +5,4 @@ RECIPIENT_SELF=+12015550123 RECIPIENT_1=+12015550124 RECIPIENT_2=+12015550125 CODE1_SECRET=replace-with-a-unique-random-secret +PROD=false diff --git a/public/challenge3.txt b/public/challenge3.txt index e8e6b33..92ad990 100644 --- a/public/challenge3.txt +++ b/public/challenge3.txt @@ -1 +1,24 @@ -Assembly data will be added here. + .data +code: .byte 0x61 0x73 0x1E 0x49 0x43 0x27 0x1A 0x6C 0x61 0x78 0x1E 0x71 0x6B 0x1B 0x32 0x19 0x27 0x48 0x72 0x24 0x6F 0x1B 0x5B 0x65 0x77 0x60 0x77 0x70 0x1C 0x6E 0x6F 0x67 0x6E 0x78 0x1E 0x66 0x6A 0x1B 0x4A 0x61 0x69 0x6B 0x5F 0x61 0x61 0x67 0x6A 0x61 0x69 0x60 0x2A 0x1D 0x5E 0x70 0x6E 0x19 0x77 0x67 0x5F 0x71 0x1C 0x5C 0x5C 0x68 0x75 0x73 0x1E 0x74 0x64 0x60 0x68 0x19 0x69 0x73 0x1E 0x6F 0x5D 0x64 0x68 0x6C 0x3F 0x26 +code_len = 82 + + .text +main: + lea s0, 0(code) + andi s1, s1, 0 + li s2, code_len +label1: + jge s1, s2, label2 + + add t0, s0, s1 + lbu t1, 0(t0) + andi t2, s1, 7 + add t1, t1, t2 + putc t1 + + addi s1, s1, 1 + jmp label1 +label2: + li t1, 10 + putc t1 + halt diff --git a/public/challenge3_annotated.txt b/public/challenge3_annotated.txt new file mode 100644 index 0000000..0bd2713 --- /dev/null +++ b/public/challenge3_annotated.txt @@ -0,0 +1,24 @@ + .data +code: .byte 0x61 0x73 0x1E 0x49 0x43 0x27 0x1A 0x6C 0x61 0x78 0x1E 0x71 0x6B 0x1B 0x32 0x19 0x27 0x48 0x72 0x24 0x6F 0x1B 0x5B 0x65 0x77 0x60 0x77 0x70 0x1C 0x6E 0x6F 0x67 0x6E 0x78 0x1E 0x66 0x6A 0x1B 0x4A 0x61 0x69 0x6B 0x5F 0x61 0x61 0x67 0x6A 0x61 0x69 0x60 0x2A 0x1D 0x5E 0x70 0x6E 0x19 0x77 0x67 0x5F 0x71 0x1C 0x5C 0x5C 0x68 0x75 0x73 0x1E 0x74 0x64 0x60 0x68 0x19 0x69 0x73 0x1E 0x6F 0x5D 0x64 0x68 0x6C 0x3F 0x26 +code_len = 82 + + .text +main: + lea s0, 0(code) ; s0 := &code + andi s1, s1, 0 ; s1 = i := 0 + li s2, code_len ; s2 := code_len +label1: + jge s1, s2, label2 ; if i >= 12, we're finished + + add t0, s0, s1 ; t0 := &code[i] + lbu t1, 0(t0) ; t1 := *t0 = code[i] + andi t2, s1, 7 ; t2 := i % 8 + add t1, t1, t2 ; t1 := t1 + t2 = code[i] + (i % 8) + putc t1 ; print t1 + + addi s1, s1, 1 ; i := i + 1 + jmp label1 +label2: + li t1, 10 ; load \n + putc t1 + halt diff --git a/src/codes/code1.rs b/src/codes/code1.rs index 118d5ad..d2beb52 100644 --- a/src/codes/code1.rs +++ b/src/codes/code1.rs @@ -3,21 +3,62 @@ use serde_json::Value; use std::net::SocketAddr; pub const ID: &str = "code1"; +const TARGET_DELAY_MS: i64 = 6430; const SELF_NOTIFICATION: &str = "Code 1 has been submitted while the server is live."; -const CHALLENGE_1_FIRST: &str = "TODO: secded data"; -const CHALLENGE_1_SECOND: &str = "SECDED-13\nMSB-LSB"; +const CHALLENGE_1_FIRST: &str = "SECDED-13\nPARITY MSB...LSB"; +const CHALLENGE_1_SECOND: &str = "0x144A +0x17AE +0x17AA +0x07A0 +0x079E +0x1751 +0x02FD +0x02FD +0x163F +0x067A +0x062B +0x122C +0x12FA +0x1667 +0x0606 +0x179B +0x164D +0x16F9 +0x027E +0x0629 +0x1E2C +0x07B3 +0x12FA +0x121F +0x0676 +0x1667 +0x02FC +0x0F9E +0x07AD +0x061C +0x1267 +0x064D +0x16AA +0x02F5 +0x0505 +0x065B +0x1329 +0x1E2C +0x1484 +0x1671"; pub async fn buzz_event_handler( ConnectInfo(address): ConnectInfo, Json(payload): Json, ) -> Result { - let delta_ms = payload - .get("delta_ms") + let delay_ms = payload + .get("delay_ms") .and_then(Value::as_i64) + .filter(|delay_ms| *delay_ms >= 0) .ok_or_else(|| { ( StatusCode::BAD_REQUEST, - "delta_ms must be a signed 64-bit integer".to_string(), + "delay_ms must be a non-negative 64-bit integer".to_string(), ) })?; let secret = payload @@ -39,6 +80,7 @@ pub async fn buzz_event_handler( let live = crate::manage::server_live_state(); crate::codes::history::record_successful_submission(address.ip(), live, ID) .map_err(|error| (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()))?; + let delta_ms = delay_ms - TARGET_DELAY_MS; on_submit(live, delta_ms).await.map_err(|error| { leptos::logging::log!("Code 1 buzz event failed: {error}"); ( @@ -50,11 +92,15 @@ pub async fn buzz_event_handler( Ok(StatusCode::ACCEPTED) } -async fn on_submit(live: bool, delta_ms: i64) -> Result<(), String> { +async fn on_submit(live: bool, mut delta_ms: i64) -> Result<(), String> { let direction = match delta_ms.cmp(&0) { std::cmp::Ordering::Less => "short", std::cmp::Ordering::Greater => "long", - std::cmp::Ordering::Equal => "exactly on time", + std::cmp::Ordering::Equal => { + // pretend they were off a little bit + delta_ms = 52; + "long" + } }; let congrats_message = format!( "Congratulations on completing Challenge 1. It appears that you've figured out what to do, although your buzz was {}ms {}. Consider being more precise in the future. Your sloppiness has been noted.", diff --git a/src/manage.rs b/src/manage.rs index d558b0b..a68d3e3 100644 --- a/src/manage.rs +++ b/src/manage.rs @@ -2,14 +2,22 @@ use leptos::prelude::*; use leptos_router::components::A; #[cfg(feature = "ssr")] -use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + OnceLock, +}; #[cfg(feature = "ssr")] -static SERVER_LIVE: AtomicBool = AtomicBool::new(false); +static SERVER_LIVE: OnceLock = OnceLock::new(); + +#[cfg(feature = "ssr")] +fn live_state() -> &'static AtomicBool { + SERVER_LIVE.get_or_init(|| AtomicBool::new(crate::secrets::runtime().initial_live)) +} #[cfg(feature = "ssr")] pub(crate) fn server_live_state() -> bool { - SERVER_LIVE.load(Ordering::SeqCst) + live_state().load(Ordering::SeqCst) } #[server] @@ -23,7 +31,7 @@ async fn set_live_state(live: bool, secret_key: String) -> Result<(), ServerFnEr return Err(ServerFnError::ServerError("Invalid secret key".to_string())); } - SERVER_LIVE.store(live, Ordering::SeqCst); + live_state().store(live, Ordering::SeqCst); Ok(()) } diff --git a/src/secrets.rs b/src/secrets.rs index ad7de8a..05ef001 100644 --- a/src/secrets.rs +++ b/src/secrets.rs @@ -7,6 +7,7 @@ pub(crate) const RECIPIENT_SELF: &str = "RECIPIENT_SELF"; pub(crate) const RECIPIENT_1: &str = "RECIPIENT_1"; pub(crate) const RECIPIENT_2: &str = "RECIPIENT_2"; pub(crate) const CODE1_SECRET: &str = "CODE1_SECRET"; +pub(crate) const PROD: &str = "PROD"; static RUNTIME_SECRETS: OnceLock = OnceLock::new(); @@ -18,6 +19,7 @@ pub(crate) struct RuntimeSecrets { pub(crate) recipient_1: String, pub(crate) recipient_2: String, pub(crate) code1_secret: String, + pub(crate) initial_live: bool, } pub fn init() -> Result<(), String> { @@ -54,6 +56,9 @@ impl RuntimeSecrets { let recipient_1 = required_recipient(RECIPIENT_1)?; let recipient_2 = required_recipient(RECIPIENT_2)?; let code1_secret = required(CODE1_SECRET)?; + let initial_live = env::var(PROD) + .map(|value| is_truthy(&value)) + .unwrap_or(false); if code1_secret.len() < 32 { return Err(format!("{CODE1_SECRET} must contain at least 32 bytes")); @@ -71,6 +76,7 @@ impl RuntimeSecrets { recipient_1, recipient_2, code1_secret, + initial_live, }) } } @@ -98,3 +104,10 @@ fn is_e164_number(number: &str) -> bool { && !digits.starts_with('0') && digits.bytes().all(|byte| byte.is_ascii_digit()) } + +fn is_truthy(value: &str) -> bool { + matches!( + value.trim().to_ascii_lowercase().as_str(), + "1" | "true" | "yes" | "on" + ) +}