Compare commits

...

9 Commits

Author SHA1 Message Date
07ef2a96bc Updated the README.md and the tests
Some checks failed
Check, format and test / build (push) Failing after 50s
2024-07-31 13:13:07 +02:00
b2b5f0c66c 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
2024-07-31 12:00:34 +02:00
987af19ec8 Added a sample main function to get the data 2024-06-16 17:57:32 +02:00
35e75745db Put the lib into a workspace 2024-06-16 17:48:15 +02:00
0dd05c0d91 Avoided two overflows 2024-06-16 14:06:13 +02:00
78248eb2a5 Renamed a variable 2024-05-13 13:25:17 +02:00
4d9361e383 Return a None if the name is an empty String 2024-05-13 13:05:27 +02:00
85e07886e9 Added Nix Flake 2024-05-06 12:27:05 +02:00
ba20f457f8 Reversed the direction of the iterator 2024-05-06 11:31:41 +02:00
7 changed files with 187 additions and 68 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

24
Cargo.lock generated
View File

@ -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",

View File

@ -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 }

View File

@ -1,8 +1,62 @@
# 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><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"),
);
});
}
```

View File

@ -65,26 +65,28 @@ impl R701 {
// If the response length is right but the header is `01 00 00 00 00 00 // 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 // 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] if response.len() == 34
&& response.len() == 34 && response[..12] == [0xaa, 0x55, 0x01, 0, 0, 0, 0, 0, 0, 0, 0x55, 0xaa]
{ {
return Ok(None); return Ok(None);
} }
// If one between the response length or the response header is wrong // If one between the response length or the response header is wrong
// return an error // return an error
if response[..12] != [0xaa, 0x55, 0x01, 0x01, 0, 0, 0, 0, 0, 0, 0x55, 0xaa] if response.len() != 34
|| response.len() != 34 || response[..12] != [0xaa, 0x55, 0x01, 0x01, 0, 0, 0, 0, 0, 0, 0x55, 0xaa]
{ {
return Err(Error::new(InvalidData, "Malformed response")); return Err(Error::new(InvalidData, "Malformed response"));
} }
// Get the name as a UTF-8 string and delete the `\0` at the end // Get the name as a UTF-8 string and delete the `\0` at the end
Ok(Some( let name = String::from_utf8_lossy(&response[12..22])
String::from_utf8_lossy(&response[12..22]) .trim_end_matches('\0')
.trim_end_matches(char::from(0)) .to_string();
.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> { pub fn get_total_record_count(&mut self) -> Result<u16> {
@ -94,9 +96,9 @@ impl R701 {
// If one between the response length or the response header is wrong // If one between the response length or the response header is wrong
// return an error // return an error
if response[..4] != [0xaa, 0x55, 0x01, 0x01] if response.len() != 10
|| response[..4] != [0xaa, 0x55, 0x01, 0x01]
|| response[6..] != [0u8; 4] || response[6..] != [0u8; 4]
|| response.len() != 10
{ {
return Err(Error::new(InvalidData, "Malformed response")); return Err(Error::new(InvalidData, "Malformed response"));
} }
@ -133,18 +135,18 @@ impl R701 {
// If one between the response length, the response header or the last // If one between the response length, the response header or the last
// two bits is wrong return an error // two bits is wrong return an error
if response[..12] != [0xaa, 0x55, 0x01, 0x01, 0, 0, 0, 0, 0, 0, 0x55, 0xaa] if response.len() != 1038
|| response[..12] != [0xaa, 0x55, 0x01, 0x01, 0, 0, 0, 0, 0, 0, 0x55, 0xaa]
|| response[1036..] != [0, 0] || response[1036..] != [0, 0]
|| response.len() != 1038
{ {
return Err(Error::new(InvalidData, "Malformed response")); return Err(Error::new(InvalidData, "Malformed response"));
} }
// Return only the payload bits as a vector // Return only the payload bits as a vector
Ok(response[12..response.len() - 2].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)
} }
} }

View File

@ -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")
) );
} }
} }

View File

@ -6,25 +6,40 @@ pub struct RecordIterator<'a> {
r701: &'a mut R701, r701: &'a mut R701,
input_buffer: Vec<u8>, input_buffer: Vec<u8>,
sequence_number: u16, sequence_number: u16,
record_count: u16,
total_records: u16, total_records: u16,
record_count: u16,
} }
impl<'a> RecordIterator<'a> { impl<'a> RecordIterator<'a> {
pub fn from(r701: &'a mut R701) -> Result<Self> { pub fn from(r701: &'a mut R701) -> Result<Self> {
// Ping the endpoint
r701.ping()?;
// Get the total number of records // Get the total number of records
let total_records = r701.get_total_record_count()?; 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
//
// 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
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 // Return a new Iterator
Ok(Self { Ok(Self {
r701, r701,
input_buffer: Vec::new(), input_buffer,
record_count: 0, sequence_number,
sequence_number: 0,
total_records, total_records,
record_count: total_records,
}) })
} }
} }
@ -33,32 +48,39 @@ impl<'a> Iterator for RecordIterator<'a> {
type Item = Record; type Item = Record;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
self.record_count += 1; // Stop iterating when there are no more records
if self.record_count == 0 {
// If we exceeded the total number of records, return None
if self.record_count > self.total_records {
return None; return None;
} }
// If the buffer is empty, make another request to the endpoint asking self.record_count -= 1;
// for more data
// If the input buffer is almost empty, make another request to the
// endpoint asking for more data
if self.input_buffer.len() < 12 { if self.input_buffer.len() < 12 {
let bytes = &mut self // 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 .r701
.get_record_bytes(self.total_records, self.sequence_number) .get_record_bytes(self.total_records, self.sequence_number)
.ok()?; .ok()?;
self.input_buffer.append(bytes); // Put the bytes at the start of the vector
self.sequence_number += 1; self.input_buffer.splice(0..0, bytes.iter().copied());
}
// If the record bytes are set to `ff ff ff ff ff ff ff ff ff ff ff ff`,
// return None
if self.input_buffer[..12] == [0xff_u8; 12] {
return None;
} }
// Return a new Record // Return a new Record
self.input_buffer.drain(..12).as_slice().try_into().ok() self.input_buffer
.drain(self.input_buffer.len() - 12..)
.as_slice()
.try_into()
.ok()
} }
} }