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

842 lines
26 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 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
}