add websocket binary message protocol
This commit is contained in:
		
							parent
							
								
									f434b5bf83
								
							
						
					
					
						commit
						c7764c49e1
					
				
					 3 changed files with 301 additions and 16 deletions
				
			
		
							
								
								
									
										151
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										151
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							|  | @ -84,12 +84,27 @@ dependencies = [ | |||
|  "wyz", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "block-buffer" | ||||
| version = "0.10.4" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" | ||||
| dependencies = [ | ||||
|  "generic-array", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "byteorder" | ||||
| version = "1.5.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "bytes" | ||||
| version = "1.7.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "bzip2" | ||||
| version = "0.4.4" | ||||
|  | @ -193,6 +208,15 @@ version = "1.0.2" | |||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "cpufeatures" | ||||
| version = "0.2.14" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" | ||||
| dependencies = [ | ||||
|  "libc", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "crc32fast" | ||||
| version = "1.4.2" | ||||
|  | @ -202,6 +226,16 @@ dependencies = [ | |||
|  "cfg-if", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "crypto-common" | ||||
| version = "0.1.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" | ||||
| dependencies = [ | ||||
|  "generic-array", | ||||
|  "typenum", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "csbindgen" | ||||
| version = "1.9.3" | ||||
|  | @ -212,6 +246,22 @@ dependencies = [ | |||
|  "syn", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "data-encoding" | ||||
| version = "2.6.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "digest" | ||||
| version = "0.10.7" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" | ||||
| dependencies = [ | ||||
|  "block-buffer", | ||||
|  "crypto-common", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "equivalent" | ||||
| version = "1.0.1" | ||||
|  | @ -244,12 +294,28 @@ dependencies = [ | |||
|  "miniz_oxide", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "fnv" | ||||
| version = "1.0.7" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "funty" | ||||
| version = "2.0.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "generic-array" | ||||
| version = "0.14.7" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" | ||||
| dependencies = [ | ||||
|  "typenum", | ||||
|  "version_check", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "getrandom" | ||||
| version = "0.2.15" | ||||
|  | @ -279,6 +345,23 @@ version = "0.5.0" | |||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "http" | ||||
| version = "1.1.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" | ||||
| dependencies = [ | ||||
|  "bytes", | ||||
|  "fnv", | ||||
|  "itoa", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "httparse" | ||||
| version = "1.9.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "indexmap" | ||||
| version = "2.6.0" | ||||
|  | @ -536,6 +619,7 @@ dependencies = [ | |||
|  "log", | ||||
|  "rand", | ||||
|  "rust-lzma", | ||||
|  "tungstenite", | ||||
|  "zstd", | ||||
| ] | ||||
| 
 | ||||
|  | @ -556,6 +640,17 @@ dependencies = [ | |||
|  "servicepoint_binding_c", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "sha1" | ||||
| version = "0.10.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" | ||||
| dependencies = [ | ||||
|  "cfg-if", | ||||
|  "cpufeatures", | ||||
|  "digest", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "shlex" | ||||
| version = "1.3.0" | ||||
|  | @ -598,6 +693,26 @@ dependencies = [ | |||
|  "windows-sys 0.59.0", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "thiserror" | ||||
| version = "1.0.64" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" | ||||
| dependencies = [ | ||||
|  "thiserror-impl", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "thiserror-impl" | ||||
| version = "1.0.64" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "toml" | ||||
| version = "0.8.19" | ||||
|  | @ -632,12 +747,42 @@ dependencies = [ | |||
|  "winnow", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "tungstenite" | ||||
| version = "0.24.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" | ||||
| dependencies = [ | ||||
|  "byteorder", | ||||
|  "bytes", | ||||
|  "data-encoding", | ||||
|  "http", | ||||
|  "httparse", | ||||
|  "log", | ||||
|  "rand", | ||||
|  "sha1", | ||||
|  "thiserror", | ||||
|  "utf-8", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "typenum" | ||||
| version = "1.17.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "unicode-ident" | ||||
| version = "1.0.13" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "utf-8" | ||||
| version = "0.7.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "utf8parse" | ||||
| version = "0.2.2" | ||||
|  | @ -650,6 +795,12 @@ version = "0.2.15" | |||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "version_check" | ||||
| version = "0.9.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "wasi" | ||||
| version = "0.11.0+wasi-snapshot-preview1" | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ bzip2 = { version = "0.4", optional = true } | |||
| zstd = { version = "0.13", optional = true } | ||||
| rust-lzma = { version = "0.6.0", optional = true } | ||||
| rand = { version = "0.8", optional = true } | ||||
| tungstenite = { version = "0.24.0", optional = true } | ||||
| 
 | ||||
| [features] | ||||
| default = ["compression_lzma", "protocol_udp"] | ||||
|  | @ -30,6 +31,7 @@ compression_zstd = ["dep:zstd"] | |||
| all_compressions = ["compression_zlib", "compression_bzip2", "compression_lzma", "compression_zstd"] | ||||
| rand = ["dep:rand"] | ||||
| protocol_udp = [] | ||||
| protocol_websocket = ["dep:tungstenite"] | ||||
| 
 | ||||
| [[example]] | ||||
| name = "random_brightness" | ||||
|  |  | |||
|  | @ -1,12 +1,5 @@ | |||
| use std::fmt::Debug; | ||||
| 
 | ||||
| use log::debug; | ||||
| 
 | ||||
| #[cfg(feature = "protocol_udp")] | ||||
| use log::info; | ||||
| #[cfg(feature = "protocol_udp")] | ||||
| use std::net::{ToSocketAddrs, UdpSocket}; | ||||
| 
 | ||||
| use crate::packet::Packet; | ||||
| 
 | ||||
| /// A connection to the display.
 | ||||
|  | @ -15,22 +8,54 @@ use crate::packet::Packet; | |||
| ///
 | ||||
| /// # Examples
 | ||||
| /// ```rust
 | ||||
| /// let connection = servicepoint::Connection::open("172.23.42.29:2342")
 | ||||
| /// let connection = servicepoint::Connection::open("127.0.0.1:2342")
 | ||||
| ///     .expect("connection failed");
 | ||||
| ///  connection.send(servicepoint::Command::Clear)
 | ||||
| ///     .expect("send failed");
 | ||||
| /// ```
 | ||||
| #[derive(Debug)] | ||||
| pub enum Connection { | ||||
|     /// A real connection using the UDP protocol
 | ||||
|     /// A connection using the UDP protocol.
 | ||||
|     ///
 | ||||
|     /// Use this when sending commands directly to the display.
 | ||||
|     ///
 | ||||
|     /// Requires the feature "protocol_udp" which is enabled by default.
 | ||||
|     #[cfg(feature = "protocol_udp")] | ||||
|     Udp(UdpSocket), | ||||
|     Udp(std::net::UdpSocket), | ||||
| 
 | ||||
|     /// A connection using the WebSocket protocol.
 | ||||
|     ///
 | ||||
|     /// Note that you will need to forward the WebSocket messages via UDP to the display.
 | ||||
|     /// You can use [servicepoint-websocket-relay] for this.
 | ||||
|     ///
 | ||||
|     /// To create a new WebSocket automatically, use [Connection::open_websocket].
 | ||||
|     ///
 | ||||
|     /// Requires the feature "protocol_websocket" which is disabled by default.
 | ||||
|     ///
 | ||||
|     /// [servicepoint-websocket-relay]: https://github.com/kaesaecracker/servicepoint-websocket-relay
 | ||||
|     #[cfg(feature = "protocol_websocket")] | ||||
|     WebSocket( | ||||
|         tungstenite::WebSocket< | ||||
|             tungstenite::stream::MaybeTlsStream<std::net::TcpStream>, | ||||
|         >, | ||||
|     ), | ||||
| 
 | ||||
|     /// A fake connection for testing that does not actually send anything.
 | ||||
|     ///
 | ||||
|     /// This variant allows immutable send.
 | ||||
|     Fake, | ||||
| 
 | ||||
|     /// A fake connection for testing that does not actually send anything.
 | ||||
|     ///
 | ||||
|     /// This variant does not allow immutable send.
 | ||||
|     FakeMutableSend, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| pub enum SendError { | ||||
|     IoError(std::io::Error), | ||||
|     #[cfg(feature = "protocol_websocket")] | ||||
|     WebsocketError(tungstenite::Error), | ||||
| } | ||||
| 
 | ||||
| impl Connection { | ||||
|  | @ -38,42 +63,85 @@ impl Connection { | |||
|     ///
 | ||||
|     /// Note that this is UDP, which means that the open call can succeed even if the display is unreachable.
 | ||||
|     ///
 | ||||
|     /// The address of the display in CCCB is `172.23.42.29:2342`.
 | ||||
|     ///
 | ||||
|     /// # Errors
 | ||||
|     ///
 | ||||
|     /// Any errors resulting from binding the udp socket.
 | ||||
|     ///
 | ||||
|     /// # Examples
 | ||||
|     /// ```rust
 | ||||
|     ///  let connection = servicepoint::Connection::open("172.23.42.29:2342")
 | ||||
|     ///  let connection = servicepoint::Connection::open("127.0.0.1:2342")
 | ||||
|     ///     .expect("connection failed");
 | ||||
|     /// ```
 | ||||
|     #[cfg(feature = "protocol_udp")] | ||||
|     pub fn open(addr: impl ToSocketAddrs + Debug) -> std::io::Result<Self> { | ||||
|         info!("connecting to {addr:?}"); | ||||
|         let socket = UdpSocket::bind("0.0.0.0:0")?; | ||||
|     pub fn open( | ||||
|         addr: impl std::net::ToSocketAddrs + Debug, | ||||
|     ) -> std::io::Result<Self> { | ||||
|         log::info!("connecting to {addr:?}"); | ||||
|         let socket = std::net::UdpSocket::bind("0.0.0.0:0")?; | ||||
|         socket.connect(addr)?; | ||||
|         Ok(Self::Udp(socket)) | ||||
|     } | ||||
| 
 | ||||
|     /// Open a new WebSocket and connect to the provided host.
 | ||||
|     ///
 | ||||
|     /// Requires the feature "protocol_websocket" which is disabled by default.
 | ||||
|     ///
 | ||||
|     /// # Examples
 | ||||
|     ///
 | ||||
|     /// ```rust
 | ||||
|     /// use tungstenite::http::Uri;
 | ||||
|     /// use servicepoint::{Command, Connection};
 | ||||
|     /// let uri = "ws://localhost:8080".parse().unwrap();
 | ||||
|     /// let mut connection = Connection::open_websocket(uri)
 | ||||
|     ///     .expect("could not connect");
 | ||||
|     /// connection.send_mut(Command::Clear)
 | ||||
|     ///     .expect("send failed");
 | ||||
|     /// ```
 | ||||
|     #[cfg(feature = "protocol_websocket")] | ||||
|     pub fn open_websocket( | ||||
|         uri: tungstenite::http::Uri, | ||||
|     ) -> tungstenite::Result<Self> { | ||||
|         use tungstenite::{ | ||||
|             client::IntoClientRequest, connect, ClientRequestBuilder, | ||||
|         }; | ||||
| 
 | ||||
|         log::info!("connecting to {uri:?}"); | ||||
| 
 | ||||
|         let request = ClientRequestBuilder::new(uri).into_client_request()?; | ||||
|         let (sock, _) = connect(request)?; | ||||
| 
 | ||||
|         Ok(Self::WebSocket(sock)) | ||||
|     } | ||||
| 
 | ||||
|     /// Send something packet-like to the display. Usually this is in the form of a Command.
 | ||||
|     ///
 | ||||
|     /// This variant can only be used for connections that support immutable send, e.g. [Connection::Udp].
 | ||||
|     ///
 | ||||
|     /// If you want to be able to switch the protocol, you should use [Self::send_mut] instead.
 | ||||
|     ///
 | ||||
|     /// # Arguments
 | ||||
|     ///
 | ||||
|     /// - `packet`: the packet-like to send
 | ||||
|     ///
 | ||||
|     /// returns: true if packet was sent, otherwise false
 | ||||
|     ///
 | ||||
|     /// # Panics
 | ||||
|     ///
 | ||||
|     /// If the connection does not support immutable send, e.g. for [Connection::WebSocket].
 | ||||
|     ///
 | ||||
|     /// # Examples
 | ||||
|     ///
 | ||||
|     /// ```rust
 | ||||
|     /// # let connection = servicepoint::Connection::Fake;
 | ||||
|     ///  let connection = servicepoint::Connection::Fake;
 | ||||
|     ///  // turn off all pixels on display
 | ||||
|     ///  connection.send(servicepoint::Command::Clear)
 | ||||
|     ///      .expect("send failed");
 | ||||
|     /// ```
 | ||||
|     pub fn send(&self, packet: impl Into<Packet>) -> Result<(), SendError> { | ||||
|         let packet = packet.into(); | ||||
|         debug!("sending {packet:?}"); | ||||
|         log::debug!("sending {packet:?}"); | ||||
|         let data: Vec<u8> = packet.into(); | ||||
|         match self { | ||||
|             #[cfg(feature = "protocol_udp")] | ||||
|  | @ -87,6 +155,55 @@ impl Connection { | |||
|                 let _ = data; | ||||
|                 Ok(()) | ||||
|             } | ||||
|             #[allow(unreachable_patterns)] // depends on features
 | ||||
|             _ => { | ||||
|                 panic!("Connection {:?} does not support immutable send", self) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Send something packet-like to the display. Usually this is in the form of a Command.
 | ||||
|     ///
 | ||||
|     /// This variant has to be used for connections that do not support immutable send, e.g. [Connection::WebSocket].
 | ||||
|     ///
 | ||||
|     /// If you want to be able to switch the protocol, you should use this variant.
 | ||||
|     ///
 | ||||
|     /// # Arguments
 | ||||
|     ///
 | ||||
|     /// - `packet`: the packet-like to send
 | ||||
|     ///
 | ||||
|     /// returns: true if packet was sent, otherwise false
 | ||||
|     ///
 | ||||
|     /// # Examples
 | ||||
|     ///
 | ||||
|     /// ```rust
 | ||||
|     ///  let mut connection = servicepoint::Connection::FakeMutableSend;
 | ||||
|     ///  // turn off all pixels on display
 | ||||
|     ///  connection.send_mut(servicepoint::Command::Clear)
 | ||||
|     ///      .expect("send failed");
 | ||||
|     /// ```
 | ||||
|     pub fn send_mut( | ||||
|         &mut self, | ||||
|         packet: impl Into<Packet>, | ||||
|     ) -> Result<(), SendError> { | ||||
|         match self { | ||||
|             #[cfg(feature = "protocol_websocket")] | ||||
|             Connection::WebSocket(socket) => { | ||||
|                 let packet = packet.into(); | ||||
|                 log::debug!("sending {packet:?}"); | ||||
|                 let data: Vec<u8> = packet.into(); | ||||
|                 socket | ||||
|                     .send(tungstenite::Message::Binary(data)) | ||||
|                     .map_err(SendError::WebsocketError) | ||||
|             } | ||||
|             Connection::FakeMutableSend => { | ||||
|                 let packet = packet.into(); | ||||
|                 log::debug!("sending {packet:?}"); | ||||
|                 let data: Vec<u8> = packet.into(); | ||||
|                 let _ = data; | ||||
|                 Ok(()) | ||||
|             } | ||||
|             _ => self.send(packet), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -102,4 +219,19 @@ mod tests { | |||
|         let packet = Packet::try_from(data).unwrap(); | ||||
|         Connection::Fake.send(packet).unwrap() | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn send_fake_mutable() { | ||||
|         let data: &[u8] = &[0u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; | ||||
|         let packet = Packet::try_from(data).unwrap(); | ||||
|         Connection::FakeMutableSend.send_mut(packet).unwrap() | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     #[should_panic] | ||||
|     fn send_fake_mutable_panic() { | ||||
|         let data: &[u8] = &[0u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; | ||||
|         let packet = Packet::try_from(data).unwrap(); | ||||
|         Connection::FakeMutableSend.send(packet).unwrap() | ||||
|     } | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Vinzenz Schroeter
						Vinzenz Schroeter