Added the Record struct
This commit is contained in:
149
src/lib.rs
149
src/lib.rs
@ -1,145 +1,4 @@
|
||||
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 lenght 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[..12] == [0xaa, 0x55, 0x01, 0, 0, 0, 0, 0, 0, 0, 0x55, 0xaa]
|
||||
&& response.len() == 34
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// If one between the response length or the response header is wrong
|
||||
// return an error
|
||||
if response[..12] != [0xaa, 0x55, 0x01, 0x01, 0, 0, 0, 0, 0, 0, 0x55, 0xaa]
|
||||
|| response.len() != 34
|
||||
{
|
||||
return Err(Error::new(InvalidData, "Malformed response"));
|
||||
}
|
||||
|
||||
// Get the name as a UTF-8 string and delete the `\0` at the end
|
||||
Ok(Some(
|
||||
String::from_utf8_lossy(&response[12..22])
|
||||
.trim_end_matches(char::from(0))
|
||||
.to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
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[..4] != [0xaa, 0x55, 0x01, 0x01]
|
||||
|| response[6..] != [0u8; 4]
|
||||
|| response.len() != 10
|
||||
{
|
||||
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[..12] != [0xaa, 0x55, 0x01, 0x01, 0, 0, 0, 0, 0, 0, 0x55, 0xaa]
|
||||
|| response[1036..] != [0, 0]
|
||||
|| response.len() != 1038
|
||||
{
|
||||
return Err(Error::new(InvalidData, "Malformed response"));
|
||||
}
|
||||
|
||||
// Return only the payload bits as a vector
|
||||
Ok(response[12..response.len() - 2].to_vec())
|
||||
}
|
||||
}
|
||||
mod r701;
|
||||
mod record;
|
||||
pub use r701::R701;
|
||||
pub use record::{Clock, Record};
|
||||
|
@ -1,5 +0,0 @@
|
||||
use r701::R701;
|
||||
|
||||
fn main() {
|
||||
let _r701 = R701::connect("127.0.0.1:5005").unwrap();
|
||||
}
|
145
src/r701.rs
Normal file
145
src/r701.rs
Normal file
@ -0,0 +1,145 @@
|
||||
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[..12] == [0xaa, 0x55, 0x01, 0, 0, 0, 0, 0, 0, 0, 0x55, 0xaa]
|
||||
&& response.len() == 34
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// If one between the response length or the response header is wrong
|
||||
// return an error
|
||||
if response[..12] != [0xaa, 0x55, 0x01, 0x01, 0, 0, 0, 0, 0, 0, 0x55, 0xaa]
|
||||
|| response.len() != 34
|
||||
{
|
||||
return Err(Error::new(InvalidData, "Malformed response"));
|
||||
}
|
||||
|
||||
// Get the name as a UTF-8 string and delete the `\0` at the end
|
||||
Ok(Some(
|
||||
String::from_utf8_lossy(&response[12..22])
|
||||
.trim_end_matches(char::from(0))
|
||||
.to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
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[..4] != [0xaa, 0x55, 0x01, 0x01]
|
||||
|| response[6..] != [0u8; 4]
|
||||
|| response.len() != 10
|
||||
{
|
||||
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[..12] != [0xaa, 0x55, 0x01, 0x01, 0, 0, 0, 0, 0, 0, 0x55, 0xaa]
|
||||
|| response[1036..] != [0, 0]
|
||||
|| response.len() != 1038
|
||||
{
|
||||
return Err(Error::new(InvalidData, "Malformed response"));
|
||||
}
|
||||
|
||||
// Return only the payload bits as a vector
|
||||
Ok(response[12..response.len() - 2].to_vec())
|
||||
}
|
||||
}
|
117
src/record.rs
Normal file
117
src/record.rs
Normal file
@ -0,0 +1,117 @@
|
||||
use chrono::{DateTime, Local, TimeZone};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
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)]
|
||||
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")
|
||||
)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user