83 lines
2.5 KiB
Python
83 lines
2.5 KiB
Python
"""Utility helpers for pyaegis.
|
|
|
|
Currently provides Python-side aligned allocation helpers that avoid relying
|
|
on libc/posix_memalign. Memory is owned by Python; C code only borrows it.
|
|
"""
|
|
|
|
from typing import Protocol
|
|
|
|
from ._loader import ffi
|
|
|
|
__all__ = ["new_aligned_struct", "aligned_address", "Buffer", "nonce_increment", "wipe"]
|
|
|
|
try:
|
|
from collections.abc import Buffer # type: ignore
|
|
except ImportError:
|
|
# Fallback for Python < 3.12
|
|
class Buffer(Protocol):
|
|
def __buffer__(self, flags: int) -> memoryview: ...
|
|
|
|
|
|
def aligned_address(obj) -> int:
|
|
"""Return the integer address of the start of a cffi array object."""
|
|
return int(ffi.cast("uintptr_t", ffi.addressof(obj, 0)))
|
|
|
|
|
|
class StructHolder:
|
|
"""Proxy object for aligned struct allocation.
|
|
|
|
Exposes the aligned pointer as a property and wipes the buffer on deletion.
|
|
"""
|
|
|
|
def __init__(self, ptr: object, view: memoryview):
|
|
self._ptr = ptr
|
|
self._view = view # Keep memoryview slice and its bytearray alive
|
|
|
|
@property
|
|
def ptr(self) -> object:
|
|
"""The aligned pointer to the struct."""
|
|
return self._ptr
|
|
|
|
def __del__(self):
|
|
wipe(self._view)
|
|
del self._ptr, self._view
|
|
|
|
|
|
def new_aligned_struct(ctype: str, alignment: int) -> StructHolder:
|
|
"""Allocate memory for one instance of ``ctype`` with requested alignment."""
|
|
# Allocate backing storage with extra space for alignment
|
|
size = ffi.sizeof(ctype)
|
|
view = memoryview(bytearray(size + alignment - 1))
|
|
# Compute alignment offset from the base address
|
|
offset = (-aligned_address(ffi.from_buffer(view))) & (alignment - 1)
|
|
# Slice the memoryview to the aligned region (keeps bytearray alive)
|
|
view = view[offset : offset + size]
|
|
return StructHolder(ffi.from_buffer(f"{ctype} *", view), view)
|
|
|
|
|
|
def nonce_increment(nonce: Buffer) -> None:
|
|
"""Increment the nonce in place using little-endian byte order.
|
|
|
|
Useful for generating unique nonces for each consecutive message.
|
|
|
|
Args:
|
|
nonce: The nonce buffer to increment (modified in place).
|
|
"""
|
|
n = memoryview(nonce)
|
|
for i in range(len(n)):
|
|
if n[i] < 255:
|
|
n[i] += 1
|
|
return
|
|
n[i] = 0
|
|
|
|
|
|
def wipe(buffer: Buffer) -> None:
|
|
"""Securely clearing sensitive data from memory. Sets all bytes of the buffer to 0xFF.
|
|
|
|
Args:
|
|
buffer: The buffer to wipe (modified in place).
|
|
"""
|
|
# This is the fastest method I have found in Python
|
|
n = memoryview(buffer).cast("B")
|
|
n[:] = b"\xff" * len(n)
|