From ce70ecd9e29d594f3bb137033cfd4f49c7cb4f48 Mon Sep 17 00:00:00 2001
From: Vinzenz Schroeter <vinzenz.f.s@gmail.com>
Date: Sat, 12 Apr 2025 21:47:23 +0200
Subject: [PATCH] add command code constants, send header

---
 Cargo.lock                            |  2 +-
 Cargo.toml                            |  3 ++
 example/Makefile                      | 37 ++++++++--------
 example/announce.c                    |  8 ++--
 include/servicepoint.h                | 63 +++++++++++++++++++++++----
 src/brightness_grid.rs                |  6 +--
 src/byte_slice.rs                     |  2 +-
 src/commands/mod.rs                   |  3 ++
 src/{command.rs => commands/typed.rs} | 10 +----
 src/connection.rs                     | 34 ++++++++++++---
 src/lib.rs                            | 11 ++---
 src/packet.rs                         | 22 ++++++++--
 12 files changed, 141 insertions(+), 60 deletions(-)
 create mode 100644 src/commands/mod.rs
 rename src/{command.rs => commands/typed.rs} (94%)

diff --git a/Cargo.lock b/Cargo.lock
index f87ecea..968bfeb 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -408,7 +408,7 @@ dependencies = [
 [[package]]
 name = "servicepoint"
 version = "0.13.2"
-source = "git+https://git.berlin.ccc.de/servicepoint/servicepoint/?branch=next#531d4e6b4a368dc126cab9dc12b64d2ca8a81694"
+source = "git+https://git.berlin.ccc.de/servicepoint/servicepoint/?branch=next#114385868af03f8cba7c87a630b501bb0106d140"
 dependencies = [
  "bitvec",
  "bzip2",
diff --git a/Cargo.toml b/Cargo.toml
index 7e5ed5d..e92ceb2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -33,6 +33,9 @@ default = ["full"]
 missing-docs = "warn"
 unsafe_op_in_unsafe_fn = "warn"
 
+[lints.clippy]
+missing_safety_doc = "allow"
+
 [package.metadata.docs.rs]
 all-features = true
 
diff --git a/example/Makefile b/example/Makefile
index bb53fe1..f69f385 100644
--- a/example/Makefile
+++ b/example/Makefile
@@ -1,9 +1,12 @@
 CC := gcc
 CARGO := rustup run nightly cargo
 
+TARGET := x86_64-unknown-linux-musl
+PROFILE := size-optimized
+
 THIS_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST))))
 REPO_ROOT := $(THIS_DIR)/..
-RUST_TARGET_DIR := $(REPO_ROOT)/target/x86_64-unknown-linux-musl/size-optimized
+RUST_TARGET_DIR := $(REPO_ROOT)/target/$(TARGET)/$(PROFILE)
 export SERVICEPOINT_HEADER_OUT := $(REPO_ROOT)/include
 
 RUSTFLAGS := -Zlocation-detail=none \
@@ -17,9 +20,9 @@ RUSTFLAGS := -Zlocation-detail=none \
 	-C panic=abort
 
 CARGOFLAGS := --manifest-path=$(REPO_ROOT)/Cargo.toml \
-	--profile=size-optimized \
+	--profile=$(PROFILE) \
 	--no-default-features \
-	--target=x86_64-unknown-linux-musl \
+	--target=$(TARGET) \
 	-Zbuild-std="core,std,alloc,proc_macro,panic_abort" \
 	-Zbuild-std-features="panic_immediate_abort" \
 
@@ -45,23 +48,23 @@ CCFLAGS := -static -Os \
 
 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
+_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)
+all: $(_bins)
 
 clean:
 	rm -r out || true
 	rm include/servicepoint.h || true
 	cargo clean
 
-PHONY: all clean sizes $(run_programs)
+PHONY: all clean sizes $(_run_programs)
 
-$(unstripped_bins): out/%_unstripped: %.c $(SERVICEPOINT_HEADER_OUT)/servicepoint.h $(RUST_TARGET_DIR)/libservicepoint_binding_c.a
+$(_unstripped_bins): out/%_unstripped: %.c $(SERVICEPOINT_HEADER_OUT)/servicepoint.h $(RUST_TARGET_DIR)/libservicepoint_binding_c.a
 	mkdir -p out || true
 	${CC} $^ \
 		-I $(SERVICEPOINT_HEADER_OUT) \
@@ -69,18 +72,18 @@ $(unstripped_bins): out/%_unstripped: %.c $(SERVICEPOINT_HEADER_OUT)/servicepoin
 		$(CCFLAGS) \
 		-o $@
 
-$(bins): out/%: out/%_unstripped
+$(_bins): out/%: out/%_unstripped
 	strip $(STRIPFLAGS) $^ -o $@
 
-$(SERVICEPOINT_HEADER_OUT)/servicepoint.h $(RUST_TARGET_DIR)/libservicepoint_binding_c.a: $(rs_src)
+$(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
+$(_run_programs): run_%: out/% FORCE
 	./$<
 
-sizes: $(bins)
+sizes: $(_bins)
 	ls -lB out
 
 #analyze-size: out/example_unstripped
@@ -88,6 +91,4 @@ sizes: $(bins)
 #		| awk '{size=$$2+0; print size "\t" $$4}' \
 #		| less
 
-
-
 FORCE: ;
diff --git a/example/announce.c b/example/announce.c
index d7a55fb..9220242 100644
--- a/example/announce.c
+++ b/example/announce.c
@@ -1,16 +1,17 @@
 #include "servicepoint.h"
 
 int main(void) {
-    UdpConnection *connection = sp_udp_open_ipv4(172, 23, 42, 29, 2342);
+    //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_command(connection, sp_command_clear());
+    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');
@@ -22,7 +23,6 @@ int main(void) {
     sp_char_grid_set(grid, 3, 1, 'l');
     sp_char_grid_set(grid, 4, 1, 'd');
 
-
     TypedCommand *command = sp_command_char_grid(0, 0, grid);
     sp_udp_send_command(connection, command);
 
diff --git a/include/servicepoint.h b/include/servicepoint.h
index aa7893f..0a0d340 100644
--- a/include/servicepoint.h
+++ b/include/servicepoint.h
@@ -95,6 +95,36 @@ enum BinaryOperation
 typedef uint8_t BinaryOperation;
 #endif // __cplusplus
 
+/**
+ * The u16 command codes used for the [Command]s.
+ */
+enum CommandCode
+#ifdef __cplusplus
+  : uint16_t
+#endif // __cplusplus
+ {
+    COMMAND_CODE_CLEAR = 2,
+    COMMAND_CODE_CP437_DATA = 3,
+    COMMAND_CODE_CHAR_BRIGHTNESS = 5,
+    COMMAND_CODE_BRIGHTNESS = 7,
+    COMMAND_CODE_HARD_RESET = 11,
+    COMMAND_CODE_FADE_OUT = 13,
+    COMMAND_CODE_BITMAP_LEGACY = 16,
+    COMMAND_CODE_BITMAP_LINEAR = 18,
+    COMMAND_CODE_BITMAP_LINEAR_WIN_UNCOMPRESSED = 19,
+    COMMAND_CODE_BITMAP_LINEAR_AND = 20,
+    COMMAND_CODE_BITMAP_LINEAR_OR = 21,
+    COMMAND_CODE_BITMAP_LINEAR_XOR = 22,
+    COMMAND_CODE_BITMAP_LINEAR_WIN_ZLIB = 23,
+    COMMAND_CODE_BITMAP_LINEAR_WIN_BZIP2 = 24,
+    COMMAND_CODE_BITMAP_LINEAR_WIN_LZMA = 25,
+    COMMAND_CODE_UTF8_DATA = 32,
+    COMMAND_CODE_BITMAP_LINEAR_WIN_ZSTD = 26,
+};
+#ifndef __cplusplus
+typedef uint16_t CommandCode;
+#endif // __cplusplus
+
 /**
  * Specifies the kind of compression to use. Availability depends on features.
  *
@@ -683,7 +713,7 @@ BrightnessGrid *sp_brightness_grid_load(size_t width,
  *
  * # Examples
  * ```C
- * UdpConnection connection = sp_connection_open("127.0.0.1:2342");
+ * UdpConnection connection = sp_udp_open("127.0.0.1:2342");
  * if (connection == NULL)
  *     return 1;
  *
@@ -692,7 +722,7 @@ BrightnessGrid *sp_brightness_grid_load(size_t width,
  * sp_brightness_grid_set(grid, 1, 1, 10);
  *
  * TypedCommand command = sp_command_char_brightness(grid);
- * sp_connection_free(connection);
+ * sp_udp_free(connection);
  * ```
  */
 BrightnessGrid */*notnull*/ sp_brightness_grid_new(size_t width, size_t height);
@@ -899,7 +929,7 @@ TypedCommand */*notnull*/ sp_command_char_grid(size_t x,
  * # Examples
  *
  * ```C
- * sp_connection_send_command(connection, sp_command_clear());
+ * sp_udp_send_command(connection, sp_command_clear());
  * ```
  */
 TypedCommand */*notnull*/ sp_command_clear(void);
@@ -1122,6 +1152,8 @@ void sp_packet_set_payload(Packet */*notnull*/ packet, ByteSlice data);
  */
 Packet *sp_packet_try_load(ByteSlice data);
 
+bool sp_u16_to_command_code(uint16_t code, CommandCode *result);
+
 /**
  * Closes and deallocates a [UdpConnection].
  */
@@ -1135,9 +1167,9 @@ void sp_udp_free(UdpConnection */*notnull*/ connection);
  * # Examples
  *
  * ```C
- * UdpConnection connection = sp_connection_open("172.23.42.29:2342");
+ * UdpConnection connection = sp_udp_open("172.23.42.29:2342");
  * if (connection != NULL)
- *     sp_connection_send_command(connection, sp_command_clear());
+ *     sp_udp_send_command(connection, sp_command_clear());
  * ```
  */
 UdpConnection *sp_udp_open(char */*notnull*/ host);
@@ -1150,9 +1182,9 @@ UdpConnection *sp_udp_open(char */*notnull*/ host);
  * # Examples
  *
  * ```C
- * UdpConnection connection = sp_connection_open_ipv4(172, 23, 42, 29, 2342);
+ * UdpConnection connection = sp_udp_open_ipv4(172, 23, 42, 29, 2342);
  * if (connection != NULL)
- *     sp_connection_send_command(connection, sp_command_clear());
+ *     sp_udp_send_command(connection, sp_command_clear());
  * ```
  */
 UdpConnection *sp_udp_open_ipv4(uint8_t ip1,
@@ -1171,13 +1203,26 @@ UdpConnection *sp_udp_open_ipv4(uint8_t ip1,
  * # Examples
  *
  * ```C
- * sp_connection_send_command(connection, sp_command_clear());
- * sp_connection_send_command(connection, sp_command_brightness(5));
+ * sp_udp_send_command(connection, sp_command_brightness(5));
  * ```
  */
 bool sp_udp_send_command(UdpConnection */*notnull*/ connection,
                          TypedCommand */*notnull*/ command);
 
+/**
+ * Sends a [Header] to the display using the [UdpConnection].
+ *
+ * returns: true in case of success
+ *
+ * # Examples
+ *
+ * ```C
+ * sp_udp_send_header(connection, sp_command_brightness(5));
+ * ```
+ */
+bool sp_udp_send_header(UdpConnection */*notnull*/ udp_connection,
+                        Header header);
+
 /**
  * Sends a [Packet] to the display using the [UdpConnection].
  *
diff --git a/src/brightness_grid.rs b/src/brightness_grid.rs
index 823a1fd..dcde8cf 100644
--- a/src/brightness_grid.rs
+++ b/src/brightness_grid.rs
@@ -9,7 +9,7 @@ use std::ptr::NonNull;
 ///
 /// # Examples
 /// ```C
-/// UdpConnection connection = sp_connection_open("127.0.0.1:2342");
+/// UdpConnection connection = sp_udp_open("127.0.0.1:2342");
 /// if (connection == NULL)
 ///     return 1;
 ///
@@ -18,7 +18,7 @@ use std::ptr::NonNull;
 /// sp_brightness_grid_set(grid, 1, 1, 10);
 ///
 /// TypedCommand command = sp_command_char_brightness(grid);
-/// sp_connection_free(connection);
+/// sp_udp_free(connection);
 /// ```
 #[no_mangle]
 pub unsafe extern "C" fn sp_brightness_grid_new(
@@ -167,5 +167,5 @@ pub unsafe extern "C" fn sp_brightness_grid_unsafe_data_ref(
     const _: () = assert!(size_of::<Brightness>() == 1);
 
     let data = unsafe { (*brightness_grid.as_ptr()).data_ref_mut() };
-    unsafe { ByteSlice::from_slice(transmute(data)) }
+    unsafe { ByteSlice::from_slice(transmute::<&mut [Brightness], &mut [u8]>(data)) }
 }
diff --git a/src/byte_slice.rs b/src/byte_slice.rs
index 001a616..de7d0df 100644
--- a/src/byte_slice.rs
+++ b/src/byte_slice.rs
@@ -24,7 +24,7 @@ impl ByteSlice {
         unsafe { std::slice::from_raw_parts(self.start.as_ptr(), self.length) }
     }
 
-    pub(crate) unsafe fn as_slice_mut(&self) -> &mut [u8] {
+    pub(crate) unsafe fn as_slice_mut(&mut self) -> &mut [u8] {
         unsafe {
             std::slice::from_raw_parts_mut(self.start.as_ptr(), self.length)
         }
diff --git a/src/commands/mod.rs b/src/commands/mod.rs
new file mode 100644
index 0000000..1ea03d5
--- /dev/null
+++ b/src/commands/mod.rs
@@ -0,0 +1,3 @@
+mod typed;
+
+pub use typed::*;
diff --git a/src/command.rs b/src/commands/typed.rs
similarity index 94%
rename from src/command.rs
rename to src/commands/typed.rs
index 32d5acd..9e27540 100644
--- a/src/command.rs
+++ b/src/commands/typed.rs
@@ -40,7 +40,7 @@ pub unsafe extern "C" fn sp_command_clone(
 /// # Examples
 ///
 /// ```C
-/// sp_connection_send_command(connection, sp_command_clear());
+/// sp_udp_send_command(connection, sp_command_clear());
 /// ```
 #[no_mangle]
 pub unsafe extern "C" fn sp_command_clear() -> NonNull<TypedCommand> {
@@ -115,10 +115,6 @@ pub unsafe extern "C" fn sp_command_bitvec(
     operation: BinaryOperation,
 ) -> *mut TypedCommand {
     let bit_vec = unsafe { *Box::from_raw(bit_vec.as_ptr()) };
-    let compression = match compression.try_into() {
-        Ok(compression) => compression,
-        Err(_) => return std::ptr::null_mut(),
-    };
     let command = servicepoint::BitVecCommand {
         offset,
         operation,
@@ -182,10 +178,6 @@ pub unsafe extern "C" fn sp_command_bitmap(
     compression: CompressionCode,
 ) -> *mut TypedCommand {
     let bitmap = unsafe { *Box::from_raw(bitmap.as_ptr()) };
-    let compression = match compression.try_into() {
-        Ok(compression) => compression,
-        Err(_) => return std::ptr::null_mut(),
-    };
     let command = servicepoint::BitmapCommand {
         origin: servicepoint::Origin::new(x, y),
         bitmap,
diff --git a/src/connection.rs b/src/connection.rs
index 7b29e29..eec1f43 100644
--- a/src/connection.rs
+++ b/src/connection.rs
@@ -1,5 +1,5 @@
 use crate::{heap_drop, heap_move};
-use servicepoint::{Connection, Packet, TypedCommand, UdpConnection};
+use servicepoint::{Connection, Header, Packet, TypedCommand, UdpConnection};
 use std::ffi::{c_char, CStr};
 use std::net::{Ipv4Addr, SocketAddrV4};
 use std::ptr::NonNull;
@@ -11,9 +11,9 @@ use std::ptr::NonNull;
 /// # Examples
 ///
 /// ```C
-/// UdpConnection connection = sp_connection_open("172.23.42.29:2342");
+/// UdpConnection connection = sp_udp_open("172.23.42.29:2342");
 /// if (connection != NULL)
-///     sp_connection_send_command(connection, sp_command_clear());
+///     sp_udp_send_command(connection, sp_command_clear());
 /// ```
 #[no_mangle]
 pub unsafe extern "C" fn sp_udp_open(
@@ -37,9 +37,9 @@ pub unsafe extern "C" fn sp_udp_open(
 /// # Examples
 ///
 /// ```C
-/// UdpConnection connection = sp_connection_open_ipv4(172, 23, 42, 29, 2342);
+/// UdpConnection connection = sp_udp_open_ipv4(172, 23, 42, 29, 2342);
 /// if (connection != NULL)
-///     sp_connection_send_command(connection, sp_command_clear());
+///     sp_udp_send_command(connection, sp_command_clear());
 /// ```
 #[no_mangle]
 pub unsafe extern "C" fn sp_udp_open_ipv4(
@@ -80,8 +80,7 @@ pub unsafe extern "C" fn sp_udp_send_packet(
 /// # Examples
 ///
 /// ```C
-/// sp_connection_send_command(connection, sp_command_clear());
-/// sp_connection_send_command(connection, sp_command_brightness(5));
+/// sp_udp_send_command(connection, sp_command_brightness(5));
 /// ```
 #[no_mangle]
 pub unsafe extern "C" fn sp_udp_send_command(
@@ -92,6 +91,27 @@ pub unsafe extern "C" fn sp_udp_send_command(
     unsafe { connection.as_ref().send(command) }.is_ok()
 }
 
+/// Sends a [Header] to the display using the [UdpConnection].
+///
+/// returns: true in case of success
+///
+/// # Examples
+///
+/// ```C
+/// sp_udp_send_header(connection, sp_command_brightness(5));
+/// ```
+#[no_mangle]
+pub unsafe extern "C" fn sp_udp_send_header(
+    udp_connection: NonNull<UdpConnection>,
+    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>) {
diff --git a/src/lib.rs b/src/lib.rs
index f90e93d..c434ab1 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -9,7 +9,7 @@
 //! #include "servicepoint.h"
 //!
 //! int main(void) {
-//!     UdpConnection *connection = sp_connection_open("172.23.42.29:2342");
+//!     UdpConnection *connection = sp_udp_open("172.23.42.29:2342");
 //!     if (connection == NULL)
 //!         return 1;
 //!
@@ -17,10 +17,10 @@
 //!     sp_bitmap_fill(pixels, true);
 //!
 //!     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_connection_free(connection);
+//!     sp_udp_free(connection);
 //!     return 0;
 //! }
 //! ```
@@ -30,10 +30,11 @@ pub use crate::bitvec::*;
 pub use crate::brightness_grid::*;
 pub use crate::byte_slice::*;
 pub use crate::char_grid::*;
-pub use crate::command::*;
+pub use crate::commands::*;
 pub use crate::connection::*;
 pub use crate::cp437_grid::*;
 pub use crate::packet::*;
+pub use servicepoint::CommandCode;
 use std::ptr::NonNull;
 
 mod bitmap;
@@ -41,7 +42,7 @@ mod bitvec;
 mod brightness_grid;
 mod byte_slice;
 mod char_grid;
-mod command;
+mod commands;
 mod connection;
 mod cp437_grid;
 mod packet;
diff --git a/src/packet.rs b/src/packet.rs
index a9def67..4a30b51 100644
--- a/src/packet.rs
+++ b/src/packet.rs
@@ -1,5 +1,5 @@
 use crate::{heap_drop, heap_move, heap_move_nonnull, ByteSlice};
-use servicepoint::{Header, Packet, TypedCommand};
+use servicepoint::{CommandCode, Header, Packet, TypedCommand};
 use std::ptr::NonNull;
 
 /// Turns a [TypedCommand] into a [Packet].
@@ -65,7 +65,7 @@ pub unsafe extern "C" fn sp_packet_get_header(
 pub unsafe extern "C" fn sp_packet_get_payload(
     packet: NonNull<Packet>,
 ) -> ByteSlice {
-    unsafe { ByteSlice::from_slice(&mut *(*packet.as_ptr()).payload) }
+    unsafe { ByteSlice::from_slice(&mut (*packet.as_ptr()).payload) }
 }
 
 /// Sets the payload of the provided packet to the provided data.
@@ -87,7 +87,7 @@ pub unsafe extern "C" fn sp_packet_set_payload(
 #[no_mangle]
 pub unsafe extern "C" fn sp_packet_serialize_to(
     packet: NonNull<Packet>,
-    buffer: ByteSlice,
+    mut buffer: ByteSlice,
 ) {
     unsafe {
         packet.as_ref().serialize_to(buffer.as_slice_mut());
@@ -107,3 +107,19 @@ pub unsafe extern "C" fn sp_packet_clone(
 pub unsafe extern "C" fn sp_packet_free(packet: NonNull<Packet>) {
     unsafe { heap_drop(packet) }
 }
+
+#[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,
+    }
+}