use bstr::BString;
use chrono::{Datelike, Local, NaiveTime};
use super::{PCBoardError, convert_pcboard_str};
use crate::{
pcboard::{DATE_LEN, FROM_TO_LEN, PASSWORD_LEN, TIME_LEN},
util::basic_real::BasicReal,
};
use std::io::Read;
#[derive(Clone, Debug)]
pub struct PCBoardMessageHeader {
pub status: u8,
pub msg_number: u32,
pub reply_to: u32,
pub num_blocks: u8,
pub date_time: String,
pub to_field: BString,
pub reply_date: u32,
pub reply_time: String,
pub reply_status: u8,
pub from_field: BString,
pub subj_field: BString,
pub password: BString,
pub active_flag: u8,
pub echo_flag: u8,
pub reserved: [u8; 4],
pub extended_status: u8,
pub net_tag: u8,
}
mod extended_status {
pub const TO: u8 = 1;
pub const FROM: u8 = 2;
pub const SUBJ: u8 = 4;
pub const LIST: u8 = 8;
pub const ATTACH: u8 = 16;
pub const REQRR: u8 = 64;
}
pub const MSG_ACTIVE: u8 = 225;
pub const MSG_INACTIVE: u8 = 226;
pub const ECHO: u8 = b'E';
pub const NOECHO: u8 = b' ';
pub const REPLIED: u8 = b'R';
pub const NOT_REPLIED: u8 = b' ';
impl PCBoardMessageHeader {
pub const HEADER_SIZE: usize = 128;
pub fn date_time(&self) -> chrono::naive::NaiveDateTime {
chrono::NaiveDateTime::parse_from_str(&self.date_time, "%m-%d-%y%H:%M").unwrap_or_default()
}
pub fn reply_date_time(&self) -> chrono::naive::NaiveDateTime {
let time = NaiveTime::parse_from_str(&self.reply_time, "%H:%M").unwrap_or_default();
let now = Local::now();
let century = (now.year() / 100) * 100;
let mut year = (self.reply_date / 10000) as i32 + century;
if year > now.year() {
year -= 100;
}
let month = (self.reply_date % 10000) / 100;
let day = self.reply_date % 100;
let date = chrono::NaiveDate::from_ymd_opt(year, month, day).unwrap_or_default();
chrono::NaiveDateTime::new(date, time)
}
pub fn has_to(&self) -> bool {
self.extended_status & extended_status::TO != 0
}
pub fn has_from(&self) -> bool {
self.extended_status & extended_status::FROM != 0
}
pub fn has_subject(&self) -> bool {
self.extended_status & extended_status::SUBJ != 0
}
pub fn has_list(&self) -> bool {
self.extended_status & extended_status::LIST != 0
}
pub fn has_attach(&self) -> bool {
self.extended_status & extended_status::ATTACH != 0
}
pub fn has_reqrr(&self) -> bool {
self.extended_status & extended_status::REQRR != 0
}
pub fn replied(&self) -> bool {
self.reply_status == REPLIED
}
pub fn read<R: Read>(file: &mut R) -> crate::Result<Self> {
let data = &mut [0; Self::HEADER_SIZE];
file.read_exact(data)?;
let mut data = &data[..];
convert_u8!(status, data);
convert_u32!(msg_number, data);
let msg_number = BasicReal::from(msg_number.to_le_bytes()).into();
convert_u32!(ref_number, data);
let ref_number = BasicReal::from(ref_number.to_le_bytes()).into();
convert_u8!(num_blocks, data);
convert_to_string!(date_time, data, DATE_LEN + TIME_LEN);
convert_to_string!(to_field, data, FROM_TO_LEN);
convert_u32!(reply_date, data);
let reply_date = BasicReal::from(reply_date.to_le_bytes()).into();
convert_to_string!(reply_time, data, TIME_LEN);
convert_u8!(reply_status, data);
convert_to_string!(from_field, data, FROM_TO_LEN);
convert_to_string!(subj_field, data, FROM_TO_LEN);
convert_to_string!(password, data, PASSWORD_LEN);
convert_u8!(active_flag, data);
convert_u8!(echo_flag, data);
let reserved = [data[0], data[1], data[2], data[3]];
data = &data[4..];
convert_u8!(extended_status, data);
convert_u8!(net_tag, data);
Ok(Self {
status,
msg_number,
reply_to: ref_number,
num_blocks,
date_time: date_time.to_string(),
to_field,
reply_date,
reply_time: reply_time.to_string(),
reply_status,
from_field,
subj_field,
password,
active_flag,
echo_flag,
reserved,
extended_status,
net_tag,
})
}
pub(crate) fn is_deleted(&self) -> bool {
self.active_flag != MSG_ACTIVE
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum ExtendedHeaderInformation {
To,
From,
Subject,
Attach,
List,
Route,
Origin,
Reqrr,
Ackrr,
Ackname,
Packout,
To2,
From2,
Forward,
Ufollow,
Unewsgr,
}
const TO: &[u8; 7] = b"TO ";
const FROM: &[u8; 7] = b"FROM ";
const SUBJECT: &[u8; 7] = b"SUBJECT";
const ATTACH: &[u8; 7] = b"ATTACH ";
const LIST: &[u8; 7] = b"LIST ";
const ROUTE: &[u8; 7] = b"ROUTE ";
const ORIGIN: &[u8; 7] = b"ORIGIN ";
const REQRR: &[u8; 7] = b"REQRR ";
const ACKRR: &[u8; 7] = b"ACKRR ";
const ACKNAME: &[u8; 7] = b"ACKNAME";
const PACKOUT: &[u8; 7] = b"PACKOUT";
const TO2: &[u8; 7] = b"TO2 ";
const FROM2: &[u8; 7] = b"FROM2 ";
const FORWARD: &[u8; 7] = b"FORWARD";
const UFOLLOW: &[u8; 7] = b"UFOLLOW";
const UNEWSGR: &[u8; 7] = b"UNEWSGR";
impl ExtendedHeaderInformation {
fn from_data(data: &[u8]) -> crate::Result<Self> {
if *data == *TO {
return Ok(Self::To);
}
if *data == *FROM {
return Ok(Self::From);
}
if *data == *SUBJECT {
return Ok(Self::Subject);
}
if *data == *ATTACH {
return Ok(Self::Attach);
}
if *data == *LIST {
return Ok(Self::List);
}
if *data == *ROUTE {
return Ok(Self::Route);
}
if *data == *ORIGIN {
return Ok(Self::Origin);
}
if *data == *REQRR {
return Ok(Self::Reqrr);
}
if *data == *ACKRR {
return Ok(Self::Ackrr);
}
if *data == *ACKNAME {
return Ok(Self::Ackname);
}
if *data == *PACKOUT {
return Ok(Self::Packout);
}
if *data == *TO2 {
return Ok(Self::To2);
}
if *data == *FROM2 {
return Ok(Self::From2);
}
if *data == *FORWARD {
return Ok(Self::Forward);
}
if *data == *UFOLLOW {
return Ok(Self::Ufollow);
}
if *data == *UNEWSGR {
return Ok(Self::Unewsgr);
}
Err(PCBoardError::UnknownExtendedHeader(BString::new(data.to_vec())).into())
}
pub fn as_str(&self) -> &'static str {
match self {
Self::To => "TO ",
Self::From => "FROM ",
Self::Subject => "SUBJECT",
Self::Attach => "ATTACH ",
Self::List => "LIST ",
Self::Route => "ROUTE ",
Self::Origin => "ORIGIN ",
Self::Reqrr => "REQRR ",
Self::Ackrr => "ACKRR ",
Self::Ackname => "ACKNAME",
Self::Packout => "PACKOUT",
Self::To2 => "TO2 ",
Self::From2 => "FROM2 ",
Self::Forward => "FORWARD",
Self::Ufollow => "UFOLLOW",
Self::Unewsgr => "UNEWSGR",
}
}
}
pub struct PCBoardExtendedHeader {
pub info: ExtendedHeaderInformation,
pub content: BString,
pub status: u8,
}
impl PCBoardExtendedHeader {
const FUNC_LEN: usize = 7;
const DESC_LEN: usize = 60;
pub(crate) const RECORD_SIZE: usize = 0x48;
pub fn is_read(&self) -> bool {
self.status == b'R'
}
pub fn deserialize(buf: &[u8]) -> crate::Result<Self> {
let mut i = 2;
if buf.len() < i + Self::FUNC_LEN + 1 + Self::DESC_LEN + 1 {
return Err(crate::Error::pcboard(
0,
"extended header record is too short",
));
}
let function = ExtendedHeaderInformation::from_data(&buf[i..i + Self::FUNC_LEN])?;
i += Self::FUNC_LEN + 1;
let content = convert_pcboard_str(&buf[i..i + Self::DESC_LEN]);
i += Self::DESC_LEN;
let status = buf[i];
Ok(Self {
info: function,
content,
status,
})
}
}