//! 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 { 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 { 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 { 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() ); }