Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
07ef2a96bc | |||
b2b5f0c66c | |||
987af19ec8 | |||
35e75745db | |||
0dd05c0d91 | |||
78248eb2a5 | |||
4d9361e383 | |||
85e07886e9 | |||
ba20f457f8 |
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"),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
32
src/r701.rs
32
src/r701.rs
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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")
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user