mirror of
https://github.com/lkmio/gb-cms.git
synced 2025-09-26 19:51:22 +08:00
456 lines
14 KiB
Go
456 lines
14 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"github.com/ghettovoice/gosip"
|
|
"github.com/ghettovoice/gosip/log"
|
|
"github.com/ghettovoice/gosip/sip"
|
|
"github.com/ghettovoice/gosip/util"
|
|
"github.com/lkmio/avformat/utils"
|
|
"net"
|
|
"net/http"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
logger log.Logger
|
|
GlobalContactAddress *sip.Address
|
|
)
|
|
|
|
const (
|
|
CmdTagStart = "<CmdType>"
|
|
CmdTagEnd = "</CmdType>"
|
|
|
|
XmlNameControl = "Control"
|
|
XmlNameQuery = "Query" //主动查询消息
|
|
XmlNameNotify = "Notify" //订阅产生的通知消息
|
|
XmlNameResponse = "Response" //响应消息
|
|
|
|
CmdDeviceInfo = "DeviceInfo"
|
|
CmdDeviceStatus = "DeviceStatus"
|
|
CmdCatalog = "Catalog"
|
|
CmdRecordInfo = "RecordInfo"
|
|
CmdMobilePosition = "MobilePosition"
|
|
CmdKeepalive = "Keepalive"
|
|
CmdBroadcast = "Broadcast"
|
|
)
|
|
|
|
func init() {
|
|
logger = log.NewDefaultLogrusLogger().WithPrefix("Server")
|
|
}
|
|
|
|
type SipServer interface {
|
|
SendRequestWithContext(ctx context.Context,
|
|
request sip.Request,
|
|
options ...gosip.RequestWithContextOption)
|
|
|
|
SendRequest(request sip.Request) sip.ClientTransaction
|
|
|
|
SendRequestWithTimeout(seconds int, request sip.Request, options ...gosip.RequestWithContextOption) (sip.Response, error)
|
|
|
|
Send(msg sip.Message) error
|
|
|
|
ListenAddr() string
|
|
}
|
|
|
|
type sipServer struct {
|
|
sip gosip.Server
|
|
listenAddr string
|
|
xmlReflectTypes map[string]reflect.Type
|
|
handler EventHandler
|
|
}
|
|
|
|
type SipRequestSource struct {
|
|
req sip.Request
|
|
tx sip.ServerTransaction
|
|
fromCascade bool
|
|
fromJt bool
|
|
}
|
|
|
|
func (s *sipServer) Send(msg sip.Message) error {
|
|
return s.sip.Send(msg)
|
|
}
|
|
|
|
func setToTag(response sip.Message) {
|
|
toHeader := response.GetHeaders("To")
|
|
to := toHeader[0].(*sip.ToHeader)
|
|
to.Params = sip.NewParams().Add("tag", sip.String{Str: util.RandString(10)})
|
|
}
|
|
|
|
func (s *sipServer) OnRegister(wrapper *SipRequestSource) {
|
|
var device GBDevice
|
|
var queryCatalog bool
|
|
|
|
fromHeaders := wrapper.req.GetHeaders("From")
|
|
if len(fromHeaders) == 0 {
|
|
Sugar.Errorf("not find From header. message: %s", wrapper.req.String())
|
|
return
|
|
}
|
|
|
|
_ = wrapper.req.GetHeaders("Authorization")
|
|
fromHeader := fromHeaders[0].(*sip.FromHeader)
|
|
expiresHeader := wrapper.req.GetHeaders("Expires")
|
|
|
|
response := sip.NewResponseFromRequest("", wrapper.req, 200, "OK", "")
|
|
id := fromHeader.Address.User().String()
|
|
if len(expiresHeader) > 0 && "0" == expiresHeader[0].Value() {
|
|
Sugar.Infof("设备注销 Device: %s", id)
|
|
s.handler.OnUnregister(id)
|
|
} else /*if authorizationHeader == nil*/ {
|
|
var expires int
|
|
expires, device, queryCatalog = s.handler.OnRegister(id, wrapper.req.Transport(), wrapper.req.Source())
|
|
if device != nil {
|
|
Sugar.Infof("注册成功 Device: %s addr: %s", id, wrapper.req.Source())
|
|
expiresHeader := sip.Expires(expires)
|
|
response.AppendHeader(&expiresHeader)
|
|
} else {
|
|
Sugar.Infof("注册失败 Device: %s", id)
|
|
response = sip.NewResponseFromRequest("", wrapper.req, 401, "Unauthorized", "")
|
|
}
|
|
}
|
|
|
|
SendResponse(wrapper.tx, response)
|
|
|
|
if device != nil {
|
|
// 查询设备信息
|
|
device.QueryDeviceInfo()
|
|
}
|
|
|
|
if queryCatalog {
|
|
device.QueryCatalog()
|
|
}
|
|
}
|
|
|
|
// OnInvite 收到上级预览/下级设备广播请求
|
|
func (s *sipServer) OnInvite(wrapper *SipRequestSource) {
|
|
SendResponse(wrapper.tx, sip.NewResponseFromRequest("", wrapper.req, 100, "Trying", ""))
|
|
user := wrapper.req.Recipient().User().String()
|
|
|
|
//if len(user) != 20 {
|
|
// SendResponseWithStatusCode(req, tx, http.StatusNotFound)
|
|
// return
|
|
//}
|
|
|
|
// 查找对应的设备
|
|
var device GBDevice
|
|
if wrapper.fromCascade {
|
|
// 级联设备
|
|
device = PlatformManager.Find(wrapper.req.Source())
|
|
} else if wrapper.fromJt {
|
|
// 部标设备
|
|
// 1. 根据通道查找到对应的设备ID
|
|
// 2. 根据Subject头域查找对应的设备ID
|
|
if channels, _ := ChannelDao.QueryChannelsByChannelID(user); len(channels) > 0 {
|
|
device = JTDeviceManager.Find(channels[0].RootID)
|
|
}
|
|
} else {
|
|
if session := EarlyDialogs.Find(user); session != nil {
|
|
// 语音广播设备
|
|
device, _ = DeviceDao.QueryDevice(session.data.(*Sink).SinkStreamID.DeviceID())
|
|
} else {
|
|
// 根据Subject头域查找设备
|
|
headers := wrapper.req.GetHeaders("Subject")
|
|
if len(headers) > 0 {
|
|
subject := headers[0].(*sip.GenericHeader)
|
|
split := strings.Split(strings.Split(subject.Value(), ",")[0], ":")
|
|
if len(split) > 1 {
|
|
device, _ = DeviceDao.QueryDevice(split[1])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if device == nil {
|
|
logger.Error("处理Invite失败, 找不到设备. request: %s", wrapper.req.String())
|
|
|
|
SendResponseWithStatusCode(wrapper.req, wrapper.tx, http.StatusNotFound)
|
|
} else {
|
|
response := device.OnInvite(wrapper.req, user)
|
|
SendResponse(wrapper.tx, response)
|
|
}
|
|
}
|
|
|
|
func (s *sipServer) OnAck(wrapper *SipRequestSource) {
|
|
|
|
}
|
|
|
|
func (s *sipServer) OnBye(wrapper *SipRequestSource) {
|
|
response := sip.NewResponseFromRequest("", wrapper.req, 200, "OK", "")
|
|
SendResponse(wrapper.tx, response)
|
|
|
|
id, _ := wrapper.req.CallID()
|
|
var deviceId string
|
|
|
|
if stream, _ := StreamDao.DeleteStreamByCallID(id.Value()); stream != nil {
|
|
// 下级设备挂断, 关闭流
|
|
deviceId = stream.StreamID.DeviceID()
|
|
stream.Close(false, true)
|
|
} else if sink, _ := SinkDao.DeleteForwardSinkByCallID(id.Value()); sink != nil {
|
|
sink.Close(false, true)
|
|
}
|
|
|
|
if wrapper.fromCascade {
|
|
// 级联上级挂断
|
|
if platform := PlatformManager.Find(wrapper.req.Source()); platform != nil {
|
|
platform.OnBye(wrapper.req)
|
|
}
|
|
} else if wrapper.fromJt {
|
|
// 部标设备挂断
|
|
if jtDevice := JTDeviceManager.Find(deviceId); jtDevice != nil {
|
|
jtDevice.OnBye(wrapper.req)
|
|
}
|
|
} else if device, _ := DeviceDao.QueryDevice(deviceId); device != nil {
|
|
device.OnBye(wrapper.req)
|
|
}
|
|
}
|
|
|
|
func (s *sipServer) OnNotify(wrapper *SipRequestSource) {
|
|
response := sip.NewResponseFromRequest("", wrapper.req, 200, "OK", "")
|
|
SendResponse(wrapper.tx, response)
|
|
|
|
mobilePosition := MobilePositionNotify{}
|
|
if err := DecodeXML([]byte(wrapper.req.Body()), &mobilePosition); err != nil {
|
|
Sugar.Errorf("解析位置通知失败 err: %s request: %s", err.Error(), wrapper.req.String())
|
|
return
|
|
}
|
|
|
|
s.handler.OnNotifyPosition(&mobilePosition)
|
|
}
|
|
|
|
func (s *sipServer) OnMessage(wrapper *SipRequestSource) {
|
|
var ok bool
|
|
defer func() {
|
|
var response sip.Response
|
|
if ok {
|
|
response = CreateResponseWithStatusCode(wrapper.req, http.StatusOK)
|
|
} else {
|
|
response = CreateResponseWithStatusCode(wrapper.req, http.StatusForbidden)
|
|
}
|
|
|
|
SendResponse(wrapper.tx, response)
|
|
}()
|
|
|
|
body := wrapper.req.Body()
|
|
xmlName := GetRootElementName(body)
|
|
cmd := GetCmdType(body)
|
|
src, ok := s.xmlReflectTypes[xmlName+"."+cmd]
|
|
if !ok {
|
|
Sugar.Errorf("处理XML消息失败, 找不到结构体. request: %s", wrapper.req.String())
|
|
return
|
|
}
|
|
|
|
message := reflect.New(src).Interface()
|
|
if err := DecodeXML([]byte(body), message); err != nil {
|
|
Sugar.Errorf("解析XML消息失败 err: %s request: %s", err.Error(), body)
|
|
return
|
|
}
|
|
|
|
// 查找设备
|
|
deviceId := message.(BaseMessageGetter).GetDeviceID()
|
|
if CmdBroadcast == cmd {
|
|
// 广播消息
|
|
from, _ := wrapper.req.From()
|
|
deviceId = from.Address.User().String()
|
|
}
|
|
|
|
switch xmlName {
|
|
case XmlNameControl:
|
|
break
|
|
case XmlNameQuery:
|
|
// 被上级查询
|
|
var device GBClient
|
|
if wrapper.fromCascade {
|
|
device = PlatformManager.Find(wrapper.req.Source())
|
|
} else if wrapper.fromJt {
|
|
device = JTDeviceManager.Find(deviceId)
|
|
}
|
|
|
|
if ok = device != nil; !ok {
|
|
Sugar.Errorf("处理上级请求消息失败, 找不到设备 addr: %s request: %s", wrapper.req.Source(), wrapper.req.String())
|
|
return
|
|
}
|
|
|
|
if CmdDeviceInfo == cmd {
|
|
device.OnQueryDeviceInfo(message.(*BaseMessage).SN)
|
|
} else if CmdCatalog == cmd {
|
|
var channels []*Channel
|
|
|
|
// 查询出所有通道
|
|
if wrapper.fromCascade {
|
|
result, err := PlatformDao.QueryPlatformChannels(device.GetDomain())
|
|
if err != nil {
|
|
Sugar.Errorf("查询设备通道列表失败 err: %s device: %s", err.Error(), device.GetID())
|
|
}
|
|
|
|
channels = result
|
|
} else if wrapper.fromJt {
|
|
channels, _ = ChannelDao.QueryChannelsByRootID(device.GetID())
|
|
} else {
|
|
// 从模拟多个国标客户端中查找
|
|
channels = DeviceChannelsManager.FindChannels(device.GetID())
|
|
}
|
|
|
|
device.OnQueryCatalog(message.(*BaseMessage).SN, channels)
|
|
}
|
|
|
|
break
|
|
case XmlNameNotify:
|
|
if CmdKeepalive == cmd {
|
|
// 下级设备心跳通知
|
|
ok = s.handler.OnKeepAlive(deviceId, wrapper.req.Source())
|
|
}
|
|
|
|
break
|
|
|
|
case XmlNameResponse:
|
|
// 查询下级的应答
|
|
if CmdCatalog == cmd {
|
|
s.handler.OnCatalog(deviceId, message.(*CatalogResponse))
|
|
} else if CmdRecordInfo == cmd {
|
|
Sugar.Infof("查询录像列表 %s", body)
|
|
s.handler.OnRecord(deviceId, message.(*QueryRecordInfoResponse))
|
|
} else if CmdDeviceInfo == cmd {
|
|
s.handler.OnDeviceInfo(deviceId, message.(*DeviceInfoResponse))
|
|
}
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
func CreateResponseWithStatusCode(request sip.Request, code int) sip.Response {
|
|
return sip.NewResponseFromRequest("", request, sip.StatusCode(code), StatusCode2Reason(code), "")
|
|
}
|
|
|
|
func SendResponseWithStatusCode(request sip.Request, tx sip.ServerTransaction, code int) {
|
|
SendResponse(tx, CreateResponseWithStatusCode(request, code))
|
|
}
|
|
|
|
func SendResponse(tx sip.ServerTransaction, response sip.Response) bool {
|
|
sendError := tx.Respond(response)
|
|
|
|
if sendError != nil {
|
|
Sugar.Errorf("发送响应消息失败, error: %s response: %s", sendError.Error(), response.String())
|
|
}
|
|
|
|
return sendError == nil
|
|
}
|
|
|
|
func (s *sipServer) SendRequestWithContext(ctx context.Context, request sip.Request, options ...gosip.RequestWithContextOption) {
|
|
s.sip.RequestWithContext(ctx, request, options...)
|
|
}
|
|
|
|
func (s *sipServer) SendRequestWithTimeout(seconds int, request sip.Request, options ...gosip.RequestWithContextOption) (sip.Response, error) {
|
|
reqCtx, _ := context.WithTimeout(context.Background(), time.Duration(seconds)*time.Second)
|
|
return s.sip.RequestWithContext(reqCtx, request, options...)
|
|
}
|
|
|
|
func (s *sipServer) SendRequest(request sip.Request) sip.ClientTransaction {
|
|
transaction, err := s.sip.Request(request)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return transaction
|
|
}
|
|
|
|
func (s *sipServer) ListenAddr() string {
|
|
return s.listenAddr
|
|
}
|
|
|
|
// 过滤SIP消息、超找消息来源
|
|
func filterRequest(f func(wrapper *SipRequestSource)) gosip.RequestHandler {
|
|
return func(req sip.Request, tx sip.ServerTransaction) {
|
|
|
|
source := req.Source()
|
|
// 是否是级联上级下发的请求
|
|
platform := PlatformManager.Find(source)
|
|
// 是否是部标设备上级下发的请求
|
|
var fromJt bool
|
|
if platform == nil {
|
|
fromJt = JTDeviceManager.ExistClientByServerAddr(req.Source())
|
|
}
|
|
switch req.Method() {
|
|
case sip.SUBSCRIBE, sip.INFO:
|
|
if platform == nil || fromJt {
|
|
// SUBSCRIBE/INFO只能本级域向下级发起
|
|
SendResponseWithStatusCode(req, tx, http.StatusBadRequest)
|
|
Sugar.Errorf("处理%s请求失败, %s消息只能上级发起. request: %s", req.Method(), req.Method(), req.String())
|
|
return
|
|
}
|
|
break
|
|
case sip.NOTIFY, sip.REGISTER:
|
|
if platform != nil || fromJt {
|
|
// NOTIFY和REGISTER只能下级发起
|
|
SendResponseWithStatusCode(req, tx, http.StatusBadRequest)
|
|
Sugar.Errorf("处理%s请求失败, %s消息只能下级发起. request: %s", req.Method(), req.Method(), req.String())
|
|
return
|
|
}
|
|
break
|
|
}
|
|
|
|
f(&SipRequestSource{
|
|
req,
|
|
tx,
|
|
platform != nil,
|
|
fromJt,
|
|
})
|
|
}
|
|
}
|
|
|
|
func StartSipServer(id, listenIP, publicIP string, listenPort int) (SipServer, error) {
|
|
ua := gosip.NewServer(gosip.ServerConfig{
|
|
Host: publicIP,
|
|
UserAgent: "github/lkmio",
|
|
}, nil, nil, logger)
|
|
|
|
addr := net.JoinHostPort(listenIP, strconv.Itoa(listenPort))
|
|
if err := ua.Listen("udp", addr); err != nil {
|
|
return nil, err
|
|
} else if err := ua.Listen("tcp", addr); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
server := &sipServer{sip: ua, xmlReflectTypes: map[string]reflect.Type{
|
|
fmt.Sprintf("%s.%s", XmlNameQuery, CmdCatalog): reflect.TypeOf(BaseMessage{}),
|
|
fmt.Sprintf("%s.%s", XmlNameQuery, CmdDeviceInfo): reflect.TypeOf(BaseMessage{}),
|
|
fmt.Sprintf("%s.%s", XmlNameQuery, CmdDeviceStatus): reflect.TypeOf(BaseMessage{}),
|
|
fmt.Sprintf("%s.%s", XmlNameResponse, CmdCatalog): reflect.TypeOf(CatalogResponse{}),
|
|
fmt.Sprintf("%s.%s", XmlNameResponse, CmdDeviceInfo): reflect.TypeOf(DeviceInfoResponse{}),
|
|
fmt.Sprintf("%s.%s", XmlNameResponse, CmdDeviceStatus): reflect.TypeOf(DeviceStatusResponse{}),
|
|
fmt.Sprintf("%s.%s", XmlNameResponse, CmdRecordInfo): reflect.TypeOf(QueryRecordInfoResponse{}),
|
|
fmt.Sprintf("%s.%s", XmlNameNotify, CmdKeepalive): reflect.TypeOf(BaseMessage{}),
|
|
fmt.Sprintf("%s.%s", XmlNameNotify, CmdMobilePosition): reflect.TypeOf(BaseMessage{}),
|
|
fmt.Sprintf("%s.%s", XmlNameResponse, CmdBroadcast): reflect.TypeOf(BaseMessage{}),
|
|
}}
|
|
|
|
utils.Assert(ua.OnRequest(sip.REGISTER, filterRequest(server.OnRegister)) == nil)
|
|
utils.Assert(ua.OnRequest(sip.INVITE, filterRequest(server.OnInvite)) == nil)
|
|
utils.Assert(ua.OnRequest(sip.BYE, filterRequest(server.OnBye)) == nil)
|
|
utils.Assert(ua.OnRequest(sip.ACK, filterRequest(server.OnAck)) == nil)
|
|
utils.Assert(ua.OnRequest(sip.NOTIFY, filterRequest(server.OnNotify)) == nil)
|
|
utils.Assert(ua.OnRequest(sip.MESSAGE, filterRequest(server.OnMessage)) == nil)
|
|
|
|
utils.Assert(ua.OnRequest(sip.INFO, filterRequest(func(wrapper *SipRequestSource) {
|
|
})) == nil)
|
|
utils.Assert(ua.OnRequest(sip.CANCEL, filterRequest(func(wrapper *SipRequestSource) {
|
|
})) == nil)
|
|
utils.Assert(ua.OnRequest(sip.SUBSCRIBE, filterRequest(func(wrapper *SipRequestSource) {
|
|
})) == nil)
|
|
|
|
server.listenAddr = addr
|
|
port := sip.Port(listenPort)
|
|
GlobalContactAddress = &sip.Address{
|
|
Uri: &sip.SipUri{
|
|
FUser: sip.String{Str: id},
|
|
FHost: publicIP,
|
|
FPort: &port,
|
|
},
|
|
}
|
|
|
|
return server, nil
|
|
}
|