Nachrichten zwischen Host und Gästen
Gäste grüßen mit Protokollversion, Regel-Kennung, Schlüssel und Name und schicken danach nur Aktionen ohne Spielernummer. Der Host schickt seine Welt, Ablehnungen mit Grund und jeden Takt mit seinen Befehlen, jede Sekunde mit einer Prüfsumme. Ein Gruß einer anderen Version bleibt erkennbar, und das Format ist in den festgeschriebenen Werten verankert. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
use grovewright_core::codec::{DecodeError, Writer};
|
||||
use grovewright_core::command::Command;
|
||||
use grovewright_core::map::Pos;
|
||||
use grovewright_core::player::{PlayerId, MAX_NAME_BYTES};
|
||||
use grovewright_core::protocol::{
|
||||
Action, Refusal, ToGuest, ToHost, MAX_GUEST_MESSAGE_BYTES, PROTOCOL_VERSION,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn every_message_to_the_host_survives_encoding() {
|
||||
let messages = [
|
||||
ToHost::Hello {
|
||||
rules: 0x0123_4567_89ab_cdef,
|
||||
key: [9; 16],
|
||||
name: "Ada".to_owned(),
|
||||
},
|
||||
ToHost::Act(Action::Dig {
|
||||
pos: Pos::new(-3, 70),
|
||||
}),
|
||||
ToHost::Diverged {
|
||||
tick: 1_234,
|
||||
hash: u64::MAX,
|
||||
},
|
||||
];
|
||||
for message in &messages {
|
||||
assert_eq!(&ToHost::decode(&message.encode()).unwrap(), message);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_message_to_a_guest_survives_encoding() {
|
||||
let messages = [
|
||||
ToGuest::World {
|
||||
snapshot: vec![1, 2, 3],
|
||||
},
|
||||
ToGuest::Refused(Refusal::TooManyPlayers),
|
||||
ToGuest::Tick {
|
||||
tick: 99,
|
||||
commands: vec![
|
||||
Command::Leave {
|
||||
player: PlayerId(2),
|
||||
},
|
||||
Command::Dig {
|
||||
player: PlayerId(0),
|
||||
pos: Pos::new(4, 5),
|
||||
},
|
||||
],
|
||||
check: Some(7),
|
||||
},
|
||||
ToGuest::Tick {
|
||||
tick: 100,
|
||||
commands: vec![],
|
||||
check: None,
|
||||
},
|
||||
];
|
||||
for message in &messages {
|
||||
assert_eq!(&ToGuest::decode(&message.encode()).unwrap(), message);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_action_becomes_a_command_for_the_given_player() {
|
||||
let action = Action::Dig {
|
||||
pos: Pos::new(1, 2),
|
||||
};
|
||||
assert_eq!(
|
||||
action.command(PlayerId(3)),
|
||||
Command::Dig {
|
||||
player: PlayerId(3),
|
||||
pos: Pos::new(1, 2)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_greeting_in_another_protocol_version_can_still_be_recognised() {
|
||||
let mut w = Writer::new();
|
||||
w.u8(1); // the tag of a greeting, which never changes
|
||||
w.u32(PROTOCOL_VERSION + 1);
|
||||
w.raw(b"whatever the other version sends after its version");
|
||||
assert_eq!(
|
||||
ToHost::decode(&w.into_bytes()),
|
||||
Ok(ToHost::ForeignHello {
|
||||
protocol: PROTOCOL_VERSION + 1
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_refusal_keeps_its_code() {
|
||||
let codes = [
|
||||
(Refusal::OtherProtocol, 1),
|
||||
(Refusal::OtherRules, 2),
|
||||
(Refusal::WorldFull, 3),
|
||||
(Refusal::TooManyPlayers, 4),
|
||||
(Refusal::AlreadyPlaying, 5),
|
||||
];
|
||||
for (refusal, code) in codes {
|
||||
assert_eq!(refusal.code(), code);
|
||||
assert_eq!(Refusal::from_code(code), Some(refusal));
|
||||
}
|
||||
assert_eq!(Refusal::from_code(0), None);
|
||||
assert_eq!(Refusal::from_code(6), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_greeting_shortens_an_overlong_name() {
|
||||
let hello = ToHost::Hello {
|
||||
rules: 1,
|
||||
key: [1; 16],
|
||||
name: "x".repeat(MAX_NAME_BYTES + 5),
|
||||
};
|
||||
assert_eq!(
|
||||
ToHost::decode(&hello.encode()),
|
||||
Ok(ToHost::Hello {
|
||||
rules: 1,
|
||||
key: [1; 16],
|
||||
name: "x".repeat(MAX_NAME_BYTES),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_greeting_with_an_overlong_name_written_by_hand_is_rejected() {
|
||||
let mut w = Writer::new();
|
||||
w.u8(1);
|
||||
w.u32(PROTOCOL_VERSION);
|
||||
w.u64(1);
|
||||
w.raw(&[1; 16]);
|
||||
w.str(&"x".repeat(MAX_NAME_BYTES + 1));
|
||||
assert_eq!(
|
||||
ToHost::decode(&w.into_bytes()),
|
||||
Err(DecodeError::InvalidValue("player name"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn broken_messages_are_rejected() {
|
||||
assert_eq!(ToHost::decode(&[]), Err(DecodeError::UnexpectedEnd));
|
||||
assert_eq!(
|
||||
ToHost::decode(&[0xEE]),
|
||||
Err(DecodeError::InvalidValue("message tag"))
|
||||
);
|
||||
assert_eq!(
|
||||
ToHost::decode(&[2, 0xEE]),
|
||||
Err(DecodeError::InvalidValue("action tag"))
|
||||
);
|
||||
assert_eq!(
|
||||
ToHost::decode(&[2; MAX_GUEST_MESSAGE_BYTES + 1]),
|
||||
Err(DecodeError::InvalidValue("message length"))
|
||||
);
|
||||
let mut act = ToHost::Act(Action::Dig {
|
||||
pos: Pos::new(0, 0),
|
||||
})
|
||||
.encode();
|
||||
act.push(0);
|
||||
assert_eq!(ToHost::decode(&act), Err(DecodeError::TrailingBytes));
|
||||
|
||||
assert_eq!(
|
||||
ToGuest::decode(&[0xEE]),
|
||||
Err(DecodeError::InvalidValue("message tag"))
|
||||
);
|
||||
assert_eq!(
|
||||
ToGuest::decode(&[2, 99]),
|
||||
Err(DecodeError::InvalidValue("refusal"))
|
||||
);
|
||||
let tick = ToGuest::Tick {
|
||||
tick: 1,
|
||||
commands: vec![],
|
||||
check: Some(5),
|
||||
}
|
||||
.encode();
|
||||
assert_eq!(
|
||||
ToGuest::decode(&tick[..tick.len() - 1]),
|
||||
Err(DecodeError::UnexpectedEnd)
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user