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 */

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)

in librtmp from OBS studio

Stream Buffer Ready (=0x0020)

(def boxes-per-row 4)
(draw-column-headers)
(draw-box "UNKNOWN" {:span 4})
(draw-bottom)

SWFVerification Ping Request (=0x001A)

UNKNOWN

in librtmp from OBS studio

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

Command(_result(connect))

Command(CreateStream)

send_meta_data

send_packet

circlebuf_push_back

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

  1. Generate random bytes payload.
  2. Generate 128-bit DH key.
  3. Write DH public key in calulate key offset with Payload format.
  4. Calculate digest offset with Payload format.
  5. Calculate HMAC-SHA256 from HHello message except digest 32 bytes with GenuineFPConst.
  6. Write digest and send to the responder.

RHello

  1. Generate random bytes payload.
  2. Generate 128-bit DH key.
  3. Write DH public key in calulate key offset with Payload format.
  4. Calculate digest offset with Payload format.
  5. Calculate HMAC-SHA256 from HHello message except digest 32 bytes with GenuineFMSConst.
  6. Write digest and send to the initiator.

Determine payload format

  1. Get digest offset according to payload format. (need check both)
  2. Calculate HMAC-SHA256 from received HHello message except digest 32 bytes with GenuineConst(Use opposite side’s Const; Initiator: GenuineFMSConst, Responder: GenuineFPConst).
  3. Compare calculated and received HMAC-SHA256.
    1. if match, Read public key from payload.
    2. 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.