use bloop_protocol::{
ClientMessage, DataHash, Decode, Encode, MessageSet, MessageSetError, NfcUid, Payload,
RawMessage,
};
#[derive(Clone, Debug, Decode, Encode, Eq, Payload, PartialEq)]
#[bloop(opcode = 0x80)]
struct StatusRequest;
#[derive(Clone, Debug, Decode, Encode, Eq, Payload, PartialEq)]
#[bloop(opcode = 0x82)]
struct RegisterTag {
name: String,
nfc_uid: NfcUid,
}
#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq)]
struct StatusEntry {
name: String,
audio_hash: Option<DataHash>,
}
#[derive(Clone, Debug, Decode, Encode, Eq, Payload, PartialEq)]
#[bloop(opcode = 0x81)]
struct StatusReport {
#[bloop(count = u8)]
entries: Vec<StatusEntry>,
#[bloop(count = u32)]
blob: Vec<u8>,
}
#[derive(Clone, Debug, Eq, MessageSet, PartialEq)]
enum TestRequest {
Status(StatusRequest),
RegisterTag(RegisterTag),
}
#[derive(Clone, Debug, Eq, MessageSet, PartialEq)]
enum TestResponse {
Status(StatusReport),
}
#[test]
fn unit_message_round_trips() {
let raw = TestRequest::Status(StatusRequest).encode().unwrap();
assert_eq!(raw.message_type, 0x80);
assert!(raw.payload.is_empty());
assert_eq!(
TestRequest::decode(&raw).unwrap(),
TestRequest::Status(StatusRequest)
);
}
#[test]
fn field_message_has_expected_layout() {
let request = RegisterTag {
name: "ab".to_string(),
nfc_uid: NfcUid::try_from(&[1u8, 2, 3, 4][..]).unwrap(),
};
let raw = TestRequest::RegisterTag(request.clone()).encode().unwrap();
assert_eq!(raw.message_type, 0x82);
assert_eq!(raw.payload, [2, b'a', b'b', 4, 1, 2, 3, 4]);
assert_eq!(
TestRequest::decode(&raw).unwrap(),
TestRequest::RegisterTag(request)
);
}
#[test]
fn counted_fields_use_their_prefix_width() {
let report = StatusReport {
entries: vec![StatusEntry {
name: "x".to_string(),
audio_hash: None,
}],
blob: vec![7, 8],
};
let raw = TestResponse::Status(report.clone()).encode().unwrap();
assert_eq!(raw.message_type, 0x81);
assert_eq!(raw.payload, [1, 1, b'x', 0, 2, 0, 0, 0, 7, 8]);
assert_eq!(
TestResponse::decode(&raw).unwrap(),
TestResponse::Status(report)
);
}
#[test]
fn unknown_opcode_is_reported() {
let raw = RawMessage::new(0x90, vec![]);
let error = TestRequest::decode(&raw).unwrap_err();
assert!(matches!(error, MessageSetError::UnknownOpcode(0x90)));
}
#[test]
fn malformed_payload_is_reported() {
let raw = RawMessage::new(0x82, vec![5, b'a']);
let error = TestRequest::decode(&raw).unwrap_err();
assert!(matches!(
error,
MessageSetError::Malformed { opcode: 0x82, .. }
));
}
#[test]
fn trailing_bytes_are_malformed() {
let raw = RawMessage::new(0x80, vec![1]);
let error = TestRequest::decode(&raw).unwrap_err();
assert!(matches!(
error,
MessageSetError::Malformed { opcode: 0x80, .. }
));
}
#[test]
fn from_impls_are_generated() {
let request: TestRequest = StatusRequest.into();
assert_eq!(request, TestRequest::Status(StatusRequest));
assert_eq!(request.opcode(), 0x80);
}
#[derive(Clone, Debug, Eq, MessageSet, PartialEq)]
enum ComposedResponse<Ext> {
Status(StatusReport),
Custom(Ext),
}
#[test]
fn user_defined_catch_all_delegates() {
let report = StatusReport {
entries: vec![],
blob: vec![],
};
let raw = TestResponse::Status(report.clone()).encode().unwrap();
let decoded = ComposedResponse::<RawMessage>::decode(&raw).unwrap();
assert_eq!(decoded, ComposedResponse::Status(report));
let unknown = RawMessage::new(0x90, vec![1]);
let decoded = ComposedResponse::<RawMessage>::decode(&unknown).unwrap();
assert_eq!(decoded, ComposedResponse::Custom(unknown));
}
#[test]
fn extension_set_plugs_into_the_standard_set() {
let raw = TestRequest::Status(StatusRequest).encode().unwrap();
let decoded = ClientMessage::<TestRequest>::decode(&raw).unwrap();
assert_eq!(
decoded,
ClientMessage::Custom(TestRequest::Status(StatusRequest))
);
let standard = RawMessage::new(0x05, vec![]);
let decoded = ClientMessage::<TestRequest>::decode(&standard).unwrap();
assert!(matches!(decoded, ClientMessage::Ping(_)));
let unknown = RawMessage::new(0x90, vec![]);
let error = ClientMessage::<TestRequest>::decode(&unknown).unwrap_err();
assert!(matches!(error, MessageSetError::UnknownOpcode(0x90)));
}