Compare commits

..

10 commits

Author SHA1 Message Date
Vinzenz Schroeter 25942b6ee3 use NotNull for parameters
Some checks failed
Rust / build (pull_request) Failing after 1m23s
2025-04-12 13:04:38 +02:00
Vinzenz Schroeter 5b678a432a add /*notnull*/ comments in header 2025-04-12 13:04:38 +02:00
Vinzenz Schroeter c9be3d87ec re-export constants from base lib 2025-04-12 13:04:38 +02:00
Vinzenz Schroeter 69014dff38 wip remove newtypes 2025-04-12 13:04:38 +02:00
Vinzenz Schroeter 38633f0b6b functions for manipulating packets 2025-04-12 13:04:36 +02:00
Vinzenz Schroeter cf9877ce7b wip remove newtypes 2025-04-12 13:04:30 +02:00
Vinzenz Schroeter 6f5698a95e cargo fmt 2025-04-12 13:04:23 +02:00
Vinzenz Schroeter ad4eb27674 wip 80k example 2025-04-12 13:04:23 +02:00
Vinzenz Schroeter da1c5ebb03 wip update to next servicepoint version 2025-04-12 13:04:21 +02:00
Vinzenz Schroeter ac3c470b44 fix example path 2025-02-15 13:04:07 +01:00
23 changed files with 3228 additions and 1453 deletions

View file

@ -25,20 +25,10 @@ jobs:
- name: Install rust toolchain - name: Install rust toolchain
run: sudo apt-get install -qy cargo rust-clippy run: sudo apt-get install -qy cargo rust-clippy
- name: install lzma - name: install lzma
run: sudo apt-get install -qy liblzma-dev run: sudo apt-get update && sudo apt-get install -y liblzma-dev
- name: install gcc
run: sudo apt-get install -qy gcc make
- name: Run Clippy - name: Run Clippy
run: cargo clippy run: cargo clippy
- name: generate bindings
run: ./generate-binding.sh
- name: check that generated files did not change
run: output=$(git status --porcelain) && [ -z "$output" ]
- name: build - name: build
run: cargo build run: cargo build
- name: build example
run: cd example && make

31
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,31 @@
# Contributing
Contributions are accepted in any form (issues, documentation, feature requests, code, review, ...).
All creatures welcome.
If you have access, please contribute on the [CCCB Forgejo](https://git.berlin.ccc.de/servicepoint/servicepoint).
Contributions on GitHub will be copied over and merged there.
## Pull requests
Feel free to create a PR, even if your change is not done yet.
Mark your PR as a draft as long as you do not want it to be merged.
The main branch is supposed to be a working version, including language bindings,
which means sometimes your PR may be merged into a temporary development branch.
Unit tests and documentation are required for the core library.
## Language bindings
Pull requests for your preferred language will be accepted.
If there is no code generator, it should call the C ABI methods provided by `servicepoint_binding_c`.
It should be able to send most of the basic commands in a way the simulator accepts, receiving is
not required for the merge.
It is okay for the feature set of a language binding to lag behind the one of the rust crate.
This also means you do not have to expose a feature to all the language bindings when adding something to the core.
If your change may break other language bindings, please note that in your PR description so someone can check them.

View file

@ -3,12 +3,12 @@
[![crates.io](https://img.shields.io/crates/v/servicepoint_binding_c.svg)](https://crates.io/crates/servicepoint) [![crates.io](https://img.shields.io/crates/v/servicepoint_binding_c.svg)](https://crates.io/crates/servicepoint)
[![Crates.io Total Downloads](https://img.shields.io/crates/d/servicepoint_binding_c)](https://crates.io/crates/servicepoint) [![Crates.io Total Downloads](https://img.shields.io/crates/d/servicepoint_binding_c)](https://crates.io/crates/servicepoint)
[![docs.rs](https://img.shields.io/docsrs/servicepoint_binding_c)](https://docs.rs/servicepoint/latest/servicepoint/) [![docs.rs](https://img.shields.io/docsrs/servicepoint_binding_c)](https://docs.rs/servicepoint/latest/servicepoint/)
[![GPLv3 licensed](https://img.shields.io/crates/l/servicepoint_binding_c)](./LICENSE) [![GPLv3 licensed](https://img.shields.io/crates/l/servicepoint_binding_c)](../LICENSE)
In [CCCB](https://berlin.ccc.de/), there is a big pixel matrix hanging on the wall. In [CCCB](https://berlin.ccc.de/), there is a big pixel matrix hanging on the wall.
It is called "Service Point Display" or "Airport Display". It is called "Service Point Display" or "Airport Display".
This crate contains C bindings for the [servicepoint](https://git.berlin.ccc.de/servicepoint/servicepoint) library, enabling users to parse, encode and send packets to this display via UDP. This crate contains C bindings for the `servicepoint` library, enabling users to parse, encode and send packets to this display via UDP.
## Examples ## Examples
@ -43,61 +43,21 @@ Please specify the full version including patch in your Cargo.toml until 1.0 is
## Installation ## Installation
1. Add this repo as a submodule: Copy the header to your project and compile against.
```bash
git submodule add https://git.berlin.ccc.de/servicepoint/servicepoint-binding-c.git
git commit -m "add servicepoint-binding-c submodule"
```
2. Add a build step for the servicepoint library. If you use make, this could look something like this:
```
dependencies: FORCE
cargo build --manifest-path=servicepoint-binding-c/Cargo.toml --release
FORCE: ;
```
3. Link against the library. If you are on linux and linking statically:
```
${CC} main.c \
-I servicepoint-binding-c/include \
-L servicepoint-binding-c/target/release \
-Wl,-Bstatic -lservicepoint_binding_c \
-Wl,-Bdynamic -llzma \
-o out/example
```
You have the choice of linking statically (recommended) or dynamically. You have the choice of linking statically (recommended) or dynamically.
- The C example shows how to link statically against the `staticlib` variant. - The C example shows how to link statically against the `staticlib` variant.
- When linked dynamically, you have to provide the `cdylib` at runtime in the _same_ version, as there are no API/ABI - When linked dynamically, you have to provide the `cdylib` at runtime in the _same_ version, as there are no API/ABI guarantees yet.
guarantees yet.
## Notes on differences to rust library ## Notes on differences to rust library
- function names are: `sp_` \<struct_name\> \<rust name\>. - function names are: `sp_` \<struct_name\> \<rust name\>.
- Instances get consumed in the same way they do when writing rust code. Do not use an instance after an (implicit!) - Instances get consumed in the same way they do when writing rust code. Do not use an instance after an (implicit!) free.
free.
- Option<T> or Result<T, E> turn into nullable return values - check for NULL! - Option<T> or Result<T, E> turn into nullable return values - check for NULL!
- There are no specifics for C++ here yet. You might get a nicer header when generating directly for C++, but it should - There are no specifics for C++ here yet. You might get a nicer header when generating directly for C++, but it should be usable.
be usable.
- Reading and writing to instances concurrently is not safe. Only reading concurrently is safe. - Reading and writing to instances concurrently is not safe. Only reading concurrently is safe.
- documentation is included in the header and - documentation is included in the header and available [online](https://docs.rs/servicepoint_binding_c/latest/servicepoint_binding_c/)
available [online](https://docs.rs/servicepoint_binding_c/latest/servicepoint_binding_c/)
## Safety
Functions expect that C code honors NonNull annotations.
Any created instances have to be freed in some way.
Pointers to those instances cannot be used anymore after that.
Instances cannot be shared between threads and need to be locked in the using code.
Enum values have to be used as-is. Do not pass values that are not part of the enum.
UTF-8 or UTF-32 encoding has to be used properly.
Brightness values provided as u8 parameters must be in range.
## Everything else ## Everything else
Look at the main project [README](https://git.berlin.ccc.de/servicepoint/servicepoint/src/branch/main/README.md) for Look at the main project [README](https://git.berlin.ccc.de/servicepoint/servicepoint/src/branch/main/README.md) for further information.
further information.

41
about_display.md Normal file
View file

@ -0,0 +1,41 @@
# About the display
- Resolution: 352x160=56,320 pixels
- Pixels are grouped into 44x20=880 tiles (8x8=64 pixels each)
- Smallest addressable unit: row of pixels inside of a tile (8 pixels = 1 byte)
- The brightness can only be set per tile
- Screen content can be changed using a simple UDP protocol
- Between each row of tiles, there is a gap of around 4 pixels size. This gap changes the aspect ratio of the display.
### Binary format
A UDP package sent to the display has a header size of 10 bytes.
Each header value has a size of two bytes (unsigned 16 bit integer).
Depending on the command, there can be a payload following the header.
To change screen contents, these commands are the most relevant:
1. Clear screen
- command: `0x0002`
- (rest does not matter)
2. Send CP437 data: render specified text into rectangular region
- command: `0x0003`
- top left tile x
- top left tile y
- width in tiles
- height in tiles
- payload: (width in tiles * height in tiles) bytes
- 1 byte = 1 character
- each character is rendered into one tile (mono-spaced)
- characters are encoded using code page 437
3. Send bitmap window: set pixel states for a rectangular region
- command: `0x0013`
- top left tile x
- top left _pixel_ y
- width in tiles
- height in _pixels_
- payload: (width in tiles * height in pixels) bytes
- network byte order
- 1 bit = 1 pixel
There are other commands implemented as well, e.g. for changing the brightness.

View file

@ -35,8 +35,7 @@ include = []
exclude = [] exclude = []
[export.rename] [export.rename]
"SpBitVec" = "BitVec" "TypedCommand" = "Command"
"SpByteSlice" = "ByteSlice"
[enum] [enum]
rename_variants = "QualifiedScreamingSnakeCase" rename_variants = "QualifiedScreamingSnakeCase"

View file

@ -0,0 +1,14 @@
[package]
name = "lang_c"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
test = false
[build-dependencies]
cc = "1.2"
[dependencies]
servicepoint_binding_c = { path = "../.." }

View file

@ -2,7 +2,7 @@ CC := gcc
CARGO := rustup run nightly cargo CARGO := rustup run nightly cargo
THIS_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST)))) THIS_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST))))
REPO_ROOT := $(THIS_DIR)/.. REPO_ROOT := $(THIS_DIR)/../../
RUST_TARGET_DIR := $(REPO_ROOT)/target/x86_64-unknown-linux-musl/size-optimized RUST_TARGET_DIR := $(REPO_ROOT)/target/x86_64-unknown-linux-musl/size-optimized
RUSTFLAGS := -Zlocation-detail=none \ RUSTFLAGS := -Zlocation-detail=none \
@ -41,29 +41,29 @@ CCFLAGS := -static -Os \
-fno-exceptions -fno-exceptions
#-Wl,--icf=all \ #-Wl,--icf=all \
export SERVICEPOINT_HEADER_OUT := $(REPO_ROOT)/include export SERVICEPOINT_HEADER_OUT := $(THIS_DIR)/include
build: out/example build: out/lang_c
clean: clean:
rm -r out || true rm -r out || true
rm include/servicepoint.h || true rm include/servicepoint.h || true
cargo clean cargo clean
run: out/example run: out/lang_c
out/example out/lang_c
PHONY: build clean dependencies run PHONY: build clean dependencies run
out/example_unstripped: dependencies main.c out/lang_c_unstripped: dependencies src/main.c
mkdir -p out || true mkdir -p out || true
${CC} main.c \ ${CC} src/main.c \
-I $(SERVICEPOINT_HEADER_OUT) \ -I $(SERVICEPOINT_HEADER_OUT) \
-L $(RUST_TARGET_DIR)\ -L $(RUST_TARGET_DIR)\
$(CCFLAGS) \ $(CCFLAGS) \
-o out/example_unstripped -o out/lang_c_unstripped
out/example: out/example_unstripped out/lang_c: out/lang_c_unstripped
strip -s -R .comment -R .gnu.version --strip-unneeded out/example_unstripped -o out/example strip -s -R .comment -R .gnu.version --strip-unneeded out/lang_c_unstripped -o out/lang_c
#strip -S --strip-unneeded --remove-section=.note.gnu.gold-version --remove-section=.comment --remove-section=.note --remove-section=.note.gnu.build-id --remove-section=.note.ABI-tag #strip -S --strip-unneeded --remove-section=.note.gnu.gold-version --remove-section=.comment --remove-section=.note --remove-section=.note.gnu.build-id --remove-section=.note.ABI-tag
dependencies: FORCE dependencies: FORCE

17
examples/lang_c/build.rs Normal file
View file

@ -0,0 +1,17 @@
const SP_INCLUDE: &str = "DEP_SERVICEPOINT_INCLUDE";
fn main() {
println!("cargo::rerun-if-changed=src/main.c");
println!("cargo::rerun-if-changed=build.rs");
println!("cargo::rerun-if-env-changed={SP_INCLUDE}");
let sp_include =
std::env::var_os(SP_INCLUDE).unwrap().into_string().unwrap();
// this builds a lib, this is only to check that the example compiles
let mut cc = cc::Build::new();
cc.file("src/main.c");
cc.include(&sp_include);
cc.opt_level(2);
cc.compile("lang_c");
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1 @@

View file

@ -12,7 +12,7 @@ int main(void) {
sp_bitmap_fill(pixels, true); sp_bitmap_fill(pixels, true);
TypedCommand *command = sp_command_bitmap_linear_win(0, 0, pixels, COMPRESSION_CODE_UNCOMPRESSED); Command *command = sp_command_bitmap_linear_win(0, 0, pixels, COMPRESSION_CODE_UNCOMPRESSED);
if (command == NULL) if (command == NULL)
return 1; return 1;

View file

@ -1,6 +0,0 @@
#!/usr/bin/env bash
set -e
SCRIPT_PATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
SERVICEPOINT_HEADER_OUT="$SCRIPT_PATH/include" cargo build --release

File diff suppressed because it is too large Load diff

View file

@ -1,16 +1,31 @@
//! C functions for interacting with [SPBitmap]s
//!
//! prefix `sp_bitmap_`
//!
//! A grid of pixels.
//!
//! # Examples
//!
//! ```C
//! Cp437Grid grid = sp_bitmap_new(8, 3);
//! sp_bitmap_fill(grid, true);
//! sp_bitmap_set(grid, 0, 0, false);
//! sp_bitmap_free(grid);
//! ```
use servicepoint::{Bitmap, DataRef, Grid}; use servicepoint::{Bitmap, DataRef, Grid};
use std::ptr::NonNull; use std::ptr::NonNull;
use crate::byte_slice::ByteSlice; use crate::byte_slice::SPByteSlice;
/// Creates a new [Bitmap] with the specified dimensions. /// Creates a new [SPBitmap] with the specified dimensions.
/// ///
/// # Arguments /// # Arguments
/// ///
/// - `width`: size in pixels in x-direction /// - `width`: size in pixels in x-direction
/// - `height`: size in pixels in y-direction /// - `height`: size in pixels in y-direction
/// ///
/// returns: [Bitmap] initialized to all pixels off, or NULL in case of an error. /// returns: [SPBitmap] initialized to all pixels off, or NULL in case of an error.
/// ///
/// # Errors /// # Errors
/// ///
@ -18,14 +33,12 @@ use crate::byte_slice::ByteSlice;
/// ///
/// - when the width is not dividable by 8 /// - when the width is not dividable by 8
/// ///
/// # Examples /// # Safety
/// ///
/// ```C /// The caller has to make sure that:
/// Cp437Grid grid = sp_bitmap_new(8, 3); ///
/// sp_bitmap_fill(grid, true); /// - the returned instance is freed in some way, either by using a consuming function or
/// sp_bitmap_set(grid, 0, 0, false); /// by explicitly calling `sp_bitmap_free`.
/// sp_bitmap_free(grid);
/// ```
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_bitmap_new( pub unsafe extern "C" fn sp_bitmap_new(
width: usize, width: usize,
@ -38,28 +51,54 @@ pub unsafe extern "C" fn sp_bitmap_new(
} }
} }
/// Creates a new [Bitmap] with a size matching the screen. /// Creates a new [SPBitmap] with a size matching the screen.
/// ///
/// returns: [Bitmap] initialized to all pixels off. /// returns: [SPBitmap] initialized to all pixels off. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling [sp_bitmap_free].
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_bitmap_new_screen_sized() -> NonNull<Bitmap> { pub unsafe extern "C" fn sp_bitmap_new_screen_sized() -> NonNull<Bitmap> {
let result = Box::new(Bitmap::max_sized()); let result = Box::new(Bitmap::max_sized());
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
/// Loads a [Bitmap] with the specified dimensions from the provided data. /// Loads a [SPBitmap] with the specified dimensions from the provided data.
/// ///
/// # Arguments /// # Arguments
/// ///
/// - `width`: size in pixels in x-direction /// - `width`: size in pixels in x-direction
/// - `height`: size in pixels in y-direction /// - `height`: size in pixels in y-direction
/// ///
/// returns: [Bitmap] that contains a copy of the provided data, or NULL in case of an error. /// returns: [SPBitmap] that contains a copy of the provided data, or NULL in case of an error.
///
/// # Errors
///
/// In the following cases, this function will return NULL:
///
/// - when the dimensions and data size do not match exactly.
/// - when the width is not dividable by 8
///
/// # Panics
///
/// - when `data` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `data` points to a valid memory location of at least `data_length` bytes in size.
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_bitmap_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_bitmap_load( pub unsafe extern "C" fn sp_bitmap_load(
width: usize, width: usize,
height: usize, height: usize,
data: ByteSlice, data: SPByteSlice,
) -> *mut Bitmap { ) -> *mut Bitmap {
let data = unsafe { data.as_slice() }; let data = unsafe { data.as_slice() };
if let Ok(bitmap) = Bitmap::load(width, height, data) { if let Ok(bitmap) = Bitmap::load(width, height, data) {
@ -69,7 +108,22 @@ pub unsafe extern "C" fn sp_bitmap_load(
} }
} }
/// Clones a [Bitmap]. /// Clones a [SPBitmap].
///
/// Will never return NULL.
///
/// # Panics
///
/// - when `bitmap` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid [SPBitmap]
/// - `bitmap` is not written to concurrently
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_bitmap_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_bitmap_clone( pub unsafe extern "C" fn sp_bitmap_clone(
bitmap: NonNull<Bitmap>, bitmap: NonNull<Bitmap>,
@ -78,13 +132,25 @@ pub unsafe extern "C" fn sp_bitmap_clone(
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
/// Deallocates a [Bitmap]. /// Deallocates a [SPBitmap].
///
/// # Panics
///
/// - when `bitmap` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid [SPBitmap]
///
/// [SPCommand]: [crate::SPCommand]
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_bitmap_free(bitmap: NonNull<Bitmap>) { pub unsafe extern "C" fn sp_bitmap_free(bitmap: NonNull<Bitmap>) {
_ = unsafe { Box::from_raw(bitmap.as_ptr()) }; _ = unsafe { Box::from_raw(bitmap.as_ptr()) };
} }
/// Gets the current value at the specified position in the [Bitmap]. /// Gets the current value at the specified position in the [SPBitmap].
/// ///
/// # Arguments /// # Arguments
/// ///
@ -93,7 +159,15 @@ pub unsafe extern "C" fn sp_bitmap_free(bitmap: NonNull<Bitmap>) {
/// ///
/// # Panics /// # Panics
/// ///
/// - when `bitmap` is NULL
/// - when accessing `x` or `y` out of bounds /// - when accessing `x` or `y` out of bounds
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid [SPBitmap]
/// - `bitmap` is not written to concurrently
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_bitmap_get( pub unsafe extern "C" fn sp_bitmap_get(
bitmap: NonNull<Bitmap>, bitmap: NonNull<Bitmap>,
@ -103,7 +177,7 @@ pub unsafe extern "C" fn sp_bitmap_get(
unsafe { bitmap.as_ref().get(x, y) } unsafe { bitmap.as_ref().get(x, y) }
} }
/// Sets the value of the specified position in the [Bitmap]. /// Sets the value of the specified position in the [SPBitmap].
/// ///
/// # Arguments /// # Arguments
/// ///
@ -111,9 +185,19 @@ pub unsafe extern "C" fn sp_bitmap_get(
/// - `x` and `y`: position of the cell /// - `x` and `y`: position of the cell
/// - `value`: the value to write to the cell /// - `value`: the value to write to the cell
/// ///
/// returns: old value of the cell
///
/// # Panics /// # Panics
/// ///
/// - when `bitmap` is NULL
/// - when accessing `x` or `y` out of bounds /// - when accessing `x` or `y` out of bounds
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid [SPBitmap]
/// - `bitmap` is not written to or read from concurrently
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_bitmap_set( pub unsafe extern "C" fn sp_bitmap_set(
bitmap: NonNull<Bitmap>, bitmap: NonNull<Bitmap>,
@ -124,18 +208,29 @@ pub unsafe extern "C" fn sp_bitmap_set(
unsafe { (*bitmap.as_ptr()).set(x, y, value) }; unsafe { (*bitmap.as_ptr()).set(x, y, value) };
} }
/// Sets the state of all pixels in the [Bitmap]. /// Sets the state of all pixels in the [SPBitmap].
/// ///
/// # Arguments /// # Arguments
/// ///
/// - `bitmap`: instance to write to /// - `bitmap`: instance to write to
/// - `value`: the value to set all pixels to /// - `value`: the value to set all pixels to
///
/// # Panics
///
/// - when `bitmap` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid [SPBitmap]
/// - `bitmap` is not written to or read from concurrently
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_bitmap_fill(bitmap: NonNull<Bitmap>, value: bool) { pub unsafe extern "C" fn sp_bitmap_fill(bitmap: NonNull<Bitmap>, value: bool) {
unsafe { (*bitmap.as_ptr()).fill(value) }; unsafe { (*bitmap.as_ptr()).fill(value) };
} }
/// Gets the width in pixels of the [Bitmap] instance. /// Gets the width in pixels of the [SPBitmap] instance.
/// ///
/// # Arguments /// # Arguments
/// ///
@ -149,28 +244,48 @@ pub unsafe extern "C" fn sp_bitmap_fill(bitmap: NonNull<Bitmap>, value: bool) {
/// ///
/// The caller has to make sure that: /// The caller has to make sure that:
/// ///
/// - `bitmap` points to a valid [Bitmap] /// - `bitmap` points to a valid [SPBitmap]
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_bitmap_width(bitmap: NonNull<Bitmap>) -> usize { pub unsafe extern "C" fn sp_bitmap_width(bitmap: NonNull<Bitmap>) -> usize {
unsafe { bitmap.as_ref().width() } unsafe { bitmap.as_ref().width() }
} }
/// Gets the height in pixels of the [Bitmap] instance. /// Gets the height in pixels of the [SPBitmap] instance.
/// ///
/// # Arguments /// # Arguments
/// ///
/// - `bitmap`: instance to read from /// - `bitmap`: instance to read from
///
/// # Panics
///
/// - when `bitmap` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid [SPBitmap]
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_bitmap_height(bitmap: NonNull<Bitmap>) -> usize { pub unsafe extern "C" fn sp_bitmap_height(bitmap: NonNull<Bitmap>) -> usize {
unsafe { bitmap.as_ref().height() } unsafe { bitmap.as_ref().height() }
} }
/// Gets an unsafe reference to the data of the [Bitmap] instance. /// Gets an unsafe reference to the data of the [SPBitmap] instance.
/// ///
/// The returned memory is valid for the lifetime of the bitmap. /// # Panics
///
/// - when `bitmap` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid [SPBitmap]
/// - the returned memory range is never accessed after the passed [SPBitmap] has been freed
/// - the returned memory range is never accessed concurrently, either via the [SPBitmap] or directly
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_bitmap_unsafe_data_ref( pub unsafe extern "C" fn sp_bitmap_unsafe_data_ref(
mut bitmap: NonNull<Bitmap>, mut bitmap: NonNull<Bitmap>,
) -> ByteSlice { ) -> SPByteSlice {
unsafe { ByteSlice::from_slice(bitmap.as_mut().data_ref_mut()) } unsafe { SPByteSlice::from_slice(bitmap.as_mut().data_ref_mut()) }
} }

View file

@ -1,6 +1,9 @@
use crate::ByteSlice; //! C functions for interacting with [SPBitVec]s
//!
//! prefix `sp_bitvec_`
use crate::SPByteSlice;
use std::ptr::NonNull; use std::ptr::NonNull;
use servicepoint::BitVecU8Msb0;
/// A vector of bits /// A vector of bits
/// ///
@ -10,7 +13,7 @@ use servicepoint::BitVecU8Msb0;
/// sp_bitvec_set(vec, 5, true); /// sp_bitvec_set(vec, 5, true);
/// sp_bitvec_free(vec); /// sp_bitvec_free(vec);
/// ``` /// ```
pub struct SPBitVec(pub(crate) BitVecU8Msb0); pub struct SPBitVec(pub(crate) servicepoint::BitVecU8Msb0);
impl Clone for SPBitVec { impl Clone for SPBitVec {
fn clone(&self) -> Self { fn clone(&self) -> Self {
@ -24,31 +27,67 @@ impl Clone for SPBitVec {
/// ///
/// - `size`: size in bits. /// - `size`: size in bits.
/// ///
/// returns: [SPBitVec] with all bits set to false. /// returns: [SPBitVec] with all bits set to false. Will never return NULL.
/// ///
/// # Panics /// # Panics
/// ///
/// - when `size` is not divisible by 8. /// - when `size` is not divisible by 8.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_bitvec_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_bitvec_new(size: usize) -> NonNull<SPBitVec> { pub unsafe extern "C" fn sp_bitvec_new(size: usize) -> NonNull<SPBitVec> {
let result = let result =
Box::new(SPBitVec(BitVecU8Msb0::repeat(false, size))); Box::new(SPBitVec(servicepoint::BitVecU8Msb0::repeat(false, size)));
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
/// Interpret the data as a series of bits and load then into a new [SPBitVec] instance. /// Interpret the data as a series of bits and load then into a new [SPBitVec] instance.
/// ///
/// returns: [SPBitVec] instance containing data. /// returns: [SPBitVec] instance containing data. Will never return NULL.
///
/// # Panics
///
/// - when `data` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `data` points to a valid memory location of at least `data_length`
/// bytes in size.
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_bitvec_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_bitvec_load( pub unsafe extern "C" fn sp_bitvec_load(
data: ByteSlice, data: SPByteSlice,
) -> NonNull<SPBitVec> { ) -> NonNull<SPBitVec> {
let data = unsafe { data.as_slice() }; let data = unsafe { data.as_slice() };
let result = Box::new(SPBitVec(BitVecU8Msb0::from_slice(data))); let result =
Box::new(SPBitVec(servicepoint::BitVecU8Msb0::from_slice(data)));
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
/// Clones a [SPBitVec]. /// Clones a [SPBitVec].
///
/// returns: new [SPBitVec] instance. Will never return NULL.
///
/// # Panics
///
/// - when `bit_vec` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid [SPBitVec]
/// - `bit_vec` is not written to concurrently
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_bitvec_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_bitvec_clone( pub unsafe extern "C" fn sp_bitvec_clone(
bit_vec: NonNull<SPBitVec>, bit_vec: NonNull<SPBitVec>,
@ -58,6 +97,20 @@ pub unsafe extern "C" fn sp_bitvec_clone(
} }
/// Deallocates a [SPBitVec]. /// Deallocates a [SPBitVec].
///
/// # Panics
///
/// - when `but_vec` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid [SPBitVec]
/// - `bit_vec` is not used concurrently or after this call
/// - `bit_vec` was not passed to another consuming function, e.g. to create a [SPCommand]
///
/// [SPCommand]: [crate::SPCommand]
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_bitvec_free(bit_vec: NonNull<SPBitVec>) { pub unsafe extern "C" fn sp_bitvec_free(bit_vec: NonNull<SPBitVec>) {
_ = unsafe { Box::from_raw(bit_vec.as_ptr()) }; _ = unsafe { Box::from_raw(bit_vec.as_ptr()) };
@ -74,7 +127,15 @@ pub unsafe extern "C" fn sp_bitvec_free(bit_vec: NonNull<SPBitVec>) {
/// ///
/// # Panics /// # Panics
/// ///
/// - when `bit_vec` is NULL
/// - when accessing `index` out of bounds /// - when accessing `index` out of bounds
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid [SPBitVec]
/// - `bit_vec` is not written to concurrently
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_bitvec_get( pub unsafe extern "C" fn sp_bitvec_get(
bit_vec: NonNull<SPBitVec>, bit_vec: NonNull<SPBitVec>,
@ -93,7 +154,15 @@ pub unsafe extern "C" fn sp_bitvec_get(
/// ///
/// # Panics /// # Panics
/// ///
/// - when `bit_vec` is NULL
/// - when accessing `index` out of bounds /// - when accessing `index` out of bounds
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid [SPBitVec]
/// - `bit_vec` is not written to or read from concurrently
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_bitvec_set( pub unsafe extern "C" fn sp_bitvec_set(
bit_vec: NonNull<SPBitVec>, bit_vec: NonNull<SPBitVec>,
@ -109,6 +178,17 @@ pub unsafe extern "C" fn sp_bitvec_set(
/// ///
/// - `bit_vec`: instance to write to /// - `bit_vec`: instance to write to
/// - `value`: the value to set all bits to /// - `value`: the value to set all bits to
///
/// # Panics
///
/// - when `bit_vec` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid [SPBitVec]
/// - `bit_vec` is not written to or read from concurrently
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_bitvec_fill( pub unsafe extern "C" fn sp_bitvec_fill(
bit_vec: NonNull<SPBitVec>, bit_vec: NonNull<SPBitVec>,
@ -122,6 +202,16 @@ pub unsafe extern "C" fn sp_bitvec_fill(
/// # Arguments /// # Arguments
/// ///
/// - `bit_vec`: instance to write to /// - `bit_vec`: instance to write to
///
/// # Panics
///
/// - when `bit_vec` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid [SPBitVec]
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_bitvec_len(bit_vec: NonNull<SPBitVec>) -> usize { pub unsafe extern "C" fn sp_bitvec_len(bit_vec: NonNull<SPBitVec>) -> usize {
unsafe { bit_vec.as_ref().0.len() } unsafe { bit_vec.as_ref().0.len() }
@ -132,6 +222,16 @@ pub unsafe extern "C" fn sp_bitvec_len(bit_vec: NonNull<SPBitVec>) -> usize {
/// # Arguments /// # Arguments
/// ///
/// - `bit_vec`: instance to write to /// - `bit_vec`: instance to write to
///
/// # Panics
///
/// - when `bit_vec` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid [SPBitVec]
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_bitvec_is_empty( pub unsafe extern "C" fn sp_bitvec_is_empty(
bit_vec: NonNull<SPBitVec>, bit_vec: NonNull<SPBitVec>,
@ -141,14 +241,24 @@ pub unsafe extern "C" fn sp_bitvec_is_empty(
/// Gets an unsafe reference to the data of the [SPBitVec] instance. /// Gets an unsafe reference to the data of the [SPBitVec] instance.
/// ///
/// The returned memory is valid for the lifetime of the bitvec.
///
/// # Arguments /// # Arguments
/// ///
/// - `bit_vec`: instance to write to /// - `bit_vec`: instance to write to
///
/// # Panics
///
/// - when `bit_vec` is NULL
///
/// ## Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid [SPBitVec]
/// - the returned memory range is never accessed after the passed [SPBitVec] has been freed
/// - the returned memory range is never accessed concurrently, either via the [SPBitVec] or directly
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_bitvec_unsafe_data_ref( pub unsafe extern "C" fn sp_bitvec_unsafe_data_ref(
bit_vec: NonNull<SPBitVec>, bit_vec: NonNull<SPBitVec>,
) -> ByteSlice { ) -> SPByteSlice {
unsafe { ByteSlice::from_slice((*bit_vec.as_ptr()).0.as_raw_mut_slice()) } unsafe { SPByteSlice::from_slice((*bit_vec.as_ptr()).0.as_raw_mut_slice()) }
} }

View file

@ -1,25 +1,47 @@
use crate::ByteSlice; //! C functions for interacting with [SPBrightnessGrid]s
use servicepoint::{Brightness, BrightnessGrid, ByteGrid, DataRef, Grid}; //!
//! prefix `sp_brightness_grid_`
//!
//!
//! A grid containing brightness values.
//!
//! # Examples
//! ```C
//! SPConnection connection = sp_connection_open("127.0.0.1:2342");
//! if (connection == NULL)
//! return 1;
//!
//! SPBrightnessGrid grid = sp_brightness_grid_new(2, 2);
//! sp_brightness_grid_set(grid, 0, 0, 0);
//! sp_brightness_grid_set(grid, 1, 1, 10);
//!
//! SPCommand command = sp_command_char_brightness(grid);
//! sp_connection_free(connection);
//! ```
use crate::SPByteSlice;
use servicepoint::{BrightnessGrid, DataRef, Grid};
use std::convert::Into;
use std::mem::transmute; use std::mem::transmute;
use std::ptr::NonNull; use std::ptr::NonNull;
/// Creates a new [BrightnessGrid] with the specified dimensions. /// see [servicepoint::Brightness::MIN]
pub const SP_BRIGHTNESS_MIN: u8 = 0;
/// see [servicepoint::Brightness::MAX]
pub const SP_BRIGHTNESS_MAX: u8 = 11;
/// Count of possible brightness values
pub const SP_BRIGHTNESS_LEVELS: u8 = 12;
/// Creates a new [SPBrightnessGrid] with the specified dimensions.
/// ///
/// returns: [BrightnessGrid] initialized to 0. /// returns: [SPBrightnessGrid] initialized to 0. Will never return NULL.
/// ///
/// # Examples /// # Safety
/// ```C
/// UdpConnection connection = sp_connection_open("127.0.0.1:2342");
/// if (connection == NULL)
/// return 1;
/// ///
/// BrightnessGrid grid = sp_brightness_grid_new(2, 2); /// The caller has to make sure that:
/// sp_brightness_grid_set(grid, 0, 0, 0);
/// sp_brightness_grid_set(grid, 1, 1, 10);
/// ///
/// TypedCommand command = sp_command_char_brightness(grid); /// - the returned instance is freed in some way, either by using a consuming function or
/// sp_connection_free(connection); /// by explicitly calling `sp_brightness_grid_free`.
/// ```
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_new( pub unsafe extern "C" fn sp_brightness_grid_new(
width: usize, width: usize,
@ -29,17 +51,31 @@ pub unsafe extern "C" fn sp_brightness_grid_new(
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
/// Loads a [BrightnessGrid] with the specified dimensions from the provided data. /// Loads a [SPBrightnessGrid] with the specified dimensions from the provided data.
/// ///
/// returns: new [BrightnessGrid] instance, or NULL in case of an error. /// returns: new [SPBrightnessGrid] instance. Will never return NULL.
///
/// # Panics
///
/// - when `data` is NULL
/// - when the provided `data_length` does not match `height` and `width`
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `data` points to a valid memory location of at least `data_length`
/// bytes in size.
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_brightness_grid_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_load( pub unsafe extern "C" fn sp_brightness_grid_load(
width: usize, width: usize,
height: usize, height: usize,
data: ByteSlice, data: SPByteSlice,
) -> *mut BrightnessGrid { ) -> *mut BrightnessGrid {
let data = unsafe { data.as_slice() }; let data = unsafe { data.as_slice() };
let grid = match ByteGrid::load(width, height, data) { let grid = match servicepoint::ByteGrid::load(width, height, data) {
None => return std::ptr::null_mut(), None => return std::ptr::null_mut(),
Some(grid) => grid, Some(grid) => grid,
}; };
@ -50,7 +86,26 @@ pub unsafe extern "C" fn sp_brightness_grid_load(
} }
} }
/// Clones a [BrightnessGrid]. /// Clones a [SPBrightnessGrid].
///
/// # Arguments
///
/// - `brightness_grid`: instance to read from
///
/// returns: new [SPBrightnessGrid] instance. Will never return NULL.
///
/// # Panics
///
/// - when `brightness_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `brightness_grid` points to a valid [SPBrightnessGrid]
/// - `brightness_grid` is not written to concurrently
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_brightness_grid_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_clone( pub unsafe extern "C" fn sp_brightness_grid_clone(
brightness_grid: NonNull<BrightnessGrid>, brightness_grid: NonNull<BrightnessGrid>,
@ -59,7 +114,25 @@ pub unsafe extern "C" fn sp_brightness_grid_clone(
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
/// Deallocates a [BrightnessGrid]. /// Deallocates a [SPBrightnessGrid].
///
/// # Arguments
///
/// - `brightness_grid`: instance to read from
///
/// # Panics
///
/// - when `brightness_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `brightness_grid` points to a valid [SPBrightnessGrid]
/// - `brightness_grid` is not used concurrently or after this call
/// - `brightness_grid` was not passed to another consuming function, e.g. to create a [SPCommand]
///
/// [SPCommand]: [crate::SPCommand]
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_free( pub unsafe extern "C" fn sp_brightness_grid_free(
brightness_grid: NonNull<BrightnessGrid>, brightness_grid: NonNull<BrightnessGrid>,
@ -77,17 +150,26 @@ pub unsafe extern "C" fn sp_brightness_grid_free(
/// returns: value at position /// returns: value at position
/// ///
/// # Panics /// # Panics
///
/// - when `brightness_grid` is NULL
/// - When accessing `x` or `y` out of bounds. /// - When accessing `x` or `y` out of bounds.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `brightness_grid` points to a valid [SPBrightnessGrid]
/// - `brightness_grid` is not written to concurrently
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_get( pub unsafe extern "C" fn sp_brightness_grid_get(
brightness_grid: NonNull<BrightnessGrid>, brightness_grid: NonNull<BrightnessGrid>,
x: usize, x: usize,
y: usize, y: usize,
) -> Brightness { ) -> u8 {
unsafe { brightness_grid.as_ref().get(x, y) } unsafe { brightness_grid.as_ref().get(x, y) }.into()
} }
/// Sets the value of the specified position in the [BrightnessGrid]. /// Sets the value of the specified position in the [SPBrightnessGrid].
/// ///
/// # Arguments /// # Arguments
/// ///
@ -99,38 +181,73 @@ pub unsafe extern "C" fn sp_brightness_grid_get(
/// ///
/// # Panics /// # Panics
/// ///
/// - when `brightness_grid` is NULL
/// - When accessing `x` or `y` out of bounds. /// - When accessing `x` or `y` out of bounds.
/// - When providing an invalid brightness value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `brightness_grid` points to a valid [SPBrightnessGrid]
/// - `brightness_grid` is not written to or read from concurrently
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_set( pub unsafe extern "C" fn sp_brightness_grid_set(
brightness_grid: NonNull<BrightnessGrid>, brightness_grid: NonNull<BrightnessGrid>,
x: usize, x: usize,
y: usize, y: usize,
value: Brightness, value: u8,
) { ) {
unsafe { (*brightness_grid.as_ptr()).set(x, y, value) }; let brightness = servicepoint::Brightness::try_from(value)
.expect("invalid brightness value");
unsafe { (*brightness_grid.as_ptr()).set(x, y, brightness) };
} }
/// Sets the value of all cells in the [BrightnessGrid]. /// Sets the value of all cells in the [SPBrightnessGrid].
/// ///
/// # Arguments /// # Arguments
/// ///
/// - `brightness_grid`: instance to write to /// - `brightness_grid`: instance to write to
/// - `value`: the value to set all cells to /// - `value`: the value to set all cells to
///
/// # Panics
///
/// - when `brightness_grid` is NULL
/// - When providing an invalid brightness value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `brightness_grid` points to a valid [SPBrightnessGrid]
/// - `brightness_grid` is not written to or read from concurrently
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_fill( pub unsafe extern "C" fn sp_brightness_grid_fill(
brightness_grid: NonNull<BrightnessGrid>, brightness_grid: NonNull<BrightnessGrid>,
value: Brightness, value: u8,
) { ) {
unsafe { (*brightness_grid.as_ptr()).fill(value) }; let brightness = servicepoint::Brightness::try_from(value)
.expect("invalid brightness value");
unsafe { (*brightness_grid.as_ptr()).fill(brightness) };
} }
/// Gets the width of the [BrightnessGrid] instance. /// Gets the width of the [SPBrightnessGrid] instance.
/// ///
/// # Arguments /// # Arguments
/// ///
/// - `brightness_grid`: instance to read from /// - `brightness_grid`: instance to read from
/// ///
/// returns: width /// returns: width
///
/// # Panics
///
/// - when `brightness_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `brightness_grid` points to a valid [SPBrightnessGrid]
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_width( pub unsafe extern "C" fn sp_brightness_grid_width(
brightness_grid: NonNull<BrightnessGrid>, brightness_grid: NonNull<BrightnessGrid>,
@ -138,13 +255,23 @@ pub unsafe extern "C" fn sp_brightness_grid_width(
unsafe { brightness_grid.as_ref().width() } unsafe { brightness_grid.as_ref().width() }
} }
/// Gets the height of the [BrightnessGrid] instance. /// Gets the height of the [SPBrightnessGrid] instance.
/// ///
/// # Arguments /// # Arguments
/// ///
/// - `brightness_grid`: instance to read from /// - `brightness_grid`: instance to read from
/// ///
/// returns: height /// returns: height
///
/// # Panics
///
/// - when `brightness_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `brightness_grid` points to a valid [SPBrightnessGrid]
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_height( pub unsafe extern "C" fn sp_brightness_grid_height(
brightness_grid: NonNull<BrightnessGrid>, brightness_grid: NonNull<BrightnessGrid>,
@ -152,22 +279,31 @@ pub unsafe extern "C" fn sp_brightness_grid_height(
unsafe { brightness_grid.as_ref().height() } unsafe { brightness_grid.as_ref().height() }
} }
/// Gets an unsafe reference to the data of the [BrightnessGrid] instance. /// Gets an unsafe reference to the data of the [SPBrightnessGrid] instance.
///
/// The returned memory is valid for the lifetime of the brightness grid.
/// ///
/// # Arguments /// # Arguments
/// ///
/// - `brightness_grid`: instance to read from /// - `brightness_grid`: instance to read from
/// ///
/// returns: slice of bytes underlying the `brightness_grid`. /// returns: slice of bytes underlying the `brightness_grid`.
///
/// # Panics
///
/// - when `brightness_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `brightness_grid` points to a valid [SPBrightnessGrid]
/// - the returned memory range is never accessed after the passed [SPBrightnessGrid] has been freed
/// - the returned memory range is never accessed concurrently, either via the [SPBrightnessGrid] or directly
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_unsafe_data_ref( pub unsafe extern "C" fn sp_brightness_grid_unsafe_data_ref(
brightness_grid: NonNull<BrightnessGrid>, brightness_grid: NonNull<BrightnessGrid>,
) -> ByteSlice { ) -> SPByteSlice {
//noinspection RsAssertEqual assert_eq!(size_of::<servicepoint::Brightness>(), 1);
const _: () = assert!(size_of::<Brightness>() == 1);
let data = unsafe { (*brightness_grid.as_ptr()).data_ref_mut() }; let data = unsafe { (*brightness_grid.as_ptr()).data_ref_mut() };
unsafe { ByteSlice::from_slice(transmute(data)) } // this assumes more about the memory layout than rust guarantees. yikes!
unsafe { SPByteSlice::from_slice(transmute(data)) }
} }

View file

@ -2,7 +2,9 @@
use std::ptr::NonNull; use std::ptr::NonNull;
/// Represents a span of memory (`&mut [u8]` ) as a struct. /// Represents a span of memory (`&mut [u8]` ) as a struct usable by C code.
///
/// You should not create an instance of this type in your C code.
/// ///
/// # Safety /// # Safety
/// ///
@ -11,15 +13,17 @@ use std::ptr::NonNull;
/// - accesses to the memory pointed to with `start` is never accessed outside `length` /// - accesses to the memory pointed to with `start` is never accessed outside `length`
/// - the lifetime of the `CByteSlice` does not outlive the memory it points to, as described in /// - the lifetime of the `CByteSlice` does not outlive the memory it points to, as described in
/// the function returning this type. /// the function returning this type.
/// - an instance of this created from C is never passed to a consuming function, as the rust code
/// will try to free the memory of a potentially separate allocator.
#[repr(C)] #[repr(C)]
pub struct ByteSlice { pub struct SPByteSlice {
/// The start address of the memory. /// The start address of the memory
pub start: NonNull<u8>, pub start: NonNull<u8>,
/// The amount of memory in bytes. /// The amount of memory in bytes
pub length: usize, pub length: usize,
} }
impl ByteSlice { impl SPByteSlice {
pub(crate) unsafe fn as_slice(&self) -> &[u8] { pub(crate) unsafe fn as_slice(&self) -> &[u8] {
unsafe { std::slice::from_raw_parts(self.start.as_ptr(), self.length) } unsafe { std::slice::from_raw_parts(self.start.as_ptr(), self.length) }
} }

View file

@ -1,19 +1,37 @@
use crate::ByteSlice; //! C functions for interacting with [SPCharGrid]s
//!
//! prefix `sp_char_grid_`
//!
//! A C-wrapper for grid containing UTF-8 characters.
//!
//! As the rust [char] type is not FFI-safe, characters are passed in their UTF-32 form as 32bit unsigned integers.
//!
//! The encoding is enforced in most cases by the rust standard library
//! and will panic when provided with illegal characters.
//!
//! # Examples
//!
//! ```C
//! CharGrid grid = sp_char_grid_new(4, 3);
//! sp_char_grid_fill(grid, '?');
//! sp_char_grid_set(grid, 0, 0, '!');
//! sp_char_grid_free(grid);
//! ```
use crate::SPByteSlice;
use servicepoint::{CharGrid, Grid}; use servicepoint::{CharGrid, Grid};
use std::ptr::NonNull; use std::ptr::NonNull;
/// Creates a new [CharGrid] with the specified dimensions. /// Creates a new [SPCharGrid] with the specified dimensions.
/// ///
/// returns: [CharGrid] initialized to 0. /// returns: [SPCharGrid] initialized to 0. Will never return NULL.
/// ///
/// # Examples /// # Safety
/// ///
/// ```C /// The caller has to make sure that:
/// CharGrid grid = sp_char_grid_new(4, 3); ///
/// sp_char_grid_fill(grid, '?'); /// - the returned instance is freed in some way, either by using a consuming function or
/// sp_char_grid_set(grid, 0, 0, '!'); /// by explicitly calling `sp_char_grid_free`.
/// sp_char_grid_free(grid);
/// ```
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_char_grid_new( pub unsafe extern "C" fn sp_char_grid_new(
width: usize, width: usize,
@ -23,39 +41,82 @@ pub unsafe extern "C" fn sp_char_grid_new(
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
/// Loads a [CharGrid] with the specified dimensions from the provided data. /// Loads a [SPCharGrid] with the specified dimensions from the provided data.
/// ///
/// returns: new CharGrid or NULL in case of an error /// Will never return NULL.
///
/// # Panics
///
/// - when `data` is NULL
/// - when the provided `data_length` does not match `height` and `width`
/// - when `data` is not valid UTF-8
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `data` points to a valid memory location of at least `data_length`
/// bytes in size.
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_char_grid_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_char_grid_load( pub unsafe extern "C" fn sp_char_grid_load(
width: usize, width: usize,
height: usize, height: usize,
data: ByteSlice, data: SPByteSlice,
) -> *mut CharGrid { ) -> NonNull<CharGrid> {
let data = unsafe { data.as_slice() }; let data = unsafe { data.as_slice() };
if let Ok(grid) = CharGrid::load_utf8(width, height, data.to_vec()) { // TODO remove unwrap
Box::leak(Box::new(grid)) let result =
} else { Box::new(CharGrid::load_utf8(width, height, data.to_vec()).unwrap());
std::ptr::null_mut() NonNull::from(Box::leak(result))
}
} }
/// Clones a [CharGrid]. /// Clones a [SPCharGrid].
///
/// Will never return NULL.
///
/// # Panics
///
/// - when `char_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `char_grid` points to a valid [SPCharGrid]
/// - `char_grid` is not written to concurrently
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_char_grid_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_char_grid_clone( pub unsafe extern "C" fn sp_char_grid_clone(
char_grid: NonNull<CharGrid>, char_grid: NonNull<CharGrid>,
) -> NonNull<CharGrid> { ) -> NonNull<CharGrid> {
let result = unsafe { char_grid.as_ref().clone() }; let result = Box::new(unsafe { char_grid.as_ref().clone() });
NonNull::from(Box::leak(Box::new(result))) NonNull::from(Box::leak(result))
} }
/// Deallocates a [CharGrid]. /// Deallocates a [SPCharGrid].
///
/// # Panics
///
/// - when `char_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `char_grid` points to a valid [SPCharGrid]
/// - `char_grid` is not used concurrently or after char_grid call
/// - `char_grid` was not passed to another consuming function, e.g. to create a [SPCommand]
///
/// [SPCommand]: [crate::SPCommand]
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_char_grid_free(char_grid: NonNull<CharGrid>) { pub unsafe extern "C" fn sp_char_grid_free(char_grid: NonNull<CharGrid>) {
_ = unsafe { Box::from_raw(char_grid.as_ptr()) }; _ = unsafe { Box::from_raw(char_grid.as_ptr()) };
} }
/// Returns the current value at the specified position. /// Gets the current value at the specified position.
/// ///
/// # Arguments /// # Arguments
/// ///
@ -64,7 +125,15 @@ pub unsafe extern "C" fn sp_char_grid_free(char_grid: NonNull<CharGrid>) {
/// ///
/// # Panics /// # Panics
/// ///
/// - when `char_grid` is NULL
/// - when accessing `x` or `y` out of bounds /// - when accessing `x` or `y` out of bounds
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `char_grid` points to a valid [SPCharGrid]
/// - `char_grid` is not written to concurrently
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_char_grid_get( pub unsafe extern "C" fn sp_char_grid_get(
char_grid: NonNull<CharGrid>, char_grid: NonNull<CharGrid>,
@ -74,7 +143,7 @@ pub unsafe extern "C" fn sp_char_grid_get(
unsafe { char_grid.as_ref().get(x, y) as u32 } unsafe { char_grid.as_ref().get(x, y) as u32 }
} }
/// Sets the value of the specified position in the [CharGrid]. /// Sets the value of the specified position in the [SPCharGrid].
/// ///
/// # Arguments /// # Arguments
/// ///
@ -86,7 +155,17 @@ pub unsafe extern "C" fn sp_char_grid_get(
/// ///
/// # Panics /// # Panics
/// ///
/// - when `char_grid` is NULL
/// - when accessing `x` or `y` out of bounds /// - when accessing `x` or `y` out of bounds
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `char_grid` points to a valid [SPBitVec]
/// - `char_grid` is not written to or read from concurrently
///
/// [SPBitVec]: [crate::SPBitVec]
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_char_grid_set( pub unsafe extern "C" fn sp_char_grid_set(
char_grid: NonNull<CharGrid>, char_grid: NonNull<CharGrid>,
@ -97,12 +176,23 @@ pub unsafe extern "C" fn sp_char_grid_set(
unsafe { (*char_grid.as_ptr()).set(x, y, char::from_u32(value).unwrap()) }; unsafe { (*char_grid.as_ptr()).set(x, y, char::from_u32(value).unwrap()) };
} }
/// Sets the value of all cells in the [CharGrid]. /// Sets the value of all cells in the [SPCharGrid].
/// ///
/// # Arguments /// # Arguments
/// ///
/// - `char_grid`: instance to write to /// - `char_grid`: instance to write to
/// - `value`: the value to set all cells to /// - `value`: the value to set all cells to
///
/// # Panics
///
/// - when `char_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `char_grid` points to a valid [SPCharGrid]
/// - `char_grid` is not written to or read from concurrently
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_char_grid_fill( pub unsafe extern "C" fn sp_char_grid_fill(
char_grid: NonNull<CharGrid>, char_grid: NonNull<CharGrid>,
@ -111,11 +201,21 @@ pub unsafe extern "C" fn sp_char_grid_fill(
unsafe { (*char_grid.as_ptr()).fill(char::from_u32(value).unwrap()) }; unsafe { (*char_grid.as_ptr()).fill(char::from_u32(value).unwrap()) };
} }
/// Gets the width of the [CharGrid] instance. /// Gets the width of the [SPCharGrid] instance.
/// ///
/// # Arguments /// # Arguments
/// ///
/// - `char_grid`: instance to read from /// - `char_grid`: instance to read from
///
/// # Panics
///
/// - when `char_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `char_grid` points to a valid [SPCharGrid]
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_char_grid_width( pub unsafe extern "C" fn sp_char_grid_width(
char_grid: NonNull<CharGrid>, char_grid: NonNull<CharGrid>,
@ -123,11 +223,21 @@ pub unsafe extern "C" fn sp_char_grid_width(
unsafe { char_grid.as_ref().width() } unsafe { char_grid.as_ref().width() }
} }
/// Gets the height of the [CharGrid] instance. /// Gets the height of the [SPCharGrid] instance.
/// ///
/// # Arguments /// # Arguments
/// ///
/// - `char_grid`: instance to read from /// - `char_grid`: instance to read from
///
/// # Panics
///
/// - when `char_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `char_grid` points to a valid [SPCharGrid]
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_char_grid_height( pub unsafe extern "C" fn sp_char_grid_height(
char_grid: NonNull<CharGrid>, char_grid: NonNull<CharGrid>,

View file

@ -1,12 +1,48 @@
//! C functions for interacting with [SPCommand]s
//!
//! prefix `sp_command_`
use crate::SPBitVec; use crate::SPBitVec;
use servicepoint::{BinaryOperation, Bitmap, Brightness, BrightnessGrid, CharGrid, CompressionCode, Cp437Grid, GlobalBrightnessCommand, Packet, TypedCommand}; use servicepoint::{
BinaryOperation, Bitmap, BrightnessGrid, CharGrid, CompressionCode,
Cp437Grid, GlobalBrightnessCommand, Packet, TypedCommand,
};
use std::ptr::NonNull; use std::ptr::NonNull;
/// Tries to turn a [Packet] into a [TypedCommand]. /// A low-level display command.
///
/// This struct and associated functions implement the UDP protocol for the display.
///
/// To send a [SPCommand], use a [SPConnection].
///
/// # Examples
///
/// ```C
/// sp_connection_send_command(connection, sp_command_clear());
/// sp_connection_send_command(connection, sp_command_brightness(5));
/// ```
///
/// [SPConnection]: [crate::SPConnection]
/// Tries to turn a [SPPacket] into a [SPCommand].
/// ///
/// The packet is deallocated in the process. /// The packet is deallocated in the process.
/// ///
/// Returns: pointer to new [TypedCommand] instance or NULL if parsing failed. /// Returns: pointer to new [SPCommand] instance or NULL if parsing failed.
///
/// # Panics
///
/// - when `packet` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - [SPPacket] points to a valid instance of [SPPacket]
/// - [SPPacket] is not used concurrently or after this call
/// - the result is checked for NULL
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_command_try_from_packet( pub unsafe extern "C" fn sp_command_try_from_packet(
packet: NonNull<Packet>, packet: NonNull<Packet>,
@ -18,9 +54,22 @@ pub unsafe extern "C" fn sp_command_try_from_packet(
} }
} }
/// Clones a [TypedCommand] instance. /// Clones a [SPCommand] instance.
/// ///
/// returns: new [TypedCommand] instance. /// returns: new [SPCommand] instance. Will never return NULL.
///
/// # Panics
///
/// - when `command` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `command` points to a valid instance of [SPCommand]
/// - `command` is not written to concurrently
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_command_clone( pub unsafe extern "C" fn sp_command_clone(
command: NonNull<TypedCommand>, command: NonNull<TypedCommand>,
@ -33,13 +82,20 @@ pub unsafe extern "C" fn sp_command_clone(
/// ///
/// Does not affect brightness. /// Does not affect brightness.
/// ///
/// Returns: a new [servicepoint::Command::Clear] instance. /// Returns: a new [servicepoint::Command::Clear] instance. Will never return NULL.
/// ///
/// # Examples /// # Examples
/// ///
/// ```C /// ```C
/// sp_connection_send_command(connection, sp_command_clear()); /// sp_connection_send_command(connection, sp_command_clear());
/// ``` /// ```
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_command_clear() -> NonNull<TypedCommand> { pub unsafe extern "C" fn sp_command_clear() -> NonNull<TypedCommand> {
let result = Box::new(servicepoint::ClearCommand.into()); let result = Box::new(servicepoint::ClearCommand.into());
@ -50,7 +106,14 @@ pub unsafe extern "C" fn sp_command_clear() -> NonNull<TypedCommand> {
/// ///
/// Please do not send this in your normal program flow. /// Please do not send this in your normal program flow.
/// ///
/// Returns: a new [servicepoint::Command::HardReset] instance. /// Returns: a new [servicepoint::Command::HardReset] instance. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_command_hard_reset() -> NonNull<TypedCommand> { pub unsafe extern "C" fn sp_command_hard_reset() -> NonNull<TypedCommand> {
let result = Box::new(servicepoint::HardResetCommand.into()); let result = Box::new(servicepoint::HardResetCommand.into());
@ -59,7 +122,14 @@ pub unsafe extern "C" fn sp_command_hard_reset() -> NonNull<TypedCommand> {
/// A yet-to-be-tested command. /// A yet-to-be-tested command.
/// ///
/// Returns: a new [servicepoint::Command::FadeOut] instance. /// Returns: a new [servicepoint::Command::FadeOut] instance. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_command_fade_out() -> NonNull<TypedCommand> { pub unsafe extern "C" fn sp_command_fade_out() -> NonNull<TypedCommand> {
let result = Box::new(servicepoint::FadeOutCommand.into()); let result = Box::new(servicepoint::FadeOutCommand.into());
@ -68,20 +138,46 @@ pub unsafe extern "C" fn sp_command_fade_out() -> NonNull<TypedCommand> {
/// Set the brightness of all tiles to the same value. /// Set the brightness of all tiles to the same value.
/// ///
/// Returns: a new [servicepoint::Command::Brightness] instance. /// Returns: a new [servicepoint::Command::Brightness] instance. Will never return NULL.
///
/// # Panics
///
/// - When the provided brightness value is out of range (0-11).
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_command_brightness( pub unsafe extern "C" fn sp_command_brightness(
brightness: Brightness, brightness: u8,
) -> NonNull<TypedCommand> { ) -> NonNull<TypedCommand> {
let brightness = servicepoint::Brightness::try_from(brightness)
.expect("invalid brightness");
let result = Box::new(GlobalBrightnessCommand::from(brightness).into()); let result = Box::new(GlobalBrightnessCommand::from(brightness).into());
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
/// Set the brightness of individual tiles in a rectangular area of the display. /// Set the brightness of individual tiles in a rectangular area of the display.
/// ///
/// The passed [BrightnessGrid] gets consumed. /// The passed [SPBrightnessGrid] gets consumed.
/// ///
/// Returns: a new [servicepoint::Command::CharBrightness] instance. /// Returns: a new [servicepoint::Command::CharBrightness] instance. Will never return NULL.
///
/// # Panics
///
/// - when `grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `grid` points to a valid instance of [SPBrightnessGrid]
/// - `grid` is not used concurrently or after this call
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_command_char_brightness( pub unsafe extern "C" fn sp_command_char_brightness(
x: usize, x: usize,
@ -108,7 +204,22 @@ pub unsafe extern "C" fn sp_command_char_brightness(
/// ///
/// The passed [SPBitVec] gets consumed. /// The passed [SPBitVec] gets consumed.
/// ///
/// Returns: a new [servicepoint::Command::BitmapLinear] instance. /// Returns: a new [servicepoint::Command::BitmapLinear] instance. Will never return NULL.
///
/// # Panics
///
/// - when `bit_vec` is null
/// - when `compression_code` is not a valid value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid instance of [SPBitVec]
/// - `bit_vec` is not used concurrently or after this call
/// - `compression` matches one of the allowed enum values
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_command_bitmap_linear( pub unsafe extern "C" fn sp_command_bitmap_linear(
offset: usize, offset: usize,
@ -134,7 +245,22 @@ pub unsafe extern "C" fn sp_command_bitmap_linear(
/// ///
/// The passed [SPBitVec] gets consumed. /// The passed [SPBitVec] gets consumed.
/// ///
/// Returns: a new [servicepoint::Command::BitmapLinearAnd] instance. /// Returns: a new [servicepoint::Command::BitmapLinearAnd] instance. Will never return NULL.
///
/// # Panics
///
/// - when `bit_vec` is null
/// - when `compression_code` is not a valid value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid instance of [SPBitVec]
/// - `bit_vec` is not used concurrently or after this call
/// - `compression` matches one of the allowed enum values
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_command_bitmap_linear_and( pub unsafe extern "C" fn sp_command_bitmap_linear_and(
offset: usize, offset: usize,
@ -160,7 +286,22 @@ pub unsafe extern "C" fn sp_command_bitmap_linear_and(
/// ///
/// The passed [SPBitVec] gets consumed. /// The passed [SPBitVec] gets consumed.
/// ///
/// Returns: a new [servicepoint::Command::BitmapLinearOr] instance. /// Returns: a new [servicepoint::Command::BitmapLinearOr] instance. Will never return NULL.
///
/// # Panics
///
/// - when `bit_vec` is null
/// - when `compression_code` is not a valid value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid instance of [SPBitVec]
/// - `bit_vec` is not used concurrently or after this call
/// - `compression` matches one of the allowed enum values
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_command_bitmap_linear_or( pub unsafe extern "C" fn sp_command_bitmap_linear_or(
offset: usize, offset: usize,
@ -186,7 +327,22 @@ pub unsafe extern "C" fn sp_command_bitmap_linear_or(
/// ///
/// The passed [SPBitVec] gets consumed. /// The passed [SPBitVec] gets consumed.
/// ///
/// Returns: a new [servicepoint::Command::BitmapLinearXor] instance. /// Returns: a new [servicepoint::Command::BitmapLinearXor] instance. Will never return NULL.
///
/// # Panics
///
/// - when `bit_vec` is null
/// - when `compression_code` is not a valid value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid instance of [SPBitVec]
/// - `bit_vec` is not used concurrently or after this call
/// - `compression` matches one of the allowed enum values
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_command_bitmap_linear_xor( pub unsafe extern "C" fn sp_command_bitmap_linear_xor(
offset: usize, offset: usize,
@ -203,6 +359,7 @@ pub unsafe extern "C" fn sp_command_bitmap_linear_xor(
} }
} }
#[inline]
unsafe fn sp_command_bitmap_linear_internal( unsafe fn sp_command_bitmap_linear_internal(
offset: usize, offset: usize,
bit_vec: NonNull<SPBitVec>, bit_vec: NonNull<SPBitVec>,
@ -226,9 +383,22 @@ unsafe fn sp_command_bitmap_linear_internal(
/// Show codepage 437 encoded text on the screen. /// Show codepage 437 encoded text on the screen.
/// ///
/// The passed [Cp437Grid] gets consumed. /// The passed [SPCp437Grid] gets consumed.
/// ///
/// Returns: a new [servicepoint::Command::Cp437Data] instance. /// Returns: a new [servicepoint::Command::Cp437Data] instance. Will never return NULL.
///
/// # Panics
///
/// - when `grid` is null
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `grid` points to a valid instance of [SPCp437Grid]
/// - `grid` is not used concurrently or after this call
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_command_cp437_data( pub unsafe extern "C" fn sp_command_cp437_data(
x: usize, x: usize,
@ -248,9 +418,22 @@ pub unsafe extern "C" fn sp_command_cp437_data(
/// Show UTF-8 encoded text on the screen. /// Show UTF-8 encoded text on the screen.
/// ///
/// The passed [CharGrid] gets consumed. /// The passed [SPCharGrid] gets consumed.
/// ///
/// Returns: a new [servicepoint::Command::Utf8Data] instance. /// Returns: a new [servicepoint::Command::Utf8Data] instance. Will never return NULL.
///
/// # Panics
///
/// - when `grid` is null
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `grid` points to a valid instance of [SPCharGrid]
/// - `grid` is not used concurrently or after this call
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_command_utf8_data( pub unsafe extern "C" fn sp_command_utf8_data(
x: usize, x: usize,
@ -270,9 +453,24 @@ pub unsafe extern "C" fn sp_command_utf8_data(
/// Sets a window of pixels to the specified values. /// Sets a window of pixels to the specified values.
/// ///
/// The passed [Bitmap] gets consumed. /// The passed [SPBitmap] gets consumed.
/// ///
/// Returns: a new [servicepoint::Command::BitmapLinearWin] instance. /// Returns: a new [servicepoint::Command::BitmapLinearWin] instance. Will never return NULL.
///
/// # Panics
///
/// - when `bitmap` is null
/// - when `compression_code` is not a valid value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid instance of [SPBitmap]
/// - `bitmap` is not used concurrently or after this call
/// - `compression` matches one of the allowed enum values
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_command_bitmap_linear_win( pub unsafe extern "C" fn sp_command_bitmap_linear_win(
x: usize, x: usize,
@ -294,14 +492,26 @@ pub unsafe extern "C" fn sp_command_bitmap_linear_win(
Box::leak(Box::new(command)) Box::leak(Box::new(command))
} }
/// Deallocates a [TypedCommand]. /// Deallocates a [SPCommand].
/// ///
/// # Examples /// # Examples
/// ///
/// ```C /// ```C
/// TypedCommand c = sp_command_clear(); /// SPCommand c = sp_command_clear();
/// sp_command_free(c); /// sp_command_free(c);
/// ``` /// ```
///
/// # Panics
///
/// - when `command` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `command` points to a valid [SPCommand]
/// - `command` is not used concurrently or after this call
/// - `command` was not passed to another consuming function, e.g. to create a [SPPacket]
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_command_free(command: NonNull<TypedCommand>) { pub unsafe extern "C" fn sp_command_free(command: NonNull<TypedCommand>) {
_ = unsafe { Box::from_raw(command.as_ptr()) }; _ = unsafe { Box::from_raw(command.as_ptr()) };

View file

@ -1,18 +1,35 @@
//! C functions for interacting with [SPConnection]s
//!
//! prefix `sp_connection_`
//!
//! A connection to the display.
//!
//! # Examples
//!
//! ```C
//! CConnection connection = sp_connection_open("172.23.42.29:2342");
//! if (connection != NULL)
//! sp_connection_send_command(connection, sp_command_clear());
//! ```
use servicepoint::{Connection, Packet, TypedCommand, UdpConnection}; use servicepoint::{Connection, Packet, TypedCommand, UdpConnection};
use std::ffi::{c_char, CStr}; use std::ffi::{c_char, CStr};
use std::ptr::NonNull; use std::ptr::NonNull;
/// Creates a new instance of [UdpConnection]. /// Creates a new instance of [SPConnection].
/// ///
/// returns: NULL if connection fails, or connected instance /// returns: NULL if connection fails, or connected instance
/// ///
/// # Examples /// # Panics
/// ///
/// ```C /// - when `host` is null or an invalid host
/// CConnection connection = sp_connection_open("172.23.42.29:2342"); ///
/// if (connection != NULL) /// # Safety
/// sp_connection_send_command(connection, sp_command_clear()); ///
/// ``` /// The caller has to make sure that:
///
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_connection_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_connection_open( pub unsafe extern "C" fn sp_connection_open(
host: NonNull<c_char>, host: NonNull<c_char>,
@ -31,18 +48,18 @@ pub unsafe extern "C" fn sp_connection_open(
//#[no_mangle] //#[no_mangle]
//pub unsafe extern "C" fn sp_connection_open_ipv4( //pub unsafe extern "C" fn sp_connection_open_ipv4(
// host: SocketAddrV4, // host: SocketAddrV4,
//) -> *mut UdpConnection { //) -> *mut SPConnection {
// let connection = match servicepoint::UdpConnection::open(host) { // let connection = match servicepoint::UdpConnection::open(host) {
// Err(_) => return std::ptr::null_mut(), // Err(_) => return std::ptr::null_mut(),
// Ok(value) => value, // Ok(value) => value,
// }; // };
// //
// Box::into_raw(Box::new(UdpConnection(connection))) // Box::into_raw(Box::new(SPConnection(connection)))
//} //}
// /// Creates a new instance of [SPUdpConnection] for testing that does not actually send anything. // /// Creates a new instance of [SPUdpConnection] for testing that does not actually send anything.
// /// // ///
// /// returns: a new instance. // /// returns: a new instance. Will never return NULL.
// /// // ///
// /// # Safety // /// # Safety
// /// // ///
@ -56,11 +73,24 @@ pub unsafe extern "C" fn sp_connection_open(
// NonNull::from(Box::leak(result)) // NonNull::from(Box::leak(result))
// } // }
/// Sends a [Packet] to the display using the [UdpConnection]. /// Sends a [SPPacket] to the display using the [SPConnection].
/// ///
/// The passed `packet` gets consumed. /// The passed `packet` gets consumed.
/// ///
/// returns: true in case of success /// returns: true in case of success
///
/// # Panics
///
/// - when `connection` is NULL
/// - when `packet` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `connection` points to a valid instance of [SPConnection]
/// - `packet` points to a valid instance of [SPPacket]
/// - `packet` is not used concurrently or after this call
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_connection_send_packet( pub unsafe extern "C" fn sp_connection_send_packet(
connection: NonNull<UdpConnection>, connection: NonNull<UdpConnection>,
@ -70,18 +100,24 @@ pub unsafe extern "C" fn sp_connection_send_packet(
unsafe { connection.as_ref().send(*packet) }.is_ok() unsafe { connection.as_ref().send(*packet) }.is_ok()
} }
/// Sends a [TypedCommand] to the display using the [UdpConnection]. /// Sends a [SPCommand] to the display using the [SPConnection].
/// ///
/// The passed `command` gets consumed. /// The passed `command` gets consumed.
/// ///
/// returns: true in case of success /// returns: true in case of success
/// ///
/// # Examples /// # Panics
/// ///
/// ```C /// - when `connection` is NULL
/// sp_connection_send_command(connection, sp_command_clear()); /// - when `command` is NULL
/// sp_connection_send_command(connection, sp_command_brightness(5)); ///
/// ``` /// # Safety
///
/// The caller has to make sure that:
///
/// - `connection` points to a valid instance of [SPConnection]
/// - `command` points to a valid instance of [SPPacket]
/// - `command` is not used concurrently or after this call
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_connection_send_command( pub unsafe extern "C" fn sp_connection_send_command(
connection: NonNull<UdpConnection>, connection: NonNull<UdpConnection>,
@ -91,7 +127,18 @@ pub unsafe extern "C" fn sp_connection_send_command(
unsafe { connection.as_ref().send(command) }.is_ok() unsafe { connection.as_ref().send(command) }.is_ok()
} }
/// Closes and deallocates a [UdpConnection]. /// Closes and deallocates a [SPConnection].
///
/// # Panics
///
/// - when `connection` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `connection` points to a valid [SPConnection]
/// - `connection` is not used concurrently or after this call
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_connection_free( pub unsafe extern "C" fn sp_connection_free(
connection: NonNull<UdpConnection>, connection: NonNull<UdpConnection>,

View file

@ -1,10 +1,35 @@
use crate::ByteSlice; //! C functions for interacting with [SPCp437Grid]s
//!
//! prefix `sp_cp437_grid_`
//!
//!
//! A C-wrapper for grid containing codepage 437 characters.
//!
//! The encoding is currently not enforced.
//!
//! # Examples
//!
//! ```C
//! Cp437Grid grid = sp_cp437_grid_new(4, 3);
//! sp_cp437_grid_fill(grid, '?');
//! sp_cp437_grid_set(grid, 0, 0, '!');
//! sp_cp437_grid_free(grid);
//! ```
use crate::SPByteSlice;
use servicepoint::{Cp437Grid, DataRef, Grid}; use servicepoint::{Cp437Grid, DataRef, Grid};
use std::ptr::NonNull; use std::ptr::NonNull;
/// Creates a new [Cp437Grid] with the specified dimensions. /// Creates a new [SPCp437Grid] with the specified dimensions.
/// ///
/// returns: [Cp437Grid] initialized to 0. /// returns: [SPCp437Grid] initialized to 0. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_cp437_grid_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_new( pub unsafe extern "C" fn sp_cp437_grid_new(
width: usize, width: usize,
@ -14,12 +39,28 @@ pub unsafe extern "C" fn sp_cp437_grid_new(
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
/// Loads a [Cp437Grid] with the specified dimensions from the provided data. /// Loads a [SPCp437Grid] with the specified dimensions from the provided data.
///
/// Will never return NULL.
///
/// # Panics
///
/// - when `data` is NULL
/// - when the provided `data_length` does not match `height` and `width`
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `data` points to a valid memory location of at least `data_length`
/// bytes in size.
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_cp437_grid_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_load( pub unsafe extern "C" fn sp_cp437_grid_load(
width: usize, width: usize,
height: usize, height: usize,
data: ByteSlice, data: SPByteSlice,
) -> *mut Cp437Grid { ) -> *mut Cp437Grid {
let data = unsafe { data.as_slice() }; let data = unsafe { data.as_slice() };
let grid = Cp437Grid::load(width, height, data); let grid = Cp437Grid::load(width, height, data);
@ -30,7 +71,22 @@ pub unsafe extern "C" fn sp_cp437_grid_load(
} }
} }
/// Clones a [Cp437Grid]. /// Clones a [SPCp437Grid].
///
/// Will never return NULL.
///
/// # Panics
///
/// - when `cp437_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `cp437_grid` points to a valid [SPCp437Grid]
/// - `cp437_grid` is not written to concurrently
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_cp437_grid_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_clone( pub unsafe extern "C" fn sp_cp437_grid_clone(
cp437_grid: NonNull<Cp437Grid>, cp437_grid: NonNull<Cp437Grid>,
@ -39,7 +95,21 @@ pub unsafe extern "C" fn sp_cp437_grid_clone(
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
/// Deallocates a [Cp437Grid]. /// Deallocates a [SPCp437Grid].
///
/// # Panics
///
/// - when `cp437_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `cp437_grid` points to a valid [SPCp437Grid]
/// - `cp437_grid` is not used concurrently or after cp437_grid call
/// - `cp437_grid` was not passed to another consuming function, e.g. to create a [SPCommand]
///
/// [SPCommand]: [crate::SPCommand]
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_free(cp437_grid: NonNull<Cp437Grid>) { pub unsafe extern "C" fn sp_cp437_grid_free(cp437_grid: NonNull<Cp437Grid>) {
_ = unsafe { Box::from_raw(cp437_grid.as_ptr()) }; _ = unsafe { Box::from_raw(cp437_grid.as_ptr()) };
@ -54,7 +124,15 @@ pub unsafe extern "C" fn sp_cp437_grid_free(cp437_grid: NonNull<Cp437Grid>) {
/// ///
/// # Panics /// # Panics
/// ///
/// - when `cp437_grid` is NULL
/// - when accessing `x` or `y` out of bounds /// - when accessing `x` or `y` out of bounds
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `cp437_grid` points to a valid [SPCp437Grid]
/// - `cp437_grid` is not written to concurrently
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_get( pub unsafe extern "C" fn sp_cp437_grid_get(
cp437_grid: NonNull<Cp437Grid>, cp437_grid: NonNull<Cp437Grid>,
@ -64,7 +142,7 @@ pub unsafe extern "C" fn sp_cp437_grid_get(
unsafe { cp437_grid.as_ref().get(x, y) } unsafe { cp437_grid.as_ref().get(x, y) }
} }
/// Sets the value of the specified position in the [Cp437Grid]. /// Sets the value of the specified position in the [SPCp437Grid].
/// ///
/// # Arguments /// # Arguments
/// ///
@ -76,7 +154,17 @@ pub unsafe extern "C" fn sp_cp437_grid_get(
/// ///
/// # Panics /// # Panics
/// ///
/// - when `cp437_grid` is NULL
/// - when accessing `x` or `y` out of bounds /// - when accessing `x` or `y` out of bounds
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `cp437_grid` points to a valid [SPBitVec]
/// - `cp437_grid` is not written to or read from concurrently
///
/// [SPBitVec]: [crate::SPBitVec]
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_set( pub unsafe extern "C" fn sp_cp437_grid_set(
cp437_grid: NonNull<Cp437Grid>, cp437_grid: NonNull<Cp437Grid>,
@ -87,12 +175,23 @@ pub unsafe extern "C" fn sp_cp437_grid_set(
unsafe { (*cp437_grid.as_ptr()).set(x, y, value) }; unsafe { (*cp437_grid.as_ptr()).set(x, y, value) };
} }
/// Sets the value of all cells in the [Cp437Grid]. /// Sets the value of all cells in the [SPCp437Grid].
/// ///
/// # Arguments /// # Arguments
/// ///
/// - `cp437_grid`: instance to write to /// - `cp437_grid`: instance to write to
/// - `value`: the value to set all cells to /// - `value`: the value to set all cells to
///
/// # Panics
///
/// - when `cp437_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `cp437_grid` points to a valid [SPCp437Grid]
/// - `cp437_grid` is not written to or read from concurrently
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_fill( pub unsafe extern "C" fn sp_cp437_grid_fill(
cp437_grid: NonNull<Cp437Grid>, cp437_grid: NonNull<Cp437Grid>,
@ -101,11 +200,21 @@ pub unsafe extern "C" fn sp_cp437_grid_fill(
unsafe { (*cp437_grid.as_ptr()).fill(value) }; unsafe { (*cp437_grid.as_ptr()).fill(value) };
} }
/// Gets the width of the [Cp437Grid] instance. /// Gets the width of the [SPCp437Grid] instance.
/// ///
/// # Arguments /// # Arguments
/// ///
/// - `cp437_grid`: instance to read from /// - `cp437_grid`: instance to read from
///
/// # Panics
///
/// - when `cp437_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `cp437_grid` points to a valid [SPCp437Grid]
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_width( pub unsafe extern "C" fn sp_cp437_grid_width(
cp437_grid: NonNull<Cp437Grid>, cp437_grid: NonNull<Cp437Grid>,
@ -113,11 +222,21 @@ pub unsafe extern "C" fn sp_cp437_grid_width(
unsafe { cp437_grid.as_ref().width() } unsafe { cp437_grid.as_ref().width() }
} }
/// Gets the height of the [Cp437Grid] instance. /// Gets the height of the [SPCp437Grid] instance.
/// ///
/// # Arguments /// # Arguments
/// ///
/// - `cp437_grid`: instance to read from /// - `cp437_grid`: instance to read from
///
/// # Panics
///
/// - when `cp437_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `cp437_grid` points to a valid [SPCp437Grid]
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_height( pub unsafe extern "C" fn sp_cp437_grid_height(
cp437_grid: NonNull<Cp437Grid>, cp437_grid: NonNull<Cp437Grid>,
@ -125,12 +244,24 @@ pub unsafe extern "C" fn sp_cp437_grid_height(
unsafe { cp437_grid.as_ref().height() } unsafe { cp437_grid.as_ref().height() }
} }
/// Gets an unsafe reference to the data of the [Cp437Grid] instance. /// Gets an unsafe reference to the data of the [SPCp437Grid] instance.
/// ///
/// The returned memory is valid for the lifetime of the grid. /// Will never return NULL.
///
/// # Panics
///
/// - when `cp437_grid` is NULL
///
/// ## Safety
///
/// The caller has to make sure that:
///
/// - `cp437_grid` points to a valid [SPCp437Grid]
/// - the returned memory range is never accessed after the passed [SPCp437Grid] has been freed
/// - the returned memory range is never accessed concurrently, either via the [SPCp437Grid] or directly
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_unsafe_data_ref( pub unsafe extern "C" fn sp_cp437_grid_unsafe_data_ref(
cp437_grid: NonNull<Cp437Grid>, cp437_grid: NonNull<Cp437Grid>,
) -> ByteSlice { ) -> SPByteSlice {
unsafe { ByteSlice::from_slice((*cp437_grid.as_ptr()).data_ref_mut()) } unsafe { SPByteSlice::from_slice((*cp437_grid.as_ptr()).data_ref_mut()) }
} }

View file

@ -9,14 +9,14 @@
//! #include "servicepoint.h" //! #include "servicepoint.h"
//! //!
//! int main(void) { //! int main(void) {
//! UdpConnection *connection = sp_connection_open("172.23.42.29:2342"); //! SPConnection *connection = sp_connection_open("172.23.42.29:2342");
//! if (connection == NULL) //! if (connection == NULL)
//! return 1; //! return 1;
//! //!
//! Bitmap *pixels = sp_bitmap_new(SP_PIXEL_WIDTH, SP_PIXEL_HEIGHT); //! SPBitmap *pixels = sp_bitmap_new(SP_PIXEL_WIDTH, SP_PIXEL_HEIGHT);
//! sp_bitmap_fill(pixels, true); //! sp_bitmap_fill(pixels, true);
//! //!
//! TypedCommand *command = sp_command_bitmap_linear_win(0, 0, pixels, Uncompressed); //! SPCommand *command = sp_command_bitmap_linear_win(0, 0, pixels, Uncompressed);
//! while (sp_connection_send_command(connection, sp_command_clone(command))); //! while (sp_connection_send_command(connection, sp_command_clone(command)));
//! //!
//! sp_command_free(command); //! sp_command_free(command);

View file

@ -1,11 +1,31 @@
use crate::ByteSlice; //! C functions for interacting with [SPPacket]s
//!
//! prefix `sp_packet_`
//!
//!
//! The raw packet
use crate::SPByteSlice;
use servicepoint::{Header, Packet, TypedCommand}; use servicepoint::{Header, Packet, TypedCommand};
use std::ptr::NonNull; use std::ptr::NonNull;
/// Turns a [TypedCommand] into a [Packet]. /// Turns a [SPCommand] into a [SPPacket].
/// The [TypedCommand] gets consumed. /// The [SPCommand] gets consumed.
/// ///
/// Returns NULL in case of an error. /// Will never return NULL.
///
/// # Panics
///
/// - when `command` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - [SPCommand] points to a valid instance of [SPCommand]
/// - [SPCommand] is not used concurrently or after this call
/// - the returned [SPPacket] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_packet_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_packet_from_command( pub unsafe extern "C" fn sp_packet_from_command(
command: NonNull<TypedCommand>, command: NonNull<TypedCommand>,
@ -18,11 +38,24 @@ pub unsafe extern "C" fn sp_packet_from_command(
} }
} }
/// Tries to load a [Packet] from the passed array with the specified length. /// Tries to load a [SPPacket] from the passed array with the specified length.
/// ///
/// returns: NULL in case of an error, pointer to the allocated packet otherwise /// returns: NULL in case of an error, pointer to the allocated packet otherwise
///
/// # Panics
///
/// - when `data` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `data` points to a valid memory region of at least `length` bytes
/// - `data` is not written to concurrently
/// - the returned [SPPacket] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_packet_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_packet_try_load(data: ByteSlice) -> *mut Packet { pub unsafe extern "C" fn sp_packet_try_load(data: SPByteSlice) -> *mut Packet {
let data = unsafe { data.as_slice() }; let data = unsafe { data.as_slice() };
match servicepoint::Packet::try_from(data) { match servicepoint::Packet::try_from(data) {
Err(_) => std::ptr::null_mut(), Err(_) => std::ptr::null_mut(),
@ -30,13 +63,34 @@ pub unsafe extern "C" fn sp_packet_try_load(data: ByteSlice) -> *mut Packet {
} }
} }
/// Creates a raw [Packet] from parts. /// Creates a raw [SPPacket] from parts.
///
/// # Arguments
///
/// - `command_code` specifies which command this packet contains
/// - `a`, `b`, `c` and `d` are command-specific header values
/// - `payload` is the optional data that is part of the command
/// - `payload_len` is the size of the payload
/// ///
/// returns: new instance. Will never return null. /// returns: new instance. Will never return null.
///
/// # Panics
///
/// - when `payload` is null, but `payload_len` is not zero
/// - when `payload_len` is zero, but `payload` is nonnull
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `payload` points to a valid memory region of at least `payload_len` bytes
/// - `payload` is not written to concurrently
/// - the returned [SPPacket] instance is freed in some way, either by using a consuming function or
/// by explicitly calling [sp_packet_free].
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_packet_from_parts( pub unsafe extern "C" fn sp_packet_from_parts(
header: Header, header: Header,
payload: *const ByteSlice, payload: *const SPByteSlice,
) -> NonNull<Packet> { ) -> NonNull<Packet> {
let payload = if payload.is_null() { let payload = if payload.is_null() {
vec![] vec![]
@ -49,9 +103,6 @@ pub unsafe extern "C" fn sp_packet_from_parts(
NonNull::from(Box::leak(packet)) NonNull::from(Box::leak(packet))
} }
/// Returns a pointer to the header field of the provided packet.
///
/// The returned header can be changed and will be valid for the lifetime of the packet.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_packet_get_header( pub unsafe extern "C" fn sp_packet_get_header(
packet: NonNull<Packet>, packet: NonNull<Packet>,
@ -59,43 +110,47 @@ pub unsafe extern "C" fn sp_packet_get_header(
NonNull::from(&mut unsafe { (*packet.as_ptr()).header }) NonNull::from(&mut unsafe { (*packet.as_ptr()).header })
} }
/// Returns a pointer to the current payload of the provided packet.
///
/// The returned memory can be changed and will be valid until a new payload is set.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_packet_get_payload( pub unsafe extern "C" fn sp_packet_get_payload(
packet: NonNull<Packet>, packet: NonNull<Packet>,
) -> ByteSlice { ) -> SPByteSlice {
unsafe { ByteSlice::from_slice(&mut *(*packet.as_ptr()).payload) } unsafe { SPByteSlice::from_slice(&mut *(*packet.as_ptr()).payload) }
} }
/// Sets the payload of the provided packet to the provided data.
///
/// This makes previous payload pointers invalid.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_packet_set_payload( pub unsafe extern "C" fn sp_packet_set_payload(
packet: NonNull<Packet>, packet: NonNull<Packet>,
data: ByteSlice, data: SPByteSlice,
) { ) {
unsafe { (*packet.as_ptr()).payload = data.as_slice().to_vec() } unsafe { (*packet.as_ptr()).payload = data.as_slice().to_vec() }
} }
/// Serialize the packet into the provided buffer.
///
/// # Panics
///
/// - if the buffer is not big enough to hold header+payload.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_packet_serialize_to( pub unsafe extern "C" fn sp_packet_write_to(
packet: NonNull<Packet>, packet: NonNull<Packet>,
buffer: ByteSlice, buffer: SPByteSlice,
) { ) {
unsafe { unsafe {
packet.as_ref().serialize_to(buffer.as_slice_mut()); packet.as_ref().serialize_to(buffer.as_slice_mut());
} }
} }
/// Clones a [Packet]. /// Clones a [SPPacket].
///
/// Will never return NULL.
///
/// # Panics
///
/// - when `packet` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `packet` points to a valid [SPPacket]
/// - `packet` is not written to concurrently
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_packet_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_packet_clone( pub unsafe extern "C" fn sp_packet_clone(
packet: NonNull<Packet>, packet: NonNull<Packet>,
@ -104,7 +159,18 @@ pub unsafe extern "C" fn sp_packet_clone(
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
/// Deallocates a [Packet]. /// Deallocates a [SPPacket].
///
/// # Panics
///
/// - when `packet` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `packet` points to a valid [SPPacket]
/// - `packet` is not used concurrently or after this call
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_packet_free(packet: NonNull<Packet>) { pub unsafe extern "C" fn sp_packet_free(packet: NonNull<Packet>) {
_ = unsafe { Box::from_raw(packet.as_ptr()) } _ = unsafe { Box::from_raw(packet.as_ptr()) }