scuffle_rtmp/handshake/simple.rs
1//! Simple Handshake Server
2
3use std::io::{self, Seek, Write};
4
5use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
6use bytes::Bytes;
7use rand::Rng;
8use scuffle_bytes_util::BytesCursorExt;
9
10use super::{RTMP_HANDSHAKE_SIZE, RtmpVersion, ServerHandshakeState, TIME_VERSION_LENGTH, current_time};
11
12/// Simple Handshake Server
13///
14/// Defined by:
15/// - Legacy RTMP spec, 5.2
16pub struct SimpleHandshakeServer {
17 version: RtmpVersion,
18 requested_version: RtmpVersion,
19 state: ServerHandshakeState,
20 c1_bytes: Bytes,
21 c1_timestamp: u32,
22}
23
24impl Default for SimpleHandshakeServer {
25 fn default() -> Self {
26 Self {
27 state: ServerHandshakeState::ReadC0C1,
28 c1_bytes: Bytes::new(),
29 c1_timestamp: 0,
30 version: RtmpVersion::Version3,
31 requested_version: RtmpVersion(0),
32 }
33 }
34}
35
36impl SimpleHandshakeServer {
37 /// Returns true if the handshake is finished.
38 pub fn is_finished(&self) -> bool {
39 self.state == ServerHandshakeState::Finish
40 }
41
42 /// Perform the handshake, writing to the output and reading from the input.
43 pub fn handshake(&mut self, input: &mut io::Cursor<Bytes>, output: &mut Vec<u8>) -> Result<(), crate::error::RtmpError> {
44 match self.state {
45 ServerHandshakeState::ReadC0C1 => {
46 self.read_c0(input)?;
47 self.read_c1(input)?;
48 self.write_s0(output)?;
49 self.write_s1(output)?;
50 self.write_s2(output)?;
51 self.state = ServerHandshakeState::ReadC2;
52 }
53 ServerHandshakeState::ReadC2 => {
54 self.read_c2(input)?;
55 self.state = ServerHandshakeState::Finish;
56 }
57 ServerHandshakeState::Finish => {}
58 }
59
60 Ok(())
61 }
62
63 fn read_c0(&mut self, input: &mut io::Cursor<Bytes>) -> Result<(), crate::error::RtmpError> {
64 // Version (8 bits): In C0, this field identifies the RTMP version
65 // requested by the client.
66 self.requested_version = RtmpVersion::from(input.read_u8()?);
67
68 // We only support version 3 for now.
69 // Therefore we set the version to 3.
70 self.version = RtmpVersion::Version3;
71
72 Ok(())
73 }
74
75 fn read_c1(&mut self, input: &mut io::Cursor<Bytes>) -> Result<(), crate::error::RtmpError> {
76 // Time (4 bytes): This field contains a timestamp, which SHOULD be
77 // used as the epoch for all future chunks sent from this endpoint.
78 // This may be 0, or some arbitrary value. To synchronize multiple
79 // chunkstreams, the endpoint may wish to send the current value of
80 // the other chunkstream’s timestamp.
81 self.c1_timestamp = input.read_u32::<BigEndian>()?;
82
83 // Zero (4 bytes): This field MUST be all 0s.
84 input.read_u32::<BigEndian>()?;
85
86 // Random data (1528 bytes): This field can contain any arbitrary
87 // values. Since each endpoint has to distinguish between the
88 // response to the handshake it has initiated and the handshake
89 // initiated by its peer,this data SHOULD send something sufficiently
90 // random. But there is no need for cryptographically-secure
91 // randomness, or even dynamic values.
92 self.c1_bytes = input.extract_bytes(RTMP_HANDSHAKE_SIZE - TIME_VERSION_LENGTH)?;
93
94 Ok(())
95 }
96
97 fn read_c2(&mut self, input: &mut io::Cursor<Bytes>) -> Result<(), crate::error::RtmpError> {
98 // We don't care too much about the data in C2, so we just read it
99 // and discard it.
100 //
101 // We should technically check that the timestamp is the same as
102 // the one we sent in S1, but we don't care. And that the random
103 // data is the same as the one we sent in S2, but we don't care.
104 // Some clients are not strict to spec and send different data.
105 //
106 // We can just ignore it and not be super strict.
107 input.seek_relative(RTMP_HANDSHAKE_SIZE as i64)?;
108
109 Ok(())
110 }
111
112 /// Defined in RTMP Specification 1.0 - 5.2.2
113 fn write_s0(&mut self, output: &mut Vec<u8>) -> Result<(), crate::error::RtmpError> {
114 // Version (8 bits): In S0, this field identifies the RTMP
115 // version selected by the server. The version defined by this
116 // specification is 3. A server that does not recognize the
117 // client’s requested version SHOULD respond with 3. The client MAY
118 // choose to degrade to version 3, or to abandon the handshake.
119 output.write_u8(self.version.0)?;
120
121 Ok(())
122 }
123
124 /// Defined in RTMP Specification 1.0 - 5.2.3
125 fn write_s1(&mut self, output: &mut Vec<u8>) -> Result<(), crate::error::RtmpError> {
126 // Time (4 bytes): This field contains a timestamp, which SHOULD be
127 // used as the epoch for all future chunks sent from this endpoint.
128 // This may be 0, or some arbitrary value. To synchronize multiple
129 // chunkstreams, the endpoint may wish to send the current value of
130 // the other chunkstream’s timestamp.
131 output.write_u32::<BigEndian>(current_time())?;
132
133 // Zero(4 bytes): This field MUST be all 0s.
134 output.write_u32::<BigEndian>(0)?;
135
136 // Random data (1528 bytes): This field can contain any arbitrary
137 // values. Since each endpoint has to distinguish between the
138 // response to the handshake it has initiated and the handshake
139 // initiated by its peer,this data SHOULD send something sufficiently
140 // random. But there is no need for cryptographically-secure
141 // randomness, or even dynamic values.
142 let mut rng = rand::rng();
143 for _ in 0..1528 {
144 output.write_u8(rng.random())?;
145 }
146
147 Ok(())
148 }
149
150 fn write_s2(&mut self, output: &mut Vec<u8>) -> Result<(), crate::error::RtmpError> {
151 // Time (4 bytes): This field MUST contain the timestamp sent by the C1 (for
152 // S2).
153 output.write_u32::<BigEndian>(self.c1_timestamp)?;
154
155 // Time2 (4 bytes): This field MUST contain the timestamp at which the
156 // previous packet(s1 or c1) sent by the peer was read.
157 output.write_u32::<BigEndian>(current_time())?;
158
159 // Random echo (1528 bytes): This field MUST contain the random data
160 // field sent by the peer in S1 (for C2) or S2 (for C1). Either peer
161 // can use the time and time2 fields together with the current
162 // timestamp as a quick estimate of the bandwidth and/or latency of
163 // the connection, but this is unlikely to be useful.
164 output.write_all(&self.c1_bytes[..])?;
165
166 Ok(())
167 }
168}