Files
monibuca/plugin/gb28181/platform.go
2025-09-26 15:57:26 +08:00

1405 lines
40 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 (
"bytes"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"time"
"github.com/langhuihui/gotask"
"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/transform"
"m7s.live/v5"
"m7s.live/v5/pkg/util"
"github.com/emiago/sipgo"
"github.com/emiago/sipgo/sip"
"github.com/icholy/digest"
gb28181 "m7s.live/v5/plugin/gb28181/pkg"
)
// Platform 表示GB28181平台的运行时实例
type Platform struct {
task.Job `gorm:"-:all"` // 使用TickTask并且排除 gorm 序列化
PlatformModel *gb28181.PlatformModel
// SIP相关字段不存储到数据库
Client *sipgo.Client `gorm:"-" json:"-"` // SIP客户端
DialogClient *sipgo.DialogClientCache `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表示心跳未回复次数
RegisterCallID string `gorm:"-" json:"registerCallID"` // CallID表示SIP会话的标识符
SN int
// 插件配置
plugin *GB28181Plugin
unRegister bool
channels util.Collection[string, *Channel] `gorm:"-:all"`
register *Register
}
// UTF8ToGB2312 将UTF-8编码的字符串转换为GB2312编码
func UTF8ToGB2312(s string) (string, error) {
reader := transform.NewReader(bytes.NewReader([]byte(s)), simplifiedchinese.GB18030.NewEncoder())
d, err := io.ReadAll(reader)
if err != nil {
return "", err
}
return string(d), nil
}
func NewPlatform(pm *gb28181.PlatformModel, plugin *GB28181Plugin, unRegister bool) *Platform {
p := &Platform{
PlatformModel: pm,
plugin: plugin,
unRegister: unRegister,
}
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", "err", 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.NewDialogClientCache(p.Client, *p.ContactHDR)
p.MaxForwardsHDR = sip.MaxForwardsHeader(70)
return p
}
func (p *Platform) Start() error {
if p.unRegister {
err := p.Unregister()
if err != nil {
p.Error("failed to unregister", "err", err)
}
p.unRegister = false
}
register := NewRegister(p, "firstRegister")
register.OnStart(func() {
register.Tick(nil)
})
p.register = register
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() (*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.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.SetBody(gb28181.BuildKeepAliveXML(p.SN, p.PlatformModel.DeviceGBID))
p.SN++
tx, err := p.Client.TransactionRequest(p, 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
}
// Register 执行注册或注销流程
func (p *Platform) Register(isUnregister bool) error {
// 创建基本的REGISTER请求
req := sip.NewRequest(sip.REGISTER, p.Recipient)
// 设置日志标签
logTag := "register"
if isUnregister {
logTag = "unregister"
}
//callid
if p.RegisterCallID != "" {
callID := sip.CallIDHeader(p.RegisterCallID)
req.AppendHeader(&callID)
} else {
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头部根据是否注销设置不同值
if isUnregister {
req.AppendHeader(sip.NewHeader("Expires", "0"))
} else {
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(p, req)
if err != nil {
p.plugin.Error(logTag, "error", err.Error())
return fmt.Errorf("创建事务失败: %v", err)
}
defer tx.Terminate()
// 获取响应
res, err := p.getResponse(tx)
if err != nil {
p.plugin.Error(logTag, "error", err.Error())
return err
}
// 处理401未授权响应
if res.StatusCode == 401 {
// 获取WWW-Authenticate头部
wwwAuth := res.GetHeader("WWW-Authenticate")
if wwwAuth == nil {
p.plugin.Error(logTag, "error", "no auth challenge")
return fmt.Errorf("no auth challenge")
}
// 解析认证质询
chal, err := digest.ParseChallenge(wwwAuth.Value())
if err != nil {
p.plugin.Error(logTag, "error", err.Error())
return err
}
p.plugin.Debug("received auth challenge",
"realm", chal.Realm,
"nonce", chal.Nonce,
"algorithm", chal.Algorithm,
"qop", chal.QOP)
// 生成认证响应
opts := digest.Options{
Method: req.Method.String(),
URI: "sip:" + p.PlatformModel.ServerGBDomain,
Username: p.PlatformModel.Username,
Password: p.PlatformModel.Password,
Cnonce: sip.GenerateTagN(16),
Count: 1,
}
cred, err := digest.Digest(chal, opts)
if err != nil {
p.plugin.Error("calculating digest failed", "error", err.Error())
return err
}
p.plugin.Debug("calculated response info",
"username", opts.Username,
"uri", opts.URI,
"cnonce", opts.Cnonce,
"count", opts.Count,
"response", cred.Response)
// 创建新的带认证信息的请求
newReq := req.Clone()
newReq.RemoveHeader("Via") // 必须由传输层重新生成
newReq.AppendHeader(sip.NewHeader("Authorization", cred.String()))
newReq.CSeq().SeqNo = uint32(p.SN) // 更新CSeq序号
p.SN++
// 发送认证请求
tx, err = p.Client.TransactionRequest(p, newReq, sipgo.ClientRequestAddVia)
if err != nil {
p.plugin.Error(logTag, "error", err.Error())
return err
}
defer tx.Terminate()
// 获取认证响应
res, err = p.getResponse(tx)
if err != nil {
p.plugin.Error(logTag, "error", err.Error())
return err
}
}
// 检查最终响应状态
if res.StatusCode != 200 {
p.plugin.Error(logTag, "status", res.StatusCode)
return fmt.Errorf("%s失败状态码: %d", logTag, res.StatusCode)
}
p.plugin.Info(logTag, "status", "success")
// 根据操作类型设置状态
p.PlatformModel.Status = !isUnregister
return nil
}
// Unregister 发送注销请求到上级平台
func (p *Platform) Unregister() error {
return p.Register(true)
}
// DoRegister 执行注册流程
func (p *Platform) DoRegister() error {
return p.Register(false)
}
// 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
}
_, err := k.platform.Keepalive()
if err != nil {
k.platform.KeepAliveReply++
k.Error("keepalive", "error", err.Error())
if k.platform.KeepAliveReply >= 3 {
k.platform.PlatformModel.Status = false
// 重新启动注册任务
//k.platform.Start()
platform := k.platform
platform.KeepAliveReply = 0
k.Stop(fmt.Errorf("max keepalive retries reached"))
platform.register.registerType = "firstRegister"
platform.register.Ticker.Reset(time.Second * time.Duration(platform.PlatformModel.Expires))
platform.register.Tick(nil)
}
} 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 "DeviceStatus":
// 处理设备信息请求
return p.handleDeviceStatus(req, tx, msg)
case "PresetQuery":
// 处理预置位查询请求
return p.handlePresetQuery(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.ServerGBID)
// 查询通道列表
var channels []gb28181.DeviceChannel
//if p.plugin.DB != nil {
// if err := p.plugin.DB.Table("gb28181_channel gc").
// Select(`gc.*`).
// Joins("left join gb28181_platform_channel gpc on gc.id=gpc.channel_db_id").
// Where("gpc.platform_server_gb_id = ? and gc.status='ON'", p.PlatformModel.ServerGBID).
// Find(&channels).Error; err != nil {
// return fmt.Errorf("query channels error: %v", err)
// }
//}
for channel := range p.channels.Range {
channels = append(channels, *channel.DeviceChannel)
}
// 发送目录响应,无论是否有通道
p.plugin.Info("get channels success", "channels", 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.DeviceChannel) 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))
// 修正使用TransactionRequest替代Do
tx, err := p.Client.TransactionRequest(p, request)
if err != nil {
p.Error("sendCatalogResponse", "error", err.Error())
return fmt.Errorf("创建事务失败: %v", err)
}
defer tx.Terminate()
// 获取响应
res, err := p.getResponse(tx)
if err != nil {
p.Error("sendCatalogResponse", "error", err.Error())
return err
}
// 处理401未授权响应
if res.StatusCode == 401 {
// 获取WWW-Authenticate头部
wwwAuth := res.GetHeader("WWW-Authenticate")
if wwwAuth == nil {
p.Error("sendCatalogResponse", "error", "no auth challenge")
return fmt.Errorf("no auth challenge")
}
// 解析认证质询
chal, err := digest.ParseChallenge(wwwAuth.Value())
if err != nil {
p.Error("sendCatalogResponse", "error", err.Error())
return err
}
p.plugin.Debug("received auth challenge",
"realm", chal.Realm,
"nonce", chal.Nonce,
"algorithm", chal.Algorithm,
"qop", chal.QOP)
// 生成认证响应
opts := digest.Options{
Method: request.Method.String(),
URI: "sip:" + p.PlatformModel.ServerGBDomain,
Username: p.PlatformModel.Username,
Password: p.PlatformModel.Password,
Cnonce: sip.GenerateTagN(16),
Count: 1,
}
cred, err := digest.Digest(chal, opts)
if err != nil {
p.Error("calculating digest failed", "error", err.Error())
return err
}
p.plugin.Debug("calculated response info",
"username", opts.Username,
"uri", opts.URI,
"cnonce", opts.Cnonce,
"count", opts.Count,
"response", cred.Response)
// 创建新的带认证信息的请求
newReq := request.Clone()
newReq.RemoveHeader("Via") // 必须由传输层重新生成
newReq.AppendHeader(sip.NewHeader("Authorization", cred.String()))
// 发送认证请求
tx, err = p.Client.TransactionRequest(p, newReq, sipgo.ClientRequestAddVia)
if err != nil {
p.Error("sendCatalogResponse", "error", err.Error())
return err
}
defer tx.Terminate()
// 获取认证响应
res, err = p.getResponse(tx)
if err != nil {
p.Error("sendCatalogResponse", "error", err.Error())
return err
}
}
// 检查最终响应状态
if res.StatusCode != 200 {
p.Error("sendCatalogResponse", "status", res.StatusCode)
return fmt.Errorf("发送目录响应失败,状态码: %d", res.StatusCode)
}
return nil
}
// 有通道时为每个通道单独发送一个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))
// 修正使用TransactionRequest替代Do
tx, err := p.Client.TransactionRequest(p, request)
if err != nil {
p.Error("sendCatalogResponse", "error", err.Error(), "channel_index", i)
return fmt.Errorf("创建事务失败: %v", err)
}
defer tx.Terminate()
// 获取响应
res, err := p.getResponse(tx)
if err != nil {
p.Error("sendCatalogResponse", "error", err.Error(), "channel_index", i)
return err
}
// 处理401未授权响应
if res.StatusCode == 401 {
// 获取WWW-Authenticate头部
wwwAuth := res.GetHeader("WWW-Authenticate")
if wwwAuth == nil {
p.Error("sendCatalogResponse", "error", "no auth challenge", "channel_index", i)
return fmt.Errorf("no auth challenge")
}
// 解析认证质询
chal, err := digest.ParseChallenge(wwwAuth.Value())
if err != nil {
p.Error("sendCatalogResponse", "error", err.Error(), "channel_index", i)
return err
}
p.plugin.Debug("received auth challenge",
"realm", chal.Realm,
"nonce", chal.Nonce,
"algorithm", chal.Algorithm,
"qop", chal.QOP,
"channel_index", i)
// 生成认证响应
opts := digest.Options{
Method: request.Method.String(),
URI: "sip:" + p.PlatformModel.ServerGBDomain,
Username: p.PlatformModel.Username,
Password: p.PlatformModel.Password,
Cnonce: sip.GenerateTagN(16),
Count: 1,
}
cred, err := digest.Digest(chal, opts)
if err != nil {
p.Error("calculating digest failed", "error", err.Error(), "channel_index", i)
return err
}
p.plugin.Debug("calculated response info",
"username", opts.Username,
"uri", opts.URI,
"cnonce", opts.Cnonce,
"count", opts.Count,
"response", cred.Response,
"channel_index", i)
// 创建新的带认证信息的请求
newReq := request.Clone()
newReq.RemoveHeader("Via") // 必须由传输层重新生成
newReq.AppendHeader(sip.NewHeader("Authorization", cred.String()))
// 发送认证请求
tx, err = p.Client.TransactionRequest(p, newReq, sipgo.ClientRequestAddVia)
if err != nil {
p.Error("sendCatalogResponse", "error", err.Error(), "channel_index", i)
return err
}
defer tx.Terminate()
// 获取认证响应
res, err = p.getResponse(tx)
if err != nil {
p.Error("sendCatalogResponse", "error", err.Error(), "channel_index", i)
return err
}
}
// 检查最终响应状态
if res.StatusCode != 200 {
p.Error("sendCatalogResponse", "status", res.StatusCode, "channel_index", i)
return fmt.Errorf("发送目录响应失败,状态码: %d", res.StatusCode)
}
// 添加短暂延迟以防止发送过快
time.Sleep(time.Millisecond * 50)
}
return nil
}
// buildChannelItem 构建单个通道的XML项
func (p *Platform) buildChannelItem(channel gb28181.DeviceChannel) string {
// 确保字符串字段不为空
deviceID := channel.ChannelId
if deviceID == "" {
deviceID = "unknown_device" // 如果没有设备ID使用默认值
}
name := channel.Name
if name == "" {
name = "未命名设备"
}
manufacturer := channel.Manufacturer
if manufacturer == "" {
manufacturer = "未知厂商"
}
model := channel.Model
if model == "" {
model = "未知型号"
}
owner := channel.Owner
if owner == "" {
owner = "未知所有者"
}
address := channel.Address
if address == "" {
address = "未知地址"
}
parentID := channel.ParentId
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.RegisterWay, // 直接使用整数值
channel.Secrecy, // 直接使用整数值
parentID,
channel.Parental, // 直接使用整数值
channel.SafetyWay) // 直接使用整数值
}
// handleDeviceControl 处理设备控制请求
func (p *Platform) handleDeviceControl(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)
}
// 获取通道ID
channelId := msg.DeviceID
var deviceId string
if tmpChannel, ok := p.plugin.channels.Find(func(c *Channel) bool {
return c.ChannelId == channelId
}); ok {
deviceId = tmpChannel.DeviceId
} else {
p.Error("设备不存在或未注册", "device_id", msg.DeviceID)
return fmt.Errorf("device not found or not registered: %v", msg.DeviceID)
}
// 从devices集合中获取设备实例
device, ok := p.plugin.devices.Get(deviceId)
if !ok {
p.Error("设备不存在或未注册", "device_id", deviceId)
return fmt.Errorf("device not found or not registered: %v", deviceId)
}
// 创建转发请求
request := sip.NewRequest(sip.MESSAGE, device.Recipient)
// 设置From头部使用平台信息
fromHeader := device.fromHDR
fromTag, _ := req.From().Params.Get("tag")
fromHeader.Params.Add("tag", fromTag)
request.AppendHeader(&fromHeader)
// 添加To头部使用设备信息
toHeader := sip.ToHeader{
Address: device.Recipient,
}
request.AppendHeader(&toHeader)
// 添加Via头部
//viaHeader := sip.ViaHeader{
// ProtocolName: "SIP",
// ProtocolVersion: "2.0",
// Transport: device.Transport,
// Host: device.SipIp,
// Port: device.LocalPort,
// Params: sip.NewParams(),
//}
//viaHeader.Params.Add("branch", sip.GenerateBranchN(16)).Add("rport", "")
//request.AppendHeader(&viaHeader)
// 设置Content-Type
contentTypeHeader := sip.ContentTypeHeader("Application/MANSCDP+xml")
request.AppendHeader(&contentTypeHeader)
// 直接使用原始消息体
request.SetBody(req.Body())
// 设置传输协议
request.SetTransport(strings.ToUpper(device.Transport))
// 发送请求
_, err = device.client.Do(p, request)
if err != nil {
p.Error("发送控制命令失败", "error", err.Error())
return fmt.Errorf("send control command failed: %v", err)
}
return nil
}
// handleDeviceStatus 处理设备状态查询请求
func (p *Platform) handleDeviceStatus(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.sendDeviceStatusResponse(req, nil, sn, fromTag)
}
// 2. 查询通道和设备信息
type Result struct {
DeviceID string `gorm:"column:device_id"`
}
var result Result
if p.plugin.DB != nil {
// 多表联查: channel_gb28181pro -> device_gb28181pro -> devices
if err := p.plugin.DB.Table("gb28181_channel gc").
Select("gd.device_id").
Joins("LEFT JOIN gb28181_device gd ON gc.device_id = gd.device_id").
Where("gc.channel_id = ?", channelId).
First(&result).Error; err != nil {
p.Error("查询通道和设备信息失败", "error", err.Error())
return fmt.Errorf("channel or device not found: %v", err)
}
}
// 3. 从devices集合中获取设备实例
device, ok := p.plugin.devices.Get(result.DeviceID)
if !ok {
p.Error("设备不存在或未注册", "device_id", result.DeviceID)
return fmt.Errorf("device not found or not registered: %v", result.DeviceID)
}
// 4. 发送设备状态响应
return p.sendDeviceStatusResponse(req, device, sn, fromTag)
}
// sendDeviceStatusResponse 发送设备状态响应
func (p *Platform) sendDeviceStatusResponse(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,
},
}
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)
// 设置Content-Type
contentTypeHeader := sip.ContentTypeHeader("Application/MANSCDP+xml")
request.AppendHeader(&contentTypeHeader)
// 获取当前时间,格式化为设备时间
currentTime := time.Now().Format("2006-01-02T15:04:05")
// 根据设备状态构建响应
var deviceID, online, status, encode, record string
if device == nil {
// 平台自身状态
deviceID = p.PlatformModel.DeviceGBID
online = "ONLINE"
status = "OK"
encode = "ON"
record = "OFF"
} else {
// 设备状态
deviceID = device.DeviceId
// 将布尔值转换为对应的状态字符串
if device.Online {
online = "ONLINE"
status = "OK"
encode = "ON" // 在线时默认编码开启
record = "OFF" // 默认不录制
} else {
online = "OFFLINE"
status = "ERROR"
encode = "OFF" // 离线时编码关闭
record = "OFF" // 离线时不录制
}
}
// 构建响应XML
xmlContent := fmt.Sprintf(`<?xml version="1.0" encoding="GB2312" standalone="yes" ?>
<Response>
<CmdType>DeviceStatus</CmdType>
<SN>%s</SN>
<DeviceID>%s</DeviceID>
<Result>OK</Result>
<Online>%s</Online>
<Status>%s</Status>
<DeviceTime>%s</DeviceTime>
<Encode>%s</Encode>
<Record>%s</Record>
<Alarmstatus Num="0"/>
</Response>`, sn, deviceID, online, status, currentTime, encode, record)
request.SetBody([]byte(xmlContent))
// 设置传输协议
request.SetTransport(strings.ToUpper(p.PlatformModel.Transport))
// 发送响应
_, err := p.Client.Do(p, request)
if err != nil {
p.Error("发送设备状态响应失败", "error", err.Error())
return fmt.Errorf("send device status response failed: %v", err)
}
return nil
}
// 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.DeviceChannel
if p.plugin.DB != nil {
if err := p.plugin.DB.Where("device_id = ? AND channel_id = ?", p.PlatformModel.ServerGBID, 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.DeviceId == "" {
// 非国标通道不支持设备信息查询
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.DeviceId).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,
},
}
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)
contentTypeHeader := sip.ContentTypeHeader("Application/MANSCDP+xml")
request.AppendHeader(&contentTypeHeader)
// 构建响应XML
var xmlContent string
if device == nil {
// 返回平台信息
xmlContent = fmt.Sprintf(`<?xml version="1.0" encoding="GB2312"?>
<Response>
<CmdType>DeviceInfo</CmdType>
<SN>%s</SN>
<DeviceID>%s</DeviceID>
<Result>OK</Result>
<DeviceName>%s</DeviceName>
<Manufacturer>%s</Manufacturer>
<Model>%s</Model>
<Firmware>%s</Firmware>
<Channel>%d</Channel>
</Response>`, sn, p.PlatformModel.DeviceGBID, p.PlatformModel.Name, p.PlatformModel.Manufacturer, p.PlatformModel.Model, "", p.PlatformModel.ChannelCount)
} else {
// 返回设备信息
xmlContent = fmt.Sprintf(`<?xml version="1.0" encoding="GB2312"?>
<Response>
<CmdType>DeviceInfo</CmdType>
<SN>%s</SN>
<DeviceID>%s</DeviceID>
<Result>OK</Result>
<DeviceName>%s</DeviceName>
<Manufacturer>%s</Manufacturer>
<Model>%s</Model>
<Firmware>%s</Firmware>
<Channel>%d</Channel>
</Response>`, sn, device.DeviceId, device.Name, device.Manufacturer, device.Model, device.Firmware, device.ChannelCount)
}
// 将UTF-8编码的XML内容转换为GB2312编码
gb2312Content, err := UTF8ToGB2312(xmlContent)
if err != nil {
p.Error("sendDeviceInfoResponse", "encoding error", err.Error())
// 如果转换失败,仍然使用原始内容,避免完全失败
request.SetBody([]byte(xmlContent))
} else {
// 使用转换后的GB2312编码内容
request.SetBody([]byte(gb2312Content))
}
// 修正:使用正确的上下文参数
tx, err := p.Client.TransactionRequest(p, request)
if err != nil {
p.Error("sendDeviceInfoResponse", "error", err.Error())
return fmt.Errorf("创建事务失败: %v", err)
}
defer tx.Terminate()
// 获取响应
res, err := p.getResponse(tx)
if err != nil {
p.Error("sendDeviceInfoResponse", "error", err.Error())
return err
}
// 处理401未授权响应
if res.StatusCode == 401 {
// 获取WWW-Authenticate头部
wwwAuth := res.GetHeader("WWW-Authenticate")
if wwwAuth == nil {
p.Error("sendDeviceInfoResponse", "error", "no auth challenge")
return fmt.Errorf("no auth challenge")
}
// 解析认证质询
chal, err := digest.ParseChallenge(wwwAuth.Value())
if err != nil {
p.Error("sendDeviceInfoResponse", "error", err.Error())
return err
}
p.Debug("received auth challenge",
"realm", chal.Realm,
"nonce", chal.Nonce,
"algorithm", chal.Algorithm,
"qop", chal.QOP)
// 生成认证响应
opts := digest.Options{
Method: request.Method.String(),
URI: "sip:" + p.PlatformModel.ServerGBDomain,
Username: p.PlatformModel.Username,
Password: p.PlatformModel.Password,
Cnonce: sip.GenerateTagN(16),
Count: 1,
}
cred, err := digest.Digest(chal, opts)
if err != nil {
p.Error("calculating digest failed", "error", err.Error())
return err
}
p.Debug("calculated response info",
"username", opts.Username,
"uri", opts.URI,
"cnonce", opts.Cnonce,
"count", opts.Count,
"response", cred.Response)
// 创建新的带认证信息的请求
newReq := request.Clone()
newReq.RemoveHeader("Via") // 必须由传输层重新生成
newReq.AppendHeader(sip.NewHeader("Authorization", cred.String()))
// 发送认证请求
tx, err = p.Client.TransactionRequest(p, newReq, sipgo.ClientRequestAddVia)
if err != nil {
p.Error("sendDeviceInfoResponse", "error", err.Error())
return err
}
defer tx.Terminate()
// 获取认证响应
res, err = p.getResponse(tx)
if err != nil {
p.Error("sendDeviceInfoResponse", "error", err.Error())
return err
}
}
// 检查最终响应状态
if res.StatusCode != 200 {
p.Error("sendDeviceInfoResponse", "status", res.StatusCode)
return fmt.Errorf("发送设备信息响应失败,状态码: %d", res.StatusCode)
}
p.Info("sendDeviceInfoResponse", "status", "success")
return nil
}
// 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)
}
// handlePresetQuery 处理预置位查询请求
func (p *Platform) handlePresetQuery(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)
}
// 获取通道ID
channelID := msg.DeviceID
// 查询通道和设备信息
type Result struct {
DeviceID string `gorm:"column:device_id"`
}
var result Result
if p.plugin.DB != nil {
// 多表联查: channel_gb28181pro -> device_gb28181pro -> devices
if err := p.plugin.DB.Table("gb28181_channel gc").
Select("gc.device_id").
Joins("LEFT JOIN gb28181_device gd ON gd.device_id = gc.device_id").
Where("gc.channel_id = ?", channelID).
First(&result).Error; err != nil {
p.Error("查询通道和设备信息失败", "error", err.Error())
return fmt.Errorf("channel or device not found: %v", err)
}
}
// 从devices集合中获取设备实例
device, ok := p.plugin.devices.Get(result.DeviceID)
if !ok {
p.Error("设备不存在或未注册", "device_id", result.DeviceID)
return fmt.Errorf("device not found or not registered: %v", result.DeviceID)
}
// 创建转发请求
request := sip.NewRequest(sip.MESSAGE, device.Recipient)
// 设置From头部使用平台信息
fromHeader := device.fromHDR
fromTag, _ := req.From().Params.Get("tag")
fromHeader.Params.Add("tag", fromTag)
request.AppendHeader(&fromHeader)
// 添加To头部使用设备信息
toHeader := sip.ToHeader{
Address: device.Recipient,
}
request.AppendHeader(&toHeader)
// 添加Via头部
//viaHeader := sip.ViaHeader{
// ProtocolName: "SIP",
// ProtocolVersion: "2.0",
// Transport: device.Transport,
// Host: device.SipIp,
// Port: device.LocalPort,
// Params: sip.NewParams(),
//}
//viaHeader.Params.Add("branch", sip.GenerateBranchN(16)).Add("rport", "")
//request.AppendHeader(&viaHeader)
// 设置Content-Type
contentTypeHeader := sip.ContentTypeHeader("Application/MANSCDP+xml")
request.AppendHeader(&contentTypeHeader)
// 直接使用原始消息体
request.SetBody(req.Body())
// 设置传输协议
request.SetTransport(strings.ToUpper(device.Transport))
// 发送请求
_, err = device.client.Do(p, request)
if err != nil {
p.Error("发送预置位查询命令失败", "error", err.Error())
return fmt.Errorf("send preset query command failed: %v", err)
}
return nil
}
// GetKey 返回平台的唯一标识符
func (p *Platform) GetKey() string {
return p.PlatformModel.ServerGBID
}