Repository: Nukesor/pueue (language: rust)

Issue:
[Debt] Move away from the `serde_cbor` crate
The `cbor` crate has been officially archived.

The owner suggests to move to either of these alternatives:
- https://crates.io/crates/ciborium
- https://crates.io/crates/minicbor


# Ciborium

[According to the author](https://github.com/enarx/ciborium#compatibility-with-other-implementations), this one is compatible with `serde_cbor` and could thereby be used as a drop-in replacement.

# Minicbor

This one is, as the name suggests, rather minimalistic. It might be enough for our use case, but we don't really aim for no-std environments and we also don't have to be minimalistic.


Discussion / hints:
After leaving this one alone for a while, `ciborium` seems to be kind of abandoned as well... 

We should take a look at minicbor
@Nukesor what about [binrw](https://github.com/jam1garner/binrw.git)?
It gives smooth transitions from binary struct serialization into `io::Cursor` compatible data kinds. 
Changing the format would introduce a breaking change.
This is a possibility, but something I would only want to consider if the data format in question has superior capabilities to the current one.

I chose cbor specifically to allow forward-compatible and in parts backward-compatible changes in the protocol's schema, such as adding and removing fields, without the need of API versioning and an version negotiation layer. Many binary formats don't support this. As far as I can see there's no info in `binrw`'s description about such capabilities.

This ticket isn't an urgent one.
The current implementation works flawlessly and there's no urgent need to switch a library that could be somewhat considered "finished". If we find a good and maintained drop-in replacement, I would be happy, otherwise we just roll with the current library until we have to switch.
I'll close this for the time being, until this becomes a problem :) 

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5c0185e3..6058b875 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -78,6 +78,7 @@ Upon updating Pueue and restarting the daemon, the previous state will be wiped,
 - Revisited, fixed and cleaned up CLI help texts.
 - Print most of Pueue's info/log messages to `stderr`. Only keep useful stuff like json and task log output on `stdout`.
 - **Breaking**: Ported from `anyhow` to `color_eyre` for prettier log output.
+- **Breaking**: Switch `cbor` handling library, potentially breaking backwards-compatible communication on a data format level.
 
 ### Add
 
diff --git a/Cargo.lock b/Cargo.lock
index 4137b985..abc42f14 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -274,6 +274,33 @@ dependencies = [
  "windows-targets 0.52.6",
 ]
 
+[[package]]
+name = "ciborium"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
+dependencies = [
+ "ciborium-io",
+ "ciborium-ll",
+ "serde",
+]
+
+[[package]]
+name = "ciborium-io"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
+
+[[package]]
+name = "ciborium-ll"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
+dependencies = [
+ "ciborium-io",
+ "half",
+]
+
 [[package]]
 name = "clang-sys"
 version = "1.8.1"
@@ -449,6 +476,12 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "crunchy"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
+
 [[package]]
 name = "crypto-common"
 version = "0.1.6"
@@ -676,9 +709,13 @@ checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
 
 [[package]]
 name = "half"
-version = "1.8.3"
+version = "2.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403"
+checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
+dependencies = [
+ "cfg-if",
+ "crunchy",
+]
 
 [[package]]
 name = "handlebars"
@@ -1273,6 +1310,7 @@ dependencies = [
  "better-panic",
  "byteorder",
  "chrono",
+ "ciborium",
  "color-eyre",
  "dirs 6.0.0",
  "portpicker",
@@ -1283,7 +1321,6 @@ dependencies = [
  "rustls",
  "rustls-pemfile",
  "serde",
- "serde_cbor",
  "serde_json",
  "serde_yaml",
  "shellexpand",
@@ -1592,16 +1629,6 @@ dependencies = [
  "serde_derive",
 ]
 
-[[package]]
-name = "serde_cbor"
-version = "0.11.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5"
-dependencies = [
- "half",
- "serde",
-]
-
 [[package]]
 name = "serde_derive"
 version = "1.0.217"
diff --git a/pueue_lib/Cargo.toml b/pueue_lib/Cargo.toml
index b0a68bb4..d86a4e3b 100644
--- a/pueue_lib/Cargo.toml
+++ b/pueue_lib/Cargo.toml
@@ -21,6 +21,7 @@ client = ["dep:color-eyre"]
 async-trait = "0.1"
 byteorder = "1.5"
 chrono.workspace = true
+ciborium = { version = "0.2", features = ["std"] }
 color-eyre = { workspace = true, optional = true }
 dirs = "6.0"
 rand = "0.8"
@@ -34,7 +35,6 @@ rustls = { version = "0.23", features = [
 ], default-features = false }
 rustls-pemfile = "2"
 serde.workspace = true
-serde_cbor = "0.11"
 serde_json.workspace = true
 serde_yaml = "0.9"
 shellexpand = "3.1"
diff --git a/pueue_lib/src/error.rs b/pueue_lib/src/error.rs
index daf665f2..c4766dc8 100644
--- a/pueue_lib/src/error.rs
+++ b/pueue_lib/src/error.rs
@@ -1,6 +1,6 @@
 use std::path::PathBuf;
 
-use serde_cbor::Value;
+use ciborium::Value;
 
 #[derive(thiserror::Error, Debug)]
 pub enum Error {
diff --git a/pueue_lib/src/network/mod.rs b/pueue_lib/src/network/mod.rs
index 992f912d..2e980b16 100644
--- a/pueue_lib/src/network/mod.rs
+++ b/pueue_lib/src/network/mod.rs
@@ -25,7 +25,7 @@
 //! These have additional wrappers for [`Request`](crate::Request) and
 //! [`Response`](crate::Response) with [`send_request`] and [`receive_response`].
 //!
-//! The serialization/deserialization format that's used by `pueue_lib` is [`cbor`](::serde_cbor).
+//! The serialization/deserialization format that's used by `pueue_lib` is [`cbor`](::ciborium).
 //!
 //! ## Protocol
 //!
diff --git a/pueue_lib/src/network/protocol.rs b/pueue_lib/src/network/protocol.rs
index c5e9cc21..55fe5f6a 100644
--- a/pueue_lib/src/network/protocol.rs
+++ b/pueue_lib/src/network/protocol.rs
@@ -1,8 +1,8 @@
 use std::io::Cursor;
 
 use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
+use ciborium::{from_reader, into_writer};
 use serde::{de::DeserializeOwned, Serialize};
-use serde_cbor::{de::from_slice, ser::to_vec};
 use tokio::io::{AsyncReadExt, AsyncWriteExt};
 
 // Reexport all stream/socket related stuff for convenience purposes
@@ -49,7 +49,9 @@ where
     let message: T = message.into();
     debug!("Sending message: {message:#?}",);
     // Prepare command for transfer and determine message byte size
-    let payload = to_vec(&message).map_err(|err| Error::MessageSerialization(err.to_string()))?;
+    let mut payload = Vec::new();
+    into_writer(&message, &mut payload)
+        .map_err(|err| Error::MessageSerialization(err.to_string()))?;
 
     send_bytes(&payload, stream).await
 }
@@ -158,11 +160,11 @@ pub async fn receive_message<T: DeserializeOwned + std::fmt::Debug>(
     }
 
     // Deserialize the message.
-    let message: T = from_slice(&payload_bytes).map_err(|err| {
+    let message: T = from_reader(payload_bytes.as_slice()).map_err(|err| {
         // In the case of an error, try to deserialize it to a generic cbor Value.
         // That way we know whether the payload was corrupted or maybe just unexpected due to
         // version differences.
-        if let Ok(value) = from_slice::<serde_cbor::Value>(&payload_bytes) {
+        if let Ok(value) = from_reader::<ciborium::Value, _>(payload_bytes.as_slice()) {
             Error::UnexpectedPayload(value)
         } else {
             Error::MessageDeserialization(err.to_string())
@@ -212,7 +214,8 @@ mod test {
             input: payload,
         }
         .into();
-        let original_bytes = to_vec(&request).expect("Failed to serialize message.");
+        let mut original_bytes = Vec::new();
+        into_writer(&request, &mut original_bytes).expect("Failed to serialize message.");
 
         let listener: GenericListener = Box::new(listener);
 
@@ -224,7 +227,7 @@ mod test {
             let mut stream = listener.accept().await.unwrap();
             let message_bytes = receive_bytes(&mut stream).await.unwrap();
 
-            let message: Request = from_slice(&message_bytes).unwrap();
+            let message: Request = from_reader(message_bytes.as_slice()).unwrap();
 
             send_request(message, &mut stream).await.unwrap();
         });
@@ -234,7 +237,7 @@ mod test {
         // Create a client that sends a message and instantly receives it
         send_request(request, &mut client).await?;
         let response_bytes = receive_bytes(&mut client).await?;
-        let _message: Request = from_slice(&response_bytes)
+        let _message: Request = from_reader(response_bytes.as_slice())
             .map_err(|err| Error::MessageDeserialization(err.to_string()))?;
 
         assert_eq!(response_bytes, original_bytes);


Test patch:
diff --git a/pueue_lib/tests/tls_socket.rs b/pueue_lib/tests/tls_socket.rs
index 2e6766dc..03aaac6b 100644
--- a/pueue_lib/tests/tls_socket.rs
+++ b/pueue_lib/tests/tls_socket.rs
@@ -1,7 +1,7 @@
+use ciborium::{from_reader, into_writer};
 use color_eyre::Result;
 use pretty_assertions::assert_eq;
 use pueue_lib::network::{certificate::create_certificates, message::*, protocol::*};
-use serde_cbor::{de::from_slice, ser::to_vec};
 use tokio::task;
 
 mod helper;
@@ -17,7 +17,8 @@ async fn test_tls_socket() -> Result<()> {
 
     let listener = get_listener(&shared_settings).await.unwrap();
     let message = Request::Status;
-    let original_bytes = to_vec(&message).expect("Failed to serialize message.");
+    let mut original_bytes = Vec::new();
+    into_writer(&message, &mut original_bytes).expect("Failed to serialize message.");
 
     // Spawn a sub thread that:
     // 1. Accepts a new connection
@@ -27,7 +28,7 @@ async fn test_tls_socket() -> Result<()> {
         let mut stream = listener.accept().await.unwrap();
         let message_bytes = receive_bytes(&mut stream).await.unwrap();
 
-        let message: Request = from_slice(&message_bytes).unwrap();
+        let message: Request = from_reader(message_bytes.as_slice()).unwrap();
 
         send_request(message, &mut stream).await.unwrap();
     });
@@ -37,7 +38,7 @@ async fn test_tls_socket() -> Result<()> {
     // Create a client that sends a message and instantly receives it
     send_request(message, &mut client).await.unwrap();
     let response_bytes = receive_bytes(&mut client).await.unwrap();
-    let _message: Request = from_slice(&response_bytes).unwrap();
+    let _message: Request = from_reader(response_bytes.as_slice()).unwrap();
 
     assert_eq!(response_bytes, original_bytes);
 
diff --git a/pueue_lib/tests/unix_socket.rs b/pueue_lib/tests/unix_socket.rs
index 20fcb254..707bd795 100644
--- a/pueue_lib/tests/unix_socket.rs
+++ b/pueue_lib/tests/unix_socket.rs
@@ -3,10 +3,10 @@ mod helper;
 
 #[cfg(not(target_os = "windows"))]
 mod tests {
+    use ciborium::{from_reader, into_writer};
     use color_eyre::Result;
     use pretty_assertions::assert_eq;
     use pueue_lib::network::{message::*, protocol::*};
-    use serde_cbor::{de::from_slice, ser::to_vec};
     use tokio::task;
 
     use super::*;
@@ -19,7 +19,9 @@ mod tests {
 
         let listener = get_listener(&shared_settings).await?;
         let message = Request::Status;
-        let original_bytes = to_vec(&message).expect("Failed to serialize message.");
+
+        let mut original_bytes = Vec::new();
+        into_writer(&message, &mut original_bytes).expect("Failed to serialize message.");
 
         // Spawn a sub thread that:
         // 1. Accepts a new connection
@@ -29,7 +31,7 @@ mod tests {
             let mut stream = listener.accept().await.unwrap();
             let message_bytes = receive_bytes(&mut stream).await.unwrap();
 
-            let message: Request = from_slice(&message_bytes).unwrap();
+            let message: Request = from_reader(message_bytes.as_slice()).unwrap();
 
             send_request(message, &mut stream).await.unwrap();
         });
@@ -39,7 +41,7 @@ mod tests {
         // Create a client that sends a message and instantly receives it
         send_request(message, &mut client).await?;
         let response_bytes = receive_bytes(&mut client).await?;
-        let _message: Request = from_slice(&response_bytes)?;
+        let _message: Request = from_reader(response_bytes.as_slice())?;
 
         assert_eq!(response_bytes, original_bytes);