WIP: next #1

Draft
vinzenz wants to merge 25 commits from next into main
25 changed files with 1842 additions and 3128 deletions

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#114385868af03f8cba7c87a630b501bb0106d140"
checksum = "93b52049be55a15fe37c13249d7f96aa8a5de56e1a41838e74a822ee8316a0c4"
dependencies = [ dependencies = [
"bitvec", "bitvec",
"bzip2", "bzip2",

View file

@ -15,14 +15,34 @@ 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"
[lints.clippy]
missing_safety_doc = "allow"
[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

@ -82,6 +82,21 @@ You have the choice of linking statically (recommended) or dynamically.
- documentation is included in the header and - documentation is included in the header and
available [online](https://docs.rs/servicepoint_binding_c/latest/servicepoint_binding_c/) available [online](https://docs.rs/servicepoint_binding_c/latest/servicepoint_binding_c/)
## Safety
Functions expect that C code honors NonNull annotations.
Any created instances have to be freed in some way.
Pointers to those instances cannot be used anymore after that.
Instances cannot be shared between threads and need to be locked in the using code.
Enum values have to be used as-is. Do not pass values that are not part of the enum.
UTF-8 or UTF-32 encoding has to be used properly.
Brightness values provided as u8 parameters must be in range.
## Everything else ## Everything else
Look at the main project [README](https://git.berlin.ccc.de/servicepoint/servicepoint/src/branch/main/README.md) for Look at the main project [README](https://git.berlin.ccc.de/servicepoint/servicepoint/src/branch/main/README.md) for

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", "std"]
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*/"

View file

@ -1,30 +1,94 @@
CC := gcc CC := gcc
CARGO := rustup run nightly cargo
TARGET := x86_64-unknown-linux-musl
PROFILE := size-optimized
THIS_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST)))) THIS_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST))))
REPO_ROOT := $(THIS_DIR)/.. REPO_ROOT := $(THIS_DIR)/..
RUST_TARGET_DIR := $(REPO_ROOT)/target/$(TARGET)/$(PROFILE)
export SERVICEPOINT_HEADER_OUT := $(REPO_ROOT)/include
build: out/example 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=$(PROFILE) \
--no-default-features \
--target=$(TARGET) \
-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 \
-Wall
#-fuse-ld=gold \
-fno-exceptions
#-Wl,--icf=all \
STRIPFLAGS := -s --strip-unneeded -R .comment -R .gnu.version -R .comment -R .note -R .note.gnu.build-id -R .note.ABI-tag
_c_src := $(wildcard *.c)
_programs := $(basename $(_c_src))
_bins := $(addprefix out/, $(_programs))
_unstripped_bins := $(addsuffix _unstripped, $(_bins))
_run_programs := $(addprefix run_, $(_programs))
_rs_src := $(wildcard ../src/**.rs) ../Cargo.lock
all: $(_bins)
clean: clean:
rm -r out || true rm -r out || true
rm include/servicepoint.h || true rm include/servicepoint.h || true
cargo clean cargo clean
run: out/example PHONY: all clean sizes $(_run_programs)
out/example
PHONY: build clean dependencies run $(_unstripped_bins): out/%_unstripped: %.c $(SERVICEPOINT_HEADER_OUT)/servicepoint.h $(RUST_TARGET_DIR)/libservicepoint_binding_c.a
out/example: dependencies main.c
mkdir -p out || true mkdir -p out || true
${CC} main.c \ ${CC} $^ \
-I $(REPO_ROOT)/include \ -I $(SERVICEPOINT_HEADER_OUT) \
-L $(REPO_ROOT)/target/release \ -L $(RUST_TARGET_DIR)\
-Wl,-Bstatic -lservicepoint_binding_c \ $(CCFLAGS) \
-Wl,-Bdynamic -llzma \ -o $@
-o out/example
dependencies: FORCE $(_bins): out/%: out/%_unstripped
cargo build --manifest-path=$(REPO_ROOT)/Cargo.toml --release strip $(STRIPFLAGS) $^ -o $@
$(SERVICEPOINT_HEADER_OUT)/servicepoint.h $(RUST_TARGET_DIR)/libservicepoint_binding_c.a: $(_rs_src)
mkdir -p include || true
# generate servicepoint header and binary to link against
${CARGO} rustc $(CARGOFLAGS) -- $(RUSTFLAGS)
$(_run_programs): run_%: out/% FORCE
./$<
sizes: $(_bins)
ls -lB out
#analyze-size: out/example_unstripped
# nm --print-size --size-sort --reverse-sort --radix=d --demangle out/example_unstripped \
# | awk '{size=$$2+0; print size "\t" $$4}' \
# | less
FORCE: ; FORCE: ;

33
example/announce.c Normal file
View file

@ -0,0 +1,33 @@
#include "servicepoint.h"
int main(void) {
//UdpConnection *connection = sp_udp_open_ipv4(172, 23, 42, 29, 2342);
UdpConnection *connection = sp_udp_open_ipv4(127, 0, 0, 1, 2342);
if (connection == NULL)
return 1;
sp_udp_send_header(connection, (Header) {.command_code = COMMAND_CODE_CLEAR});
CharGrid *grid = sp_char_grid_new(5, 2);
if (grid == NULL)
return 1;
sp_char_grid_set(grid, 0, 0, 'H');
sp_char_grid_set(grid, 1, 0, 'e');
sp_char_grid_set(grid, 2, 0, 'l');
sp_char_grid_set(grid, 3, 0, 'l');
sp_char_grid_set(grid, 4, 0, 'o');
sp_char_grid_set(grid, 0, 1, 'W');
sp_char_grid_set(grid, 1, 1, 'o');
sp_char_grid_set(grid, 2, 1, 'r');
sp_char_grid_set(grid, 3, 1, 'l');
sp_char_grid_set(grid, 4, 1, 'd');
Packet *packet = sp_char_grid_into_packet(grid, 0, 0);
if (packet == NULL)
return 1;
sp_udp_send_packet(connection, packet);
sp_udp_free(connection);
return 0;
}

View file

@ -0,0 +1,30 @@
#include "servicepoint.h"
int main(void) {
// UdpConnection *connection = sp_udp_open_ipv4(172, 23, 42, 29, 2342);
UdpConnection *connection = sp_udp_open_ipv4(127, 0, 0, 1, 2342);
if (connection == NULL)
return -1;
Bitmap *all_on = sp_bitmap_new_max_sized();
sp_bitmap_fill(all_on, true);
Packet *packet = sp_bitmap_into_packet(all_on, 0, 0, COMPRESSION_CODE_UNCOMPRESSED);
if (packet == NULL)
return -1;
sp_udp_send_packet(connection, packet);
BrightnessGrid *grid = sp_brightness_grid_new(TILE_WIDTH, TILE_HEIGHT);
ByteSlice slice = sp_brightness_grid_unsafe_data_ref(grid);
for (size_t index = 0; index < slice.length; index++) {
slice.start[index] = (uint8_t) (index % ((size_t) Brightness_MAX));
}
packet = sp_brightness_grid_into_packet(grid, 0, 0);
sp_udp_send_packet(connection, packet);
sp_udp_free(connection);
return 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;
}

26
example/random_stuff.c Normal file
View file

@ -0,0 +1,26 @@
#include <stdio.h>
#include "servicepoint.h"
int main(void) {
UdpConnection *connection = sp_udp_open_ipv4(127, 0, 0, 1, 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);
Packet *packet = sp_bitmap_into_packet(pixels, 0, 0, COMPRESSION_CODE_UNCOMPRESSED);
if (packet == NULL)
return 1;
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_udp_send_packet(connection, packet);
sp_udp_free(connection);
return 0;
}

View file

@ -33,25 +33,34 @@
{ 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}";
}; };
} }

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,22 @@
//! C functions for interacting with [SPBitmap]s use crate::byte_slice::ByteSlice;
//! use crate::{heap_drop, heap_move, heap_move_nonnull, heap_remove, SPBitVec};
//! prefix `sp_bitmap_` use servicepoint::{Bitmap, BitmapCommand, CompressionCode, DataRef, Grid, Origin, Packet};
use servicepoint::{DataRef, Grid};
use std::ptr::NonNull; use std::ptr::NonNull;
use crate::byte_slice::SPByteSlice; /// Creates a new [Bitmap] with the specified dimensions.
///
/// A grid of pixels. /// # 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,81 @@ 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)) heap_move(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_max_sized() -> NonNull<Bitmap> {
let result = Box::new(SPBitmap(servicepoint::Bitmap::max_sized())); heap_move_nonnull(Bitmap::max_sized())
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); heap_move(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]. /// Tries to convert the BitVec to a Bitmap.
/// ///
/// Will never return NULL. /// The provided BitVec gets consumed.
/// ///
/// # Panics /// Returns NULL in case of error.
/// #[no_mangle]
/// - when `bitmap` is NULL pub unsafe extern "C" fn sp_bitmap_from_bitvec(
/// width: usize,
/// # Safety bitvec: NonNull<SPBitVec>,
/// ) -> *mut Bitmap {
/// The caller has to make sure that: let bitvec = unsafe { heap_remove(bitvec) };
/// if let Ok(bitmap) = Bitmap::from_bitvec(width, bitvec.0) {
/// - `bitmap` points to a valid [SPBitmap] heap_move(bitmap)
/// - `bitmap` is not written to concurrently } else {
/// - the returned instance is freed in some way, either by using a consuming function or std::ptr::null_mut()
/// by explicitly calling `sp_bitmap_free`. }
}
/// Clones a [Bitmap].
#[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()); heap_move_nonnull(unsafe { bitmap.as_ref().clone() })
let result = Box::new(SPBitmap((*bitmap).0.clone()));
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 { heap_drop(bitmap) }
_ = 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 +109,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 +127,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 +165,60 @@ 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(), /// Consumes the Bitmap and returns the contained BitVec
length: data.len(), #[no_mangle]
pub unsafe extern "C" fn sp_bitmap_into_bitvec(
bitmap: NonNull<Bitmap>,
) -> NonNull<SPBitVec> {
let bitmap = unsafe { heap_remove(bitmap) };
heap_move_nonnull(SPBitVec(bitmap.into()))
}
/// Creates a [BitmapCommand] and immediately turns that into a [Packet].
///
/// The provided [Bitmap] gets consumed.
///
/// Returns NULL in case of an error.
#[no_mangle]
pub unsafe extern "C" fn sp_bitmap_into_packet(
bitmap: NonNull<Bitmap>,
x: usize,
y: usize,
compression: CompressionCode
) -> *mut Packet {
let bitmap = unsafe { heap_remove(bitmap) };
match Packet::try_from(BitmapCommand {
bitmap,
origin: Origin::new(x, y),
compression,
}) {
Ok(packet) => heap_move(packet),
Err(_) => std::ptr::null_mut(),
} }
} }

View file

@ -1,8 +1,5 @@
//! C functions for interacting with [SPBitVec]s use crate::{heap_drop, heap_move, heap_move_nonnull, heap_remove, ByteSlice};
//! use servicepoint::{BinaryOperation, BitVecCommand, BitVecU8Msb0, CompressionCode, Packet};
//! prefix `sp_bitvec_`
use crate::SPByteSlice;
use std::ptr::NonNull; use std::ptr::NonNull;
/// 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,37 @@ 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))); heap_move_nonnull(SPBitVec(BitVecU8Msb0::repeat(false, size)))
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: ByteSlice) -> NonNull<SPBitVec> {
data: *const u8, let data = unsafe { data.as_slice() };
data_length: usize, heap_move_nonnull(SPBitVec(BitVecU8Msb0::from_slice(data)))
) -> NonNull<SPBitVec> {
assert!(!data.is_null());
let data = std::slice::from_raw_parts(data, data_length);
let result = Box::new(SPBitVec(servicepoint::BitVec::from_slice(data)));
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()); heap_move_nonnull(unsafe { bit_vec.as_ref().clone() })
let result = Box::new((*bit_vec).clone());
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 { heap_drop(bit_vec) }
_ = 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 +68,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 +87,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 +103,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 +116,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 +126,47 @@ 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(), /// Creates a [BitVecCommand] and immediately turns that into a [Packet].
length: data.len(), ///
/// The provided [SPBitVec] gets consumed.
///
/// Returns NULL in case of an error.
#[no_mangle]
pub unsafe extern "C" fn sp_bitvec_into_packet(
bitvec: NonNull<SPBitVec>,
offset: usize,
operation: BinaryOperation,
compression: CompressionCode
) -> *mut Packet {
let bitvec = unsafe { heap_remove(bitvec) }.0;
match Packet::try_from(BitVecCommand {
bitvec,
offset,
operation,
compression,
}) {
Ok(packet) => heap_move(packet),
Err(_) => std::ptr::null_mut(),
} }
} }

View file

@ -1,146 +1,71 @@
//! C functions for interacting with [SPBrightnessGrid]s use crate::{heap_drop, heap_move, heap_move_nonnull, heap_remove, ByteSlice};
//! use servicepoint::{
//! prefix `sp_brightness_grid_` Brightness, BrightnessGrid, BrightnessGridCommand, ByteGrid, DataRef, Grid,
Origin, Packet,
use crate::SPByteSlice; };
use servicepoint::{DataRef, Grid}; use std::mem::transmute;
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_udp_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_udp_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( heap_move_nonnull(BrightnessGrid::new(width, height))
width, height,
)));
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. /// Any out of range values will be set to [Brightness::MAX] or [Brightness::MIN].
/// ///
/// # Panics /// returns: new [BrightnessGrid] instance, or NULL in case of an error.
///
/// - 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 data = std::slice::from_raw_parts(data, data_length); match ByteGrid::load(width, height, data)
let grid = servicepoint::ByteGrid::load(width, height, data); .map(move |grid| grid.map(Brightness::saturating_from))
let grid = servicepoint::BrightnessGrid::try_from(grid) {
.expect("invalid brightness value"); None => std::ptr::null_mut(),
let result = Box::new(SPBrightnessGrid(grid)); Some(grid) => heap_move(grid),
NonNull::from(Box::leak(result)) }
} }
/// 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()); heap_move_nonnull(unsafe { brightness_grid.as_ref().clone() })
let result = Box::new((*brightness_grid).clone());
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 { heap_drop(brightness_grid) }
_ = Box::from_raw(brightness_grid);
} }
/// Gets the current value at the specified position. /// Gets the current value at the specified position.
@ -153,27 +78,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 +100,98 @@ 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 {
SPByteSlice { ByteSlice::from_slice(transmute::<&mut [Brightness], &mut [u8]>(data))
start: NonNull::new(data.as_mut_ptr_range().start).unwrap(), }
length: data.len(), }
/// Creates a [BrightnessGridCommand] and immediately turns that into a [Packet].
///
/// The provided [BrightnessGrid] gets consumed.
///
/// Returns NULL in case of an error.
#[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_into_packet(
grid: NonNull<BrightnessGrid>,
x: usize,
y: usize,
) -> *mut Packet {
let grid = unsafe { heap_remove(grid) };
match Packet::try_from(BrightnessGridCommand {
grid,
origin: Origin::new(x, y),
}) {
Ok(packet) => heap_move(packet),
Err(_) => std::ptr::null_mut(),
} }
} }

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(&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::{heap_drop, heap_move, heap_move_nonnull, heap_remove, ByteSlice};
//! use servicepoint::{CharGrid, CharGridCommand, Grid, Origin, Packet};
//! 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,46 @@ 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 = heap_move_nonnull(CharGrid::new(width, height))
Box::new(SPCharGrid(servicepoint::CharGrid::new(width, height)));
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); heap_move(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()); heap_move_nonnull(unsafe { char_grid.as_ref().clone() })
let result = Box::new((*char_grid).clone());
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 { heap_drop(char_grid) }
_ = 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 +62,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 +84,72 @@ 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() }
/// Creates a [CharGridCommand] and immediately turns that into a [Packet].
///
/// The provided [CharGrid] gets consumed.
///
/// Returns NULL in case of an error.
#[no_mangle]
pub unsafe extern "C" fn sp_char_grid_into_packet(
grid: NonNull<CharGrid>,
x: usize,
y: usize,
) -> *mut Packet {
let grid = unsafe { heap_remove(grid) };
match Packet::try_from(CharGridCommand {
grid,
origin: Origin::new(x, y),
}) {
Ok(packet) => heap_move(packet),
Err(_) => std::ptr::null_mut(),
}
} }

View file

@ -1,498 +0,0 @@
//! C functions for interacting with [SPCommand]s
//!
//! prefix `sp_command_`
use std::ptr::{null_mut, NonNull};
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.
///
/// Returns: pointer to new [SPCommand] instance or NULL if parsing failed.
///
/// # Panics
///
/// - when `packet` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - [SPPacket] points to a valid instance of [SPPacket]
/// - [SPPacket] is not used concurrently or after this call
/// - the result is checked for NULL
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_try_from_packet(
packet: *mut SPPacket,
) -> *mut SPCommand {
let packet = *Box::from_raw(packet);
match servicepoint::Command::try_from(packet.0) {
Err(_) => null_mut(),
Ok(command) => Box::into_raw(Box::new(SPCommand(command))),
}
}
/// Clones a [SPCommand] instance.
///
/// returns: new [SPCommand] instance. Will never return NULL.
///
/// # Panics
///
/// - when `command` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `command` points to a valid instance of [SPCommand]
/// - `command` is not written to concurrently
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_clone(
command: *const SPCommand,
) -> NonNull<SPCommand> {
assert!(!command.is_null());
let result = Box::new((*command).clone());
NonNull::from(Box::leak(result))
}
/// Set all pixels to the off state.
///
/// Does not affect brightness.
///
/// Returns: a new [servicepoint::Command::Clear] instance. Will never return NULL.
///
/// # Examples
///
/// ```C
/// 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]
pub unsafe extern "C" fn sp_command_clear() -> NonNull<SPCommand> {
let result = Box::new(SPCommand(servicepoint::Command::Clear));
NonNull::from(Box::leak(result))
}
/// Kills the udp daemon on the display, which usually results in a restart.
///
/// Please do not send this in your normal program flow.
///
/// Returns: a new [servicepoint::Command::HardReset] instance. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_hard_reset() -> NonNull<SPCommand> {
let result = Box::new(SPCommand(servicepoint::Command::HardReset));
NonNull::from(Box::leak(result))
}
/// A yet-to-be-tested command.
///
/// Returns: a new [servicepoint::Command::FadeOut] instance. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_fade_out() -> NonNull<SPCommand> {
let result = Box::new(SPCommand(servicepoint::Command::FadeOut));
NonNull::from(Box::leak(result))
}
/// Set the brightness of all tiles to the same value.
///
/// Returns: a new [servicepoint::Command::Brightness] instance. Will never return NULL.
///
/// # Panics
///
/// - When the provided brightness value is out of range (0-11).
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_brightness(
brightness: u8,
) -> NonNull<SPCommand> {
let brightness = servicepoint::Brightness::try_from(brightness)
.expect("invalid brightness");
let result =
Box::new(SPCommand(servicepoint::Command::Brightness(brightness)));
NonNull::from(Box::leak(result))
}
/// Set the brightness of individual tiles in a rectangular area of the display.
///
/// The passed [SPBrightnessGrid] gets consumed.
///
/// Returns: a new [servicepoint::Command::CharBrightness] instance. Will never return NULL.
///
/// # Panics
///
/// - when `grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `grid` points to a valid instance of [SPBrightnessGrid]
/// - `grid` is not used concurrently or after this call
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_char_brightness(
x: usize,
y: usize,
grid: *mut SPBrightnessGrid,
) -> NonNull<SPCommand> {
assert!(!grid.is_null());
let byte_grid = *Box::from_raw(grid);
let result = Box::new(SPCommand(servicepoint::Command::CharBrightness(
servicepoint::Origin::new(x, y),
byte_grid.0,
)));
NonNull::from(Box::leak(result))
}
/// Set pixel data starting at the pixel offset on screen.
///
/// The screen will continuously overwrite more pixel data without regarding the offset, meaning
/// once the starting row is full, overwriting will continue on column 0.
///
/// The contained [SPBitVec] is always uncompressed.
///
/// The passed [SPBitVec] gets consumed.
///
/// Returns: a new [servicepoint::Command::BitmapLinear] instance. Will never return NULL.
///
/// # Panics
///
/// - when `bit_vec` is null
/// - when `compression_code` is not a valid value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid instance of [SPBitVec]
/// - `bit_vec` is not used concurrently or after this call
/// - `compression` matches one of the allowed enum values
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_bitmap_linear(
offset: usize,
bit_vec: *mut SPBitVec,
compression: SPCompressionCode,
) -> NonNull<SPCommand> {
assert!(!bit_vec.is_null());
let bit_vec = *Box::from_raw(bit_vec);
let result = Box::new(SPCommand(servicepoint::Command::BitmapLinear(
offset,
bit_vec.into(),
compression.try_into().expect("invalid compression code"),
)));
NonNull::from(Box::leak(result))
}
/// Set pixel data according to an and-mask starting at the offset.
///
/// The screen will continuously overwrite more pixel data without regarding the offset, meaning
/// once the starting row is full, overwriting will continue on column 0.
///
/// The contained [SPBitVec] is always uncompressed.
///
/// The passed [SPBitVec] gets consumed.
///
/// Returns: a new [servicepoint::Command::BitmapLinearAnd] instance. Will never return NULL.
///
/// # Panics
///
/// - when `bit_vec` is null
/// - when `compression_code` is not a valid value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid instance of [SPBitVec]
/// - `bit_vec` is not used concurrently or after this call
/// - `compression` matches one of the allowed enum values
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_bitmap_linear_and(
offset: usize,
bit_vec: *mut SPBitVec,
compression: SPCompressionCode,
) -> NonNull<SPCommand> {
assert!(!bit_vec.is_null());
let bit_vec = *Box::from_raw(bit_vec);
let result = Box::new(SPCommand(servicepoint::Command::BitmapLinearAnd(
offset,
bit_vec.into(),
compression.try_into().expect("invalid compression code"),
)));
NonNull::from(Box::leak(result))
}
/// Set pixel data according to an or-mask starting at the offset.
///
/// The screen will continuously overwrite more pixel data without regarding the offset, meaning
/// once the starting row is full, overwriting will continue on column 0.
///
/// The contained [SPBitVec] is always uncompressed.
///
/// The passed [SPBitVec] gets consumed.
///
/// Returns: a new [servicepoint::Command::BitmapLinearOr] instance. Will never return NULL.
///
/// # Panics
///
/// - when `bit_vec` is null
/// - when `compression_code` is not a valid value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid instance of [SPBitVec]
/// - `bit_vec` is not used concurrently or after this call
/// - `compression` matches one of the allowed enum values
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_bitmap_linear_or(
offset: usize,
bit_vec: *mut SPBitVec,
compression: SPCompressionCode,
) -> NonNull<SPCommand> {
assert!(!bit_vec.is_null());
let bit_vec = *Box::from_raw(bit_vec);
let result = Box::new(SPCommand(servicepoint::Command::BitmapLinearOr(
offset,
bit_vec.into(),
compression.try_into().expect("invalid compression code"),
)));
NonNull::from(Box::leak(result))
}
/// Set pixel data according to a xor-mask starting at the offset.
///
/// The screen will continuously overwrite more pixel data without regarding the offset, meaning
/// once the starting row is full, overwriting will continue on column 0.
///
/// The contained [SPBitVec] is always uncompressed.
///
/// The passed [SPBitVec] gets consumed.
///
/// Returns: a new [servicepoint::Command::BitmapLinearXor] instance. Will never return NULL.
///
/// # Panics
///
/// - when `bit_vec` is null
/// - when `compression_code` is not a valid value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid instance of [SPBitVec]
/// - `bit_vec` is not used concurrently or after this call
/// - `compression` matches one of the allowed enum values
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_bitmap_linear_xor(
offset: usize,
bit_vec: *mut SPBitVec,
compression: SPCompressionCode,
) -> NonNull<SPCommand> {
assert!(!bit_vec.is_null());
let bit_vec = *Box::from_raw(bit_vec);
let result = Box::new(SPCommand(servicepoint::Command::BitmapLinearXor(
offset,
bit_vec.into(),
compression.try_into().expect("invalid compression code"),
)));
NonNull::from(Box::leak(result))
}
/// Show codepage 437 encoded text on the screen.
///
/// The passed [SPCp437Grid] gets consumed.
///
/// Returns: a new [servicepoint::Command::Cp437Data] instance. Will never return NULL.
///
/// # Panics
///
/// - when `grid` is null
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `grid` points to a valid instance of [SPCp437Grid]
/// - `grid` is not used concurrently or after this call
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_cp437_data(
x: usize,
y: usize,
grid: *mut SPCp437Grid,
) -> NonNull<SPCommand> {
assert!(!grid.is_null());
let grid = *Box::from_raw(grid);
let result = Box::new(SPCommand(servicepoint::Command::Cp437Data(
servicepoint::Origin::new(x, y),
grid.0,
)));
NonNull::from(Box::leak(result))
}
/// Show UTF-8 encoded text on the screen.
///
/// The passed [SPCharGrid] gets consumed.
///
/// Returns: a new [servicepoint::Command::Utf8Data] instance. Will never return NULL.
///
/// # Panics
///
/// - when `grid` is null
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `grid` points to a valid instance of [SPCharGrid]
/// - `grid` is not used concurrently or after this call
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_utf8_data(
x: usize,
y: usize,
grid: *mut SPCharGrid,
) -> NonNull<SPCommand> {
assert!(!grid.is_null());
let grid = *Box::from_raw(grid);
let result = Box::new(SPCommand(servicepoint::Command::Utf8Data(
servicepoint::Origin::new(x, y),
grid.0,
)));
NonNull::from(Box::leak(result))
}
/// Sets a window of pixels to the specified values.
///
/// The passed [SPBitmap] gets consumed.
///
/// Returns: a new [servicepoint::Command::BitmapLinearWin] instance. Will never return NULL.
///
/// # Panics
///
/// - when `bitmap` is null
/// - when `compression_code` is not a valid value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid instance of [SPBitmap]
/// - `bitmap` is not used concurrently or after this call
/// - `compression` matches one of the allowed enum values
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_bitmap_linear_win(
x: usize,
y: usize,
bitmap: *mut SPBitmap,
compression_code: SPCompressionCode,
) -> NonNull<SPCommand> {
assert!(!bitmap.is_null());
let byte_grid = (*Box::from_raw(bitmap)).0;
let result = Box::new(SPCommand(servicepoint::Command::BitmapLinearWin(
servicepoint::Origin::new(x, y),
byte_grid,
compression_code
.try_into()
.expect("invalid compression code"),
)));
NonNull::from(Box::leak(result))
}
/// Deallocates a [SPCommand].
///
/// # Examples
///
/// ```C
/// SPCommand c = sp_command_clear();
/// 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]
pub unsafe extern "C" fn sp_command_free(command: *mut SPCommand) {
assert!(!command.is_null());
_ = Box::from_raw(command);
}

3
src/commands/mod.rs Normal file
View file

@ -0,0 +1,3 @@
mod typed;
pub use typed::*;

201
src/commands/typed.rs Normal file
View file

@ -0,0 +1,201 @@
use crate::{heap_drop, heap_move, heap_move_nonnull, SPBitVec};
use servicepoint::{
BinaryOperation, Bitmap, Brightness, BrightnessGrid, CharGrid,
CompressionCode, Cp437Grid, GlobalBrightnessCommand, Packet, TypedCommand,
};
use std::ptr::NonNull;
/// Tries to turn a [Packet] into a [TypedCommand].
///
/// The packet is deallocated in the process.
///
/// Returns: pointer to new [TypedCommand] instance or NULL if parsing failed.
#[no_mangle]
pub unsafe extern "C" fn sp_command_try_from_packet(
packet: NonNull<Packet>,
) -> *mut TypedCommand {
let packet = *unsafe { Box::from_raw(packet.as_ptr()) };
match servicepoint::TypedCommand::try_from(packet) {
Err(_) => std::ptr::null_mut(),
Ok(command) => heap_move(command),
}
}
/// Clones a [TypedCommand] instance.
///
/// returns: new [TypedCommand] instance.
#[no_mangle]
pub unsafe extern "C" fn sp_command_clone(
command: NonNull<TypedCommand>,
) -> NonNull<TypedCommand> {
heap_move_nonnull(unsafe { command.as_ref().clone() })
}
/// Set all pixels to the off state.
///
/// Does not affect brightness.
///
/// Returns: a new [servicepoint::Command::Clear] instance.
///
/// # Examples
///
/// ```C
/// sp_udp_send_command(connection, sp_command_clear());
/// ```
#[no_mangle]
pub unsafe extern "C" fn sp_command_clear() -> NonNull<TypedCommand> {
heap_move_nonnull(servicepoint::ClearCommand.into())
}
/// Kills the udp daemon on the display, which usually results in a restart.
///
/// Please do not send this in your normal program flow.
///
/// Returns: a new [servicepoint::Command::HardReset] instance.
#[no_mangle]
pub unsafe extern "C" fn sp_command_hard_reset() -> NonNull<TypedCommand> {
heap_move_nonnull(servicepoint::HardResetCommand.into())
}
/// A yet-to-be-tested command.
///
/// Returns: a new [servicepoint::Command::FadeOut] instance.
#[no_mangle]
pub unsafe extern "C" fn sp_command_fade_out() -> NonNull<TypedCommand> {
heap_move_nonnull(servicepoint::FadeOutCommand.into())
}
/// Set the brightness of all tiles to the same value.
///
/// Returns: a new [servicepoint::Command::Brightness] instance.
#[no_mangle]
pub unsafe extern "C" fn sp_command_global_brightness(
brightness: Brightness,
) -> NonNull<TypedCommand> {
heap_move_nonnull(GlobalBrightnessCommand::from(brightness).into())
}
/// Set the brightness of individual tiles in a rectangular area of the display.
///
/// The passed [BrightnessGrid] gets consumed.
///
/// Returns: a new [servicepoint::Command::CharBrightness] instance.
#[no_mangle]
pub unsafe extern "C" fn sp_command_brightness_grid(
x: usize,
y: usize,
grid: NonNull<BrightnessGrid>,
) -> NonNull<TypedCommand> {
let grid = unsafe { *Box::from_raw(grid.as_ptr()) };
let result = servicepoint::BrightnessGridCommand {
origin: servicepoint::Origin::new(x, y),
grid,
}
.into();
heap_move_nonnull(result)
}
/// Set pixel data starting at the pixel offset on screen.
///
/// The screen will continuously overwrite more pixel data without regarding the offset, meaning
/// once the starting row is full, overwriting will continue on column 0.
///
/// The [`BinaryOperation`] will be applied on the display comparing old and sent bit.
///
/// `new_bit = old_bit op sent_bit`
///
/// For example, [`BinaryOperation::Or`] can be used to turn on some pixels without affecting other pixels.
///
/// The contained [`BitVecU8Msb0`] is always uncompressed.
#[no_mangle]
pub unsafe extern "C" fn sp_command_bitvec(
offset: usize,
bit_vec: NonNull<SPBitVec>,
compression: CompressionCode,
operation: BinaryOperation,
) -> *mut TypedCommand {
let bit_vec = unsafe { *Box::from_raw(bit_vec.as_ptr()) };
let command = servicepoint::BitVecCommand {
offset,
operation,
bitvec: bit_vec.0,
compression,
}
.into();
heap_move(command)
}
/// Show codepage 437 encoded text on the screen.
///
/// The passed [Cp437Grid] gets consumed.
///
/// Returns: a new [servicepoint::Cp437GridCommand] instance.
#[no_mangle]
pub unsafe extern "C" fn sp_command_cp437_grid(
x: usize,
y: usize,
grid: NonNull<Cp437Grid>,
) -> NonNull<TypedCommand> {
let grid = *unsafe { Box::from_raw(grid.as_ptr()) };
let result = servicepoint::Cp437GridCommand {
origin: servicepoint::Origin::new(x, y),
grid,
}
.into();
heap_move_nonnull(result)
}
/// Show UTF-8 encoded text on the screen.
///
/// The passed [CharGrid] gets consumed.
///
/// Returns: a new [servicepoint::CharGridCommand] instance.
#[no_mangle]
pub unsafe extern "C" fn sp_command_char_grid(
x: usize,
y: usize,
grid: NonNull<CharGrid>,
) -> NonNull<TypedCommand> {
let grid = unsafe { *Box::from_raw(grid.as_ptr()) };
let result = servicepoint::CharGridCommand {
origin: servicepoint::Origin::new(x, y),
grid,
}
.into();
heap_move_nonnull(result)
}
/// Sets a window of pixels to the specified values.
///
/// The passed [Bitmap] gets consumed.
///
/// Returns: a new [servicepoint::BitmapCommand] instance.
#[no_mangle]
pub unsafe extern "C" fn sp_command_bitmap(
x: usize,
y: usize,
bitmap: NonNull<Bitmap>,
compression: CompressionCode,
) -> *mut TypedCommand {
let bitmap = unsafe { *Box::from_raw(bitmap.as_ptr()) };
let command = servicepoint::BitmapCommand {
origin: servicepoint::Origin::new(x, y),
bitmap,
compression,
}
.into();
heap_move(command)
}
/// Deallocates a [TypedCommand].
///
/// # Examples
///
/// ```C
/// TypedCommand c = sp_command_clear();
/// sp_command_free(c);
/// ```
#[no_mangle]
pub unsafe extern "C" fn sp_command_free(command: NonNull<TypedCommand>) {
unsafe { heap_drop(command) }
}

View file

@ -1,139 +1,119 @@
//! C functions for interacting with [SPConnection]s use crate::{heap_drop, heap_move, heap_remove};
//! use servicepoint::{Connection, Header, 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::net::{Ipv4Addr, SocketAddrV4};
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
/// ///
/// ```C /// ```C
/// CConnection connection = sp_connection_open("172.23.42.29:2342"); /// UdpConnection connection = sp_udp_open("172.23.42.29:2342");
/// if (connection != NULL) /// if (connection != NULL)
/// sp_connection_send_command(connection, sp_command_clear()); /// sp_udp_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_udp_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))) heap_move(connection)
} }
/// Creates a new instance of [SPConnection] for testing that does not actually send anything. /// Creates a new instance of [UdpConnection].
/// ///
/// returns: a new instance. Will never return NULL. /// returns: NULL if connection fails, or connected instance
/// ///
/// # Safety /// # Examples
/// ///
/// The caller has to make sure that: /// ```C
/// /// UdpConnection connection = sp_udp_open_ipv4(172, 23, 42, 29, 2342);
/// - the returned instance is freed in some way, either by using a consuming function or /// if (connection != NULL)
/// by explicitly calling `sp_connection_free`. /// sp_udp_send_command(connection, sp_command_clear());
/// ```
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_connection_fake() -> NonNull<SPConnection> { pub unsafe extern "C" fn sp_udp_open_ipv4(
let result = Box::new(SPConnection(servicepoint::Connection::Fake)); ip1: u8,
NonNull::from(Box::leak(result)) ip2: u8,
ip3: u8,
ip4: u8,
port: u16,
) -> *mut UdpConnection {
let addr = SocketAddrV4::new(Ipv4Addr::from([ip1, ip2, ip3, ip4]), port);
let connection = match UdpConnection::open(addr) {
Err(_) => return std::ptr::null_mut(),
Ok(value) => value,
};
heap_move(connection)
} }
/// Sends a [SPPacket] to the display using the [SPConnection]. /// 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_udp_send_packet(
connection: *const SPConnection, connection: NonNull<UdpConnection>,
packet: *mut SPPacket, packet: NonNull<Packet>,
) -> bool { ) -> bool {
assert!(!connection.is_null()); let packet = unsafe { heap_remove(packet) };
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_udp_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_udp_send_command(
connection: *const SPConnection, connection: NonNull<UdpConnection>,
command: *mut SPCommand, command: NonNull<TypedCommand>,
) -> bool { ) -> bool {
assert!(!connection.is_null()); let command = unsafe { heap_remove(command) };
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]. /// Sends a [Header] to the display using the [UdpConnection].
/// ///
/// # Panics /// returns: true in case of success
/// ///
/// - when `connection` is NULL /// # Examples
/// ///
/// # Safety /// ```C
/// /// sp_udp_send_header(connection, sp_command_brightness(5));
/// 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_udp_send_header(
assert!(!connection.is_null()); udp_connection: NonNull<UdpConnection>,
_ = Box::from_raw(connection); header: Header,
) -> bool {
let packet = Packet {
header,
payload: vec![],
};
unsafe { udp_connection.as_ref() }.send(packet).is_ok()
}
/// Closes and deallocates a [UdpConnection].
#[no_mangle]
pub unsafe extern "C" fn sp_udp_free(connection: NonNull<UdpConnection>) {
unsafe { heap_drop(connection) }
} }

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,46 @@
//! C functions for interacting with [SPCp437Grid]s use crate::{heap_drop, heap_move, heap_move_nonnull, heap_remove, ByteSlice};
//! use servicepoint::{Cp437Grid, Cp437GridCommand, DataRef, Grid, Origin, Packet};
//! 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 = heap_move_nonnull(Cp437Grid::new(width, height))
Box::new(SPCp437Grid(servicepoint::Cp437Grid::new(width, height)));
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( heap_move(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()); heap_move_nonnull(unsafe { cp437_grid.as_ref().clone() })
let result = Box::new((*cp437_grid).clone());
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 { heap_drop(cp437_grid) }
_ = Box::from_raw(cp437_grid);
} }
/// Gets the current value at the specified position. /// Gets the current value at the specified position.
@ -133,26 +52,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 +74,82 @@ 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(), /// Creates a [Cp437GridCommand] and immediately turns that into a [Packet].
///
/// The provided [Cp437Grid] gets consumed.
///
/// Returns NULL in case of an error.
#[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_into_packet(
grid: NonNull<Cp437Grid>,
x: usize,
y: usize,
) -> *mut Packet {
let grid = unsafe { heap_remove(grid) };
match Packet::try_from(Cp437GridCommand {
grid,
origin: Origin::new(x, y),
}) {
Ok(packet) => heap_move(packet),
Err(_) => std::ptr::null_mut(),
} }
} }

View file

@ -9,18 +9,18 @@
//! #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_udp_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_udp_send_command(connection, sp_command_clone(command)));
//! //!
//! sp_command_free(command); //! sp_command_free(command);
//! sp_connection_free(connection); //! sp_udp_free(connection);
//! return 0; //! return 0;
//! } //! }
//! ``` //! ```
@ -30,19 +30,40 @@ pub use crate::bitvec::*;
pub use crate::brightness_grid::*; pub use crate::brightness_grid::*;
pub use crate::byte_slice::*; pub use crate::byte_slice::*;
pub use crate::char_grid::*; pub use crate::char_grid::*;
pub use crate::command::*; pub use crate::commands::*;
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::*;
pub use servicepoint::CommandCode;
use std::ptr::NonNull;
mod bitmap; mod bitmap;
mod bitvec; mod bitvec;
mod brightness_grid; mod brightness_grid;
mod byte_slice; mod byte_slice;
mod char_grid; mod char_grid;
mod command; mod commands;
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();
pub(crate) fn heap_move<T>(x: T) -> *mut T {
Box::into_raw(Box::new(x))
}
pub(crate) fn heap_move_nonnull<T>(x: T) -> NonNull<T> {
NonNull::from(Box::leak(Box::new(x)))
}
pub(crate) unsafe fn heap_drop<T>(x: NonNull<T>) {
drop(unsafe { heap_remove(x) });
}
pub(crate) unsafe fn heap_remove<T>(x: NonNull<T>) -> T {
unsafe { *Box::from_raw(x.as_ptr()) }
}

View file

@ -1,166 +1,128 @@
//! C functions for interacting with [SPPacket]s use crate::{heap_drop, heap_move, heap_move_nonnull, heap_remove, ByteSlice};
//! use servicepoint::{CommandCode, 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 { heap_remove(command) };
let command = *Box::from_raw(command); if let Ok(packet) = command.try_into() {
let result = Box::new(SPPacket(command.0.into())); heap_move(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) => heap_move(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 { heap_move_nonnull(Packet { header, payload })
header: servicepoint::Header {
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: mut 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()); heap_move_nonnull(unsafe { packet.as_ref().clone() })
let result = Box::new(SPPacket((*packet).0.clone()));
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 { heap_drop(packet) }
_ = Box::from_raw(packet) }
/// Converts u16 into [CommandCode].
///
/// If the provided value is not valid, false is returned and result is not changed.
#[no_mangle]
pub unsafe extern "C" fn sp_u16_to_command_code(
code: u16,
result: *mut CommandCode,
) -> bool {
match CommandCode::try_from(code) {
Ok(code) => {
unsafe {
*result = code;
}
true
}
Err(_) => false,
}
} }