fix(starter-rust): fix compilation errors in Rust starter kit

- Add Clone derive to AppState for axum compatibility
- Import Digest trait from sha2 for hash computation
- Use String instead of &str in response headers for lifetime safety
- Add Position import to grid.rs module
- Make Position Copy for easier cloning
- Replace constant_time_eq with custom hmac_equal function
- Add musl-dev to Dockerfile for Alpine build compatibility

The Rust starter kit now compiles and builds successfully with
cargo check and Docker, matching the requirements from plan §5.3
and §12 (Phase 2).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
jedarden 2026-05-08 10:17:53 -04:00
parent 7694723758
commit b60b103c0f
4 changed files with 23 additions and 16 deletions

View file

@ -1 +1 @@
b31c306013e5d99cdf1224c14c7ff887139f187d
dff235e19318f50109a3f29edcd1f01b6cc94857

View file

@ -4,7 +4,7 @@ WORKDIR /app
COPY Cargo.toml ./
COPY src ./src
RUN cargo build --release
RUN apk add --no-cache musl-dev && cargo build --release
FROM alpine:3.21

View file

@ -3,6 +3,7 @@
//! Provides toroidal distance calculations, neighbor enumeration,
//! and BFS pathfinding on a wrapping grid.
use crate::Position;
use std::collections::{HashMap, VecDeque};
/// Manhattan distance with wrap-around on a toroidal grid.

View file

@ -10,11 +10,11 @@ use axum::{
extract::State,
http::{HeaderMap, StatusCode},
routing::{get, post},
Json, Router,
Router,
};
use hmac::{Hmac, Mac};
use serde::{Deserialize, Serialize};
use sha2::Sha256;
use sha2::{Digest, Sha256};
use std::env;
type HmacSha256 = Hmac<Sha256>;
@ -57,7 +57,7 @@ struct You {
score: u32,
}
#[derive(Deserialize, Serialize, Clone)]
#[derive(Deserialize, Serialize, Clone, Copy, PartialEq, Eq)]
pub struct Position {
pub row: u32,
pub col: u32,
@ -87,6 +87,7 @@ struct Move {
direction: String,
}
#[derive(Clone)]
struct AppState {
secret: String,
}
@ -117,7 +118,7 @@ async fn handle_turn(
State(state): State<AppState>,
headers: HeaderMap,
body: Bytes,
) -> Result<(StatusCode, [(&str, String); 2], String), StatusCode> {
) -> Result<(StatusCode, [(String, String); 2], String), StatusCode> {
let signature = headers
.get("X-ACB-Signature")
.and_then(|v| v.to_str().ok())
@ -171,8 +172,8 @@ async fn handle_turn(
Ok((
StatusCode::OK,
[
("Content-Type".to_string(), "application/json".to_string()),
("X-ACB-Signature".to_string(), response_sig),
("Content-Type".to_owned(), "application/json".to_owned()),
("X-ACB-Signature".to_owned(), response_sig),
],
response_body,
))
@ -241,7 +242,6 @@ fn verify_signature(
body: &[u8],
signature: &str,
) -> bool {
use hex::FromHex;
let body_hash = sha2::Sha256::digest(body);
let signing_string = format!(
"{}.{}.{}.{}",
@ -254,15 +254,21 @@ fn verify_signature(
let mut mac =
HmacSha256::new_from_slice(secret.as_bytes()).expect("HMAC key error");
mac.update(signing_string.as_bytes());
let expected = mac.finalize().into_bytes();
let expected = hex::encode(mac.finalize().into_bytes());
match Vec::from_hex(signature) {
Ok(sig_bytes) => {
let sig_truncated: &[u8] = &sig_bytes;
hmac::digest::constant_time_eq(sig_truncated, &expected)
}
Err(_) => false,
hmac_equal(signature, &expected)
}
/// Constant-time string comparison
fn hmac_equal(a: &str, b: &str) -> bool {
if a.len() != b.len() {
return false;
}
a.as_bytes()
.iter()
.zip(b.as_bytes().iter())
.fold(0, |acc, (x, y)| acc | (x ^ y))
== 0
}
fn sign_response(secret: &str, match_id: &str, turn: u32, body: &[u8]) -> String {