162 lines
4.9 KiB
Go
162 lines
4.9 KiB
Go
/**
|
||
* package mediaserver
|
||
*
|
||
* RTMP 握手实现
|
||
* 支持 Simple Handshake (Version 3)
|
||
*/
|
||
package mediaserver
|
||
|
||
import (
|
||
"bytes"
|
||
"crypto/rand"
|
||
"encoding/binary"
|
||
"fmt"
|
||
"io"
|
||
"log"
|
||
"net"
|
||
"time"
|
||
)
|
||
|
||
// 握手相关常量
|
||
const (
|
||
HANDSHAKE_SIZE = 1536
|
||
RTMP_VERSION = 3
|
||
)
|
||
|
||
// DoHandshake 执行 RTMP 握手(服务器端)
|
||
// 握手流程:
|
||
// 1. 接收 C0 + C1 (1 + 1536 bytes)
|
||
// 2. 发送 S0 + S1 + S2 (1 + 1536 + 1536 bytes)
|
||
// 3. 接收 C2 (1536 bytes)
|
||
func DoHandshake(conn net.Conn, timeout time.Duration) error {
|
||
// 设置超时
|
||
conn.SetDeadline(time.Now().Add(timeout))
|
||
defer conn.SetDeadline(time.Time{}) // 清除超时
|
||
|
||
// 1. 接收 C0 (1 byte: version)
|
||
c0 := make([]byte, 1)
|
||
if _, err := io.ReadFull(conn, c0); err != nil {
|
||
return fmt.Errorf("读取 C0 失败: %w", err)
|
||
}
|
||
|
||
version := c0[0]
|
||
if version != RTMP_VERSION {
|
||
// 尝试兼容其他版本
|
||
log.Printf("⚠️ [RTMP Handshake] 客户端版本: %d (期望: %d)", version, RTMP_VERSION)
|
||
}
|
||
|
||
// 2. 接收 C1 (1536 bytes)
|
||
c1 := make([]byte, HANDSHAKE_SIZE)
|
||
if _, err := io.ReadFull(conn, c1); err != nil {
|
||
return fmt.Errorf("读取 C1 失败: %w", err)
|
||
}
|
||
|
||
// C1 结构:
|
||
// - time (4 bytes): 时间戳
|
||
// - zero (4 bytes): 必须为0(简单握手)或版本信息(复杂握手)
|
||
// - random (1528 bytes): 随机数据
|
||
c1Time := binary.BigEndian.Uint32(c1[0:4])
|
||
c1Zero := binary.BigEndian.Uint32(c1[4:8])
|
||
|
||
log.Printf("📡 [RTMP Handshake] C1: time=%d zero=%d", c1Time, c1Zero)
|
||
|
||
// 3. 生成 S0 + S1 + S2
|
||
s0 := []byte{RTMP_VERSION}
|
||
|
||
// S1 (1536 bytes): time(4) + zero(4) + random(1528)
|
||
s1 := make([]byte, HANDSHAKE_SIZE)
|
||
binary.BigEndian.PutUint32(s1[0:4], uint32(time.Now().Unix())) // time
|
||
binary.BigEndian.PutUint32(s1[4:8], 0) // zero
|
||
rand.Read(s1[8:]) // random
|
||
|
||
// S2 (1536 bytes): 回显 C1(简单握手)
|
||
// - time (4 bytes): C1 的时间戳
|
||
// - time2 (4 bytes): S1 的时间戳
|
||
// - random echo (1528 bytes): C1 的随机数据
|
||
s2 := make([]byte, HANDSHAKE_SIZE)
|
||
copy(s2[0:4], c1[0:4]) // 回显 C1 时间戳
|
||
binary.BigEndian.PutUint32(s2[4:8], binary.BigEndian.Uint32(s1[0:4])) // S1 时间戳
|
||
copy(s2[8:], c1[8:]) // 回显 C1 随机数据
|
||
|
||
// 4. 发送 S0 + S1 + S2
|
||
response := make([]byte, 0, 1+HANDSHAKE_SIZE*2)
|
||
response = append(response, s0...)
|
||
response = append(response, s1...)
|
||
response = append(response, s2...)
|
||
|
||
if _, err := conn.Write(response); err != nil {
|
||
return fmt.Errorf("发送 S0+S1+S2 失败: %w", err)
|
||
}
|
||
|
||
// 5. 接收 C2 (1536 bytes)
|
||
c2 := make([]byte, HANDSHAKE_SIZE)
|
||
if _, err := io.ReadFull(conn, c2); err != nil {
|
||
return fmt.Errorf("读取 C2 失败: %w", err)
|
||
}
|
||
|
||
// 验证 C2 (可选,简单握手可以跳过)
|
||
// C2 应该回显 S1 的数据
|
||
c2Time := binary.BigEndian.Uint32(c2[0:4])
|
||
if c2Time != binary.BigEndian.Uint32(s1[0:4]) {
|
||
log.Printf("⚠️ [RTMP Handshake] C2 时间戳不匹配 (收到: %d, 期望: %d)", c2Time, binary.BigEndian.Uint32(s1[0:4]))
|
||
// 不返回错误,继续处理
|
||
}
|
||
|
||
// 简单验证:比较随机数据的前几个字节
|
||
if !bytes.Equal(c2[8:16], s1[8:16]) {
|
||
log.Printf("⚠️ [RTMP Handshake] C2 随机数据不匹配")
|
||
// 不返回错误,继续处理
|
||
}
|
||
|
||
log.Printf("✅ [RTMP Handshake] 握手完成 | remote=%s", conn.RemoteAddr())
|
||
return nil
|
||
}
|
||
|
||
// DoClientHandshake 执行 RTMP 握手(客户端)
|
||
// 用于测试或代理场景
|
||
func DoClientHandshake(conn net.Conn, timeout time.Duration) error {
|
||
// 设置超时
|
||
conn.SetDeadline(time.Now().Add(timeout))
|
||
defer conn.SetDeadline(time.Time{})
|
||
|
||
// 1. 发送 C0 + C1
|
||
c0 := []byte{RTMP_VERSION}
|
||
|
||
c1 := make([]byte, HANDSHAKE_SIZE)
|
||
binary.BigEndian.PutUint32(c1[0:4], uint32(time.Now().Unix())) // time
|
||
binary.BigEndian.PutUint32(c1[4:8], 0) // zero
|
||
rand.Read(c1[8:]) // random
|
||
|
||
if _, err := conn.Write(append(c0, c1...)); err != nil {
|
||
return fmt.Errorf("发送 C0+C1 失败: %w", err)
|
||
}
|
||
|
||
// 2. 接收 S0 + S1 + S2
|
||
s0s1s2 := make([]byte, 1+HANDSHAKE_SIZE*2)
|
||
if _, err := io.ReadFull(conn, s0s1s2); err != nil {
|
||
return fmt.Errorf("读取 S0+S1+S2 失败: %w", err)
|
||
}
|
||
|
||
// 验证服务器版本
|
||
serverVersion := s0s1s2[0]
|
||
if serverVersion != RTMP_VERSION {
|
||
log.Printf("⚠️ [RTMP Handshake] 服务器版本: %d", serverVersion)
|
||
}
|
||
|
||
s1 := s0s1s2[1 : 1+HANDSHAKE_SIZE]
|
||
|
||
// 3. 发送 C2 (回显 S1)
|
||
c2 := make([]byte, HANDSHAKE_SIZE)
|
||
copy(c2[0:4], s1[0:4]) // 回显 S1 时间戳
|
||
binary.BigEndian.PutUint32(c2[4:8], binary.BigEndian.Uint32(c1[0:4])) // C1 时间戳
|
||
copy(c2[8:], s1[8:]) // 回显 S1 随机数据
|
||
|
||
if _, err := conn.Write(c2); err != nil {
|
||
return fmt.Errorf("发送 C2 失败: %w", err)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
|