SRS 代码分析【RTMP握手实现】

来源:互联网 发布:js设置placeholder颜色 编辑:程序博客网 时间:2024/06/08 01:20

RTMP简单握手实现

1.客户端握手的代码如下:

int SrsSimpleHandshake::handshake_with_server(SrsHandshakeBytes* hs_bytes, ISrsProtocolReaderWriter* io){    int ret = ERROR_SUCCESS;        ssize_t nsize;        // simple handshake    if ((ret = hs_bytes->create_c0c1()) != ERROR_SUCCESS) {        return ret;    }        if ((ret = io->write(hs_bytes->c0c1, 1537, &nsize)) != ERROR_SUCCESS) {        srs_warn("write c0c1 failed. ret=%d", ret);        return ret;    }    srs_verbose("write c0c1 success.");        if ((ret = hs_bytes->read_s0s1s2(io)) != ERROR_SUCCESS) {        return ret;    }        // plain text required.    if (hs_bytes->s0s1s2[0] != 0x03) {        ret = ERROR_RTMP_HANDSHAKE;        srs_warn("handshake failed, plain text required. ret=%d", ret);        return ret;    }        if ((ret = hs_bytes->create_c2()) != ERROR_SUCCESS) {        return ret;    }        // for simple handshake, copy s1 to c2.    // @see https://github.com/ossrs/srs/issues/418    memcpy(hs_bytes->c2, hs_bytes->s0s1s2 + 1, 1536);        if ((ret = io->write(hs_bytes->c2, 1536, &nsize)) != ERROR_SUCCESS) {        srs_warn("simple handshake write c2 failed. ret=%d", ret);        return ret;    }    srs_verbose("simple handshake write c2 success.");        srs_trace("simple handshake success.");        return ret;}

1).调用hs_bytes->create_c0c1()创建c0c1,并发送c0c1给服务端

int SrsHandshakeBytes::create_c0c1(){    int ret = ERROR_SUCCESS;        if (c0c1) {        return ret;    }        c0c1 = new char[1537];    srs_random_generate(c0c1, 1537);        // plain text required.    SrsBuffer stream;    if ((ret = stream.initialize(c0c1, 9)) != ERROR_SUCCESS) {        return ret;    }    stream.write_1bytes(0x03);    stream.write_4bytes((int32_t)::time(NULL));    stream.write_4bytes(0x00);        return ret;}

2).客户端调用hs_bytes->read_s0s1s2(io)等待读取服务端发送来的s0s1s2

3).客户端读取完s0s1s2后,接着调用hs_bytes->create_c2创建c2,c2的第二个时间是从s1中拷贝的,创建完成后会发送c2给服务端

int SrsHandshakeBytes::create_c2(){    int ret = ERROR_SUCCESS;        if (c2) {        return ret;    }        c2 = new char[1536];    srs_random_generate(c2, 1536);        // time    SrsBuffer stream;    if ((ret = stream.initialize(c2, 8)) != ERROR_SUCCESS) {        return ret;    }    stream.write_4bytes((int32_t)::time(NULL));    // c2 time2 copy from s1    if (s0s1s2) {        stream.write_bytes(s0s1s2 + 1, 4);    }        return ret;}


2.服务端握手的代码如下:

int SrsSimpleHandshake::handshake_with_client(SrsHandshakeBytes* hs_bytes, ISrsProtocolReaderWriter* io){    int ret = ERROR_SUCCESS;        ssize_t nsize;        if ((ret = hs_bytes->read_c0c1(io)) != ERROR_SUCCESS) {        return ret;    }        // plain text required.    if (hs_bytes->c0c1[0] != 0x03) {        ret = ERROR_RTMP_PLAIN_REQUIRED;        srs_warn("only support rtmp plain text. ret=%d", ret);        return ret;    }    srs_verbose("check c0 success, required plain text.");        if ((ret = hs_bytes->create_s0s1s2(hs_bytes->c0c1 + 1)) != ERROR_SUCCESS) {        return ret;    }        if ((ret = io->write(hs_bytes->s0s1s2, 3073, &nsize)) != ERROR_SUCCESS) {        srs_warn("simple handshake send s0s1s2 failed. ret=%d", ret);        return ret;    }    srs_verbose("simple handshake send s0s1s2 success.");        if ((ret = hs_bytes->read_c2(io)) != ERROR_SUCCESS) {        return ret;    }        srs_trace("simple handshake success.");        return ret;}
1).调用hs_bytes->read_c0c1(io)等待读取客户端发送来的c0,c1。
2).服务端读取c0c1后,会调用hs_bytes->create_s0s1s2创建s0s1s2。

    其中s1的第二个时间是从c1中拷贝的,s2是直接拷贝的c1。

    s0s1s2创建完成后会发送给客户端。

int SrsHandshakeBytes::create_s0s1s2(const char* c1){    int ret = ERROR_SUCCESS;        if (s0s1s2) {        return ret;    }        s0s1s2 = new char[3073];    srs_random_generate(s0s1s2, 3073);        // plain text required.    SrsBuffer stream;    if ((ret = stream.initialize(s0s1s2, 9)) != ERROR_SUCCESS) {        return ret;    }    stream.write_1bytes(0x03);    stream.write_4bytes((int32_t)::time(NULL));    // s1 time2 copy from c1    if (c0c1) {        stream.write_bytes(c0c1 + 1, 4);    }        // if c1 specified, copy c1 to s2.    // @see: https://github.com/ossrs/srs/issues/46    if (c1) {        memcpy(s0s1s2 + 1537, c1, 1536);    }        return ret;}
3).服务端调用hs_bytes->read_c2(io)等待读取客户端发送来的c2

包结构说明

c0 s0包结构:


只有8个bit占用1字节,双方通过这个命令来同步版本号,现在版本是03.


c1 s1包结构



这两个包第一个time是双方各自发出的time,c1包zero的4个字节都为0,s1 包中zero的四个字节使用从c1中获取的time。


c2 s2包结构



c2,s2是有发送顺序的,必须是s2首先发出,然后c2才可以发出。



RTMP复杂握手说明

转载:http://blog.csdn.net/win_lin/article/details/13006803


当服务器和客户端的握手是按照rtmp协议进行,是不支持h264/aac的,有数据,就是没有视频和声音。

原因是adobe变更了握手的数据结构,标准rtmp协议的握手的包是随机的1536字节(S1S2C1C2),变更后的是需要进行摘要和加密。

rtmp协议定义的为simple handshake,变更后加密握手可称为complex handshake。

本文详细分析了rtmpd(ccrtmpserver)中的处理逻辑,以及rtmpdump的处理逻辑,从一个全是魔法数字的世界找到他们的数据结构和算法。

complex handshake C1S1结构

complex handshake将C1S1分为4个部分,它们的顺序(schema)一种可能是:

[plain] view plain copy
  1. // c1s1 schema0  
  2. time: 4bytes  
  3. version: 4bytes  
  4. key: 764bytes  
  5. digest: 764bytes  

其中,key和digest可能会交换位置,即schema可能是:

[plain] view plain copy
  1. // c1s1 schema1  
  2. time: 4bytes  
  3. version: 4bytes  
  4. digest: 764bytes  
  5. key: 764bytes  

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


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

[plain] view plain copy
  1. // 764bytes key结构  
  2. random-data: (offset)bytes  
  3. key-data: 128bytes  
  4. random-data: (764-offset-128-4)bytes  
  5. offset: 4bytes  
  6.   
  7. // 764bytes digest结构  
  8. offset: 4bytes  
  9. random-data: (offset)bytes  
  10. digest-data: 32bytes  
  11. random-data: (764-4-offset-32)bytes  

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


如下图所示:



crtmp中这些全是魔法数字。

complex handshake C2S2结构

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

[plain] view plain copy
  1. // 1536bytes C2S2结构  
  2. random-data: 1504bytes  
  3. digest-data: 32bytes  

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


下面介绍C1S1C2S2的生成以及验证算法。

complex handshake C1S1算法

C1S1中都是包含32字节的digest,而且digest将C1S1分成两部分:

[plain] view plain copy
  1. // C1S1被digest分成两部分  
  2. c1s1-part1: n bytes  
  3. digest-data: 32bytes  
  4. c1s1-part2: (1536-n-32)bytes  

如下图所示:



在生成C1时,需要用到c1s1-part1和c1s1-part2这两个部分的字节拼接起来的字节,定义为:

[plain] view plain copy
  1. c1s1-joined = bytes_join(c1s1-part1, c1s1-part2)  

也就是说,把1536字节的c1s1中的32字节的digest拿剪刀剪掉,剩下的头和尾加在一起就是c1s1-joined。

用到的两个常量FPKey和FMSKey:

[plain] view plain copy
  1. u_int8_t FMSKey[] = {  
  2.     0x47, 0x65, 0x6e, 0x75, 0x69, 0x6e, 0x65, 0x20,  
  3.     0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x46, 0x6c,  
  4.     0x61, 0x73, 0x68, 0x20, 0x4d, 0x65, 0x64, 0x69,  
  5.     0x61, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,  
  6.     0x20, 0x30, 0x30, 0x31, // Genuine Adobe Flash Media Server 001  
  7.     0xf0, 0xee, 0xc2, 0x4a, 0x80, 0x68, 0xbe, 0xe8,  
  8.     0x2e, 0x00, 0xd0, 0xd1, 0x02, 0x9e, 0x7e, 0x57,  
  9.     0x6e, 0xec, 0x5d, 0x2d, 0x29, 0x80, 0x6f, 0xab,  
  10.     0x93, 0xb8, 0xe6, 0x36, 0xcf, 0xeb, 0x31, 0xae  
  11. }; // 68  
  12.   
  13. u_int8_t FPKey[] = {  
  14.     0x47, 0x65, 0x6E, 0x75, 0x69, 0x6E, 0x65, 0x20,  
  15.     0x41, 0x64, 0x6F, 0x62, 0x65, 0x20, 0x46, 0x6C,  
  16.     0x61, 0x73, 0x68, 0x20, 0x50, 0x6C, 0x61, 0x79,  
  17.     0x65, 0x72, 0x20, 0x30, 0x30, 0x31, // Genuine Adobe Flash Player 001  
  18.     0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8,  
  19.     0x2E, 0x00, 0xD0, 0xD1, 0x02, 0x9E, 0x7E, 0x57,  
  20.     0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB,  
  21.     0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE  
  22. }; // 62  

生成C1的算法如下:

[plain] view plain copy
  1. calc_c1_digest(c1, schema) {  
  2.     get c1s1-joined from c1 by specified schema  
  3.     digest-data = HMACsha256(c1s1-joined, FPKey, 30)  
  4.     return digest-data;  
  5. }  
  6. random fill 1536bytes c1 // also fill the c1-128bytes-key  
  7. time = time() // c1[0-3]  
  8. version = [0x80, 0x00, 0x07, 0x02] // c1[4-7]  
  9. schema = choose schema0 or schema1  
  10. digest-data = calc_c1_digest(c1, schema)  
  11. copy digest-data to c1  

生成S1的算法如下:

[plain] view plain copy
  1. /*decode c1 try schema0 then schema1*/  
  2. c1-digest-data = get-c1-digest-data(schema0)  
  3. if c1-digest-data equals to calc_c1_digest(c1, schema0) {  
  4.     c1-key-data = get-c1-key-data(schema0)  
  5.     schema = schema0  
  6. } else {  
  7.     c1-digest-data = get-c1-digest-data(schema1)  
  8.     if c1-digest-data not equals to calc_c1_digest(c1, schema1) {  
  9.         switch to simple handshake.  
  10.         return  
  11.     }  
  12.     c1-key-data = get-c1-key-data(schema1)  
  13.     schema = schema1  
  14. }  
  15.   
  16. /*generate s1*/  
  17. random fill 1536bytes s1  
  18. time = time() // c1[0-3]  
  19. version = [0x04, 0x05, 0x00, 0x01] // s1[4-7]  
  20. s1-key-data=shared_key=DH_compute_key(peer_pub_key=c1-key-data)  
  21. get c1s1-joined by specified schema  
  22. s1-digest-data = HMACsha256(c1s1-joined, FMSKey, 36)  
  23. copy s1-digest-data and s1-key-data to s1.  

C1S1的算法完毕。

complex handshake C2S2

C2S2的生成算法如下:

[plain] view plain copy
  1. random fill c2s2 1536 bytes  
  2.   
  3. // client generate C2, or server valid C2  
  4. temp-key = HMACsha256(s1-digest, FPKey, 62)  
  5. c2-digest-data = HMACsha256(c2-random-data, temp-key, 32)  
  6.   
  7. // server generate S2, or client valid S2  
  8. temp-key = HMACsha256(c1-digest, FMSKey, 68)  
  9. s2-digest-data = HMACsha256(s2-random-data, temp-key, 32)  

验证的算法是一样的。