Skip to main content

rumtk_core/buffers/
helpers.rs

1/*
2 *     rumtk attempts to implement HL7 and medical protocols for interoperability in medicine.
3 *     This toolkit aims to be reliable, simple, performant, and standards compliant.
4 *     Copyright (C) 2026  Luis M. Santos, M.D. <lsantos@medicalmasses.com>
5 *     Copyright (C) 2026  MedicalMasses L.L.C. <contact@medicalmasses.com>
6 *
7 *     This program is free software: you can redistribute it and/or modify
8 *     it under the terms of the GNU General Public License as published by
9 *     the Free Software Foundation, either version 3 of the License, or
10 *     (at your option) any later version.
11 *
12 *     This program is distributed in the hope that it will be useful,
13 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
14 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 *     GNU General Public License for more details.
16 *
17 *     You should have received a copy of the GNU General Public License
18 *     along with this program.  If not, see <https://www.gnu.org/licenses/>.
19 */
20
21use crate::base::{RUMResult, RUMVec};
22use crate::buffers::constants::DEFAULT_BYTE_WINDOW_SIZE;
23use crate::buffers::RUMBuffer;
24use crate::cpu::*;
25use crate::strings::{rumtk_format, RUMArrayConversions, RUMString};
26use clap::builder::TypedValueParser;
27use rand::{distr::Alphanumeric, RngExt};
28use tokio::io::AsyncReadExt;
29
30///
31/// Convert slice of `&[u8]` to [RUMBuffer].
32///
33/// ## Example
34/// ```
35/// use rumtk_core::buffers::slice_to_buffer;
36/// use rumtk_core::buffers::*;
37///
38/// const expected: &str = "Hello World!";
39/// let buffer = RUMBuffer::from(expected.as_bytes());
40/// let result = slice_to_buffer(expected.as_bytes());
41///
42/// assert_eq!(result, buffer, "Slice to RUMBuffer conversion failed!");
43/// ```
44///
45#[inline(always)]
46pub fn slice_to_buffer(buffer: &[u8]) -> RUMBuffer {
47    RUMBuffer::from(buffer)
48}
49
50///
51/// Generates a new random buffer using the `rand` crate and wrapped inside a [RUMBuffer](RUMBuffer).
52///
53/// The buffer size can be adjusted via the turbofish method => `new_random_buffer::<10>()`.
54///
55/// ## Example
56///
57/// ```
58/// use rumtk_core::buffers::{new_random_buffer, DEFAULT_BUFFER_CHUNK_SIZE};
59///
60/// let buffer = new_random_buffer::<DEFAULT_BUFFER_CHUNK_SIZE>();
61///
62/// assert_eq!(buffer.is_empty(), false, "Function returned an empty random buffer which was unexpected!");
63/// assert_eq!(buffer.len(), DEFAULT_BUFFER_CHUNK_SIZE, "The new random buffer does not have the expected size!");
64/// ```
65///
66#[inline(always)]
67pub fn new_random_buffer<const N: usize>() -> [u8; N] {
68    let mut buffer = [0u8; N];
69    rand::fill(&mut buffer);
70    buffer
71}
72
73///
74/// Generates a new random buffer using the `rand` crate and wrapped inside a [RUMBuffer](RUMBuffer).
75///
76/// The buffer size can be adjusted via the turbofish method => `new_random_buffer::<10>()`.
77///
78/// ## Example
79///
80/// ```
81/// use rumtk_core::buffers::{new_random_buffer, DEFAULT_BUFFER_CHUNK_SIZE};
82///
83/// let buffer = new_random_buffer::<DEFAULT_BUFFER_CHUNK_SIZE>();
84///
85/// assert_eq!(buffer.is_empty(), false, "Function returned an empty random buffer which was unexpected!");
86/// assert_eq!(buffer.len(), DEFAULT_BUFFER_CHUNK_SIZE, "The new random buffer does not have the expected size!");
87/// ```
88///
89#[inline(always)]
90pub fn new_random_rumbuffer<const N: usize>() -> RUMBuffer {
91    slice_to_buffer(&new_random_buffer::<N>())
92}
93
94///
95/// Generates a new random string using the `rand` crate and wrapped inside a [RUMString](RUMString).
96///
97/// The buffer size can be adjusted via the turbofish method => `new_random_string_buffer::<10>()`.
98///
99/// ## Example
100///
101/// ```
102/// use rumtk_core::buffers::{new_random_string_buffer, DEFAULT_BUFFER_CHUNK_SIZE};
103///
104/// let buffer = new_random_string_buffer::<DEFAULT_BUFFER_CHUNK_SIZE>();
105///
106/// assert_eq!(buffer.is_empty(), false, "Function returned an empty random buffer which was unexpected!");
107/// assert_eq!(buffer.len(), DEFAULT_BUFFER_CHUNK_SIZE, "The new random buffer does not have the expected size!");
108/// ```
109///
110pub fn new_random_string_buffer<const N: usize>() -> RUMString {
111    rand::rng()
112        .sample_iter(&Alphanumeric)
113        .take(N) // Length of the string
114        .map(char::from)
115        .collect()
116}
117
118///
119/// Generates a new random set of [RUMString] using the `rand` crate.
120///
121/// The buffer size for each item can be adjusted via the turbofish method => `new_random_string_set::<10>()`.
122///
123/// ## Example
124///
125/// ```
126/// use rumtk_core::buffers::{new_random_string_set, DEFAULT_BUFFER_CHUNK_SIZE};
127///const item_count: usize = 5;
128///
129/// let buffer = new_random_string_set::<DEFAULT_BUFFER_CHUNK_SIZE>(item_count);
130///
131/// assert_eq!(buffer.is_empty(), false, "Function returned an empty random buffer which was unexpected!");
132/// assert_eq!(buffer.len(), item_count, "The new random buffer does not have the expected item count!");
133/// assert_eq!(buffer.get(0).unwrap().len(), DEFAULT_BUFFER_CHUNK_SIZE, "The new random buffer does not have the expected size!");
134/// ```
135///
136pub fn new_random_string_set<const N: usize>(item_count: usize) -> RUMVec<RUMString> {
137    let mut set = RUMVec::<RUMString>::with_capacity(item_count);
138
139    for _ in 0..item_count {
140        set.push(new_random_string_buffer::<N>())
141    }
142
143    set
144}
145
146///
147/// Convert buffer to string.
148///
149/// ## Example
150/// ```
151/// use rumtk_core::buffers::buffer_to_string;
152/// use rumtk_core::buffers::*;
153///
154/// const expected: &str = "Hello World!";
155/// let buffer = RUMBuffer::from(expected.as_bytes());
156/// let result = buffer_to_string(&buffer).unwrap();
157///
158/// assert_eq!(result, expected, "Buffer to RUMString conversion failed!");
159/// ```
160///
161#[inline(always)]
162pub fn buffer_to_string(buffer: &[u8]) -> RUMResult<RUMString> {
163    match buffer.to_string() {
164        Ok(string) => Ok(string),
165        Err(e) => Err(rumtk_format!("Failure to parse incoming UTF-8 string: {}", e)),
166    }
167}
168
169#[inline(always)]
170pub fn buffer_to_str(buffer: &[u8]) -> RUMResult<&str> {
171    match std::str::from_utf8(buffer) {
172        Ok(string) => Ok(string),
173        Err(e) => Err(rumtk_format!("Failure to parse incoming UTF-8 string: {}", e)),
174    }
175}
176
177#[inline(always)]
178pub fn buffer_count(buffer: &[u8], pattern: u8) -> usize {
179    bytecount::count(buffer, pattern)
180}
181
182#[inline(always)]
183pub fn buffer_contains(buffer: &[u8], pattern: u8) -> bool {
184    buffer_find_byte(buffer, pattern).unwrap_or(buffer.len()) < buffer.len()
185}
186
187#[inline(always)]
188pub fn buffer_slice_to_array(chunk: &[u8]) -> &[u8; DEFAULT_BYTE_WINDOW_SIZE] {
189    chunk.try_into().expect("length mismatch")
190}
191
192#[inline(always)]
193pub fn buffer_chunk_find_fallback(chunk: &[u8], byte: u8) -> Option<usize> {
194    chunk.iter().position(|c| *c==byte)
195}
196
197#[inline(always)]
198pub fn buffer_chunk_find(chunk: &[u8], byte: u8) -> usize {
199    let length = chunk.len();
200
201    if length == DEFAULT_BYTE_WINDOW_SIZE {
202        let chunk_window = buffer_slice_to_array(chunk);
203        cpu_find_simd(chunk_window, byte).unwrap_or(length)
204    } else {
205        buffer_chunk_find_fallback(chunk, byte).unwrap_or(length)
206    }
207}
208
209#[inline(always)]
210pub fn buffer_find_byte(buffer: &[u8], byte: u8) -> Option<usize> {
211    cpu_find_simd(buffer, byte)
212}
213
214#[inline(always)]
215pub fn buffer_find(buffer: &[u8], pattern: &[u8]) -> usize {
216    if buffer.is_empty() {
217        return buffer.len();
218    }
219
220    let start_pattern_byte = pattern[0];
221    let pattern_length = pattern.len();
222    let mut working_buffer = buffer;
223    let mut cumulative = 0;
224    let mut end = 0;
225
226    while (end + pattern_length) < working_buffer.len() {
227        working_buffer = &working_buffer[end..];
228
229        if working_buffer[..pattern_length] == *pattern {
230            return cumulative;
231        } else {
232            working_buffer = &working_buffer[pattern_length..];
233            cumulative += pattern_length;
234        }
235
236        end = buffer_find_byte(&working_buffer, start_pattern_byte).unwrap_or(working_buffer.len());
237        cumulative += end;
238    }
239
240    buffer.len()
241}
242
243#[inline(always)]
244pub fn buffer_find_instances<'a>(buffer: &'a [u8], pattern: &[u8]) -> RUMVec<(usize, &'a [u8])> {
245    if buffer.is_empty() {
246        return RUMVec::new();
247    }
248
249    let pattern_length = pattern.len();
250    let buffer_length = buffer.len() - pattern_length;
251    let mut instances = RUMVec::<(usize, &[u8])>::with_capacity(100);
252
253    let mut cursor = buffer_find(buffer, pattern);
254    let mut cumulative = cursor;
255    let mut remainder = &buffer[..];
256
257    while cumulative < buffer_length {
258        instances.push((cumulative, &remainder[..cursor]));
259        let next = cursor + pattern_length;
260        if next <= remainder.len() {
261            remainder = &remainder[cursor + pattern_length..];
262            cursor = buffer_find(remainder, pattern);
263            cumulative += cursor;
264        } else {
265            cumulative += remainder.len();
266        }
267    }
268
269    instances
270}
271
272#[inline(always)]
273pub fn buffer_replace_in_place<'a>(buffer: &'a mut [u8], pattern: &[u8], replacement: &[u8]) {
274    // Optimize the nothing passed to us case.
275    if buffer.is_empty() || pattern.is_empty() || replacement.is_empty() {
276        return;
277    }
278
279    // Optimize the single byte replace using SIMD
280    if pattern.len() == 1  {
281        cpu_replace_simd(buffer, pattern[0], replacement[0]);
282        return;
283    }
284
285    let replacement_length = replacement.len();
286    let mut cursor = buffer_find(&buffer, pattern);
287    let mut remainder = buffer;
288
289    while cursor < remainder.len() {
290        for i in 0..replacement_length {
291            remainder[cursor + i] = replacement[i];
292        }
293
294        remainder = &mut remainder[cursor + pattern.len()..];
295        cursor = buffer_find(remainder, pattern);
296    }
297}
298
299#[inline(always)]
300pub fn buffer_replace(buffer: &[u8], pattern: &[u8], replacement: &[u8]) -> RUMBuffer {
301    match buffer.is_empty() {
302        true => RUMBuffer::from(buffer),
303        false => {
304            let pattern_length = pattern.len();
305            let replacement_length = replacement.len();
306            let instances = buffer_find_instances(&buffer, pattern);
307            let mut new_buffer =  RUMVec::with_capacity(buffer.len() + (instances.len() * (replacement_length)));
308            let mut last = 0;
309
310            for (indx, chunk) in instances {
311                new_buffer.extend_from_slice(chunk);
312                new_buffer.extend_from_slice(replacement);
313                last = indx + pattern_length;
314            }
315
316            new_buffer.extend_from_slice(&buffer[last..]);
317            RUMBuffer::from(new_buffer)
318        }
319    }
320}
321
322#[inline]
323pub fn buffer_trim(buffer: &RUMBuffer) -> RUMBuffer {
324    let trimmed = buffer_slice_trim(&buffer[..]);
325    RUMBuffer::from(trimmed)
326}
327
328#[inline(always)]
329pub fn buffer_slice_trim(buffer: &[u8]) -> &[u8] {
330    buffer.trim_ascii()
331}
332
333pub fn buffer_has_pattern(buffer: &[u8], pattern: &[u8]) -> bool {
334    buffer_find(buffer, pattern) != buffer.len()
335}
336
337pub fn is_unique_bytes(data: &[u8]) -> bool {
338    let mut items = ahash::AHashSet::with_capacity(data.len());
339    for i in 0..data.len() {
340        if !items.insert(data[i]) {
341            return false;
342        }
343    }
344    true
345}