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) ); }