websocket_base/
message.rs

1//! Module containing the default implementation for messages.
2use crate::dataframe::Opcode;
3use crate::result::{WebSocketError, WebSocketResult};
4use crate::ws;
5use crate::ws::dataframe::DataFrame as DataFrameTrait;
6use crate::ws::util::bytes_to_string;
7use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
8use std::borrow::Cow;
9use std::io;
10use std::io::Write;
11use std::str::from_utf8;
12
13const FALSE_RESERVED_BITS: &[bool; 3] = &[false; 3];
14
15/// Valid types of messages (in the default implementation)
16#[derive(Debug, Eq, PartialEq, Clone, Copy)]
17pub enum Type {
18	/// Message with UTF8 test
19	Text = 1,
20	/// Message containing binary data
21	Binary = 2,
22	/// Ping message with data
23	Ping = 9,
24	/// Pong message with data
25	Pong = 10,
26	/// Close connection message with optional reason
27	Close = 8,
28}
29
30/// Represents a WebSocket message.
31///
32/// This message also has the ability to not own its payload, and stores its entire payload in
33/// chunks that get written in order when the message gets sent. This makes the `write_payload`
34/// allocate less memory than the `payload` method (which creates a new buffer every time).
35///
36/// Incidentally this (the default implementation of `Message`) implements the `DataFrame` trait
37/// because this message just gets sent as one single `DataFrame`.
38#[derive(Eq, PartialEq, Clone, Debug)]
39pub struct Message<'a> {
40	/// Type of WebSocket message
41	pub opcode: Type,
42	/// Optional status code to send when closing a connection.
43	/// (only used if this message is of Type::Close)
44	pub cd_status_code: Option<u16>,
45	/// Main payload
46	pub payload: Cow<'a, [u8]>,
47}
48
49impl<'a> Message<'a> {
50	fn new(code: Type, status: Option<u16>, payload: Cow<'a, [u8]>) -> Self {
51		Message {
52			opcode: code,
53			cd_status_code: status,
54			payload,
55		}
56	}
57
58	/// Create a new WebSocket message with text data
59	pub fn text<S>(data: S) -> Self
60	where
61		S: Into<Cow<'a, str>>,
62	{
63		Message::new(
64			Type::Text,
65			None,
66			match data.into() {
67				Cow::Owned(msg) => Cow::Owned(msg.into_bytes()),
68				Cow::Borrowed(msg) => Cow::Borrowed(msg.as_bytes()),
69			},
70		)
71	}
72
73	/// Create a new WebSocket message with binary data
74	pub fn binary<B>(data: B) -> Self
75	where
76		B: IntoCowBytes<'a>,
77	{
78		Message::new(Type::Binary, None, data.into())
79	}
80
81	/// Create a new WebSocket message that signals the end of a WebSocket
82	/// connection, although messages can still be sent after sending this
83	pub fn close() -> Self {
84		Message::new(Type::Close, None, Cow::Borrowed(&[0 as u8; 0]))
85	}
86
87	/// Create a new WebSocket message that signals the end of a WebSocket
88	/// connection and provide a text reason and a status code for why.
89	/// Messages can still be sent after sending this message.
90	pub fn close_because<S>(code: u16, reason: S) -> Self
91	where
92		S: Into<Cow<'a, str>>,
93	{
94		Message::new(
95			Type::Close,
96			Some(code),
97			match reason.into() {
98				Cow::Owned(msg) => Cow::Owned(msg.into_bytes()),
99				Cow::Borrowed(msg) => Cow::Borrowed(msg.as_bytes()),
100			},
101		)
102	}
103
104	/// Create a ping WebSocket message, a pong is usually sent back
105	/// after sending this with the same data
106	pub fn ping<P>(data: P) -> Self
107	where
108		P: IntoCowBytes<'a>,
109	{
110		Message::new(Type::Ping, None, data.into())
111	}
112
113	/// Create a pong WebSocket message, usually a response to a
114	/// ping message
115	pub fn pong<P>(data: P) -> Self
116	where
117		P: IntoCowBytes<'a>,
118	{
119		Message::new(Type::Pong, None, data.into())
120	}
121
122	// TODO: change this to match conventions
123	#[allow(clippy::wrong_self_convention)]
124	/// Convert a ping message to a pong, keeping the data.
125	/// This will fail if the original message is not a ping.
126	pub fn into_pong(&mut self) -> Result<(), ()> {
127		if self.opcode == Type::Ping {
128			self.opcode = Type::Pong;
129			Ok(())
130		} else {
131			Err(())
132		}
133	}
134}
135
136impl<'a> ws::dataframe::DataFrame for Message<'a> {
137	#[inline(always)]
138	fn is_last(&self) -> bool {
139		true
140	}
141
142	#[inline(always)]
143	fn opcode(&self) -> u8 {
144		self.opcode as u8
145	}
146
147	#[inline(always)]
148	fn reserved(&self) -> &[bool; 3] {
149		FALSE_RESERVED_BITS
150	}
151
152	fn size(&self) -> usize {
153		self.payload.len() + if self.cd_status_code.is_some() { 2 } else { 0 }
154	}
155
156	fn write_payload(&self, socket: &mut dyn Write) -> WebSocketResult<()> {
157		if let Some(reason) = self.cd_status_code {
158			socket.write_u16::<BigEndian>(reason)?;
159		}
160		socket.write_all(&*self.payload)?;
161		Ok(())
162	}
163
164	fn take_payload(self) -> Vec<u8> {
165		if let Some(reason) = self.cd_status_code {
166			let mut buf = Vec::with_capacity(2 + self.payload.len());
167			buf.write_u16::<BigEndian>(reason)
168				.expect("failed to write close code in take_payload");
169			buf.append(&mut self.payload.into_owned());
170			buf
171		} else {
172			self.payload.into_owned()
173		}
174	}
175}
176
177impl<'a> ws::Message for Message<'a> {
178	/// Attempt to form a message from a series of data frames
179	fn serialize(&self, writer: &mut dyn Write, masked: bool) -> WebSocketResult<()> {
180		self.write_to(writer, masked)
181	}
182
183	/// Returns how many bytes this message will take up
184	fn message_size(&self, masked: bool) -> usize {
185		self.frame_size(masked)
186	}
187
188	/// Attempt to form a message from a series of data frames
189	fn from_dataframes<D>(frames: Vec<D>) -> WebSocketResult<Self>
190	where
191		D: DataFrameTrait,
192	{
193		let opcode = frames
194			.first()
195			.ok_or(WebSocketError::ProtocolError("No dataframes provided"))
196			.map(ws::dataframe::DataFrame::opcode)?;
197		let opcode = Opcode::new(opcode);
198
199		let payload_size = frames.iter().map(ws::dataframe::DataFrame::size).sum();
200
201		let mut data = Vec::with_capacity(payload_size);
202
203		for (i, dataframe) in frames.into_iter().enumerate() {
204			if i > 0 && dataframe.opcode() != Opcode::Continuation as u8 {
205				return Err(WebSocketError::ProtocolError(
206					"Unexpected non-continuation data frame",
207				));
208			}
209			if *dataframe.reserved() != [false; 3] {
210				return Err(WebSocketError::ProtocolError(
211					"Unsupported reserved bits received",
212				));
213			}
214			data.append(&mut dataframe.take_payload());
215		}
216
217		if opcode == Some(Opcode::Text) {
218			if let Err(e) = from_utf8(data.as_slice()) {
219				return Err(e.into());
220			}
221		}
222
223		let msg = match opcode {
224			Some(Opcode::Text) => Message {
225				opcode: Type::Text,
226				cd_status_code: None,
227				payload: Cow::Owned(data),
228			},
229			Some(Opcode::Binary) => Message::binary(data),
230			Some(Opcode::Close) => {
231				if !data.is_empty() {
232					let status_code = (&data[..]).read_u16::<BigEndian>()?;
233					let reason = bytes_to_string(&data[2..])?;
234					Message::close_because(status_code, reason)
235				} else {
236					Message::close()
237				}
238			}
239			Some(Opcode::Ping) => Message::ping(data),
240			Some(Opcode::Pong) => Message::pong(data),
241			_ => return Err(WebSocketError::ProtocolError("Unsupported opcode received")),
242		};
243		Ok(msg)
244	}
245}
246
247/// Represents an owned WebSocket message.
248///
249/// `OwnedMessage`s are generated when the user receives a message (since the data
250/// has to be copied out of the network buffer anyway).
251/// If you would like to create a message out of borrowed data to use for sending
252/// please use the `Message` struct (which contains a `Cow`).
253///
254/// Note that `OwnedMessage` and `Message` can be converted into each other.
255#[derive(Eq, PartialEq, Clone, Debug)]
256pub enum OwnedMessage {
257	/// A message containing UTF-8 text data
258	Text(String),
259	/// A message containing binary data
260	Binary(Vec<u8>),
261	/// A message which indicates closure of the WebSocket connection.
262	/// This message may or may not contain data.
263	Close(Option<CloseData>),
264	/// A ping message - should be responded to with a pong message.
265	/// Usually the pong message will be sent with the same data as the
266	/// received ping message.
267	Ping(Vec<u8>),
268	/// A pong message, sent in response to a Ping message, usually
269	/// containing the same data as the received ping message.
270	Pong(Vec<u8>),
271}
272
273impl OwnedMessage {
274	/// Checks if this message is a close message.
275	///
276	///```rust
277	///# use websocket_base::OwnedMessage;
278	///assert!(OwnedMessage::Close(None).is_close());
279	///```
280	pub fn is_close(&self) -> bool {
281		match *self {
282			OwnedMessage::Close(_) => true,
283			_ => false,
284		}
285	}
286
287	/// Checks if this message is a control message.
288	/// Control messages are either `Close`, `Ping`, or `Pong`.
289	///
290	///```rust
291	///# use websocket_base::OwnedMessage;
292	///assert!(OwnedMessage::Ping(vec![]).is_control());
293	///assert!(OwnedMessage::Pong(vec![]).is_control());
294	///assert!(OwnedMessage::Close(None).is_control());
295	///```
296	pub fn is_control(&self) -> bool {
297		match *self {
298			OwnedMessage::Close(_) => true,
299			OwnedMessage::Ping(_) => true,
300			OwnedMessage::Pong(_) => true,
301			_ => false,
302		}
303	}
304
305	/// Checks if this message is a data message.
306	/// Data messages are either `Text` or `Binary`.
307	///
308	///```rust
309	///# use websocket_base::OwnedMessage;
310	///assert!(OwnedMessage::Text("1337".to_string()).is_data());
311	///assert!(OwnedMessage::Binary(vec![]).is_data());
312	///```
313	pub fn is_data(&self) -> bool {
314		!self.is_control()
315	}
316
317	/// Checks if this message is a ping message.
318	/// `Ping` messages can come at any time and usually generate a `Pong` message
319	/// response.
320	///
321	///```rust
322	///# use websocket_base::OwnedMessage;
323	///assert!(OwnedMessage::Ping("ping".to_string().into_bytes()).is_ping());
324	///```
325	pub fn is_ping(&self) -> bool {
326		match *self {
327			OwnedMessage::Ping(_) => true,
328			_ => false,
329		}
330	}
331
332	/// Checks if this message is a pong message.
333	/// `Pong` messages are usually sent only in response to `Ping` messages.
334	///
335	///```rust
336	///# use websocket_base::OwnedMessage;
337	///assert!(OwnedMessage::Pong("pong".to_string().into_bytes()).is_pong());
338	///```
339	pub fn is_pong(&self) -> bool {
340		match *self {
341			OwnedMessage::Pong(_) => true,
342			_ => false,
343		}
344	}
345}
346
347impl ws::Message for OwnedMessage {
348	/// Attempt to form a message from a series of data frames
349	fn serialize(&self, writer: &mut dyn Write, masked: bool) -> WebSocketResult<()> {
350		self.write_to(writer, masked)
351	}
352
353	/// Returns how many bytes this message will take up
354	fn message_size(&self, masked: bool) -> usize {
355		self.frame_size(masked)
356	}
357
358	/// Attempt to form a message from a series of data frames
359	fn from_dataframes<D>(frames: Vec<D>) -> WebSocketResult<Self>
360	where
361		D: DataFrameTrait,
362	{
363		Ok(Message::from_dataframes(frames)?.into())
364	}
365}
366
367impl ws::dataframe::DataFrame for OwnedMessage {
368	#[inline(always)]
369	fn is_last(&self) -> bool {
370		true
371	}
372
373	#[inline(always)]
374	fn opcode(&self) -> u8 {
375		(match *self {
376			OwnedMessage::Text(_) => Type::Text,
377			OwnedMessage::Binary(_) => Type::Binary,
378			OwnedMessage::Close(_) => Type::Close,
379			OwnedMessage::Ping(_) => Type::Ping,
380			OwnedMessage::Pong(_) => Type::Pong,
381		}) as u8
382	}
383
384	#[inline(always)]
385	fn reserved(&self) -> &[bool; 3] {
386		FALSE_RESERVED_BITS
387	}
388
389	fn size(&self) -> usize {
390		match *self {
391			OwnedMessage::Text(ref txt) => txt.len(),
392			OwnedMessage::Binary(ref bin) => bin.len(),
393			OwnedMessage::Ping(ref data) => data.len(),
394			OwnedMessage::Pong(ref data) => data.len(),
395			OwnedMessage::Close(ref data) => match data {
396				&Some(ref c) => c.reason.len() + 2,
397				&None => 0,
398			},
399		}
400	}
401
402	fn write_payload(&self, socket: &mut dyn Write) -> WebSocketResult<()> {
403		match *self {
404			OwnedMessage::Text(ref txt) => socket.write_all(txt.as_bytes())?,
405			OwnedMessage::Binary(ref bin) => socket.write_all(bin.as_slice())?,
406			OwnedMessage::Ping(ref data) => socket.write_all(data.as_slice())?,
407			OwnedMessage::Pong(ref data) => socket.write_all(data.as_slice())?,
408			OwnedMessage::Close(ref data) => match data {
409				&Some(ref c) => {
410					socket.write_u16::<BigEndian>(c.status_code)?;
411					socket.write_all(c.reason.as_bytes())?
412				}
413				&None => (),
414			},
415		};
416		Ok(())
417	}
418
419	fn take_payload(self) -> Vec<u8> {
420		match self {
421			OwnedMessage::Text(txt) => txt.into_bytes(),
422			OwnedMessage::Binary(bin) => bin,
423			OwnedMessage::Ping(data) => data,
424			OwnedMessage::Pong(data) => data,
425			OwnedMessage::Close(data) => match data {
426				Some(c) => {
427					let mut buf = Vec::with_capacity(2 + c.reason.len());
428					buf.write_u16::<BigEndian>(c.status_code)
429						.expect("failed to write close code in take_payload");
430					buf.append(&mut c.reason.into_bytes());
431					buf
432				}
433				None => vec![],
434			},
435		}
436	}
437}
438
439impl From<String> for OwnedMessage {
440	fn from(text: String) -> Self {
441		OwnedMessage::Text(text)
442	}
443}
444
445impl From<Vec<u8>> for OwnedMessage {
446	fn from(buf: Vec<u8>) -> Self {
447		OwnedMessage::Binary(buf)
448	}
449}
450
451impl<'m> From<Message<'m>> for OwnedMessage {
452	fn from(message: Message<'m>) -> Self {
453		match message.opcode {
454			Type::Text => {
455				let convert = String::from_utf8_lossy(&message.payload).into_owned();
456				OwnedMessage::Text(convert)
457			}
458			Type::Close => match message.cd_status_code {
459				Some(code) => OwnedMessage::Close(Some(CloseData {
460					status_code: code,
461					reason: String::from_utf8_lossy(&message.payload).into_owned(),
462				})),
463				None => OwnedMessage::Close(None),
464			},
465			Type::Binary => OwnedMessage::Binary(message.payload.into_owned()),
466			Type::Ping => OwnedMessage::Ping(message.payload.into_owned()),
467			Type::Pong => OwnedMessage::Pong(message.payload.into_owned()),
468		}
469	}
470}
471
472impl<'m> From<OwnedMessage> for Message<'m> {
473	fn from(message: OwnedMessage) -> Self {
474		match message {
475			OwnedMessage::Text(txt) => Message::text(txt),
476			OwnedMessage::Binary(bin) => Message::binary(bin),
477			OwnedMessage::Close(because) => match because {
478				Some(c) => Message::close_because(c.status_code, c.reason),
479				None => Message::close(),
480			},
481			OwnedMessage::Ping(data) => Message::ping(data),
482			OwnedMessage::Pong(data) => Message::pong(data),
483		}
484	}
485}
486
487/// Represents data contained in a Close message
488#[derive(Eq, PartialEq, Clone, Debug)]
489pub struct CloseData {
490	/// The status-code of the CloseData
491	pub status_code: u16,
492	/// The reason-phrase of the CloseData
493	pub reason: String,
494}
495
496impl CloseData {
497	/// Create a new CloseData object
498	pub fn new(status_code: u16, reason: String) -> CloseData {
499		CloseData {
500			status_code,
501			reason,
502		}
503	}
504	/// Convert this into a vector of bytes
505	pub fn into_bytes(self) -> io::Result<Vec<u8>> {
506		let mut buf = Vec::new();
507		buf.write_u16::<BigEndian>(self.status_code)?;
508		for i in self.reason.as_bytes().iter() {
509			buf.push(*i);
510		}
511		Ok(buf)
512	}
513}
514
515/// Trait representing the ability to convert
516/// self to a `Cow<'a, [u8]>`
517pub trait IntoCowBytes<'a> {
518	/// Consume `self` and produce a `Cow<'a, [u8]>`
519	fn into(self) -> Cow<'a, [u8]>;
520}
521
522impl<'a> IntoCowBytes<'a> for Vec<u8> {
523	fn into(self) -> Cow<'a, [u8]> {
524		Cow::Owned(self)
525	}
526}
527
528impl<'a> IntoCowBytes<'a> for &'a [u8] {
529	fn into(self) -> Cow<'a, [u8]> {
530		Cow::Borrowed(self)
531	}
532}
533
534impl<'a> IntoCowBytes<'a> for Cow<'a, [u8]> {
535	fn into(self) -> Cow<'a, [u8]> {
536		self
537	}
538}