Compare commits

...

17 commits

Author SHA1 Message Date
Vinzenz Schroeter 613f21310e fix rebase
Some checks failed
Rust / build (pull_request) Failing after 1m23s
2025-04-12 16:33:39 +02:00
Vinzenz Schroeter 7c8ede61b0 move generic safety information into README 2025-04-12 16:25:20 +02:00
Vinzenz Schroeter 0c3bc0b004 wip fix documentation 2025-04-12 16:25:20 +02:00
Vinzenz Schroeter cf2c72de7c use brightness type from base crate 2025-04-12 16:25:20 +02:00
Vinzenz Schroeter 30bec50f7c use NotNull for parameters 2025-04-12 16:25:20 +02:00
Vinzenz Schroeter b443adc3ab add /*notnull*/ comments in header 2025-04-12 16:25:20 +02:00
Vinzenz Schroeter 24ff341f9d re-export constants from base lib 2025-04-12 16:25:20 +02:00
Vinzenz Schroeter 35cb42df48 wip remove newtypes 2025-04-12 16:25:20 +02:00
Vinzenz Schroeter 5120e220ec functions for manipulating packets 2025-04-12 16:25:20 +02:00
Vinzenz Schroeter d1ecd21114 wip remove newtypes 2025-04-12 16:25:20 +02:00
Vinzenz Schroeter 2ab80d395e cargo fmt 2025-04-12 16:25:20 +02:00
Vinzenz Schroeter 69bed7c665 wip 80k example 2025-04-12 16:25:20 +02:00
Vinzenz Schroeter 57d9e90b0f wip update to next servicepoint version 2025-04-12 16:25:20 +02:00
Vinzenz Schroeter 3c8eafecfb fix example path 2025-04-12 16:25:17 +02:00
Vinzenz Schroeter 65a26a9110 remove redundant infos
All checks were successful
Rust / build (push) Successful in 3m8s
2025-02-16 18:27:50 +01:00
Vinzenz Schroeter ed8e362574 extend installation instructions, fix perm
All checks were successful
Rust / build (push) Successful in 3m8s
2025-02-16 18:17:08 +01:00
Vinzenz Schroeter 77a8a242bf simplify example
Some checks failed
Rust / build (push) Failing after 1m21s
2025-02-16 18:06:19 +01:00
30 changed files with 1975 additions and 3617 deletions

View file

@ -25,10 +25,20 @@ 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 update && sudo apt-get install -y liblzma-dev run: sudo apt-get install -qy 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

View file

@ -1,31 +0,0 @@
# 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.

11
Cargo.lock generated
View file

@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 4
[[package]] [[package]]
name = "adler2" name = "adler2"
@ -98,9 +98,9 @@ dependencies = [
[[package]] [[package]]
name = "cbindgen" name = "cbindgen"
version = "0.27.0" version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fce8dd7fcfcbf3a0a87d8f515194b49d6135acab73e18bd380d1d93bb1a15eb" checksum = "eadd868a2ce9ca38de7eeafdcec9c7065ef89b42b32f0839278d55f35c54d1ff"
dependencies = [ dependencies = [
"clap", "clap",
"heck", "heck",
@ -407,9 +407,8 @@ dependencies = [
[[package]] [[package]]
name = "servicepoint" name = "servicepoint"
version = "0.13.1" version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://git.berlin.ccc.de/servicepoint/servicepoint/?branch=next#971bee5065139f220022e8108cfaa9c263b8a8a0"
checksum = "93b52049be55a15fe37c13249d7f96aa8a5de56e1a41838e74a822ee8316a0c4"
dependencies = [ dependencies = [
"bitvec", "bitvec",
"bzip2", "bzip2",

View file

@ -15,14 +15,31 @@ keywords = ["cccb", "cccb-servicepoint", "cbindgen"]
crate-type = ["staticlib", "cdylib", "rlib"] crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies] [build-dependencies]
cbindgen = "0.27.0" cbindgen = "0.28.0"
[dependencies.servicepoint] [dependencies.servicepoint]
version = "0.13.1" package = "servicepoint"
features = ["all_compressions"] version = "0.13.2"
default-features = false
features = ["protocol_udp"]
git = "https://git.berlin.ccc.de/servicepoint/servicepoint/"
branch = "next"
[features]
full = ["servicepoint/all_compressions", "servicepoint/default"]
default = ["full"]
[lints.rust] [lints.rust]
missing-docs = "warn" missing-docs = "warn"
unsafe_op_in_unsafe_fn = "warn"
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true
[profile.size-optimized]
inherits = "release"
opt-level = 'z' # Optimize for size
lto = true # Enable link-time optimization
codegen-units = 1 # Reduce number of codegen units to increase optimizations
panic = 'abort' # Abort on panic
strip = true # Strip symbols from binary

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` library, enabling users to parse, encode and send packets to this display via UDP. 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.
## Examples ## Examples
@ -43,21 +43,61 @@ Please specify the full version including patch in your Cargo.toml until 1.0 is
## Installation ## Installation
Copy the header to your project and compile against. 1. Add this repo as a submodule:
```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 guarantees yet. - When linked dynamically, you have to provide the `cdylib` at runtime in the _same_ version, as there are no API/ABI
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!) free. - Instances get consumed in the same way they do when writing rust code. Do not use an instance after an (implicit!)
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 be usable. - There are no specifics for C++ here yet. You might get a nicer header when generating directly for C++, but it should
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 available [online](https://docs.rs/servicepoint_binding_c/latest/servicepoint_binding_c/) - documentation is included in the header and
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 further information. Look at the main project [README](https://git.berlin.ccc.de/servicepoint/servicepoint/src/branch/main/README.md) for
further information.

View file

@ -1,41 +0,0 @@
# 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

@ -6,28 +6,30 @@
use std::{env, fs::copy}; use std::{env, fs::copy};
use cbindgen::{generate_with_config, Config};
fn main() { fn main() {
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
println!("cargo::rerun-if-changed={crate_dir}"); println!("cargo::rerun-if-changed={crate_dir}");
let config = let config =
Config::from_file(crate_dir.clone() + "/cbindgen.toml").unwrap(); cbindgen::Config::from_file(crate_dir.clone() + "/cbindgen.toml")
.unwrap();
let output_dir = env::var("OUT_DIR").unwrap(); let output_dir = env::var("OUT_DIR").unwrap();
let header_file = output_dir.clone() + "/servicepoint.h"; let header_file = output_dir.clone() + "/servicepoint.h";
generate_with_config(crate_dir, config) if let Ok(bindings) = cbindgen::generate_with_config(crate_dir, config) {
.unwrap() bindings.write_to_file(&header_file);
.write_to_file(&header_file);
println!("cargo:include={output_dir}");
println!("cargo::rerun-if-env-changed=SERVICEPOINT_HEADER_OUT"); println!("cargo:include={output_dir}");
if let Ok(header_out) = env::var("SERVICEPOINT_HEADER_OUT") {
let header_copy = header_out + "/servicepoint.h"; println!("cargo::rerun-if-env-changed=SERVICEPOINT_HEADER_OUT");
println!("cargo:warning=Copying header to {header_copy}"); if let Ok(header_out) = env::var("SERVICEPOINT_HEADER_OUT") {
copy(header_file, &header_copy).unwrap(); let header_copy = header_out + "/servicepoint.h";
println!("cargo::rerun-if-changed={header_copy}"); println!("cargo:warning=Copying header to {header_copy}");
copy(header_file, &header_copy).unwrap();
println!("cargo::rerun-if-changed={header_copy}");
}
} else {
eprintln!("cargo:warning=Servicepoint header could not be generated");
} }
} }

View file

@ -19,18 +19,27 @@ line_endings = "LF"
style = "type" style = "type"
usize_is_size_t = true usize_is_size_t = true
# this is needed because otherwise the order in the C# bindings is different on different machines # this is needed because otherwise the order in the C bindings is different on different machines
sort_by = "Name" sort_by = "Name"
[parse] [parse]
parse_deps = false parse_deps = true
include = ["servicepoint"]
extra_bindings = ["servicepoint"]
[parse.expand] [parse.expand]
all_features = true features = ["full"]
[export] [export]
include = [] include = []
exclude = [] exclude = []
[export.rename]
"SpBitVec" = "BitVec"
"SpByteSlice" = "ByteSlice"
[enum] [enum]
rename_variants = "QualifiedScreamingSnakeCase" rename_variants = "QualifiedScreamingSnakeCase"
[ptr]
non_null_attribute = "/*notnull*/"

73
example/Makefile Normal file
View file

@ -0,0 +1,73 @@
CC := gcc
CARGO := rustup run nightly cargo
THIS_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST))))
REPO_ROOT := $(THIS_DIR)/..
RUST_TARGET_DIR := $(REPO_ROOT)/target/x86_64-unknown-linux-musl/size-optimized
RUSTFLAGS := -Zlocation-detail=none \
-Zfmt-debug=none \
-C linker=musl-gcc \
-C link-arg=-s \
-C link-arg=--gc-sections \
-C link-arg=-z,norelro \
-C link-arg=--hash-style=gnu \
--crate-type=staticlib \
-C panic=abort
CARGOFLAGS := --manifest-path=$(REPO_ROOT)/Cargo.toml \
--profile=size-optimized \
--no-default-features \
--target=x86_64-unknown-linux-musl \
-Zbuild-std="core,std,alloc,proc_macro,panic_abort" \
-Zbuild-std-features="panic_immediate_abort" \
CCFLAGS := -static -Os \
-ffunction-sections -fdata-sections \
-fwrapv -fomit-frame-pointer -fno-stack-protector\
-fwhole-program \
-nodefaultlibs -lservicepoint_binding_c -lc \
-Wl,--gc-sections \
-fno-unroll-loops \
-fno-unwind-tables -fno-asynchronous-unwind-tables \
-fmerge-all-constants \
-Wl,-z,norelro \
-Wl,--hash-style=gnu \
-fvisibility=hidden \
-Bsymbolic \
-Wl,--exclude-libs,ALL \
-fno-ident
#-fuse-ld=gold \
-fno-exceptions
#-Wl,--icf=all \
export SERVICEPOINT_HEADER_OUT := $(REPO_ROOT)/include
build: out/example
clean:
rm -r out || true
rm include/servicepoint.h || true
cargo clean
run: out/example
out/example
PHONY: build clean dependencies run
out/example_unstripped: dependencies main.c
mkdir -p out || true
${CC} main.c \
-I $(SERVICEPOINT_HEADER_OUT) \
-L $(RUST_TARGET_DIR)\
$(CCFLAGS) \
-o out/example_unstripped
out/example: out/example_unstripped
strip -s -R .comment -R .gnu.version --strip-unneeded out/example_unstripped -o out/example
#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
mkdir -p include || true
# generate servicepoint header and binary to link against
${CARGO} rustc $(CARGOFLAGS) -- $(RUSTFLAGS)
FORCE: ;

28
example/main.c Normal file
View file

@ -0,0 +1,28 @@
#include <stdio.h>
#include "servicepoint.h"
int main(void) {
UdpConnection *connection = sp_connection_open("localhost:2342");
if (connection == NULL)
return 1;
Bitmap *pixels = sp_bitmap_new(PIXEL_WIDTH, PIXEL_HEIGHT);
if (pixels == NULL)
return 1;
sp_bitmap_fill(pixels, true);
TypedCommand *command = sp_command_bitmap_linear_win(0, 0, pixels, COMPRESSION_CODE_UNCOMPRESSED);
if (command == NULL)
return 1;
Packet *packet = sp_packet_from_command(command);
Header *header = sp_packet_get_header(packet);
printf("[%d, %d, %d, %d, %d]\n", header->command_code, header->a, header->b, header->c, header->d);
sp_connection_send_packet(connection, packet);
sp_connection_free(connection);
return 0;
}

View file

@ -1,14 +0,0 @@
[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

@ -1,34 +0,0 @@
CC := gcc
THIS_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST))))
REPO_ROOT := $(THIS_DIR)/../../../..
build: out/lang_c
clean:
rm -r out || true
rm include/servicepoint.h || true
cargo clean
run: out/lang_c
out/lang_c
PHONY: build clean dependencies run
out/lang_c: dependencies src/main.c
mkdir -p out || true
${CC} src/main.c \
-I include \
-L $(REPO_ROOT)/target/release \
-Wl,-Bstatic -lservicepoint_binding_c \
-Wl,-Bdynamic -llzma \
-o out/lang_c
dependencies: FORCE
mkdir -p include || true
# generate servicepoint header and binary to link against
SERVICEPOINT_HEADER_OUT=$(THIS_DIR)/include cargo build \
--manifest-path=$(REPO_ROOT)/crates/servicepoint_binding_c/Cargo.toml \
--release
FORCE: ;

View file

@ -1,17 +0,0 @@
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

@ -1 +0,0 @@

View file

@ -1,17 +0,0 @@
#include <stdio.h>
#include "servicepoint.h"
int main(void) {
SPConnection *connection = sp_connection_open("localhost:2342");
if (connection == NULL)
return 1;
SPBitmap *pixels = sp_bitmap_new(SP_PIXEL_WIDTH, SP_PIXEL_HEIGHT);
sp_bitmap_fill(pixels, true);
SPCommand *command = sp_command_bitmap_linear_win(0, 0, pixels, SP_COMPRESSION_CODE_UNCOMPRESSED);
sp_connection_send_command(connection, command);
sp_connection_free(connection);
return 0;
}

View file

@ -33,25 +33,35 @@
{ pkgs, system }: { pkgs, system }:
{ {
default = pkgs.mkShell rec { default = pkgs.mkShell rec {
packages = with pkgs; [ buildInputs = with pkgs;[
xe
xz
libgcc
#glibc.static
musl
libunwind
];
nativeBuildInputs = with pkgs;[
(pkgs.symlinkJoin { (pkgs.symlinkJoin {
name = "rust-toolchain"; name = "rust-toolchain";
paths = with pkgs; [ paths = with pkgs; [
rustc #rustc
cargo #cargo
rustPlatform.rustcSrc #rustPlatform.rustcSrc
rustfmt #rustfmt
clippy #clippy
cargo-expand cargo-expand
cargo-tarpaulin cargo-tarpaulin
rustup
]; ];
}) })
gcc gcc
gnumake gnumake
xe
xz
pkg-config pkg-config
]; ];
RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}"; RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
}; };
} }

6
generate-binding.sh Executable file
View file

@ -0,0 +1,6 @@
#!/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

1174
include/servicepoint.h Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,22 @@
//! C functions for interacting with [SPBitmap]s use servicepoint::{Bitmap, DataRef, Grid};
//!
//! prefix `sp_bitmap_`
use servicepoint::{DataRef, Grid};
use std::ptr::NonNull; use std::ptr::NonNull;
use crate::byte_slice::SPByteSlice; use crate::byte_slice::ByteSlice;
/// A grid of pixels. /// Creates a new [Bitmap] with the specified dimensions.
///
/// # Arguments
///
/// - `width`: size in pixels in x-direction
/// - `height`: size in pixels in y-direction
///
/// returns: [Bitmap] initialized to all pixels off, or NULL in case of an error.
///
/// # Errors
///
/// In the following cases, this function will return NULL:
///
/// - when the width is not dividable by 8
/// ///
/// # Examples /// # Examples
/// ///
@ -17,135 +26,65 @@ use crate::byte_slice::SPByteSlice;
/// sp_bitmap_set(grid, 0, 0, false); /// sp_bitmap_set(grid, 0, 0, false);
/// sp_bitmap_free(grid); /// sp_bitmap_free(grid);
/// ``` /// ```
pub struct SPBitmap(pub(crate) servicepoint::Bitmap);
/// Creates a new [SPBitmap] with the specified dimensions.
///
/// # Arguments
///
/// - `width`: size in pixels in x-direction
/// - `height`: size in pixels in y-direction
///
/// returns: [SPBitmap] initialized to all pixels off. Will never return NULL.
///
/// # Panics
///
/// - when the width is not dividable 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_bitmap_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_bitmap_new( pub unsafe extern "C" fn sp_bitmap_new(
width: usize, width: usize,
height: usize, height: usize,
) -> NonNull<SPBitmap> { ) -> *mut Bitmap {
let result = Box::new(SPBitmap(servicepoint::Bitmap::new(width, height))); if let Some(bitmap) = Bitmap::new(width, height) {
NonNull::from(Box::leak(result)) Box::leak(Box::new(bitmap))
} else {
std::ptr::null_mut()
}
} }
/// Creates a new [SPBitmap] with a size matching the screen. /// Creates a new [Bitmap] with a size matching the screen.
/// ///
/// returns: [SPBitmap] initialized to all pixels off. Will never return NULL. /// returns: [Bitmap] initialized to all pixels off.
///
/// # 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<SPBitmap> { pub unsafe extern "C" fn sp_bitmap_new_screen_sized() -> NonNull<Bitmap> {
let result = Box::new(SPBitmap(servicepoint::Bitmap::max_sized())); let result = Box::new(Bitmap::max_sized());
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
/// Loads a [SPBitmap] with the specified dimensions from the provided data. /// Loads a [Bitmap] 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: [SPBitmap] that contains a copy of the provided data. Will never return NULL. /// returns: [Bitmap] that contains a copy of the provided data, or NULL in case of an error.
///
/// # Panics
///
/// - when `data` is NULL
/// - when the dimensions and data size do not match exactly.
/// - when the width is not dividable by 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_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: *const u8, data: ByteSlice,
data_length: usize, ) -> *mut Bitmap {
) -> NonNull<SPBitmap> { let data = unsafe { data.as_slice() };
assert!(!data.is_null()); if let Ok(bitmap) = Bitmap::load(width, height, data) {
let data = std::slice::from_raw_parts(data, data_length); Box::leak(Box::new(bitmap))
let result = } else {
Box::new(SPBitmap(servicepoint::Bitmap::load(width, height, data))); std::ptr::null_mut()
NonNull::from(Box::leak(result)) }
} }
/// Clones a [SPBitmap]. /// Clones a [Bitmap].
///
/// 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: *const SPBitmap, bitmap: NonNull<Bitmap>,
) -> NonNull<SPBitmap> { ) -> NonNull<Bitmap> {
assert!(!bitmap.is_null()); let result = Box::new(unsafe { bitmap.as_ref().clone() });
let result = Box::new(SPBitmap((*bitmap).0.clone()));
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
/// Deallocates a [SPBitmap]. /// Deallocates a [Bitmap].
///
/// # Panics
///
/// - when `bitmap` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid [SPBitmap]
/// - `bitmap` is not used concurrently or after bitmap call
/// - `bitmap` 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_bitmap_free(bitmap: *mut SPBitmap) { pub unsafe extern "C" fn sp_bitmap_free(bitmap: NonNull<Bitmap>) {
assert!(!bitmap.is_null()); _ = unsafe { Box::from_raw(bitmap.as_ptr()) };
_ = Box::from_raw(bitmap);
} }
/// Gets the current value at the specified position in the [SPBitmap]. /// Gets the current value at the specified position in the [Bitmap].
/// ///
/// # Arguments /// # Arguments
/// ///
@ -154,26 +93,17 @@ pub unsafe extern "C" fn sp_bitmap_free(bitmap: *mut SPBitmap) {
/// ///
/// # 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: *const SPBitmap, bitmap: NonNull<Bitmap>,
x: usize, x: usize,
y: usize, y: usize,
) -> bool { ) -> bool {
assert!(!bitmap.is_null()); unsafe { bitmap.as_ref().get(x, y) }
(*bitmap).0.get(x, y)
} }
/// Sets the value of the specified position in the [SPBitmap]. /// Sets the value of the specified position in the [Bitmap].
/// ///
/// # Arguments /// # Arguments
/// ///
@ -181,54 +111,31 @@ 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: *mut SPBitmap, bitmap: NonNull<Bitmap>,
x: usize, x: usize,
y: usize, y: usize,
value: bool, value: bool,
) { ) {
assert!(!bitmap.is_null()); unsafe { (*bitmap.as_ptr()).set(x, y, value) };
(*bitmap).0.set(x, y, value);
} }
/// Sets the state of all pixels in the [SPBitmap]. /// Sets the state of all pixels in the [Bitmap].
/// ///
/// # 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: *mut SPBitmap, value: bool) { pub unsafe extern "C" fn sp_bitmap_fill(bitmap: NonNull<Bitmap>, value: bool) {
assert!(!bitmap.is_null()); unsafe { (*bitmap.as_ptr()).fill(value) };
(*bitmap).0.fill(value);
} }
/// Gets the width in pixels of the [SPBitmap] instance. /// Gets the width in pixels of the [Bitmap] instance.
/// ///
/// # Arguments /// # Arguments
/// ///
@ -242,55 +149,28 @@ pub unsafe extern "C" fn sp_bitmap_fill(bitmap: *mut SPBitmap, value: bool) {
/// ///
/// The caller has to make sure that: /// The caller has to make sure that:
/// ///
/// - `bitmap` points to a valid [SPBitmap] /// - `bitmap` points to a valid [Bitmap]
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_bitmap_width(bitmap: *const SPBitmap) -> usize { pub unsafe extern "C" fn sp_bitmap_width(bitmap: NonNull<Bitmap>) -> usize {
assert!(!bitmap.is_null()); unsafe { bitmap.as_ref().width() }
(*bitmap).0.width()
} }
/// Gets the height in pixels of the [SPBitmap] instance. /// Gets the height in pixels of the [Bitmap] 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: *const SPBitmap) -> usize { pub unsafe extern "C" fn sp_bitmap_height(bitmap: NonNull<Bitmap>) -> usize {
assert!(!bitmap.is_null()); unsafe { bitmap.as_ref().height() }
(*bitmap).0.height()
} }
/// Gets an unsafe reference to the data of the [SPBitmap] instance. /// Gets an unsafe reference to the data of the [Bitmap] instance.
/// ///
/// # Panics /// The returned memory is valid for the lifetime of the bitmap.
///
/// - 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(
bitmap: *mut SPBitmap, mut bitmap: NonNull<Bitmap>,
) -> SPByteSlice { ) -> ByteSlice {
assert!(!bitmap.is_null()); unsafe { ByteSlice::from_slice(bitmap.as_mut().data_ref_mut()) }
let data = (*bitmap).0.data_ref_mut();
SPByteSlice {
start: NonNull::new(data.as_mut_ptr_range().start).unwrap(),
length: data.len(),
}
} }

View file

@ -1,9 +1,6 @@
//! C functions for interacting with [SPBitVec]s use crate::ByteSlice;
//!
//! 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
/// ///
@ -13,19 +10,7 @@ use std::ptr::NonNull;
/// sp_bitvec_set(vec, 5, true); /// sp_bitvec_set(vec, 5, true);
/// sp_bitvec_free(vec); /// sp_bitvec_free(vec);
/// ``` /// ```
pub struct SPBitVec(servicepoint::BitVec); pub struct SPBitVec(pub(crate) BitVecU8Msb0);
impl From<servicepoint::BitVec> for SPBitVec {
fn from(actual: servicepoint::BitVec) -> Self {
Self(actual)
}
}
impl From<SPBitVec> for servicepoint::BitVec {
fn from(value: SPBitVec) -> Self {
value.0
}
}
impl Clone for SPBitVec { impl Clone for SPBitVec {
fn clone(&self) -> Self { fn clone(&self) -> Self {
@ -39,95 +24,43 @@ impl Clone for SPBitVec {
/// ///
/// - `size`: size in bits. /// - `size`: size in bits.
/// ///
/// returns: [SPBitVec] with all bits set to false. Will never return NULL. /// returns: [SPBitVec] with all bits set to false.
/// ///
/// # 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 = Box::new(SPBitVec(servicepoint::BitVec::repeat(false, size))); let result =
Box::new(SPBitVec(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. Will never return NULL. /// returns: [SPBitVec] instance containing data.
///
/// # 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: *const u8, data: ByteSlice,
data_length: usize,
) -> NonNull<SPBitVec> { ) -> NonNull<SPBitVec> {
assert!(!data.is_null()); let data = unsafe { data.as_slice() };
let data = std::slice::from_raw_parts(data, data_length); let result = Box::new(SPBitVec(BitVecU8Msb0::from_slice(data)));
let result = Box::new(SPBitVec(servicepoint::BitVec::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: *const SPBitVec, bit_vec: NonNull<SPBitVec>,
) -> NonNull<SPBitVec> { ) -> NonNull<SPBitVec> {
assert!(!bit_vec.is_null()); let result = Box::new(unsafe { bit_vec.as_ref().clone() });
let result = Box::new((*bit_vec).clone());
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
/// 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: *mut SPBitVec) { pub unsafe extern "C" fn sp_bitvec_free(bit_vec: NonNull<SPBitVec>) {
assert!(!bit_vec.is_null()); _ = unsafe { Box::from_raw(bit_vec.as_ptr()) };
_ = Box::from_raw(bit_vec);
} }
/// Gets the value of a bit from the [SPBitVec]. /// Gets the value of a bit from the [SPBitVec].
@ -141,22 +74,13 @@ pub unsafe extern "C" fn sp_bitvec_free(bit_vec: *mut 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: *const SPBitVec, bit_vec: NonNull<SPBitVec>,
index: usize, index: usize,
) -> bool { ) -> bool {
assert!(!bit_vec.is_null()); unsafe { *bit_vec.as_ref().0.get(index).unwrap() }
*(*bit_vec).0.get(index).unwrap()
} }
/// Sets the value of a bit in the [SPBitVec]. /// Sets the value of a bit in the [SPBitVec].
@ -169,23 +93,14 @@ 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: *mut SPBitVec, bit_vec: NonNull<SPBitVec>,
index: usize, index: usize,
value: bool, value: bool,
) { ) {
assert!(!bit_vec.is_null()); unsafe { (*bit_vec.as_ptr()).0.set(index, value) }
(*bit_vec).0.set(index, value)
} }
/// Sets the value of all bits in the [SPBitVec]. /// Sets the value of all bits in the [SPBitVec].
@ -194,21 +109,12 @@ 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(bit_vec: *mut SPBitVec, value: bool) { pub unsafe extern "C" fn sp_bitvec_fill(
assert!(!bit_vec.is_null()); bit_vec: NonNull<SPBitVec>,
(*bit_vec).0.fill(value) value: bool,
) {
unsafe { (*bit_vec.as_ptr()).0.fill(value) }
} }
/// Gets the length of the [SPBitVec] in bits. /// Gets the length of the [SPBitVec] in bits.
@ -216,20 +122,9 @@ pub unsafe extern "C" fn sp_bitvec_fill(bit_vec: *mut SPBitVec, value: bool) {
/// # 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: *const SPBitVec) -> usize { pub unsafe extern "C" fn sp_bitvec_len(bit_vec: NonNull<SPBitVec>) -> usize {
assert!(!bit_vec.is_null()); unsafe { bit_vec.as_ref().0.len() }
(*bit_vec).0.len()
} }
/// Returns true if length is 0. /// Returns true if length is 0.
@ -237,47 +132,23 @@ pub unsafe extern "C" fn sp_bitvec_len(bit_vec: *const 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(bit_vec: *const SPBitVec) -> bool { pub unsafe extern "C" fn sp_bitvec_is_empty(
assert!(!bit_vec.is_null()); bit_vec: NonNull<SPBitVec>,
(*bit_vec).0.is_empty() ) -> bool {
unsafe { bit_vec.as_ref().0.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: *mut SPBitVec, bit_vec: NonNull<SPBitVec>,
) -> SPByteSlice { ) -> ByteSlice {
assert!(!bit_vec.is_null()); unsafe { ByteSlice::from_slice((*bit_vec.as_ptr()).0.as_raw_mut_slice()) }
let data = (*bit_vec).0.as_raw_mut_slice();
SPByteSlice {
start: NonNull::new(data.as_mut_ptr_range().start).unwrap(),
length: data.len(),
}
} }

View file

@ -1,146 +1,70 @@
//! C functions for interacting with [SPBrightnessGrid]s use crate::ByteSlice;
//! use servicepoint::{Brightness, BrightnessGrid, ByteGrid, DataRef, Grid};
//! prefix `sp_brightness_grid_` use std::mem::transmute;
use crate::SPByteSlice;
use servicepoint::{DataRef, Grid};
use std::convert::Into;
use std::intrinsics::transmute;
use std::ptr::NonNull; use std::ptr::NonNull;
/// see [servicepoint::Brightness::MIN] /// Creates a new [BrightnessGrid] with the specified dimensions.
pub const SP_BRIGHTNESS_MIN: u8 = 0; ///
/// see [servicepoint::Brightness::MAX] /// returns: [BrightnessGrid] initialized to 0.
pub const SP_BRIGHTNESS_MAX: u8 = 11;
/// Count of possible brightness values
pub const SP_BRIGHTNESS_LEVELS: u8 = 12;
/// A grid containing brightness values.
/// ///
/// # Examples /// # Examples
/// ```C /// ```C
/// SPConnection connection = sp_connection_open("127.0.0.1:2342"); /// UdpConnection connection = sp_connection_open("127.0.0.1:2342");
/// if (connection == NULL) /// if (connection == NULL)
/// return 1; /// return 1;
/// ///
/// SPBrightnessGrid grid = sp_brightness_grid_new(2, 2); /// BrightnessGrid grid = sp_brightness_grid_new(2, 2);
/// sp_brightness_grid_set(grid, 0, 0, 0); /// sp_brightness_grid_set(grid, 0, 0, 0);
/// sp_brightness_grid_set(grid, 1, 1, 10); /// sp_brightness_grid_set(grid, 1, 1, 10);
/// ///
/// SPCommand command = sp_command_char_brightness(grid); /// TypedCommand command = sp_command_char_brightness(grid);
/// sp_connection_free(connection); /// sp_connection_free(connection);
/// ``` /// ```
#[derive(Clone)]
pub struct SPBrightnessGrid(pub(crate) servicepoint::BrightnessGrid);
/// Creates a new [SPBrightnessGrid] with the specified dimensions.
///
/// returns: [SPBrightnessGrid] 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_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,
height: usize, height: usize,
) -> NonNull<SPBrightnessGrid> { ) -> NonNull<BrightnessGrid> {
let result = Box::new(SPBrightnessGrid(servicepoint::BrightnessGrid::new( let result = Box::new(BrightnessGrid::new(width, height));
width, height,
)));
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
/// Loads a [SPBrightnessGrid] with the specified dimensions from the provided data. /// Loads a [BrightnessGrid] with the specified dimensions from the provided data.
/// ///
/// returns: new [SPBrightnessGrid] instance. Will never return NULL. /// returns: new [BrightnessGrid] instance, or NULL in case of an error.
///
/// # 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: *const u8, data: ByteSlice,
data_length: usize, ) -> *mut BrightnessGrid {
) -> NonNull<SPBrightnessGrid> { let data = unsafe { data.as_slice() };
assert!(!data.is_null()); let grid = match ByteGrid::load(width, height, data) {
let data = std::slice::from_raw_parts(data, data_length); None => return std::ptr::null_mut(),
let grid = servicepoint::ByteGrid::load(width, height, data); Some(grid) => grid,
let grid = servicepoint::BrightnessGrid::try_from(grid) };
.expect("invalid brightness value"); if let Ok(grid) = BrightnessGrid::try_from(grid) {
let result = Box::new(SPBrightnessGrid(grid)); Box::leak(Box::new(grid))
NonNull::from(Box::leak(result)) } else {
std::ptr::null_mut()
}
} }
/// Clones a [SPBrightnessGrid]. /// Clones a [BrightnessGrid].
///
/// # 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: *const SPBrightnessGrid, brightness_grid: NonNull<BrightnessGrid>,
) -> NonNull<SPBrightnessGrid> { ) -> NonNull<BrightnessGrid> {
assert!(!brightness_grid.is_null()); let result = Box::new(unsafe { brightness_grid.as_ref().clone() });
let result = Box::new((*brightness_grid).clone());
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
/// Deallocates a [SPBrightnessGrid]. /// Deallocates a [BrightnessGrid].
///
/// # 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: *mut SPBrightnessGrid, brightness_grid: NonNull<BrightnessGrid>,
) { ) {
assert!(!brightness_grid.is_null()); _ = unsafe { Box::from_raw(brightness_grid.as_ptr()) };
_ = Box::from_raw(brightness_grid);
} }
/// Gets the current value at the specified position. /// Gets the current value at the specified position.
@ -153,27 +77,17 @@ 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: *const SPBrightnessGrid, brightness_grid: NonNull<BrightnessGrid>,
x: usize, x: usize,
y: usize, y: usize,
) -> u8 { ) -> Brightness {
assert!(!brightness_grid.is_null()); unsafe { brightness_grid.as_ref().get(x, y) }
(*brightness_grid).0.get(x, y).into()
} }
/// Sets the value of the specified position in the [SPBrightnessGrid]. /// Sets the value of the specified position in the [BrightnessGrid].
/// ///
/// # Arguments /// # Arguments
/// ///
@ -185,138 +99,75 @@ 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: *mut SPBrightnessGrid, brightness_grid: NonNull<BrightnessGrid>,
x: usize, x: usize,
y: usize, y: usize,
value: u8, value: Brightness,
) { ) {
assert!(!brightness_grid.is_null()); unsafe { (*brightness_grid.as_ptr()).set(x, y, value) };
let brightness = servicepoint::Brightness::try_from(value)
.expect("invalid brightness value");
(*brightness_grid).0.set(x, y, brightness);
} }
/// Sets the value of all cells in the [SPBrightnessGrid]. /// Sets the value of all cells in the [BrightnessGrid].
/// ///
/// # 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: *mut SPBrightnessGrid, brightness_grid: NonNull<BrightnessGrid>,
value: u8, value: Brightness,
) { ) {
assert!(!brightness_grid.is_null()); unsafe { (*brightness_grid.as_ptr()).fill(value) };
let brightness = servicepoint::Brightness::try_from(value)
.expect("invalid brightness value");
(*brightness_grid).0.fill(brightness);
} }
/// Gets the width of the [SPBrightnessGrid] instance. /// Gets the width of the [BrightnessGrid] 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: *const SPBrightnessGrid, brightness_grid: NonNull<BrightnessGrid>,
) -> usize { ) -> usize {
assert!(!brightness_grid.is_null()); unsafe { brightness_grid.as_ref().width() }
(*brightness_grid).0.width()
} }
/// Gets the height of the [SPBrightnessGrid] instance. /// Gets the height of the [BrightnessGrid] 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: *const SPBrightnessGrid, brightness_grid: NonNull<BrightnessGrid>,
) -> usize { ) -> usize {
assert!(!brightness_grid.is_null()); unsafe { brightness_grid.as_ref().height() }
(*brightness_grid).0.height()
} }
/// Gets an unsafe reference to the data of the [SPBrightnessGrid] instance. /// Gets an unsafe reference to the data of the [BrightnessGrid] 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: *mut SPBrightnessGrid, brightness_grid: NonNull<BrightnessGrid>,
) -> SPByteSlice { ) -> ByteSlice {
assert!(!brightness_grid.is_null()); //noinspection RsAssertEqual
assert_eq!(core::mem::size_of::<servicepoint::Brightness>(), 1); const _: () = assert!(size_of::<Brightness>() == 1);
let data = (*brightness_grid).0.data_ref_mut();
// this assumes more about the memory layout than rust guarantees. yikes! let data = unsafe { (*brightness_grid.as_ptr()).data_ref_mut() };
let data: &mut [u8] = transmute(data); unsafe { ByteSlice::from_slice(transmute(data)) }
SPByteSlice {
start: NonNull::new(data.as_mut_ptr_range().start).unwrap(),
length: data.len(),
}
} }

View file

@ -2,10 +2,7 @@
use std::ptr::NonNull; use std::ptr::NonNull;
#[repr(C)] /// 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
/// ///
@ -14,11 +11,29 @@ 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 #[repr(C)]
/// will try to free the memory of a potentially separate allocator. 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 {
pub(crate) unsafe fn as_slice(&self) -> &[u8] {
unsafe { std::slice::from_raw_parts(self.start.as_ptr(), self.length) }
}
pub(crate) unsafe fn as_slice_mut(&self) -> &mut [u8] {
unsafe {
std::slice::from_raw_parts_mut(self.start.as_ptr(), self.length)
}
}
pub(crate) unsafe fn from_slice(slice: &mut [u8]) -> Self {
Self {
start: NonNull::new(slice.as_mut_ptr()).unwrap(),
length: slice.len(),
}
}
}

View file

@ -1,16 +1,10 @@
//! C functions for interacting with [SPCharGrid]s use crate::ByteSlice;
//! use servicepoint::{CharGrid, Grid};
//! prefix `sp_char_grid_`
use servicepoint::Grid;
use std::ptr::NonNull; use std::ptr::NonNull;
/// A C-wrapper for grid containing UTF-8 characters. /// Creates a new [CharGrid] with the specified dimensions.
/// ///
/// As the rust [char] type is not FFI-safe, characters are passed in their UTF-32 form as 32bit unsigned integers. /// returns: [CharGrid] initialized to 0.
///
/// The encoding is enforced in most cases by the rust standard library
/// and will panic when provided with illegal characters.
/// ///
/// # Examples /// # Examples
/// ///
@ -20,115 +14,48 @@ use std::ptr::NonNull;
/// sp_char_grid_set(grid, 0, 0, '!'); /// sp_char_grid_set(grid, 0, 0, '!');
/// sp_char_grid_free(grid); /// sp_char_grid_free(grid);
/// ``` /// ```
pub struct SPCharGrid(pub(crate) servicepoint::CharGrid);
impl Clone for SPCharGrid {
fn clone(&self) -> Self {
SPCharGrid(self.0.clone())
}
}
/// Creates a new [SPCharGrid] with the specified dimensions.
///
/// returns: [SPCharGrid] 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_char_grid_free`.
#[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,
height: usize, height: usize,
) -> NonNull<SPCharGrid> { ) -> NonNull<CharGrid> {
let result = let result = Box::new(CharGrid::new(width, height));
Box::new(SPCharGrid(servicepoint::CharGrid::new(width, height)));
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
/// Loads a [SPCharGrid] with the specified dimensions from the provided data. /// Loads a [CharGrid] with the specified dimensions from the provided data.
/// ///
/// Will never return NULL. /// returns: new CharGrid or NULL in case of an error
///
/// # 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: *const u8, data: ByteSlice,
data_length: usize, ) -> *mut CharGrid {
) -> NonNull<SPCharGrid> { let data = unsafe { data.as_slice() };
assert!(data.is_null()); if let Ok(grid) = CharGrid::load_utf8(width, height, data.to_vec()) {
let data = std::slice::from_raw_parts(data, data_length); Box::leak(Box::new(grid))
let result = Box::new(SPCharGrid( } else {
servicepoint::CharGrid::load_utf8(width, height, data.to_vec()) std::ptr::null_mut()
.unwrap(), }
));
NonNull::from(Box::leak(result))
} }
/// Clones a [SPCharGrid]. /// Clones a [CharGrid].
///
/// 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: *const SPCharGrid, char_grid: NonNull<CharGrid>,
) -> NonNull<SPCharGrid> { ) -> NonNull<CharGrid> {
assert!(!char_grid.is_null()); let result = unsafe { char_grid.as_ref().clone() };
let result = Box::new((*char_grid).clone()); NonNull::from(Box::leak(Box::new(result)))
NonNull::from(Box::leak(result))
} }
/// Deallocates a [SPCharGrid]. /// Deallocates a [CharGrid].
///
/// # 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: *mut SPCharGrid) { pub unsafe extern "C" fn sp_char_grid_free(char_grid: NonNull<CharGrid>) {
assert!(!char_grid.is_null()); _ = unsafe { Box::from_raw(char_grid.as_ptr()) };
_ = Box::from_raw(char_grid);
} }
/// Gets the current value at the specified position. /// Returns the current value at the specified position.
/// ///
/// # Arguments /// # Arguments
/// ///
@ -137,26 +64,17 @@ pub unsafe extern "C" fn sp_char_grid_free(char_grid: *mut SPCharGrid) {
/// ///
/// # 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: *const SPCharGrid, char_grid: NonNull<CharGrid>,
x: usize, x: usize,
y: usize, y: usize,
) -> u32 { ) -> u32 {
assert!(!char_grid.is_null()); unsafe { char_grid.as_ref().get(x, y) as u32 }
(*char_grid).0.get(x, y) as u32
} }
/// Sets the value of the specified position in the [SPCharGrid]. /// Sets the value of the specified position in the [CharGrid].
/// ///
/// # Arguments /// # Arguments
/// ///
@ -168,96 +86,51 @@ 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: *mut SPCharGrid, char_grid: NonNull<CharGrid>,
x: usize, x: usize,
y: usize, y: usize,
value: u32, value: u32,
) { ) {
assert!(!char_grid.is_null()); unsafe { (*char_grid.as_ptr()).set(x, y, char::from_u32(value).unwrap()) };
(*char_grid).0.set(x, y, char::from_u32(value).unwrap());
} }
/// Sets the value of all cells in the [SPCharGrid]. /// Sets the value of all cells in the [CharGrid].
/// ///
/// # 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: *mut SPCharGrid, char_grid: NonNull<CharGrid>,
value: u32, value: u32,
) { ) {
assert!(!char_grid.is_null()); unsafe { (*char_grid.as_ptr()).fill(char::from_u32(value).unwrap()) };
(*char_grid).0.fill(char::from_u32(value).unwrap());
} }
/// Gets the width of the [SPCharGrid] instance. /// Gets the width of the [CharGrid] 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: *const SPCharGrid, char_grid: NonNull<CharGrid>,
) -> usize { ) -> usize {
assert!(!char_grid.is_null()); unsafe { char_grid.as_ref().width() }
(*char_grid).0.width()
} }
/// Gets the height of the [SPCharGrid] instance. /// Gets the height of the [CharGrid] 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: *const SPCharGrid, char_grid: NonNull<CharGrid>,
) -> usize { ) -> usize {
assert!(!char_grid.is_null()); unsafe { char_grid.as_ref().height() }
(*char_grid).0.height()
} }

View file

@ -1,88 +1,31 @@
//! C functions for interacting with [SPCommand]s use crate::SPBitVec;
//! use servicepoint::{BinaryOperation, Bitmap, Brightness, BrightnessGrid, CharGrid, CompressionCode, Cp437Grid, GlobalBrightnessCommand, Packet, TypedCommand};
//! prefix `sp_command_` use std::ptr::NonNull;
use std::ptr::{null_mut, NonNull}; /// Tries to turn a [Packet] into a [TypedCommand].
use crate::{
SPBitVec, SPBitmap, SPBrightnessGrid, SPCharGrid, SPCompressionCode,
SPCp437Grid, SPPacket,
};
/// 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]
pub struct SPCommand(pub(crate) servicepoint::Command);
impl Clone for SPCommand {
fn clone(&self) -> Self {
SPCommand(self.0.clone())
}
}
/// 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 [SPCommand] instance or NULL if parsing failed. /// Returns: pointer to new [TypedCommand] 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: *mut SPPacket, packet: NonNull<Packet>,
) -> *mut SPCommand { ) -> *mut TypedCommand {
let packet = *Box::from_raw(packet); let packet = *unsafe { Box::from_raw(packet.as_ptr()) };
match servicepoint::Command::try_from(packet.0) { match servicepoint::TypedCommand::try_from(packet) {
Err(_) => null_mut(), Err(_) => std::ptr::null_mut(),
Ok(command) => Box::into_raw(Box::new(SPCommand(command))), Ok(command) => Box::into_raw(Box::new(command)),
} }
} }
/// Clones a [SPCommand] instance. /// Clones a [TypedCommand] instance.
/// ///
/// returns: new [SPCommand] instance. Will never return NULL. /// returns: new [TypedCommand] instance.
///
/// # 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: *const SPCommand, command: NonNull<TypedCommand>,
) -> NonNull<SPCommand> { ) -> NonNull<TypedCommand> {
assert!(!command.is_null()); let result = Box::new(unsafe { command.as_ref().clone() });
let result = Box::new((*command).clone());
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
@ -90,23 +33,16 @@ pub unsafe extern "C" fn sp_command_clone(
/// ///
/// Does not affect brightness. /// Does not affect brightness.
/// ///
/// Returns: a new [servicepoint::Command::Clear] instance. Will never return NULL. /// Returns: a new [servicepoint::Command::Clear] instance.
/// ///
/// # 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<SPCommand> { pub unsafe extern "C" fn sp_command_clear() -> NonNull<TypedCommand> {
let result = Box::new(SPCommand(servicepoint::Command::Clear)); let result = Box::new(servicepoint::ClearCommand.into());
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
@ -114,91 +50,52 @@ pub unsafe extern "C" fn sp_command_clear() -> NonNull<SPCommand> {
/// ///
/// 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. Will never return NULL. /// Returns: a new [servicepoint::Command::HardReset] instance.
///
/// # 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<SPCommand> { pub unsafe extern "C" fn sp_command_hard_reset() -> NonNull<TypedCommand> {
let result = Box::new(SPCommand(servicepoint::Command::HardReset)); let result = Box::new(servicepoint::HardResetCommand.into());
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
/// A yet-to-be-tested command. /// A yet-to-be-tested command.
/// ///
/// Returns: a new [servicepoint::Command::FadeOut] instance. Will never return NULL. /// Returns: a new [servicepoint::Command::FadeOut] instance.
///
/// # 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<SPCommand> { pub unsafe extern "C" fn sp_command_fade_out() -> NonNull<TypedCommand> {
let result = Box::new(SPCommand(servicepoint::Command::FadeOut)); let result = Box::new(servicepoint::FadeOutCommand.into());
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
/// 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. Will never return NULL. /// Returns: a new [servicepoint::Command::Brightness] instance.
///
/// # 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: u8, brightness: Brightness,
) -> NonNull<SPCommand> { ) -> NonNull<TypedCommand> {
let brightness = servicepoint::Brightness::try_from(brightness) let result = Box::new(GlobalBrightnessCommand::from(brightness).into());
.expect("invalid brightness");
let result =
Box::new(SPCommand(servicepoint::Command::Brightness(brightness)));
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 [SPBrightnessGrid] gets consumed. /// The passed [BrightnessGrid] gets consumed.
/// ///
/// Returns: a new [servicepoint::Command::CharBrightness] instance. Will never return NULL. /// Returns: a new [servicepoint::Command::CharBrightness] instance.
///
/// # 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,
y: usize, y: usize,
grid: *mut SPBrightnessGrid, grid: NonNull<BrightnessGrid>,
) -> NonNull<SPCommand> { ) -> NonNull<TypedCommand> {
assert!(!grid.is_null()); let grid = unsafe { *Box::from_raw(grid.as_ptr()) };
let byte_grid = *Box::from_raw(grid); let result = Box::new(
let result = Box::new(SPCommand(servicepoint::Command::CharBrightness( servicepoint::BrightnessGridCommand {
servicepoint::Origin::new(x, y), origin: servicepoint::Origin::new(x, y),
byte_grid.0, grid,
))); }
.into(),
);
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
@ -211,36 +108,21 @@ 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. Will never return NULL. /// Returns: a new [servicepoint::Command::BitmapLinear] instance.
///
/// # 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,
bit_vec: *mut SPBitVec, bit_vec: NonNull<SPBitVec>,
compression: SPCompressionCode, compression: CompressionCode,
) -> NonNull<SPCommand> { ) -> *mut TypedCommand {
assert!(!bit_vec.is_null()); unsafe {
let bit_vec = *Box::from_raw(bit_vec); sp_command_bitmap_linear_internal(
let result = Box::new(SPCommand(servicepoint::Command::BitmapLinear( offset,
offset, bit_vec,
bit_vec.into(), compression,
compression.try_into().expect("invalid compression code"), BinaryOperation::Overwrite,
))); )
NonNull::from(Box::leak(result)) }
} }
/// Set pixel data according to an and-mask starting at the offset. /// Set pixel data according to an and-mask starting at the offset.
@ -252,36 +134,21 @@ 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. Will never return NULL. /// Returns: a new [servicepoint::Command::BitmapLinearAnd] instance.
///
/// # 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,
bit_vec: *mut SPBitVec, bit_vec: NonNull<SPBitVec>,
compression: SPCompressionCode, compression: CompressionCode,
) -> NonNull<SPCommand> { ) -> *mut TypedCommand {
assert!(!bit_vec.is_null()); unsafe {
let bit_vec = *Box::from_raw(bit_vec); sp_command_bitmap_linear_internal(
let result = Box::new(SPCommand(servicepoint::Command::BitmapLinearAnd( offset,
offset, bit_vec,
bit_vec.into(), compression,
compression.try_into().expect("invalid compression code"), BinaryOperation::Xor,
))); )
NonNull::from(Box::leak(result)) }
} }
/// Set pixel data according to an or-mask starting at the offset. /// Set pixel data according to an or-mask starting at the offset.
@ -293,36 +160,21 @@ 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. Will never return NULL. /// Returns: a new [servicepoint::Command::BitmapLinearOr] instance.
///
/// # 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,
bit_vec: *mut SPBitVec, bit_vec: NonNull<SPBitVec>,
compression: SPCompressionCode, compression: CompressionCode,
) -> NonNull<SPCommand> { ) -> *mut TypedCommand {
assert!(!bit_vec.is_null()); unsafe {
let bit_vec = *Box::from_raw(bit_vec); sp_command_bitmap_linear_internal(
let result = Box::new(SPCommand(servicepoint::Command::BitmapLinearOr( offset,
offset, bit_vec,
bit_vec.into(), compression,
compression.try_into().expect("invalid compression code"), BinaryOperation::Or,
))); )
NonNull::from(Box::leak(result)) }
} }
/// Set pixel data according to a xor-mask starting at the offset. /// Set pixel data according to a xor-mask starting at the offset.
@ -334,165 +186,123 @@ 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. Will never return NULL. /// Returns: a new [servicepoint::Command::BitmapLinearXor] instance.
///
/// # 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,
bit_vec: *mut SPBitVec, bit_vec: NonNull<SPBitVec>,
compression: SPCompressionCode, compression: CompressionCode,
) -> NonNull<SPCommand> { ) -> *mut TypedCommand {
assert!(!bit_vec.is_null()); unsafe {
let bit_vec = *Box::from_raw(bit_vec); sp_command_bitmap_linear_internal(
let result = Box::new(SPCommand(servicepoint::Command::BitmapLinearXor( offset,
bit_vec,
compression,
BinaryOperation::Xor,
)
}
}
unsafe fn sp_command_bitmap_linear_internal(
offset: usize,
bit_vec: NonNull<SPBitVec>,
compression: CompressionCode,
operation: BinaryOperation,
) -> *mut TypedCommand {
let bit_vec = unsafe { *Box::from_raw(bit_vec.as_ptr()) };
let compression = match compression.try_into() {
Ok(compression) => compression,
Err(_) => return std::ptr::null_mut(),
};
let command = servicepoint::BitVecCommand {
offset, offset,
bit_vec.into(), operation,
compression.try_into().expect("invalid compression code"), bitvec: bit_vec.0,
))); compression,
NonNull::from(Box::leak(result)) }
.into();
Box::leak(Box::new(command))
} }
/// Show codepage 437 encoded text on the screen. /// Show codepage 437 encoded text on the screen.
/// ///
/// The passed [SPCp437Grid] gets consumed. /// The passed [Cp437Grid] gets consumed.
/// ///
/// Returns: a new [servicepoint::Command::Cp437Data] instance. Will never return NULL. /// Returns: a new [servicepoint::Command::Cp437Data] instance.
///
/// # 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,
y: usize, y: usize,
grid: *mut SPCp437Grid, grid: NonNull<Cp437Grid>,
) -> NonNull<SPCommand> { ) -> NonNull<TypedCommand> {
assert!(!grid.is_null()); let grid = *unsafe { Box::from_raw(grid.as_ptr()) };
let grid = *Box::from_raw(grid); let result = Box::new(
let result = Box::new(SPCommand(servicepoint::Command::Cp437Data( servicepoint::Cp437GridCommand {
servicepoint::Origin::new(x, y), origin: servicepoint::Origin::new(x, y),
grid.0, grid,
))); }
.into(),
);
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
/// Show UTF-8 encoded text on the screen. /// Show UTF-8 encoded text on the screen.
/// ///
/// The passed [SPCharGrid] gets consumed. /// The passed [CharGrid] gets consumed.
/// ///
/// Returns: a new [servicepoint::Command::Utf8Data] instance. Will never return NULL. /// Returns: a new [servicepoint::Command::Utf8Data] instance.
///
/// # 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,
y: usize, y: usize,
grid: *mut SPCharGrid, grid: NonNull<CharGrid>,
) -> NonNull<SPCommand> { ) -> NonNull<TypedCommand> {
assert!(!grid.is_null()); let grid = unsafe { *Box::from_raw(grid.as_ptr()) };
let grid = *Box::from_raw(grid); let result = Box::new(
let result = Box::new(SPCommand(servicepoint::Command::Utf8Data( servicepoint::CharGridCommand {
servicepoint::Origin::new(x, y), origin: servicepoint::Origin::new(x, y),
grid.0, grid,
))); }
.into(),
);
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
/// Sets a window of pixels to the specified values. /// Sets a window of pixels to the specified values.
/// ///
/// The passed [SPBitmap] gets consumed. /// The passed [Bitmap] gets consumed.
/// ///
/// Returns: a new [servicepoint::Command::BitmapLinearWin] instance. Will never return NULL. /// Returns: a new [servicepoint::Command::BitmapLinearWin] instance.
///
/// # 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,
y: usize, y: usize,
bitmap: *mut SPBitmap, bitmap: NonNull<Bitmap>,
compression_code: SPCompressionCode, compression: CompressionCode,
) -> NonNull<SPCommand> { ) -> *mut TypedCommand {
assert!(!bitmap.is_null()); let bitmap = unsafe { *Box::from_raw(bitmap.as_ptr()) };
let byte_grid = (*Box::from_raw(bitmap)).0; let compression = match compression.try_into() {
let result = Box::new(SPCommand(servicepoint::Command::BitmapLinearWin( Ok(compression) => compression,
servicepoint::Origin::new(x, y), Err(_) => return std::ptr::null_mut(),
byte_grid, };
compression_code let command = servicepoint::BitmapCommand {
.try_into() origin: servicepoint::Origin::new(x, y),
.expect("invalid compression code"), bitmap,
))); compression,
NonNull::from(Box::leak(result)) }
.into();
Box::leak(Box::new(command))
} }
/// Deallocates a [SPCommand]. /// Deallocates a [TypedCommand].
/// ///
/// # Examples /// # Examples
/// ///
/// ```C /// ```C
/// SPCommand c = sp_command_clear(); /// TypedCommand 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: *mut SPCommand) { pub unsafe extern "C" fn sp_command_free(command: NonNull<TypedCommand>) {
assert!(!command.is_null()); _ = unsafe { Box::from_raw(command.as_ptr()) };
_ = Box::from_raw(command);
} }

View file

@ -1,13 +1,10 @@
//! C functions for interacting with [SPConnection]s use servicepoint::{Connection, Packet, TypedCommand, UdpConnection};
//!
//! prefix `sp_connection_`
use std::ffi::{c_char, CStr}; use std::ffi::{c_char, CStr};
use std::ptr::{null_mut, NonNull}; use std::ptr::NonNull;
use crate::{SPCommand, SPPacket}; /// Creates a new instance of [UdpConnection].
///
/// A connection to the display. /// returns: NULL if connection fails, or connected instance
/// ///
/// # Examples /// # Examples
/// ///
@ -16,124 +13,88 @@ use crate::{SPCommand, SPPacket};
/// if (connection != NULL) /// if (connection != NULL)
/// sp_connection_send_command(connection, sp_command_clear()); /// sp_connection_send_command(connection, sp_command_clear());
/// ``` /// ```
pub struct SPConnection(pub(crate) servicepoint::Connection);
/// Creates a new instance of [SPConnection].
///
/// returns: NULL if connection fails, or connected instance
///
/// # Panics
///
/// - when `host` is null or an invalid host
///
/// # 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_connection_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_connection_open( pub unsafe extern "C" fn sp_connection_open(
host: *const c_char, host: NonNull<c_char>,
) -> *mut SPConnection { ) -> *mut UdpConnection {
assert!(!host.is_null()); let host = unsafe { CStr::from_ptr(host.as_ptr()) }
let host = CStr::from_ptr(host).to_str().expect("Bad encoding"); .to_str()
let connection = match servicepoint::Connection::open(host) { .expect("Bad encoding");
Err(_) => return null_mut(), let connection = match UdpConnection::open(host) {
Err(_) => return std::ptr::null_mut(),
Ok(value) => value, Ok(value) => value,
}; };
Box::into_raw(Box::new(SPConnection(connection))) Box::into_raw(Box::new(connection))
} }
/// Creates a new instance of [SPConnection] for testing that does not actually send anything. //#[no_mangle]
/// //pub unsafe extern "C" fn sp_connection_open_ipv4(
/// returns: a new instance. Will never return NULL. // host: SocketAddrV4,
/// //) -> *mut UdpConnection {
/// # Safety // let connection = match servicepoint::UdpConnection::open(host) {
/// // Err(_) => return std::ptr::null_mut(),
/// The caller has to make sure that: // Ok(value) => value,
/// // };
/// - the returned instance is freed in some way, either by using a consuming function or //
/// by explicitly calling `sp_connection_free`. // Box::into_raw(Box::new(UdpConnection(connection)))
#[no_mangle] //}
pub unsafe extern "C" fn sp_connection_fake() -> NonNull<SPConnection> {
let result = Box::new(SPConnection(servicepoint::Connection::Fake));
NonNull::from(Box::leak(result))
}
/// Sends a [SPPacket] to the display using the [SPConnection]. // /// Creates a new instance of [SPUdpConnection] for testing that does not actually send anything.
// ///
// /// returns: a new instance.
// ///
// /// # 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_connection_free`.
// #[no_mangle]
// pub unsafe extern "C" fn sp_connection_fake() -> NonNull<SPUdpConnection> {
// let result = Box::new(SPUdpConnection(servicepoint::Connection::Fake));
// NonNull::from(Box::leak(result))
// }
/// Sends a [Packet] to the display using the [UdpConnection].
/// ///
/// 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: *const SPConnection, connection: NonNull<UdpConnection>,
packet: *mut SPPacket, packet: NonNull<Packet>,
) -> bool { ) -> bool {
assert!(!connection.is_null()); let packet = unsafe { Box::from_raw(packet.as_ptr()) };
assert!(!packet.is_null()); unsafe { connection.as_ref().send(*packet) }.is_ok()
let packet = Box::from_raw(packet);
(*connection).0.send((*packet).0).is_ok()
} }
/// Sends a [SPCommand] to the display using the [SPConnection]. /// Sends a [TypedCommand] to the display using the [UdpConnection].
/// ///
/// The passed `command` gets consumed. /// The passed `command` gets consumed.
/// ///
/// returns: true in case of success /// returns: true in case of success
/// ///
/// # Panics /// # Examples
/// ///
/// - when `connection` is NULL /// ```C
/// - when `command` is NULL /// sp_connection_send_command(connection, sp_command_clear());
/// /// 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: *const SPConnection, connection: NonNull<UdpConnection>,
command: *mut SPCommand, command: NonNull<TypedCommand>,
) -> bool { ) -> bool {
assert!(!connection.is_null()); let command = *unsafe { Box::from_raw(command.as_ptr()) };
assert!(!command.is_null()); unsafe { connection.as_ref().send(command) }.is_ok()
let command = (*Box::from_raw(command)).0;
(*connection).0.send(command).is_ok()
} }
/// Closes and deallocates a [SPConnection]. /// Closes and deallocates a [UdpConnection].
///
/// # 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(connection: *mut SPConnection) { pub unsafe extern "C" fn sp_connection_free(
assert!(!connection.is_null()); connection: NonNull<UdpConnection>,
_ = Box::from_raw(connection); ) {
_ = unsafe { Box::from_raw(connection.as_ptr()) };
} }

View file

@ -1,48 +0,0 @@
//! re-exported constants for use in C
use servicepoint::CompressionCode;
use std::time::Duration;
/// size of a single tile in one dimension
pub const SP_TILE_SIZE: usize = 8;
/// Display tile count in the x-direction
pub const SP_TILE_WIDTH: usize = 56;
/// Display tile count in the y-direction
pub const SP_TILE_HEIGHT: usize = 20;
/// Display width in pixels
pub const SP_PIXEL_WIDTH: usize = SP_TILE_WIDTH * SP_TILE_SIZE;
/// Display height in pixels
pub const SP_PIXEL_HEIGHT: usize = SP_TILE_HEIGHT * SP_TILE_SIZE;
/// pixel count on whole screen
pub const SP_PIXEL_COUNT: usize = SP_PIXEL_WIDTH * SP_PIXEL_HEIGHT;
/// Actual hardware limit is around 28-29ms/frame. Rounded up for less dropped packets.
pub const SP_FRAME_PACING_MS: u128 = Duration::from_millis(30).as_millis();
/// Specifies the kind of compression to use.
#[repr(u16)]
pub enum SPCompressionCode {
/// no compression
Uncompressed = 0x0,
/// compress using flate2 with zlib header
Zlib = 0x677a,
/// compress using bzip2
Bzip2 = 0x627a,
/// compress using lzma
Lzma = 0x6c7a,
/// compress using Zstandard
Zstd = 0x7a73,
}
impl TryFrom<SPCompressionCode> for CompressionCode {
type Error = ();
fn try_from(value: SPCompressionCode) -> Result<Self, Self::Error> {
CompressionCode::try_from(value as u16)
}
}

View file

@ -1,127 +1,48 @@
//! C functions for interacting with [SPCp437Grid]s use crate::ByteSlice;
//! use servicepoint::{Cp437Grid, DataRef, Grid};
//! prefix `sp_cp437_grid_`
use crate::SPByteSlice;
use servicepoint::{DataRef, Grid};
use std::ptr::NonNull; use std::ptr::NonNull;
/// A C-wrapper for grid containing codepage 437 characters. /// Creates a new [Cp437Grid] with the specified dimensions.
/// ///
/// The encoding is currently not enforced. /// returns: [Cp437Grid] initialized to 0.
///
/// # 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);
/// ```
pub struct SPCp437Grid(pub(crate) servicepoint::Cp437Grid);
impl Clone for SPCp437Grid {
fn clone(&self) -> Self {
SPCp437Grid(self.0.clone())
}
}
/// Creates a new [SPCp437Grid] with the specified dimensions.
///
/// 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,
height: usize, height: usize,
) -> NonNull<SPCp437Grid> { ) -> NonNull<Cp437Grid> {
let result = let result = Box::new(Cp437Grid::new(width, height));
Box::new(SPCp437Grid(servicepoint::Cp437Grid::new(width, height)));
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
/// Loads a [SPCp437Grid] with the specified dimensions from the provided data. /// Loads a [Cp437Grid] 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: *const u8, data: ByteSlice,
data_length: usize, ) -> *mut Cp437Grid {
) -> NonNull<SPCp437Grid> { let data = unsafe { data.as_slice() };
assert!(data.is_null()); let grid = Cp437Grid::load(width, height, data);
let data = std::slice::from_raw_parts(data, data_length); if let Some(grid) = grid {
let result = Box::new(SPCp437Grid(servicepoint::Cp437Grid::load( Box::leak(Box::new(grid))
width, height, data, } else {
))); std::ptr::null_mut()
NonNull::from(Box::leak(result)) }
} }
/// Clones a [SPCp437Grid]. /// Clones a [Cp437Grid].
///
/// 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: *const SPCp437Grid, cp437_grid: NonNull<Cp437Grid>,
) -> NonNull<SPCp437Grid> { ) -> NonNull<Cp437Grid> {
assert!(!cp437_grid.is_null()); let result = Box::new(unsafe { cp437_grid.as_ref().clone() });
let result = Box::new((*cp437_grid).clone());
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
/// Deallocates a [SPCp437Grid]. /// Deallocates a [Cp437Grid].
///
/// # 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: *mut SPCp437Grid) { pub unsafe extern "C" fn sp_cp437_grid_free(cp437_grid: NonNull<Cp437Grid>) {
assert!(!cp437_grid.is_null()); _ = unsafe { Box::from_raw(cp437_grid.as_ptr()) };
_ = Box::from_raw(cp437_grid);
} }
/// Gets the current value at the specified position. /// Gets the current value at the specified position.
@ -133,26 +54,17 @@ pub unsafe extern "C" fn sp_cp437_grid_free(cp437_grid: *mut SPCp437Grid) {
/// ///
/// # 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: *const SPCp437Grid, cp437_grid: NonNull<Cp437Grid>,
x: usize, x: usize,
y: usize, y: usize,
) -> u8 { ) -> u8 {
assert!(!cp437_grid.is_null()); unsafe { cp437_grid.as_ref().get(x, y) }
(*cp437_grid).0.get(x, y)
} }
/// Sets the value of the specified position in the [SPCp437Grid]. /// Sets the value of the specified position in the [Cp437Grid].
/// ///
/// # Arguments /// # Arguments
/// ///
@ -164,122 +76,61 @@ 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: *mut SPCp437Grid, cp437_grid: NonNull<Cp437Grid>,
x: usize, x: usize,
y: usize, y: usize,
value: u8, value: u8,
) { ) {
assert!(!cp437_grid.is_null()); unsafe { (*cp437_grid.as_ptr()).set(x, y, value) };
(*cp437_grid).0.set(x, y, value);
} }
/// Sets the value of all cells in the [SPCp437Grid]. /// Sets the value of all cells in the [Cp437Grid].
/// ///
/// # 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: *mut SPCp437Grid, cp437_grid: NonNull<Cp437Grid>,
value: u8, value: u8,
) { ) {
assert!(!cp437_grid.is_null()); unsafe { (*cp437_grid.as_ptr()).fill(value) };
(*cp437_grid).0.fill(value);
} }
/// Gets the width of the [SPCp437Grid] instance. /// Gets the width of the [Cp437Grid] 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: *const SPCp437Grid, cp437_grid: NonNull<Cp437Grid>,
) -> usize { ) -> usize {
assert!(!cp437_grid.is_null()); unsafe { cp437_grid.as_ref().width() }
(*cp437_grid).0.width()
} }
/// Gets the height of the [SPCp437Grid] instance. /// Gets the height of the [Cp437Grid] 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: *const SPCp437Grid, cp437_grid: NonNull<Cp437Grid>,
) -> usize { ) -> usize {
assert!(!cp437_grid.is_null()); unsafe { cp437_grid.as_ref().height() }
(*cp437_grid).0.height()
} }
/// Gets an unsafe reference to the data of the [SPCp437Grid] instance. /// Gets an unsafe reference to the data of the [Cp437Grid] instance.
/// ///
/// Will never return NULL. /// The returned memory is valid for the lifetime of the grid.
///
/// # 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: *mut SPCp437Grid, cp437_grid: NonNull<Cp437Grid>,
) -> SPByteSlice { ) -> ByteSlice {
let data = (*cp437_grid).0.data_ref_mut(); unsafe { ByteSlice::from_slice((*cp437_grid.as_ptr()).data_ref_mut()) }
SPByteSlice {
start: NonNull::new(data.as_mut_ptr_range().start).unwrap(),
length: data.len(),
}
} }

View file

@ -9,14 +9,14 @@
//! #include "servicepoint.h" //! #include "servicepoint.h"
//! //!
//! int main(void) { //! int main(void) {
//! SPConnection *connection = sp_connection_open("172.23.42.29:2342"); //! UdpConnection *connection = sp_connection_open("172.23.42.29:2342");
//! if (connection == NULL) //! if (connection == NULL)
//! return 1; //! return 1;
//! //!
//! SPBitmap *pixels = sp_bitmap_new(SP_PIXEL_WIDTH, SP_PIXEL_HEIGHT); //! Bitmap *pixels = sp_bitmap_new(SP_PIXEL_WIDTH, SP_PIXEL_HEIGHT);
//! sp_bitmap_fill(pixels, true); //! sp_bitmap_fill(pixels, true);
//! //!
//! SPCommand *command = sp_command_bitmap_linear_win(0, 0, pixels, Uncompressed); //! TypedCommand *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);
@ -32,7 +32,6 @@ pub use crate::byte_slice::*;
pub use crate::char_grid::*; pub use crate::char_grid::*;
pub use crate::command::*; pub use crate::command::*;
pub use crate::connection::*; pub use crate::connection::*;
pub use crate::constants::*;
pub use crate::cp437_grid::*; pub use crate::cp437_grid::*;
pub use crate::packet::*; pub use crate::packet::*;
@ -43,6 +42,10 @@ mod byte_slice;
mod char_grid; mod char_grid;
mod command; mod command;
mod connection; mod connection;
mod constants;
mod cp437_grid; mod cp437_grid;
mod packet; mod packet;
use std::time::Duration;
/// Actual hardware limit is around 28-29ms/frame. Rounded up for less dropped packets.
pub const SP_FRAME_PACING_MS: u128 = Duration::from_millis(30).as_millis();

View file

@ -1,166 +1,111 @@
//! C functions for interacting with [SPPacket]s use crate::ByteSlice;
//! use servicepoint::{Header, Packet, TypedCommand};
//! prefix `sp_packet_` use std::ptr::NonNull;
use std::ptr::{null_mut, NonNull}; /// Turns a [TypedCommand] into a [Packet].
/// The [TypedCommand] gets consumed.
use crate::SPCommand;
/// The raw packet
pub struct SPPacket(pub(crate) servicepoint::Packet);
/// Turns a [SPCommand] into a [SPPacket].
/// The [SPCommand] gets consumed.
/// ///
/// Will never return NULL. /// Returns NULL in case of an error.
///
/// # 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: *mut SPCommand, command: NonNull<TypedCommand>,
) -> NonNull<SPPacket> { ) -> *mut Packet {
assert!(!command.is_null()); let command = unsafe { *Box::from_raw(command.as_ptr()) };
let command = *Box::from_raw(command); if let Ok(packet) = command.try_into() {
let result = Box::new(SPPacket(command.0.into())); Box::leak(Box::new(packet))
NonNull::from(Box::leak(result)) } else {
} std::ptr::null_mut()
/// 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
///
/// # 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]
pub unsafe extern "C" fn sp_packet_try_load(
data: *const u8,
length: usize,
) -> *mut SPPacket {
assert!(!data.is_null());
let data = std::slice::from_raw_parts(data, length);
match servicepoint::Packet::try_from(data) {
Err(_) => null_mut(),
Ok(packet) => Box::into_raw(Box::new(SPPacket(packet))),
} }
} }
/// Creates a raw [SPPacket] from parts. /// Tries to load a [Packet] from the passed array with the specified length.
/// ///
/// # Arguments /// returns: NULL in case of an error, pointer to the allocated packet otherwise
/// #[no_mangle]
/// - `command_code` specifies which command this packet contains pub unsafe extern "C" fn sp_packet_try_load(data: ByteSlice) -> *mut Packet {
/// - `a`, `b`, `c` and `d` are command-specific header values let data = unsafe { data.as_slice() };
/// - `payload` is the optional data that is part of the command match servicepoint::Packet::try_from(data) {
/// - `payload_len` is the size of the payload Err(_) => std::ptr::null_mut(),
Ok(packet) => Box::into_raw(Box::new(packet)),
}
}
/// Creates a raw [Packet] from parts.
/// ///
/// 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(
command_code: u16, header: Header,
a: u16, payload: *const ByteSlice,
b: u16, ) -> NonNull<Packet> {
c: u16,
d: u16,
payload: *const u8,
payload_len: usize,
) -> NonNull<SPPacket> {
assert_eq!(payload.is_null(), payload_len == 0);
let payload = if payload.is_null() { let payload = if payload.is_null() {
vec![] vec![]
} else { } else {
let payload = std::slice::from_raw_parts(payload, payload_len); let payload = unsafe { (*payload).as_slice() };
Vec::from(payload) Vec::from(payload)
}; };
let packet = servicepoint::Packet { let packet = Box::new(Packet { header, payload });
header: servicepoint::Header { NonNull::from(Box::leak(packet))
command_code,
a,
b,
c,
d,
},
payload,
};
let result = Box::new(SPPacket(packet));
NonNull::from(Box::leak(result))
} }
/// Clones a [SPPacket]. /// Returns a pointer to the header field of the provided packet.
/// ///
/// Will never return NULL. /// The returned header can be changed and will be valid for the lifetime of the packet.
#[no_mangle]
pub unsafe extern "C" fn sp_packet_get_header(
packet: NonNull<Packet>,
) -> NonNull<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]
pub unsafe extern "C" fn sp_packet_get_payload(
packet: NonNull<Packet>,
) -> ByteSlice {
unsafe { ByteSlice::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]
pub unsafe extern "C" fn sp_packet_set_payload(
packet: NonNull<Packet>,
data: ByteSlice,
) {
unsafe { (*packet.as_ptr()).payload = data.as_slice().to_vec() }
}
/// Serialize the packet into the provided buffer.
/// ///
/// # Panics /// # Panics
/// ///
/// - when `packet` is NULL /// - if the buffer is not big enough to hold header+payload.
/// #[no_mangle]
/// # Safety pub unsafe extern "C" fn sp_packet_serialize_to(
/// packet: NonNull<Packet>,
/// The caller has to make sure that: buffer: ByteSlice,
/// ) {
/// - `packet` points to a valid [SPPacket] unsafe {
/// - `packet` is not written to concurrently packet.as_ref().serialize_to(buffer.as_slice_mut());
/// - the returned instance is freed in some way, either by using a consuming function or }
/// by explicitly calling `sp_packet_free`. }
/// Clones a [Packet].
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_packet_clone( pub unsafe extern "C" fn sp_packet_clone(
packet: *const SPPacket, packet: NonNull<Packet>,
) -> NonNull<SPPacket> { ) -> NonNull<Packet> {
assert!(!packet.is_null()); let result = Box::new(unsafe { packet.as_ref().clone() });
let result = Box::new(SPPacket((*packet).0.clone()));
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
/// Deallocates a [SPPacket]. /// Deallocates a [Packet].
///
/// # 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: *mut SPPacket) { pub unsafe extern "C" fn sp_packet_free(packet: NonNull<Packet>) {
assert!(!packet.is_null()); _ = unsafe { Box::from_raw(packet.as_ptr()) }
_ = Box::from_raw(packet)
} }