Update README and environment configuration; enhance code1 functionality and add assembly code
This commit is contained in:
@@ -49,6 +49,10 @@ Copy-Item dev.env .env
|
|||||||
You can also provide `DEV_SECRET_KEY` through the process environment. Do not
|
You can also provide `DEV_SECRET_KEY` through the process environment. Do not
|
||||||
commit secrets or expose them in client-side code.
|
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
|
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
|
`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
|
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.
|
step when ready.
|
||||||
|
|
||||||
The robot submits code 1 with `POST /event/buzz` and a JSON body containing
|
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
|
Set `CODE1_SECRET` to a unique random secret of at least 32 bytes in
|
||||||
`.env`. For example, with the server running locally:
|
`.env`. For example, with the server running locally:
|
||||||
|
|
||||||
```sh
|
```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;
|
The server calculates `6388 - 6430 = -42` ms for the congratulations message.
|
||||||
a valid request returns HTTP `202`.
|
A valid request returns HTTP `202`.
|
||||||
|
|
||||||
### Test code1 with Postman
|
### 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:
|
raw > JSON**, and send:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"delta_ms": -42,
|
"delay_ms": 6388,
|
||||||
"secret": "<your CODE1_SECRET value>"
|
"secret": "<your CODE1_SECRET value>"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -94,14 +100,14 @@ single test event:
|
|||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
async function main() {
|
async function main() {
|
||||||
const deltaMs = -42;
|
const delayMs = 6388;
|
||||||
const secret = process.env.CODE1_SECRET;
|
const secret = process.env.CODE1_SECRET;
|
||||||
if (!secret) throw new Error("CODE1_SECRET is not set");
|
if (!secret) throw new Error("CODE1_SECRET is not set");
|
||||||
|
|
||||||
const response = await fetch("http://127.0.0.1:3000/event/buzz", {
|
const response = await fetch("http://127.0.0.1:3000/event/buzz", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "content-type": "application/json" },
|
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());
|
console.log(response.status, await response.text());
|
||||||
|
|||||||
@@ -5,3 +5,4 @@ RECIPIENT_SELF=+12015550123
|
|||||||
RECIPIENT_1=+12015550124
|
RECIPIENT_1=+12015550124
|
||||||
RECIPIENT_2=+12015550125
|
RECIPIENT_2=+12015550125
|
||||||
CODE1_SECRET=replace-with-a-unique-random-secret
|
CODE1_SECRET=replace-with-a-unique-random-secret
|
||||||
|
PROD=false
|
||||||
|
|||||||
+24
-1
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -3,21 +3,62 @@ use serde_json::Value;
|
|||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
pub const ID: &str = "code1";
|
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 SELF_NOTIFICATION: &str = "Code 1 has been submitted while the server is live.";
|
||||||
const CHALLENGE_1_FIRST: &str = "TODO: secded data";
|
const CHALLENGE_1_FIRST: &str = "SECDED-13\nPARITY MSB...LSB";
|
||||||
const CHALLENGE_1_SECOND: &str = "SECDED-13\nMSB-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(
|
pub async fn buzz_event_handler(
|
||||||
ConnectInfo(address): ConnectInfo<SocketAddr>,
|
ConnectInfo(address): ConnectInfo<SocketAddr>,
|
||||||
Json(payload): Json<Value>,
|
Json(payload): Json<Value>,
|
||||||
) -> Result<StatusCode, (StatusCode, String)> {
|
) -> Result<StatusCode, (StatusCode, String)> {
|
||||||
let delta_ms = payload
|
let delay_ms = payload
|
||||||
.get("delta_ms")
|
.get("delay_ms")
|
||||||
.and_then(Value::as_i64)
|
.and_then(Value::as_i64)
|
||||||
|
.filter(|delay_ms| *delay_ms >= 0)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
(
|
(
|
||||||
StatusCode::BAD_REQUEST,
|
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
|
let secret = payload
|
||||||
@@ -39,6 +80,7 @@ pub async fn buzz_event_handler(
|
|||||||
let live = crate::manage::server_live_state();
|
let live = crate::manage::server_live_state();
|
||||||
crate::codes::history::record_successful_submission(address.ip(), live, ID)
|
crate::codes::history::record_successful_submission(address.ip(), live, ID)
|
||||||
.map_err(|error| (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()))?;
|
.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| {
|
on_submit(live, delta_ms).await.map_err(|error| {
|
||||||
leptos::logging::log!("Code 1 buzz event failed: {error}");
|
leptos::logging::log!("Code 1 buzz event failed: {error}");
|
||||||
(
|
(
|
||||||
@@ -50,11 +92,15 @@ pub async fn buzz_event_handler(
|
|||||||
Ok(StatusCode::ACCEPTED)
|
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) {
|
let direction = match delta_ms.cmp(&0) {
|
||||||
std::cmp::Ordering::Less => "short",
|
std::cmp::Ordering::Less => "short",
|
||||||
std::cmp::Ordering::Greater => "long",
|
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!(
|
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.",
|
"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
@@ -2,14 +2,22 @@ use leptos::prelude::*;
|
|||||||
use leptos_router::components::A;
|
use leptos_router::components::A;
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::{
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
OnceLock,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
#[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")]
|
#[cfg(feature = "ssr")]
|
||||||
pub(crate) fn server_live_state() -> bool {
|
pub(crate) fn server_live_state() -> bool {
|
||||||
SERVER_LIVE.load(Ordering::SeqCst)
|
live_state().load(Ordering::SeqCst)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[server]
|
#[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()));
|
return Err(ServerFnError::ServerError("Invalid secret key".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
SERVER_LIVE.store(live, Ordering::SeqCst);
|
live_state().store(live, Ordering::SeqCst);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ pub(crate) const RECIPIENT_SELF: &str = "RECIPIENT_SELF";
|
|||||||
pub(crate) const RECIPIENT_1: &str = "RECIPIENT_1";
|
pub(crate) const RECIPIENT_1: &str = "RECIPIENT_1";
|
||||||
pub(crate) const RECIPIENT_2: &str = "RECIPIENT_2";
|
pub(crate) const RECIPIENT_2: &str = "RECIPIENT_2";
|
||||||
pub(crate) const CODE1_SECRET: &str = "CODE1_SECRET";
|
pub(crate) const CODE1_SECRET: &str = "CODE1_SECRET";
|
||||||
|
pub(crate) const PROD: &str = "PROD";
|
||||||
|
|
||||||
static RUNTIME_SECRETS: OnceLock<RuntimeSecrets> = OnceLock::new();
|
static RUNTIME_SECRETS: OnceLock<RuntimeSecrets> = OnceLock::new();
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ pub(crate) struct RuntimeSecrets {
|
|||||||
pub(crate) recipient_1: String,
|
pub(crate) recipient_1: String,
|
||||||
pub(crate) recipient_2: String,
|
pub(crate) recipient_2: String,
|
||||||
pub(crate) code1_secret: String,
|
pub(crate) code1_secret: String,
|
||||||
|
pub(crate) initial_live: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init() -> Result<(), String> {
|
pub fn init() -> Result<(), String> {
|
||||||
@@ -54,6 +56,9 @@ impl RuntimeSecrets {
|
|||||||
let recipient_1 = required_recipient(RECIPIENT_1)?;
|
let recipient_1 = required_recipient(RECIPIENT_1)?;
|
||||||
let recipient_2 = required_recipient(RECIPIENT_2)?;
|
let recipient_2 = required_recipient(RECIPIENT_2)?;
|
||||||
let code1_secret = required(CODE1_SECRET)?;
|
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 {
|
if code1_secret.len() < 32 {
|
||||||
return Err(format!("{CODE1_SECRET} must contain at least 32 bytes"));
|
return Err(format!("{CODE1_SECRET} must contain at least 32 bytes"));
|
||||||
@@ -71,6 +76,7 @@ impl RuntimeSecrets {
|
|||||||
recipient_1,
|
recipient_1,
|
||||||
recipient_2,
|
recipient_2,
|
||||||
code1_secret,
|
code1_secret,
|
||||||
|
initial_live,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,3 +104,10 @@ fn is_e164_number(number: &str) -> bool {
|
|||||||
&& !digits.starts_with('0')
|
&& !digits.starts_with('0')
|
||||||
&& digits.bytes().all(|byte| byte.is_ascii_digit())
|
&& 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"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user