Files
r701/README.md
T
nicolabellutiandClaude Sonnet 5 e0e453c9c5
Check, format and test / build (push) Failing after 5s
Cleaned up the protocol layer and metadata
- request() now reads exactly the expected response length with
  read_exact instead of a single non-guaranteed fill_buf, fixing a
  latent bug where a response split across multiple TCP packets could
  be mistaken for malformed data; the per-method length checks are now
  redundant and were removed
- sequence_number wraps explicitly via wrapping_add instead of
  relying on implicit (and debug-vs-release inconsistent) overflow
  behavior after 65536 requests
- get_name validates UTF-8 strictly instead of silently lossy-decoding
  invalid bytes; on invalid data it now returns the FromUtf8Error
  itself (byte-level \0 trimming moved after the UTF-8 check), letting
  the caller choose to fail or fall back to a lossy rendering
- removed the unused Default derive from Clock and Record
- removed npm from the CI image (no JS/Node in this repo), fixed a
  typo in a CI step name
- fixed the Cargo.toml license field to the actual SPDX identifier
  (AGPL-3.0-or-later, per the notice at the end of LICENSE) instead of
  the free-text "GNU AGPLv3.0"

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-01 14:43:50 +02:00

2.5 KiB

R701 🕰️

Rust Claude Code GNU AGPLv3.0 License Buymeacoffee
CI Badge


A reverse-engineered library to communicate with the R701 by I.P.S. Informatica, written in Rust

If you want to read about this reverse engineering attempt, check out https://nicolabelluti.me/series/attendance-reader/.

Library Usage

  1. Add the library to you project:

    cargo add r701 --git https://git.nicolabelluti.me/nicolabelluti/r701.git
    
  2. Use the library in you project:

    This library is async and built on tokio; add it to your own [dependencies] (e.g. tokio = { version = "1", features = ["rt-multi-thread", "macros"] }) to run the example below.

    use r701::R701;
    use tokio_stream::StreamExt;
    
    #[tokio::main]
    async fn main() {
        let r701 = R701::connect("127.0.0.1:5005").await.unwrap();
        let mut record_stream = r701.into_record_iter().await.unwrap();
        let mut id = 0;
    
        println!("No\tMchn\tEnNo\t\tName\t\tMode\tIOMd\tDateTime\t");
        while let Some(record) = record_stream.next().await {
            let record = record.unwrap();
            let name = match r701.get_name(record.employee_id).await.unwrap() {
                Some(Ok(name)) => name,
                Some(Err(error)) => String::from_utf8_lossy(error.as_bytes()).into_owned(),
                None => 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"),
            );
    
            id += 1;
        }
    }