diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index e9947d5..9089694 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -13,44 +13,32 @@ env: RUSTFLAGS: "-Dwarnings" jobs: + build: - build-gnu-apt: runs-on: ubuntu-latest + steps: - uses: actions/checkout@v4 - name: Update repos run: sudo apt-get update -qq - - name: Install toolchain - run: sudo apt-get install -qy cargo rust-clippy liblzma-dev gcc make pkgconf + - name: Install rust toolchain + run: sudo apt-get install -qy cargo rust-clippy + - name: install lzma + run: sudo apt-get install -qy liblzma-dev + - name: install gcc + run: sudo apt-get install -qy gcc make - name: Run Clippy run: cargo clippy - - name: build - run: cargo build - - name: generate bindings run: ./generate-binding.sh - name: check that generated files did not change run: output=$(git status --porcelain) && [ -z "$output" ] + + - name: build + run: cargo build - - name: build example -- glibc release - run: cd example && make clean && make TARGET=aarch64-unknown-linux-gnu PROFILE=release - - name: build example -- glibc debug - run: cd example && make clean && make TARGET=aarch64-unknown-linux-gnu PROFILE=debug - - build-size-gnu-unstable: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Update repos - run: sudo apt-get update -qq - - name: Install toolchain - run: sudo apt-get install -qy liblzma-dev gcc make pkgconf rustup - - name: install rust targets - run: rustup toolchain install nightly -t aarch64-unknown-linux-gnu -c rust-src --no-self-update - - - name: build example -- glibc size-optimized - run: cd example && make clean && make TARGET=aarch64-unknown-linux-gnu PROFILE=size-optimized CARGO="rustup run nightly cargo" LTO=1 + - name: build example + run: cd example && make diff --git a/Cargo.lock b/Cargo.lock index 78e2169..9fcfa8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 4 +version = 3 [[package]] name = "adler2" @@ -49,20 +49,19 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "once_cell", "windows-sys", ] [[package]] name = "bitflags" -version = "2.9.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be" [[package]] name = "bitvec" @@ -78,28 +77,30 @@ dependencies = [ [[package]] name = "bzip2" -version = "0.5.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" +checksum = "bafdbf26611df8c14810e268ddceda071c297570a5fb360ceddf617fe417ef58" dependencies = [ "bzip2-sys", + "libc", ] [[package]] name = "bzip2-sys" -version = "0.1.13+1.0.8" +version = "0.1.11+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" dependencies = [ "cc", + "libc", "pkg-config", ] [[package]] name = "cbindgen" -version = "0.28.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadd868a2ce9ca38de7eeafdcec9c7065ef89b42b32f0839278d55f35c54d1ff" +checksum = "3fce8dd7fcfcbf3a0a87d8f515194b49d6135acab73e18bd380d1d93bb1a15eb" dependencies = [ "clap", "heck", @@ -116,9 +117,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.21" +version = "1.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0" +checksum = "c8293772165d9345bdaaa39b45b2109591e63fe5e6fbc23c6ff930a048aa310b" dependencies = [ "jobserver", "libc", @@ -133,18 +134,18 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.37" +version = "4.5.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" +checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.37" +version = "4.5.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" +checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121" dependencies = [ "anstream", "anstyle", @@ -175,15 +176,15 @@ dependencies = [ [[package]] name = "equivalent" -version = "1.0.2" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.11" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", "windows-sys", @@ -197,9 +198,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "flate2" -version = "1.1.1" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", "miniz_oxide", @@ -213,21 +214,20 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "getrandom" -version = "0.3.2" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", - "r-efi", "wasi", ] [[package]] name = "hashbrown" -version = "0.15.3" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "heck" @@ -237,9 +237,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "indexmap" -version = "2.9.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", "hashbrown", @@ -253,37 +253,36 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jobserver" -version = "0.1.33" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" dependencies = [ - "getrandom", "libc", ] [[package]] name = "libc" -version = "0.2.172" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "log" -version = "0.4.27" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" @@ -293,49 +292,43 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" dependencies = [ "adler2", ] [[package]] name = "once_cell" -version = "1.21.3" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] -[[package]] -name = "r-efi" -version = "5.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" - [[package]] name = "radium" version = "0.7.0" @@ -354,9 +347,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.7" +version = "0.38.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" dependencies = [ "bitflags", "errno", @@ -367,24 +360,24 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", @@ -393,9 +386,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" dependencies = [ "itoa", "memchr", @@ -414,9 +407,9 @@ dependencies = [ [[package]] name = "servicepoint" -version = "0.14.1" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6bd5cfa49c73aeecb344680ffbf697abf73e0563a441b93b9723ae43867500f" +checksum = "93b52049be55a15fe37c13249d7f96aa8a5de56e1a41838e74a822ee8316a0c4" dependencies = [ "bitvec", "bzip2", @@ -430,7 +423,7 @@ dependencies = [ [[package]] name = "servicepoint_binding_c" -version = "0.14.1" +version = "0.13.1" dependencies = [ "cbindgen", "servicepoint", @@ -450,9 +443,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.101" +version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", @@ -467,10 +460,11 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.19.1" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" +checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" dependencies = [ + "cfg-if", "fastrand", "getrandom", "once_cell", @@ -480,18 +474,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", @@ -500,9 +494,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.22" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", @@ -512,38 +506,31 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.9" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.26" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", - "toml_write", "winnow", ] -[[package]] -name = "toml_write" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" - [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "utf8parse" @@ -559,12 +546,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "wasi" -version = "0.14.2+wasi-0.2.4" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" -dependencies = [ - "wit-bindgen-rt", -] +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "windows-sys" @@ -641,22 +625,13 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.9" +version = "0.6.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9fb597c990f03753e08d3c29efbfcf2019a003b4bf4ba19225c158e1549f0f3" +checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" dependencies = [ "memchr", ] -[[package]] -name = "wit-bindgen-rt" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags", -] - [[package]] name = "wyz" version = "0.5.1" @@ -668,27 +643,27 @@ dependencies = [ [[package]] name = "zstd" -version = "0.13.3" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.2.4" +version = "7.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.15+zstd.1.5.7" +version = "2.0.13+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 0eba069..7750abc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "servicepoint_binding_c" -version = "0.14.1" +version = "0.13.1" publish = true edition = "2021" license = "GPL-3.0-or-later" @@ -15,31 +15,14 @@ keywords = ["cccb", "cccb-servicepoint", "cbindgen"] crate-type = ["staticlib", "cdylib", "rlib"] [build-dependencies] -cbindgen = "0.28.0" +cbindgen = "0.27.0" [dependencies.servicepoint] -package = "servicepoint" -version = "0.14.1" -default-features = false - -[features] -all_compressions = ["servicepoint/all_compressions"] -default = ["all_compressions", "servicepoint/default"] +version = "0.13.1" +features = ["all_compressions"] [lints.rust] missing-docs = "warn" -unsafe_op_in_unsafe_fn = "warn" - -[lints.clippy] -missing_safety_doc = "allow" [package.metadata.docs.rs] 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 diff --git a/README.md b/README.md index 4788c75..e085b1b 100644 --- a/README.md +++ b/README.md @@ -43,37 +43,32 @@ Please specify the full version including patch in your Cargo.toml until 1.0 is ## Installation -This manual uses gnumake, though it should work similarly for other build systems. - -Add this repo as a submodule: - -```bash -git submodule add https://git.berlin.ccc.de/servicepoint/servicepoint-binding-c.git -git commit -m "add servicepoint-binding-c submodule" -``` - -Update your Makefile: - -```Makefile -# provide header and binary to CC -CFLAGS += -I servicepoint-binding-c/include -L servicepoint-binding-c/target/release -# link statically against servicepoint, link against lzma dynamically -CFLAGS += -Wl,-Bstatic -lservicepoint_binding_c -Wl,-Bdynamic -llzma - -# add target to build rust library - cargo will check for dependency changes -servicepoint-binding-c/target/release/libservicepoint.a: - cargo build --manifest-path=servicepoint-binding-c/Cargo.toml --release - -# add static lib as a dependency for your compilation -# if you build .so files separately, this should be part of your build step -my_bin: $(src) servicepoint-binding-c/target/release/libservicepoint.a - $(CC) $(src) $(CFLAGS) -o $@ -``` +1. Add this repo as a submodule: + ```bash + git submodule add https://git.berlin.ccc.de/servicepoint/servicepoint-binding-c.git + git commit -m "add servicepoint-binding-c submodule" + ``` +2. Add a build step for the servicepoint library. If you use make, this could look something like this: + ``` + dependencies: FORCE + cargo build --manifest-path=servicepoint-binding-c/Cargo.toml --release + FORCE: ; + ``` +3. Link against the library. If you are on linux and linking statically: + ``` + ${CC} main.c \ + -I servicepoint-binding-c/include \ + -L servicepoint-binding-c/target/release \ + -Wl,-Bstatic -lservicepoint_binding_c \ + -Wl,-Bdynamic -llzma \ + -o out/example + ``` You have the choice of linking statically (recommended) or dynamically. -When linked dynamically, you have to provide the `cdylib` at runtime in the _same_ version, as there are no API/ABI + +- The C example shows how to link statically against the `staticlib` variant. +- When linked dynamically, you have to provide the `cdylib` at runtime in the _same_ version, as there are no API/ABI guarantees yet. -The Makefile in the example directory contains a bunch of additional compiler flags you might want to add depending on your use case. ## Notes on differences to rust library @@ -87,21 +82,6 @@ The Makefile in the example directory contains a bunch of additional compiler fl - documentation is included in the header and available [online](https://docs.rs/servicepoint_binding_c/latest/servicepoint_binding_c/) -## Safety - -Functions expect that C code honors NonNull annotations. - -Any created instances have to be freed in some way. -Pointers to those instances cannot be used anymore after that. - -Instances cannot be shared between threads and need to be locked in the using code. - -Enum values have to be used as-is. Do not pass values that are not part of the enum. - -UTF-8 or UTF-32 encoding has to be used properly. - -Brightness values provided as u8 parameters must be in range. - ## Everything else Look at the main project [README](https://git.berlin.ccc.de/servicepoint/servicepoint/src/branch/main/README.md) for diff --git a/build.rs b/build.rs index 83e9641..93bf703 100644 --- a/build.rs +++ b/build.rs @@ -6,30 +6,28 @@ use std::{env, fs::copy}; +use cbindgen::{generate_with_config, Config}; + fn main() { let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); println!("cargo::rerun-if-changed={crate_dir}"); let config = - cbindgen::Config::from_file(crate_dir.clone() + "/cbindgen.toml") - .unwrap(); + Config::from_file(crate_dir.clone() + "/cbindgen.toml").unwrap(); let output_dir = env::var("OUT_DIR").unwrap(); let header_file = output_dir.clone() + "/servicepoint.h"; - if let Ok(bindings) = cbindgen::generate_with_config(crate_dir, config) { - bindings.write_to_file(&header_file); + generate_with_config(crate_dir, config) + .unwrap() + .write_to_file(&header_file); + println!("cargo:include={output_dir}"); - println!("cargo:include={output_dir}"); - - println!("cargo::rerun-if-env-changed=SERVICEPOINT_HEADER_OUT"); - if let Ok(header_out) = env::var("SERVICEPOINT_HEADER_OUT") { - let header_copy = header_out + "/servicepoint.h"; - 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"); + println!("cargo::rerun-if-env-changed=SERVICEPOINT_HEADER_OUT"); + if let Ok(header_out) = env::var("SERVICEPOINT_HEADER_OUT") { + let header_copy = header_out + "/servicepoint.h"; + println!("cargo:warning=Copying header to {header_copy}"); + copy(header_file, &header_copy).unwrap(); + println!("cargo::rerun-if-changed={header_copy}"); } } diff --git a/cbindgen.toml b/cbindgen.toml index 221e915..7fc0fdf 100644 --- a/cbindgen.toml +++ b/cbindgen.toml @@ -19,27 +19,18 @@ line_endings = "LF" style = "type" 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" [parse] -parse_deps = true -include = ["servicepoint", "std"] -extra_bindings = ["servicepoint"] +parse_deps = false [parse.expand] -features = ["full"] +all_features = true [export] include = [] exclude = [] -[export.rename] -"SpBitVec" = "BitVec" -"SpByteSlice" = "ByteSlice" - [enum] rename_variants = "QualifiedScreamingSnakeCase" - -[ptr] -non_null_attribute = "/*notnull*/" diff --git a/example/Makefile b/example/Makefile index 32e6091..abdd69f 100644 --- a/example/Makefile +++ b/example/Makefile @@ -1,129 +1,30 @@ -CARGO ?= cargo -STRIP ?= strip - -FEATURES := "" +CC := gcc THIS_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST)))) -REPO_ROOT := $(realpath $(THIS_DIR)/..) -export SERVICEPOINT_HEADER_OUT := $(REPO_ROOT)/include +REPO_ROOT := $(THIS_DIR)/.. -override CFG_MUSL := $(if $(CFG_MUSL),$(CFG_MUSL),$(if $(MUSL),$(MUSL),0)) -override CFG_PROFILE := $(if $(CFG_PROFILE),$(CFG_PROFILE),$(if $(PROFILE),$(PROFILE),release)) +build: out/example -CCFLAGS += -Wall -fwhole-program -fPIE -pie - -STRIPFLAGS := -s --strip-unneeded -R .comment -R .gnu.version -R .note -R .note.gnu.build-id -R .note.ABI-tag - -ifeq ($(CFG_MUSL), 1) - TARGET ?= x86_64-unknown-linux-musl - CC ?= musl-gcc - CCFLAGS += -static -lservicepoint_binding_c - RUSTFLAGS += --crate-type=staticlib -Ctarget-feature=-crt-static -else - TARGET ?= x86_64-unknown-linux-gnu - CC ?= gcc - #CCFLAGS += -shared - CCFLAGS += -Wl,-Bstatic -lservicepoint_binding_c -Wl,-Bdynamic -endif - -#ifeq ($(CFG_PROFILE), size-optimized) -# CCFLAGS += -nodefaultlibs -lc -#endif - -RUST_TARGET_DIR := $(REPO_ROOT)/target/$(TARGET)/$(CFG_PROFILE) - -ifeq ($(CFG_PROFILE), size-optimized) - CARGO_PROFILE := size-optimized - CCFLAGS += -Oz \ - -fwrapv -fomit-frame-pointer -fno-stack-protector\ - -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 \ - -fno-exceptions - CARGOFLAGS += -Zbuild-std="core,std,alloc,proc_macro,panic_abort" \ - -Zbuild-std-features="panic_immediate_abort" - RUSTFLAGS += -Zlocation-detail=none \ - -Zfmt-debug=none \ - -C link-arg=-z,norelro \ - -C panic=abort - #-C link-arg=--hash-style=gnu -else ifeq ($(CFG_PROFILE), release) - CARGO_PROFILE := release - CCFLAGS += -O2 -else ifeq ($(CFG_PROFILE), debug) - CCFLAGS += -Og - CARGO_PROFILE := dev -else - CFG_PROFILE := $(error "PROFILE has to be set to one of: debug, release, size-optimized") -endif - -CARGOFLAGS += --manifest-path=$(REPO_ROOT)/Cargo.toml \ - --profile=$(CARGO_PROFILE) \ - --no-default-features \ - --features=$(FEATURES) \ - --target=$(TARGET) - -ifneq ($(CFG_PROFILE), debug) - CCFLAGS += -ffunction-sections -fdata-sections -Wl,--gc-sections - RUSTFLAGS += -C link-arg=-s -C link-arg=-Wl,--gc-sections -endif - -ifeq ($(LTO), 1) - CCFLAGS += -flto -endif - -_c_src := $(wildcard ./src/*.c) -_programs := $(basename $(notdir $(_c_src))) -_bins := $(addprefix out/, $(_programs)) -_unstripped_bins := $(addsuffix _unstripped, $(_bins)) -_run_programs := $(addprefix run_, $(_programs)) -_rs_src := $(wildcard ../src/**.rs) ../Cargo.lock ../Cargo.toml ../cbindgen.toml -_sp_artifacts := $(SERVICEPOINT_HEADER_OUT)/servicepoint.h $(RUST_TARGET_DIR)/libservicepoint_binding_c.a $(RUST_TARGET_DIR)/libservicepoint_binding_c.so - -all: $(_bins) - -clean: clean-c clean-rust - -clean-c: +clean: rm -r out || true - -clean-rust: - rm $(SERVICEPOINT_HEADER_OUT)/servicepoint.h || true + rm include/servicepoint.h || true cargo clean -.PHONY: all clean sizes $(_run_programs) clean-c clean-rust +run: out/example + out/example -$(_unstripped_bins): out/%_unstripped: src/%.c $(SERVICEPOINT_HEADER_OUT)/servicepoint.h $(_sp_artifacts) +PHONY: build clean dependencies run + +out/example: dependencies main.c mkdir -p out || true - $(CC) $< \ - -I $(SERVICEPOINT_HEADER_OUT) \ - -L $(RUST_TARGET_DIR) \ - $(CCFLAGS) \ - -o $@ + ${CC} main.c \ + -I $(REPO_ROOT)/include \ + -L $(REPO_ROOT)/target/release \ + -Wl,-Bstatic -lservicepoint_binding_c \ + -Wl,-Bdynamic -llzma \ + -o out/example -$(_bins): out/%: out/%_unstripped - $(STRIP) $(STRIPFLAGS) $^ -o $@ - -$(_sp_artifacts): $(_rs_src) - mkdir -p $(SERVICEPOINT_HEADER_OUT) || true - # generate servicepoint header and library to link against - $(CARGO) rustc $(CARGOFLAGS) -- $(RUSTFLAGS) - -$(_run_programs): run_%: out/% FORCE - ./$< - -sizes: $(_bins) - ls -lB out - -analyze-size: out/$(BIN)_unstripped - nm --print-size --size-sort --reverse-sort --radix=d --demangle out/$(BIN)_unstripped \ - | awk '{size=$$2+0; print size "\t" $$4}' \ - | less +dependencies: FORCE + cargo build --manifest-path=$(REPO_ROOT)/Cargo.toml --release FORCE: ; diff --git a/example/main.c b/example/main.c new file mode 100644 index 0000000..1454804 --- /dev/null +++ b/example/main.c @@ -0,0 +1,17 @@ +#include +#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; +} diff --git a/example/src/announce.c b/example/src/announce.c deleted file mode 100644 index d256cff..0000000 --- a/example/src/announce.c +++ /dev/null @@ -1,37 +0,0 @@ -#include -#include "servicepoint.h" - - -int main(void) { - printf("test\n"); - - UdpSocket *connection = sp_udp_open_ipv4(172, 23, 42, 29, 2342); - //UdpSocket *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; -} diff --git a/example/src/brightness_tester.c b/example/src/brightness_tester.c deleted file mode 100644 index c3e42ba..0000000 --- a/example/src/brightness_tester.c +++ /dev/null @@ -1,30 +0,0 @@ -#include "servicepoint.h" - -int main(void) { - UdpSocket *connection = sp_udp_open_ipv4(172, 23, 42, 29, 2342); - //UdpSocket *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; -} diff --git a/example/src/random_stuff.c b/example/src/random_stuff.c deleted file mode 100644 index 2ebd485..0000000 --- a/example/src/random_stuff.c +++ /dev/null @@ -1,26 +0,0 @@ -#include -#include "servicepoint.h" - -int main(void) { - UdpSocket *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; -} diff --git a/flake.nix b/flake.nix index 82983f7..9318237 100644 --- a/flake.nix +++ b/flake.nix @@ -33,15 +33,7 @@ { pkgs, system }: { default = pkgs.mkShell rec { - buildInputs = with pkgs; [ - xe - xz - libgcc - libunwind - pkgsStatic.musl - ]; - - nativeBuildInputs = with pkgs; [ + packages = with pkgs; [ (pkgs.symlinkJoin { name = "rust-toolchain"; paths = with pkgs; [ @@ -55,12 +47,11 @@ ]; }) gcc - gdb - pkgsStatic.gcc gnumake + xe + xz pkg-config ]; - RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}"; }; } diff --git a/include/servicepoint.h b/include/servicepoint.h index 5863ef5..d9cbe57 100644 --- a/include/servicepoint.h +++ b/include/servicepoint.h @@ -1,4 +1,4 @@ -/* Generated with cbindgen:0.28.0 */ +/* Generated with cbindgen:0.27.0 */ /* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */ @@ -8,148 +8,55 @@ #include #include +/** + * Count of possible brightness values + */ +#define SP_BRIGHTNESS_LEVELS 12 + +/** + * see [servicepoint::Brightness::MAX] + */ +#define SP_BRIGHTNESS_MAX 11 + +/** + * see [servicepoint::Brightness::MIN] + */ +#define SP_BRIGHTNESS_MIN 0 + /** * pixel count on whole screen */ -#define PIXEL_COUNT (PIXEL_WIDTH * PIXEL_HEIGHT) +#define SP_PIXEL_COUNT (SP_PIXEL_WIDTH * SP_PIXEL_HEIGHT) /** * Display height in pixels - * - * # Examples - * - * ```rust - * # use servicepoint::{PIXEL_HEIGHT, PIXEL_WIDTH, Bitmap}; - * let grid = Bitmap::new(PIXEL_WIDTH, PIXEL_HEIGHT); - * ``` */ -#define PIXEL_HEIGHT (TILE_HEIGHT * TILE_SIZE) +#define SP_PIXEL_HEIGHT (SP_TILE_HEIGHT * SP_TILE_SIZE) /** * Display width in pixels - * - * # Examples - * - * ```rust - * # use servicepoint::{PIXEL_HEIGHT, PIXEL_WIDTH, Bitmap}; - * let grid = Bitmap::new(PIXEL_WIDTH, PIXEL_HEIGHT); - * ``` */ -#define PIXEL_WIDTH (TILE_WIDTH * TILE_SIZE) +#define SP_PIXEL_WIDTH (SP_TILE_WIDTH * SP_TILE_SIZE) /** * Display tile count in the y-direction - * - * # Examples - * - * ```rust - * # use servicepoint::{Cp437Grid, TILE_HEIGHT, TILE_WIDTH}; - * let grid = Cp437Grid::new(TILE_WIDTH, TILE_HEIGHT); - * ``` */ -#define TILE_HEIGHT 20 +#define SP_TILE_HEIGHT 20 /** * size of a single tile in one dimension */ -#define TILE_SIZE 8 +#define SP_TILE_SIZE 8 /** * Display tile count in the x-direction - * - * # Examples - * - * ```rust - * # use servicepoint::{Cp437Grid, TILE_HEIGHT, TILE_WIDTH}; - * let grid = Cp437Grid::new(TILE_WIDTH, TILE_HEIGHT); - * ``` */ -#define TILE_WIDTH 56 +#define SP_TILE_WIDTH 56 /** - * Binary operations for use with the [`BitVecCommand`] command. + * Specifies the kind of compression to use. */ -enum BinaryOperation -#ifdef __cplusplus - : uint8_t -#endif // __cplusplus - { - /** - * r := a - */ - BINARY_OPERATION_OVERWRITE, - /** - * r := a && b - */ - BINARY_OPERATION_AND, - /** - * r := a || b - */ - BINARY_OPERATION_OR, - /** - * r := (a || b) && (a != b) - */ - BINARY_OPERATION_XOR, -}; -#ifndef __cplusplus -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. - * - * # Examples - * - * ```rust - * # use servicepoint::*; - * // create command without payload compression - * # let pixels = Bitmap::max_sized(); - * _ = BitmapCommand { - * origin: Origin::ZERO, - * bitmap: pixels, - * compression: CompressionCode::Uncompressed - * }; - * - * // create command with payload compressed with lzma and appropriate header flags - * # let pixels = Bitmap::max_sized(); - * _ = BitmapCommand { - * origin: Origin::ZERO, - * bitmap: pixels, - * compression: CompressionCode::Lzma - * }; - * ``` - */ -enum CompressionCode +enum SPCompressionCode #ifdef __cplusplus : uint16_t #endif // __cplusplus @@ -157,54 +64,28 @@ enum CompressionCode /** * no compression */ - COMPRESSION_CODE_UNCOMPRESSED = 0, + SP_COMPRESSION_CODE_UNCOMPRESSED = 0, /** * compress using flate2 with zlib header */ - COMPRESSION_CODE_ZLIB = 26490, + SP_COMPRESSION_CODE_ZLIB = 26490, /** * compress using bzip2 */ - COMPRESSION_CODE_BZIP2 = 25210, + SP_COMPRESSION_CODE_BZIP2 = 25210, /** * compress using lzma */ - COMPRESSION_CODE_LZMA = 27770, + SP_COMPRESSION_CODE_LZMA = 27770, /** * compress using Zstandard */ - COMPRESSION_CODE_ZSTD = 31347, + SP_COMPRESSION_CODE_ZSTD = 31347, }; #ifndef __cplusplus -typedef uint16_t CompressionCode; +typedef uint16_t SPCompressionCode; #endif // __cplusplus -/** - * A fixed-size 2D grid of booleans. - * - * The values are stored in packed bytes (8 values per byte) in the same order as used by the display for storing pixels. - * This means that no conversion is necessary for sending the data to the display. - * The downside is that the width has to be a multiple of 8. - * - * # Examples - * - * ```rust - * use servicepoint::Bitmap; - * let mut bitmap = Bitmap::new(8, 2); - * - * ``` - */ -typedef struct Bitmap Bitmap; - -/** - * The raw packet. - * - * Contents should probably only be used directly to use features not exposed by the library. - * - * You may want to use [`crate::Command`] or [`crate::TypedCommand`] instead. - */ -typedef struct Packet Packet; - /** * A vector of bits * @@ -218,280 +99,7 @@ typedef struct Packet Packet; typedef struct SPBitVec SPBitVec; /** - * This enum contains all commands provided by the library. - * This is useful in case you want one data type for all kinds of commands without using `dyn`. - * - * Please look at the contained structs for documentation per command. - */ -typedef struct TypedCommand TypedCommand; - -/** - * This is a type only used by cbindgen to have a type for pointers. - */ -typedef struct UdpSocket UdpSocket; - -/** - * A 2D grid of values. - * - * The memory layout is the one the display expects in [`crate::Command`]s. - * - * This structure can be used with any type that implements the [Value] trait. - * You can also use the concrete type aliases provided in this crate, e.g. [`crate::CharGrid`] and [`crate::ByteGrid`]. - */ -typedef struct ValueGrid_Brightness ValueGrid_Brightness; - -/** - * A 2D grid of values. - * - * The memory layout is the one the display expects in [`crate::Command`]s. - * - * This structure can be used with any type that implements the [Value] trait. - * You can also use the concrete type aliases provided in this crate, e.g. [`crate::CharGrid`] and [`crate::ByteGrid`]. - */ -typedef struct ValueGrid_char ValueGrid_char; - -/** - * A 2D grid of values. - * - * The memory layout is the one the display expects in [`crate::Command`]s. - * - * This structure can be used with any type that implements the [Value] trait. - * You can also use the concrete type aliases provided in this crate, e.g. [`crate::CharGrid`] and [`crate::ByteGrid`]. - */ -typedef struct ValueGrid_u8 ValueGrid_u8; - -/** - * Represents a span of memory (`&mut [u8]` ) as a struct. - * - * # Safety - * - * The caller has to make sure that: - * - * - 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 function returning this type. - */ -typedef struct { - /** - * The start address of the memory. - */ - uint8_t */*notnull*/ start; - /** - * The amount of memory in bytes. - */ - size_t length; -} ByteSlice; - -/** - * A grid containing brightness values. - * - * # Examples - * - * ```rust - * # use servicepoint::*; - * let mut grid = BrightnessGrid::new(2,2); - * grid.set(0, 0, Brightness::MIN); - * grid.set(1, 1, Brightness::MIN); - * - * # let connection = FakeConnection; - * connection.send_command(BrightnessGridCommand { - * origin: Origin::new(3, 7), - * grid - * }).unwrap() - * ``` - */ -typedef ValueGrid_Brightness BrightnessGrid; - -/** - * A display brightness value, checked for correct value range - * - * # Examples - * - * ``` - * # use servicepoint::*; - * let b = Brightness::MAX; - * let val: u8 = b.into(); - * - * let b = Brightness::try_from(7).unwrap(); - * # let connection = FakeConnection; - * let result = connection.send_command(GlobalBrightnessCommand::from(b)); - * ``` - */ -typedef uint8_t Brightness; -/** - * highest possible brightness value, 11 - */ -#define Brightness_MAX 11 -/** - * lowest possible brightness value, 0 - */ -#define Brightness_MIN 0 - -/** - * A grid containing UTF-8 characters. - * - * To send a `CharGrid` to the display, use a [`crate::CharGridCommand`]. - * - * Also see [`ValueGrid`] for the non-specialized operations and examples. - * - * # Examples - * - * ```rust - * # use servicepoint::*; - * let grid = CharGrid::from("You can\nload multiline\nstrings directly"); - * assert_eq!(grid.get_row_str(1), Some("load multiline\0\0".to_string())); - * - * # let connection = FakeConnection; - * let command = CharGridCommand { origin: Origin::ZERO, grid }; - * connection.send_command(command).unwrap() - * ``` - */ -typedef ValueGrid_char CharGrid; - -/** - * A grid containing codepage 437 characters. - * - * The encoding is currently not enforced. - */ -typedef ValueGrid_u8 Cp437Grid; - -/** - * A raw header. - * - * The header specifies the kind of command, the size of the payload and where to display the - * payload, where applicable. - * - * Because the meaning of most fields depend on the command, there are no speaking names for them. - * - * The contained values are in platform endian-ness and may need to be converted before sending. - */ -typedef struct { - /** - * The first two bytes specify which command this packet represents. - */ - uint16_t command_code; - /** - * First command-specific value - */ - uint16_t a; - /** - * Second command-specific value - */ - uint16_t b; - /** - * Third command-specific value - */ - uint16_t c; - /** - * Fourth command-specific value - */ - uint16_t d; -} Header; - - - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -/** - * Clones a [Bitmap]. - */ -Bitmap */*notnull*/ sp_bitmap_clone(Bitmap */*notnull*/ bitmap); - -/** - * Sets the state of all pixels in the [Bitmap]. - * - * # Arguments - * - * - `bitmap`: instance to write to - * - `value`: the value to set all pixels to - */ -void sp_bitmap_fill(Bitmap */*notnull*/ bitmap, bool value); - -/** - * Deallocates a [Bitmap]. - */ -void sp_bitmap_free(Bitmap */*notnull*/ bitmap); - -/** - * Tries to convert the BitVec to a Bitmap. - * - * The provided BitVec gets consumed. - * - * Returns NULL in case of error. - */ -Bitmap *sp_bitmap_from_bitvec(size_t width, SPBitVec */*notnull*/ bitvec); - -/** - * Gets the current value at the specified position in the [Bitmap]. - * - * # Arguments - * - * - `bitmap`: instance to read from - * - `x` and `y`: position of the cell to read - * - * # Panics - * - * - when accessing `x` or `y` out of bounds - */ -bool sp_bitmap_get(Bitmap */*notnull*/ bitmap, size_t x, size_t y); - -/** - * Gets the height in pixels of the [Bitmap] instance. - * - * # Arguments - * - * - `bitmap`: instance to read from - */ -size_t sp_bitmap_height(Bitmap */*notnull*/ bitmap); - -/** - * Consumes the Bitmap and returns the contained BitVec - */ -SPBitVec */*notnull*/ sp_bitmap_into_bitvec(Bitmap */*notnull*/ bitmap); - -/** - * Creates a [BitmapCommand] and immediately turns that into a [Packet]. - * - * The provided [Bitmap] gets consumed. - * - * Returns NULL in case of an error. - */ -Packet *sp_bitmap_into_packet(Bitmap */*notnull*/ bitmap, - size_t x, - size_t y, - CompressionCode compression); - -/** - * Loads a [Bitmap] with the specified dimensions from the provided data. - * - * # Arguments - * - * - `width`: size in pixels in x-direction - * - `height`: size in pixels in y-direction - * - * returns: [Bitmap] that contains a copy of the provided data, or NULL in case of an error. - */ -Bitmap *sp_bitmap_load(size_t width, - size_t height, - ByteSlice data); - -/** - * Creates a new [Bitmap] with the specified dimensions. - * - * # Arguments - * - * - `width`: size in pixels in x-direction - * - `height`: size in pixels in y-direction - * - * returns: [Bitmap] initialized to all pixels off, or NULL in case of an error. - * - * # Errors - * - * In the following cases, this function will return NULL: - * - * - when the width is not dividable by 8 + * A grid of pixels. * * # Examples * @@ -502,39 +110,212 @@ Bitmap *sp_bitmap_load(size_t width, * sp_bitmap_free(grid); * ``` */ -Bitmap *sp_bitmap_new(size_t width, size_t height); +typedef struct SPBitmap SPBitmap; /** - * Creates a new [Bitmap] with a size matching the screen. + * A grid containing brightness values. * - * returns: [Bitmap] initialized to all pixels off. + * # Examples + * ```C + * SPConnection connection = sp_connection_open("127.0.0.1:2342"); + * if (connection == NULL) + * return 1; + * + * SPBrightnessGrid grid = sp_brightness_grid_new(2, 2); + * sp_brightness_grid_set(grid, 0, 0, 0); + * sp_brightness_grid_set(grid, 1, 1, 10); + * + * SPCommand command = sp_command_char_brightness(grid); + * sp_connection_free(connection); + * ``` */ -Bitmap */*notnull*/ sp_bitmap_new_max_sized(void); +typedef struct SPBrightnessGrid SPBrightnessGrid; /** - * Sets the value of the specified position in the [Bitmap]. + * A C-wrapper for grid containing UTF-8 characters. + * + * As the rust [char] type is not FFI-safe, characters are passed in their UTF-32 form as 32bit unsigned integers. + * + * The encoding is enforced in most cases by the rust standard library + * and will panic when provided with illegal characters. + * + * # Examples + * + * ```C + * CharGrid grid = sp_char_grid_new(4, 3); + * sp_char_grid_fill(grid, '?'); + * sp_char_grid_set(grid, 0, 0, '!'); + * sp_char_grid_free(grid); + * ``` + */ +typedef struct SPCharGrid SPCharGrid; + +/** + * 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] + */ +typedef struct SPCommand SPCommand; + +/** + * A connection to the display. + * + * # Examples + * + * ```C + * CConnection connection = sp_connection_open("172.23.42.29:2342"); + * if (connection != NULL) + * sp_connection_send_command(connection, sp_command_clear()); + * ``` + */ +typedef struct SPConnection SPConnection; + +/** + * A C-wrapper for grid containing codepage 437 characters. + * + * The encoding is currently not enforced. + * + * # Examples + * + * ```C + * Cp437Grid grid = sp_cp437_grid_new(4, 3); + * sp_cp437_grid_fill(grid, '?'); + * sp_cp437_grid_set(grid, 0, 0, '!'); + * sp_cp437_grid_free(grid); + * ``` + */ +typedef struct SPCp437Grid SPCp437Grid; + +/** + * The raw packet + */ +typedef struct SPPacket SPPacket; + +/** + * 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 + * + * The caller has to make sure that: + * + * - 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 function returning this type. + * - an instance of this created from C is never passed to a consuming function, as the rust code + * will try to free the memory of a potentially separate allocator. + */ +typedef struct { + /** + * The start address of the memory + */ + uint8_t *start; + /** + * The amount of memory in bytes + */ + size_t length; +} SPByteSlice; + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * Clones a [SPBitmap]. + * + * Will never return NULL. + * + * # Panics + * + * - when `bitmap` is NULL + * + * # Safety + * + * The caller has to make sure that: + * + * - `bitmap` points to a valid [SPBitmap] + * - `bitmap` is not written to concurrently + * - the returned instance is freed in some way, either by using a consuming function or + * by explicitly calling `sp_bitmap_free`. + */ +SPBitmap *sp_bitmap_clone(const SPBitmap *bitmap); + +/** + * Sets the state of all pixels in the [SPBitmap]. * * # Arguments * * - `bitmap`: instance to write to - * - `x` and `y`: position of the cell - * - `value`: the value to write to the cell + * - `value`: the value to set all pixels to * * # Panics * - * - when accessing `x` or `y` out of bounds - */ -void sp_bitmap_set(Bitmap */*notnull*/ bitmap, size_t x, size_t y, bool value); - -/** - * Gets an unsafe reference to the data of the [Bitmap] instance. + * - when `bitmap` is NULL * - * The returned memory is valid for the lifetime of the bitmap. + * # Safety + * + * The caller has to make sure that: + * + * - `bitmap` points to a valid [SPBitmap] + * - `bitmap` is not written to or read from concurrently */ -ByteSlice sp_bitmap_unsafe_data_ref(Bitmap */*notnull*/ bitmap); +void sp_bitmap_fill(SPBitmap *bitmap, bool value); /** - * Gets the width in pixels of the [Bitmap] instance. + * Deallocates a [SPBitmap]. + * + * # 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] + */ +void sp_bitmap_free(SPBitmap *bitmap); + +/** + * Gets the current value at the specified position in the [SPBitmap]. + * + * # Arguments + * + * - `bitmap`: instance to read from + * - `x` and `y`: position of the cell to read + * + * # Panics + * + * - when `bitmap` is NULL + * - 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 + */ +bool sp_bitmap_get(const SPBitmap *bitmap, size_t x, size_t y); + +/** + * Gets the height in pixels of the [SPBitmap] instance. * * # Arguments * @@ -548,14 +329,157 @@ ByteSlice sp_bitmap_unsafe_data_ref(Bitmap */*notnull*/ bitmap); * * The caller has to make sure that: * - * - `bitmap` points to a valid [Bitmap] + * - `bitmap` points to a valid [SPBitmap] */ -size_t sp_bitmap_width(Bitmap */*notnull*/ bitmap); +size_t sp_bitmap_height(const SPBitmap *bitmap); + +/** + * Loads a [SPBitmap] with the specified dimensions from the provided data. + * + * # Arguments + * + * - `width`: size in pixels in x-direction + * - `height`: size in pixels in y-direction + * + * returns: [SPBitmap] that contains a copy of the provided data. Will never return NULL. + * + * # 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`. + */ +SPBitmap *sp_bitmap_load(size_t width, + size_t height, + const uint8_t *data, + size_t data_length); + +/** + * 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`. + */ +SPBitmap *sp_bitmap_new(size_t width, + size_t height); + +/** + * Creates a new [SPBitmap] with a size matching the screen. + * + * returns: [SPBitmap] initialized to all pixels off. Will never return NULL. + * + * # Safety + * + * The caller has to make sure that: + * + * - the returned instance is freed in some way, either by using a consuming function or + * by explicitly calling [sp_bitmap_free]. + */ +SPBitmap *sp_bitmap_new_screen_sized(void); + +/** + * Sets the value of the specified position in the [SPBitmap]. + * + * # Arguments + * + * - `bitmap`: instance to write to + * - `x` and `y`: position of the cell + * - `value`: the value to write to the cell + * + * returns: old value of the cell + * + * # Panics + * + * - when `bitmap` is NULL + * - 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 + */ +void sp_bitmap_set(SPBitmap *bitmap, size_t x, size_t y, bool value); + +/** + * Gets an unsafe reference to the data of the [SPBitmap] instance. + * + * # Panics + * + * - when `bitmap` is NULL + * + * # Safety + * + * The caller has to make sure that: + * + * - `bitmap` points to a valid [SPBitmap] + * - the returned memory range is never accessed after the passed [SPBitmap] has been freed + * - the returned memory range is never accessed concurrently, either via the [SPBitmap] or directly + */ +SPByteSlice sp_bitmap_unsafe_data_ref(SPBitmap *bitmap); + +/** + * Gets the width in pixels of the [SPBitmap] instance. + * + * # Arguments + * + * - `bitmap`: instance to read from + * + * # Panics + * + * - when `bitmap` is NULL + * + * # Safety + * + * The caller has to make sure that: + * + * - `bitmap` points to a valid [SPBitmap] + */ +size_t sp_bitmap_width(const SPBitmap *bitmap); /** * 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`. */ -SPBitVec */*notnull*/ sp_bitvec_clone(SPBitVec */*notnull*/ bit_vec); +SPBitVec *sp_bitvec_clone(const SPBitVec *bit_vec); /** * Sets the value of all bits in the [SPBitVec]. @@ -564,13 +488,38 @@ SPBitVec */*notnull*/ sp_bitvec_clone(SPBitVec */*notnull*/ bit_vec); * * - `bit_vec`: instance to write 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 */ -void sp_bitvec_fill(SPBitVec */*notnull*/ bit_vec, bool value); +void sp_bitvec_fill(SPBitVec *bit_vec, bool value); /** * 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] */ -void sp_bitvec_free(SPBitVec */*notnull*/ bit_vec); +void sp_bitvec_free(SPBitVec *bit_vec); /** * Gets the value of a bit from the [SPBitVec]. @@ -584,21 +533,17 @@ void sp_bitvec_free(SPBitVec */*notnull*/ bit_vec); * * # Panics * + * - when `bit_vec` is NULL * - when accessing `index` out of bounds - */ -bool sp_bitvec_get(SPBitVec */*notnull*/ bit_vec, size_t index); - -/** - * Creates a [BitVecCommand] and immediately turns that into a [Packet]. * - * The provided [SPBitVec] gets consumed. + * # Safety * - * Returns NULL in case of an error. + * The caller has to make sure that: + * + * - `bit_vec` points to a valid [SPBitVec] + * - `bit_vec` is not written to concurrently */ -Packet *sp_bitvec_into_packet(SPBitVec */*notnull*/ bitvec, - size_t offset, - BinaryOperation operation, - CompressionCode compression); +bool sp_bitvec_get(const SPBitVec *bit_vec, size_t index); /** * Returns true if length is 0. @@ -606,8 +551,18 @@ Packet *sp_bitvec_into_packet(SPBitVec */*notnull*/ bitvec, * # Arguments * * - `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] */ -bool sp_bitvec_is_empty(SPBitVec */*notnull*/ bit_vec); +bool sp_bitvec_is_empty(const SPBitVec *bit_vec); /** * Gets the length of the [SPBitVec] in bits. @@ -615,15 +570,39 @@ bool sp_bitvec_is_empty(SPBitVec */*notnull*/ bit_vec); * # Arguments * * - `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] */ -size_t sp_bitvec_len(SPBitVec */*notnull*/ bit_vec); +size_t sp_bitvec_len(const SPBitVec *bit_vec); /** * Interpret the data as a series of bits and load then into a new [SPBitVec] instance. * - * returns: [SPBitVec] instance containing data. + * returns: [SPBitVec] instance containing data. Will never return NULL. + * + * # Panics + * + * - when `data` is NULL + * + * # Safety + * + * The caller has to make sure that: + * + * - `data` points to a valid memory location of at least `data_length` + * bytes in size. + * - the returned instance is freed in some way, either by using a consuming function or + * by explicitly calling `sp_bitvec_free`. */ -SPBitVec */*notnull*/ sp_bitvec_load(ByteSlice data); +SPBitVec *sp_bitvec_load(const uint8_t *data, + size_t data_length); /** * Creates a new [SPBitVec] instance. @@ -632,13 +611,20 @@ SPBitVec */*notnull*/ sp_bitvec_load(ByteSlice data); * * - `size`: size in bits. * - * returns: [SPBitVec] with all bits set to false. + * returns: [SPBitVec] with all bits set to false. Will never return NULL. * * # Panics * * - 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`. */ -SPBitVec */*notnull*/ sp_bitvec_new(size_t size); +SPBitVec *sp_bitvec_new(size_t size); /** * Sets the value of a bit in the [SPBitVec]. @@ -651,41 +637,107 @@ SPBitVec */*notnull*/ sp_bitvec_new(size_t size); * * # Panics * + * - when `bit_vec` is NULL * - 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 */ -void sp_bitvec_set(SPBitVec */*notnull*/ bit_vec, size_t index, bool value); +void sp_bitvec_set(SPBitVec *bit_vec, size_t index, bool value); /** * Gets an unsafe reference to the data of the [SPBitVec] instance. * - * The returned memory is valid for the lifetime of the bitvec. - * * # Arguments * * - `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 */ -ByteSlice sp_bitvec_unsafe_data_ref(SPBitVec */*notnull*/ bit_vec); +SPByteSlice sp_bitvec_unsafe_data_ref(SPBitVec *bit_vec); /** - * Clones a [BrightnessGrid]. + * Clones a [SPBrightnessGrid]. + * + * # Arguments + * + * - `brightness_grid`: instance to read from + * + * returns: new [SPBrightnessGrid] instance. Will never return NULL. + * + * # Panics + * + * - when `brightness_grid` is NULL + * + * # Safety + * + * The caller has to make sure that: + * + * - `brightness_grid` points to a valid [SPBrightnessGrid] + * - `brightness_grid` is not written to concurrently + * - the returned instance is freed in some way, either by using a consuming function or + * by explicitly calling `sp_brightness_grid_free`. */ -BrightnessGrid */*notnull*/ sp_brightness_grid_clone(BrightnessGrid */*notnull*/ brightness_grid); +SPBrightnessGrid *sp_brightness_grid_clone(const SPBrightnessGrid *brightness_grid); /** - * Sets the value of all cells in the [BrightnessGrid]. + * Sets the value of all cells in the [SPBrightnessGrid]. * * # Arguments * * - `brightness_grid`: instance to write 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 */ -void sp_brightness_grid_fill(BrightnessGrid */*notnull*/ brightness_grid, - Brightness value); +void sp_brightness_grid_fill(SPBrightnessGrid *brightness_grid, uint8_t value); /** - * Deallocates a [BrightnessGrid]. + * Deallocates a [SPBrightnessGrid]. + * + * # Arguments + * + * - `brightness_grid`: instance to read from + * + * # Panics + * + * - when `brightness_grid` is NULL + * + * # Safety + * + * The caller has to make sure that: + * + * - `brightness_grid` points to a valid [SPBrightnessGrid] + * - `brightness_grid` is not used concurrently or after this call + * - `brightness_grid` was not passed to another consuming function, e.g. to create a [SPCommand] + * + * [SPCommand]: [crate::SPCommand] */ -void sp_brightness_grid_free(BrightnessGrid */*notnull*/ brightness_grid); +void sp_brightness_grid_free(SPBrightnessGrid *brightness_grid); /** * Gets the current value at the specified position. @@ -698,68 +750,83 @@ void sp_brightness_grid_free(BrightnessGrid */*notnull*/ brightness_grid); * returns: value at position * * # Panics + * + * - when `brightness_grid` is NULL * - 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 */ -Brightness sp_brightness_grid_get(BrightnessGrid */*notnull*/ brightness_grid, - size_t x, - size_t y); +uint8_t sp_brightness_grid_get(const SPBrightnessGrid *brightness_grid, + size_t x, + size_t y); /** - * Gets the height of the [BrightnessGrid] instance. + * Gets the height of the [SPBrightnessGrid] instance. * * # Arguments * * - `brightness_grid`: instance to read from * * returns: height + * + * # Panics + * + * - when `brightness_grid` is NULL + * + * # Safety + * + * The caller has to make sure that: + * + * - `brightness_grid` points to a valid [SPBrightnessGrid] */ -size_t sp_brightness_grid_height(BrightnessGrid */*notnull*/ brightness_grid); +size_t sp_brightness_grid_height(const SPBrightnessGrid *brightness_grid); /** - * Creates a [BrightnessGridCommand] and immediately turns that into a [Packet]. + * Loads a [SPBrightnessGrid] with the specified dimensions from the provided data. * - * The provided [BrightnessGrid] gets consumed. + * returns: new [SPBrightnessGrid] instance. Will never return NULL. * - * Returns NULL in case of an error. + * # Panics + * + * - when `data` is NULL + * - when the provided `data_length` does not match `height` and `width` + * + * # Safety + * + * The caller has to make sure that: + * + * - `data` points to a valid memory location of at least `data_length` + * bytes in size. + * - the returned instance is freed in some way, either by using a consuming function or + * by explicitly calling `sp_brightness_grid_free`. */ -Packet *sp_brightness_grid_into_packet(BrightnessGrid */*notnull*/ grid, - size_t x, - size_t y); +SPBrightnessGrid *sp_brightness_grid_load(size_t width, + size_t height, + const uint8_t *data, + size_t data_length); /** - * Loads a [BrightnessGrid] with the specified dimensions from the provided data. + * Creates a new [SPBrightnessGrid] with the specified dimensions. * - * Any out of range values will be set to [Brightness::MAX] or [Brightness::MIN]. + * returns: [SPBrightnessGrid] initialized to 0. Will never return NULL. * - * returns: new [BrightnessGrid] instance, or NULL in case of an error. + * # 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`. */ -BrightnessGrid *sp_brightness_grid_load(size_t width, - size_t height, - ByteSlice data); +SPBrightnessGrid *sp_brightness_grid_new(size_t width, + size_t height); /** - * Creates a new [BrightnessGrid] with the specified dimensions. - * - * returns: [BrightnessGrid] initialized to 0. - * - * # Examples - * ```C - * UdpConnection connection = sp_udp_open("127.0.0.1:2342"); - * if (connection == NULL) - * return 1; - * - * BrightnessGrid grid = sp_brightness_grid_new(2, 2); - * sp_brightness_grid_set(grid, 0, 0, 0); - * sp_brightness_grid_set(grid, 1, 1, 10); - * - * TypedCommand command = sp_command_char_brightness(grid); - * sp_udp_free(connection); - * ``` - */ -BrightnessGrid */*notnull*/ sp_brightness_grid_new(size_t width, size_t height); - -/** - * Sets the value of the specified position in the [BrightnessGrid]. + * Sets the value of the specified position in the [SPBrightnessGrid]. * * # Arguments * @@ -771,59 +838,128 @@ BrightnessGrid */*notnull*/ sp_brightness_grid_new(size_t width, size_t height); * * # Panics * + * - when `brightness_grid` is NULL * - 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 */ -void sp_brightness_grid_set(BrightnessGrid */*notnull*/ brightness_grid, +void sp_brightness_grid_set(SPBrightnessGrid *brightness_grid, size_t x, size_t y, - Brightness value); + uint8_t value); /** - * Gets an unsafe reference to the data of the [BrightnessGrid] instance. - * - * The returned memory is valid for the lifetime of the brightness grid. + * Gets an unsafe reference to the data of the [SPBrightnessGrid] instance. * * # Arguments * * - `brightness_grid`: instance to read from * * 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 */ -ByteSlice sp_brightness_grid_unsafe_data_ref(BrightnessGrid */*notnull*/ brightness_grid); +SPByteSlice sp_brightness_grid_unsafe_data_ref(SPBrightnessGrid *brightness_grid); /** - * Gets the width of the [BrightnessGrid] instance. + * Gets the width of the [SPBrightnessGrid] instance. * * # Arguments * * - `brightness_grid`: instance to read from * * returns: width + * + * # Panics + * + * - when `brightness_grid` is NULL + * + * # Safety + * + * The caller has to make sure that: + * + * - `brightness_grid` points to a valid [SPBrightnessGrid] */ -size_t sp_brightness_grid_width(BrightnessGrid */*notnull*/ brightness_grid); +size_t sp_brightness_grid_width(const SPBrightnessGrid *brightness_grid); /** - * Clones a [CharGrid]. + * Clones a [SPCharGrid]. + * + * Will never return NULL. + * + * # Panics + * + * - when `char_grid` is NULL + * + * # Safety + * + * The caller has to make sure that: + * + * - `char_grid` points to a valid [SPCharGrid] + * - `char_grid` is not written to concurrently + * - the returned instance is freed in some way, either by using a consuming function or + * by explicitly calling `sp_char_grid_free`. */ -CharGrid */*notnull*/ sp_char_grid_clone(CharGrid */*notnull*/ char_grid); +SPCharGrid *sp_char_grid_clone(const SPCharGrid *char_grid); /** - * Sets the value of all cells in the [CharGrid]. + * Sets the value of all cells in the [SPCharGrid]. * * # Arguments * * - `char_grid`: instance to write 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 */ -void sp_char_grid_fill(CharGrid */*notnull*/ char_grid, uint32_t value); +void sp_char_grid_fill(SPCharGrid *char_grid, uint32_t value); /** - * Deallocates a [CharGrid]. + * Deallocates a [SPCharGrid]. + * + * # Panics + * + * - when `char_grid` is NULL + * + * # Safety + * + * The caller has to make sure that: + * + * - `char_grid` points to a valid [SPCharGrid] + * - `char_grid` is not used concurrently or after char_grid call + * - `char_grid` was not passed to another consuming function, e.g. to create a [SPCommand] + * + * [SPCommand]: [crate::SPCommand] */ -void sp_char_grid_free(CharGrid */*notnull*/ char_grid); +void sp_char_grid_free(SPCharGrid *char_grid); /** - * Returns the current value at the specified position. + * Gets the current value at the specified position. * * # Arguments * @@ -832,55 +968,79 @@ void sp_char_grid_free(CharGrid */*notnull*/ char_grid); * * # Panics * + * - when `char_grid` is NULL * - 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 */ -uint32_t sp_char_grid_get(CharGrid */*notnull*/ char_grid, size_t x, size_t y); +uint32_t sp_char_grid_get(const SPCharGrid *char_grid, size_t x, size_t y); /** - * Gets the height of the [CharGrid] instance. + * Gets the height of the [SPCharGrid] instance. * * # Arguments * * - `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] */ -size_t sp_char_grid_height(CharGrid */*notnull*/ char_grid); +size_t sp_char_grid_height(const SPCharGrid *char_grid); /** - * Creates a [CharGridCommand] and immediately turns that into a [Packet]. + * Loads a [SPCharGrid] with the specified dimensions from the provided data. * - * The provided [CharGrid] gets consumed. + * Will never return NULL. * - * Returns 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`. */ -Packet *sp_char_grid_into_packet(CharGrid */*notnull*/ grid, - size_t x, - size_t y); +SPCharGrid *sp_char_grid_load(size_t width, + size_t height, + const uint8_t *data, + size_t data_length); /** - * Loads a [CharGrid] with the specified dimensions from the provided data. + * Creates a new [SPCharGrid] with the specified dimensions. * - * returns: new CharGrid or NULL in case of an error + * 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`. */ -CharGrid *sp_char_grid_load(size_t width, size_t height, ByteSlice data); +SPCharGrid *sp_char_grid_new(size_t width, + size_t height); /** - * Creates a new [CharGrid] with the specified dimensions. - * - * returns: [CharGrid] initialized to 0. - * - * # Examples - * - * ```C - * CharGrid grid = sp_char_grid_new(4, 3); - * sp_char_grid_fill(grid, '?'); - * sp_char_grid_set(grid, 0, 0, '!'); - * sp_char_grid_free(grid); - * ``` - */ -CharGrid */*notnull*/ sp_char_grid_new(size_t width, size_t height); - -/** - * Sets the value of the specified position in the [CharGrid]. + * Sets the value of the specified position in the [SPCharGrid]. * * # Arguments * @@ -892,33 +1052,41 @@ CharGrid */*notnull*/ sp_char_grid_new(size_t width, size_t height); * * # Panics * + * - when `char_grid` is NULL * - 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] */ -void sp_char_grid_set(CharGrid */*notnull*/ char_grid, +void sp_char_grid_set(SPCharGrid *char_grid, size_t x, size_t y, uint32_t value); /** - * Gets the width of the [CharGrid] instance. + * Gets the width of the [SPCharGrid] instance. * * # Arguments * * - `char_grid`: instance to read from - */ -size_t sp_char_grid_width(CharGrid */*notnull*/ char_grid); - -/** - * Sets a window of pixels to the specified values. * - * The passed [Bitmap] gets consumed. + * # Panics * - * Returns: a new [servicepoint::BitmapCommand] instance. + * - when `char_grid` is NULL + * + * # Safety + * + * The caller has to make sure that: + * + * - `char_grid` points to a valid [SPCharGrid] */ -TypedCommand *sp_command_bitmap(size_t x, - size_t y, - Bitmap */*notnull*/ bitmap, - CompressionCode compression); +size_t sp_char_grid_width(const SPCharGrid *char_grid); /** * Set pixel data starting at the pixel offset on screen. @@ -926,137 +1094,513 @@ TypedCommand *sp_command_bitmap(size_t x, * 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. + * The contained [SPBitVec] is always uncompressed. * - * `new_bit = old_bit op sent_bit` + * The passed [SPBitVec] gets consumed. * - * For example, [`BinaryOperation::Or`] can be used to turn on some pixels without affecting other pixels. + * Returns: a new [servicepoint::Command::BitmapLinear] instance. Will never return NULL. * - * The contained [`BitVecU8Msb0`] is always uncompressed. + * # 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`. */ -TypedCommand *sp_command_bitvec(size_t offset, - SPBitVec */*notnull*/ bit_vec, - CompressionCode compression, - BinaryOperation operation); +SPCommand *sp_command_bitmap_linear(size_t offset, + SPBitVec *bit_vec, + SPCompressionCode compression); + +/** + * 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`. + */ +SPCommand *sp_command_bitmap_linear_and(size_t offset, + SPBitVec *bit_vec, + SPCompressionCode compression); + +/** + * 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`. + */ +SPCommand *sp_command_bitmap_linear_or(size_t offset, + SPBitVec *bit_vec, + SPCompressionCode compression); + +/** + * 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`. + */ +SPCommand *sp_command_bitmap_linear_win(size_t x, + size_t y, + SPBitmap *bitmap, + SPCompressionCode compression_code); + +/** + * 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`. + */ +SPCommand *sp_command_bitmap_linear_xor(size_t offset, + SPBitVec *bit_vec, + SPCompressionCode compression); + +/** + * 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`. + */ +SPCommand *sp_command_brightness(uint8_t brightness); /** * Set the brightness of individual tiles in a rectangular area of the display. * - * The passed [BrightnessGrid] gets consumed. + * The passed [SPBrightnessGrid] gets consumed. * - * Returns: a new [servicepoint::Command::CharBrightness] instance. + * Returns: a new [servicepoint::Command::CharBrightness] instance. Will never return NULL. + * + * # Panics + * + * - when `grid` is NULL + * + * # Safety + * + * The caller has to make sure that: + * + * - `grid` points to a valid instance of [SPBrightnessGrid] + * - `grid` is not used concurrently or after this call + * - the returned [SPCommand] instance is freed in some way, either by using a consuming function or + * by explicitly calling `sp_command_free`. */ -TypedCommand */*notnull*/ sp_command_brightness_grid(size_t x, - size_t y, - BrightnessGrid */*notnull*/ grid); - -/** - * Show UTF-8 encoded text on the screen. - * - * The passed [CharGrid] gets consumed. - * - * Returns: a new [servicepoint::CharGridCommand] instance. - */ -TypedCommand */*notnull*/ sp_command_char_grid(size_t x, - size_t y, - CharGrid */*notnull*/ grid); +SPCommand *sp_command_char_brightness(size_t x, + size_t y, + SPBrightnessGrid *grid); /** * Set all pixels to the off state. * * Does not affect brightness. * - * Returns: a new [servicepoint::Command::Clear] instance. + * Returns: a new [servicepoint::Command::Clear] instance. Will never return NULL. * * # Examples * * ```C - * sp_udp_send_command(connection, sp_command_clear()); + * sp_connection_send_command(connection, sp_command_clear()); * ``` + * + * # Safety + * + * The caller has to make sure that: + * + * - the returned [SPCommand] instance is freed in some way, either by using a consuming function or + * by explicitly calling `sp_command_free`. */ -TypedCommand */*notnull*/ sp_command_clear(void); +SPCommand *sp_command_clear(void); /** - * Clones a [TypedCommand] instance. + * Clones a [SPCommand] instance. * - * returns: new [TypedCommand] instance. + * returns: new [SPCommand] instance. Will never return NULL. + * + * # Panics + * + * - when `command` is NULL + * + * # Safety + * + * The caller has to make sure that: + * + * - `command` points to a valid instance of [SPCommand] + * - `command` is not written to concurrently + * - the returned [SPCommand] instance is freed in some way, either by using a consuming function or + * by explicitly calling `sp_command_free`. */ -TypedCommand */*notnull*/ sp_command_clone(TypedCommand */*notnull*/ command); +SPCommand *sp_command_clone(const SPCommand *command); /** * Show codepage 437 encoded text on the screen. * - * The passed [Cp437Grid] gets consumed. + * The passed [SPCp437Grid] gets consumed. * - * Returns: a new [servicepoint::Cp437GridCommand] instance. + * Returns: a new [servicepoint::Command::Cp437Data] instance. Will never return NULL. + * + * # Panics + * + * - when `grid` is null + * + * # Safety + * + * The caller has to make sure that: + * + * - `grid` points to a valid instance of [SPCp437Grid] + * - `grid` is not used concurrently or after this call + * - the returned [SPCommand] instance is freed in some way, either by using a consuming function or + * by explicitly calling `sp_command_free`. */ -TypedCommand */*notnull*/ sp_command_cp437_grid(size_t x, - size_t y, - Cp437Grid */*notnull*/ grid); +SPCommand *sp_command_cp437_data(size_t x, + size_t y, + SPCp437Grid *grid); /** * A yet-to-be-tested command. * - * Returns: a new [servicepoint::Command::FadeOut] instance. + * Returns: a new [servicepoint::Command::FadeOut] instance. Will never return NULL. + * + * # Safety + * + * The caller has to make sure that: + * + * - the returned [SPCommand] instance is freed in some way, either by using a consuming function or + * by explicitly calling `sp_command_free`. */ -TypedCommand */*notnull*/ sp_command_fade_out(void); +SPCommand *sp_command_fade_out(void); /** - * Deallocates a [TypedCommand]. + * Deallocates a [SPCommand]. * * # Examples * * ```C - * TypedCommand c = sp_command_clear(); + * SPCommand c = sp_command_clear(); * sp_command_free(c); * ``` - */ -void sp_command_free(TypedCommand */*notnull*/ command); - -/** - * Set the brightness of all tiles to the same value. * - * Returns: a new [servicepoint::Command::Brightness] instance. + * # 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] */ -TypedCommand */*notnull*/ sp_command_global_brightness(Brightness brightness); +void sp_command_free(SPCommand *command); /** * 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. + * 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`. */ -TypedCommand */*notnull*/ sp_command_hard_reset(void); +SPCommand *sp_command_hard_reset(void); /** - * Tries to turn a [Packet] into a [TypedCommand]. + * Tries to turn a [SPPacket] into a [SPCommand]. * * The packet is deallocated in the process. * - * Returns: pointer to new [TypedCommand] instance or NULL if parsing failed. + * Returns: pointer to new [SPCommand] instance or NULL if parsing failed. + * + * # Panics + * + * - when `packet` is NULL + * + * # Safety + * + * The caller has to make sure that: + * + * - [SPPacket] points to a valid instance of [SPPacket] + * - [SPPacket] is not used concurrently or after this call + * - the result is checked for NULL + * - the returned [SPCommand] instance is freed in some way, either by using a consuming function or + * by explicitly calling `sp_command_free`. */ -TypedCommand *sp_command_try_from_packet(Packet */*notnull*/ packet); +SPCommand *sp_command_try_from_packet(SPPacket *packet); /** - * Clones a [Cp437Grid]. + * 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`. */ -Cp437Grid */*notnull*/ sp_cp437_grid_clone(Cp437Grid */*notnull*/ cp437_grid); +SPCommand *sp_command_utf8_data(size_t x, + size_t y, + SPCharGrid *grid); /** - * Sets the value of all cells in the [Cp437Grid]. + * Creates a new instance of [SPConnection] for testing that does not actually send anything. + * + * returns: a new instance. 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_connection_free`. + */ +SPConnection *sp_connection_fake(void); + +/** + * Closes and deallocates a [SPConnection]. + * + * # Panics + * + * - when `connection` is NULL + * + * # Safety + * + * The caller has to make sure that: + * + * - `connection` points to a valid [SPConnection] + * - `connection` is not used concurrently or after this call + */ +void sp_connection_free(SPConnection *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`. + */ +SPConnection *sp_connection_open(const char *host); + +/** + * Sends a [SPCommand] to the display using the [SPConnection]. + * + * The passed `command` gets consumed. + * + * returns: true in case of success + * + * # Panics + * + * - when `connection` is NULL + * - when `command` is NULL + * + * # 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 + */ +bool sp_connection_send_command(const SPConnection *connection, + SPCommand *command); + +/** + * Sends a [SPPacket] to the display using the [SPConnection]. + * + * The passed `packet` gets consumed. + * + * 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 + */ +bool sp_connection_send_packet(const SPConnection *connection, + SPPacket *packet); + +/** + * Clones a [SPCp437Grid]. + * + * Will never return NULL. + * + * # Panics + * + * - when `cp437_grid` is NULL + * + * # Safety + * + * The caller has to make sure that: + * + * - `cp437_grid` points to a valid [SPCp437Grid] + * - `cp437_grid` is not written to concurrently + * - the returned instance is freed in some way, either by using a consuming function or + * by explicitly calling `sp_cp437_grid_free`. + */ +SPCp437Grid *sp_cp437_grid_clone(const SPCp437Grid *cp437_grid); + +/** + * Sets the value of all cells in the [SPCp437Grid]. * * # Arguments * * - `cp437_grid`: instance to write 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 */ -void sp_cp437_grid_fill(Cp437Grid */*notnull*/ cp437_grid, uint8_t value); +void sp_cp437_grid_fill(SPCp437Grid *cp437_grid, uint8_t value); /** - * Deallocates a [Cp437Grid]. + * Deallocates a [SPCp437Grid]. + * + * # Panics + * + * - when `cp437_grid` is NULL + * + * # Safety + * + * The caller has to make sure that: + * + * - `cp437_grid` points to a valid [SPCp437Grid] + * - `cp437_grid` is not used concurrently or after cp437_grid call + * - `cp437_grid` was not passed to another consuming function, e.g. to create a [SPCommand] + * + * [SPCommand]: [crate::SPCommand] */ -void sp_cp437_grid_free(Cp437Grid */*notnull*/ cp437_grid); +void sp_cp437_grid_free(SPCp437Grid *cp437_grid); /** * Gets the current value at the specified position. @@ -1068,46 +1612,78 @@ void sp_cp437_grid_free(Cp437Grid */*notnull*/ cp437_grid); * * # Panics * + * - when `cp437_grid` is NULL * - 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 */ -uint8_t sp_cp437_grid_get(Cp437Grid */*notnull*/ cp437_grid, - size_t x, - size_t y); +uint8_t sp_cp437_grid_get(const SPCp437Grid *cp437_grid, size_t x, size_t y); /** - * Gets the height of the [Cp437Grid] instance. + * Gets the height of the [SPCp437Grid] instance. * * # Arguments * * - `cp437_grid`: instance to read from - */ -size_t sp_cp437_grid_height(Cp437Grid */*notnull*/ cp437_grid); - -/** - * Creates a [Cp437GridCommand] and immediately turns that into a [Packet]. * - * The provided [Cp437Grid] gets consumed. + * # Panics * - * Returns NULL in case of an error. - */ -Packet *sp_cp437_grid_into_packet(Cp437Grid */*notnull*/ grid, - size_t x, - size_t y); - -/** - * Loads a [Cp437Grid] with the specified dimensions from the provided data. - */ -Cp437Grid *sp_cp437_grid_load(size_t width, size_t height, ByteSlice data); - -/** - * Creates a new [Cp437Grid] with the specified dimensions. + * - when `cp437_grid` is NULL * - * returns: [Cp437Grid] initialized to 0. + * # Safety + * + * The caller has to make sure that: + * + * - `cp437_grid` points to a valid [SPCp437Grid] */ -Cp437Grid */*notnull*/ sp_cp437_grid_new(size_t width, size_t height); +size_t sp_cp437_grid_height(const SPCp437Grid *cp437_grid); /** - * Sets the value of the specified position in the [Cp437Grid]. + * Loads a [SPCp437Grid] with the specified dimensions from the provided data. + * + * Will never return NULL. + * + * # Panics + * + * - when `data` is NULL + * - when the provided `data_length` does not match `height` and `width` + * + * # Safety + * + * The caller has to make sure that: + * + * - `data` points to a valid memory location of at least `data_length` + * bytes in size. + * - the returned instance is freed in some way, either by using a consuming function or + * by explicitly calling `sp_cp437_grid_free`. + */ +SPCp437Grid *sp_cp437_grid_load(size_t width, + size_t height, + const uint8_t *data, + size_t data_length); + +/** + * 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`. + */ +SPCp437Grid *sp_cp437_grid_new(size_t width, + size_t height); + +/** + * Sets the value of the specified position in the [SPCp437Grid]. * * # Arguments * @@ -1119,177 +1695,172 @@ Cp437Grid */*notnull*/ sp_cp437_grid_new(size_t width, size_t height); * * # Panics * + * - when `cp437_grid` is NULL * - 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] */ -void sp_cp437_grid_set(Cp437Grid */*notnull*/ cp437_grid, +void sp_cp437_grid_set(SPCp437Grid *cp437_grid, size_t x, size_t y, uint8_t value); /** - * Gets an unsafe reference to the data of the [Cp437Grid] instance. + * Gets an unsafe reference to the data of the [SPCp437Grid] instance. * - * The returned memory is valid for the lifetime of the grid. + * Will never return NULL. + * + * # Panics + * + * - when `cp437_grid` is NULL + * + * ## Safety + * + * The caller has to make sure that: + * + * - `cp437_grid` points to a valid [SPCp437Grid] + * - the returned memory range is never accessed after the passed [SPCp437Grid] has been freed + * - the returned memory range is never accessed concurrently, either via the [SPCp437Grid] or directly */ -ByteSlice sp_cp437_grid_unsafe_data_ref(Cp437Grid */*notnull*/ cp437_grid); +SPByteSlice sp_cp437_grid_unsafe_data_ref(SPCp437Grid *cp437_grid); /** - * Gets the width of the [Cp437Grid] instance. + * Gets the width of the [SPCp437Grid] instance. * * # Arguments * * - `cp437_grid`: instance to read from - */ -size_t sp_cp437_grid_width(Cp437Grid */*notnull*/ cp437_grid); - -/** - * Clones a [Packet]. - */ -Packet */*notnull*/ sp_packet_clone(Packet */*notnull*/ packet); - -/** - * Deallocates a [Packet]. - */ -void sp_packet_free(Packet */*notnull*/ packet); - -/** - * Turns a [TypedCommand] into a [Packet]. - * The [TypedCommand] gets consumed. - * - * Returns NULL in case of an error. - */ -Packet *sp_packet_from_command(TypedCommand */*notnull*/ command); - -/** - * Creates a raw [Packet] from parts. - * - * returns: new instance. Will never return null. - */ -Packet */*notnull*/ sp_packet_from_parts(Header header, - const ByteSlice *payload); - -/** - * Returns a pointer to the header field of the provided packet. - * - * The returned header can be changed and will be valid for the lifetime of the packet. - */ -Header */*notnull*/ sp_packet_get_header(Packet */*notnull*/ packet); - -/** - * 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. - */ -ByteSlice sp_packet_get_payload(Packet */*notnull*/ packet); - -/** - * Serialize the packet into the provided buffer. * * # Panics * - * - if the buffer is not big enough to hold header+payload. - */ -void sp_packet_serialize_to(Packet */*notnull*/ packet, ByteSlice buffer); - -/** - * Sets the payload of the provided packet to the provided data. + * - when `cp437_grid` is NULL * - * This makes previous payload pointers invalid. + * # Safety + * + * The caller has to make sure that: + * + * - `cp437_grid` points to a valid [SPCp437Grid] */ -void sp_packet_set_payload(Packet */*notnull*/ packet, ByteSlice data); +size_t sp_cp437_grid_width(const SPCp437Grid *cp437_grid); /** - * Tries to load a [Packet] from the passed array with the specified length. + * Clones a [SPPacket]. + * + * Will never return NULL. + * + * # Panics + * + * - when `packet` is NULL + * + * # Safety + * + * The caller has to make sure that: + * + * - `packet` points to a valid [SPPacket] + * - `packet` is not written to concurrently + * - the returned instance is freed in some way, either by using a consuming function or + * by explicitly calling `sp_packet_free`. + */ +SPPacket *sp_packet_clone(const SPPacket *packet); + +/** + * Deallocates a [SPPacket]. + * + * # Panics + * + * - when `packet` is NULL + * + * # Safety + * + * The caller has to make sure that: + * + * - `packet` points to a valid [SPPacket] + * - `packet` is not used concurrently or after this call + */ +void sp_packet_free(SPPacket *packet); + +/** + * Turns a [SPCommand] into a [SPPacket]. + * The [SPCommand] gets consumed. + * + * Will never return NULL. + * + * # Panics + * + * - when `command` is NULL + * + * # Safety + * + * The caller has to make sure that: + * + * - [SPCommand] points to a valid instance of [SPCommand] + * - [SPCommand] is not used concurrently or after this call + * - the returned [SPPacket] instance is freed in some way, either by using a consuming function or + * by explicitly calling `sp_packet_free`. + */ +SPPacket *sp_packet_from_command(SPCommand *command); + +/** + * Creates a raw [SPPacket] from parts. + * + * # Arguments + * + * - `command_code` specifies which command this packet contains + * - `a`, `b`, `c` and `d` are command-specific header values + * - `payload` is the optional data that is part of the command + * - `payload_len` is the size of the payload + * + * returns: new instance. Will never return null. + * + * # 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]. + */ +SPPacket *sp_packet_from_parts(uint16_t command_code, + uint16_t a, + uint16_t b, + uint16_t c, + uint16_t d, + const uint8_t *payload, + size_t payload_len); + +/** + * 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`. */ -Packet *sp_packet_try_load(ByteSlice data); - -/** - * Converts u16 into [CommandCode]. - * - * If the provided value is not valid, false is returned and result is not changed. - */ -bool sp_u16_to_command_code(uint16_t code, - CommandCode *result); - -/** - * Closes and deallocates a [UdpConnection]. - */ -void sp_udp_free(UdpSocket */*notnull*/ connection); - -/** - * Creates a new instance of [UdpConnection]. - * - * returns: NULL if connection fails, or connected instance - * - * # Examples - * - * ```C - * UdpConnection connection = sp_udp_open("172.23.42.29:2342"); - * if (connection != NULL) - * sp_udp_send_command(connection, sp_command_clear()); - * ``` - */ -UdpSocket *sp_udp_open(char */*notnull*/ host); - -/** - * Creates a new instance of [UdpConnection]. - * - * returns: NULL if connection fails, or connected instance - * - * # Examples - * - * ```C - * UdpConnection connection = sp_udp_open_ipv4(172, 23, 42, 29, 2342); - * if (connection != NULL) - * sp_udp_send_command(connection, sp_command_clear()); - * ``` - */ -UdpSocket *sp_udp_open_ipv4(uint8_t ip1, - uint8_t ip2, - uint8_t ip3, - uint8_t ip4, - uint16_t port); - -/** - * Sends a [TypedCommand] to the display using the [UdpConnection]. - * - * The passed `command` gets consumed. - * - * returns: true in case of success - * - * # Examples - * - * ```C - * sp_udp_send_command(connection, sp_command_brightness(5)); - * ``` - */ -bool sp_udp_send_command(UdpSocket */*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(UdpSocket */*notnull*/ udp_connection, Header header); - -/** - * Sends a [Packet] to the display using the [UdpConnection]. - * - * The passed `packet` gets consumed. - * - * returns: true in case of success - */ -bool sp_udp_send_packet(UdpSocket */*notnull*/ connection, - Packet */*notnull*/ packet); +SPPacket *sp_packet_try_load(const uint8_t *data, + size_t length); #ifdef __cplusplus } // extern "C" diff --git a/src/bitmap.rs b/src/bitmap.rs index 5db4af0..3313385 100644 --- a/src/bitmap.rs +++ b/src/bitmap.rs @@ -1,24 +1,13 @@ -use crate::byte_slice::ByteSlice; -use crate::{heap_drop, heap_move, heap_move_nonnull, heap_remove, SPBitVec}; -use servicepoint::{ - Bitmap, BitmapCommand, CompressionCode, DataRef, Grid, Origin, Packet, -}; +//! C functions for interacting with [SPBitmap]s +//! +//! prefix `sp_bitmap_` + +use servicepoint::{DataRef, Grid}; use std::ptr::NonNull; -/// Creates a new [Bitmap] with the specified dimensions. -/// -/// # Arguments -/// -/// - `width`: size in pixels in x-direction -/// - `height`: size in pixels in y-direction -/// -/// returns: [Bitmap] initialized to all pixels off, or NULL in case of an error. -/// -/// # Errors -/// -/// In the following cases, this function will return NULL: -/// -/// - when the width is not dividable by 8 +use crate::byte_slice::SPByteSlice; + +/// A grid of pixels. /// /// # Examples /// @@ -28,81 +17,135 @@ use std::ptr::NonNull; /// sp_bitmap_set(grid, 0, 0, false); /// sp_bitmap_free(grid); /// ``` -#[no_mangle] -pub unsafe extern "C" fn sp_bitmap_new( - width: usize, - height: usize, -) -> *mut Bitmap { - if let Some(bitmap) = Bitmap::new(width, height) { - heap_move(bitmap) - } else { - std::ptr::null_mut() - } -} +pub struct SPBitmap(pub(crate) servicepoint::Bitmap); -/// Creates a new [Bitmap] with a size matching the screen. -/// -/// returns: [Bitmap] initialized to all pixels off. -#[no_mangle] -pub unsafe extern "C" fn sp_bitmap_new_max_sized() -> NonNull { - heap_move_nonnull(Bitmap::max_sized()) -} - -/// Loads a [Bitmap] with the specified dimensions from the provided data. +/// Creates a new [SPBitmap] with the specified dimensions. /// /// # Arguments /// /// - `width`: size in pixels in x-direction /// - `height`: size in pixels in y-direction /// -/// returns: [Bitmap] that contains a copy of the provided data, or NULL in case of an error. +/// returns: [SPBitmap] 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] +pub unsafe extern "C" fn sp_bitmap_new( + width: usize, + height: usize, +) -> NonNull { + let result = Box::new(SPBitmap(servicepoint::Bitmap::new(width, height))); + NonNull::from(Box::leak(result)) +} + +/// Creates a new [SPBitmap] with a size matching the screen. +/// +/// returns: [SPBitmap] initialized to all pixels off. Will never return NULL. +/// +/// # Safety +/// +/// The caller has to make sure that: +/// +/// - the returned instance is freed in some way, either by using a consuming function or +/// by explicitly calling [sp_bitmap_free]. +#[no_mangle] +pub unsafe extern "C" fn sp_bitmap_new_screen_sized() -> NonNull { + let result = Box::new(SPBitmap(servicepoint::Bitmap::max_sized())); + NonNull::from(Box::leak(result)) +} + +/// Loads a [SPBitmap] with the specified dimensions from the provided data. +/// +/// # Arguments +/// +/// - `width`: size in pixels in x-direction +/// - `height`: size in pixels in y-direction +/// +/// returns: [SPBitmap] that contains a copy of the provided data. Will never return NULL. +/// +/// # 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] pub unsafe extern "C" fn sp_bitmap_load( width: usize, height: usize, - data: ByteSlice, -) -> *mut Bitmap { - let data = unsafe { data.as_slice() }; - if let Ok(bitmap) = Bitmap::load(width, height, data) { - heap_move(bitmap) - } else { - std::ptr::null_mut() - } + data: *const u8, + data_length: usize, +) -> NonNull { + assert!(!data.is_null()); + let data = std::slice::from_raw_parts(data, data_length); + let result = + Box::new(SPBitmap(servicepoint::Bitmap::load(width, height, data))); + NonNull::from(Box::leak(result)) } -/// Tries to convert the BitVec to a Bitmap. +/// Clones a [SPBitmap]. /// -/// The provided BitVec gets consumed. +/// Will never return NULL. /// -/// Returns NULL in case of error. -#[no_mangle] -pub unsafe extern "C" fn sp_bitmap_from_bitvec( - width: usize, - bitvec: NonNull, -) -> *mut Bitmap { - let bitvec = unsafe { heap_remove(bitvec) }; - if let Ok(bitmap) = Bitmap::from_bitvec(width, bitvec.0) { - heap_move(bitmap) - } else { - std::ptr::null_mut() - } -} - -/// Clones a [Bitmap]. +/// # Panics +/// +/// - when `bitmap` is NULL +/// +/// # Safety +/// +/// The caller has to make sure that: +/// +/// - `bitmap` points to a valid [SPBitmap] +/// - `bitmap` is not written to concurrently +/// - the returned instance is freed in some way, either by using a consuming function or +/// by explicitly calling `sp_bitmap_free`. #[no_mangle] pub unsafe extern "C" fn sp_bitmap_clone( - bitmap: NonNull, -) -> NonNull { - heap_move_nonnull(unsafe { bitmap.as_ref().clone() }) + bitmap: *const SPBitmap, +) -> NonNull { + assert!(!bitmap.is_null()); + let result = Box::new(SPBitmap((*bitmap).0.clone())); + NonNull::from(Box::leak(result)) } -/// Deallocates a [Bitmap]. +/// Deallocates a [SPBitmap]. +/// +/// # Panics +/// +/// - when `bitmap` is NULL +/// +/// # Safety +/// +/// The caller has to make sure that: +/// +/// - `bitmap` points to a valid [SPBitmap] +/// - `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] -pub unsafe extern "C" fn sp_bitmap_free(bitmap: NonNull) { - unsafe { heap_drop(bitmap) } +pub unsafe extern "C" fn sp_bitmap_free(bitmap: *mut SPBitmap) { + assert!(!bitmap.is_null()); + _ = Box::from_raw(bitmap); } -/// Gets the current value at the specified position in the [Bitmap]. +/// Gets the current value at the specified position in the [SPBitmap]. /// /// # Arguments /// @@ -111,17 +154,26 @@ pub unsafe extern "C" fn sp_bitmap_free(bitmap: NonNull) { /// /// # Panics /// +/// - when `bitmap` is NULL /// - 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] pub unsafe extern "C" fn sp_bitmap_get( - bitmap: NonNull, + bitmap: *const SPBitmap, x: usize, y: usize, ) -> bool { - unsafe { bitmap.as_ref().get(x, y) } + assert!(!bitmap.is_null()); + (*bitmap).0.get(x, y) } -/// Sets the value of the specified position in the [Bitmap]. +/// Sets the value of the specified position in the [SPBitmap]. /// /// # Arguments /// @@ -129,31 +181,54 @@ pub unsafe extern "C" fn sp_bitmap_get( /// - `x` and `y`: position of the cell /// - `value`: the value to write to the cell /// +/// returns: old value of the cell +/// /// # Panics /// +/// - when `bitmap` is NULL /// - 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] pub unsafe extern "C" fn sp_bitmap_set( - bitmap: NonNull, + bitmap: *mut SPBitmap, x: usize, y: usize, value: bool, ) { - unsafe { (*bitmap.as_ptr()).set(x, y, value) }; + assert!(!bitmap.is_null()); + (*bitmap).0.set(x, y, value); } -/// Sets the state of all pixels in the [Bitmap]. +/// Sets the state of all pixels in the [SPBitmap]. /// /// # Arguments /// /// - `bitmap`: instance to write 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] -pub unsafe extern "C" fn sp_bitmap_fill(bitmap: NonNull, value: bool) { - unsafe { (*bitmap.as_ptr()).fill(value) }; +pub unsafe extern "C" fn sp_bitmap_fill(bitmap: *mut SPBitmap, value: bool) { + assert!(!bitmap.is_null()); + (*bitmap).0.fill(value); } -/// Gets the width in pixels of the [Bitmap] instance. +/// Gets the width in pixels of the [SPBitmap] instance. /// /// # Arguments /// @@ -167,60 +242,55 @@ pub unsafe extern "C" fn sp_bitmap_fill(bitmap: NonNull, value: bool) { /// /// The caller has to make sure that: /// -/// - `bitmap` points to a valid [Bitmap] +/// - `bitmap` points to a valid [SPBitmap] #[no_mangle] -pub unsafe extern "C" fn sp_bitmap_width(bitmap: NonNull) -> usize { - unsafe { bitmap.as_ref().width() } +pub unsafe extern "C" fn sp_bitmap_width(bitmap: *const SPBitmap) -> usize { + assert!(!bitmap.is_null()); + (*bitmap).0.width() } -/// Gets the height in pixels of the [Bitmap] instance. +/// Gets the height in pixels of the [SPBitmap] instance. /// /// # Arguments /// /// - `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] -pub unsafe extern "C" fn sp_bitmap_height(bitmap: NonNull) -> usize { - unsafe { bitmap.as_ref().height() } +pub unsafe extern "C" fn sp_bitmap_height(bitmap: *const SPBitmap) -> usize { + assert!(!bitmap.is_null()); + (*bitmap).0.height() } -/// Gets an unsafe reference to the data of the [Bitmap] instance. +/// Gets an unsafe reference to the data of the [SPBitmap] instance. /// -/// The returned memory is valid for the lifetime of the bitmap. +/// # Panics +/// +/// - when `bitmap` is NULL +/// +/// # Safety +/// +/// The caller has to make sure that: +/// +/// - `bitmap` points to a valid [SPBitmap] +/// - the returned memory range is never accessed after the passed [SPBitmap] has been freed +/// - the returned memory range is never accessed concurrently, either via the [SPBitmap] or directly #[no_mangle] pub unsafe extern "C" fn sp_bitmap_unsafe_data_ref( - mut bitmap: NonNull, -) -> ByteSlice { - unsafe { ByteSlice::from_slice(bitmap.as_mut().data_ref_mut()) } -} - -/// Consumes the Bitmap and returns the contained BitVec -#[no_mangle] -pub unsafe extern "C" fn sp_bitmap_into_bitvec( - bitmap: NonNull, -) -> NonNull { - 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, - 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(), + bitmap: *mut SPBitmap, +) -> SPByteSlice { + assert!(!bitmap.is_null()); + let data = (*bitmap).0.data_ref_mut(); + SPByteSlice { + start: NonNull::new(data.as_mut_ptr_range().start).unwrap(), + length: data.len(), } } diff --git a/src/bitvec.rs b/src/bitvec.rs index 0e7ee5e..484e849 100644 --- a/src/bitvec.rs +++ b/src/bitvec.rs @@ -1,7 +1,8 @@ -use crate::{heap_drop, heap_move, heap_move_nonnull, heap_remove, ByteSlice}; -use servicepoint::{ - BinaryOperation, BitVecCommand, CompressionCode, DisplayBitVec, Packet, -}; +//! C functions for interacting with [SPBitVec]s +//! +//! prefix `sp_bitvec_` + +use crate::SPByteSlice; use std::ptr::NonNull; /// A vector of bits @@ -12,7 +13,19 @@ use std::ptr::NonNull; /// sp_bitvec_set(vec, 5, true); /// sp_bitvec_free(vec); /// ``` -pub struct SPBitVec(pub(crate) DisplayBitVec); +pub struct SPBitVec(servicepoint::BitVec); + +impl From for SPBitVec { + fn from(actual: servicepoint::BitVec) -> Self { + Self(actual) + } +} + +impl From for servicepoint::BitVec { + fn from(value: SPBitVec) -> Self { + value.0 + } +} impl Clone for SPBitVec { fn clone(&self) -> Self { @@ -26,37 +39,95 @@ impl Clone for SPBitVec { /// /// - `size`: size in bits. /// -/// returns: [SPBitVec] with all bits set to false. +/// returns: [SPBitVec] with all bits set to false. Will never return NULL. /// /// # Panics /// /// - 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] pub unsafe extern "C" fn sp_bitvec_new(size: usize) -> NonNull { - heap_move_nonnull(SPBitVec(DisplayBitVec::repeat(false, size))) + let result = Box::new(SPBitVec(servicepoint::BitVec::repeat(false, size))); + NonNull::from(Box::leak(result)) } /// Interpret the data as a series of bits and load then into a new [SPBitVec] instance. /// -/// returns: [SPBitVec] instance containing data. +/// returns: [SPBitVec] instance containing data. Will never return NULL. +/// +/// # Panics +/// +/// - when `data` is NULL +/// +/// # Safety +/// +/// The caller has to make sure that: +/// +/// - `data` points to a valid memory location of at least `data_length` +/// bytes in size. +/// - the returned instance is freed in some way, either by using a consuming function or +/// by explicitly calling `sp_bitvec_free`. #[no_mangle] -pub unsafe extern "C" fn sp_bitvec_load(data: ByteSlice) -> NonNull { - let data = unsafe { data.as_slice() }; - heap_move_nonnull(SPBitVec(DisplayBitVec::from_slice(data))) +pub unsafe extern "C" fn sp_bitvec_load( + data: *const u8, + data_length: usize, +) -> NonNull { + 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]. +/// +/// 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] pub unsafe extern "C" fn sp_bitvec_clone( - bit_vec: NonNull, + bit_vec: *const SPBitVec, ) -> NonNull { - heap_move_nonnull(unsafe { bit_vec.as_ref().clone() }) + assert!(!bit_vec.is_null()); + let result = Box::new((*bit_vec).clone()); + NonNull::from(Box::leak(result)) } /// 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] -pub unsafe extern "C" fn sp_bitvec_free(bit_vec: NonNull) { - unsafe { heap_drop(bit_vec) } +pub unsafe extern "C" fn sp_bitvec_free(bit_vec: *mut SPBitVec) { + assert!(!bit_vec.is_null()); + _ = Box::from_raw(bit_vec); } /// Gets the value of a bit from the [SPBitVec]. @@ -70,13 +141,22 @@ pub unsafe extern "C" fn sp_bitvec_free(bit_vec: NonNull) { /// /// # Panics /// +/// - when `bit_vec` is NULL /// - 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] pub unsafe extern "C" fn sp_bitvec_get( - bit_vec: NonNull, + bit_vec: *const SPBitVec, index: usize, ) -> bool { - unsafe { *bit_vec.as_ref().0.get(index).unwrap() } + assert!(!bit_vec.is_null()); + *(*bit_vec).0.get(index).unwrap() } /// Sets the value of a bit in the [SPBitVec]. @@ -89,14 +169,23 @@ pub unsafe extern "C" fn sp_bitvec_get( /// /// # Panics /// +/// - when `bit_vec` is NULL /// - 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] pub unsafe extern "C" fn sp_bitvec_set( - bit_vec: NonNull, + bit_vec: *mut SPBitVec, index: usize, value: bool, ) { - unsafe { (*bit_vec.as_ptr()).0.set(index, value) } + assert!(!bit_vec.is_null()); + (*bit_vec).0.set(index, value) } /// Sets the value of all bits in the [SPBitVec]. @@ -105,12 +194,21 @@ pub unsafe extern "C" fn sp_bitvec_set( /// /// - `bit_vec`: instance to write 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] -pub unsafe extern "C" fn sp_bitvec_fill( - bit_vec: NonNull, - value: bool, -) { - unsafe { (*bit_vec.as_ptr()).0.fill(value) } +pub unsafe extern "C" fn sp_bitvec_fill(bit_vec: *mut SPBitVec, value: bool) { + assert!(!bit_vec.is_null()); + (*bit_vec).0.fill(value) } /// Gets the length of the [SPBitVec] in bits. @@ -118,9 +216,20 @@ pub unsafe extern "C" fn sp_bitvec_fill( /// # Arguments /// /// - `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] -pub unsafe extern "C" fn sp_bitvec_len(bit_vec: NonNull) -> usize { - unsafe { bit_vec.as_ref().0.len() } +pub unsafe extern "C" fn sp_bitvec_len(bit_vec: *const SPBitVec) -> usize { + assert!(!bit_vec.is_null()); + (*bit_vec).0.len() } /// Returns true if length is 0. @@ -128,47 +237,47 @@ pub unsafe extern "C" fn sp_bitvec_len(bit_vec: NonNull) -> usize { /// # Arguments /// /// - `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] -pub unsafe extern "C" fn sp_bitvec_is_empty( - bit_vec: NonNull, -) -> bool { - unsafe { bit_vec.as_ref().0.is_empty() } +pub unsafe extern "C" fn sp_bitvec_is_empty(bit_vec: *const SPBitVec) -> bool { + assert!(!bit_vec.is_null()); + (*bit_vec).0.is_empty() } /// Gets an unsafe reference to the data of the [SPBitVec] instance. /// -/// The returned memory is valid for the lifetime of the bitvec. -/// /// # Arguments /// /// - `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] pub unsafe extern "C" fn sp_bitvec_unsafe_data_ref( - bit_vec: NonNull, -) -> ByteSlice { - unsafe { ByteSlice::from_slice((*bit_vec.as_ptr()).0.as_raw_mut_slice()) } -} - -/// Creates a [BitVecCommand] and immediately turns that into a [Packet]. -/// -/// 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, - 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(), + bit_vec: *mut SPBitVec, +) -> SPByteSlice { + assert!(!bit_vec.is_null()); + let data = (*bit_vec).0.as_raw_mut_slice(); + SPByteSlice { + start: NonNull::new(data.as_mut_ptr_range().start).unwrap(), + length: data.len(), } } diff --git a/src/brightness_grid.rs b/src/brightness_grid.rs index 1099be0..83af008 100644 --- a/src/brightness_grid.rs +++ b/src/brightness_grid.rs @@ -1,71 +1,146 @@ -use crate::{heap_drop, heap_move, heap_move_nonnull, heap_remove, ByteSlice}; -use servicepoint::{ - Brightness, BrightnessGrid, BrightnessGridCommand, ByteGrid, DataRef, Grid, - Origin, Packet, -}; -use std::mem::transmute; +//! C functions for interacting with [SPBrightnessGrid]s +//! +//! prefix `sp_brightness_grid_` + +use crate::SPByteSlice; +use servicepoint::{DataRef, Grid}; +use std::convert::Into; +use std::intrinsics::transmute; use std::ptr::NonNull; -/// Creates a new [BrightnessGrid] with the specified dimensions. -/// -/// returns: [BrightnessGrid] initialized to 0. +/// see [servicepoint::Brightness::MIN] +pub const SP_BRIGHTNESS_MIN: u8 = 0; +/// see [servicepoint::Brightness::MAX] +pub const SP_BRIGHTNESS_MAX: u8 = 11; +/// Count of possible brightness values +pub const SP_BRIGHTNESS_LEVELS: u8 = 12; + +/// A grid containing brightness values. /// /// # Examples /// ```C -/// UdpConnection connection = sp_udp_open("127.0.0.1:2342"); +/// SPConnection connection = sp_connection_open("127.0.0.1:2342"); /// if (connection == NULL) /// return 1; /// -/// BrightnessGrid grid = sp_brightness_grid_new(2, 2); +/// SPBrightnessGrid grid = sp_brightness_grid_new(2, 2); /// sp_brightness_grid_set(grid, 0, 0, 0); /// sp_brightness_grid_set(grid, 1, 1, 10); /// -/// TypedCommand command = sp_command_char_brightness(grid); -/// sp_udp_free(connection); +/// SPCommand command = sp_command_char_brightness(grid); +/// sp_connection_free(connection); /// ``` +#[derive(Clone)] +pub struct SPBrightnessGrid(pub(crate) servicepoint::BrightnessGrid); + +/// Creates a new [SPBrightnessGrid] with the specified dimensions. +/// +/// returns: [SPBrightnessGrid] initialized to 0. Will never return NULL. +/// +/// # Safety +/// +/// The caller has to make sure that: +/// +/// - the returned instance is freed in some way, either by using a consuming function or +/// by explicitly calling `sp_brightness_grid_free`. #[no_mangle] pub unsafe extern "C" fn sp_brightness_grid_new( width: usize, height: usize, -) -> NonNull { - heap_move_nonnull(BrightnessGrid::new(width, height)) +) -> NonNull { + let result = Box::new(SPBrightnessGrid(servicepoint::BrightnessGrid::new( + width, height, + ))); + NonNull::from(Box::leak(result)) } -/// Loads a [BrightnessGrid] with the specified dimensions from the provided data. +/// Loads a [SPBrightnessGrid] with the specified dimensions from the provided data. /// -/// Any out of range values will be set to [Brightness::MAX] or [Brightness::MIN]. +/// returns: new [SPBrightnessGrid] instance. Will never return NULL. /// -/// returns: new [BrightnessGrid] instance, or NULL in case of an error. +/// # Panics +/// +/// - when `data` is NULL +/// - when the provided `data_length` does not match `height` and `width` +/// +/// # Safety +/// +/// The caller has to make sure that: +/// +/// - `data` points to a valid memory location of at least `data_length` +/// bytes in size. +/// - the returned instance is freed in some way, either by using a consuming function or +/// by explicitly calling `sp_brightness_grid_free`. #[no_mangle] pub unsafe extern "C" fn sp_brightness_grid_load( width: usize, height: usize, - data: ByteSlice, -) -> *mut BrightnessGrid { - let data = unsafe { data.as_slice() }; - - match ByteGrid::load(width, height, data) - .map(move |grid| grid.map(Brightness::saturating_from)) - { - None => std::ptr::null_mut(), - Some(grid) => heap_move(grid), - } + data: *const u8, + data_length: usize, +) -> NonNull { + assert!(!data.is_null()); + let data = std::slice::from_raw_parts(data, data_length); + let grid = servicepoint::ByteGrid::load(width, height, data); + let grid = servicepoint::BrightnessGrid::try_from(grid) + .expect("invalid brightness value"); + let result = Box::new(SPBrightnessGrid(grid)); + NonNull::from(Box::leak(result)) } -/// Clones a [BrightnessGrid]. +/// Clones a [SPBrightnessGrid]. +/// +/// # Arguments +/// +/// - `brightness_grid`: instance to read from +/// +/// returns: new [SPBrightnessGrid] instance. Will never return NULL. +/// +/// # Panics +/// +/// - when `brightness_grid` is NULL +/// +/// # Safety +/// +/// The caller has to make sure that: +/// +/// - `brightness_grid` points to a valid [SPBrightnessGrid] +/// - `brightness_grid` is not written to concurrently +/// - the returned instance is freed in some way, either by using a consuming function or +/// by explicitly calling `sp_brightness_grid_free`. #[no_mangle] pub unsafe extern "C" fn sp_brightness_grid_clone( - brightness_grid: NonNull, -) -> NonNull { - heap_move_nonnull(unsafe { brightness_grid.as_ref().clone() }) + brightness_grid: *const SPBrightnessGrid, +) -> NonNull { + assert!(!brightness_grid.is_null()); + let result = Box::new((*brightness_grid).clone()); + NonNull::from(Box::leak(result)) } -/// Deallocates a [BrightnessGrid]. +/// Deallocates a [SPBrightnessGrid]. +/// +/// # Arguments +/// +/// - `brightness_grid`: instance to read from +/// +/// # Panics +/// +/// - when `brightness_grid` is NULL +/// +/// # Safety +/// +/// The caller has to make sure that: +/// +/// - `brightness_grid` points to a valid [SPBrightnessGrid] +/// - `brightness_grid` is not used concurrently or after this call +/// - `brightness_grid` was not passed to another consuming function, e.g. to create a [SPCommand] +/// +/// [SPCommand]: [crate::SPCommand] #[no_mangle] pub unsafe extern "C" fn sp_brightness_grid_free( - brightness_grid: NonNull, + brightness_grid: *mut SPBrightnessGrid, ) { - unsafe { heap_drop(brightness_grid) } + assert!(!brightness_grid.is_null()); + _ = Box::from_raw(brightness_grid); } /// Gets the current value at the specified position. @@ -78,17 +153,27 @@ pub unsafe extern "C" fn sp_brightness_grid_free( /// returns: value at position /// /// # Panics +/// +/// - when `brightness_grid` is NULL /// - 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] pub unsafe extern "C" fn sp_brightness_grid_get( - brightness_grid: NonNull, + brightness_grid: *const SPBrightnessGrid, x: usize, y: usize, -) -> Brightness { - unsafe { brightness_grid.as_ref().get(x, y) } +) -> u8 { + assert!(!brightness_grid.is_null()); + (*brightness_grid).0.get(x, y).into() } -/// Sets the value of the specified position in the [BrightnessGrid]. +/// Sets the value of the specified position in the [SPBrightnessGrid]. /// /// # Arguments /// @@ -100,98 +185,138 @@ pub unsafe extern "C" fn sp_brightness_grid_get( /// /// # Panics /// +/// - when `brightness_grid` is NULL /// - 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] pub unsafe extern "C" fn sp_brightness_grid_set( - brightness_grid: NonNull, + brightness_grid: *mut SPBrightnessGrid, x: usize, y: usize, - value: Brightness, + value: u8, ) { - unsafe { (*brightness_grid.as_ptr()).set(x, y, value) }; + assert!(!brightness_grid.is_null()); + 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 [BrightnessGrid]. +/// Sets the value of all cells in the [SPBrightnessGrid]. /// /// # Arguments /// /// - `brightness_grid`: instance to write 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] pub unsafe extern "C" fn sp_brightness_grid_fill( - brightness_grid: NonNull, - value: Brightness, + brightness_grid: *mut SPBrightnessGrid, + value: u8, ) { - unsafe { (*brightness_grid.as_ptr()).fill(value) }; + assert!(!brightness_grid.is_null()); + let brightness = servicepoint::Brightness::try_from(value) + .expect("invalid brightness value"); + (*brightness_grid).0.fill(brightness); } -/// Gets the width of the [BrightnessGrid] instance. +/// Gets the width of the [SPBrightnessGrid] instance. /// /// # Arguments /// /// - `brightness_grid`: instance to read from /// /// 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] pub unsafe extern "C" fn sp_brightness_grid_width( - brightness_grid: NonNull, + brightness_grid: *const SPBrightnessGrid, ) -> usize { - unsafe { brightness_grid.as_ref().width() } + assert!(!brightness_grid.is_null()); + (*brightness_grid).0.width() } -/// Gets the height of the [BrightnessGrid] instance. +/// Gets the height of the [SPBrightnessGrid] instance. /// /// # Arguments /// /// - `brightness_grid`: instance to read from /// /// 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] pub unsafe extern "C" fn sp_brightness_grid_height( - brightness_grid: NonNull, + brightness_grid: *const SPBrightnessGrid, ) -> usize { - unsafe { brightness_grid.as_ref().height() } + assert!(!brightness_grid.is_null()); + (*brightness_grid).0.height() } -/// Gets an unsafe reference to the data of the [BrightnessGrid] instance. -/// -/// The returned memory is valid for the lifetime of the brightness grid. +/// Gets an unsafe reference to the data of the [SPBrightnessGrid] instance. /// /// # Arguments /// /// - `brightness_grid`: instance to read from /// /// 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] pub unsafe extern "C" fn sp_brightness_grid_unsafe_data_ref( - brightness_grid: NonNull, -) -> ByteSlice { - //noinspection RsAssertEqual - const _: () = assert!(size_of::() == 1); - - let data = unsafe { (*brightness_grid.as_ptr()).data_ref_mut() }; - unsafe { - ByteSlice::from_slice(transmute::<&mut [Brightness], &mut [u8]>(data)) - } -} - -/// 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, - 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(), + brightness_grid: *mut SPBrightnessGrid, +) -> SPByteSlice { + assert!(!brightness_grid.is_null()); + assert_eq!(core::mem::size_of::(), 1); + let data = (*brightness_grid).0.data_ref_mut(); + // this assumes more about the memory layout than rust guarantees. yikes! + let data: &mut [u8] = transmute(data); + SPByteSlice { + start: NonNull::new(data.as_mut_ptr_range().start).unwrap(), + length: data.len(), } } diff --git a/src/byte_slice.rs b/src/byte_slice.rs index de7d0df..678a5d7 100644 --- a/src/byte_slice.rs +++ b/src/byte_slice.rs @@ -2,7 +2,10 @@ use std::ptr::NonNull; -/// Represents a span of memory (`&mut [u8]` ) as a struct. +#[repr(C)] +/// 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 /// @@ -11,29 +14,11 @@ use std::ptr::NonNull; /// - 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 function returning this type. -#[repr(C)] -pub struct ByteSlice { - /// The start address of the memory. +/// - an instance of this created from C is never passed to a consuming function, as the rust code +/// will try to free the memory of a potentially separate allocator. +pub struct SPByteSlice { + /// The start address of the memory pub start: NonNull, - /// The amount of memory in bytes. + /// The amount of memory in bytes 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(), - } - } -} diff --git a/src/char_grid.rs b/src/char_grid.rs index bfb2585..dfaf225 100644 --- a/src/char_grid.rs +++ b/src/char_grid.rs @@ -1,10 +1,16 @@ -use crate::{heap_drop, heap_move, heap_move_nonnull, heap_remove, ByteSlice}; -use servicepoint::{CharGrid, CharGridCommand, Grid, Origin, Packet}; +//! C functions for interacting with [SPCharGrid]s +//! +//! prefix `sp_char_grid_` + +use servicepoint::Grid; use std::ptr::NonNull; -/// Creates a new [CharGrid] with the specified dimensions. +/// A C-wrapper for grid containing UTF-8 characters. /// -/// returns: [CharGrid] initialized to 0. +/// As the rust [char] type is not FFI-safe, characters are passed in their UTF-32 form as 32bit unsigned integers. +/// +/// The encoding is enforced in most cases by the rust standard library +/// and will panic when provided with illegal characters. /// /// # Examples /// @@ -14,46 +20,115 @@ use std::ptr::NonNull; /// sp_char_grid_set(grid, 0, 0, '!'); /// 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] pub unsafe extern "C" fn sp_char_grid_new( width: usize, height: usize, -) -> NonNull { - heap_move_nonnull(CharGrid::new(width, height)) +) -> NonNull { + let result = + Box::new(SPCharGrid(servicepoint::CharGrid::new(width, height))); + NonNull::from(Box::leak(result)) } -/// Loads a [CharGrid] with the specified dimensions from the provided data. +/// Loads a [SPCharGrid] with the specified dimensions from the provided data. /// -/// returns: new CharGrid or NULL in case of an error +/// Will never return NULL. +/// +/// # Panics +/// +/// - when `data` is NULL +/// - when the provided `data_length` does not match `height` and `width` +/// - when `data` is not valid UTF-8 +/// +/// # Safety +/// +/// The caller has to make sure that: +/// +/// - `data` points to a valid memory location of at least `data_length` +/// bytes in size. +/// - the returned instance is freed in some way, either by using a consuming function or +/// by explicitly calling `sp_char_grid_free`. #[no_mangle] pub unsafe extern "C" fn sp_char_grid_load( width: usize, height: usize, - data: ByteSlice, -) -> *mut CharGrid { - let data = unsafe { data.as_slice() }; - if let Ok(grid) = CharGrid::load_utf8(width, height, data.to_vec()) { - heap_move(grid) - } else { - std::ptr::null_mut() - } + data: *const u8, + data_length: usize, +) -> NonNull { + assert!(data.is_null()); + let data = std::slice::from_raw_parts(data, data_length); + let result = Box::new(SPCharGrid( + servicepoint::CharGrid::load_utf8(width, height, data.to_vec()) + .unwrap(), + )); + NonNull::from(Box::leak(result)) } -/// Clones a [CharGrid]. +/// Clones a [SPCharGrid]. +/// +/// Will never return NULL. +/// +/// # Panics +/// +/// - when `char_grid` is NULL +/// +/// # Safety +/// +/// The caller has to make sure that: +/// +/// - `char_grid` points to a valid [SPCharGrid] +/// - `char_grid` is not written to concurrently +/// - the returned instance is freed in some way, either by using a consuming function or +/// by explicitly calling `sp_char_grid_free`. #[no_mangle] pub unsafe extern "C" fn sp_char_grid_clone( - char_grid: NonNull, -) -> NonNull { - heap_move_nonnull(unsafe { char_grid.as_ref().clone() }) + char_grid: *const SPCharGrid, +) -> NonNull { + assert!(!char_grid.is_null()); + let result = Box::new((*char_grid).clone()); + NonNull::from(Box::leak(result)) } -/// Deallocates a [CharGrid]. +/// Deallocates a [SPCharGrid]. +/// +/// # Panics +/// +/// - when `char_grid` is NULL +/// +/// # Safety +/// +/// The caller has to make sure that: +/// +/// - `char_grid` points to a valid [SPCharGrid] +/// - `char_grid` is not used concurrently or after char_grid call +/// - `char_grid` was not passed to another consuming function, e.g. to create a [SPCommand] +/// +/// [SPCommand]: [crate::SPCommand] #[no_mangle] -pub unsafe extern "C" fn sp_char_grid_free(char_grid: NonNull) { - unsafe { heap_drop(char_grid) } +pub unsafe extern "C" fn sp_char_grid_free(char_grid: *mut SPCharGrid) { + assert!(!char_grid.is_null()); + _ = Box::from_raw(char_grid); } -/// Returns the current value at the specified position. +/// Gets the current value at the specified position. /// /// # Arguments /// @@ -62,17 +137,26 @@ pub unsafe extern "C" fn sp_char_grid_free(char_grid: NonNull) { /// /// # Panics /// +/// - when `char_grid` is NULL /// - 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] pub unsafe extern "C" fn sp_char_grid_get( - char_grid: NonNull, + char_grid: *const SPCharGrid, x: usize, y: usize, ) -> u32 { - unsafe { char_grid.as_ref().get(x, y) as u32 } + assert!(!char_grid.is_null()); + (*char_grid).0.get(x, y) as u32 } -/// Sets the value of the specified position in the [CharGrid]. +/// Sets the value of the specified position in the [SPCharGrid]. /// /// # Arguments /// @@ -84,72 +168,96 @@ pub unsafe extern "C" fn sp_char_grid_get( /// /// # Panics /// +/// - when `char_grid` is NULL /// - 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] pub unsafe extern "C" fn sp_char_grid_set( - char_grid: NonNull, + char_grid: *mut SPCharGrid, x: usize, y: usize, value: u32, ) { - unsafe { (*char_grid.as_ptr()).set(x, y, char::from_u32(value).unwrap()) }; + assert!(!char_grid.is_null()); + (*char_grid).0.set(x, y, char::from_u32(value).unwrap()); } -/// Sets the value of all cells in the [CharGrid]. +/// Sets the value of all cells in the [SPCharGrid]. /// /// # Arguments /// /// - `char_grid`: instance to write 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] pub unsafe extern "C" fn sp_char_grid_fill( - char_grid: NonNull, + char_grid: *mut SPCharGrid, value: u32, ) { - unsafe { (*char_grid.as_ptr()).fill(char::from_u32(value).unwrap()) }; + assert!(!char_grid.is_null()); + (*char_grid).0.fill(char::from_u32(value).unwrap()); } -/// Gets the width of the [CharGrid] instance. +/// Gets the width of the [SPCharGrid] instance. /// /// # Arguments /// /// - `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] pub unsafe extern "C" fn sp_char_grid_width( - char_grid: NonNull, + char_grid: *const SPCharGrid, ) -> usize { - unsafe { char_grid.as_ref().width() } + assert!(!char_grid.is_null()); + (*char_grid).0.width() } -/// Gets the height of the [CharGrid] instance. +/// Gets the height of the [SPCharGrid] instance. /// /// # Arguments /// /// - `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] pub unsafe extern "C" fn sp_char_grid_height( - char_grid: NonNull, + char_grid: *const SPCharGrid, ) -> usize { - unsafe { char_grid.as_ref().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, - 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(), - } + assert!(!char_grid.is_null()); + (*char_grid).0.height() } diff --git a/src/command.rs b/src/command.rs new file mode 100644 index 0000000..f7e50ea --- /dev/null +++ b/src/command.rs @@ -0,0 +1,498 @@ +//! 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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); +} diff --git a/src/connection.rs b/src/connection.rs new file mode 100644 index 0000000..8b31243 --- /dev/null +++ b/src/connection.rs @@ -0,0 +1,139 @@ +//! C functions for interacting with [SPConnection]s +//! +//! prefix `sp_connection_` + +use std::ffi::{c_char, CStr}; +use std::ptr::{null_mut, NonNull}; + +use crate::{SPCommand, SPPacket}; + +/// A connection to the display. +/// +/// # Examples +/// +/// ```C +/// CConnection connection = sp_connection_open("172.23.42.29:2342"); +/// if (connection != NULL) +/// sp_connection_send_command(connection, sp_command_clear()); +/// ``` +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] +pub unsafe extern "C" fn sp_connection_open( + host: *const c_char, +) -> *mut SPConnection { + assert!(!host.is_null()); + let host = CStr::from_ptr(host).to_str().expect("Bad encoding"); + let connection = match servicepoint::Connection::open(host) { + Err(_) => return null_mut(), + Ok(value) => value, + }; + + Box::into_raw(Box::new(SPConnection(connection))) +} + +/// Creates a new instance of [SPConnection] for testing that does not actually send anything. +/// +/// returns: a new instance. 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_connection_free`. +#[no_mangle] +pub unsafe extern "C" fn sp_connection_fake() -> NonNull { + let result = Box::new(SPConnection(servicepoint::Connection::Fake)); + NonNull::from(Box::leak(result)) +} + +/// Sends a [SPPacket] to the display using the [SPConnection]. +/// +/// The passed `packet` gets consumed. +/// +/// 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] +pub unsafe extern "C" fn sp_connection_send_packet( + connection: *const SPConnection, + packet: *mut SPPacket, +) -> bool { + assert!(!connection.is_null()); + assert!(!packet.is_null()); + let packet = Box::from_raw(packet); + (*connection).0.send((*packet).0).is_ok() +} + +/// Sends a [SPCommand] to the display using the [SPConnection]. +/// +/// The passed `command` gets consumed. +/// +/// returns: true in case of success +/// +/// # Panics +/// +/// - when `connection` is NULL +/// - when `command` is NULL +/// +/// # 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] +pub unsafe extern "C" fn sp_connection_send_command( + connection: *const SPConnection, + command: *mut SPCommand, +) -> bool { + assert!(!connection.is_null()); + assert!(!command.is_null()); + let command = (*Box::from_raw(command)).0; + (*connection).0.send(command).is_ok() +} + +/// Closes and deallocates a [SPConnection]. +/// +/// # Panics +/// +/// - when `connection` is NULL +/// +/// # Safety +/// +/// The caller has to make sure that: +/// +/// - `connection` points to a valid [SPConnection] +/// - `connection` is not used concurrently or after this call +#[no_mangle] +pub unsafe extern "C" fn sp_connection_free(connection: *mut SPConnection) { + assert!(!connection.is_null()); + _ = Box::from_raw(connection); +} diff --git a/src/constants.rs b/src/constants.rs new file mode 100644 index 0000000..1a268f4 --- /dev/null +++ b/src/constants.rs @@ -0,0 +1,48 @@ +//! 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 for CompressionCode { + type Error = (); + + fn try_from(value: SPCompressionCode) -> Result { + CompressionCode::try_from(value as u16) + } +} diff --git a/src/cp437_grid.rs b/src/cp437_grid.rs index ff70d2b..9b366c8 100644 --- a/src/cp437_grid.rs +++ b/src/cp437_grid.rs @@ -1,48 +1,127 @@ -use crate::{heap_drop, heap_move, heap_move_nonnull, heap_remove, ByteSlice}; -use servicepoint::{ - Cp437Grid, Cp437GridCommand, DataRef, Grid, Origin, Packet, -}; +//! C functions for interacting with [SPCp437Grid]s +//! +//! prefix `sp_cp437_grid_` + +use crate::SPByteSlice; +use servicepoint::{DataRef, Grid}; use std::ptr::NonNull; -/// Creates a new [Cp437Grid] with the specified dimensions. +/// A C-wrapper for grid containing codepage 437 characters. /// -/// returns: [Cp437Grid] initialized to 0. +/// The encoding is currently not enforced. +/// +/// # Examples +/// +/// ```C +/// Cp437Grid grid = sp_cp437_grid_new(4, 3); +/// sp_cp437_grid_fill(grid, '?'); +/// sp_cp437_grid_set(grid, 0, 0, '!'); +/// sp_cp437_grid_free(grid); +/// ``` +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] pub unsafe extern "C" fn sp_cp437_grid_new( width: usize, height: usize, -) -> NonNull { - heap_move_nonnull(Cp437Grid::new(width, height)) +) -> NonNull { + let result = + Box::new(SPCp437Grid(servicepoint::Cp437Grid::new(width, height))); + NonNull::from(Box::leak(result)) } -/// Loads a [Cp437Grid] with the specified dimensions from the provided data. +/// Loads a [SPCp437Grid] with the specified dimensions from the provided data. +/// +/// Will never return NULL. +/// +/// # Panics +/// +/// - when `data` is NULL +/// - when the provided `data_length` does not match `height` and `width` +/// +/// # Safety +/// +/// The caller has to make sure that: +/// +/// - `data` points to a valid memory location of at least `data_length` +/// bytes in size. +/// - the returned instance is freed in some way, either by using a consuming function or +/// by explicitly calling `sp_cp437_grid_free`. #[no_mangle] pub unsafe extern "C" fn sp_cp437_grid_load( width: usize, height: usize, - data: ByteSlice, -) -> *mut Cp437Grid { - let data = unsafe { data.as_slice() }; - let grid = Cp437Grid::load(width, height, data); - if let Some(grid) = grid { - heap_move(grid) - } else { - std::ptr::null_mut() - } + data: *const u8, + data_length: usize, +) -> NonNull { + assert!(data.is_null()); + let data = std::slice::from_raw_parts(data, data_length); + let result = Box::new(SPCp437Grid(servicepoint::Cp437Grid::load( + width, height, data, + ))); + NonNull::from(Box::leak(result)) } -/// Clones a [Cp437Grid]. +/// Clones a [SPCp437Grid]. +/// +/// Will never return NULL. +/// +/// # Panics +/// +/// - when `cp437_grid` is NULL +/// +/// # Safety +/// +/// The caller has to make sure that: +/// +/// - `cp437_grid` points to a valid [SPCp437Grid] +/// - `cp437_grid` is not written to concurrently +/// - the returned instance is freed in some way, either by using a consuming function or +/// by explicitly calling `sp_cp437_grid_free`. #[no_mangle] pub unsafe extern "C" fn sp_cp437_grid_clone( - cp437_grid: NonNull, -) -> NonNull { - heap_move_nonnull(unsafe { cp437_grid.as_ref().clone() }) + cp437_grid: *const SPCp437Grid, +) -> NonNull { + assert!(!cp437_grid.is_null()); + let result = Box::new((*cp437_grid).clone()); + NonNull::from(Box::leak(result)) } -/// Deallocates a [Cp437Grid]. +/// Deallocates a [SPCp437Grid]. +/// +/// # Panics +/// +/// - when `cp437_grid` is NULL +/// +/// # Safety +/// +/// The caller has to make sure that: +/// +/// - `cp437_grid` points to a valid [SPCp437Grid] +/// - `cp437_grid` is not used concurrently or after cp437_grid call +/// - `cp437_grid` was not passed to another consuming function, e.g. to create a [SPCommand] +/// +/// [SPCommand]: [crate::SPCommand] #[no_mangle] -pub unsafe extern "C" fn sp_cp437_grid_free(cp437_grid: NonNull) { - unsafe { heap_drop(cp437_grid) } +pub unsafe extern "C" fn sp_cp437_grid_free(cp437_grid: *mut SPCp437Grid) { + assert!(!cp437_grid.is_null()); + _ = Box::from_raw(cp437_grid); } /// Gets the current value at the specified position. @@ -54,17 +133,26 @@ pub unsafe extern "C" fn sp_cp437_grid_free(cp437_grid: NonNull) { /// /// # Panics /// +/// - when `cp437_grid` is NULL /// - 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] pub unsafe extern "C" fn sp_cp437_grid_get( - cp437_grid: NonNull, + cp437_grid: *const SPCp437Grid, x: usize, y: usize, ) -> u8 { - unsafe { cp437_grid.as_ref().get(x, y) } + assert!(!cp437_grid.is_null()); + (*cp437_grid).0.get(x, y) } -/// Sets the value of the specified position in the [Cp437Grid]. +/// Sets the value of the specified position in the [SPCp437Grid]. /// /// # Arguments /// @@ -76,82 +164,122 @@ pub unsafe extern "C" fn sp_cp437_grid_get( /// /// # Panics /// +/// - when `cp437_grid` is NULL /// - 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] pub unsafe extern "C" fn sp_cp437_grid_set( - cp437_grid: NonNull, + cp437_grid: *mut SPCp437Grid, x: usize, y: usize, value: u8, ) { - unsafe { (*cp437_grid.as_ptr()).set(x, y, value) }; + assert!(!cp437_grid.is_null()); + (*cp437_grid).0.set(x, y, value); } -/// Sets the value of all cells in the [Cp437Grid]. +/// Sets the value of all cells in the [SPCp437Grid]. /// /// # Arguments /// /// - `cp437_grid`: instance to write 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] pub unsafe extern "C" fn sp_cp437_grid_fill( - cp437_grid: NonNull, + cp437_grid: *mut SPCp437Grid, value: u8, ) { - unsafe { (*cp437_grid.as_ptr()).fill(value) }; + assert!(!cp437_grid.is_null()); + (*cp437_grid).0.fill(value); } -/// Gets the width of the [Cp437Grid] instance. +/// Gets the width of the [SPCp437Grid] instance. /// /// # Arguments /// /// - `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] pub unsafe extern "C" fn sp_cp437_grid_width( - cp437_grid: NonNull, + cp437_grid: *const SPCp437Grid, ) -> usize { - unsafe { cp437_grid.as_ref().width() } + assert!(!cp437_grid.is_null()); + (*cp437_grid).0.width() } -/// Gets the height of the [Cp437Grid] instance. +/// Gets the height of the [SPCp437Grid] instance. /// /// # Arguments /// /// - `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] pub unsafe extern "C" fn sp_cp437_grid_height( - cp437_grid: NonNull, + cp437_grid: *const SPCp437Grid, ) -> usize { - unsafe { cp437_grid.as_ref().height() } + assert!(!cp437_grid.is_null()); + (*cp437_grid).0.height() } -/// Gets an unsafe reference to the data of the [Cp437Grid] instance. +/// Gets an unsafe reference to the data of the [SPCp437Grid] instance. /// -/// The returned memory is valid for the lifetime of the grid. +/// Will never return NULL. +/// +/// # Panics +/// +/// - when `cp437_grid` is NULL +/// +/// ## Safety +/// +/// The caller has to make sure that: +/// +/// - `cp437_grid` points to a valid [SPCp437Grid] +/// - the returned memory range is never accessed after the passed [SPCp437Grid] has been freed +/// - the returned memory range is never accessed concurrently, either via the [SPCp437Grid] or directly #[no_mangle] pub unsafe extern "C" fn sp_cp437_grid_unsafe_data_ref( - cp437_grid: NonNull, -) -> ByteSlice { - unsafe { ByteSlice::from_slice((*cp437_grid.as_ptr()).data_ref_mut()) } -} - -/// 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, - 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(), + cp437_grid: *mut SPCp437Grid, +) -> SPByteSlice { + let data = (*cp437_grid).0.data_ref_mut(); + SPByteSlice { + start: NonNull::new(data.as_mut_ptr_range().start).unwrap(), + length: data.len(), } } diff --git a/src/lib.rs b/src/lib.rs index 60c42ab..887fb40 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,18 +9,18 @@ //! #include "servicepoint.h" //! //! int main(void) { -//! UdpConnection *connection = sp_udp_open("172.23.42.29:2342"); +//! SPConnection *connection = sp_connection_open("172.23.42.29:2342"); //! if (connection == NULL) //! return 1; //! -//! Bitmap *pixels = sp_bitmap_new(SP_PIXEL_WIDTH, SP_PIXEL_HEIGHT); +//! SPBitmap *pixels = sp_bitmap_new(SP_PIXEL_WIDTH, SP_PIXEL_HEIGHT); //! sp_bitmap_fill(pixels, true); //! -//! TypedCommand *command = sp_command_bitmap_linear_win(0, 0, pixels, Uncompressed); -//! while (sp_udp_send_command(connection, sp_command_clone(command))); +//! SPCommand *command = sp_command_bitmap_linear_win(0, 0, pixels, Uncompressed); +//! while (sp_connection_send_command(connection, sp_command_clone(command))); //! //! sp_command_free(command); -//! sp_udp_free(connection); +//! sp_connection_free(connection); //! return 0; //! } //! ``` @@ -30,43 +30,19 @@ 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::connection::*; +pub use crate::constants::*; pub use crate::cp437_grid::*; pub use crate::packet::*; -pub use crate::typed_command::*; -pub use crate::udp::*; -pub use servicepoint::CommandCode; -use std::ptr::NonNull; mod bitmap; mod bitvec; mod brightness_grid; mod byte_slice; mod char_grid; +mod command; +mod connection; +mod constants; mod cp437_grid; mod packet; -mod typed_command; -mod udp; - -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(x: T) -> *mut T { - Box::into_raw(Box::new(x)) -} - -pub(crate) fn heap_move_nonnull(x: T) -> NonNull { - NonNull::from(Box::leak(Box::new(x))) -} - -pub(crate) unsafe fn heap_drop(x: NonNull) { - drop(unsafe { heap_remove(x) }); -} - -pub(crate) unsafe fn heap_remove(x: NonNull) -> T { - unsafe { *Box::from_raw(x.as_ptr()) } -} - -/// This is a type only used by cbindgen to have a type for pointers. -pub struct UdpSocket; diff --git a/src/packet.rs b/src/packet.rs index d8b1579..9293a8a 100644 --- a/src/packet.rs +++ b/src/packet.rs @@ -1,128 +1,166 @@ -use crate::{heap_drop, heap_move, heap_move_nonnull, heap_remove, ByteSlice}; -use servicepoint::{CommandCode, Header, Packet, TypedCommand}; -use std::ptr::NonNull; +//! C functions for interacting with [SPPacket]s +//! +//! prefix `sp_packet_` -/// Turns a [TypedCommand] into a [Packet]. -/// The [TypedCommand] gets consumed. +use std::ptr::{null_mut, NonNull}; + +use crate::SPCommand; + +/// The raw packet +pub struct SPPacket(pub(crate) servicepoint::Packet); + +/// Turns a [SPCommand] into a [SPPacket]. +/// The [SPCommand] gets consumed. /// -/// Returns NULL in case of an error. -#[no_mangle] -pub unsafe extern "C" fn sp_packet_from_command( - command: NonNull, -) -> *mut Packet { - let command = unsafe { heap_remove(command) }; - if let Ok(packet) = command.try_into() { - heap_move(packet) - } else { - std::ptr::null_mut() - } -} - -/// Tries to load a [Packet] from the passed array with the specified length. -/// -/// returns: NULL in case of an error, pointer to the allocated packet otherwise -#[no_mangle] -pub unsafe extern "C" fn sp_packet_try_load(data: ByteSlice) -> *mut Packet { - let data = unsafe { data.as_slice() }; - match servicepoint::Packet::try_from(data) { - Err(_) => std::ptr::null_mut(), - Ok(packet) => heap_move(packet), - } -} - -/// Creates a raw [Packet] from parts. -/// -/// returns: new instance. Will never return null. -#[no_mangle] -pub unsafe extern "C" fn sp_packet_from_parts( - header: Header, - payload: *const ByteSlice, -) -> NonNull { - let payload = if payload.is_null() { - vec![] - } else { - let payload = unsafe { (*payload).as_slice() }; - Vec::from(payload) - }; - - heap_move_nonnull(Packet { header, payload }) -} - -/// Returns a pointer to the header field of the provided packet. -/// -/// The returned header can be changed and will be valid for the lifetime of the packet. -#[no_mangle] -pub unsafe extern "C" fn sp_packet_get_header( - packet: NonNull, -) -> NonNull
{ - 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, -) -> 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, - data: ByteSlice, -) { - unsafe { (*packet.as_ptr()).payload = data.as_slice().to_vec() } -} - -/// Serialize the packet into the provided buffer. +/// Will never return NULL. /// /// # Panics /// -/// - if the buffer is not big enough to hold header+payload. +/// - 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] -pub unsafe extern "C" fn sp_packet_serialize_to( - packet: NonNull, - mut buffer: ByteSlice, -) { - unsafe { - packet.as_ref().serialize_to(buffer.as_slice_mut()); +pub unsafe extern "C" fn sp_packet_from_command( + command: *mut SPCommand, +) -> NonNull { + assert!(!command.is_null()); + let command = *Box::from_raw(command); + let result = Box::new(SPPacket(command.0.into())); + NonNull::from(Box::leak(result)) +} + +/// 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))), } } -/// Clones a [Packet]. +/// Creates a raw [SPPacket] from parts. +/// +/// # Arguments +/// +/// - `command_code` specifies which command this packet contains +/// - `a`, `b`, `c` and `d` are command-specific header values +/// - `payload` is the optional data that is part of the command +/// - `payload_len` is the size of the payload +/// +/// returns: new instance. Will never return null. +/// +/// # 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] +pub unsafe extern "C" fn sp_packet_from_parts( + command_code: u16, + a: u16, + b: u16, + c: u16, + d: u16, + payload: *const u8, + payload_len: usize, +) -> NonNull { + assert_eq!(payload.is_null(), payload_len == 0); + + let payload = if payload.is_null() { + vec![] + } else { + let payload = std::slice::from_raw_parts(payload, payload_len); + Vec::from(payload) + }; + + let packet = servicepoint::Packet { + header: servicepoint::Header { + command_code, + a, + b, + c, + d, + }, + payload, + }; + let result = Box::new(SPPacket(packet)); + NonNull::from(Box::leak(result)) +} + +/// Clones a [SPPacket]. +/// +/// Will never return NULL. +/// +/// # Panics +/// +/// - when `packet` is NULL +/// +/// # Safety +/// +/// The caller has to make sure that: +/// +/// - `packet` points to a valid [SPPacket] +/// - `packet` is not written to concurrently +/// - the returned instance is freed in some way, either by using a consuming function or +/// by explicitly calling `sp_packet_free`. #[no_mangle] pub unsafe extern "C" fn sp_packet_clone( - packet: NonNull, -) -> NonNull { - heap_move_nonnull(unsafe { packet.as_ref().clone() }) + packet: *const SPPacket, +) -> NonNull { + assert!(!packet.is_null()); + let result = Box::new(SPPacket((*packet).0.clone())); + NonNull::from(Box::leak(result)) } -/// Deallocates a [Packet]. -#[no_mangle] -pub unsafe extern "C" fn sp_packet_free(packet: NonNull) { - unsafe { heap_drop(packet) } -} - -/// Converts u16 into [CommandCode]. +/// Deallocates a [SPPacket]. /// -/// If the provided value is not valid, false is returned and result is not changed. +/// # 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] -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, - } +pub unsafe extern "C" fn sp_packet_free(packet: *mut SPPacket) { + assert!(!packet.is_null()); + _ = Box::from_raw(packet) } diff --git a/src/typed_command.rs b/src/typed_command.rs deleted file mode 100644 index 9e27540..0000000 --- a/src/typed_command.rs +++ /dev/null @@ -1,201 +0,0 @@ -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, -) -> *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, -) -> NonNull { - 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 { - 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 { - 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 { - 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 { - 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, -) -> NonNull { - 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, - 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, -) -> NonNull { - 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, -) -> NonNull { - 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, - 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) { - unsafe { heap_drop(command) } -} diff --git a/src/udp.rs b/src/udp.rs deleted file mode 100644 index f68aa16..0000000 --- a/src/udp.rs +++ /dev/null @@ -1,119 +0,0 @@ -use crate::{heap_drop, heap_move, heap_remove}; -use servicepoint::{Header, Packet, TypedCommand, UdpSocketExt}; -use std::ffi::{c_char, CStr}; -use std::net::{Ipv4Addr, SocketAddrV4, UdpSocket}; -use std::ptr::NonNull; - -/// Creates a new instance of [UdpConnection]. -/// -/// returns: NULL if connection fails, or connected instance -/// -/// # Examples -/// -/// ```C -/// UdpConnection connection = sp_udp_open("172.23.42.29:2342"); -/// if (connection != NULL) -/// sp_udp_send_command(connection, sp_command_clear()); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn sp_udp_open(host: NonNull) -> *mut UdpSocket { - let host = unsafe { CStr::from_ptr(host.as_ptr()) } - .to_str() - .expect("Bad encoding"); - let connection = match UdpSocket::bind_connect(host) { - Err(_) => return std::ptr::null_mut(), - Ok(value) => value, - }; - - heap_move(connection) -} - -/// Creates a new instance of [UdpConnection]. -/// -/// returns: NULL if connection fails, or connected instance -/// -/// # Examples -/// -/// ```C -/// UdpConnection connection = sp_udp_open_ipv4(172, 23, 42, 29, 2342); -/// if (connection != NULL) -/// sp_udp_send_command(connection, sp_command_clear()); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn sp_udp_open_ipv4( - ip1: u8, - ip2: u8, - ip3: u8, - ip4: u8, - port: u16, -) -> *mut UdpSocket { - let addr = SocketAddrV4::new(Ipv4Addr::from([ip1, ip2, ip3, ip4]), port); - let connection = match UdpSocket::bind_connect(addr) { - Err(_) => return std::ptr::null_mut(), - Ok(value) => value, - }; - heap_move(connection) -} - -/// Sends a [Packet] to the display using the [UdpConnection]. -/// -/// The passed `packet` gets consumed. -/// -/// returns: true in case of success -#[no_mangle] -pub unsafe extern "C" fn sp_udp_send_packet( - connection: NonNull, - packet: NonNull, -) -> bool { - let packet = unsafe { heap_remove(packet) }; - unsafe { connection.as_ref().send(&Vec::from(packet)) }.is_ok() -} - -/// Sends a [TypedCommand] to the display using the [UdpConnection]. -/// -/// The passed `command` gets consumed. -/// -/// returns: true in case of success -/// -/// # Examples -/// -/// ```C -/// sp_udp_send_command(connection, sp_command_brightness(5)); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn sp_udp_send_command( - connection: NonNull, - command: NonNull, -) -> bool { - let command = unsafe { heap_remove(command) }; - unsafe { connection.as_ref().send_command(command) }.is_some() -} - -/// 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, - header: Header, -) -> bool { - let packet = Packet { - header, - payload: vec![], - }; - unsafe { udp_connection.as_ref() } - .send(&Vec::from(packet)) - .is_ok() -} - -/// Closes and deallocates a [UdpConnection]. -#[no_mangle] -pub unsafe extern "C" fn sp_udp_free(connection: NonNull) { - unsafe { heap_drop(connection) } -}