Compare commits
4 Commits
0dd05c0d91
...
main
Author | SHA1 | Date | |
---|---|---|---|
07ef2a96bc | |||
b2b5f0c66c | |||
987af19ec8 | |||
35e75745db |
31
.gitea/workflows/check-format-and-test.yaml
Normal file
31
.gitea/workflows/check-format-and-test.yaml
Normal 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
|
24
Cargo.lock
generated
24
Cargo.lock
generated
@ -31,9 +31,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.96"
|
version = "1.0.99"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd"
|
checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
@ -94,9 +94,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.154"
|
version = "0.2.155"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346"
|
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
@ -121,9 +121,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.81"
|
version = "1.0.85"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
|
checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@ -147,18 +147,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.200"
|
version = "1.0.203"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f"
|
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.200"
|
version = "1.0.203"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb"
|
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -167,9 +167,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.60"
|
version = "2.0.66"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
|
checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
15
Cargo.toml
15
Cargo.toml
@ -2,12 +2,21 @@
|
|||||||
name = "r701"
|
name = "r701"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
license = "GNU AGPLv3.0"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
repository = "https://git.nicolabelluti.me/nicolabelluti/r701"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = { version = "0.4.38", default-features = false, features = ["clock"] }
|
chrono = { version = "0.4.38", default-features = false, features = ["clock"] }
|
||||||
serde = { version = "1.0.200", default-features = false, features = ["derive"], optional = true }
|
serde = { version = "1.0.203", default-features = false, features = ["derive"], optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
serde = ["chrono/serde", "dep:serde"]
|
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 }
|
||||||
|
60
README.md
60
README.md
@ -1,8 +1,62 @@
|
|||||||
# R701 🦀
|
<div align="center">
|
||||||
|
|
||||||
|
# R701 🕰️
|
||||||
|
|
||||||
|
[](https://www.rust-lang.org)
|
||||||
|
[](https://brainmade.org)
|
||||||
|
[](https://choosealicense.com/licenses/agpl-3.0)
|
||||||
|
[](https://buymeacoffee.com/nicolabelluti)
|
||||||
|
<br>
|
||||||
|
[](https://git.nicolabelluti.me/nicolabelluti/r701/actions/?workflow=check-format-and-test.yaml)
|
||||||
|
|
||||||
|
</div><br>
|
||||||
|
|
||||||
> A reverse-engineered library to communicate with the
|
> A reverse-engineered library to communicate with the
|
||||||
> [R701](https://ipsattendant.it/rilevatore-presenze-r701/) by [I.P.S.
|
> [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/>.
|
<https://nicolabelluti.me/series/attendance-reader/>.
|
||||||
|
|
||||||
|
## Library Usage
|
||||||
|
|
||||||
|
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
27
flake.lock
generated
@ -1,27 +0,0 @@
|
|||||||
{
|
|
||||||
"nodes": {
|
|
||||||
"nixpkgs": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1714906307,
|
|
||||||
"narHash": "sha256-UlRZtrCnhPFSJlDQE7M0eyhgvuuHBTe1eJ9N9AQlJQ0=",
|
|
||||||
"owner": "nixos",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "25865a40d14b3f9cf19f19b924e2ab4069b09588",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nixos",
|
|
||||||
"ref": "nixos-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": "nixpkgs"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": "root",
|
|
||||||
"version": 7
|
|
||||||
}
|
|
23
flake.nix
23
flake.nix
@ -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
|
|
||||||
];
|
|
||||||
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
@ -146,7 +146,7 @@ impl R701 {
|
|||||||
Ok(response[12..1036].to_vec())
|
Ok(response[12..1036].to_vec())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter(&mut self) -> Result<RecordIterator> {
|
pub fn into_record_iter(&mut self) -> Result<RecordIterator> {
|
||||||
RecordIterator::from(self)
|
RecordIterator::from(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,8 +12,8 @@ pub enum Clock {
|
|||||||
SecondOut,
|
SecondOut,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl Send for Clock {}
|
// unsafe impl Send for Clock {}
|
||||||
unsafe impl Sync for Clock {}
|
// unsafe impl Sync for Clock {}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
@ -23,16 +23,16 @@ pub struct Record {
|
|||||||
pub datetime: DateTime<Local>,
|
pub datetime: DateTime<Local>,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl Send for Record {}
|
// unsafe impl Send for Record {}
|
||||||
unsafe impl Sync for Record {}
|
// unsafe impl Sync for Record {}
|
||||||
|
|
||||||
impl TryFrom<&[u8]> for Record {
|
impl TryFrom<&[u8]> for Record {
|
||||||
type Error = &'static str;
|
type Error = &'static str;
|
||||||
|
|
||||||
fn try_from(record_bytes: &[u8]) -> Result<Self, Self::Error> {
|
fn try_from(record_bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||||
// Return an error if the slice length is less than 12
|
// Return an error if the slice length is less than 12
|
||||||
if record_bytes.len() < 12 {
|
if record_bytes.len() != 12 {
|
||||||
return Err("Slice must be at least of length 12 to be converted into Record");
|
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
|
// 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);
|
let datetime = u32::from_le_bytes(datetime);
|
||||||
|
|
||||||
// Return a new Record
|
// Return a new Record
|
||||||
|
#[allow(clippy::cast_possible_wrap)]
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
employee_id,
|
employee_id,
|
||||||
clock: match record_bytes[1] >> 6 {
|
clock: match record_bytes[1] >> 6 {
|
||||||
@ -55,7 +56,7 @@ impl TryFrom<&[u8]> for Record {
|
|||||||
1 => Clock::FirstOut,
|
1 => Clock::FirstOut,
|
||||||
2 => Clock::SecondIn,
|
2 => Clock::SecondIn,
|
||||||
3 => Clock::SecondOut,
|
3 => Clock::SecondOut,
|
||||||
_ => panic!("Math has broken"),
|
_ => unreachable!("Math has broken"),
|
||||||
},
|
},
|
||||||
datetime: Local
|
datetime: Local
|
||||||
.with_ymd_and_hms(
|
.with_ymd_and_hms(
|
||||||
@ -94,9 +95,9 @@ mod tests {
|
|||||||
datetime: Local
|
datetime: Local
|
||||||
.with_ymd_and_hms(1970, 1, 1, 0, 0, 0)
|
.with_ymd_and_hms(1970, 1, 1, 0, 0, 0)
|
||||||
.single()
|
.single()
|
||||||
.unwrap(),
|
.expect("Datetime is not unique!"),
|
||||||
})
|
})
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -105,8 +106,8 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
record_bytes.try_into(),
|
record_bytes.try_into(),
|
||||||
Err::<Record, &str>("Slice must be at least of length 12 to be converted into Record")
|
Err::<Record, &str>("Slice must be of length 12 to be converted into Record")
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -116,6 +117,6 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
record_bytes.try_into(),
|
record_bytes.try_into(),
|
||||||
Err::<Record, &str>("DateTime conversion error")
|
Err::<Record, &str>("DateTime conversion error")
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,11 @@ impl<'a> RecordIterator<'a> {
|
|||||||
|
|
||||||
// Calculate the sequence number on which the last record resides and
|
// Calculate the sequence number on which the last record resides and
|
||||||
// the index of the first `ff` byte, avoiding overflows
|
// 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;
|
let first_useless_byte_index = total_records as usize * 12 % 1024;
|
||||||
|
|
||||||
// The endpoint expects the first block of records to be sent first
|
// The endpoint expects the first block of records to be sent first
|
||||||
|
Reference in New Issue
Block a user