Files
nl-im-service/internal/mediaserver/rtmp_handshake.go
2025-12-15 21:57:21 +08:00

162 lines
4.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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
}