SRS流媒体服务器RTMP握手

RTMP握手简介

rtmp 1.0规范中,指定了RTMP的握手协议:

  1. c0/s0:一个字节,说明是明文还是加密。
  2. c1/s1: 1536字节,4字节时间,4字节0x00,1528字节随机数
  3. c2/s2: 1536字节,4字节时间1,4字节时间2,1528随机数和s1相同。

这个就是srs以及其他开源软件的simple handshake,简单握手,标准握手,FMLE也是使用这个握手协议。

Flash播放器连接服务器时,如果服务器只支持简单握手,则无法播放h264和aac的流,有数据,但没有视频和声音。

  1. 原因是adobe变更了握手的数据结构,标准rtmp协议的握手的包是随机的1536字节(S1S2C1C2),变更后的是需要进行摘要和加密。
  2. adobe将简单握手改为了有一系列加密算法的复杂握手(complex handshake)

simple简单握手和complex复杂握手的主要区别:

SRS流媒体服务器RTMP握手

SRS编译时若打开了SSL选项(–with-ssl),SRS会先使用复杂握手和客户端握手,若复杂握手失败,则尝试简单握手。

complex handshake C1S1结构

1. complex handshake将C1,S1分为4个部分,它们的顺序(schema)可能是:

// c1s1 schema0
time: 4bytes
version: 4bytes
key: 764bytes
digest: 764bytes

其中key和digest可能会交换位置,即schema可能是:
// c1s1 schema1
time: 4bytes
version: 4bytes
digest: 764bytes
key: 764bytes

2. 客户端来决定使用哪种schema,服务器端则需要先尝试按照schema0解析,失败则用schema1解析。如下图所示:

SRS流媒体服务器RTMP握手

3. 无论key和digest位置如何,它们的结构是不变的:

// 764bytes key结构
random-data: (offset)bytes
key-data: 128bytes
random-data: (764-offset-128-4)bytes
offset: 4bytes

// 764bytes digest结构
offset: 4bytes
random-data: (offset)bytes
digest-data: 32bytes
random-data: (764-4-offset-32)bytes

备注:发现FMS只认识digest-key结构。

对应代码

代码在SrsComplexHandshake::handshake_with_client下:

	hs_bytes->read_c0c1(io)
    srs_error_t SrsHandshakeBytes::read_c0c1(ISrsProtocolReader* io)
    {
        srs_error_t err = srs_success;

        if (c0c1) {
            return err;
        }

        ssize_t nsize;

        c0c1 = new char[1537];
        if ((err = io->read_fully(c0c1, 1537, &nsize)) != srs_success) {
            return srs_error_wrap(err, "read c0c1");
        }

        // Whether RTMP proxy, @see https://github.com/ossrs/go-oryx/wiki/RtmpProxy
        if (uint8_t(c0c1[0]) == 0xF3) { //没进
            uint16_t nn = uint16_t(c0c1[1])<<8 | uint16_t(c0c1[2]);
            ssize_t nn_consumed = 3 + nn;
            if (nn > 1024) {
                return srs_error_new(ERROR_RTMP_PROXY_EXCEED, "proxy exceed max size, nn=%d", nn);
            }

            // 4B client real IP.
            if (nn >= 4) {
                proxy_real_ip = uint32_t(c0c1[3])<<24 | uint32_t(c0c1[4])<<16 | uint32_t(c0c1[5])<<8 | uint32_t(c0c1[6]);
                nn -= 4;
            }

            memmove(c0c1, c0c1 + nn_consumed, 1537 - nn_consumed);
            if ((err = io->read_fully(c0c1 + 1537 - nn_consumed, nn_consumed, &nsize)) != srs_success) {
                return srs_error_wrap(err, "read c0c1");
            }
        }

        return err;
    }

	c1.parse(hs_bytes->c0c1 + 1, 1536, srs_schema0)
    srs_error_t c1s1::parse(char* _c1s1, int size, srs_schema_type schema)
    {
        srs_assert(size == 1536);
        
        if (schema != srs_schema0 && schema != srs_schema1) {
            return srs_error_new(ERROR_RTMP_CH_SCHEMA, "parse c1 failed. invalid schema=%d", schema);
        }
        
        SrsBuffer stream(_c1s1, size);
        
        time = stream.read_4bytes();    //4B time
        version = stream.read_4bytes(); // client c1 version 4B version
        
        srs_freep(payload);
        if (schema == srs_schema0) { //判断顺序
            payload = new c1s1_strategy_schema0();
        } else {
            payload = new c1s1_strategy_schema1();
        }
        
        return payload->parse(_c1s1, size); //解析payload,payload由key和digest组成
    }

	payload->parse(_c1s1, size) //解析payload,payload由key和digest组成
       srs_error_t c1s1_strategy_schema0::parse(char* _c1s1, int size)
    {
        srs_error_t err = srs_success;
        
        srs_assert(size == 1536);
        
        if (true) {
            SrsBuffer stream(_c1s1 + 8, 764);
            
            if ((err = key.parse(&stream)) != srs_success) { //解析key
                return srs_error_wrap(err, "parse the c1 key");
            }
        }
        
        if (true) {
            SrsBuffer stream(_c1s1 + 8 + 764, 764);
        
            if ((err = digest.parse(&stream)) != srs_success) { //解析digest
                return srs_error_wrap(err, "parse the c1 digest");
            }
        }
        
        return err;
    } 

complex handshake C2S2结构

C2S2主要是提供对C1S1的验证。结构如下:

// 1536bytes C2S2结构
random-data: 1504bytes
digest-data: 32bytes

C2S2的结构相对比较简单。如下图所示:

SRS流媒体服务器RTMP握手

对应代码:

	s2.s2_create(&c1)
        srs_error_t c2s2::s2_create(c1s1* c1)
    {
        srs_error_t err = srs_success;
        
        char temp_key[SRS_OpensslHashSize];
        if ((err = openssl_HMACsha256(SrsGenuineFMSKey, 68, c1->get_digest(), 32, temp_key)) != srs_success) {
            return srs_error_wrap(err, "create s2 temp key");
        }
        
        char _digest[SRS_OpensslHashSize];
        if ((err = openssl_HMACsha256(temp_key, 32, random, 1504, _digest)) != srs_success) {
            return srs_error_wrap(err, "create s2 digest");
        }
        
        memcpy(digest, _digest, 32);
        
        return err;
    }

	s2.s2_validate(&c1, is_valid)
    srs_error_t c2s2::s2_validate(c1s1* c1, bool& is_valid)
    {
        is_valid = false;
        srs_error_t err = srs_success;
        
        char temp_key[SRS_OpensslHashSize];
        if ((err = openssl_HMACsha256(SrsGenuineFMSKey, 68, c1->get_digest(), 32, temp_key)) != srs_success) {
            return srs_error_wrap(err, "create s2 temp key");
        }
        
        char _digest[SRS_OpensslHashSize];
        if ((err = openssl_HMACsha256(temp_key, 32, random, 1504, _digest)) != srs_success) {
            return srs_error_wrap(err, "create s2 digest");
        }
        
        is_valid = srs_bytes_equals(digest, _digest, 32);
        
        return err;
    }

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论