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