Files
monibuca/plugin/gb28181pro/platform.go
T
2025-03-03 09:25:01 +08:00

842 lines
26 KiB
Go

package plugin_gb28181pro
import (
"context"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"m7s.live/v5"
"github.com/emiago/sipgo"
"github.com/emiago/sipgo/sip"
"github.com/icholy/digest"
"m7s.live/v5/pkg/task"
gb28181 "m7s.live/v5/plugin/gb28181pro/pkg"
)
// Platform 表示GB28181平台的运行时实例
type Platform struct {
task.Job `gorm:"-:all"` // 使用TickTask,并且排除 gorm 序列化
PlatformModel *gb28181.PlatformModel
// SIP相关字段,不存储到数据库
Client *sipgo.Client `gorm:"-" json:"-"` // SIP客户端
DialogClient *sipgo.DialogClient `gorm:"-" json:"-"` // SIP对话客户端
Recipient sip.Uri `gorm:"-" json:"-"` // 接收者地址
ContactHDR *sip.ContactHeader `gorm:"-" json:"-"` // 联系人头部
UserAgentHDR sip.Header `gorm:"-" json:"-"`
MaxForwardsHDR sip.MaxForwardsHeader `gorm:"-" json:"-"`
// 运行时字段
KeepAliveReply int `gorm:"-" json:"keepAliveReply"` // KeepAliveReply表示心跳未回复次数
CallID string `gorm:"-" json:"callId"` // CallID表示SIP会话的标识符
SN int
eventChan chan any
// 插件配置
plugin *GB28181ProPlugin
ctx context.Context
}
func (p *Platform) init() {
p.ctx = context.Background()
client, err := sipgo.NewClient(p.plugin.ua, sipgo.WithClientHostname(p.PlatformModel.DeviceIP), sipgo.WithClientPort(p.PlatformModel.DevicePort))
if err != nil {
p.Error("failed to create sip client: %v", err)
}
p.Client = client
userAgentHeader := sip.NewHeader("User-Agent", "M7S/"+m7s.Version)
p.UserAgentHDR = userAgentHeader
// 创建注册请求的目标URI,使用上级平台的信息
recipient := sip.Uri{
User: p.PlatformModel.ServerGBID,
Host: p.PlatformModel.ServerIP,
Port: p.PlatformModel.ServerPort,
}
p.Recipient = recipient
// 设置联系人头部,使用本地平台的信息
contactHdr := sip.ContactHeader{
Address: sip.Uri{
User: p.PlatformModel.DeviceGBID,
Host: p.PlatformModel.DeviceIP,
Port: p.PlatformModel.DevicePort,
},
}
p.ContactHDR = &contactHdr
// 创建对话客户端
p.DialogClient = sipgo.NewDialogClient(p.Client, *p.ContactHDR)
p.MaxForwardsHDR = sip.MaxForwardsHeader(70)
p.plugin.platforms.Add(p)
}
func (p *Platform) Start() error {
if _, ok := p.plugin.platforms.Get(p.ID); !ok {
p.init()
}
register := NewRegister(p, "firstRegister")
register.OnStart(func() {
register.Tick(nil)
})
p.AddTask(register)
return nil
}
// getResponse 从事务中获取响应
func (p *Platform) getResponse(tx sip.ClientTransaction) (*sip.Response, error) {
select {
case <-tx.Done():
return nil, fmt.Errorf("事务已终止")
case res := <-tx.Responses():
return res, nil
}
}
// Keepalive 发送心跳请求到上级平台
func (p *Platform) Keepalive(ctx context.Context) (*sipgo.DialogClientSession, error) {
req := sip.NewRequest("MESSAGE", p.Recipient)
req.SetTransport(strings.ToUpper(p.PlatformModel.Transport))
customCallID := fmt.Sprintf("%s-%d@%s", p.PlatformModel.DeviceGBID, time.Now().Unix(), p.PlatformModel.ServerIP)
callID := sip.CallIDHeader(customCallID)
req.AppendHeader(&callID)
csqHeader := sip.CSeqHeader{
SeqNo: uint32(p.SN),
MethodName: "REGISTER",
}
p.SN++
req.AppendHeader(&csqHeader)
// 添加From头部
fromHeader := sip.FromHeader{
Address: sip.Uri{
User: p.PlatformModel.DeviceGBID,
Host: p.PlatformModel.ServerGBDomain,
},
Params: sip.NewParams(),
}
fromHeader.Params.Add("tag", sip.GenerateTagN(16))
req.AppendHeader(&fromHeader)
// 添加To头部
toHeader := sip.ToHeader{
Address: sip.Uri{
User: p.PlatformModel.ServerGBID,
Host: p.PlatformModel.ServerGBDomain,
},
}
req.AppendHeader(&toHeader)
viaHeader := sip.ViaHeader{
ProtocolName: "SIP",
ProtocolVersion: "2.0",
Transport: p.PlatformModel.Transport,
Host: p.PlatformModel.DeviceIP,
Port: p.PlatformModel.DevicePort,
Params: sip.NewParams(),
}
viaHeader.Params.Add("branch", sip.GenerateBranchN(16)).Add("rport", "")
req.AppendHeader(&viaHeader)
req.SetBody(gb28181.BuildKeepAliveXML(p.SN, p.PlatformModel.DeviceGBID))
p.SN++
tx, err := p.Client.TransactionRequest(ctx, req)
if err != nil {
p.Error("keepalive", "error", err.Error())
return nil, fmt.Errorf("创建事务失败: %v", err)
}
defer tx.Terminate()
res, err := p.getResponse(tx)
if err != nil {
p.Error("keepalive", "error", err.Error())
return nil, err
}
if res.StatusCode != 200 {
p.Error("keepalive", "status", res.StatusCode)
return nil, fmt.Errorf("心跳失败,状态码: %d", res.StatusCode)
}
p.Info("keepalive", "response", res.String())
return nil, nil
}
// Unregister 发送注销请求到上级平台
func (p *Platform) Unregister(ctx context.Context) (*sipgo.DialogClientSession, error) {
// 创建注销请求的目标URI
recipient := sip.Uri{
User: p.PlatformModel.ServerGBID,
Host: p.PlatformModel.ServerIP,
Port: p.PlatformModel.ServerPort,
}
// 创建基本的REGISTER请求
req := sip.NewRequest(sip.REGISTER, recipient)
// 添加Contact头部
contactStr := fmt.Sprintf("<sip:%s@%s:%d>", p.PlatformModel.DeviceGBID, p.PlatformModel.DeviceIP, p.PlatformModel.DevicePort)
req.AppendHeader(sip.NewHeader("Contact", contactStr))
// 添加From头部
fromHeader := sip.FromHeader{
Address: sip.Uri{
User: p.PlatformModel.DeviceGBID,
Host: p.PlatformModel.ServerGBDomain,
},
Params: sip.NewParams(),
}
fromHeader.Params.Add("tag", sip.GenerateTagN(16))
req.AppendHeader(&fromHeader)
// 添加To头部
toHeader := sip.ToHeader{
Address: sip.Uri{
User: p.PlatformModel.DeviceGBID,
Host: p.PlatformModel.ServerGBDomain,
},
}
req.AppendHeader(&toHeader)
// 添加Expires头部,设置为0表示注销
req.AppendHeader(sip.NewHeader("Expires", "0"))
// 设置传输协议
req.SetTransport(strings.ToUpper(p.PlatformModel.Transport))
// 发送请求并获取响应
tx, err := p.Client.TransactionRequest(ctx, req)
if err != nil {
return nil, fmt.Errorf("创建事务失败: %v", err)
}
defer tx.Terminate()
// 获取响应
res, err := p.getResponse(tx)
if err != nil {
return nil, fmt.Errorf("获取响应失败: %v", err)
}
// 检查响应状态
if res.StatusCode != 200 {
return nil, fmt.Errorf("注销失败,状态码: %d", res.StatusCode)
}
return nil, nil
}
// PlatformKeepAliveTask 任务
type PlatformKeepAliveTask struct {
task.TickTask
platform *Platform
}
func (k *PlatformKeepAliveTask) GetTickInterval() time.Duration {
return time.Second * time.Duration(k.platform.PlatformModel.KeepTimeout)
}
func (k *PlatformKeepAliveTask) Tick(any) {
if !k.platform.PlatformModel.Enable {
return
}
ctx := context.Background()
_, err := k.platform.Keepalive(ctx)
if err != nil {
k.platform.KeepAliveReply++
k.Error("keepalive", "error", err.Error())
if k.platform.KeepAliveReply >= 3 {
k.platform.PlatformModel.Status = false
k.Stop(fmt.Errorf("max keepalive retries reached"))
// 重新启动注册任务
//k.platform.Start()
}
} else {
k.platform.KeepAliveReply = 0
}
}
// OnMessage 处理来自平台的消息
func (p *Platform) OnMessage(req *sip.Request, tx sip.ServerTransaction, msg *gb28181.Message) error {
// 更新平台状态
p.PlatformModel.UpdateTime = time.Now().Format("2006-01-02 15:04:05")
// 根据消息类型处理不同的消息
switch msg.CmdType {
case "Catalog":
// 处理目录请求
return p.handleCatalog(req, tx, msg)
case "DeviceControl":
// 处理设备控制请求
return p.handleDeviceControl(req, tx, msg)
case "DeviceInfo":
// 处理设备信息请求
return p.handleDeviceInfo(req, tx, msg)
case "Alarm":
// 处理报警消息
return p.handleAlarm(req, tx, msg)
case "MobilePosition":
// 处理移动位置信息
return p.handleMobilePosition(req, tx, msg)
default:
// 不支持的消息类型,返回错误
response := sip.NewResponseFromRequest(req, sip.StatusUnsupportedMediaType, "Unsupported message type", nil)
if err := tx.Respond(response); err != nil {
return fmt.Errorf("respond error: %v", err)
}
return fmt.Errorf("unsupported message type: %s", msg.CmdType)
}
}
// handleCatalog 处理目录请求
func (p *Platform) handleCatalog(req *sip.Request, tx sip.ServerTransaction, msg *gb28181.Message) error {
// 回复 200 OK
err := tx.Respond(sip.NewResponseFromRequest(req, http.StatusOK, "OK", nil))
if err != nil {
return err
}
// 获取 SN 和 FromTag
sn := strconv.Itoa(msg.SN)
fromTag, _ := req.From().Params.Get("tag")
p.plugin.Info("catalog", "sn", sn, "fromTag", fromTag)
// 打印平台ID
p.plugin.Info("catalog query platform_id", "platform_id", p.PlatformModel.ID)
// 查询通道列表
var channels []gb28181.CommonGBChannel
if p.plugin.DB != nil {
if err := p.plugin.DB.Table("channel_gb28181pro c").
Select(`c.id as gb_id,
c.device_db_id as gb_device_db_id,
c.stream_push_id,
c.stream_proxy_id,
c.create_time,
c.update_time,
COALESCE(nullif(pc.custom_device_id,''), nullif(c.gb_device_id,''), nullif(c.device_id,'')) as gb_device_id,
COALESCE(nullif(pc.custom_name,''), nullif(c.gb_name,''), nullif(c.name,'')) as gb_name,
COALESCE(nullif(pc.custom_manufacturer,''), nullif(c.gb_manufacturer,''), nullif(c.manufacturer,'')) as gb_manufacturer,
COALESCE(nullif(pc.custom_model,''), nullif(c.gb_model,''), nullif(c.model,'')) as gb_model,
COALESCE(nullif(pc.custom_owner,''), nullif(c.gb_owner,''), nullif(c.owner,'')) as gb_owner,
COALESCE(nullif(pc.custom_civil_code,''), nullif(c.gb_civil_code,''), nullif(c.civil_code,'')) as gb_civil_code,
COALESCE(nullif(pc.custom_block,''), nullif(c.gb_block,''), nullif(c.block,'')) as gb_block,
COALESCE(nullif(pc.custom_address,''), nullif(c.gb_address,''), nullif(c.address,'')) as gb_address,
COALESCE(nullif(pc.custom_parental,''), nullif(c.gb_parental,''), nullif(c.parental,'')) as gb_parental,
COALESCE(nullif(pc.custom_parent_id,''), nullif(c.gb_parent_id,''), nullif(c.parent_id,'')) as gb_parent_id,
COALESCE(nullif(pc.custom_safety_way,''), nullif(c.gb_safety_way,''), nullif(c.safety_way,'')) as gb_safety_way,
COALESCE(nullif(pc.custom_register_way,''), nullif(c.gb_register_way,''), nullif(c.register_way,'')) as gb_register_way,
COALESCE(nullif(pc.custom_cert_num,''), nullif(c.gb_cert_num,''), nullif(c.cert_num,'')) as gb_cert_num,
COALESCE(nullif(pc.custom_certifiable,''), nullif(c.gb_certifiable,''), nullif(c.certifiable,'')) as gb_certifiable,
COALESCE(nullif(pc.custom_err_code,''), nullif(c.gb_err_code,''), nullif(c.err_code,'')) as gb_err_code,
COALESCE(nullif(pc.custom_end_time,''), nullif(c.gb_end_time,''), nullif(c.end_time,'')) as gb_end_time,
COALESCE(nullif(pc.custom_secrecy,''), nullif(c.gb_secrecy,''), nullif(c.secrecy,'')) as gb_secrecy,
COALESCE(nullif(pc.custom_ip_address,''), nullif(c.gb_ip_address,''), nullif(c.ip_address,'')) as gb_ip_address,
COALESCE(nullif(pc.custom_port,''), nullif(c.gb_port,''), nullif(c.port,'')) as gb_port,
COALESCE(nullif(pc.custom_password,''), nullif(c.gb_password,''), nullif(c.password,'')) as gb_password,
COALESCE(nullif(pc.custom_status,''), nullif(c.gb_status,''), nullif(c.status,'')) as gb_status,
COALESCE(nullif(pc.custom_longitude,''), nullif(c.gb_longitude,''), nullif(c.longitude,'')) as gb_longitude,
COALESCE(nullif(pc.custom_latitude,''), nullif(c.gb_latitude,''), nullif(c.latitude,'')) as gb_latitude,
COALESCE(nullif(pc.custom_ptz_type,''), nullif(c.gb_ptz_type,''), nullif(c.ptz_type,'')) as gb_ptz_type,
COALESCE(nullif(pc.custom_position_type,''), nullif(c.gb_position_type,''), nullif(c.position_type,'')) as gb_position_type,
COALESCE(nullif(pc.custom_room_type,''), nullif(c.gb_room_type,''), nullif(c.room_type,'')) as gb_room_type,
COALESCE(nullif(pc.custom_use_type,''), nullif(c.gb_use_type,''), nullif(c.use_type,'')) as gb_use_type,
COALESCE(nullif(pc.custom_supply_light_type,''), nullif(c.gb_supply_light_type,''), nullif(c.supply_light_type,'')) as gb_supply_light_type,
COALESCE(nullif(pc.custom_direction_type,''), nullif(c.gb_direction_type,''), nullif(c.direction_type,'')) as gb_direction_type,
COALESCE(nullif(pc.custom_resolution,''), nullif(c.gb_resolution,''), nullif(c.resolution,'')) as gb_resolution,
COALESCE(nullif(pc.custom_business_group_id,''), nullif(c.gb_business_group_id,''), nullif(c.business_group_id,'')) as gb_business_group_id,
COALESCE(nullif(pc.custom_download_speed,''), nullif(c.gb_download_speed,''), nullif(c.download_speed,'')) as gb_download_speed,
COALESCE(nullif(pc.custom_svc_space_support_mod,''), nullif(c.gb_svc_space_support_mod,''), nullif(c.svc_space_support_mod,'')) as gb_svc_space_support_mod,
COALESCE(nullif(pc.custom_svc_time_support_mode,''), nullif(c.gb_svc_time_support_mode,''), nullif(c.svc_time_support_mode,'')) as gb_svc_time_support_mode`).
Joins("left join platform_channel_gb28181pro pc on c.id = pc.device_channel_id").
Where("pc.platform_id = ?", p.PlatformModel.ID).
Find(&channels).Error; err != nil {
return fmt.Errorf("query channels error: %v", err)
}
}
// 发送目录响应,无论是否有通道
p.plugin.Info("get channels success", channels)
return p.sendCatalogResponse(req, sn, fromTag, channels)
}
// CreateRequest 创建 SIP 请求
func (p *Platform) CreateRequest(method string) *sip.Request {
request := sip.NewRequest(sip.RequestMethod(method), p.Recipient)
//request.SetDestination(p.Recipient.String())
return request
}
// sendCatalogResponse 发送目录响应
func (p *Platform) sendCatalogResponse(req *sip.Request, sn string, fromTag string, channels []gb28181.CommonGBChannel) error {
// 如果没有通道,发送一个空的目录列表
if len(channels) == 0 {
request := p.CreateRequest("MESSAGE")
// 设置From头部
fromHeader := sip.FromHeader{
Address: sip.Uri{
User: p.PlatformModel.DeviceGBID,
Host: p.PlatformModel.ServerGBDomain,
},
Params: sip.NewParams(),
}
fromHeader.Params.Add("tag", fromTag)
request.AppendHeader(&fromHeader)
// 添加To头部
toHeader := sip.ToHeader{
Address: sip.Uri{
User: p.PlatformModel.ServerGBID,
Host: p.PlatformModel.ServerGBDomain,
},
}
request.AppendHeader(&toHeader)
// 添加Via头部
viaHeader := sip.ViaHeader{
ProtocolName: "SIP",
ProtocolVersion: "2.0",
Transport: p.PlatformModel.Transport,
Host: p.PlatformModel.DeviceIP,
Port: p.PlatformModel.DevicePort,
Params: sip.NewParams(),
}
viaHeader.Params.Add("branch", sip.GenerateBranchN(16)).Add("rport", "")
request.AppendHeader(&viaHeader)
request.SetTransport(req.Transport())
contentTypeHeader := sip.ContentTypeHeader("Application/MANSCDP+xml")
request.AppendHeader(&contentTypeHeader)
// 空目录列表XML
xmlContent := fmt.Sprintf(`<?xml version="1.0" encoding="GB2312"?>
<Response>
<CmdType>Catalog</CmdType>
<SN>%s</SN>
<DeviceID>%s</DeviceID>
<SumNum>0</SumNum>
<DeviceList Num="0">
</DeviceList>
</Response>`, sn, p.PlatformModel.DeviceGBID)
request.SetBody([]byte(xmlContent))
_, err := p.Client.Do(p.ctx, request)
if err != nil {
p.plugin.Error("p.Client.Do", err)
}
return err
}
// 有通道时,为每个通道单独发送一个XML
for i, channel := range channels {
request := p.CreateRequest("MESSAGE")
// 设置From头部
fromHeader := sip.FromHeader{
Address: sip.Uri{
User: p.PlatformModel.DeviceGBID,
Host: p.PlatformModel.ServerGBDomain,
},
Params: sip.NewParams(),
}
fromHeader.Params.Add("tag", fromTag)
request.AppendHeader(&fromHeader)
// 添加To头部
toHeader := sip.ToHeader{
Address: sip.Uri{
User: p.PlatformModel.ServerGBID,
Host: p.PlatformModel.ServerGBDomain,
},
}
request.AppendHeader(&toHeader)
// 添加Via头部
viaHeader := sip.ViaHeader{
ProtocolName: "SIP",
ProtocolVersion: "2.0",
Transport: p.PlatformModel.Transport,
Host: p.PlatformModel.DeviceIP,
Port: p.PlatformModel.DevicePort,
Params: sip.NewParams(),
}
viaHeader.Params.Add("branch", sip.GenerateBranchN(16)).Add("rport", "")
request.AppendHeader(&viaHeader)
request.SetTransport(req.Transport())
contentTypeHeader := sip.ContentTypeHeader("Application/MANSCDP+xml")
request.AppendHeader(&contentTypeHeader)
// 为单个通道创建XML
channelXML := p.buildChannelItem(channel)
xmlContent := fmt.Sprintf(`<?xml version="1.0" encoding="GB2312"?>
<Response>
<CmdType>Catalog</CmdType>
<SN>%s</SN>
<DeviceID>%s</DeviceID>
<SumNum>%d</SumNum>
<DeviceList Num="1">
%s
</DeviceList>
</Response>`, sn, p.PlatformModel.DeviceGBID, len(channels), channelXML)
request.SetBody([]byte(xmlContent))
_, err := p.Client.Do(p.ctx, request)
if err != nil {
p.Error("send catalog response", "error", err.Error(), "channel_index", i)
return err
}
// 添加短暂延迟以防止发送过快
time.Sleep(time.Millisecond * 50)
}
return nil
}
// buildChannelItem 构建单个通道的XML项
func (p *Platform) buildChannelItem(channel gb28181.CommonGBChannel) string {
// 确保字符串字段不为空
deviceID := channel.GbDeviceID
if deviceID == "" {
deviceID = "unknown_device" // 如果没有设备ID,使用默认值
}
name := channel.GbName
if name == "" {
name = "未命名设备"
}
manufacturer := channel.GbManufacturer
if manufacturer == "" {
manufacturer = "未知厂商"
}
model := channel.GbModel
if model == "" {
model = "未知型号"
}
owner := channel.GbOwner
if owner == "" {
owner = "未知所有者"
}
address := channel.GbAddress
if address == "" {
address = "未知地址"
}
parentID := channel.GbParentID
if parentID == "" {
parentID = p.PlatformModel.DeviceGBID // 使用平台ID作为父ID
}
return fmt.Sprintf(`<Item>
<DeviceID>%s</DeviceID>
<Name>%s</Name>
<Manufacturer>%s</Manufacturer>
<Model>%s</Model>
<Owner>%s</Owner>
<Address>%s</Address>
<RegisterWay>%d</RegisterWay>
<Secrecy>%d</Secrecy>
<ParentID>%s</ParentID>
<Parental>%d</Parental>
<SafetyWay>%d</SafetyWay>
<Status>ON</Status>
<Info>
</Info>
</Item>`, deviceID, name, manufacturer, model,
owner, address,
channel.GbRegisterWay, // 直接使用整数值
channel.GbSecrecy, // 直接使用整数值
parentID,
channel.GbParental, // 直接使用整数值
channel.GbSafetyWay) // 直接使用整数值
}
// handleDeviceControl 处理设备控制请求
func (p *Platform) handleDeviceControl(req *sip.Request, tx sip.ServerTransaction, msg *gb28181.Message) error {
// TODO: 实现设备控制请求处理
response := sip.NewResponseFromRequest(req, sip.StatusOK, "OK", nil)
return tx.Respond(response)
}
// handleDeviceInfo 处理设备信息查询请求
func (p *Platform) handleDeviceInfo(req *sip.Request, tx sip.ServerTransaction, msg *gb28181.Message) error {
// 先回复200 OK
err := tx.Respond(sip.NewResponseFromRequest(req, http.StatusOK, "OK", nil))
if err != nil {
return fmt.Errorf("respond error: %v", err)
}
// 获取 SN 和 FromTag
sn := strconv.Itoa(msg.SN)
fromTag, _ := req.From().Params.Get("tag")
// 获取请求的设备ID
channelId := msg.DeviceID
// 1. 判断是否是查询平台自身信息
if p.PlatformModel.DeviceGBID == channelId {
// 如果是查询平台信息,直接返回平台信息
return p.sendDeviceInfoResponse(req, nil, sn, fromTag)
}
// 2. 查询通道信息
var channel gb28181.CommonGBChannel
if p.plugin.DB != nil {
if err := p.plugin.DB.Where("platform_id = ? AND gb_device_id = ?", p.PlatformModel.ID, channelId).First(&channel).Error; err != nil {
// 通道不存在,返回404
response := sip.NewResponseFromRequest(req, sip.StatusNotFound, "channel not found or offline", nil)
return tx.Respond(response)
}
}
// 3. 判断通道类型
if channel.GbDeviceDbID == 0 {
// 非国标通道不支持设备信息查询
response := sip.NewResponseFromRequest(req, sip.StatusForbidden, "non-gb channel not supported", nil)
return tx.Respond(response)
}
// 4. 查询设备信息
var device Device
if p.plugin.DB != nil {
if err := p.plugin.DB.First(&device, channel.GbDeviceDbID).Error; err != nil {
// 设备不存在,返回404
response := sip.NewResponseFromRequest(req, sip.StatusNotFound, "device not found", nil)
return tx.Respond(response)
}
}
// 5. 发送设备信息响应
return p.sendDeviceInfoResponse(req, &device, sn, fromTag)
}
// sendDeviceInfoResponse 发送设备信息响应
func (p *Platform) sendDeviceInfoResponse(req *sip.Request, device *Device, sn string, fromTag string) error {
request := p.CreateRequest("MESSAGE")
// 设置From头部
fromHeader := sip.FromHeader{
Address: sip.Uri{
User: p.PlatformModel.DeviceGBID,
Host: p.PlatformModel.ServerGBDomain,
},
Params: sip.NewParams(),
}
fromHeader.Params.Add("tag", fromTag)
request.AppendHeader(&fromHeader)
// 添加To头部
toHeader := sip.ToHeader{
Address: sip.Uri{
User: p.PlatformModel.ServerGBID,
Host: p.PlatformModel.ServerGBDomain,
},
}
req.AppendHeader(&toHeader)
// 添加Via头部
viaHeader := sip.ViaHeader{
ProtocolName: "SIP",
ProtocolVersion: "2.0",
Transport: p.PlatformModel.Transport,
Host: p.PlatformModel.DeviceIP,
Port: p.PlatformModel.DevicePort,
Params: sip.NewParams(),
}
viaHeader.Params.Add("branch", sip.GenerateBranchN(16)).Add("rport", "")
req.AppendHeader(&viaHeader)
contentTypeHeader := sip.ContentTypeHeader("Application/MANSCDP+xml")
request.AppendHeader(&contentTypeHeader)
// 构建响应XML
var xmlContent string
if device == nil {
// 返回平台信息
xmlContent = fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
<Response>
<CmdType>DeviceInfo</CmdType>
<SN>%s</SN>
<DeviceID>%s</DeviceID>
<Result>OK</Result>
<Manufacturer>%s</Manufacturer>
<Model>%s</Model>
<Firmware>%s</Firmware>
<Channel>%d</Channel>
</Response>`, sn, p.PlatformModel.DeviceGBID, p.PlatformModel.Manufacturer, p.PlatformModel.Model, "", p.PlatformModel.ChannelCount)
} else {
// 返回设备信息
xmlContent = fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
<Response>
<CmdType>DeviceInfo</CmdType>
<SN>%s</SN>
<DeviceID>%s</DeviceID>
<Result>OK</Result>
<Manufacturer>%s</Manufacturer>
<Model>%s</Model>
<Firmware>%s</Firmware>
<Channel>%d</Channel>
</Response>`, sn, device.DeviceID, device.Manufacturer, device.Model, device.Firmware, device.ChannelCount)
}
request.SetBody([]byte(xmlContent))
_, err := p.Client.Do(p, request)
return err
}
// handleAlarm 处理报警消息
func (p *Platform) handleAlarm(req *sip.Request, tx sip.ServerTransaction, msg *gb28181.Message) error {
// TODO: 实现报警消息处理
response := sip.NewResponseFromRequest(req, sip.StatusOK, "OK", nil)
return tx.Respond(response)
}
// handleMobilePosition 处理移动位置信息
func (p *Platform) handleMobilePosition(req *sip.Request, tx sip.ServerTransaction, msg *gb28181.Message) error {
// TODO: 实现移动位置信息处理
response := sip.NewResponseFromRequest(req, sip.StatusOK, "OK", nil)
return tx.Respond(response)
}
// GetKey 返回平台的唯一标识符
func (p *Platform) GetKey() uint32 {
return p.PlatformModel.ID
}
// Register 执行注册流程
func (p *Platform) DoRegister(ctx context.Context) error {
// 创建基本的REGISTER请求
req := sip.NewRequest(sip.REGISTER, p.Recipient)
//callid
customCallID := fmt.Sprintf("%d@%s", time.Now().Unix(), p.PlatformModel.DeviceIP)
callID := sip.CallIDHeader(customCallID)
req.AppendHeader(&callID)
//cseqheader
csqHeader := sip.CSeqHeader{
SeqNo: uint32(p.SN),
MethodName: "REGISTER",
}
p.SN++
req.AppendHeader(&csqHeader)
// 设置From头部,使用本地平台的信息
fromHdr := sip.FromHeader{
Address: sip.Uri{
User: p.PlatformModel.DeviceGBID,
Host: p.PlatformModel.ServerGBDomain,
},
Params: sip.NewParams(),
}
fromHdr.Params.Add("tag", sip.GenerateTagN(16))
req.AppendHeader(&fromHdr)
// 添加To头部
toHeader := sip.ToHeader{
Address: sip.Uri{
User: p.PlatformModel.DeviceGBID,
Host: p.PlatformModel.ServerGBDomain,
},
}
req.AppendHeader(&toHeader)
// 添加Via头部
viaHeader := sip.ViaHeader{
ProtocolName: "SIP",
ProtocolVersion: "2.0",
Transport: p.PlatformModel.Transport,
Host: p.PlatformModel.DeviceIP,
Port: p.PlatformModel.DevicePort,
Params: sip.NewParams(),
}
viaHeader.Params.Add("branch", sip.GenerateBranchN(16)).Add("rport", "")
req.AppendHeader(&viaHeader)
req.AppendHeader(&p.MaxForwardsHDR)
// 添加Contact头部
req.AppendHeader(p.ContactHDR)
req.AppendHeader(p.UserAgentHDR)
// 添加Expires头部
req.AppendHeader(sip.NewHeader("Expires", fmt.Sprintf("%d", p.PlatformModel.Expires)))
contentLengthHeader := sip.ContentLengthHeader(0)
req.AppendHeader(&contentLengthHeader)
// 设置传输协议
req.SetTransport(strings.ToUpper(p.PlatformModel.Transport))
tx, err := p.Client.TransactionRequest(ctx, req)
if err != nil {
p.Error("register", "error", err.Error())
return fmt.Errorf("创建事务失败: %v", err)
}
defer tx.Terminate()
// 获取响应
res, err := p.getResponse(tx)
if err != nil {
p.Error("register", "error", err.Error())
return err
}
// 处理401未授权响应
if res.StatusCode == 401 {
// 获取WWW-Authenticate头部
wwwAuth := res.GetHeader("WWW-Authenticate")
if wwwAuth == nil {
p.Error("register", "error", "no auth challenge")
return fmt.Errorf("no auth challenge")
}
// 解析认证质询
chal, err := digest.ParseChallenge(wwwAuth.Value())
if err != nil {
p.Error("register", "error", err.Error())
return err
}
// 生成认证响应
cred, _ := digest.Digest(chal, digest.Options{
Method: req.Method.String(),
URI: p.Recipient.Host,
Username: p.PlatformModel.Username,
Password: p.PlatformModel.Password,
})
// 创建新的带认证信息的请求
newReq := req.Clone()
newReq.RemoveHeader("Via") // 必须由传输层重新生成
newReq.AppendHeader(sip.NewHeader("Authorization", cred.String()))
// 发送认证请求
tx, err = p.Client.TransactionRequest(ctx, newReq, sipgo.ClientRequestAddVia)
if err != nil {
p.Error("register", "error", err.Error())
return err
}
defer tx.Terminate()
// 获取认证响应
res, err = p.getResponse(tx)
if err != nil {
p.Error("register", "error", err.Error())
return err
}
}
// 检查最终响应状态
if res.StatusCode != 200 {
p.Error("register", "status", res.StatusCode)
return fmt.Errorf("注册失败,状态码: %d", res.StatusCode)
}
p.Info("register", "status", "success")
p.PlatformModel.Status = true
return nil
}