From b5d070c4d70f329a7b241014f3fd27a40db58dcb Mon Sep 17 00:00:00 2001 From: "Y.Horie" Date: Tue, 15 Sep 2026 07:49:19 +0900 Subject: [PATCH] feat: let a created buffer be filled through the safe API Pool::create_buffer(n) allocates n bytes, but ngx_create_temp_buf leaves pos == last, so Buffer::len() is 0 and MutableBuffer::as_bytes_mut() returns an empty slice. There was no safe way to put anything in: neither trait exposed the capacity or a way to set the length, so filling the buffer meant dropping to as_ngx_buf_mut() and advancing last by hand. Add spare_capacity() and append(). Appending more than fits writes nothing and returns None, rather than truncating, so a caller that ignores the result cannot produce a partial value. Fixes #327 Signed-off-by: Y.Horie --- src/core/buffer.rs | 76 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/src/core/buffer.rs b/src/core/buffer.rs index cfd12820..a7fa941e 100644 --- a/src/core/buffer.rs +++ b/src/core/buffer.rs @@ -1,4 +1,4 @@ -use core::slice; +use core::{ptr, slice}; use crate::ffi::*; @@ -67,6 +67,33 @@ pub trait MutableBuffer: Buffer { let buf = self.as_ngx_buf_mut(); unsafe { slice::from_raw_parts_mut((*buf).pos, self.len()) } } + + /// Returns how many bytes can still be appended. + /// + /// A buffer from [`crate::core::Pool::create_buffer`] starts empty, with + /// its whole allocation spare. + fn spare_capacity(&self) -> usize { + let buf = self.as_ngx_buf(); + unsafe { usize::wrapping_sub((*buf).end as _, (*buf).last as _) } + } + + /// Appends `bytes` to the buffer contents. + /// + /// Returns `None` and writes nothing if they do not fit, so a caller that + /// ignores the result cannot end up with a truncated value. + fn append(&mut self, bytes: &[u8]) -> Option<()> { + if bytes.len() > self.spare_capacity() { + return None; + } + + let buf = self.as_ngx_buf_mut(); + unsafe { + ptr::copy_nonoverlapping(bytes.as_ptr(), (*buf).last, bytes.len()); + (*buf).last = (*buf).last.add(bytes.len()); + } + + Some(()) + } } /// Wrapper struct for a temporary buffer, providing methods for working with an `ngx_buf_t`. @@ -127,3 +154,50 @@ impl Buffer for MemoryBuffer { self.0 } } + +#[cfg(test)] +mod tests { + use super::*; + + /// Builds the shape `ngx_create_temp_buf` produces: `pos == last == start`. + fn temp_buf(storage: &mut [u8], raw: &mut ngx_buf_t) -> TemporaryBuffer { + raw.start = storage.as_mut_ptr(); + raw.pos = raw.start; + raw.last = raw.start; + raw.end = unsafe { raw.start.add(storage.len()) }; + TemporaryBuffer::from_ngx_buf(raw) + } + + #[test] + fn append_fills_a_freshly_created_buffer() { + let mut storage = [0u8; 64]; + let mut raw: ngx_buf_t = unsafe { core::mem::zeroed() }; + let mut buf = temp_buf(&mut storage, &mut raw); + + // A new buffer holds nothing, and all of its allocation is spare. + assert_eq!(buf.len(), 0); + assert_eq!(buf.spare_capacity(), 64); + assert!(buf.as_bytes_mut().is_empty()); + + assert!(buf.append(b"hello ").is_some()); + assert!(buf.append(b"world").is_some()); + + assert_eq!(buf.as_bytes(), b"hello world"); + assert_eq!(buf.len(), 11); + assert_eq!(buf.spare_capacity(), 53); + } + + #[test] + fn append_writes_nothing_when_it_does_not_fit() { + let mut storage = [0u8; 4]; + let mut raw: ngx_buf_t = unsafe { core::mem::zeroed() }; + let mut buf = temp_buf(&mut storage, &mut raw); + + assert!(buf.append(b"too long").is_none()); + assert_eq!(buf.len(), 0); + + // A partial append would have left the buffer holding "too ". + assert!(buf.append(b"ok").is_some()); + assert_eq!(buf.as_bytes(), b"ok"); + } +}