Removed Nix and the sample-main, added a CI/CD pipeline and updated the README.md
Some checks failed
Check, format and test / build (push) Failing after 49s

This commit is contained in:
Nicola Belluti 2024-07-31 12:00:34 +02:00
parent 987af19ec8
commit b2b5f0c66c
13 changed files with 125 additions and 139 deletions

View File

@ -0,0 +1,31 @@
name: Check, format and test
on:
push:
jobs:
build:
container: docker.io/rust:1.79.0-alpine3.20
steps:
- name: Install the dependencies
run: |
rustup component add clippy rustfmt &&
apk update &&
apk add musl-dev git npm
- name: Checkout the code
uses: actions/checkout@v3
with:
submodules: recursive
- name: Check if the code compiles
run: cargo check
- name: Check if the tests run correctly
run: cargo test
- name: Check if the code is formatted correctly
run: cargo fmt --check
- name: Check if Clippy has someting to say
run: cargo clippy --all-targets

8
Cargo.lock generated
View File

@ -145,14 +145,6 @@ dependencies = [
"serde",
]
[[package]]
name = "sample-main"
version = "0.1.0"
dependencies = [
"chrono",
"r701",
]
[[package]]
name = "serde"
version = "1.0.203"

View File

@ -1,6 +1,22 @@
[workspace]
resolver = "2"
members = [
"r701",
"sample-main",
]
[package]
name = "r701"
version = "0.1.0"
edition = "2021"
license = "GNU AGPLv3.0"
repository = "https://git.nicolabelluti.me/nicolabelluti/r701"
[dependencies]
chrono = { version = "0.4.38", default-features = false, features = ["clock"] }
serde = { version = "1.0.203", default-features = false, features = ["derive"], optional = true }
[features]
serde = ["chrono/serde", "dep:serde"]
[lints.rust]
unsafe_code = "forbid"
[lints.clippy]
unwrap_used = "deny"
enum_glob_use = { level = "deny", priority = 1 }
pedantic = { level = "deny", priority = -1 }
nursery = { level = "deny", priority = -1 }

View File

@ -1,8 +1,60 @@
# R701 🦀
<div align="center">
# R701 🕰️
[![Rust](https://img.shields.io/badge/Rust-f74c00?logo=rust)](https://www.rust-lang.org)
[![Brain made](https://img.shields.io/badge/Brainmade-grey?logo=data:image/svg%2bxml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgd2lkdGg9IjY2LjUzOCIKICAgaGVpZ2h0PSI3OC43ODIiCiAgIGZpbGw9Im5vbmUiCiAgIHZlcnNpb249IjEuMSIKICAgaWQ9InN2ZzMiCiAgIHNvZGlwb2RpOmRvY25hbWU9IndoaXRlLWxvZ28taGVhZDMuc3ZnIgogICBpbmtzY2FwZTp2ZXJzaW9uPSIxLjMuMiAoMDkxZTIwZWYwZiwgMjAyMy0xMS0yNSwgY3VzdG9tKSIKICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiCiAgIHhtbG5zOnNvZGlwb2RpPSJodHRwOi8vc29kaXBvZGkuc291cmNlZm9yZ2UubmV0L0RURC9zb2RpcG9kaS0wLmR0ZCIKICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8c29kaXBvZGk6bmFtZWR2aWV3CiAgICAgaWQ9Im5hbWVkdmlldzMiCiAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIgogICAgIGJvcmRlcmNvbG9yPSIjMDAwMDAwIgogICAgIGJvcmRlcm9wYWNpdHk9IjAuMjUiCiAgICAgaW5rc2NhcGU6c2hvd3BhZ2VzaGFkb3c9IjIiCiAgICAgaW5rc2NhcGU6cGFnZW9wYWNpdHk9IjAuMCIKICAgICBpbmtzY2FwZTpwYWdlY2hlY2tlcmJvYXJkPSIwIgogICAgIGlua3NjYXBlOmRlc2tjb2xvcj0iI2QxZDFkMSIKICAgICBpbmtzY2FwZTp6b29tPSIxMC43NjM4OCIKICAgICBpbmtzY2FwZTpjeD0iMzMuMjU5MzgxIgogICAgIGlua3NjYXBlOmN5PSIzOS4zOTA5OTkiCiAgICAgaW5rc2NhcGU6d2luZG93LXdpZHRoPSIxOTIwIgogICAgIGlua3NjYXBlOndpbmRvdy1oZWlnaHQ9IjEwNTIiCiAgICAgaW5rc2NhcGU6d2luZG93LXg9IjAiCiAgICAgaW5rc2NhcGU6d2luZG93LXk9IjAiCiAgICAgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMSIKICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJzdmczIiAvPgogIDxnCiAgICAgY2xpcC1wYXRoPSJ1cmwoI2EpIgogICAgIHN0eWxlPSJmaWxsOiNmZmZmZmYiCiAgICAgZmlsbD0iI2ZmZiIKICAgICBpZD0iZzIiPgogICAgPHBhdGgKICAgICAgIGQ9Ik01Mi42MTIgNzguNzgySDIzLjMzYTIuNTU5IDIuNTU5IDAgMCAxLTIuNTYtMi41NTl2LTcuNjc2aC03Ljk3M2EyLjU2IDIuNTYgMCAwIDEtMi41Ni0yLjU2VjU1LjMxNWwtOC44Mi00LjM5N2EyLjU1OSAyLjU1OSAwIDAgMS0uOTg2LTMuNzFsOS44MDctMTQuNzE0di00LjM1QzEwLjI0IDEyLjU5OSAyMi44NDMgMCAzOC4zODggMCA1My45MzIgMCA2Ni41MzQgMTIuNiA2Ni41MzggMjguMTQzYy0uNjMyIDI3LjgyNC0xMC43NiAyMy41MTYtMTEuMTggMzQuMDQ1bC0uMTg3IDE0LjAzNWEyLjU5IDIuNTkgMCAwIDEtLjc1IDEuODEgMi41NSAyLjU1IDAgMCAxLTEuODA5Ljc1em0tMjYuNzIzLTUuMTE3aDI0LjE2NGwuMjg2LTE0LjU0MmMtLjI2My02LjY1NiAxMS43MTYtOC4yNDMgMTEuMDgtMzAuNzM0LS4zNTgtMTIuNzEzLTEwLjMxMy0yMy4yNzEtMjMuMDMxLTIzLjI3MS0xMi43MTggMC0yMy4wMjkgMTAuMzA3LTIzLjAzMiAyMy4wMjV2NS4xMThjMCAuNTA1LS4xNS45OTktLjQzIDEuNDJsLTguNjMgMTIuOTQgNy42NDUgMy44MmEyLjU1OSAyLjU1OSAwIDAgMSAxLjQxNSAyLjI5MXY5LjY5N2g3Ljk3NGEyLjU1OSAyLjU1OSAwIDAgMSAyLjU2IDIuNTU5eiIKICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmYiCiAgICAgICBpZD0icGF0aDEiIC8+CiAgICA8cGF0aAogICAgICAgZD0iTTQwLjM3MiA1OC4yMjJWMzguOTM0Yy4xMTggMCAuMjM3LjAxOC4zNTUuMDE4IDkuNzY5LS4wMTIgMTcuMDUtOS4wMTIgMTUuMDIyLTE4LjU2N2EyLjM2NiAyLjM2NiAwIDAgMC0xLjgyMS0xLjgyMmMtOC4xMDYtMS43My0xNi4xMjEgMy4yOTItMTguMDk4IDExLjM0MS0uMDI0LS4wMjQtLjA0My0uMDUtLjA2Ni0uMDczYTE1LjMyMyAxNS4zMjMgMCAwIDAtMTQuMDYtNC4xNyAyLjM2NSAyLjM2NSAwIDAgMC0xLjgyMSAxLjgyYy0yLjAyOCA5LjU1NSA1LjI1MiAxOC41NTQgMTUuMDIgMTguNTY4LjIzNiAwIC40OTItLjAyOC43MzgtLjA0djEyLjIxM1ptMi44MzktMzIuMTQzYTEwLjY0NiAxMC42NDYgMCAwIDEgOC4xMjQtMy4xMDZjLjM1IDYuMzQtNC44ODggMTEuNTc3LTExLjIyOCAxMS4yM2ExMC41OCAxMC41OCAwIDAgMSAzLjEwNC04LjEyNHpNMjcuNDAzIDM4LjE5M2ExMC42MDcgMTAuNjA3IDAgMCAxLTMuMTE4LTguMTIzYzYuMzQ0LS4zNTggMTEuNTg3IDQuODg2IDExLjIyOCAxMS4yMy0zLjAyMy4xNjktNS45NzMtLjk2MS04LjExLTMuMTA3eiIKICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmYiCiAgICAgICBpZD0icGF0aDIiIC8+CiAgPC9nPgogIDxkZWZzCiAgICAgaWQ9ImRlZnMzIj4KICAgIDxjbGlwUGF0aAogICAgICAgaWQ9ImEiPgogICAgICA8cGF0aAogICAgICAgICBmaWxsPSIjZmZmIgogICAgICAgICBkPSJNMCAwaDI1NnY4MEgweiIKICAgICAgICAgaWQ9InBhdGgzIiAvPgogICAgPC9jbGlwUGF0aD4KICA8L2RlZnM+Cjwvc3ZnPgo=)](https://brainmade.org)
[![GNU AGPLv3.0 License](https://img.shields.io/badge/License-GNU%20AGPLv3.0-dark_green?logo=gnu)](https://choosealicense.com/licenses/agpl-3.0)
[![Buymeacoffee](https://img.shields.io/badge/Buymeacoffee-gray?logo=buymeacoffee)](https://buymeacoffee.com/nicolabelluti)
<br>
[![CI Badge](https://git.nicolabelluti.me/nicolabelluti/r701/actions/workflows/check-format-and-test.yaml/badge.svg)](https://git.nicolabelluti.me/nicolabelluti/r701/actions/?workflow=check-format-and-test.yaml)
</div>
> A reverse-engineered library to communicate with the
> [R701](https://ipsattendant.it/rilevatore-presenze-r701/) by [I.P.S.
> Informatica](https://ipsinformatica.info/) via TCP, written in Rust
> Informatica](https://ipsinformatica.info/), written in Rust
If you want to read about this reverse engineering attempt you can check out
If you want to read about this reverse engineering attempt, check out
<https://nicolabelluti.me/series/attendance-reader/>.
1. Add the library to you project:
```shell
cargo add r701 --git https://git.nicolabelluti.me/nicolabelluti/r701.git
```
2. Use the library in you project:
```rust
use r701::R701;
fn main() {
let mut r701 = R701::connect("127.0.0.1:5005").unwrap();
println!("No\tMchn\tEnNo\t\tName\t\tMode\tIOMd\tDateTime\t");
r701.into_record_iter()
.unwrap()
.collect::<Vec<_>>()
.iter()
.rev()
.enumerate()
.for_each(|(id, record)| {
let name = r701
.get_name(record.employee_id)
.unwrap()
.unwrap_or(format!("user #{}", record.employee_id));
println!(
"{:0>6}\t{}\t{:0>9}\t{: <10}\t{}\t{}\t{}",
id + 1,
1,
record.employee_id,
name,
35,
record.clock as u8,
record.datetime.format("%Y/%m/%d %H:%M:%S"),
);
});
}
```

27
flake.lock generated
View File

@ -1,27 +0,0 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1718318537,
"narHash": "sha256-4Zu0RYRcAY/VWuu6awwq4opuiD//ahpc2aFHg2CWqFY=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "e9ee548d90ff586a6471b4ae80ae9cfcbceb3420",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

View File

@ -1,23 +0,0 @@
{
description = "Rust setup";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
};
outputs = { self, nixpkgs, ... }:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in {
devShells.${system}.default = pkgs.mkShell {
buildInputs = with pkgs; [
cargo
clippy
rustfmt
];
};
};
}

View File

@ -1,13 +0,0 @@
[package]
name = "r701"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
chrono = { version = "0.4.38", default-features = false, features = ["clock"] }
serde = { version = "1.0.203", default-features = false, features = ["derive"], optional = true }
[features]
serde = ["chrono/serde", "dep:serde"]

View File

@ -1,8 +0,0 @@
[package]
name = "sample-main"
version = "0.1.0"
edition = "2021"
[dependencies]
r701 = { path = "../r701" }
chrono = { version = "0.4.38", default-features = false }

View File

@ -1,39 +0,0 @@
use chrono::{Local, TimeZone};
use r701::R701;
use std::collections::HashMap;
fn main() {
let start = Local.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap();
let end = Local.with_ymd_and_hms(9999, 1, 1, 0, 0, 0).unwrap();
let mut names = HashMap::new();
let mut r701 = R701::connect("127.0.0.1:5005").unwrap();
println!("No\tMchn\tEnNo\t\tName\t\tMode\tIOMd\tDateTime\t");
r701.into_record_iter()
.unwrap()
.take_while(|record| record.datetime >= start)
.skip_while(|record| record.datetime >= end)
.collect::<Vec<_>>()
.iter()
.rev()
.enumerate()
.for_each(|(id, record)| {
let name = names.entry(record.employee_id).or_insert_with(|| {
r701.get_name(record.employee_id)
.unwrap()
.unwrap_or(format!("user #{}", record.employee_id))
});
println!(
"{:0>6}\t{}\t{:0>9}\t{: <10}\t{}\t{}\t{}",
id + 1,
1,
record.employee_id,
name,
35,
record.clock as u8,
record.datetime.format("%Y/%m/%d %H:%M:%S"),
);
});
}

View File

@ -12,8 +12,8 @@ pub enum Clock {
SecondOut,
}
unsafe impl Send for Clock {}
unsafe impl Sync for Clock {}
// 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))]
@ -23,16 +23,16 @@ pub struct Record {
pub datetime: DateTime<Local>,
}
unsafe impl Send for Record {}
unsafe impl Sync for Record {}
// 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");
if record_bytes.len() != 12 {
return Err("Slice must be of length 12 to be converted into Record");
}
// Extract the employee ID from the central 4 bytes of the record in
@ -48,6 +48,7 @@ impl TryFrom<&[u8]> for Record {
let datetime = u32::from_le_bytes(datetime);
// Return a new Record
#[allow(clippy::cast_possible_wrap)]
Ok(Self {
employee_id,
clock: match record_bytes[1] >> 6 {
@ -55,7 +56,7 @@ impl TryFrom<&[u8]> for Record {
1 => Clock::FirstOut,
2 => Clock::SecondIn,
3 => Clock::SecondOut,
_ => panic!("Math has broken"),
_ => unreachable!("Math has broken"),
},
datetime: Local
.with_ymd_and_hms(
@ -94,9 +95,9 @@ mod tests {
datetime: Local
.with_ymd_and_hms(1970, 1, 1, 0, 0, 0)
.single()
.unwrap(),
.expect("Datetime is not unique!"),
})
)
);
}
#[test]
@ -106,7 +107,7 @@ mod tests {
assert_eq!(
record_bytes.try_into(),
Err::<Record, &str>("Slice must be at least of length 12 to be converted into Record")
)
);
}
#[test]
@ -116,6 +117,6 @@ mod tests {
assert_eq!(
record_bytes.try_into(),
Err::<Record, &str>("DateTime conversion error")
)
);
}
}

View File

@ -17,7 +17,11 @@ impl<'a> RecordIterator<'a> {
// 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;
//
// TODO: Find a better way to do the multiplication and the division
// avoiding overflows
#[allow(clippy::cast_possible_truncation)]
let sequence_number = (u32::from(total_records) * 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