Files
benilla-octo/crates/benilla-protocol/tests/auth.rs
T
gromandClaude Opus 5 81d96af35d Everwood: the HD/OctoWoW line of benilla, imported onto upstream
This checkout had no git history at all, so this one commit carries the whole
of the Everwood work as a single import on top of chairmanpow/benilla-octo's
`main` (706cd71). The two trees have genuinely diverged — each has files the
other does not — so this is a fork point, not a catch-up.

Today's work, which is the reason a baseline exists at all:

- Booth previews no longer cast world shadows (`NO_BOOTH_SHADOW`). Portrait
  characters were entering the sun's shadow cascades, so every skinned-rig
  shadow specialization compiled live mid-frame; the hitch detector logged
  "main thread blocked 1316 ms" beside a `compiled LIVE` warning. This was
  the Fashion/Tmog stutter.
- Per-pane dress-up: each `DressUpModel` pane assembles and stages its own
  tried-on look instead of every pane mirroring the live player.
- Item template cache is append-only JSONL written on the IO pool, replacing
  an 11.6 MB whole-map JSON rewrite on the main thread every 10 s.
- `WOW_SYSTEM_TIMING` rebuilt per-thread: it took 2 mutexes per system enter
  and 4 per exit, costing 15 ms a frame (590 fps -> 60) and corrupting the
  measurements it was taken to produce.
- The visibility-churn sweep moved to its own `WOW_FLASH_CHURN` switch, so a
  frame-cost reading no longer pays 2.7 ms a frame for it.

Local state (`benilla-config/`, `logs/`, `target/`) is excluded by the
existing .gitignore; the account credential it holds is not in this commit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
2026-10-15 15:01:14 -06:00

94 lines
3.6 KiB
Rust

//! Login packets are not length-framed, so each realmd reader must consume its packet exactly:
//! these read a back-to-back challenge, proof and realm-list stream from one cursor.
use benilla_protocol::auth;
const N: [u8; 32] = benilla_srp::LARGE_SAFE_PRIME_LITTLE_ENDIAN;
/// The `crc_salt` a mangos-family realmd sends: its fixed `VersionChallenge` bytes.
const CRC_SALT: [u8; 16] = [
0xba, 0xa3, 0x1e, 0x99, 0xa0, 0x0b, 0x21, 0x57, 0xfc, 0x37, 0x3f, 0xb3, 0x69, 0xcd, 0xd2, 0xf1,
];
/// A successful `CMD_AUTH_LOGON_CHALLENGE_Server` body, with its `crc_salt` + `security_flag` tail.
fn challenge_packet(server_public_key: &[u8; 32], salt: &[u8; 32]) -> Vec<u8> {
let mut p = vec![0x00, 0x00, 0x00]; // opcode, protocol_version, result=success
p.extend_from_slice(server_public_key);
p.push(1); // generator length
p.push(7); // generator
p.push(32); // large-safe-prime length
p.extend_from_slice(&N);
p.extend_from_slice(salt);
p.extend_from_slice(&CRC_SALT);
p.push(0); // security_flag = None
p
}
fn proof_packet(server_proof: &[u8; 20]) -> Vec<u8> {
let mut p = vec![0x01, 0x00]; // opcode, result=success
p.extend_from_slice(server_proof);
p.extend_from_slice(&0u32.to_le_bytes()); // hardware_survey_id
p
}
fn realm_list_packet() -> Vec<u8> {
let mut realms = Vec::new();
realms.extend_from_slice(&0u32.to_le_bytes()); // realm_type
realms.push(0); // flag
realms.extend_from_slice(b"Benilla\0");
realms.extend_from_slice(b"127.0.0.1:8085\0");
realms.extend_from_slice(&0.0f32.to_le_bytes()); // population
realms.push(3); // number_of_characters
realms.push(0); // category
realms.push(1); // realm_id
let mut p = vec![0x10]; // opcode
let size = (4 + 1 + realms.len() + 2) as u16; // header_padding + count + realms + footer
p.extend_from_slice(&size.to_le_bytes());
p.extend_from_slice(&0u32.to_le_bytes()); // header_padding
p.push(1); // number_of_realms
p.extend_from_slice(&realms);
p.extend_from_slice(&0u16.to_le_bytes()); // footer_padding
p
}
#[test]
fn logon_read_sequence_stays_aligned() {
let server_public_key: [u8; 32] =
std::array::from_fn(|i| (i as u8).wrapping_mul(7).wrapping_add(9));
let salt: [u8; 32] = std::array::from_fn(|i| (i as u8).wrapping_mul(11).wrapping_add(2));
let server_proof: [u8; 20] =
std::array::from_fn(|i| (i as u8).wrapping_mul(13).wrapping_add(4));
let mut buf = challenge_packet(&server_public_key, &salt);
buf.extend_from_slice(&proof_packet(&server_proof));
buf.extend_from_slice(&realm_list_packet());
let mut stream: &[u8] = &buf;
// The challenge read must consume the `crc_salt` + `security_flag` tail to land on opcode 0x01.
let reply = auth::read_challenge_reply(&mut stream).expect("challenge reply");
assert_eq!(reply.server_public_key, server_public_key);
assert_eq!(reply.generator, 7);
assert_eq!(reply.large_safe_prime, N);
assert_eq!(reply.salt, salt);
assert_eq!(reply.crc_salt, CRC_SALT); // the proof's `crc_hash` is computed from this
let proof =
auth::read_proof_reply(&mut stream).expect("proof reply (stream desynced if this fails)");
assert_eq!(proof, server_proof);
let realms =
auth::read_realm_list(&mut stream).expect("realm list (stream desynced if this fails)");
assert_eq!(realms.len(), 1);
assert_eq!(realms[0].name, "Benilla");
assert_eq!(realms[0].address, "127.0.0.1:8085");
assert_eq!(realms[0].characters, 3);
assert!(
stream.is_empty(),
"{} trailing bytes — a reader over/under-consumed",
stream.len()
);
}