Put the lib into a workspace

This commit is contained in:
2024-06-16 17:48:15 +02:00
parent 0dd05c0d91
commit 35e75745db
8 changed files with 34 additions and 29 deletions

View File

@ -1,6 +0,0 @@
mod r701;
mod record;
mod record_iterator;
pub use r701::R701;
pub use record::{Clock, Record};
pub use record_iterator::RecordIterator;

View File

@ -1,152 +0,0 @@
use crate::RecordIterator;
use std::io::{BufRead, BufReader, Error, ErrorKind::InvalidData, Result, Write};
use std::net::{TcpStream, ToSocketAddrs};
#[derive(Debug)]
pub struct R701 {
tcp_stream: TcpStream,
sequence_number: u16,
}
impl R701 {
pub fn connect(connection_info: impl ToSocketAddrs) -> Result<Self> {
// Create a new R701 struct
let mut new = Self {
tcp_stream: TcpStream::connect(connection_info)?,
sequence_number: 0,
};
// Try to ping the endpoint
new.ping()?;
Ok(new)
}
pub fn request(&mut self, payload: &[u8; 12]) -> Result<Vec<u8>> {
// Create a blank request
let mut request = [0x55, 0xaa, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
// Insert the payload
request[2..14].clone_from_slice(payload);
// Insert the sequence number
request[14..].clone_from_slice(&self.sequence_number.to_le_bytes());
self.sequence_number += 1;
// Send the request
self.tcp_stream.write_all(&request)?;
// Create a buffer and return the response
let mut buffer = BufReader::new(&self.tcp_stream);
Ok(buffer.fill_buf()?.to_vec())
}
pub fn ping(&mut self) -> Result<()> {
// Create a request with a payload of `01 80 00 00 00 00 00 00 00 00 00
// 00` and get the response
let response = self.request(&[0x01, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])?;
// If the response is not `aa 55 01 01 00 00 00 00 00 00` then return an
// error
if response != [0xaa, 0x55, 0x01, 0x01, 0, 0, 0, 0, 0, 0] {
return Err(Error::new(InvalidData, "Malformed response"));
}
Ok(())
}
pub fn get_name(&mut self, id: u32) -> Result<Option<String>> {
// Create a request with a payload of `01 c7 xx xx xx xx 00 00 00 00 14
// 00`, where `xx xx xx xx` is the employee ID in little endian
let mut request = [0x01, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0x14, 0];
request[2..6].clone_from_slice(&id.to_le_bytes());
// Get the response
let response = self.request(&request)?;
// If the response length is right but the header is `01 00 00 00 00 00
// 00 00` then the request is been succesful but the name was not found
if response.len() == 34
&& response[..12] == [0xaa, 0x55, 0x01, 0, 0, 0, 0, 0, 0, 0, 0x55, 0xaa]
{
return Ok(None);
}
// If one between the response length or the response header is wrong
// return an error
if response.len() != 34
|| response[..12] != [0xaa, 0x55, 0x01, 0x01, 0, 0, 0, 0, 0, 0, 0x55, 0xaa]
{
return Err(Error::new(InvalidData, "Malformed response"));
}
// Get the name as a UTF-8 string and delete the `\0` at the end
let name = String::from_utf8_lossy(&response[12..22])
.trim_end_matches('\0')
.to_string();
// Return None if the name is empty, else return the name wrapped into a
// Some
Ok(Some(name).filter(|name| !name.is_empty()))
}
pub fn get_total_record_count(&mut self) -> Result<u16> {
// Create a request with a payload of `01 b4 08 00 00 00 00 00 ff ff 00
// 00` and get the response
let response = self.request(&[0x01, 0xb4, 0x08, 0, 0, 0, 0, 0, 0xff, 0xff, 0, 0])?;
// If one between the response length or the response header is wrong
// return an error
if response.len() != 10
|| response[..4] != [0xaa, 0x55, 0x01, 0x01]
|| response[6..] != [0u8; 4]
{
return Err(Error::new(InvalidData, "Malformed response"));
}
// Convert the payload into a u16 and return it
let mut record_count_le = [0u8; 2];
record_count_le.clone_from_slice(&response[4..6]);
Ok(u16::from_le_bytes(record_count_le))
}
pub fn get_record_bytes(
&mut self,
total_records: u16,
sequence_number: u16,
) -> Result<Vec<u8>> {
// Create a request with a payload of `01 a4 00 00 00 00 xx xx xx xx 00
// 04`, where `xx xx xx xx` changes meaning based on the sequence_number
// value
let mut request: [u8; 12] = [0x01, 0xa4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x04];
// If sequence_number is 0 then `xx xx xx xx` is `xx xx 00 00`, where
// `xx xx` is total_records in little endian
//
// Else `xx xx xx xx` is `00 00 xx xx`, where `xx xx` is sequence_number
// in little endian
if sequence_number == 0 {
request[6..8].clone_from_slice(&total_records.to_le_bytes());
} else {
request[8..10].clone_from_slice(&sequence_number.to_le_bytes());
}
// Get the response
let response = self.request(&request)?;
// If one between the response length, the response header or the last
// two bits is wrong return an error
if response.len() != 1038
|| response[..12] != [0xaa, 0x55, 0x01, 0x01, 0, 0, 0, 0, 0, 0, 0x55, 0xaa]
|| response[1036..] != [0, 0]
{
return Err(Error::new(InvalidData, "Malformed response"));
}
// Return only the payload bits as a vector
Ok(response[12..1036].to_vec())
}
pub fn iter(&mut self) -> Result<RecordIterator> {
RecordIterator::from(self)
}
}

View File

@ -1,121 +0,0 @@
use chrono::{DateTime, Local, TimeZone};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Clock {
#[default]
FirstIn,
FirstOut,
SecondIn,
SecondOut,
}
unsafe impl Send for Clock {}
unsafe impl Sync for Clock {}
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Record {
pub employee_id: u32,
pub clock: Clock,
pub datetime: DateTime<Local>,
}
unsafe impl Send for Record {}
unsafe impl Sync for Record {}
impl TryFrom<&[u8]> for Record {
type Error = &'static str;
fn try_from(record_bytes: &[u8]) -> Result<Self, Self::Error> {
// Return an error if the slice length is less than 12
if record_bytes.len() < 12 {
return Err("Slice must be at least of length 12 to be converted into Record");
}
// Extract the employee ID from the central 4 bytes of the record in
// little endian
let mut employee_id = [0u8; 4];
employee_id.clone_from_slice(&record_bytes[4..8]);
let employee_id = u32::from_le_bytes(employee_id);
// Extract the DateTime from the last 4 bytes of the record in little
// endian
let mut datetime = [0u8; 4];
datetime.clone_from_slice(&record_bytes[8..12]);
let datetime = u32::from_le_bytes(datetime);
// Return a new Record
Ok(Self {
employee_id,
clock: match record_bytes[1] >> 6 {
0 => Clock::FirstIn,
1 => Clock::FirstOut,
2 => Clock::SecondIn,
3 => Clock::SecondOut,
_ => panic!("Math has broken"),
},
datetime: Local
.with_ymd_and_hms(
// DateTime data is in this format:
//
// mmmmmmhh hhhddddd MMMMyyyy yyyyyyyy
//
// Using some bitshifts and some bitwise operations we can
// extract the data and put it into a DateTime struct
(datetime & 0x0fff) as i32,
(datetime >> 12) & 0x0f,
(datetime >> 16) & 0x1f,
(datetime >> 21) & 0x1f,
(datetime >> 26) & 0x3f,
0,
)
.single()
.ok_or("DateTime conversion error")?,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn valid_record_conversion() {
let record_bytes: &[u8] = &[0x10, 0x23, 0x0b, 0x1d, 0x01, 0, 0, 0, 0xb2, 0x17, 0x01, 0];
assert_eq!(
record_bytes.try_into(),
Ok(Record {
employee_id: 1,
clock: Clock::FirstIn,
datetime: Local
.with_ymd_and_hms(1970, 1, 1, 0, 0, 0)
.single()
.unwrap(),
})
)
}
#[test]
fn invalid_length() {
let record_bytes: &[u8] = &[0u8; 11];
assert_eq!(
record_bytes.try_into(),
Err::<Record, &str>("Slice must be at least of length 12 to be converted into Record")
)
}
#[test]
fn invalid_datetime() {
let record_bytes: &[u8] = &[0x10, 0x23, 0x0b, 0x1d, 0x01, 0, 0, 0, 0xb2, 0x17, 0x20, 0];
assert_eq!(
record_bytes.try_into(),
Err::<Record, &str>("DateTime conversion error")
)
}
}

View File

@ -1,82 +0,0 @@
use crate::{Record, R701};
use std::io::Result;
#[derive(Debug)]
pub struct RecordIterator<'a> {
r701: &'a mut R701,
input_buffer: Vec<u8>,
sequence_number: u16,
total_records: u16,
record_count: u16,
}
impl<'a> RecordIterator<'a> {
pub fn from(r701: &'a mut R701) -> Result<Self> {
// Get the total number of records
let total_records = r701.get_total_record_count()?;
// Calculate the sequence number on which the last record resides and
// the index of the first `ff` byte, avoiding overflows
let sequence_number = (total_records as u32 * 12 / 1024) as u16;
let first_useless_byte_index = total_records as usize * 12 % 1024;
// The endpoint expects the first block of records to be sent first
r701.get_record_bytes(total_records, 0)?;
// Get the last records and cut out all the trailing `ff` bytes
let input_buffer = r701
.get_record_bytes(total_records, sequence_number)?
.drain(..first_useless_byte_index)
.collect::<Vec<u8>>();
// Return a new Iterator
Ok(Self {
r701,
input_buffer,
sequence_number,
total_records,
record_count: total_records,
})
}
}
impl<'a> Iterator for RecordIterator<'a> {
type Item = Record;
fn next(&mut self) -> Option<Self::Item> {
// Stop iterating when there are no more records
if self.record_count == 0 {
return None;
}
self.record_count -= 1;
// If the input buffer is almost empty, make another request to the
// endpoint asking for more data
if self.input_buffer.len() < 12 {
// If the buffer is almost empty but the sequence number is already
// zero, stop iterating
if self.sequence_number == 0 {
return None;
}
self.sequence_number -= 1;
// Request new bytes
let bytes = self
.r701
.get_record_bytes(self.total_records, self.sequence_number)
.ok()?;
// Put the bytes at the start of the vector
self.input_buffer.splice(0..0, bytes.iter().copied());
}
// Return a new Record
self.input_buffer
.drain(self.input_buffer.len() - 12..)
.as_slice()
.try_into()
.ok()
}
}