use crate::error::LoRaError as Lerr;
use base64::prelude::*;
use std::cmp;
use std::collections::HashMap;
#[derive(Debug, Default, Clone, Eq, PartialEq)]
pub struct Context {
pub name: String,
pub total_chunks: u32,
pub next_chunk: u32,
pub checked_chunks: u32,
pub data: HashMap<u32, String>,
pub resend: Vec<Transfer>,
}
impl Context {
const CHUNK_LEN: usize = 172; //240 max total len
pub fn new_recv(input: &Transfer) -> Result<Self, Lerr> {
if let Transfer::Dts { chunks, name } = input {
Ok(Self {
name: name.to_string(),
total_chunks: *chunks,
next_chunk: 0,
checked_chunks: 0,
data: HashMap::new(),
resend: Vec::new(),
})
} else {
Err(Lerr::TransferTryFromError)
}
}
pub fn new_send(name: &str, data: &[u8]) -> Result<Self, Lerr> {
let data = Self::encode_data(data);
Ok(Self {
name: name.to_string(),
total_chunks: u32::try_from(data.len())?,
next_chunk: 0,
checked_chunks: 0,
data,
resend: Vec::new(),
})
}
pub fn begin_transfer(&self) -> Result<Transfer, Lerr> {
if self.total_chunks == 0 {
return Err(Lerr::TransferInvalid);
}
Ok(Transfer::Dts {
chunks: self.total_chunks,
name: self.name.to_string(),
})
}
pub fn end_transfer(&self) -> Result<Transfer, Lerr> {
if self.total_chunks != self.next_chunk {
return Err(Lerr::TransferInvalid);
}
Ok(Transfer::Dte)
}
pub fn begin_transfer_ack(&self) -> Result<Transfer, Lerr> {
Ok(Transfer::Dtr)
}
pub fn end_transfer_ack(&self) -> Result<Transfer, Lerr> {
Ok(Transfer::Dtd)
}
fn req_all_data_chunks(&mut self) -> Result<(), Lerr> {
self.next_chunk = self.total_chunks;
self.checked_chunks = 0;
if let Some(req) = self.req_data_chunks()? {
self.resend.push(req);
}
Ok(())
}
pub fn req_data_chunks(&mut self) -> Result<Option<Transfer>, Lerr> {
let mut list = Vec::new();
let mut first: Option<u32> = None;
for id in self.checked_chunks..self.next_chunk {
if !self.data.contains_key(&id) {
if first.is_none() {
first = Some(id.saturating_sub(1));
}
list.push(id);
// Limit REQ to 18 chunk_ids due to packet length limitations
if list.len() >= 18 {
break;
}
}
}
self.checked_chunks = if let Some(checked) = first {
checked
} else {
self.next_chunk
};
if list.is_empty() {
Ok(None)
} else {
Ok(Some(Transfer::Req { chunk_ids: list }))
}
}
pub fn is_transfer_complete(&self) -> bool {
for n in 0..self.total_chunks {
if !self.data.contains_key(&n) {
return false;
}
}
true
}
fn encode_data(input: &[u8]) -> HashMap<u32, String> {
let mut offset = 0;
let mut left = input.len();
let mut out = HashMap::new();
let mut id = 0u32;
while left > 0 {
let len = cmp::min(Self::CHUNK_LEN, left);
let buf = &input[offset..offset + len];
out.insert(id, BASE64_STANDARD.encode(buf));
offset = offset.saturating_add(len);
left = left.saturating_sub(len);
id = id.saturating_add(1);
}
out
}
fn decode_data(&self) -> Result<Vec<u8>, Lerr> {
let mut out = Vec::new();
for index in 0..self.total_chunks {
if let Some(data) = self.data.get(&index) {
out.append(
&mut BASE64_STANDARD
.decode(data)
.expect("error decoding")
.to_vec(),
);
} else {
return Err(Lerr::TransferIncomplete);
}
}
Ok(out)
}
pub fn get_next(&mut self) -> Result<Option<Transfer>, Lerr> {
if !self.resend.is_empty() {
let next = self.resend.remove(0);
return Ok(Some(next));
}
if let Some(data) = self.data.get(&self.next_chunk) {
let transfer = Transfer::Dat {
chunk_id: self.next_chunk,
data: data.to_string(),
};
self.next_chunk = self.next_chunk.saturating_add(1);
Ok(Some(transfer))
} else {
Ok(None)
}
}
pub fn get_transfer_data(&self) -> Result<Vec<u8>, Lerr> {
self.decode_data()
}
pub fn process(&mut self, input: &Transfer) -> Result<(), Lerr> {
match input {
Transfer::Dat { chunk_id, data } => {
self.data.insert(*chunk_id, data.to_string());
self.next_chunk = *chunk_id;
}
Transfer::Req { chunk_ids } => {
let mut out = Vec::new();
'check_ids: for id in chunk_ids {
if let Some(data) = self.data.get(id) {
for each in &out {
if let Transfer::Dat { chunk_id, .. } = each {
if chunk_id == id {
continue 'check_ids;
}
}
}
out.push(Transfer::Dat {
chunk_id: *id,
data: data.to_string(),
});
} else {
return Err(Lerr::TransferTryFromError);
}
}
self.resend = out;
return Ok(());
}
Transfer::Dte => self.req_all_data_chunks()?,
_ => (),
};
Ok(())
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Transfer {
Dts { chunks: u32, name: String },
Dat { chunk_id: u32, data: String },
Req { chunk_ids: Vec<u32> },
Dte,
Dtd,
Dtr,
Dtt { data: String },
}
impl From<Transfer> for String {
fn from(val: Transfer) -> String {
match val {
Transfer::Dts { chunks, name } => format!("DTS:{chunks:08x}:{name}"),
Transfer::Dat { chunk_id, data } => format!("DAT:{chunk_id:08x}:{data}"),
Transfer::Req { chunk_ids } => {
let mut s = "REQ".to_string();
for id in chunk_ids {
s.push_str(&format!(":{id:08x}"));
}
s
}
Transfer::Dte => "DTE".to_string(), // Data End - end of sending data
Transfer::Dtd => "DTD".to_string(), // Data Disconnect - end of transfer disconnect
Transfer::Dtr => "DTR".to_string(), // Data Ready - ready to recveive data
Transfer::Dtt { data } => format!("DTT:{data}"),
}
}
}
impl TryFrom<String> for Transfer {
type Error = Lerr;
fn try_from(i: String) -> Result<Self, Lerr> {
if i.len() < 3 {
return Err(Lerr::TransferTryFromError);
}
match &i[..3] {
"DTS" => Self::dts_tryfrom_str(&i),
"DAT" => Self::dat_tryfrom_str(&i),
"REQ" => Self::req_tryfrom_str(&i),
"DTT" => Self::dtt_tryfrom_str(&i),
"DTE" => Ok(Self::Dte),
"DTD" => Ok(Self::Dtd),
"DTR" => Ok(Self::Dtr),
_ => Err(Lerr::TransferTryFromError),
}
}
}
impl Transfer {
fn dtt_tryfrom_str(i: &str) -> Result<Self, Lerr> {
let i = &i[4..];
let data = i.to_string();
Ok(Self::Dtt { data })
}
fn dts_tryfrom_str(i: &str) -> Result<Self, Lerr> {
let i = &i[4..];
let mut parts = i.split(':');
let chunks = if let Some(chunks_str) = parts.next() {
u32::from_str_radix(chunks_str, 16)?
} else {
return Err(Lerr::TransferTryFromError);
};
let name = if let Some(name_str) = parts.next() {
name_str.to_string()
} else {
return Err(Lerr::TransferTryFromError);
};
Ok(Self::Dts { chunks, name })
}
fn dat_tryfrom_str(i: &str) -> Result<Self, Lerr> {
let i = &i[4..];
let mut parts = i.split(':');
let chunk_id = if let Some(chunk_id_str) = parts.next() {
u32::from_str_radix(chunk_id_str, 16)?
} else {
return Err(Lerr::TransferTryFromError);
};
let data = if let Some(data_str) = parts.next() {
data_str.to_string()
} else {
return Err(Lerr::TransferTryFromError);
};
Ok(Self::Dat { chunk_id, data })
}
fn req_tryfrom_str(i: &str) -> Result<Self, Lerr> {
let i = &i[4..];
let parts = i.split(':');
let mut chunk_ids = Vec::new();
for chunk_id in parts {
chunk_ids.push(u32::from_str_radix(chunk_id, 16)?);
}
Ok(Self::Req { chunk_ids })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
pub fn context_transfer() {
let name = "sendfiletransfer.txt";
let origdata = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC";
let ctx = Context::new_send(name, origdata).unwrap();
let mut data = HashMap::new();
data.insert(0u32, "QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJDQw==".to_string());
data.insert(1u32, "Q0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQkJCQg==".to_string());
data.insert(2u32, "QkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkNDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQw==".to_string());
data.insert(3u32, "Q0FBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQw==".to_string());
data.insert(4u32, "Q0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0ND".to_string());
let expected = Context {
name: "sendfiletransfer.txt".to_string(),
total_chunks: 5,
next_chunk: 0,
checked_chunks: 0,
data,
resend: Vec::new(),
};
assert_eq!(ctx, expected);
let outdata = expected.get_transfer_data().unwrap();
assert_eq!(origdata.to_vec(), outdata);
}
#[test]
pub fn context_new_recv() {
let test = "DTS:00000012:filetransfer.txt".to_string();
let ctx = Context::new_recv(&Transfer::try_from(test).unwrap()).unwrap();
let expected = Context {
name: "filetransfer.txt".to_string(),
total_chunks: 18,
next_chunk: 0,
checked_chunks: 0,
data: HashMap::new(),
resend: Vec::new(),
};
assert_eq!(ctx, expected);
}
#[test]
pub fn req_tryfrom_str() {
let test = "REQ:00000080:00000040:00000020:00000010:00000001".to_string();
let expected = Transfer::Req {
chunk_ids: vec![128, 64, 32, 16, 1],
};
assert_eq!(Transfer::try_from(test).unwrap(), expected);
}
#[test]
pub fn dat_tryfrom_str() {
let test = "DAT:00000022:AAAABBBBCCCCDDDDEEEEFFFF".to_string();
let expected = Transfer::Dat {
chunk_id: 34,
data: "AAAABBBBCCCCDDDDEEEEFFFF".to_string(),
};
assert_eq!(Transfer::try_from(test).unwrap(), expected);
}
#[test]
pub fn dts_tryfrom_str() {
let test = "DTS:00000012:filetransfer.txt".to_string();
let expected = Transfer::Dts {
chunks: 18,
name: "filetransfer.txt".to_string(),
};
assert_eq!(Transfer::try_from(test).unwrap(), expected);
}
#[test]
pub fn dtt_tryfrom_str() {
let test = "DTT:This is a text message".to_string();
let expected = Transfer::Dtt {
data: "This is a text message".to_string(),
};
assert_eq!(Transfer::try_from(test.clone()).unwrap(), expected);
assert_eq!(String::from(expected), test);
}
#[test]
pub fn dts() {
let test = Transfer::Dts {
chunks: 64,
name: "filename.txt".to_string(),
};
assert_eq!(Into::<String>::into(test), "DTS:00000040:filename.txt");
}
#[test]
pub fn dat() {
let test = Transfer::Dat {
chunk_id: 128,
data: "DATADATADATADATADATA".to_string(),
};
assert_eq!(
Into::<String>::into(test),
"DAT:00000080:DATADATADATADATADATA"
);
}
#[test]
pub fn req() {
let test = Transfer::Req {
chunk_ids: vec![128, 64, 32, 16, 1],
};
assert_eq!(
Into::<String>::into(test),
"REQ:00000080:00000040:00000020:00000010:00000001".to_string()
);
}
}