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 }