diff --git a/core/src/protocol.rs b/core/src/protocol.rs index f8b855e..ec97936 100644 --- a/core/src/protocol.rs +++ b/core/src/protocol.rs @@ -19,16 +19,20 @@ use crate::world::TICKS_PER_SECOND; /// Version of the messages. Raise it whenever a message changes. pub const PROTOCOL_VERSION: u32 = 1; /// Longest message a guest may send, in bytes. The longest one is a greeting -/// with a name of full length, which needs 65 bytes. +/// with a name of full length, which needs 65 bytes. Greetings of every +/// future version must fit as well: a longer one is rejected unread instead +/// of being recognised as another version. pub const MAX_GUEST_MESSAGE_BYTES: usize = 128; /// Ticks between two state checksums in the stream of ticks: one second. pub const CHECK_INTERVAL_TICKS: u64 = TICKS_PER_SECOND as u64; +// Never changes, see the module documentation. const TO_HOST_HELLO: u8 = 1; const TO_HOST_ACT: u8 = 2; const TO_HOST_DIVERGED: u8 = 3; const TO_GUEST_WORLD: u8 = 1; +// Never changes, see the module documentation. const TO_GUEST_REFUSED: u8 = 2; const TO_GUEST_TICK: u8 = 3; @@ -75,7 +79,8 @@ impl Action { } } -/// Why a host turned a guest away. +/// Why a host turned a guest away. The codes never change, because games of +/// other versions read them. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Refusal { /// The guest's game speaks another version of these messages. diff --git a/core/tests/golden.rs b/core/tests/golden.rs index f87f09e..684c376 100644 --- a/core/tests/golden.rs +++ b/core/tests/golden.rs @@ -12,7 +12,7 @@ use grovewright_core::command::Command; use grovewright_core::hash::fnv1a64; use grovewright_core::map::{Map, Pos, Terrain, Tile}; use grovewright_core::player::PlayerId; -use grovewright_core::protocol::{Action, Refusal, ToGuest, ToHost}; +use grovewright_core::protocol::{Action, Refusal, ToGuest, ToHost, PROTOCOL_VERSION}; use grovewright_core::replay::Recording; use grovewright_core::rules::{ end_hash, frozen_offline_script, frozen_script, rules_id, RULES_SEED, @@ -162,6 +162,45 @@ fn the_protocol_format_is_unchanged() { fnv1a64(&bytes), 0xc263_69c0_f892_247b, "the messages between host and guests changed; raise \ - PROTOCOL_VERSION in core/src/protocol.rs; then update this value" + PROTOCOL_VERSION in core/src/protocol.rs; then update this value. \ + The start of a greeting and the refusal must never change, see \ + the_greeting_start_and_the_refusal_never_change" ); } + +/// Covers the promise that games of every version can still tell each other +/// that they do not fit: a greeting starts with its tag and the version, and +/// a refusal is its tag and the code of its reason. Unlike every other part +/// of the messages, these stay the same when PROTOCOL_VERSION is raised. +#[test] +fn the_greeting_start_and_the_refusal_never_change() { + let hello = ToHost::Hello { + rules: 1, + key: [3; 16], + name: "Ada".to_owned(), + } + .encode(); + let mut start = vec![1]; + start.extend(PROTOCOL_VERSION.to_le_bytes()); + assert_eq!( + hello[..start.len()], + start[..], + "a greeting starts with the tag 1 and the version; this never \ + changes, not even with a new PROTOCOL_VERSION" + ); + let refusals = [ + (Refusal::OtherProtocol, 1), + (Refusal::OtherRules, 2), + (Refusal::WorldFull, 3), + (Refusal::TooManyPlayers, 4), + (Refusal::AlreadyPlaying, 5), + ]; + for (refusal, code) in refusals { + assert_eq!( + ToGuest::Refused(refusal).encode(), + vec![2, code], + "a refusal is the tag 2 and the code of its reason; this never \ + changes, not even with a new PROTOCOL_VERSION" + ); + } +}