From 32f90aeace1c95ae2acbe545b529d3c6463a688c Mon Sep 17 00:00:00 2001 From: Noah Pederson Date: Tue, 23 May 2023 21:49:50 -0500 Subject: [PATCH] Initial commit --- .gitignore | 2 + Cargo.toml | 10 +++++ LICENSE | 21 ++++++++++ src/.lib.rs.rustfmt | 93 +++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 92 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 218 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 src/.lib.rs.rustfmt create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ce64fd4 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "bufring-rs" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +crc = "3.0.1" +zerocopy = "0.6.1" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ba5658a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Noah Pederson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/.lib.rs.rustfmt b/src/.lib.rs.rustfmt new file mode 100644 index 0000000..63406dc --- /dev/null +++ b/src/.lib.rs.rustfmt @@ -0,0 +1,93 @@ +use std::collections::VecDeque; +use std::default::Default; +use std::rc::Rc; +use zerocopy::{FromBytes, AsBytes}; +use crc::{Crc, CRC_32_CKSUM}; + +const CHECKSUM: Crc = Crc::::new(&CRC_32_CKSUM); + +// 4MB +const DEFAULT_PAGE_SIZE: usize = 4 * 1024 * 1024; + +#[repr(packed)] +struct RingBufferConfig { + /// The number of pages in this buffer + page_count: u16, + /// The size in bytes of each page + page_size: u32, + /// The number of records stored in each page + records_per_page: u32, + /// A special flag indicating how many pages are currently in the buffer. + /// This value must be 0 through 2. + /// A 0 indicates there are *no* pages serialized to the buffer + /// A 1 indicates there are *exactly one* pages serialized to the buffer + /// A 2 indicates there are *at least two* pages serialized to the buffer + /// A 3 indicates this buffer has been filled at least once and looped back around + page_flag: u8, + /// The index of the head record. + /// If this is <0, the head was *not* saved properly and we must assume every record in the + /// buffer has not been drained. If it is 0 or greater, it is the index of the head record + head: i32, +} + +impl Default for RingBufferConfig { + fn default() -> Self { + Self { + page_count: Default::default(), + page_size: DEFAULT_PAGE_SIZE as u32, + records_per_page: 0, + page_flag: 0, + head: 0, + } + } +} + +#[repr(packed)] +struct BufferPage { + seq: u16, + crc: u32, + records: VecDeque, +} + +impl BufferPage { + + fn new(seq: u16) -> Self { + Self { + /// seq needs to be provided by the buffer + seq: seq, + /// TODO: calculate this when we go to serialize + crc: 0, + // TODO: use with_capacity and set it to the correct alignment capacity + records: VecDeque::new(), + } + } +} + +pub struct RingBuffer { + /// The configuration for this buffer + config: RingBufferConfig, + /// The index of the tail record + tail: u16, + /// The number of records currently in the buffer. + /// The tail of the buffer can be calculated by head - length and wrapping around per normal + /// ring-buffer semantics. + length: u16, + /// An optional cached page to be read from next. + /// If this is `Option::None`, we *must* read a page from disk + /// It *may* hold a reference to the same `BufferPage` as `last`. + next: Option>>, + /// The tail page of the buffer. New records should be inserted into this page. + /// It *may* hold a reference to the same `BufferPage` as `last`. + last: Rc>, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..370c85a --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,92 @@ +use crc::{Crc, CRC_32_CKSUM}; +use std::collections::VecDeque; +use std::default::Default; +use std::rc::Rc; +use zerocopy::{AsBytes, FromBytes}; + +const CHECKSUM: Crc = Crc::::new(&CRC_32_CKSUM); + +// 4MB +const DEFAULT_PAGE_SIZE: usize = 4 * 1024 * 1024; + +#[repr(packed)] +struct RingBufferConfig { + /// The number of pages in this buffer + page_count: u16, + /// The size in bytes of each page + page_size: u32, + /// The number of records stored in each page + records_per_page: u32, + /// A special flag indicating how many pages are currently in the buffer. + /// This value must be 0 through 2. + /// A 0 indicates there are *no* pages serialized to the buffer + /// A 1 indicates there are *exactly one* pages serialized to the buffer + /// A 2 indicates there are *at least two* pages serialized to the buffer + /// A 3 indicates this buffer has been filled at least once and looped back around + page_flag: u8, + /// The index of the head record. + /// If this is <0, the head was *not* saved properly and we must assume every record in the + /// buffer has not been drained. If it is 0 or greater, it is the index of the head record + head: i32, +} + +impl Default for RingBufferConfig { + fn default() -> Self { + Self { + page_count: Default::default(), + page_size: DEFAULT_PAGE_SIZE as u32, + records_per_page: 0, + page_flag: 0, + head: 0, + } + } +} + +#[repr(packed)] +struct BufferPage { + seq: u16, + crc: u32, + records: VecDeque, +} + +impl BufferPage { + fn new(seq: u16) -> Self { + Self { + /// seq needs to be provided by the buffer + seq: seq, + /// TODO: calculate this when we go to serialize + crc: 0, + // TODO: use with_capacity and set it to the correct alignment capacity + records: VecDeque::new(), + } + } +} + +pub struct RingBuffer { + /// The configuration for this buffer + config: RingBufferConfig, + /// The index of the tail record + tail: u16, + /// The number of records currently in the buffer. + /// The tail of the buffer can be calculated by head - length and wrapping around per normal + /// ring-buffer semantics. + length: u16, + /// An optional cached page to be read from next. + /// If this is `Option::None`, we *must* read a page from disk + /// It *may* hold a reference to the same `BufferPage` as `last`. + next: Option>>, + /// The tail page of the buffer. New records should be inserted into this page. + /// It *may* hold a reference to the same `BufferPage` as `last`. + last: Rc>, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} -- 2.45.2