RTMP
Adobe’ Real-Time Message Protocol
Introduction
Summary of RTMP Protocol, consider production ready about live streaming service. From librtmp(within ffmpeg; used in obs studio), obs studio’s implementation.
Terminology
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC2119.
Syntax
Handshake
Handshake Phase Version (IVersion, RVersion)
RTMP protocol version negotiation.
(def boxes-per-row 4)
(draw-column-headers)
(draw-box "version" {:span 4})
struct hversion_t
{
uint8_t version;
} : 8;
version: Initiator send supported protocol version information. A responder that does not recongnize the initiator’s request vertion SHOULD respond with 3(unencrypted). The initiator MAY choose to degrade to version 3, or to abandon the handshake.
version = $\begin{cases}\texttt{0x03}&\texttt{if unencrypted}\\texttt{0x06}&\texttt{if encrypted}\end{cases}$
Handshake Phase Hello (IHello, RHello)
(def boxes-per-row 8)
(draw-column-headers)
(draw-box "time" {:span 4})
(draw-box "version" {:span 4})
(draw-gap "payload (1528 bytes)")
struct hhello_t
{
uint32_t time;
uint32_t version;
uint8_t payload[1528];
} : 1536*8;
time: Timestamp, which SHOULD 32-bit system time(epoch), network byte ordered. MAY be 0, or some arbitrary value.
version: Flash Media Server Version. (ex. 0x09_00_7C_02: 9.0.124.2) On sent by a initiator, MUST be all 0s.
payload: SHOULD send something sufficiently random. no need cryptographically-secure randomness. OPTIONAL include HMAC-SHA256 digest explained at Appendix A: RTMPE.
Note: If HVersion negotiated as encrypted version on both side, than payload MUST contain Diffie-Hellman public key. that vertify by HMAC-SHA256 and apply encryption on chunk stream by ARC4.
Else, on unencrypted, OPTIONAL contain HMAC-SHA256.
due to support AVC video stream.
(Some articles explain that fail HMAC-SHA256 occur video stream drop)
Handshake Phase Echo (IEcho, REcho)
(def boxes-per-row 8)
(draw-column-headers)
(draw-box "time" {:span 4})
(draw-box "version" {:span 4})
(draw-gap "payload (1528 bytes)")
(draw-bottom)
struct hecho_t
{
uint32_t time;
uint32_t version;
uint8_t payload[1528];
} : 1536*8;
time: MUST contain the timestamp, which received from IHello or RHello. version: MUST contain the version, which received from IHello or RHello. payload: contain the payload, which received from IHello or RHello.
Note: In RTMP 1.0 Specification, version is defined as time2 that timestamp at receive IHello or RHello. But, In encrypted RTMP(RTMPE), last 4 bytes are using for DHPublicKey vertification.
Chunks
Basic HeaderMessage HeaderExtended Timestamp \Payload
Basic Header
{reg: [
{"bits": 2, "name": "fmt"},
{"bits": 6, "name": "cs_id"},
{"bits": 16, "name": "", type: 2},
], config: {vflip: true, bits: 24}}
{reg: [
{"bits": 2, "name": "fmt"},
{"bits": 6, "name": 0},
{"bits": 8, "name": "cs_id - 64"},
{"bits": 8, "name": "", type: 2},
], config: {vflip: true, bits: 24}}
{reg: [
{"bits": 2, "name": "fmt"},
{"bits": 6, "name": 63},
{"bits": 16, "name": "cs_id - 64"},
], config: {vflip: true, bits: 24}}
- cs_id
- librtmp
- packet.m_nChannel = 0x02; /* control channel (invoke) */
- packet.m_nChannel = 0x03; /* control channel (invoke) */
- packet.m_nChannel = 0x04; /* source channel (invoke) */
- packet.m_nChannel = 0x08; /* video channel */
- librtmp
Message Header
(def box-width 160)
(def boxes-per-row 4)
(draw-column-headers)
(draw-box "timestamp" {:span 3})
(draw-box "message length" {:span 1})
(draw-box "message length" {:span 2})
(draw-box "message type id" {:span 1})
(draw-box "message stream id" {:span 1})
(draw-box "message stream id" {:span 3})
Message
Set Chunk Size (0x01)
{reg: [
{"bits": 1, "name": 10},
{"bits": 31, "name": "chunk size"},
], config: {vflip: true}}
Abort (0x02)
{reg: [
{"bits": 32, "name": "chunk stream id"},
], config: {vflip: true}}
Acknowledgement (0x03)
{reg: [
{"bits": 32, "name": "sequence number"},
], config: {vflip: true}}
Window Acknowledgement Size (0x05)
{reg: [
{"bits": 32, "name": "Acknowledgement Window size"},
], config: {vflip: true}}
Set Peer Bandwidth (0x06)
{reg: [
{"bits": 32, "name": "Acknowledgement Window size"},
], config: {vflip: true}}
{reg: [
{"bits": 8, "name": "Limit Type"},
], config: {vflip: true}}
User Control (0x04)
Stream Begin (=0x0000)
(def boxes-per-row 4)
(draw-column-headers)
(draw-box "stream id" {:span 4})
(draw-bottom)
Stream EOF (=0x0001)
(def boxes-per-row 4)
(draw-column-headers)
(draw-box "stream id" {:span 4})
(draw-bottom)
Stream Dry (=0x0002)
(def boxes-per-row 4)
(draw-column-headers)
(draw-box "stream id" {:span 4})
(draw-bottom)
Set Buffer Length (=0x0003)
(def boxes-per-row 8)
(draw-column-headers)
(draw-box "stream id" {:span 4})
(draw-box "buffer length" {:span 4})
(draw-bottom)
Stream Is Recorded (=0x0004)
(def boxes-per-row 4)
(draw-column-headers)
(draw-box "stream id" {:span 4})
(draw-bottom)
Ping Request (=0x0006)
(def boxes-per-row 4)
(draw-column-headers)
(draw-box "timestamp" {:span 4})
(draw-bottom)
Ping Response (=0x0007)
(def boxes-per-row 4)
(draw-column-headers)
(draw-box "timestamp" {:span 4})
(draw-bottom)
Stream Buffer Empty (=0x001F)
(def boxes-per-row 4)
(draw-column-headers)
(draw-box "UNKNOWN" {:span 4})
(draw-bottom)
Stream Buffer Ready (=0x0020)
(def boxes-per-row 4)
(draw-column-headers)
(draw-box "UNKNOWN" {:span 4})
(draw-bottom)
SWFVerification Ping Request (=0x001A)
UNKNOWN
SWFVerification Ping Response (=0x001B)
UNKNOWN
Messages
Command (0x14, 0x11)
AMF3(0x11) is not suported by librtmp. It means not supported by FFMPEG.
Data (0x12, 0x0F)
Define at FLV Data tags.
AMF3(0x0F) is not suported by librtmp. It means not supported by FFMPEG.
AMF0 Object, that end with ObjectEnd(0x09).
Data is structed as (String, String, ECMAArray)
@setDataFrame, onMetaData
onMetaData
can be found in FLV’s specification, that defined as NetStream.onMetaData
in ActionScript.
- width
- Type: Double(64-bits)
- height
- Type: Double(64-bits)
- vedeodatarate
- Type: Double(64-bits)
- framerate
- Type: Double(64-bits)
- videocodecid
- Type: Double(64-bits)
- audiosamplerate
- Type: Double(64-bits)
- audiosamplesize
- Type: Double(64-bits)
- stereo
- Type: Boolean
- audiocodecid
- Type: Double(64-bits)
FFMPEG don’t send unknown value.
In AAC, MUST use audiosamplerate, audiosamplesize. Don’t trust AudioSpecificConfig or Audio tags information
Shared Object (0x13, 0x10)
Not supported by librtmp. It means not supported by FFMPEG. And, I can’t found real structure of this.
- Broadcast a Shared Object Message
sequenceDiagram
Note over Initiator, Responder: Handshaking done
Initiator->>Responder: Shared Object Event(Use)
Responder->>Initiator: Shared Object Event(Use Success, Clear)
Initiator->>Responder: Shared Object Event(Request Change)
Responder->>Initiator: Shared Object Event(Success)
Initiator->>Responder: Shared Object Event(Send Message)
Responder->>Initiator: Shared Object Event(Send Message)
Audio (0x08)
Define at FLV Audio tags.
AUDIODATA
(def boxes-per-row 4)
(draw-column-headers)
(draw-box "SoundFormat" {:span 4})
(draw-box "SoundRate" {:span 2})
(draw-box "SoundSize" {:span 1})
(draw-box "SoundType" {:span 1})
(draw-gap "SoundData")
(draw-bottom)
SoundFormat
0 = Linear PCM, platform endian 1 = ADPCM 2 = MP3 4 = Linear PCM, little endian 5 = Nellymoser 8-kHz mono 6 = Nellymoser 10 = AAC 11 = Speex
SoundRate
0 = 5.5 kHz 1 = 11 kHz 2 = 22 kHz 3 = 44 kHz
For AAC: always 3
SoundSize
0 = 8 Bits 1 = 16 Bits
Only for uncompressed formats
Compressed formats alyaws 16 bits
SoundType
0 = Mono 1 = Stereo
For Nellymouser: Always 0
For AAC: Always 1
SoundData
AACAUDIODATA
(def boxes-per-row 4)
(draw-column-headers)
(draw-box "AACPacketType" {:span 1})
(draw-gap "Data")
(draw-bottom)
- AACPacketType
- 0 = AAC sequence header
- 1 = AAC raw
- Data = $\begin{cases}\text{AudioSpecificConfig}&\text{\ \ \ \ \ \ \ if AACPacketType} == 0\\text{Raw AAC Frame data}&\text{else if AACPacketType} == 1\end{cases}$
Video (0x09)
(def boxes-per-row 4)
(draw-column-headers)
(draw-box "FrameType" {:span 4})
(draw-box "CodecID" {:span 4})
(draw-gap "VideoData")
(draw-bottom)
FrameType
1 = Key-frame
2 = Inter-frame
5 = video info/command frame
For H.263, 3 = Disposable inter-frame
CodecID
2 = Sorenson H.263
3 = Screen video
4 = On2 VP6
5 = On2 VP6 with alpha chennel
6 = Screen video version 2
7 = AVC
VideoData
Define at FLV Video tags.
AVCVIDEOPACKET
(def boxes-per-row 4)
(draw-column-headers)
(draw-box "AVCPacketType" {:span 1})
(draw-box "CompositionTime" {:span 3})
(draw-gap "Data")
(draw-bottom)
AVCPacketType
0 = AVC sequence header (avc config) 1 = AVC NALU 2 = AVC end of sequence
Aggregate (0x16)
(Define on OBS Studio) Silent Reconnect (0x20)
Commands
NetCommand
Stream ID: 0
(connect)
Type | Value | Description |
---|---|---|
String | connect | Command |
Number | 1 | |
Object | - | Command info |
Object | - | Optionalarguments |
sequenceDiagram
Note over Initiator, Responder: Handshaking done
Initiator->>Responder: Command(connect)
Responder->>Initiator: Window Acknowledgement Size
Responder->>Initiator: Set Peer Bandwidth
Initiator->>Responder: Window Acknowledgement Size
Responder->>Initiator: User Control(Stream Begin)
Responder->>Initiator: Command(_result(connect))
(Call)
Type | Value | Description |
---|---|---|
String | Call | Command |
Number | - | Transaction ID |
Object | - | Command info |
Object | - | Optionalarguments |
Remote Procedure Call
(createStream)
Type | Value | Description |
---|---|---|
String | createStream | Command |
Number | - | Transaction ID |
Object or Null | - | command info |
NetStream
Type | Value | Description |
---|---|---|
String | onStatus | Command |
Number | 0 | |
Null | - | |
Object | - | String level (one of “warning”, “status”, or “error”) String code String description |
(play)
Type | Value | Description |
---|---|---|
String | play | Command |
Number | 0 | |
Null | - | |
String | - | Stream Name |
Number | - | Start |
Number | - | Duration |
Boolean | - | Flush previous playlist |
sequenceDiagram
Note over Initiator, Responder: Handshaking and Application connect done
Initiator->>Responder: Command(createStream)
Responder->>Initiator: Command(_result(createStream))
Initiator->>Responder: Command(play)
Responder->>Initiator: Set Chunk Size
Responder->>Initiator: User Control(Stream Is Recorded)
Responder->>Initiator: User Control(Stream Begin)
Responder->>Initiator: Command(onStatus(play reset))
Responder->>Initiator: Command(onStatus(play start))
Responder->>Initiator: Audio
Responder->>Initiator: Video
Note over Initiator, Responder: Until the stream is complete
(play2)
Type | Value | Description |
---|---|---|
String | play2 | Command |
Number | 0 | |
Null | - | |
Object | - | Parameters |
sequenceDiagram
Note over Initiator, Responder: Handshaking and Application connect done
Initiator->>Responder: Command(createStream)
Responder->>Initiator: Command(_result(createStream))
Initiator->>Responder: Command(play)
Responder->>Initiator: Set Chunk Size
Responder->>Initiator: User Control(Stream Is Recorded)
Responder->>Initiator: User Control(Stream Begin)
Responder->>Initiator: Command(onStatus(play reset))
Responder->>Initiator: Command(onStatus(play start))
Responder->>Initiator: Audio
Responder->>Initiator: Video
Initiator->>Responder: Command(play2)
Responder->>Initiator: Audio (new rate)
Responder->>Initiator: Video (new rate)
Note over Initiator, Responder: Until the stream is complete
(deleteStream)
Type | Value | Description |
---|---|---|
String | deleteStream | Command |
Number | 0 | |
Null | - | |
Number | - | Stream ID |
(receiveAudio)
Type | Value | Description |
---|---|---|
String | receiveAudio | Command |
Number | 0 | |
Null | - | |
Boolean | - | Receive audeo |
(receiveVideo)
Type | Value | Description |
---|---|---|
String | receiveVideo | Command |
Number | 0 | |
Null | - | |
Boolean | - | Receive video |
(publish)
Type | Value | Description |
---|---|---|
String | publish | Command |
Number | 0 | |
Null | - | |
String | - | Name |
String | - | Type record: The stream is published and the data is recorded to a new file. The file is stored on the server in a subdirectory within the directory that contains the server application. If the file already exists, it is overwritten. append: The stream is published and the data is appended to a file. If no fileis found, it is created. live: Live data is published without recording it in a file. |
- Publish Metadata from Recorded Stream
sequenceDiagram
Note over Initiator, Responder: Handshaking and Application connect done
Initiator->>Responder: Command(createStream)
Responder->>Initiator: Command(_result(createStream))
Initiator->>Responder: Command(publish)
Responder->>Initiator: User Control(Stream Begin)
Initiator->>Responder: Data(Metadata)
- Publish Recorded Video
sequenceDiagram
Note over Initiator, Responder: Publish Metadata from Recorded Stream done
Initiator->>Responder: Audio
Initiator->>Responder: Set Chunk Size
Responder->>Initiator: Command(_result(publish))
Initiator->>Responder: Video
Note over Initiator, Responder: Until the stream is complete
(seek)
Type | Value | Description |
---|---|---|
String | seek | Command |
Number | 0 | |
Null | - | |
Number | - | Stream timestamp in milliSeconds |
(pause)
Type | Value | Description |
---|---|---|
String | pause | Command |
Number | 0 | |
Null | - | |
Boolean | - | Pause flag |
Number | - | Stream timestamp in milliSeconds |
Operation
Overview
Packet Multiplex
Packet Fragmentation
Startup
Handshake
sequenceDiagram
Initiator->>Responder: IVersion(Version=3)
Responder->>Initiator: RVersion(Version=3)/RHello(Timestamp=RNow(), Bytes)
Initiator->>Responder: IHello(Timestamp=INow(), Bytes)
Responder->>Initiator: REcho
Initiator->>Responder: IEcho
Initiator
OBS studio: publish live stream
sequenceDiagram
Note over Initiator, Responder: Handshaking done
alt OBS::try_connect
alt librtmp::RTMP_Connect
alt librtmp::RTMP_Connect1
alt librtmp::SendConnectPacket
Initiator->>Responder: Set Chunk Size
Initiator->>Responder: Command(connect)
end
end
end
alt librtmp::RTMP_ConnectStream
alt librtmp::RTMP_ClientPacket
par librtmp::HandleInvoke
Responder->>Initiator: Command(_result(connect))
Initiator->>Responder: Command(ReleaseStream)
Initiator->>Responder: Command(FCPublish)
Initiator->>Responder: Command(CreateStream)
and
Responder->>Initiator: Command(_result(CreateStream))
Initiator->>Responder: Command(publish)
Responder->>Initiator: Command(_result(publish))
end
end
end
end
alt librtmp::init_send
alt librtmp::send_meta_data
Initiator->>Responder: Data(Metadata)
end
end
par
loop librtmp::send_thread
alt librtmp::send_headers
Initiator->>Responder: Data
end
alt librtmp::send_packet
Note over Initiator: circlebuf_pop_front()
Note over Initiator: FLV data(RTMP use FLV's Audio, Video, and Data(AMF0))
Initiator->>Responder: Audio
Initiator->>Responder: Video
Initiator->>Responder: Data
end
end
and
loop librtmp::rtmp_stream_data
alt librtmp::add_video_packet
Note over Initiator: Drop frames
alt librtmp::add_packet
Note over Initiator: circlebuf_push_back()
end
end
end
end
Responder
Digest Handshake
Protocol Control
Streams
References
Appendix A: RTMPE
Summary of rtmpdump’s implementation.
Definitions
digest: HMAC-SHA256
key: Public key by Diffie-Hellman key exchange
Constants
GenuineFMSConst =
(def boxes-per-row 16)
(draw-column-headers)
(draw-box "G" {:span 1})
(draw-box "e" {:span 1})
(draw-box "n" {:span 1})
(draw-box "u" {:span 1})
(draw-box "i" {:span 1})
(draw-box "n" {:span 1})
(draw-box "e" {:span 1})
(draw-box " " {:span 1})
(draw-box "A" {:span 1})
(draw-box "d" {:span 1})
(draw-box "o" {:span 1})
(draw-box "b" {:span 1})
(draw-box "e" {:span 1})
(draw-box " " {:span 1})
(draw-box "F" {:span 1})
(draw-box "l" {:span 1})
(draw-box "a" {:span 1})
(draw-box "s" {:span 1})
(draw-box "h" {:span 1})
(draw-box " " {:span 1})
(draw-box "M" {:span 1})
(draw-box "e" {:span 1})
(draw-box "d" {:span 1})
(draw-box "i" {:span 1})
(draw-box "a" {:span 1})
(draw-box " " {:span 1})
(draw-box "S" {:span 1})
(draw-box "e" {:span 1})
(draw-box "r" {:span 1})
(draw-box "v" {:span 1})
(draw-box "e" {:span 1})
(draw-box "r" {:span 1})
(draw-box " " {:span 1})
(draw-box "0" {:span 1})
(draw-box "0" {:span 1})
(draw-box "1" {:span 1})
GenuineFPConst =
(def boxes-per-row 16)
(draw-column-headers)
(draw-box "G" {:span 1})
(draw-box "e" {:span 1})
(draw-box "n" {:span 1})
(draw-box "u" {:span 1})
(draw-box "i" {:span 1})
(draw-box "n" {:span 1})
(draw-box "e" {:span 1})
(draw-box " " {:span 1})
(draw-box "A" {:span 1})
(draw-box "d" {:span 1})
(draw-box "o" {:span 1})
(draw-box "b" {:span 1})
(draw-box "e" {:span 1})
(draw-box " " {:span 1})
(draw-box "F" {:span 1})
(draw-box "l" {:span 1})
(draw-box "a" {:span 1})
(draw-box "s" {:span 1})
(draw-box "h" {:span 1})
(draw-box " " {:span 1})
(draw-box "P" {:span 1})
(draw-box "l" {:span 1})
(draw-box "a" {:span 1})
(draw-box "y" {:span 1})
(draw-box "e" {:span 1})
(draw-box "r" {:span 1})
(draw-box " " {:span 1})
(draw-box "0" {:span 1})
(draw-box "0" {:span 1})
(draw-box "1" {:span 1})
RandomCrud =
(def boxes-per-row 16)
(draw-column-headers)
(draw-box 0xf0 {:span 1})
(draw-box 0xee {:span 1})
(draw-box 0xc2 {:span 1})
(draw-box 0x4a {:span 1})
(draw-box 0x80 {:span 1})
(draw-box 0x68 {:span 1})
(draw-box 0xbe {:span 1})
(draw-box 0xe8 {:span 1})
(draw-box 0x2e {:span 1})
(draw-box 0x00 {:span 1})
(draw-box 0xd0 {:span 1})
(draw-box 0xd1 {:span 1})
(draw-box 0x02 {:span 1})
(draw-box 0x9e {:span 1})
(draw-box 0x7e {:span 1})
(draw-box 0x57 {:span 1})
(draw-box 0x6e {:span 1})
(draw-box 0xec {:span 1})
(draw-box 0x5d {:span 1})
(draw-box 0x2d {:span 1})
(draw-box 0x29 {:span 1})
(draw-box 0x80 {:span 1})
(draw-box 0x6f {:span 1})
(draw-box 0xab {:span 1})
(draw-box 0x93 {:span 1})
(draw-box 0xb8 {:span 1})
(draw-box 0xe6 {:span 1})
(draw-box 0x36 {:span 1})
(draw-box 0xcf {:span 1})
(draw-box 0xeb {:span 1})
(draw-box 0x31 {:span 1})
(draw-box 0xae {:span 1})
GenuineFMSConstCrud = GenuineFMSConst + RandomCrud =
(def boxes-per-row 16)
(draw-column-headers)
(draw-box "G" {:span 1})
(draw-box "e" {:span 1})
(draw-box "n" {:span 1})
(draw-box "u" {:span 1})
(draw-box "i" {:span 1})
(draw-box "n" {:span 1})
(draw-box "e" {:span 1})
(draw-box " " {:span 1})
(draw-box "A" {:span 1})
(draw-box "d" {:span 1})
(draw-box "o" {:span 1})
(draw-box "b" {:span 1})
(draw-box "e" {:span 1})
(draw-box " " {:span 1})
(draw-box "F" {:span 1})
(draw-box "l" {:span 1})
(draw-box "a" {:span 1})
(draw-box "s" {:span 1})
(draw-box "h" {:span 1})
(draw-box " " {:span 1})
(draw-box "M" {:span 1})
(draw-box "e" {:span 1})
(draw-box "d" {:span 1})
(draw-box "i" {:span 1})
(draw-box "a" {:span 1})
(draw-box " " {:span 1})
(draw-box "S" {:span 1})
(draw-box "e" {:span 1})
(draw-box "r" {:span 1})
(draw-box "v" {:span 1})
(draw-box "e" {:span 1})
(draw-box "r" {:span 1})
(draw-box " " {:span 1})
(draw-box "0" {:span 1})
(draw-box "0" {:span 1})
(draw-box "1" {:span 1})
(draw-box 0xf0 {:span 1})
(draw-box 0xee {:span 1})
(draw-box 0xc2 {:span 1})
(draw-box 0x4a {:span 1})
(draw-box 0x80 {:span 1})
(draw-box 0x68 {:span 1})
(draw-box 0xbe {:span 1})
(draw-box 0xe8 {:span 1})
(draw-box 0x2e {:span 1})
(draw-box 0x00 {:span 1})
(draw-box 0xd0 {:span 1})
(draw-box 0xd1 {:span 1})
(draw-box 0x02 {:span 1})
(draw-box 0x9e {:span 1})
(draw-box 0x7e {:span 1})
(draw-box 0x57 {:span 1})
(draw-box 0x6e {:span 1})
(draw-box 0xec {:span 1})
(draw-box 0x5d {:span 1})
(draw-box 0x2d {:span 1})
(draw-box 0x29 {:span 1})
(draw-box 0x80 {:span 1})
(draw-box 0x6f {:span 1})
(draw-box 0xab {:span 1})
(draw-box 0x93 {:span 1})
(draw-box 0xb8 {:span 1})
(draw-box 0xe6 {:span 1})
(draw-box 0x36 {:span 1})
(draw-box 0xcf {:span 1})
(draw-box 0xeb {:span 1})
(draw-box 0x31 {:span 1})
(draw-box 0xae {:span 1})
GenuineFPConstCrud = GenuineFPConst + RandomCrud =
(def boxes-per-row 16)
(draw-column-headers)
(draw-box "G" {:span 1})
(draw-box "e" {:span 1})
(draw-box "n" {:span 1})
(draw-box "u" {:span 1})
(draw-box "i" {:span 1})
(draw-box "n" {:span 1})
(draw-box "e" {:span 1})
(draw-box " " {:span 1})
(draw-box "A" {:span 1})
(draw-box "d" {:span 1})
(draw-box "o" {:span 1})
(draw-box "b" {:span 1})
(draw-box "e" {:span 1})
(draw-box " " {:span 1})
(draw-box "F" {:span 1})
(draw-box "l" {:span 1})
(draw-box "a" {:span 1})
(draw-box "s" {:span 1})
(draw-box "h" {:span 1})
(draw-box " " {:span 1})
(draw-box "P" {:span 1})
(draw-box "l" {:span 1})
(draw-box "a" {:span 1})
(draw-box "y" {:span 1})
(draw-box "e" {:span 1})
(draw-box "r" {:span 1})
(draw-box " " {:span 1})
(draw-box "0" {:span 1})
(draw-box "0" {:span 1})
(draw-box "1" {:span 1})
(draw-box 0xf0 {:span 1})
(draw-box 0xee {:span 1})
(draw-box 0xc2 {:span 1})
(draw-box 0x4a {:span 1})
(draw-box 0x80 {:span 1})
(draw-box 0x68 {:span 1})
(draw-box 0xbe {:span 1})
(draw-box 0xe8 {:span 1})
(draw-box 0x2e {:span 1})
(draw-box 0x00 {:span 1})
(draw-box 0xd0 {:span 1})
(draw-box 0xd1 {:span 1})
(draw-box 0x02 {:span 1})
(draw-box 0x9e {:span 1})
(draw-box 0x7e {:span 1})
(draw-box 0x57 {:span 1})
(draw-box 0x6e {:span 1})
(draw-box 0xec {:span 1})
(draw-box 0x5d {:span 1})
(draw-box 0x2d {:span 1})
(draw-box 0x29 {:span 1})
(draw-box 0x80 {:span 1})
(draw-box 0x6f {:span 1})
(draw-box 0xab {:span 1})
(draw-box 0x93 {:span 1})
(draw-box 0xb8 {:span 1})
(draw-box 0xe6 {:span 1})
(draw-box 0x36 {:span 1})
(draw-box 0xcf {:span 1})
(draw-box 0xeb {:span 1})
(draw-box 0x31 {:span 1})
(draw-box 0xae {:span 1})
Methods
Calculate Responder Key Offset
(offset) => mod(offset, 632) + 8
Calculate Responder Digest Offset
(offset) => mod(offset, 728) + 776
Calculate Initiator Key Offset
(offset) => mod(offset, 632) + 772
Calculate Initiator Digest Offset
(offset) => mod(offset, 728) + 12
Payload format
Format 1
(def row-header-fn {})
(def boxes-per-row 14)
(draw-box "digest offset" {:span 4})
(draw-gap "random ((calculated digest offset) bytes)")
(draw-gap "digest (32 bytes)")
(draw-gap "random (760-(calculated digest offset)-(length of digest) bytes)")
(draw-gap "random ((calculated key offset) bytes)")
(draw-gap "key (128 bytes)")
(draw-gap "random (760-(calculated key offset)-(length of key) bytes)")
(draw-box "key offset" {:span 4})
(draw-bottom)
Format 2
(def row-header-fn {})
(def boxes-per-row 14)
(draw-gap "random ((calculated key offset) bytes)")
(draw-gap "key (128 bytes)")
(draw-gap "random (760-(calculated key offset)-(length of key) bytes)")
(draw-box "key offset" {:span 4})
(draw-box "digest offset" {:span 4})
(draw-gap "random ((calculated digest offset) bytes)")
(draw-gap "digest (32 bytes)")
(draw-gap "random (760-(calculated digest offset)-(length of digest) bytes)")
(draw-bottom)
Redefine
Encrypted version REcho from Responder (REEcho)
(def boxes-per-row 8)
(draw-column-headers)
(draw-box "time" {:span 4})
(draw-box "version" {:span 4})
(draw-gap "payload (1528 bytes)")
(draw-box "signature" {:span 4})
(draw-bottom)
signature is calculated as
digest = HMACsha256(DHPublicKeyC, GenuineFMSConstCrud)
signature = HMACsha256(REcho[:-4], digest)
Encrypted version IEcho from Initiator (IEEcho)
(def boxes-per-row 8)
(draw-column-headers)
(draw-box "time" {:span 4})
(draw-box "version" {:span 4})
(draw-gap "payload (1528 bytes)")
(draw-box "signature" {:span 4})
(draw-bottom)
signature is calculated as
digest = HMACsha256(DHPublicKeyS, GenuineFPConstCrud)
signature = HMACsha256(IEcho[:-4], digest)
Flow
IHello
- Generate random bytes payload.
- Generate 128-bit DH key.
- Write DH public key in calulate key offset with Payload format.
- Calculate digest offset with Payload format.
- Calculate HMAC-SHA256 from HHello message except digest 32 bytes with GenuineFPConst.
- Write digest and send to the responder.
RHello
- Generate random bytes payload.
- Generate 128-bit DH key.
- Write DH public key in calulate key offset with Payload format.
- Calculate digest offset with Payload format.
- Calculate HMAC-SHA256 from HHello message except digest 32 bytes with GenuineFMSConst.
- Write digest and send to the initiator.
Determine payload format
- Get digest offset according to payload format. (need check both)
- Calculate HMAC-SHA256 from received HHello message except digest 32 bytes with GenuineConst(Use opposite side’s Const; Initiator: GenuineFMSConst, Responder: GenuineFPConst).
- Compare calculated and received HMAC-SHA256.
- if match, Read public key from payload.
- else (dismatch), retry with the other one. or fail key exchange.
Compute Diffie-Hellmann Shared Secret
DHSharedSecret = DH(DHPrivateKey, DHPublicKey_received)
DHPrivateKey: A private key that generated at HHello. DHPublicKey_received: A public key that received by HHello.
Compute SWFVerification token
I don’t understand well. here is copy from rtmpdump’s RTMPE document
If a SWFHash is used, a SWFVerification response will need to be calculated, and returned on-demand to a “ping” request. SWFsize is the size of the SWF file.
Note: It is assumed that the reader is familiar enough with RTMP to know what a “ping” is. Where the ordinary ping type is 0x0006, and the pong response is of type 0x0007, an SWF verification ping is of type 0x001a and the SWF verification pong is of type 0x001b. Packet sizes of type 0x001b are 44 bytes: 2 bytes for the type itself and 42 bytes for the SWF verification response.
SWFVerifySig = { 0x1, 0x1 };
swfvk = payload[:-4];
SWFDigest = SWFVerifySig + bigendian32(SWFsize) + bigendian32(SWFsize) + HMACsha256(SWFHash, swfvk);
Initialise ARC4 Send / Receive Keys
KeyInbound = ARC4Key(HMACsha256(DHPublicKey_received, DHSharedSecret)[0:15])
KeyOutbound = ARC4Key(HMACsha256(DHPublicKey, DHSharedSecret)[0:15])
REcho: Validation
If success Diffie-Hellmann Key Exchange
- Send REEcho with computed signature. else
- simply send a copy.
IEcho: Validation
If success Diffie-Hellmann Key Exchange
- Vertificate received REEcho’s signature
- Send IEEcho with computed signature. else
- simply send a copy.