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(`
Catalog
%s
%s
0
`, 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(`
Catalog
%s
%s
%d
%s
`, 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(`-
%s
%s
%s
%s
%s
%s
%d
%d
%s
%d
%d
ON
`, 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(`
DeviceStatus
%s
%s
OK
%s
%s
%s
%s
%s
`, 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(`
DeviceInfo
%s
%s
OK
%s
%s
%s
%s
%d
`, sn, p.PlatformModel.DeviceGBID, p.PlatformModel.Name, p.PlatformModel.Manufacturer, p.PlatformModel.Model, "", p.PlatformModel.ChannelCount)
} else {
// 返回设备信息
xmlContent = fmt.Sprintf(`
DeviceInfo
%s
%s
OK
%s
%s
%s
%s
%d
`, 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
}