use std::convert::TryFrom;
#[cfg(not(test))]
use log::debug;
#[cfg(test)]
use std::println as debug;
use arrayvec::ArrayVec;
use super::assembler::Burst;
use crate::{Message, MessageResult};
pub fn combine<'b, B, S>(bursts: B) -> Option<MessageResult>
where
B: IntoIterator<Item = &'b S>,
S: AsRef<[u8]> + 'b + ?Sized,
{
const MIN_BURSTS_FOR_FULL_MESSAGE: u8 = 2;
let (msg, burst_count, bit_errors) = estimate_message(bursts);
if msg.is_empty() {
return None;
}
debug!(
"combiner: combined burst data: \"{}\"",
String::from_utf8_lossy(&msg)
);
let good_msg =
truncate_bytes_with_reference(&msg[..], &burst_count[..], MIN_BURSTS_FOR_FULL_MESSAGE);
match Message::try_from((good_msg, bit_errors.as_slice(), burst_count.as_slice())) {
Ok(parsed) => {
debug!(
"combiner: message ({} voting, {} errors): \"{}\"",
parsed.voting_byte_count(),
parsed.parity_error_count(),
parsed
);
Some(Ok(parsed))
}
Err(err) => {
if message_prefix_is_eom(&msg[..]) {
debug!("combiner: Fast EOM");
Some(Ok(Message::EndOfMessage))
} else if good_msg.is_empty() {
None
} else {
debug!(
"combiner: decode failure ({}): \"{}\"",
err,
String::from_utf8_lossy(&good_msg)
);
Some(Err(err))
}
}
}
}
#[inline]
pub(crate) fn is_allowed_byte(c: u8) -> bool {
const MINUS: u8 = '-' as u8;
const PLUS: u8 = '+' as u8;
const QUESTION_MARK: u8 = '?' as u8;
const OPEN_PARENTHESES: u8 = '(' as u8;
const CLOSE_PARENTHESES: u8 = ')' as u8;
const OPEN_BRACKETS: u8 = '[' as u8;
const CLOSE_BRACKETS: u8 = ']' as u8;
const PERIOD: u8 = '.' as u8;
const UNDERSCORE: u8 = '_' as u8;
const COMMA: u8 = ',' as u8;
const SLASH: u8 = '/' as u8;
const SPACE: u8 = ' ' as u8;
const NUMBERS: [u8; 2] = ['0' as u8, '9' as u8];
const UPPER_ALPHA: [u8; 2] = ['A' as u8, 'Z' as u8];
const LOWER_ALPHA: [u8; 2] = ['a' as u8, 'z' as u8];
c == MINUS
|| (c >= NUMBERS[0] && c <= NUMBERS[1])
|| (c >= UPPER_ALPHA[0] && c <= UPPER_ALPHA[1])
|| (c >= LOWER_ALPHA[0] && c <= LOWER_ALPHA[1])
|| c == SLASH
|| c == QUESTION_MARK
|| c == OPEN_PARENTHESES
|| c == CLOSE_PARENTHESES
|| c == OPEN_BRACKETS
|| c == CLOSE_BRACKETS
|| c == PERIOD
|| c == UNDERSCORE
|| c == COMMA
|| c == PLUS
|| c == SPACE
}
fn estimate_message<'b, B, S>(bursts: B) -> (Burst, Burst, Burst)
where
B: IntoIterator<Item = &'b S>,
S: AsRef<[u8]> + 'b + ?Sized,
{
let mut out_bytes = Burst::default();
let mut out_num_bursts = Burst::default();
let mut out_errs = Burst::default();
let mut byte_iters = ArrayVec::<_, 3>::default();
byte_iters.extend(bursts.into_iter().take(3).map(|b| b.as_ref().into_iter()));
let mut cur_byte_in = ArrayVec::<u8, 3>::default();
while out_bytes.len() < out_bytes.capacity() {
cur_byte_in.clear();
cur_byte_in.extend(byte_iters.iter_mut().filter_map(|itr| itr.next().cloned()));
let mut have_msb_error = false;
for b in &mut cur_byte_in {
have_msb_error |= (*b & 0x80) != 0;
*b = *b & (!0x80);
}
let (est_byte, bit_err_count) = match &cur_byte_in.len() {
0 => break,
1 => (cur_byte_in[0], 0),
2 => bit_vote_detect(cur_byte_in[0], cur_byte_in[1]),
3 => bit_vote_correct(cur_byte_in[0], cur_byte_in[1], cur_byte_in[2]),
_ => unreachable!(),
};
if !is_allowed_byte(est_byte) {
break;
}
out_bytes.push(est_byte);
out_num_bursts.push(cur_byte_in.len() as u8);
out_errs.push(bit_err_count as u8 + have_msb_error as u8);
}
(out_bytes, out_num_bursts, out_errs)
}
#[inline]
fn bit_vote_detect(b0: u8, b1: u8) -> (u8, u32) {
let xor = b0 ^ b1;
(b0 & !(0xff * (xor != 0) as u8), xor.count_ones())
}
#[inline]
fn bit_vote_correct(b0: u8, b1: u8, b2: u8) -> (u8, u32) {
let pair0 = !(b0 ^ b1);
let pair1 = !(b1 ^ b2);
let pair2 = !(b0 ^ b2);
(
(b0 & pair0) | (b2 & pair1) | (b2 & pair2),
(pair0 & pair1 & pair2).count_zeros(),
)
}
#[inline]
fn message_prefix_is_eom(inp: &[u8]) -> bool {
if inp.len() < 2 {
return false;
}
&inp[0..2] == ['N' as u8, 'N' as u8]
}
#[inline]
fn truncate_bytes_with_reference<'a>(src: &'a [u8], compare: &'_ [u8], threshold: u8) -> &'a [u8] {
let mut ind = 0;
for (v, _) in compare.iter().copied().zip(src.iter()) {
if v < threshold {
break;
}
ind += 1;
}
&src[0..ind]
}
#[cfg(test)]
mod tests {
use crate::MessageDecodeErr;
use super::*;
#[test]
fn test_bit_vote_detect() {
assert_eq!(bit_vote_detect(0xab, 0xab), (0xab, 0));
assert_eq!(bit_vote_detect(0xff, 0xff), (0xff, 0));
assert_eq!(bit_vote_detect(0x00, 0x00), (0x00, 0));
assert_eq!(bit_vote_detect(0x00, 0x01), (0x00, 1));
assert_eq!(bit_vote_detect(0x02, 0x01), (0x00, 2));
assert_eq!(bit_vote_detect(0xff, 0xf0), (0x00, 4));
assert_eq!(bit_vote_detect(0x0f, 0xf0), (0x00, 8));
assert_eq!(bit_vote_detect(0xff, 0x00), (0x00, 8));
}
#[test]
fn test_bit_vote_correct() {
assert_eq!(bit_vote_correct(0xab, 0xab, 0xab), (0xab, 0));
assert_eq!(bit_vote_correct(0xff, 0xff, 0xff), (0xff, 0));
assert_eq!(bit_vote_correct(0x00, 0x00, 0x00), (0x00, 0));
assert_eq!(bit_vote_correct(0xaa, 0xab, 0xab), (0xab, 1));
assert_eq!(bit_vote_correct(0xa0, 0xa0, 0xaf), (0xa0, 4));
assert_eq!(bit_vote_correct(0x0f, 0xf0, 0xff), (0xff, 8));
assert_eq!(bit_vote_correct(0x00, 0xf0, 0xff), (0xf0, 8));
assert_eq!(bit_vote_correct(0xaa, 0x55, 0xff), (0xff, 8));
assert_eq!(bit_vote_correct(0xaa, 0x55, 0xa5), (0xa5, 8));
}
#[test]
fn test_truncate_bytes_with_reference() {
assert_eq!("".as_bytes(), truncate_bytes_with_reference(&[], &[], 2));
assert_eq!(
"X".as_bytes(),
truncate_bytes_with_reference("XA".as_bytes(), &[2, 1], 2)
);
assert_eq!(
"XB".as_bytes(),
truncate_bytes_with_reference("XB".as_bytes(), &[3, 2], 2)
);
assert_eq!(
"".as_bytes(),
truncate_bytes_with_reference("ZZ".as_bytes(), &[1, 2], 2)
);
}
#[test]
fn test_estimate_message() {
let (msg, mbur, merr) = estimate_message(["".as_bytes()].iter());
assert!(msg.is_empty());
assert!(mbur.is_empty());
assert!(merr.is_empty());
let (msg, mbur, merr) = estimate_message(["@@".as_bytes(), "".as_bytes()].iter());
assert!(msg.is_empty());
assert!(mbur.is_empty());
assert!(merr.is_empty());
let (msg, mbur, merr) = estimate_message(["HIHI".as_bytes(), "HI".as_bytes()].iter());
let mstr = std::str::from_utf8(msg.as_slice()).unwrap();
assert_eq!("HIHI", mstr);
assert_eq!(&[2u8, 2, 1, 1], mbur.as_slice());
assert_eq!(&[0u8; 4], merr.as_slice());
let (msg, mbur, merr) =
estimate_message(["TEST".as_bytes(), "TESZ".as_bytes(), "".as_bytes()].iter());
let mstr = std::str::from_utf8(msg.as_slice()).unwrap();
assert_eq!("TES", mstr);
assert_eq!(&[2u8, 2, 2], mbur.as_slice());
assert_eq!(&[0u8, 0, 0], merr.as_slice());
let (msg, mbur, merr) =
estimate_message(["NNNN".as_bytes(), "NNNN".as_bytes(), "ZCZC-".as_bytes()].iter());
let mstr = std::str::from_utf8(msg.as_slice()).unwrap();
assert_eq!("NNNN-", mstr);
assert_eq!(&[3u8, 3, 3, 3, 1], mbur.as_slice());
assert_eq!(&[2u8, 3, 2, 3, 0], merr.as_slice());
let (msg, mbur, merr) =
estimate_message(["NNNN".as_bytes(), "NNNNB".as_bytes(), "ZC".as_bytes()].iter());
let mstr = std::str::from_utf8(msg.as_slice()).unwrap();
assert_eq!("NNNNB", mstr);
assert_eq!(&[3u8, 3, 2, 2, 1], mbur.as_slice());
assert_eq!(&[2u8, 3, 0, 0, 0], merr.as_slice());
let (msg, mbur, merr) = estimate_message([&[0xce, 'N' as u8], "NN".as_bytes()].iter());
let mstr = std::str::from_utf8(msg.as_slice()).unwrap();
assert_eq!("NN", mstr);
assert_eq!(&[2u8, 2], mbur.as_slice());
assert_eq!(&[1u8, 0], merr.as_slice());
let (msg, mbur, merr) =
estimate_message([&[0xce, 'N' as u8], "NN".as_bytes(), &['N' as u8, 0xce]].iter());
let mstr = std::str::from_utf8(msg.as_slice()).unwrap();
assert_eq!("NN", mstr);
assert_eq!(&[3u8, 3], mbur.as_slice());
assert_eq!(&[1u8, 1u8], merr.as_slice());
}
#[test]
fn test_combine() {
const MESSAGE: &[u8] = "ZCZC-EAS-DMO-999000+0015-0011122-NOCALL00-".as_bytes();
const CORRUPT: &[u8] = "ZKZK-EAS-DMO-999000+0015-0011122-NOCALL00-".as_bytes();
const GARBAGE: &[u8] = "NOPE".as_bytes();
const FAST_EOM_ONLY: &[u8] = "NNZZ".as_bytes();
assert!(combine([MESSAGE].iter()).is_none());
assert_eq!(
Ok(Message::EndOfMessage),
combine([FAST_EOM_ONLY].iter()).expect("expected fast eom")
);
assert_eq!(
Some(Err(MessageDecodeErr::Malformed)),
combine([MESSAGE, &MESSAGE[0..16]].iter())
);
assert_eq!(
Err(MessageDecodeErr::UnrecognizedPrefix),
combine([GARBAGE, GARBAGE].iter()).expect("expected error")
);
let out = combine([MESSAGE, MESSAGE].iter())
.expect("expect output")
.expect("expected message");
assert_eq!(out.as_str().as_bytes(), MESSAGE);
assert_eq!(out.voting_byte_count(), 0);
let out = combine([MESSAGE, MESSAGE, CORRUPT].iter())
.expect("expect output")
.expect("expected message");
assert_eq!(out.as_str().as_bytes(), MESSAGE);
assert_eq!(out.voting_byte_count(), MESSAGE.len());
assert_eq!(out.parity_error_count(), 2);
let out = combine([FAST_EOM_ONLY, MESSAGE, MESSAGE].iter())
.expect("expect output")
.expect("expected message");
assert_eq!(out.as_str().as_bytes(), MESSAGE);
assert_eq!(out.voting_byte_count(), FAST_EOM_ONLY.len());
}
}