Update README and environment configuration; enhance code1 functionality and add assembly code

This commit is contained in:
2026-10-13 20:17:34 -05:00
parent 1ff23a3004
commit 8775601f49
7 changed files with 140 additions and 19 deletions
+13 -7
View File
@@ -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":"<your CODE1_SECRET value>"}'
curl.exe -X POST http://127.0.0.1:3000/event/buzz -H "Content-Type: application/json" -d '{"delay_ms":6388,"secret":"<your CODE1_SECRET value>"}'
```
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": "<your CODE1_SECRET value>"
}
```
@@ -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());
+1
View File
@@ -5,3 +5,4 @@ RECIPIENT_SELF=+12015550123
RECIPIENT_1=+12015550124
RECIPIENT_2=+12015550125
CODE1_SECRET=replace-with-a-unique-random-secret
PROD=false
+24 -1
View File
@@ -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
+24
View File
@@ -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
+53 -7
View File
@@ -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<SocketAddr>,
Json(payload): Json<Value>,
) -> Result<StatusCode, (StatusCode, String)> {
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.",
+12 -4
View File
@@ -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<AtomicBool> = 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(())
}
+13
View File
@@ -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<RuntimeSecrets> = 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"
)
}