mirror of
https://github.com/Monibuca/plugin-gb28181.git
synced 2025-12-24 13:27:57 +08:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2033bdf04f | ||
|
|
79538986e5 | ||
|
|
438b0d2bd0 | ||
|
|
47c916e131 | ||
|
|
748c4117c6 | ||
|
|
11444a6398 | ||
|
|
ae6cb14536 | ||
|
|
957bdee6a4 | ||
|
|
9cf4607478 | ||
|
|
74241c20ab | ||
|
|
9d7b5feaf0 | ||
|
|
53551f3514 | ||
|
|
6e2bd0c146 | ||
|
|
60f9a18aca | ||
|
|
4bfd3103cc | ||
|
|
98bf9c6b32 | ||
|
|
cfed8a9d2f | ||
|
|
6cd4d8e51a | ||
|
|
dd5617737a | ||
|
|
616df4c2b1 | ||
|
|
b8fcfb0213 | ||
|
|
01c36d591c | ||
|
|
2143cc4911 |
114
README.md
114
README.md
@@ -10,41 +10,73 @@ github.com/Monibuca/plugin-gb28181
|
||||
|
||||
```go
|
||||
import (
|
||||
_ "github.com/Monibuca/plugin-gb28181"
|
||||
_ "m7s.live/plugin/gb28181/v4"
|
||||
)
|
||||
```
|
||||
|
||||
## 默认插件配置
|
||||
|
||||
```toml
|
||||
[GB28181]
|
||||
Serial = "34020000002000000001"
|
||||
Realm = "3402000000"
|
||||
Expires = 3600
|
||||
ListenAddr = "127.0.0.1:5060"
|
||||
AutoCloseAfter = -1
|
||||
AutoInvite = false
|
||||
MediaPort = 58200
|
||||
CatalogInterval = 30
|
||||
RemoveBanInterval = 600
|
||||
Username = ""
|
||||
Password = ""
|
||||
UdpCacheSize = 0
|
||||
TCP = false
|
||||
```yml
|
||||
gb28181:
|
||||
autoInvite: true
|
||||
autoCloseAfter: -1
|
||||
preFetchRecord: false
|
||||
udpCacheSize: 0
|
||||
sipNetwork: udp
|
||||
sipIP: 127.0.0.1
|
||||
sipPort: 5060
|
||||
serial: "34020000002000000001"
|
||||
realm: "3402000000"
|
||||
username: ""
|
||||
password: ""
|
||||
|
||||
ackTimeout: 10
|
||||
registerValidity: 60
|
||||
registerInterval: 60
|
||||
heartbeatInterval: 60
|
||||
heartbeatRetry: 3
|
||||
|
||||
mediaIP: 127.0.0.1
|
||||
mediaPort: 58200
|
||||
mediaIdleTimeout: 30
|
||||
mediaNetwork: udp
|
||||
|
||||
removeBanInterval: 600
|
||||
logVerbose: false
|
||||
audioEnable: true
|
||||
waitKeyFrame: true
|
||||
```
|
||||
|
||||
- `ListenAddr`是监听的地址,这里需要注意的是必须要带上Server的IP地址,这个IP地址是向设备发送信息的时候需要带上的。
|
||||
- `Serial` Server(SIP)的编号
|
||||
- `Realm` Server(SIP)的域
|
||||
- `AutoCloseAfter` 如果设置大于等于0,则当某个流最后一个订阅者取消订阅时会延迟N秒,会自动发送bye,节省流量。如果为了响应及时,可以设置成-1,保持流的连接
|
||||
- `AutoInvite` 表示自动发起invite,当Server(SIP)接收到设备信息时,立即向设备发送invite命令获取流
|
||||
- `MediaPort` 表示用于接收设备流的端口号
|
||||
- `CatalogInterval` 定时获取设备目录的间隔,单位秒
|
||||
- `RemoveBanInterval` 定时移除注册失败的设备黑名单,单位秒,默认10分钟(600秒)
|
||||
- `Username` 国标用户名
|
||||
- `Password` 国标密码
|
||||
- `TCP` 是否开启TCP接收国标流,默认false
|
||||
- `UdpCacheSize` 表示UDP缓存大小,默认为0,不开启。仅当TCP关闭,切缓存大于0时才开启,会最多缓存最多N个包,并排序,修复乱序造成的无法播放问题,注意开启后,会有一定的性能损耗,并丢失部分包。
|
||||
- `AutoInvite` bool 表示自动发起invite,当Server(SIP)接收到设备信息时,立即向设备发送invite命令获取流
|
||||
- `AutoCloseAfter` int 如果设置大于等于0,则当某个流最后一个订阅者取消订阅时会延迟N秒,会自动发送bye,节省流量。如果为了响应及时,可以设置成-1,保持流的连接
|
||||
- `PreFetchRecord` bool
|
||||
|
||||
* sip服务器的配置
|
||||
- `SipNetwork` string 传输协议,默认UDP,可选TCP
|
||||
- `SipIP` string sip 服务器公网IP
|
||||
- `SipPort` uint16 sip 服务器端口,默认 5060
|
||||
- `Serial` string sip 服务器 id, 默认 34020000002000000001
|
||||
- `Realm` string sip 服务器域,默认 3402000000
|
||||
- `Username` string sip 服务器账号
|
||||
- `Password` string sip 服务器密码
|
||||
|
||||
- `AckTimeout` uint16 sip 服务应答超时,单位秒
|
||||
- `RegisterValidity` int 注册有效期,单位秒,默认 3600
|
||||
- `RegisterInterval` int 注册间隔,单位秒,默认 60
|
||||
- `HeartbeatInterval` int 心跳间隔,单位秒,默认 60
|
||||
- `HeartbeatRetry` int 心跳超时次数,默认 3
|
||||
|
||||
* 媒体服务器配置
|
||||
- `MediaIP` string 媒体服务器地址
|
||||
- `MediaPort` uint16 媒体服务器端口
|
||||
- `MediaNetwork` string 媒体传输协议,默认UDP,可选TCP
|
||||
- `MediaIdleTimeout` uint16 推流超时时间,超过则断开链接,让设备重连
|
||||
|
||||
- `AudioEnable` bool 是否开启音频
|
||||
- `LogVerbose` bool
|
||||
- `WaitKeyFrame` bool 是否等待关键帧,如果等待,则在收到第一个关键帧之前,忽略所有媒体流
|
||||
- `RemoveBanInterval` int 定时移除注册失败的设备黑名单,单位秒,默认10分钟(600秒)
|
||||
- `UdpCacheSize` int 表示UDP缓存大小,默认为0,不开启。仅当TCP关闭,切缓存大于0时才开启,会最多缓存最多N个包,并排序,修复乱序造成的无法播放问题,注意开启后,会有一定的性能损耗,并丢失部分包。
|
||||
|
||||
**注意某些摄像机没有设置用户名的地方,摄像机会以自身的国标id作为用户名,这个时候m7s会忽略使用摄像机的用户名,忽略配置的用户名**
|
||||
如果设备配置了错误的用户名和密码,连续三次上报错误后,m7s会记录设备id,并在10分钟内禁止设备注册
|
||||
@@ -58,6 +90,7 @@ TCP = false
|
||||
- 发送RecordInfo命令查询设备对录像数据
|
||||
- 发送Invite命令获取设备的实时视频或者录像视频
|
||||
- 发送PTZ命令来控制摄像头云台
|
||||
- 自动同步设备位置
|
||||
|
||||
### 作为GB28281的流媒体服务器接受设备的媒体流
|
||||
|
||||
@@ -77,20 +110,17 @@ TCP = false
|
||||
|
||||
```go
|
||||
type Device struct {
|
||||
*transaction.Core `json:"-"`
|
||||
ID string
|
||||
RegisterTime time.Time
|
||||
UpdateTime time.Time
|
||||
Status string
|
||||
Channels []*Channel
|
||||
queryChannel bool
|
||||
sn int
|
||||
from *sip.Contact
|
||||
to *sip.Contact
|
||||
Addr string
|
||||
SipIP string //暴露的IP
|
||||
channelMap map[string]*Channel
|
||||
channelMutex sync.RWMutex
|
||||
ID string
|
||||
Name string
|
||||
Manufacturer string
|
||||
Model string
|
||||
Owner string
|
||||
RegisterTime time.Time
|
||||
UpdateTime time.Time
|
||||
LastKeepaliveAt time.Time
|
||||
Status string
|
||||
Channels []*Channel
|
||||
NetAddr string
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
207
channel.go
207
channel.go
@@ -9,18 +9,16 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/Monibuca/engine/v3"
|
||||
. "github.com/Monibuca/plugin-gb28181/v3/sip"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/utils"
|
||||
. "github.com/Monibuca/utils/v3"
|
||||
"github.com/ghettovoice/gosip/sip"
|
||||
"m7s.live/plugin/gb28181/v4/utils"
|
||||
)
|
||||
|
||||
type ChannelEx struct {
|
||||
device *Device `json:"-"`
|
||||
inviteRes *Response `json:"-"`
|
||||
recordInviteRes *Response `json:"-"`
|
||||
RecordPublisher *Publisher
|
||||
LivePublisher *Publisher
|
||||
device *Device `json:"-"`
|
||||
inviteRes *sip.Response `json:"-"`
|
||||
recordInviteRes *sip.Response `json:"-"`
|
||||
RecordPublisher *GBPublisher
|
||||
LivePublisher *GBPublisher
|
||||
LiveSubSP string //实时子码流
|
||||
Records []*Record
|
||||
RecordStartTime string
|
||||
@@ -29,6 +27,9 @@ type ChannelEx struct {
|
||||
recordEndTime time.Time
|
||||
state int32
|
||||
tcpPortIndex uint16
|
||||
GpsTime time.Time //gps时间
|
||||
Longitude string //经度
|
||||
Latitude string //纬度
|
||||
}
|
||||
|
||||
// Channel 通道
|
||||
@@ -50,18 +51,19 @@ type Channel struct {
|
||||
*ChannelEx //自定义属性
|
||||
}
|
||||
|
||||
func (c *Channel) CreateRequst(Method Method) (request *Request) {
|
||||
request = &Request{}
|
||||
request.Message = c.device.CreateMessage(Method)
|
||||
request.Message.StartLine.Uri = NewURI(c.DeviceID + "@" + c.device.to.Uri.Domain())
|
||||
request.Message.To = &Contact{
|
||||
Uri: request.Message.StartLine.Uri,
|
||||
}
|
||||
request.Message.From = &Contact{
|
||||
Uri: NewURI(config.Serial + "@" + config.Realm),
|
||||
Params: map[string]string{"tag": utils.RandNumString(9)},
|
||||
}
|
||||
return
|
||||
func (c *Channel) CreateRequst(Method sip.RequestMethod) (req sip.Request) {
|
||||
return c.device.CreateRequest(Method)
|
||||
// request = &Request{}
|
||||
// request.Message = c.device.CreateMessage(Method)
|
||||
// request.Message.StartLine.Uri = NewURI(c.DeviceID + "@" + c.device.to.Uri.Domain())
|
||||
// request.Message.To = &Contact{
|
||||
// Uri: request.Message.StartLine.Uri,
|
||||
// }
|
||||
// request.Message.From = &Contact{
|
||||
// Uri: NewURI(c.device.config.Serial + "@" + c.device.config.Realm),
|
||||
// Params: map[string]string{"tag": utils.RandNumString(9)},
|
||||
// }
|
||||
// return req
|
||||
}
|
||||
func (channel *Channel) QueryRecord(startTime, endTime string) int {
|
||||
d := channel.device
|
||||
@@ -70,43 +72,44 @@ func (channel *Channel) QueryRecord(startTime, endTime string) int {
|
||||
channel.recordStartTime, _ = time.Parse(TIME_LAYOUT, startTime)
|
||||
channel.recordEndTime, _ = time.Parse(TIME_LAYOUT, endTime)
|
||||
channel.Records = nil
|
||||
requestMsg := channel.CreateRequst(MESSAGE)
|
||||
requestMsg.ContentType = "Application/MANSCDP+xml"
|
||||
requestMsg.Body = fmt.Sprintf(`<?xml version="1.0"?>
|
||||
<Query>
|
||||
<CmdType>RecordInfo</CmdType>
|
||||
<SN>%d</SN>
|
||||
<DeviceID>%s</DeviceID>
|
||||
<StartTime>%s</StartTime>
|
||||
<EndTime>%s</EndTime>
|
||||
<Secrecy>0</Secrecy>
|
||||
<Type>all</Type>
|
||||
</Query>`, d.sn, requestMsg.To.Uri.UserInfo(), startTime, endTime)
|
||||
requestMsg.ContentLength = len(requestMsg.Body)
|
||||
resp, err := d.SipRequestForResponse(requestMsg)
|
||||
request := d.CreateRequest(sip.MESSAGE)
|
||||
contentType := sip.ContentType("Application/MANSCDP+xml")
|
||||
request.AppendHeader(&contentType)
|
||||
body := fmt.Sprintf(`<?xml version="1.0"?>
|
||||
<Query>
|
||||
<CmdType>RecordInfo</CmdType>
|
||||
<SN>%d</SN>
|
||||
<DeviceID>%s</DeviceID>
|
||||
<StartTime>%s</StartTime>
|
||||
<EndTime>%s</EndTime>
|
||||
<Secrecy>0</Secrecy>
|
||||
<Type>all</Type>
|
||||
</Query>`, d.sn, d.ID, startTime, endTime)
|
||||
request.SetBody(body, true)
|
||||
resp, err := d.SipRequestForResponse(request)
|
||||
if err != nil {
|
||||
return http.StatusRequestTimeout
|
||||
}
|
||||
return resp.GetStatusCode()
|
||||
|
||||
return int(resp.StatusCode())
|
||||
}
|
||||
func (channel *Channel) Control(PTZCmd string) int {
|
||||
d := channel.device
|
||||
requestMsg := channel.CreateRequst(MESSAGE)
|
||||
requestMsg.ContentType = "Application/MANSCDP+xml"
|
||||
requestMsg.Body = fmt.Sprintf(`<?xml version="1.0"?>
|
||||
<Control>
|
||||
<CmdType>DeviceControl</CmdType>
|
||||
<SN>%d</SN>
|
||||
<DeviceID>%s</DeviceID>
|
||||
<PTZCmd>%s</PTZCmd>
|
||||
</Control>`, d.sn, requestMsg.To.Uri.UserInfo(), PTZCmd)
|
||||
requestMsg.ContentLength = len(requestMsg.Body)
|
||||
resp, err := d.SipRequestForResponse(requestMsg)
|
||||
request := d.CreateRequest(sip.MESSAGE)
|
||||
contentType := sip.ContentType("Application/MANSCDP+xml")
|
||||
request.AppendHeader(&contentType)
|
||||
body := fmt.Sprintf(`<?xml version="1.0"?>
|
||||
<Control>
|
||||
<CmdType>DeviceControl</CmdType>
|
||||
<SN>%d</SN>
|
||||
<DeviceID>%s</DeviceID>
|
||||
<PTZCmd>%s</PTZCmd>
|
||||
</Control>`, d.sn, d.ID, PTZCmd)
|
||||
request.SetBody(body, true)
|
||||
resp, err := d.SipRequestForResponse(request)
|
||||
if err != nil {
|
||||
return http.StatusRequestTimeout
|
||||
}
|
||||
return resp.GetStatusCode()
|
||||
return int(resp.StatusCode())
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -183,7 +186,7 @@ func (channel *Channel) Invite(start, end string) (code int) {
|
||||
} else {
|
||||
ssrc[0] = '0'
|
||||
}
|
||||
|
||||
config := channel.device.config
|
||||
// size := 1
|
||||
// fps := 15
|
||||
// bitrate := 200
|
||||
@@ -193,43 +196,45 @@ func (channel *Channel) Invite(start, end string) (code int) {
|
||||
copy(ssrc[6:], []byte(strconv.Itoa(randNum)))
|
||||
protocol := ""
|
||||
port := config.MediaPort
|
||||
if config.TCP {
|
||||
if config.IsMediaNetworkTCP() {
|
||||
protocol = "TCP/"
|
||||
port = config.MediaPort + channel.tcpPortIndex
|
||||
if channel.tcpPortIndex++; channel.tcpPortIndex >= config.TCPMediaPortNum {
|
||||
if channel.tcpPortIndex++; channel.tcpPortIndex >= config.MediaPortMax {
|
||||
channel.tcpPortIndex = 0
|
||||
}
|
||||
}
|
||||
sdpInfo := []string{
|
||||
"v=0",
|
||||
fmt.Sprintf("o=%s 0 0 IN IP4 %s", d.Serial, d.SipIP),
|
||||
fmt.Sprintf("o=%s 0 0 IN IP4 %s", d.ID, d.config.SipIP),
|
||||
"s=" + s,
|
||||
"u=" + channel.DeviceID + ":0",
|
||||
"c=IN IP4 " + d.SipIP,
|
||||
"c=IN IP4 " + d.config.MediaIP,
|
||||
fmt.Sprintf("t=%d %d", sint, eint),
|
||||
fmt.Sprintf("m=video %d %sRTP/AVP 96", port, protocol),
|
||||
"a=recvonly",
|
||||
"a=rtpmap:96 PS/90000",
|
||||
}
|
||||
if config.TCP {
|
||||
sdpInfo = append(sdpInfo, "a=setup:passive", "a=connection:new")
|
||||
// if config.IsMediaNetworkTCP() {
|
||||
// sdpInfo = append(sdpInfo, "a=setup:passive", "a=connection:new")
|
||||
// }
|
||||
invite := channel.CreateRequst(sip.INVITE)
|
||||
contentType := sip.ContentType("application/sdp")
|
||||
invite.AppendHeader(&contentType)
|
||||
|
||||
invite.SetBody(strings.Join(sdpInfo, "\r\n")+"\r\ny="+string(ssrc)+"\r\n", true)
|
||||
|
||||
subject := sip.GenericHeader{
|
||||
HeaderName: "Subject", Contents: fmt.Sprintf("%s:%s,%s:0", channel.DeviceID, ssrc, config.Serial),
|
||||
}
|
||||
invite := channel.CreateRequst(INVITE)
|
||||
invite.ContentType = "application/sdp"
|
||||
invite.Contact = &Contact{
|
||||
Uri: NewURI(fmt.Sprintf("%s@%s:%d", d.Serial, d.SipIP, d.SipPort)),
|
||||
}
|
||||
invite.Body = strings.Join(sdpInfo, "\r\n") + "\r\ny=" + string(ssrc) + "\r\n"
|
||||
invite.ContentLength = len(invite.Body)
|
||||
invite.Subject = fmt.Sprintf("%s:%s,%s:0", channel.DeviceID, ssrc, config.Serial)
|
||||
response, _ := d.Core.SipRequestForResponse(invite)
|
||||
if response == nil {
|
||||
invite.AppendHeader(&subject)
|
||||
response, err := d.SipRequestForResponse(invite)
|
||||
if response == nil || err != nil {
|
||||
return http.StatusRequestTimeout
|
||||
}
|
||||
Printf("Channel :%s invite response status code: %d\n", channel.DeviceID, response.GetStatusCode())
|
||||
plugin.Info(fmt.Sprintf("Channel :%s invite response status code: %d", channel.DeviceID, response.StatusCode()))
|
||||
|
||||
if response.GetStatusCode() == 200 {
|
||||
ds := strings.Split(response.Body, "\r\n")
|
||||
if response.StatusCode() == 200 {
|
||||
ds := strings.Split(response.Body(), "\r\n")
|
||||
_SSRC, _ := strconv.ParseInt(string(ssrc), 10, 0)
|
||||
SSRC := uint32(_SSRC)
|
||||
for _, l := range ds {
|
||||
@@ -241,21 +246,20 @@ func (channel *Channel) Invite(start, end string) (code int) {
|
||||
}
|
||||
}
|
||||
}
|
||||
publisher := &Publisher{
|
||||
Stream: &engine.Stream{
|
||||
StreamPath: streamPath,
|
||||
AutoCloseAfter: &config.AutoCloseAfter,
|
||||
},
|
||||
publisher := &GBPublisher{
|
||||
StreamPath: streamPath,
|
||||
config: config,
|
||||
}
|
||||
if config.UdpCacheSize > 0 && !config.TCP {
|
||||
if config.UdpCacheSize > 0 && !config.IsMediaNetworkTCP() {
|
||||
publisher.udpCache = utils.NewPqRtp()
|
||||
}
|
||||
publishers := &config.publishers
|
||||
if start == "" {
|
||||
publisher.Type = "GB28181 Live"
|
||||
publisher.OnClose = func() {
|
||||
publishers.Remove(SSRC)
|
||||
channel.LivePublisher = nil
|
||||
channel.ByeBye((*Request)(channel.inviteRes))
|
||||
channel.ByeBye(channel.inviteRes)
|
||||
channel.inviteRes = nil
|
||||
atomic.StoreInt32(&channel.state, 0)
|
||||
if config.AutoInvite {
|
||||
@@ -267,7 +271,7 @@ func (channel *Channel) Invite(start, end string) (code int) {
|
||||
publisher.OnClose = func() {
|
||||
publishers.Remove(SSRC)
|
||||
channel.RecordPublisher = nil
|
||||
channel.ByeBye((*Request)(channel.recordInviteRes))
|
||||
channel.ByeBye(channel.recordInviteRes)
|
||||
channel.recordInviteRes = nil
|
||||
}
|
||||
}
|
||||
@@ -276,28 +280,20 @@ func (channel *Channel) Invite(start, end string) (code int) {
|
||||
}
|
||||
publishers.Add(SSRC, publisher)
|
||||
if start == "" {
|
||||
channel.inviteRes = response
|
||||
channel.inviteRes = &response
|
||||
channel.LivePublisher = publisher
|
||||
} else {
|
||||
channel.RecordPublisher = publisher
|
||||
channel.recordInviteRes = response
|
||||
channel.recordInviteRes = &response
|
||||
}
|
||||
ack := d.CreateMessage(ACK)
|
||||
ack.StartLine = &StartLine{
|
||||
Uri: NewURI(channel.DeviceID + "@" + d.to.Uri.Domain()),
|
||||
Method: ACK,
|
||||
}
|
||||
ack.From = response.From
|
||||
ack.To = response.To
|
||||
ack.CallID = response.CallID
|
||||
ack.CSeq.ID = invite.CSeq.ID
|
||||
d.Respond(&Response{Message: ack})
|
||||
ack := sip.NewAckRequest("", invite, response, "", nil)
|
||||
(*GetSipServer()).Send(ack)
|
||||
} else if start == "" && config.AutoInvite {
|
||||
time.AfterFunc(time.Second*5, func() {
|
||||
channel.Invite("", "")
|
||||
})
|
||||
}
|
||||
return response.GetStatusCode()
|
||||
return int(response.StatusCode())
|
||||
}
|
||||
func (channel *Channel) Bye(live bool) int {
|
||||
if live && channel.inviteRes != nil {
|
||||
@@ -307,7 +303,7 @@ func (channel *Channel) Bye(live bool) int {
|
||||
channel.LivePublisher.Close()
|
||||
}
|
||||
}()
|
||||
return channel.ByeBye((*Request)(channel.inviteRes)).GetStatusCode()
|
||||
return int((*channel.ByeBye(channel.inviteRes)).StatusCode())
|
||||
}
|
||||
if !live && channel.recordInviteRes != nil {
|
||||
defer func() {
|
||||
@@ -316,27 +312,26 @@ func (channel *Channel) Bye(live bool) int {
|
||||
channel.RecordPublisher.Close()
|
||||
}
|
||||
}()
|
||||
return channel.ByeBye((*Request)(channel.recordInviteRes)).GetStatusCode()
|
||||
return int((*channel.ByeBye(channel.recordInviteRes)).StatusCode())
|
||||
}
|
||||
return 404
|
||||
}
|
||||
func (c *Channel) ByeBye(res *Request) *Response {
|
||||
func (c *Channel) ByeBye(res *sip.Response) *sip.Response {
|
||||
if res == nil {
|
||||
return nil
|
||||
}
|
||||
d := c.device
|
||||
bye := c.device.CreateMessage(BYE)
|
||||
bye.StartLine = &StartLine{
|
||||
Uri: NewURI(c.DeviceID + "@" + c.device.to.Uri.Domain()),
|
||||
Method: BYE,
|
||||
}
|
||||
bye.From = res.From
|
||||
bye.To = res.To
|
||||
bye.CallID = res.CallID
|
||||
req := &Request{}
|
||||
req.Message = bye
|
||||
|
||||
resp, _ := d.SipRequestForResponse(req)
|
||||
return resp
|
||||
d := c.device
|
||||
bye := c.CreateRequst(sip.BYE)
|
||||
from, _ := (*res).From()
|
||||
to, _ := (*res).To()
|
||||
callId, _ := (*res).CallID()
|
||||
|
||||
bye.ReplaceHeaders(from.Name(), []sip.Header{from})
|
||||
bye.ReplaceHeaders(to.Name(), []sip.Header{to})
|
||||
bye.ReplaceHeaders(callId.Name(), []sip.Header{callId})
|
||||
|
||||
resp, _ := d.SipRequestForResponse(bye)
|
||||
return &resp
|
||||
|
||||
}
|
||||
|
||||
409
device.go
409
device.go
@@ -1,19 +1,19 @@
|
||||
package gb28181
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Monibuca/engine/v3"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/sip"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/transaction"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/utils"
|
||||
. "github.com/Monibuca/utils/v3"
|
||||
"go.uber.org/zap"
|
||||
"m7s.live/engine/v4"
|
||||
"m7s.live/plugin/gb28181/v4/utils"
|
||||
|
||||
// . "github.com/logrusorgru/aurora"
|
||||
"github.com/ghettovoice/gosip/sip"
|
||||
)
|
||||
|
||||
const TIME_LAYOUT = "2006-01-02T15:04:05"
|
||||
@@ -42,31 +42,62 @@ var (
|
||||
)
|
||||
|
||||
type Device struct {
|
||||
*transaction.Core `json:"-"`
|
||||
ID string
|
||||
Name string
|
||||
Manufacturer string
|
||||
Model string
|
||||
Owner string
|
||||
RegisterTime time.Time
|
||||
UpdateTime time.Time
|
||||
LastKeepaliveAt time.Time
|
||||
Status string
|
||||
Channels []*Channel
|
||||
sn int
|
||||
from *sip.Contact
|
||||
to *sip.Contact
|
||||
Addr string
|
||||
SipIP string //暴露的IP
|
||||
SourceAddr net.Addr
|
||||
channelMap map[string]*Channel
|
||||
channelMutex sync.RWMutex
|
||||
subscriber struct {
|
||||
//*transaction.Core `json:"-"`
|
||||
config *GB28181Config
|
||||
ID string
|
||||
Name string
|
||||
Manufacturer string
|
||||
Model string
|
||||
Owner string
|
||||
RegisterTime time.Time
|
||||
UpdateTime time.Time
|
||||
LastKeepaliveAt time.Time
|
||||
Status string
|
||||
Channels []*Channel
|
||||
sn int
|
||||
addr sip.Address
|
||||
tx *sip.ServerTransaction
|
||||
NetAddr string
|
||||
channelMap map[string]*Channel
|
||||
channelMutex sync.RWMutex
|
||||
subscriber struct {
|
||||
CallID string
|
||||
Timeout time.Time
|
||||
}
|
||||
}
|
||||
|
||||
func (config *GB28181Config) StoreDevice(id string, req sip.Request, tx *sip.ServerTransaction) {
|
||||
var d *Device
|
||||
plugin.Debug("StoreDevice", zap.String("id", id))
|
||||
|
||||
from, _ := req.From()
|
||||
deviceAddr := sip.Address{
|
||||
DisplayName: from.DisplayName,
|
||||
Uri: from.Address,
|
||||
}
|
||||
|
||||
if _d, loaded := Devices.Load(id); loaded {
|
||||
d = _d.(*Device)
|
||||
d.UpdateTime = time.Now()
|
||||
d.NetAddr = req.Source()
|
||||
d.addr = deviceAddr
|
||||
} else {
|
||||
d = &Device{
|
||||
ID: id,
|
||||
RegisterTime: time.Now(),
|
||||
UpdateTime: time.Now(),
|
||||
Status: string(sip.REGISTER),
|
||||
addr: deviceAddr,
|
||||
tx: tx,
|
||||
NetAddr: req.Source(),
|
||||
channelMap: make(map[string]*Channel),
|
||||
config: config,
|
||||
}
|
||||
Devices.Store(id, d)
|
||||
go d.Catalog()
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Device) addChannel(channel *Channel) {
|
||||
for _, c := range d.Channels {
|
||||
if c.DeviceID == channel.DeviceID {
|
||||
@@ -80,8 +111,8 @@ func (d *Device) CheckSubStream() {
|
||||
d.channelMutex.Lock()
|
||||
defer d.channelMutex.Unlock()
|
||||
for _, c := range d.Channels {
|
||||
if s := engine.FindStream("sub/" + c.DeviceID); s != nil {
|
||||
c.LiveSubSP = s.StreamPath
|
||||
if s := engine.Streams.Get("sub/" + c.DeviceID); s != nil {
|
||||
c.LiveSubSP = s.Path
|
||||
} else {
|
||||
c.LiveSubSP = ""
|
||||
}
|
||||
@@ -91,7 +122,7 @@ func (d *Device) UpdateChannels(list []*Channel) {
|
||||
d.channelMutex.Lock()
|
||||
defer d.channelMutex.Unlock()
|
||||
for _, c := range list {
|
||||
if _, ok := Ignores[c.DeviceID]; ok {
|
||||
if _, ok := d.config.Ignores[c.DeviceID]; ok {
|
||||
continue
|
||||
}
|
||||
if c.ParentID != "" {
|
||||
@@ -109,7 +140,7 @@ func (d *Device) UpdateChannels(list []*Channel) {
|
||||
}
|
||||
if old, ok := d.channelMap[c.DeviceID]; ok {
|
||||
c.ChannelEx = old.ChannelEx
|
||||
if config.PreFetchRecord {
|
||||
if d.config.PreFetchRecord {
|
||||
n := time.Now()
|
||||
n = time.Date(n.Year(), n.Month(), n.Day(), 0, 0, 0, 0, time.Local)
|
||||
if len(c.Records) == 0 || (n.Format(TIME_LAYOUT) == c.RecordStartTime &&
|
||||
@@ -117,8 +148,8 @@ func (d *Device) UpdateChannels(list []*Channel) {
|
||||
go c.QueryRecord(n.Format(TIME_LAYOUT), n.Add(time.Hour*24-time.Second).Format(TIME_LAYOUT))
|
||||
}
|
||||
}
|
||||
if config.AutoInvite &&
|
||||
(c.LivePublisher == nil || (c.LivePublisher.VideoTracks.Size == 0 && c.LivePublisher.AudioTracks.Size == 0)) {
|
||||
if d.config.AutoInvite &&
|
||||
(c.LivePublisher == nil) {
|
||||
c.Invite("", "")
|
||||
}
|
||||
|
||||
@@ -126,12 +157,12 @@ func (d *Device) UpdateChannels(list []*Channel) {
|
||||
c.ChannelEx = &ChannelEx{
|
||||
device: d,
|
||||
}
|
||||
if config.AutoInvite {
|
||||
if d.config.AutoInvite {
|
||||
c.Invite("", "")
|
||||
}
|
||||
}
|
||||
if s := engine.FindStream("sub/" + c.DeviceID); s != nil {
|
||||
c.LiveSubSP = s.StreamPath
|
||||
if s := engine.Streams.Get("sub/" + c.DeviceID); s != nil {
|
||||
c.LiveSubSP = s.Path
|
||||
} else {
|
||||
c.LiveSubSP = ""
|
||||
}
|
||||
@@ -146,115 +177,135 @@ func (d *Device) UpdateRecord(channelId string, list []*Record) {
|
||||
d.channelMutex.RUnlock()
|
||||
}
|
||||
|
||||
func (d *Device) CreateMessage(Method sip.Method) (requestMsg *sip.Message) {
|
||||
func (d *Device) CreateRequest(Method sip.RequestMethod) (req sip.Request) {
|
||||
d.sn++
|
||||
requestMsg = &sip.Message{
|
||||
Mode: sip.SIP_MESSAGE_REQUEST,
|
||||
MaxForwards: 70,
|
||||
UserAgent: "Monibuca",
|
||||
StartLine: &sip.StartLine{
|
||||
Method: Method,
|
||||
Uri: d.to.Uri,
|
||||
}, Via: &sip.Via{
|
||||
Transport: "UDP",
|
||||
Host: d.Core.SipIP,
|
||||
Port: fmt.Sprintf("%d", d.SipPort),
|
||||
Params: map[string]string{
|
||||
"branch": fmt.Sprintf("z9hG4bK%s", utils.RandNumString(8)),
|
||||
"rport": "-1", //only key,no-value
|
||||
},
|
||||
}, From: &sip.Contact{Uri: d.from.Uri, Params: map[string]string{"tag": utils.RandNumString(9)}},
|
||||
To: d.to, CSeq: &sip.CSeq{
|
||||
ID: uint32(d.sn),
|
||||
Method: Method,
|
||||
}, CallID: utils.RandNumString(10),
|
||||
Addr: d.Addr,
|
||||
}
|
||||
var err2 error
|
||||
requestMsg.DestAdd, err2 = d.ResolveAddress(requestMsg)
|
||||
if err2 != nil {
|
||||
return nil
|
||||
}
|
||||
//intranet ip , let's resolve it with public ip
|
||||
var deviceIp, deviceSourceIP net.IP
|
||||
switch addr := requestMsg.DestAdd.(type) {
|
||||
case *net.UDPAddr:
|
||||
deviceIp = addr.IP
|
||||
case *net.TCPAddr:
|
||||
deviceIp = addr.IP
|
||||
}
|
||||
|
||||
switch addr2 := d.SourceAddr.(type) {
|
||||
case *net.UDPAddr:
|
||||
deviceSourceIP = addr2.IP
|
||||
case *net.TCPAddr:
|
||||
deviceSourceIP = addr2.IP
|
||||
callId := sip.CallID(utils.RandNumString(10))
|
||||
userAgent := sip.UserAgentHeader("Monibuca")
|
||||
cseq := sip.CSeq{
|
||||
SeqNo: uint32(d.sn),
|
||||
MethodName: Method,
|
||||
}
|
||||
if deviceIp.IsPrivate() && !deviceSourceIP.IsPrivate() {
|
||||
requestMsg.DestAdd = d.SourceAddr
|
||||
port := sip.Port(d.config.SipPort)
|
||||
serverAddr := sip.Address{
|
||||
//DisplayName: sip.String{Str: d.config.Serial},
|
||||
Uri: &sip.SipUri{
|
||||
FUser: sip.String{Str: d.config.Serial},
|
||||
FHost: d.config.SipIP,
|
||||
FPort: &port,
|
||||
},
|
||||
Params: sip.NewParams().Add("tag", sip.String{Str: utils.RandNumString(9)}),
|
||||
}
|
||||
req = sip.NewRequest(
|
||||
"",
|
||||
Method,
|
||||
d.addr.Uri,
|
||||
"SIP/2.0",
|
||||
[]sip.Header{
|
||||
serverAddr.AsFromHeader(),
|
||||
d.addr.AsToHeader(),
|
||||
&callId,
|
||||
&userAgent,
|
||||
&cseq,
|
||||
serverAddr.AsContactHeader(),
|
||||
},
|
||||
"",
|
||||
nil,
|
||||
)
|
||||
|
||||
req.SetTransport(d.config.SipNetwork)
|
||||
req.SetDestination(d.NetAddr)
|
||||
//fmt.Printf("构建请求参数:%s", *&req)
|
||||
// requestMsg.DestAdd, err2 = d.ResolveAddress(requestMsg)
|
||||
// if err2 != nil {
|
||||
// return nil
|
||||
// }
|
||||
//intranet ip , let's resolve it with public ip
|
||||
// var deviceIp, deviceSourceIP net.IP
|
||||
// switch addr := requestMsg.DestAdd.(type) {
|
||||
// case *net.UDPAddr:
|
||||
// deviceIp = addr.IP
|
||||
// case *net.TCPAddr:
|
||||
// deviceIp = addr.IP
|
||||
// }
|
||||
|
||||
// switch addr2 := d.SourceAddr.(type) {
|
||||
// case *net.UDPAddr:
|
||||
// deviceSourceIP = addr2.IP
|
||||
// case *net.TCPAddr:
|
||||
// deviceSourceIP = addr2.IP
|
||||
// }
|
||||
// if deviceIp.IsPrivate() && !deviceSourceIP.IsPrivate() {
|
||||
// requestMsg.DestAdd = d.SourceAddr
|
||||
// }
|
||||
return
|
||||
}
|
||||
func (d *Device) Subscribe() int {
|
||||
requestMsg := d.CreateMessage(sip.SUBSCRIBE)
|
||||
if d.subscriber.CallID != "" {
|
||||
requestMsg.CallID = d.subscriber.CallID
|
||||
}
|
||||
requestMsg.Expires = 3600
|
||||
requestMsg.Event = "Catalog"
|
||||
d.subscriber.Timeout = time.Now().Add(time.Second * time.Duration(requestMsg.Expires))
|
||||
requestMsg.ContentType = "Application/MANSCDP+xml"
|
||||
requestMsg.Body = sip.BuildCatalogXML(d.sn, requestMsg.To.Uri.UserInfo())
|
||||
requestMsg.ContentLength = len(requestMsg.Body)
|
||||
|
||||
request := &sip.Request{Message: requestMsg}
|
||||
response, err := d.Core.SipRequestForResponse(request)
|
||||
func (d *Device) Subscribe() int {
|
||||
request := d.CreateRequest(sip.SUBSCRIBE)
|
||||
if d.subscriber.CallID != "" {
|
||||
callId := sip.CallID(utils.RandNumString(10))
|
||||
request.AppendHeader(&callId)
|
||||
}
|
||||
expires := sip.Expires(3600)
|
||||
d.subscriber.Timeout = time.Now().Add(time.Second * time.Duration(expires))
|
||||
contentType := sip.ContentType("Application/MANSCDP+xml")
|
||||
request.AppendHeader(&contentType)
|
||||
request.AppendHeader(&expires)
|
||||
|
||||
request.SetBody(BuildCatalogXML(d.sn, d.ID), true)
|
||||
|
||||
response, err := d.SipRequestForResponse(request)
|
||||
if err == nil && response != nil {
|
||||
if response.GetStatusCode() == 200 {
|
||||
d.subscriber.CallID = requestMsg.CallID
|
||||
if response.StatusCode() == 200 {
|
||||
callId, _ := request.CallID()
|
||||
d.subscriber.CallID = string(*callId)
|
||||
} else {
|
||||
d.subscriber.CallID = ""
|
||||
}
|
||||
return response.GetStatusCode()
|
||||
return int(response.StatusCode())
|
||||
}
|
||||
return http.StatusRequestTimeout
|
||||
}
|
||||
|
||||
func (d *Device) Catalog() int {
|
||||
requestMsg := d.CreateMessage(sip.MESSAGE)
|
||||
requestMsg.Expires = 3600
|
||||
requestMsg.Event = "Catalog"
|
||||
d.subscriber.Timeout = time.Now().Add(time.Second * time.Duration(requestMsg.Expires))
|
||||
requestMsg.ContentType = "Application/MANSCDP+xml"
|
||||
requestMsg.Body = sip.BuildCatalogXML(d.sn, requestMsg.To.Uri.UserInfo())
|
||||
requestMsg.ContentLength = len(requestMsg.Body)
|
||||
request := d.CreateRequest(sip.MESSAGE)
|
||||
expires := sip.Expires(3600)
|
||||
d.subscriber.Timeout = time.Now().Add(time.Second * time.Duration(expires))
|
||||
contentType := sip.ContentType("Application/MANSCDP+xml")
|
||||
|
||||
request := &sip.Request{Message: requestMsg}
|
||||
response, err := d.Core.SipRequestForResponse(request)
|
||||
if err == nil && response != nil {
|
||||
return response.GetStatusCode()
|
||||
request.AppendHeader(&contentType)
|
||||
request.AppendHeader(&expires)
|
||||
request.SetBody(BuildCatalogXML(d.sn, d.ID), true)
|
||||
// 输出Sip请求设备通道信息信令
|
||||
plugin.Sugar().Debugf("SIP->Catalog:%s", request)
|
||||
resp, err := d.SipRequestForResponse(request)
|
||||
if err == nil && resp != nil {
|
||||
return int(resp.StatusCode())
|
||||
}
|
||||
return http.StatusRequestTimeout
|
||||
}
|
||||
|
||||
func (d *Device) QueryDeviceInfo(req *sip.Request) {
|
||||
for i := time.Duration(5); i < 100; i++ {
|
||||
|
||||
Printf("device.QueryDeviceInfo:%s ipaddr:%s", d.ID, d.Addr)
|
||||
plugin.Info(fmt.Sprintf("QueryDeviceInfo:%s ipaddr:%s", d.ID, d.NetAddr))
|
||||
time.Sleep(time.Second * i)
|
||||
requestMsg := d.CreateMessage(sip.MESSAGE)
|
||||
requestMsg.ContentType = "Application/MANSCDP+xml"
|
||||
requestMsg.Body = sip.BuildDeviceInfoXML(d.sn, requestMsg.To.Uri.UserInfo())
|
||||
requestMsg.ContentLength = len(requestMsg.Body)
|
||||
request := &sip.Request{Message: requestMsg}
|
||||
request := d.CreateRequest(sip.MESSAGE)
|
||||
contentType := sip.ContentType("Application/MANSCDP+xml")
|
||||
request.AppendHeader(&contentType)
|
||||
request.SetBody(BuildDeviceInfoXML(d.sn, d.ID), true)
|
||||
|
||||
response, _ := d.Core.SipRequestForResponse(request)
|
||||
response, _ := d.SipRequestForResponse(request)
|
||||
if response != nil {
|
||||
// via, _ := response.ViaHop()
|
||||
|
||||
if response.Via != nil && response.Via.Params["received"] != "" {
|
||||
d.SipIP = response.Via.Params["received"]
|
||||
}
|
||||
if response.GetStatusCode() != 200 {
|
||||
Printf("device %s send Catalog : %d\n", d.ID, response.GetStatusCode())
|
||||
// if via != nil && via.Params.Has("received") {
|
||||
// received, _ := via.Params.Get("received")
|
||||
// d.SipIP = received.String()
|
||||
// }
|
||||
if response.StatusCode() != 200 {
|
||||
plugin.Sugar().Errorf("device %s send Catalog : %d\n", d.ID, response.StatusCode())
|
||||
} else {
|
||||
d.Subscribe()
|
||||
break
|
||||
@@ -262,3 +313,127 @@ func (d *Device) QueryDeviceInfo(req *sip.Request) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Device) SipRequestForResponse(request sip.Request) (sip.Response, error) {
|
||||
return (*GetSipServer()).RequestWithContext(context.Background(), request)
|
||||
}
|
||||
|
||||
// MobilePositionSubscribe 移动位置订阅
|
||||
func (d *Device) MobilePositionSubscribe(id string, expires int, interval int) (code int) {
|
||||
mobilePosition := d.CreateRequest(sip.SUBSCRIBE)
|
||||
if d.subscriber.CallID != "" {
|
||||
callId := sip.CallID(utils.RandNumString(10))
|
||||
mobilePosition.ReplaceHeaders(callId.Name(), []sip.Header{&callId})
|
||||
}
|
||||
expiresHeader := sip.Expires(expires)
|
||||
d.subscriber.Timeout = time.Now().Add(time.Second * time.Duration(expires))
|
||||
contentType := sip.ContentType("Application/MANSCDP+xml")
|
||||
mobilePosition.AppendHeader(&contentType)
|
||||
mobilePosition.AppendHeader(&expiresHeader)
|
||||
|
||||
mobilePosition.SetBody(BuildDevicePositionXML(d.sn, id, interval), true)
|
||||
|
||||
response, err := d.SipRequestForResponse(mobilePosition)
|
||||
if err == nil && response != nil {
|
||||
if response.StatusCode() == 200 {
|
||||
callId, _ := mobilePosition.CallID()
|
||||
d.subscriber.CallID = callId.String()
|
||||
} else {
|
||||
d.subscriber.CallID = ""
|
||||
}
|
||||
return int(response.StatusCode())
|
||||
}
|
||||
return http.StatusRequestTimeout
|
||||
}
|
||||
|
||||
// UpdateChannelPosition 更新通道GPS坐标
|
||||
func (d *Device) UpdateChannelPosition(channelId string, gpsTime string, lng string, lat string) {
|
||||
if c, ok := d.channelMap[channelId]; ok {
|
||||
c.ChannelEx.GpsTime, _ = time.ParseInLocation("2006-01-02 15:04:05", gpsTime, time.Local)
|
||||
c.ChannelEx.Longitude = lng
|
||||
c.ChannelEx.Latitude = lat
|
||||
plugin.Sugar().Debugf("更新通道[%s]坐标成功\n", c.Name)
|
||||
} else {
|
||||
plugin.Sugar().Debugf("更新失败,未找到通道[%s]\n", channelId)
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateChannelStatus 目录订阅消息处理:新增/移除/更新通道或者更改通道状态
|
||||
func (d *Device) UpdateChannelStatus(deviceList []*notifyMessage) {
|
||||
for _, v := range deviceList {
|
||||
switch v.Event {
|
||||
case "ON":
|
||||
plugin.Debug("收到通道上线通知")
|
||||
d.channelOnline(v.DeviceID)
|
||||
case "OFF":
|
||||
plugin.Debug("收到通道离线通知")
|
||||
d.channelOffline(v.DeviceID)
|
||||
case "VLOST":
|
||||
plugin.Debug("收到通道视频丢失通知")
|
||||
d.channelOffline(v.DeviceID)
|
||||
case "DEFECT":
|
||||
plugin.Debug("收到通道故障通知")
|
||||
d.channelOffline(v.DeviceID)
|
||||
case "ADD":
|
||||
plugin.Debug("收到通道新增通知")
|
||||
channel := Channel{
|
||||
DeviceID: v.DeviceID,
|
||||
ParentID: v.ParentID,
|
||||
Name: v.Name,
|
||||
Manufacturer: v.Manufacturer,
|
||||
Model: v.Model,
|
||||
Owner: v.Owner,
|
||||
CivilCode: v.CivilCode,
|
||||
Address: v.Address,
|
||||
Parental: v.Parental,
|
||||
SafetyWay: v.SafetyWay,
|
||||
RegisterWay: v.RegisterWay,
|
||||
Secrecy: v.Secrecy,
|
||||
Status: v.Status,
|
||||
}
|
||||
d.addChannel(&channel)
|
||||
case "DEL":
|
||||
//删除
|
||||
plugin.Debug("收到通道删除通知")
|
||||
delete(d.channelMap, v.DeviceID)
|
||||
case "UPDATE":
|
||||
plugin.Debug("收到通道更新通知")
|
||||
// 更新通道
|
||||
channel := &Channel{
|
||||
DeviceID: v.DeviceID,
|
||||
ParentID: v.ParentID,
|
||||
Name: v.Name,
|
||||
Manufacturer: v.Manufacturer,
|
||||
Model: v.Model,
|
||||
Owner: v.Owner,
|
||||
CivilCode: v.CivilCode,
|
||||
Address: v.Address,
|
||||
Parental: v.Parental,
|
||||
SafetyWay: v.SafetyWay,
|
||||
RegisterWay: v.RegisterWay,
|
||||
Secrecy: v.Secrecy,
|
||||
Status: v.Status,
|
||||
}
|
||||
channels := []*Channel{channel}
|
||||
d.UpdateChannels(channels)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Device) channelOnline(DeviceID string) {
|
||||
if c, ok := d.channelMap[DeviceID]; ok {
|
||||
c.Status = "ON"
|
||||
plugin.Sugar().Debugf("通道[%s]在线\n", c.Name)
|
||||
} else {
|
||||
plugin.Sugar().Debugf("更新通道[%s]状态失败,未找到\n", DeviceID)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Device) channelOffline(DeviceID string) {
|
||||
if c, ok := d.channelMap[DeviceID]; ok {
|
||||
c.Status = "OFF"
|
||||
plugin.Sugar().Debugf("通道[%s]离线\n", c.Name)
|
||||
} else {
|
||||
plugin.Sugar().Debugf("更新通道[%s]状态失败,未找到\n", DeviceID)
|
||||
}
|
||||
}
|
||||
|
||||
48
go.mod
48
go.mod
@@ -1,14 +1,46 @@
|
||||
module github.com/Monibuca/plugin-gb28181/v3
|
||||
module m7s.live/plugin/gb28181/v4
|
||||
|
||||
go 1.13
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/Monibuca/engine/v3 v3.5.0
|
||||
github.com/Monibuca/utils/v3 v3.0.5
|
||||
github.com/agiledragon/gomonkey/v2 v2.2.0
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/ghettovoice/gosip v0.0.0-20220203123749-bc456081bd7a
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||
github.com/pion/rtp v1.7.4
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
|
||||
github.com/pion/rtp/v2 v2.0.0-20220302185659-b3d10fc096b0
|
||||
go.uber.org/zap v1.21.0
|
||||
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b
|
||||
golang.org/x/text v0.3.7
|
||||
m7s.live/engine/v4 v4.0.0-beta5
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cnotch/ipchub v1.1.0 // indirect
|
||||
github.com/discoviking/fsm v0.0.0-20150126104936-f4a273feecca // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/gobwas/ws v1.1.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.4 // indirect
|
||||
github.com/mattn/go-isatty v0.0.8 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||
github.com/pion/randutil v0.1.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/q191201771/naza v0.19.1 // indirect
|
||||
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.22.1 // indirect
|
||||
github.com/sirupsen/logrus v1.4.2 // indirect
|
||||
github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.9 // indirect
|
||||
github.com/tklauser/numcpus v0.3.0 // indirect
|
||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/sys v0.0.0-20220111092808-5a964db01320 // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
)
|
||||
|
||||
154
go.sum
154
go.sum
@@ -1,14 +1,4 @@
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
|
||||
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/Monibuca/engine/v3 v3.5.0 h1:hkuOdEXlnjcUhgDqOBPcrJ6fgv+HuWuMSpYhqvT3pfc=
|
||||
github.com/Monibuca/engine/v3 v3.5.0/go.mod h1:yNiVKeHxgv+Ez+f2RHXMkXoa5Oxv+G7Ch+MJdHi7ing=
|
||||
github.com/Monibuca/utils/v3 v3.0.5 h1:w14x0HkWTbF4MmHbINLlOwe4VJNoSOeaQChMk5E/4es=
|
||||
github.com/Monibuca/utils/v3 v3.0.5/go.mod h1:RpNS95gapWs6gimwh8Xn2x72FN5tO7Powabj7dTFyvE=
|
||||
github.com/agiledragon/gomonkey/v2 v2.2.0 h1:QJWqpdEhGV/JJy70sZ/LDnhbSlMrqHAWHcNOjz1kyuI=
|
||||
github.com/agiledragon/gomonkey/v2 v2.2.0/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY=
|
||||
github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef h1:2JGTg6JapxP9/R33ZaagQtAM4EkkSYnIAlOG5EI8gkM=
|
||||
github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef/go.mod h1:JS7hed4L1fj0hXcyEejnW57/7LCetXggd+vwrRnYeII=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/cnotch/apirouter v0.0.0-20200731232942-89e243a791f3/go.mod h1:5deJPLON/x/s2dLOQfuKS0lenhOIT4xX0pvtN/OEIuY=
|
||||
@@ -22,59 +12,115 @@ github.com/cnotch/xlog v0.0.0-20201208005456-cfda439cd3a0/go.mod h1:RW9oHsR79ffl
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/discoviking/fsm v0.0.0-20150126104936-f4a273feecca h1:cTTdXpkQ1aVbOOmHwdwtYuwUZcQtcMrleD1UXLWhAq8=
|
||||
github.com/discoviking/fsm v0.0.0-20150126104936-f4a273feecca/go.mod h1:W+3LQaEkN8qAwwcw0KC546sUEnX86GIT8CcMLZC4mG0=
|
||||
github.com/emitter-io/address v1.0.0/go.mod h1:GfZb5+S/o8694B1GMGK2imUYQyn2skszMvGNA5D84Ug=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/funny/slab v0.0.0-20180511031532-b1fad5e5d478 h1:Db9StoJ6RZN3YttC0Pm0I4Y5izITRYch3RMbT59BYN0=
|
||||
github.com/funny/slab v0.0.0-20180511031532-b1fad5e5d478/go.mod h1:0j1+svBH8ABEIPdUP0AIg4qedsybnXGJBakCEw8cfoo=
|
||||
github.com/funny/utest v0.0.0-20161029064919-43870a374500 h1:Z0r1CZnoIWFB/Uiwh1BU5FYmuFe6L5NPi6XWQEmsTRg=
|
||||
github.com/funny/utest v0.0.0-20161029064919-43870a374500/go.mod h1:mUn39tBov9jKnTWV1RlOYoNzxdBFHiSzXWdY1FoNGGg=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/ghettovoice/gosip v0.0.0-20220203123749-bc456081bd7a h1:tcQBkP7K5Ew3+eIqOStyrFxyoeIu7DL0aM+aLVexIaw=
|
||||
github.com/ghettovoice/gosip v0.0.0-20220203123749-bc456081bd7a/go.mod h1:yTr3BEYSFe9As6XM7ldyrVgqsPwlnw8Ahc4N28VFM2g=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.1.0-rc.1/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0=
|
||||
github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA=
|
||||
github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kelindar/process v0.0.0-20170730150328-69a29e249ec3/go.mod h1:+lTCLnZFXOkqwD8sLPl6u4erAc0cP8wFegQHfipz7KE=
|
||||
github.com/kelindar/rate v1.0.0/go.mod h1:AjT4G+hTItNwt30lucEGZIz8y7Uk5zPho6vurIZ+1Es=
|
||||
github.com/kelindar/tcp v1.0.0/go.mod h1:JB5hj1cshLU60XrLij2BBxW3JQ4hOye8vqbyvuKb52k=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.5 h1:obHEce3upls1IBn1gTw/o7bCv7OJb6Ib/o7wNO+4eKw=
|
||||
github.com/nxadm/tail v1.4.5/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M=
|
||||
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.10.4 h1:NiTx7EEvBzu9sFOD1zORteLSt3o8gnlvZZwSE9TnY9U=
|
||||
github.com/onsi/gomega v1.10.4/go.mod h1:g/HbgYopi++010VEqkFgJHKC09uJiW9UkXvMUuKHUCQ=
|
||||
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
||||
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||
github.com/pion/rtp v1.6.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||
github.com/pion/rtp v1.7.4 h1:4dMbjb1SuynU5OpA3kz1zHK+u+eOCQjW3MAeVHf1ODA=
|
||||
github.com/pion/rtp v1.7.4/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||
github.com/pion/rtp/v2 v2.0.0-20220302185659-b3d10fc096b0 h1:zyOGxHutZ6IhksQSMtwf3OFXB29W5R18yFQWOQJYWjU=
|
||||
github.com/pion/rtp/v2 v2.0.0-20220302185659-b3d10fc096b0/go.mod h1:Vj+rrFbJCT3yxqE/VSwaOo9DQ2pMKGPxuE7hplGOlOs=
|
||||
github.com/pixelbender/go-sdp v1.1.0/go.mod h1:6IBlz9+BrUHoFTea7gcp4S54khtOhjCW/nVDLhmZBAs=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/q191201771/naza v0.19.1 h1:4KLcxT2CHztO+7miPRtBG3FFgadSQYQw1gPPPKN7rnY=
|
||||
github.com/q191201771/naza v0.19.1/go.mod h1:5LeGupZZFtYP1g/S203n9vXoUNVdlRnPIfM6rExjqt0=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
|
||||
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM=
|
||||
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/shirou/gopsutil/v3 v3.22.1 h1:33y31Q8J32+KstqPfscvFwBlNJ6xLaBy4xqBXzlYV5w=
|
||||
github.com/shirou/gopsutil/v3 v3.22.1/go.mod h1:WapW1AOOPlHyXr+yOyw3uYx36enocrtSoSBy0L5vUHY=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sqs/goreturns v0.0.0-20181028201513-538ac6014518/go.mod h1:CKI4AZ4XmGV240rTHfO0hfE83S6/a3/Q1siZJ/vXf7A=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5 h1:hNna6Fi0eP1f2sMBe/rJicDmaHmoXGe1Ta84FPYHLuE=
|
||||
github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5/go.mod h1:f1SCnEOt6sc3fOJfPQDRDzHOtSXuTtnz0ImG9kPRDV0=
|
||||
github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo=
|
||||
github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=
|
||||
github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ=
|
||||
github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
|
||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg=
|
||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
||||
@@ -85,61 +131,87 @@ go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
|
||||
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b h1:vI32FkLJNAWtGD4BwkThwEy6XS7ZLLMHkSkYfF8M0W0=
|
||||
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201214095126-aec9a390925b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220111092808-5a964db01320 h1:0jf+tOCoZ3LyutmCOWpVni1chK4VfFLhRsDK7MhqGRY=
|
||||
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
m7s.live/engine/v4 v4.0.0-beta h1:8PlvLNPW7O2uoZenz8Y5NrTmgor5uaTqivbqVrIDQb0=
|
||||
m7s.live/engine/v4 v4.0.0-beta/go.mod h1:Ez870pCfemZCUQQYP88jIHsgbRCb9ELjtCau14YTl7E=
|
||||
m7s.live/engine/v4 v4.0.0-beta5 h1:zGMTR6x7P0fP3OFpDc7tmG37QW/NokiRNNYrrOv3J1o=
|
||||
m7s.live/engine/v4 v4.0.0-beta5/go.mod h1:Ez870pCfemZCUQQYP88jIHsgbRCb9ELjtCau14YTl7E=
|
||||
|
||||
198
handle.go
198
handle.go
@@ -2,20 +2,61 @@ package gb28181
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/xml"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/sip"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/transaction"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/utils"
|
||||
"github.com/logrusorgru/aurora"
|
||||
"fmt"
|
||||
|
||||
"github.com/logrusorgru/aurora"
|
||||
"go.uber.org/zap"
|
||||
"m7s.live/plugin/gb28181/v4/utils"
|
||||
|
||||
"github.com/ghettovoice/gosip/sip"
|
||||
|
||||
. "github.com/Monibuca/utils/v3"
|
||||
"golang.org/x/net/html/charset"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/html/charset"
|
||||
)
|
||||
|
||||
func OnRegister(req *sip.Request, tx *transaction.GBTx) {
|
||||
id := req.From.Uri.UserInfo()
|
||||
type Authorization struct {
|
||||
*sip.Authorization
|
||||
}
|
||||
|
||||
func (a *Authorization) Verify(username, passwd, realm, nonce string) bool {
|
||||
|
||||
//1、将 username,realm,password 依次组合获取 1 个字符串,并用算法加密的到密文 r1
|
||||
s1 := fmt.Sprintf("%s:%s:%s", username, realm, passwd)
|
||||
r1 := a.getDigest(s1)
|
||||
//2、将 method,即REGISTER ,uri 依次组合获取 1 个字符串,并对这个字符串使用算法 加密得到密文 r2
|
||||
s2 := fmt.Sprintf("REGISTER:%s", a.Uri())
|
||||
r2 := a.getDigest(s2)
|
||||
|
||||
if r1 == "" || r2 == "" {
|
||||
fmt.Println("Authorization algorithm wrong")
|
||||
return false
|
||||
}
|
||||
//3、将密文 1,nonce 和密文 2 依次组合获取 1 个字符串,并对这个字符串使用算法加密,获得密文 r3,即Response
|
||||
s3 := fmt.Sprintf("%s:%s:%s", r1, nonce, r2)
|
||||
r3 := a.getDigest(s3)
|
||||
|
||||
//4、计算服务端和客户端上报的是否相等
|
||||
return r3 == a.Response()
|
||||
}
|
||||
|
||||
func (a *Authorization) getDigest(raw string) string {
|
||||
switch a.Algorithm() {
|
||||
case "MD5":
|
||||
return fmt.Sprintf("%x", md5.Sum([]byte(raw)))
|
||||
default: //如果没有算法,默认使用MD5
|
||||
return fmt.Sprintf("%x", md5.Sum([]byte(raw)))
|
||||
}
|
||||
}
|
||||
|
||||
func (config *GB28181Config) OnRegister(req sip.Request, tx sip.ServerTransaction) {
|
||||
from, _ := req.From()
|
||||
|
||||
id := from.Address.User().String()
|
||||
plugin.Debug(id)
|
||||
|
||||
passAuth := false
|
||||
// 不需要密码情况
|
||||
@@ -23,58 +64,62 @@ func OnRegister(req *sip.Request, tx *transaction.GBTx) {
|
||||
passAuth = true
|
||||
} else {
|
||||
// 需要密码情况 设备第一次上报,返回401和加密算法
|
||||
if req.Authorization != nil && req.Authorization.GetUsername() != "" {
|
||||
if hdrs := req.GetHeaders("Authorization"); len(hdrs) > 0 {
|
||||
authenticateHeader := hdrs[0].(*sip.GenericHeader)
|
||||
auth := &Authorization{sip.AuthFromValue(authenticateHeader.Contents)}
|
||||
|
||||
// 有些摄像头没有配置用户名的地方,用户名就是摄像头自己的国标id
|
||||
var username string
|
||||
if req.Authorization.GetUsername() == id {
|
||||
if auth.Username() == id {
|
||||
username = id
|
||||
} else {
|
||||
username = config.Username
|
||||
}
|
||||
|
||||
if dc, ok := DeviceRegisterCount.LoadOrStore(id, 1); ok && dc.(int) > MaxRegisterCount {
|
||||
var response sip.Response
|
||||
response.Message = req.BuildResponse(http.StatusForbidden)
|
||||
_ = tx.Respond(&response)
|
||||
response := sip.NewResponseFromRequest("", req, http.StatusForbidden, "Forbidden", "")
|
||||
tx.Respond(response)
|
||||
return
|
||||
} else {
|
||||
// 设备第二次上报,校验
|
||||
_nonce, loaded := DeviceNonce.Load(id)
|
||||
if loaded && req.Authorization.Verify(username, config.Password, config.Realm, _nonce.(string)) {
|
||||
if loaded && auth.Verify(username, config.Password, config.Realm, _nonce.(string)) {
|
||||
passAuth = true
|
||||
} else {
|
||||
DeviceRegisterCount.Store(id, dc.(int)+1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if passAuth {
|
||||
storeDevice(id, tx.Core, req.Message)
|
||||
config.StoreDevice(id, req, &tx)
|
||||
DeviceNonce.Delete(id)
|
||||
DeviceRegisterCount.Delete(id)
|
||||
m := req.BuildOK()
|
||||
resp := &sip.Response{Message: m}
|
||||
_ = tx.Respond(resp)
|
||||
|
||||
_ = tx.Respond(sip.NewResponseFromRequest("", req, http.StatusOK, "OK", ""))
|
||||
} else {
|
||||
var response sip.Response
|
||||
response.Message = req.BuildResponseWithPhrase(401, "Unauthorized")
|
||||
response := sip.NewResponseFromRequest("", req, http.StatusUnauthorized, "Unauthorized", "")
|
||||
_nonce, _ := DeviceNonce.LoadOrStore(id, utils.RandNumString(32))
|
||||
response.WwwAuthenticate = sip.NewWwwAuthenticate(config.Realm, _nonce.(string), sip.DIGEST_ALGO_MD5)
|
||||
response.SourceAdd = req.DestAdd
|
||||
response.DestAdd = req.SourceAdd
|
||||
_ = tx.Respond(&response)
|
||||
auth := fmt.Sprintf(
|
||||
`Digest realm="%s",algorithm=%s,nonce="%s"`,
|
||||
config.Realm,
|
||||
"MD5",
|
||||
_nonce.(string),
|
||||
)
|
||||
response.AppendHeader(&sip.GenericHeader{
|
||||
HeaderName: "WWW-Authenticate",
|
||||
Contents: auth,
|
||||
})
|
||||
_ = tx.Respond(response)
|
||||
}
|
||||
}
|
||||
func OnMessage(req *sip.Request, tx *transaction.GBTx) {
|
||||
|
||||
if v, ok := Devices.Load(req.From.Uri.UserInfo()); ok {
|
||||
func (config *GB28181Config) OnMessage(req sip.Request, tx sip.ServerTransaction) {
|
||||
from, _ := req.From()
|
||||
id := from.Address.User().String()
|
||||
if v, ok := Devices.Load(id); ok {
|
||||
d := v.(*Device)
|
||||
d.SourceAddr = req.SourceAdd
|
||||
if d.Status == string(sip.REGISTER) {
|
||||
d.Status = "ONLINE"
|
||||
go d.QueryDeviceInfo(req)
|
||||
//go d.QueryDeviceInfo(req)
|
||||
}
|
||||
d.UpdateTime = time.Now()
|
||||
temp := &struct {
|
||||
@@ -88,13 +133,13 @@ func OnMessage(req *sip.Request, tx *transaction.GBTx) {
|
||||
DeviceList []*Channel `xml:"DeviceList>Item"`
|
||||
RecordList []*Record `xml:"RecordList>Item"`
|
||||
}{}
|
||||
decoder := xml.NewDecoder(bytes.NewReader([]byte(req.Body)))
|
||||
decoder := xml.NewDecoder(bytes.NewReader([]byte(req.Body())))
|
||||
decoder.CharsetReader = charset.NewReaderLabel
|
||||
err := decoder.Decode(temp)
|
||||
if err != nil {
|
||||
err = utils.DecodeGbk(temp, []byte(req.Body))
|
||||
err = utils.DecodeGbk(temp, []byte(req.Body()))
|
||||
if err != nil {
|
||||
Printf("decode catelog err: %s", err)
|
||||
plugin.Error("decode catelog err", zap.Error(err))
|
||||
}
|
||||
}
|
||||
var body string
|
||||
@@ -110,7 +155,7 @@ func OnMessage(req *sip.Request, tx *transaction.GBTx) {
|
||||
} else {
|
||||
for _, c := range d.Channels {
|
||||
if config.AutoInvite &&
|
||||
(c.LivePublisher == nil || (c.LivePublisher.VideoTracks.Size == 0 && c.LivePublisher.AudioTracks.Size == 0)) {
|
||||
(c.LivePublisher == nil) {
|
||||
c.Invite("", "")
|
||||
}
|
||||
}
|
||||
@@ -129,21 +174,84 @@ func OnMessage(req *sip.Request, tx *transaction.GBTx) {
|
||||
d.Model = temp.Model
|
||||
case "Alarm":
|
||||
d.Status = "Alarmed"
|
||||
body = sip.BuildAlarmResponseXML(d.ID)
|
||||
body = BuildAlarmResponseXML(d.ID)
|
||||
default:
|
||||
Println("DeviceID:", aurora.Red(d.ID), " Not supported CmdType : "+temp.CmdType+" body:\n", req.Body)
|
||||
response := &sip.Response{req.BuildResponse(http.StatusBadRequest)}
|
||||
plugin.Sugar().Warnf("DeviceID:", aurora.Red(d.ID), " Not supported CmdType : "+temp.CmdType+" body:\n", req.Body)
|
||||
response := sip.NewResponseFromRequest("", req, http.StatusBadRequest, "", "")
|
||||
tx.Respond(response)
|
||||
return
|
||||
}
|
||||
|
||||
buildOK := req.BuildOK()
|
||||
buildOK.Body = body
|
||||
response := &sip.Response{buildOK}
|
||||
tx.Respond(response)
|
||||
tx.Respond(sip.NewResponseFromRequest("", req, http.StatusOK, "OK", body))
|
||||
}
|
||||
}
|
||||
func onBye(req *sip.Request, tx *transaction.GBTx) {
|
||||
response := &sip.Response{req.BuildOK()}
|
||||
_ = tx.Respond(response)
|
||||
func (config *GB28181Config) onBye(req sip.Request, tx sip.ServerTransaction) {
|
||||
tx.Respond(sip.NewResponseFromRequest("", req, http.StatusOK, "OK", ""))
|
||||
}
|
||||
|
||||
// OnNotify 订阅通知处理
|
||||
func (config *GB28181Config) OnNotify(req sip.Request, tx sip.ServerTransaction) {
|
||||
from, _ := req.From()
|
||||
id := from.Address.User().String()
|
||||
if v, ok := Devices.Load(id); ok {
|
||||
d := v.(*Device)
|
||||
d.UpdateTime = time.Now()
|
||||
temp := &struct {
|
||||
XMLName xml.Name
|
||||
CmdType string
|
||||
DeviceID string
|
||||
Time string //位置订阅-GPS时间
|
||||
Longitude string //位置订阅-经度
|
||||
Latitude string //位置订阅-维度
|
||||
// Speed string //位置订阅-速度(km/h)(可选)
|
||||
// Direction string //位置订阅-方向(取值为当前摄像头方向与正北方的顺时针夹角,取值范围0°~360°,单位:°)(可选)
|
||||
// Altitude string //位置订阅-海拔高度,单位:m(可选)
|
||||
DeviceList []*notifyMessage `xml:"DeviceList>Item"` //目录订阅
|
||||
}{}
|
||||
decoder := xml.NewDecoder(bytes.NewReader([]byte(req.Body())))
|
||||
decoder.CharsetReader = charset.NewReaderLabel
|
||||
err := decoder.Decode(temp)
|
||||
if err != nil {
|
||||
err = utils.DecodeGbk(temp, []byte(req.Body()))
|
||||
if err != nil {
|
||||
plugin.Error("decode catelog err", zap.Error(err))
|
||||
}
|
||||
}
|
||||
var body string
|
||||
switch temp.CmdType {
|
||||
case "Catalog":
|
||||
//目录状态
|
||||
d.UpdateChannelStatus(temp.DeviceList)
|
||||
case "MobilePosition":
|
||||
//更新channel的坐标
|
||||
d.UpdateChannelPosition(temp.DeviceID, temp.Time, temp.Longitude, temp.Latitude)
|
||||
// case "Alarm":
|
||||
// //报警事件通知 TODO
|
||||
default:
|
||||
plugin.Sugar().Warnf("DeviceID:", aurora.Red(d.ID), " Not supported CmdType : "+temp.CmdType+" body:", req.Body)
|
||||
response := sip.NewResponseFromRequest("", req, http.StatusBadRequest, "", "")
|
||||
tx.Respond(response)
|
||||
return
|
||||
}
|
||||
|
||||
tx.Respond(sip.NewResponseFromRequest("", req, http.StatusOK, "OK", body))
|
||||
}
|
||||
}
|
||||
|
||||
type notifyMessage struct {
|
||||
DeviceID string
|
||||
ParentID string
|
||||
Name string
|
||||
Manufacturer string
|
||||
Model string
|
||||
Owner string
|
||||
CivilCode string
|
||||
Address string
|
||||
Parental int
|
||||
SafetyWay int
|
||||
RegisterWay int
|
||||
Secrecy int
|
||||
Status string
|
||||
//状态改变事件 ON:上线,OFF:离线,VLOST:视频丢失,DEFECT:故障,ADD:增加,DEL:删除,UPDATE:更新(必选)
|
||||
Event string
|
||||
}
|
||||
|
||||
422
main.go
422
main.go
@@ -1,333 +1,89 @@
|
||||
package gb28181
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Monibuca/engine/v3"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/sip"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/transaction"
|
||||
. "github.com/Monibuca/utils/v3"
|
||||
. "github.com/logrusorgru/aurora"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
var (
|
||||
Ignores = make(map[string]struct{})
|
||||
publishers Publishers
|
||||
serverConfig *transaction.Config
|
||||
)
|
||||
|
||||
const MaxRegisterCount = 3
|
||||
|
||||
func FindChannel(deviceId string, channelId string) (c *Channel) {
|
||||
if v, ok := Devices.Load(deviceId); ok {
|
||||
d := v.(*Device)
|
||||
d.channelMutex.RLock()
|
||||
c = d.channelMap[channelId]
|
||||
d.channelMutex.RUnlock()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type Publishers struct {
|
||||
data map[uint32]*Publisher
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func (p *Publishers) Add(key uint32, pp *Publisher) {
|
||||
p.Lock()
|
||||
p.data[key] = pp
|
||||
p.Unlock()
|
||||
}
|
||||
func (p *Publishers) Remove(key uint32) {
|
||||
p.Lock()
|
||||
delete(p.data, key)
|
||||
p.Unlock()
|
||||
}
|
||||
func (p *Publishers) Get(key uint32) *Publisher {
|
||||
p.RLock()
|
||||
defer p.RUnlock()
|
||||
return p.data[key]
|
||||
}
|
||||
|
||||
var config = struct {
|
||||
Serial string
|
||||
Realm string
|
||||
ListenAddr string
|
||||
Expires int
|
||||
MediaPort uint16
|
||||
AutoInvite bool
|
||||
AutoCloseAfter int
|
||||
Ignore []string
|
||||
TCP bool
|
||||
TCPMediaPortNum uint16
|
||||
RemoveBanInterval int
|
||||
PreFetchRecord bool
|
||||
Username string
|
||||
Password string
|
||||
UdpCacheSize int //udp排序缓存
|
||||
LogVerbose bool
|
||||
}{"34020000002000000001", "3402000000", "127.0.0.1:5060", 3600, 58200, false, -1, nil, false, 1, 600, false, "", "", 0, false}
|
||||
|
||||
func init() {
|
||||
pc := engine.PluginConfig{
|
||||
Name: "GB28181",
|
||||
Config: &config,
|
||||
}
|
||||
pc.Install(run)
|
||||
publishers.data = make(map[uint32]*Publisher)
|
||||
}
|
||||
|
||||
func storeDevice(id string, s *transaction.Core, req *sip.Message) {
|
||||
var d *Device
|
||||
|
||||
if _d, loaded := Devices.Load(id); loaded {
|
||||
d = _d.(*Device)
|
||||
d.UpdateTime = time.Now()
|
||||
d.from = &sip.Contact{Uri: req.StartLine.Uri, Params: make(map[string]string)}
|
||||
d.to = req.To
|
||||
d.Addr = req.Via.GetSendBy()
|
||||
|
||||
//TODO: Should we send GetDeviceInf request?
|
||||
//message := d.CreateMessage(sip.MESSAGE)
|
||||
//message.Body = sip.GetDeviceInfoXML(d.ID)
|
||||
|
||||
//request := &sip.Request{Message: message}
|
||||
//if newTx, err := s.Request(request); err == nil {
|
||||
// if _, err = newTx.SipResponse(); err != nil {
|
||||
// Println("notify device after register,", err)
|
||||
// return
|
||||
// }
|
||||
//}
|
||||
|
||||
} else {
|
||||
Devices.Store(id, &Device{
|
||||
ID: id,
|
||||
RegisterTime: time.Now(),
|
||||
UpdateTime: time.Now(),
|
||||
Status: string(sip.REGISTER),
|
||||
Core: s,
|
||||
from: &sip.Contact{Uri: req.StartLine.Uri, Params: make(map[string]string)},
|
||||
to: req.To,
|
||||
Addr: req.Via.GetSendBy(),
|
||||
SipIP: serverConfig.MediaIP,
|
||||
channelMap: make(map[string]*Channel),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func run() {
|
||||
ipAddr, err := net.ResolveUDPAddr("", config.ListenAddr)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
Print(Green("server gb28181 start at"), BrightBlue(config.ListenAddr))
|
||||
for _, id := range config.Ignore {
|
||||
Ignores[id] = struct{}{}
|
||||
}
|
||||
useTCP := config.TCP
|
||||
serverConfig = &transaction.Config{
|
||||
SipIP: ipAddr.IP.String(),
|
||||
SipPort: uint16(ipAddr.Port),
|
||||
SipNetwork: "UDP",
|
||||
Serial: config.Serial,
|
||||
Realm: config.Realm,
|
||||
Username: config.Username,
|
||||
Password: config.Password,
|
||||
AckTimeout: 10,
|
||||
MediaIP: ipAddr.IP.String(),
|
||||
RegisterValidity: config.Expires,
|
||||
RegisterInterval: 60,
|
||||
HeartbeatInterval: 60,
|
||||
HeartbeatRetry: 3,
|
||||
AudioEnable: true,
|
||||
WaitKeyFrame: true,
|
||||
MediaIdleTimeout: 30,
|
||||
RemoveBanInterval: config.RemoveBanInterval,
|
||||
UdpCacheSize: config.UdpCacheSize,
|
||||
LogVerbose: config.LogVerbose,
|
||||
}
|
||||
|
||||
s := transaction.NewCore(serverConfig)
|
||||
s.RegistHandler(sip.REGISTER, OnRegister)
|
||||
s.RegistHandler(sip.MESSAGE, OnMessage)
|
||||
s.RegistHandler(sip.BYE, onBye)
|
||||
|
||||
//OnStreamClosedHooks.AddHook(func(stream *Stream) {
|
||||
// Devices.Range(func(key, value interface{}) bool {
|
||||
// device:=value.(*Device)
|
||||
// for _,channel := range device.Channels {
|
||||
// if stream.StreamPath == channel.RecordSP {
|
||||
//
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
//})
|
||||
if useTCP {
|
||||
listenMediaTCP()
|
||||
} else {
|
||||
go listenMediaUDP()
|
||||
}
|
||||
// go queryCatalog(serverConfig)
|
||||
if serverConfig.Username != "" || serverConfig.Password != "" {
|
||||
go removeBanDevice(serverConfig)
|
||||
}
|
||||
|
||||
http.HandleFunc("/api/gb28181/query/records", func(w http.ResponseWriter, r *http.Request) {
|
||||
CORS(w, r)
|
||||
id := r.URL.Query().Get("id")
|
||||
channel := r.URL.Query().Get("channel")
|
||||
startTime := r.URL.Query().Get("startTime")
|
||||
endTime := r.URL.Query().Get("endTime")
|
||||
if c := FindChannel(id, channel); c != nil {
|
||||
w.WriteHeader(c.QueryRecord(startTime, endTime))
|
||||
} else {
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
})
|
||||
http.HandleFunc("/api/gb28181/list", func(w http.ResponseWriter, r *http.Request) {
|
||||
CORS(w, r)
|
||||
sse := NewSSE(w, r.Context())
|
||||
for {
|
||||
var list []*Device
|
||||
Devices.Range(func(key, value interface{}) bool {
|
||||
device := value.(*Device)
|
||||
if time.Since(device.UpdateTime) > time.Duration(serverConfig.RegisterValidity)*time.Second {
|
||||
Devices.Delete(key)
|
||||
} else {
|
||||
list = append(list, device)
|
||||
}
|
||||
return true
|
||||
})
|
||||
sse.WriteJSON(list)
|
||||
select {
|
||||
case <-time.After(time.Second * 5):
|
||||
case <-sse.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
http.HandleFunc("/api/gb28181/control", func(w http.ResponseWriter, r *http.Request) {
|
||||
CORS(w, r)
|
||||
id := r.URL.Query().Get("id")
|
||||
channel := r.URL.Query().Get("channel")
|
||||
ptzcmd := r.URL.Query().Get("ptzcmd")
|
||||
if c := FindChannel(id, channel); c != nil {
|
||||
w.WriteHeader(c.Control(ptzcmd))
|
||||
} else {
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
})
|
||||
http.HandleFunc("/api/gb28181/invite", func(w http.ResponseWriter, r *http.Request) {
|
||||
CORS(w, r)
|
||||
query := r.URL.Query()
|
||||
id := query.Get("id")
|
||||
channel := r.URL.Query().Get("channel")
|
||||
startTime := query.Get("startTime")
|
||||
endTime := query.Get("endTime")
|
||||
if c := FindChannel(id, channel); c != nil {
|
||||
if startTime == "" && c.LivePublisher != nil {
|
||||
w.WriteHeader(304) //直播流已存在
|
||||
} else {
|
||||
w.WriteHeader(c.Invite(startTime, endTime))
|
||||
}
|
||||
} else {
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
})
|
||||
http.HandleFunc("/api/gb28181/bye", func(w http.ResponseWriter, r *http.Request) {
|
||||
CORS(w, r)
|
||||
id := r.URL.Query().Get("id")
|
||||
channel := r.URL.Query().Get("channel")
|
||||
live := r.URL.Query().Get("live")
|
||||
if c := FindChannel(id, channel); c != nil {
|
||||
w.WriteHeader(c.Bye(live != "false"))
|
||||
} else {
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
})
|
||||
|
||||
s.StartAndWait()
|
||||
}
|
||||
func listenMediaTCP() {
|
||||
for i := uint16(0); i < config.TCPMediaPortNum; i++ {
|
||||
addr := ":" + strconv.Itoa(int(config.MediaPort+i))
|
||||
go ListenTCP(addr, func(conn net.Conn) {
|
||||
var rtpPacket rtp.Packet
|
||||
reader := bufio.NewReader(conn)
|
||||
lenBuf := make([]byte, 2)
|
||||
defer conn.Close()
|
||||
var err error
|
||||
for err == nil {
|
||||
if _, err = io.ReadFull(reader, lenBuf); err != nil {
|
||||
return
|
||||
}
|
||||
ps := make([]byte, BigEndian.Uint16(lenBuf))
|
||||
if _, err = io.ReadFull(reader, ps); err != nil {
|
||||
return
|
||||
}
|
||||
if err := rtpPacket.Unmarshal(ps); err != nil {
|
||||
Println("gb28181 decode rtp error:", err)
|
||||
} else if publisher := publishers.Get(rtpPacket.SSRC); publisher != nil && publisher.Err() == nil {
|
||||
publisher.PushPS(&rtpPacket)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
func listenMediaUDP() {
|
||||
var rtpPacket rtp.Packet
|
||||
networkBuffer := 1048576
|
||||
addr := ":" + strconv.Itoa(int(config.MediaPort))
|
||||
conn, err := ListenUDP(addr, networkBuffer)
|
||||
if err != nil {
|
||||
Printf("listen udp %s err: %v", addr, err)
|
||||
return
|
||||
}
|
||||
bufUDP := make([]byte, networkBuffer)
|
||||
Printf("udp server start listen video port[%d]", config.MediaPort)
|
||||
defer Printf("udp server stop listen video port[%d]", config.MediaPort)
|
||||
for n, _, err := conn.ReadFromUDP(bufUDP); err == nil; n, _, err = conn.ReadFromUDP(bufUDP) {
|
||||
ps := bufUDP[:n]
|
||||
if err := rtpPacket.Unmarshal(ps); err != nil {
|
||||
Println("gb28181 decode rtp error:", err)
|
||||
}
|
||||
if publisher := publishers.Get(rtpPacket.SSRC); publisher != nil && publisher.Err() == nil {
|
||||
publisher.PushPS(&rtpPacket)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// func queryCatalog(config *transaction.Config) {
|
||||
// t := time.NewTicker(time.Duration(config.CatalogInterval) * time.Second)
|
||||
// for range t.C {
|
||||
// Devices.Range(func(key, value interface{}) bool {
|
||||
// device := value.(*Device)
|
||||
// if time.Since(device.UpdateTime) > time.Duration(config.RegisterValidity)*time.Second {
|
||||
// Devices.Delete(key)
|
||||
// } else if device.Channels != nil {
|
||||
// go device.Catalog()
|
||||
// }
|
||||
// return true
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
func removeBanDevice(config *transaction.Config) {
|
||||
t := time.NewTicker(time.Duration(config.RemoveBanInterval) * time.Second)
|
||||
for range t.C {
|
||||
DeviceRegisterCount.Range(func(key, value interface{}) bool {
|
||||
if value.(int) > MaxRegisterCount {
|
||||
DeviceRegisterCount.Delete(key)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
package gb28181
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
. "m7s.live/engine/v4"
|
||||
"m7s.live/engine/v4/config"
|
||||
)
|
||||
|
||||
type GB28181Config struct {
|
||||
AutoInvite bool
|
||||
AutoCloseAfter int
|
||||
PreFetchRecord bool
|
||||
|
||||
//sip服务器的配置
|
||||
SipNetwork string //传输协议,默认UDP,可选TCP
|
||||
SipIP string //sip 服务器公网IP
|
||||
SipPort uint16 //sip 服务器端口,默认 5060
|
||||
Serial string //sip 服务器 id, 默认 34020000002000000001
|
||||
Realm string //sip 服务器域,默认 3402000000
|
||||
Username string //sip 服务器账号
|
||||
Password string //sip 服务器密码
|
||||
|
||||
AckTimeout uint16 //sip 服务应答超时,单位秒
|
||||
RegisterValidity int //注册有效期,单位秒,默认 3600
|
||||
RegisterInterval int //注册间隔,单位秒,默认 60
|
||||
HeartbeatInterval int //心跳间隔,单位秒,默认 60
|
||||
HeartbeatRetry int //心跳超时次数,默认 3
|
||||
|
||||
//媒体服务器配置
|
||||
MediaIP string //媒体服务器地址
|
||||
MediaPort uint16 //媒体服务器端口
|
||||
MediaNetwork string //媒体传输协议,默认UDP,可选TCP
|
||||
MediaPortMin uint16
|
||||
MediaPortMax uint16
|
||||
MediaIdleTimeout uint16 //推流超时时间,超过则断开链接,让设备重连
|
||||
|
||||
AudioEnable bool //是否开启音频
|
||||
LogVerbose bool
|
||||
WaitKeyFrame bool //是否等待关键帧,如果等待,则在收到第一个关键帧之前,忽略所有媒体流
|
||||
RemoveBanInterval int //移除禁止设备间隔
|
||||
UdpCacheSize int //udp缓存大小
|
||||
|
||||
config.Publish
|
||||
Server
|
||||
}
|
||||
|
||||
func (c *GB28181Config) OnEvent(event any) {
|
||||
switch event.(type) {
|
||||
case FirstConfig:
|
||||
c.startServer()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *GB28181Config) IsMediaNetworkTCP() bool {
|
||||
return strings.ToLower(c.MediaNetwork) == "tcp"
|
||||
}
|
||||
|
||||
var conf = &GB28181Config{
|
||||
AutoInvite: true,
|
||||
AutoCloseAfter: -1,
|
||||
PreFetchRecord: false,
|
||||
UdpCacheSize: 0,
|
||||
SipNetwork: "udp",
|
||||
SipIP: "127.0.0.1",
|
||||
SipPort: 5060,
|
||||
Serial: "34020000002000000001",
|
||||
Realm: "3402000000",
|
||||
Username: "",
|
||||
Password: "",
|
||||
|
||||
AckTimeout: 10,
|
||||
RegisterValidity: 60,
|
||||
RegisterInterval: 60,
|
||||
HeartbeatInterval: 60,
|
||||
HeartbeatRetry: 3,
|
||||
|
||||
MediaIP: "127.0.0.1",
|
||||
MediaPort: 58200,
|
||||
MediaIdleTimeout: 30,
|
||||
MediaNetwork: "udp",
|
||||
|
||||
RemoveBanInterval: 600,
|
||||
LogVerbose: false,
|
||||
AudioEnable: true,
|
||||
WaitKeyFrame: true,
|
||||
}
|
||||
|
||||
var plugin = InstallPlugin(conf)
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
package sip
|
||||
package gb28181
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Request Request
|
||||
type Request struct {
|
||||
*Message
|
||||
}
|
||||
|
||||
var (
|
||||
// CatalogXML 获取设备列表xml样式
|
||||
CatalogXML = `<?xml version="1.0"?><Query>
|
||||
@@ -38,6 +33,14 @@ var (
|
||||
<DeviceID>%s</DeviceID>
|
||||
</Query>
|
||||
`
|
||||
// DevicePositionXML 订阅设备位置
|
||||
DevicePositionXML = `<?xml version="1.0"?>
|
||||
<Query>
|
||||
<CmdType>MobilePosition</CmdType>
|
||||
<SN>%d</SN>
|
||||
<DeviceID>%s</DeviceID>
|
||||
<Interval>%d</Interval>
|
||||
</Query>`
|
||||
)
|
||||
|
||||
// BuildDeviceInfoXML 获取设备详情指令
|
||||
@@ -54,3 +57,24 @@ func BuildCatalogXML(sn int, id string) string {
|
||||
func BuildRecordInfoXML(sn int, id string, start, end int64) string {
|
||||
return fmt.Sprintf(RecordInfoXML, sn, id, time.Unix(start, 0).Format("2006-01-02T15:04:05"), time.Unix(end, 0).Format("2006-01-02T15:04:05"))
|
||||
}
|
||||
|
||||
// BuildDevicePositionXML 订阅设备位置
|
||||
func BuildDevicePositionXML(sn int, id string, interval int) string {
|
||||
return fmt.Sprintf(DevicePositionXML, sn, id, interval)
|
||||
}
|
||||
|
||||
// AlarmResponseXML alarm response xml样式
|
||||
var (
|
||||
AlarmResponseXML = `<?xml version="1.0"?>
|
||||
<Response>
|
||||
<CmdType>Alarm</CmdType>
|
||||
<SN>17430</SN>
|
||||
<DeviceID>%s</DeviceID>
|
||||
</Response>
|
||||
`
|
||||
)
|
||||
|
||||
// BuildRecordInfoXML 获取录像文件列表指令
|
||||
func BuildAlarmResponseXML(id string) string {
|
||||
return fmt.Sprintf(AlarmResponseXML, id)
|
||||
}
|
||||
131
publisher.go
131
publisher.go
@@ -1,69 +1,83 @@
|
||||
package gb28181
|
||||
|
||||
import (
|
||||
"github.com/Monibuca/engine/v3"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/utils"
|
||||
. "github.com/Monibuca/utils/v3"
|
||||
"github.com/pion/rtp"
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/pion/rtp/v2"
|
||||
. "m7s.live/engine/v4"
|
||||
"m7s.live/engine/v4/common"
|
||||
"m7s.live/engine/v4/track"
|
||||
. "m7s.live/engine/v4/track"
|
||||
"m7s.live/plugin/gb28181/v4/utils"
|
||||
)
|
||||
|
||||
type Publisher struct {
|
||||
*engine.Stream
|
||||
parser *utils.DecPSPackage
|
||||
pushVideo func(uint32, uint32, []byte)
|
||||
pushAudio func(uint32, []byte)
|
||||
lastSeq uint16
|
||||
udpCache *utils.PriorityQueueRtp
|
||||
type GBPublisher struct {
|
||||
Publisher
|
||||
StreamPath string
|
||||
parser *utils.DecPSPackage
|
||||
OnClose func()
|
||||
lastSeq uint16
|
||||
udpCache *utils.PriorityQueueRtp
|
||||
config *GB28181Config
|
||||
vt common.VideoTrack
|
||||
at *track.G711
|
||||
}
|
||||
|
||||
func (p *Publisher) PushVideo(ts uint32, cts uint32, payload []byte) {
|
||||
p.pushVideo(ts, cts, payload)
|
||||
}
|
||||
func (p *Publisher) PushAudio(ts uint32, payload []byte) {
|
||||
p.pushAudio(ts, payload)
|
||||
}
|
||||
func (p *Publisher) Publish() (result bool) {
|
||||
if result = p.Stream.Publish(); result {
|
||||
p.pushVideo = func(ts uint32, cts uint32, payload []byte) {
|
||||
var vt *engine.VideoTrack
|
||||
switch p.parser.VideoStreamType {
|
||||
case utils.StreamTypeH264:
|
||||
vt = p.Stream.NewVideoTrack(7)
|
||||
case utils.StreamTypeH265:
|
||||
vt = p.Stream.NewVideoTrack(12)
|
||||
default:
|
||||
return
|
||||
}
|
||||
vt.PushAnnexB(ts, cts, payload)
|
||||
p.pushVideo = vt.PushAnnexB
|
||||
}
|
||||
p.pushAudio = func(ts uint32, payload []byte) {
|
||||
switch p.parser.AudioStreamType {
|
||||
case utils.G711A:
|
||||
at := p.Stream.NewAudioTrack(7)
|
||||
at.SoundRate = 8000
|
||||
at.SoundSize = 16
|
||||
at.Channels = 1
|
||||
at.ExtraData = []byte{(at.CodecID << 4) | (1 << 1)}
|
||||
at.PushRaw(ts, payload)
|
||||
p.pushAudio = at.PushRaw
|
||||
// case utils.G711U:
|
||||
// at := p.Stream.NewAudioTrack(8)
|
||||
// at.SoundRate = 8000
|
||||
// at.SoundSize = 16
|
||||
// asc := at.CodecID << 4
|
||||
// asc = asc + 1<<1
|
||||
// at.ExtraData = []byte{asc}
|
||||
// at.PushRaw(pack)
|
||||
// p.pushAudio = at.PushRaw
|
||||
}
|
||||
func (p *GBPublisher) PushVideo(pts uint32, dts uint32, payload []byte) {
|
||||
if p.vt == nil {
|
||||
switch p.parser.VideoStreamType {
|
||||
case utils.StreamTypeH264:
|
||||
p.vt = NewH264(p.Stream)
|
||||
case utils.StreamTypeH265:
|
||||
p.vt = NewH265(p.Stream)
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
p.vt.WriteAnnexB(pts, dts, payload)
|
||||
}
|
||||
func (p *Publisher) PushPS(rtp *rtp.Packet) {
|
||||
func (p *GBPublisher) PushAudio(ts uint32, payload []byte) {
|
||||
if p.at == nil {
|
||||
switch p.parser.AudioStreamType {
|
||||
case utils.G711A:
|
||||
at := NewG711(p.Stream, true)
|
||||
at.SampleRate = 8000
|
||||
at.SampleSize = 16
|
||||
at.Channels = 1
|
||||
at.AVCCHead = []byte{(byte(at.CodecID) << 4) | (1 << 1)}
|
||||
p.at = at
|
||||
case utils.G711A + 1:
|
||||
at := NewG711(p.Stream, false)
|
||||
at.SampleRate = 8000
|
||||
at.SampleSize = 16
|
||||
at.Channels = 1
|
||||
at.AVCCHead = []byte{(byte(at.CodecID) << 4) | (1 << 1)}
|
||||
p.at = at
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
p.at.WriteAVCC(ts, payload)
|
||||
}
|
||||
|
||||
func (p *GBPublisher) Publish() (result bool) {
|
||||
if err := plugin.Publish(p.StreamPath, p); err == nil {
|
||||
if p.vt != nil {
|
||||
p.vt.Detach()
|
||||
p.vt = nil
|
||||
}
|
||||
if p.at != nil {
|
||||
p.at.Detach()
|
||||
p.at = nil
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *GBPublisher) PushPS(rtp *rtp.Packet) {
|
||||
originRtp := *rtp
|
||||
if config.UdpCacheSize > 0 && config.TCP == false {
|
||||
if p.config.UdpCacheSize > 0 && !p.config.IsMediaNetworkTCP() {
|
||||
//序号小于第一个包的丢弃,rtp包序号达到65535后会从0开始,所以这里需要判断一下
|
||||
if rtp.SequenceNumber < p.lastSeq && p.lastSeq-rtp.SequenceNumber < utils.MaxRtpDiff {
|
||||
return
|
||||
@@ -76,8 +90,8 @@ func (p *Publisher) PushPS(rtp *rtp.Packet) {
|
||||
if p.lastSeq != 0 {
|
||||
// rtp序号不连续,丢弃PS
|
||||
if p.lastSeq+1 != rtp.SequenceNumber {
|
||||
if config.UdpCacheSize > 0 && config.TCP == false {
|
||||
if p.udpCache.Len() < config.UdpCacheSize {
|
||||
if p.config.UdpCacheSize > 0 && !p.config.IsMediaNetworkTCP() {
|
||||
if p.udpCache.Len() < p.config.UdpCacheSize {
|
||||
p.udpCache.Push(*rtp)
|
||||
return
|
||||
} else {
|
||||
@@ -89,11 +103,10 @@ func (p *Publisher) PushPS(rtp *rtp.Packet) {
|
||||
}
|
||||
}
|
||||
p.lastSeq = rtp.SequenceNumber
|
||||
p.Update()
|
||||
if p.parser == nil {
|
||||
p.parser = new(utils.DecPSPackage)
|
||||
}
|
||||
if len(ps) >= 4 && BigEndian.Uint32(ps) == utils.StartCodePS {
|
||||
if len(ps) >= 4 && binary.BigEndian.Uint32(ps) == utils.StartCodePS {
|
||||
if p.parser.Len() > 0 {
|
||||
p.parser.Skip(4)
|
||||
p.parser.Read(rtp.Timestamp, p)
|
||||
|
||||
107
restful.go
Normal file
107
restful.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package gb28181
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"m7s.live/engine/v4/util"
|
||||
)
|
||||
|
||||
func (conf *GB28181Config) API_list(w http.ResponseWriter, r *http.Request) {
|
||||
sse := util.NewSSE(w, r.Context())
|
||||
|
||||
for {
|
||||
var list []*Device
|
||||
Devices.Range(func(key, value interface{}) bool {
|
||||
device := value.(*Device)
|
||||
if time.Since(device.UpdateTime) > time.Duration(conf.RegisterValidity)*time.Second {
|
||||
Devices.Delete(key)
|
||||
} else {
|
||||
list = append(list, device)
|
||||
}
|
||||
return true
|
||||
})
|
||||
sse.WriteJSON(list)
|
||||
select {
|
||||
case <-time.After(time.Second * 5):
|
||||
case <-sse.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (conf *GB28181Config) API_records(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.URL.Query().Get("id")
|
||||
channel := r.URL.Query().Get("channel")
|
||||
startTime := r.URL.Query().Get("startTime")
|
||||
endTime := r.URL.Query().Get("endTime")
|
||||
if c := FindChannel(id, channel); c != nil {
|
||||
w.WriteHeader(c.QueryRecord(startTime, endTime))
|
||||
} else {
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
}
|
||||
|
||||
func (conf *GB28181Config) API_control(w http.ResponseWriter, r *http.Request) {
|
||||
// CORS(w, r)
|
||||
id := r.URL.Query().Get("id")
|
||||
channel := r.URL.Query().Get("channel")
|
||||
ptzcmd := r.URL.Query().Get("ptzcmd")
|
||||
if c := FindChannel(id, channel); c != nil {
|
||||
w.WriteHeader(c.Control(ptzcmd))
|
||||
} else {
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
}
|
||||
|
||||
func (conf *GB28181Config) API_invite(w http.ResponseWriter, r *http.Request) {
|
||||
// CORS(w, r)
|
||||
query := r.URL.Query()
|
||||
id := query.Get("id")
|
||||
channel := r.URL.Query().Get("channel")
|
||||
startTime := query.Get("startTime")
|
||||
endTime := query.Get("endTime")
|
||||
if c := FindChannel(id, channel); c != nil {
|
||||
if startTime == "" && c.LivePublisher != nil {
|
||||
w.WriteHeader(304) //直播流已存在
|
||||
} else {
|
||||
w.WriteHeader(c.Invite(startTime, endTime))
|
||||
}
|
||||
} else {
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
}
|
||||
|
||||
func (conf *GB28181Config) API_bye(w http.ResponseWriter, r *http.Request) {
|
||||
// CORS(w, r)
|
||||
id := r.URL.Query().Get("id")
|
||||
channel := r.URL.Query().Get("channel")
|
||||
live := r.URL.Query().Get("live")
|
||||
if c := FindChannel(id, channel); c != nil {
|
||||
w.WriteHeader(c.Bye(live != "false"))
|
||||
} else {
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
}
|
||||
|
||||
func (conf *GB28181Config) API_position(w http.ResponseWriter, r *http.Request) {
|
||||
//CORS(w, r)
|
||||
query := r.URL.Query()
|
||||
//设备id
|
||||
id := query.Get("id")
|
||||
//订阅周期(单位:秒)
|
||||
expires := query.Get("expires")
|
||||
//订阅间隔(单位:秒)
|
||||
interval := query.Get("interval")
|
||||
|
||||
expiresInt, _ := strconv.Atoi(expires)
|
||||
intervalInt, _ := strconv.Atoi(interval)
|
||||
|
||||
if v, ok := Devices.Load(id); ok {
|
||||
d := v.(*Device)
|
||||
w.WriteHeader(d.MobilePositionSubscribe(id, expiresInt, intervalInt))
|
||||
} else {
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
}
|
||||
114
rtpsort_test.go
114
rtpsort_test.go
@@ -1,114 +0,0 @@
|
||||
package gb28181
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Monibuca/engine/v3"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/utils"
|
||||
"github.com/agiledragon/gomonkey/v2"
|
||||
"github.com/pion/rtp"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// 测试rtp序号数据
|
||||
var items = []uint16{
|
||||
65526, 65530, 65524, 65525, 65527, 65528, 65529,
|
||||
0, 65533, 65531, 65532, 65534, 65535, 1,
|
||||
3, 6, 5, 4, 2, 8, 7,
|
||||
}
|
||||
|
||||
var items2 = []uint16{
|
||||
11672, 11673, 11674, 11675, 11676, 11677, 11678,
|
||||
11679, 11680, 11681, 11682, 11683, 11684, 11685,
|
||||
11686, 11687, 11688, 11689, 11690, 11691, 11692,
|
||||
11693, 11694, 11695, 11696, 11697, 11698, 11699,
|
||||
11700, 11701, 11702, 11703, 11704, 11705, 11706,
|
||||
11707, 11708, 11709, 11710, 11711, 11712,
|
||||
}
|
||||
|
||||
func _pushPsWithCache(p *Publisher, rtp *rtp.Packet) {
|
||||
originRtp := *rtp
|
||||
if config.UdpCacheSize > 0 && !config.TCP {
|
||||
//序号小于第一个包的丢弃,rtp包序号达到65535后会从0开始,所以这里需要判断一下
|
||||
if rtp.SequenceNumber < p.lastSeq && p.lastSeq-rtp.SequenceNumber < utils.MaxRtpDiff {
|
||||
return
|
||||
}
|
||||
p.udpCache.Push(*rtp)
|
||||
rtpTmp, _ := p.udpCache.Pop()
|
||||
rtp = &rtpTmp
|
||||
}
|
||||
|
||||
if p.lastSeq != 0 {
|
||||
// rtp序号不连续,丢弃PS
|
||||
if p.lastSeq+1 != rtp.SequenceNumber {
|
||||
if config.UdpCacheSize > 0 && !config.TCP {
|
||||
if p.udpCache.Len() < config.UdpCacheSize {
|
||||
p.udpCache.Push(*rtp)
|
||||
return
|
||||
} else {
|
||||
p.udpCache.Empty()
|
||||
rtp = &originRtp
|
||||
}
|
||||
}
|
||||
p.parser.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
p.lastSeq = rtp.SequenceNumber
|
||||
fmt.Println("rtp.SequenceNumber:", rtp.SequenceNumber)
|
||||
|
||||
}
|
||||
|
||||
// 如果运行失败可以关闭gc,go test -gcflags=all=-l -v
|
||||
func TestRtpSort(t *testing.T) {
|
||||
publisher := Publisher{
|
||||
Stream: &engine.Stream{
|
||||
StreamPath: "live/test",
|
||||
},
|
||||
udpCache: utils.NewPqRtp(),
|
||||
}
|
||||
config.UdpCacheSize = 7
|
||||
|
||||
patches := gomonkey.ApplyMethod(reflect.TypeOf(&publisher), "PushPS", _pushPsWithCache)
|
||||
defer patches.Reset()
|
||||
|
||||
for i := 0; i < len(items); i++ {
|
||||
rtpPacket := &rtp.Packet{Header: rtp.Header{SequenceNumber: items[i]}}
|
||||
publisher.PushPS(rtpPacket)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 如果运行失败可以关闭gc,go test -gcflags=all=-l -v
|
||||
// 测试有empty的情况
|
||||
func TestRtpSortWithEmpty(t *testing.T) {
|
||||
publisher := Publisher{
|
||||
Stream: &engine.Stream{
|
||||
StreamPath: "live/test",
|
||||
},
|
||||
udpCache: utils.NewPqRtp(),
|
||||
}
|
||||
config.UdpCacheSize = 7
|
||||
publisher.udpCache.Push(rtp.Packet{Header: rtp.Header{SequenceNumber: 11665}})
|
||||
patches := gomonkey.ApplyMethod(reflect.TypeOf(&publisher), "PushPS", _pushPsWithCache)
|
||||
defer patches.Reset()
|
||||
|
||||
for i := 0; i < len(items2); i++ {
|
||||
rtpPacket := &rtp.Packet{Header: rtp.Header{SequenceNumber: items2[i]}}
|
||||
publisher.PushPS(rtpPacket)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestPqSort(t *testing.T) {
|
||||
pq := utils.NewPqRtp()
|
||||
for i := 0; i < len(items); i++ {
|
||||
rtpPacket := rtp.Packet{Header: rtp.Header{SequenceNumber: items[i]}}
|
||||
pq.Push(rtpPacket)
|
||||
}
|
||||
|
||||
for pq.Len() > 0 {
|
||||
rtpPacket, _ := pq.Pop()
|
||||
fmt.Println("packet seq:", rtpPacket.SequenceNumber)
|
||||
}
|
||||
}
|
||||
195
server.go
Normal file
195
server.go
Normal file
@@ -0,0 +1,195 @@
|
||||
package gb28181
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/logrusorgru/aurora"
|
||||
"github.com/pion/rtp/v2"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ghettovoice/gosip"
|
||||
"github.com/ghettovoice/gosip/log"
|
||||
"github.com/ghettovoice/gosip/sip"
|
||||
)
|
||||
|
||||
var srv gosip.Server
|
||||
|
||||
type Server struct {
|
||||
Ignores map[string]struct{}
|
||||
publishers Publishers
|
||||
}
|
||||
|
||||
const MaxRegisterCount = 3
|
||||
|
||||
func FindChannel(deviceId string, channelId string) (c *Channel) {
|
||||
if v, ok := Devices.Load(deviceId); ok {
|
||||
d := v.(*Device)
|
||||
d.channelMutex.RLock()
|
||||
c = d.channelMap[channelId]
|
||||
d.channelMutex.RUnlock()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type Publishers struct {
|
||||
data map[uint32]*GBPublisher
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func (p *Publishers) Add(key uint32, pp *GBPublisher) {
|
||||
p.Lock()
|
||||
p.data[key] = pp
|
||||
p.Unlock()
|
||||
}
|
||||
func (p *Publishers) Remove(key uint32) {
|
||||
p.Lock()
|
||||
delete(p.data, key)
|
||||
p.Unlock()
|
||||
}
|
||||
func (p *Publishers) Get(key uint32) *GBPublisher {
|
||||
p.RLock()
|
||||
defer p.RUnlock()
|
||||
return p.data[key]
|
||||
}
|
||||
|
||||
func GetSipServer() *gosip.Server {
|
||||
return &srv
|
||||
}
|
||||
|
||||
func (config *GB28181Config) startServer() {
|
||||
config.publishers.data = make(map[uint32]*GBPublisher)
|
||||
|
||||
plugin.Info(fmt.Sprint(aurora.Green("Server gb28181 start at"), aurora.BrightBlue(":"+strconv.Itoa(int(config.SipPort)))))
|
||||
logger := log.NewDefaultLogrusLogger().WithPrefix("GB SIP Server")
|
||||
|
||||
srvConf := gosip.ServerConfig{}
|
||||
|
||||
srv = gosip.NewServer(srvConf, nil, nil, logger)
|
||||
srv.OnRequest(sip.REGISTER, config.OnRegister)
|
||||
srv.OnRequest(sip.MESSAGE, config.OnMessage)
|
||||
srv.OnRequest(sip.NOTIFY, config.OnNotify)
|
||||
srv.OnRequest(sip.BYE, config.onBye)
|
||||
|
||||
addr := "0.0.0.0:" + strconv.Itoa(int(config.SipPort))
|
||||
go srv.Listen(strings.ToLower(config.SipNetwork), addr)
|
||||
|
||||
go config.startMediaServer()
|
||||
|
||||
if config.Username != "" || config.Password != "" {
|
||||
go removeBanDevice(config)
|
||||
}
|
||||
}
|
||||
|
||||
func (config *GB28181Config) startMediaServer() {
|
||||
if config.MediaNetwork == "tcp" {
|
||||
listenMediaTCP(config)
|
||||
} else {
|
||||
listenMediaUDP(config)
|
||||
}
|
||||
}
|
||||
|
||||
func processTcpMediaConn(config *GB28181Config, conn net.Conn) {
|
||||
var rtpPacket rtp.Packet
|
||||
reader := bufio.NewReader(conn)
|
||||
lenBuf := make([]byte, 2)
|
||||
defer conn.Close()
|
||||
var err error
|
||||
for err == nil {
|
||||
if _, err = io.ReadFull(reader, lenBuf); err != nil {
|
||||
return
|
||||
}
|
||||
ps := make([]byte, binary.BigEndian.Uint16(lenBuf))
|
||||
if _, err = io.ReadFull(reader, ps); err != nil {
|
||||
return
|
||||
}
|
||||
if err := rtpPacket.Unmarshal(ps); err != nil {
|
||||
plugin.Error("gb28181 decode rtp error:", zap.Error(err))
|
||||
} else if publisher := config.publishers.Get(rtpPacket.SSRC); publisher != nil && publisher.Err() == nil {
|
||||
publisher.PushPS(&rtpPacket)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func listenMediaTCP(config *GB28181Config) {
|
||||
addr := ":" + strconv.Itoa(int(config.MediaPort))
|
||||
mediaAddr, _ := net.ResolveTCPAddr("tcp", addr)
|
||||
listen, err := net.ListenTCP("tcp", mediaAddr)
|
||||
|
||||
if err != nil {
|
||||
plugin.Error("listen media server tcp err", zap.String("addr", addr), zap.Error(err))
|
||||
return
|
||||
}
|
||||
plugin.Info("Media tcp server start.", zap.Uint16("port", config.MediaPort))
|
||||
defer listen.Close()
|
||||
defer plugin.Info("Media tcp server stop", zap.Uint16("port", config.MediaPort))
|
||||
|
||||
for {
|
||||
conn, err := listen.Accept()
|
||||
if err != nil {
|
||||
plugin.Error("Accept err=", zap.Error(err))
|
||||
}
|
||||
go processTcpMediaConn(config, conn)
|
||||
}
|
||||
}
|
||||
|
||||
func listenMediaUDP(config *GB28181Config) {
|
||||
var rtpPacket rtp.Packet
|
||||
networkBuffer := 1048576
|
||||
|
||||
addr := ":" + strconv.Itoa(int(config.MediaPort))
|
||||
mediaAddr, _ := net.ResolveUDPAddr("udp", addr)
|
||||
conn, err := net.ListenUDP("udp", mediaAddr)
|
||||
|
||||
if err != nil {
|
||||
plugin.Error("listen media server udp err", zap.String("addr", addr), zap.Error(err))
|
||||
return
|
||||
}
|
||||
bufUDP := make([]byte, networkBuffer)
|
||||
plugin.Info("Media udp server start.", zap.Uint16("port", config.MediaPort))
|
||||
defer plugin.Info("Media udp server stop", zap.Uint16("port", config.MediaPort))
|
||||
|
||||
for n, _, err := conn.ReadFromUDP(bufUDP); err == nil; n, _, err = conn.ReadFromUDP(bufUDP) {
|
||||
ps := bufUDP[:n]
|
||||
if err := rtpPacket.Unmarshal(ps); err != nil {
|
||||
plugin.Error("Decode rtp error:", zap.Error(err))
|
||||
}
|
||||
if publisher := config.publishers.Get(rtpPacket.SSRC); publisher != nil && publisher.Err() == nil {
|
||||
publisher.PushPS(&rtpPacket)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// func queryCatalog(config *transaction.Config) {
|
||||
// t := time.NewTicker(time.Duration(config.CatalogInterval) * time.Second)
|
||||
// for range t.C {
|
||||
// Devices.Range(func(key, value interface{}) bool {
|
||||
// device := value.(*Device)
|
||||
// if time.Since(device.UpdateTime) > time.Duration(config.RegisterValidity)*time.Second {
|
||||
// Devices.Delete(key)
|
||||
// } else if device.Channels != nil {
|
||||
// go device.Catalog()
|
||||
// }
|
||||
// return true
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
func removeBanDevice(config *GB28181Config) {
|
||||
t := time.NewTicker(time.Duration(config.RemoveBanInterval) * time.Second)
|
||||
for range t.C {
|
||||
DeviceRegisterCount.Range(func(key, value interface{}) bool {
|
||||
if value.(int) > MaxRegisterCount {
|
||||
DeviceRegisterCount.Delete(key)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
120
sip/README.md
120
sip/README.md
@@ -1,120 +0,0 @@
|
||||
|
||||
#### SIP消息
|
||||
|
||||
```
|
||||
SIP-message = Request / Response
|
||||
Request = Request-Line
|
||||
*( message-header )
|
||||
CRLF
|
||||
[ message-body ]
|
||||
```
|
||||
|
||||
#### SIP消息头域
|
||||
|
||||
```
|
||||
message-header = (Accept
|
||||
/ Accept-Encoding
|
||||
/ Accept-Language
|
||||
/ Alert-Info
|
||||
/ Allow
|
||||
/ Authentication-Info
|
||||
/ Authorization
|
||||
/ Call-ID
|
||||
/ Call-Info
|
||||
/ Contact
|
||||
/ Content-Disposition
|
||||
/ Content-Encoding
|
||||
/ Content-Language
|
||||
/ Content-Length
|
||||
/ Content-Type
|
||||
/ CSeq
|
||||
/ Date
|
||||
/ Error-Info
|
||||
/ Expires
|
||||
/ From
|
||||
/ In-Reply-To
|
||||
/ Max-Forwards
|
||||
/ MIME-Version
|
||||
/ Min-Expires
|
||||
/ Organization
|
||||
/ Priority
|
||||
/ Proxy-Authenticate
|
||||
/ Proxy-Authorization
|
||||
/ Proxy-Require
|
||||
/ Record-Route
|
||||
/ Reply-To
|
||||
/ Require
|
||||
/ Retry-After
|
||||
/ Route
|
||||
/ Server
|
||||
/ Subject
|
||||
/ Supported
|
||||
/ Timestamp
|
||||
/ To
|
||||
/ Unsupported
|
||||
/ User-Agent
|
||||
/ Via
|
||||
/ Warning
|
||||
/ WWW-Authenticate
|
||||
/ extension-header) CRLF
|
||||
```
|
||||
|
||||
|
||||
#### SIP 响应状态码
|
||||
|
||||
```
|
||||
Informational = "100" ; Trying
|
||||
/ "180" ; Ringing
|
||||
/ "181" ; Call Is Being Forwarded
|
||||
/ "182" ; Queued
|
||||
/ "183" ; Session Progress
|
||||
Success = "200" ; OK
|
||||
|
||||
Redirection = "300" ; Multiple Choices
|
||||
/ "301" ; Moved Permanently
|
||||
/ "302" ; Moved Temporarily
|
||||
/ "305" ; Use Proxy
|
||||
/ "380" ; Alternative Service
|
||||
|
||||
Client-Error = "400" ; Bad Request
|
||||
/ "401" ; Unauthorized
|
||||
/ "402" ; Payment Required
|
||||
/ "403" ; Forbidden
|
||||
/ "404" ; Not Found
|
||||
/ "405" ; Method Not Allowed
|
||||
/ "406" ; Not Acceptable
|
||||
/ "407" ; Proxy Authentication Required
|
||||
/ "408" ; Request Timeout
|
||||
/ "410" ; Gone
|
||||
/ "413" ; Request Entity Too Large
|
||||
/ "414" ; Request-URI Too Large
|
||||
/ "415" ; Unsupported Media Type
|
||||
/ "416" ; Unsupported URI Scheme
|
||||
/ "420" ; Bad Extension
|
||||
/ "421" ; Extension Required
|
||||
/ "423" ; Interval Too Brief
|
||||
/ "480" ; Temporarily not available
|
||||
/ "481" ; Call Leg/Transaction Does Not Exist
|
||||
/ "482" ; Loop Detected
|
||||
/ "483" ; Too Many Hops
|
||||
/ "484" ; Address Incomplete
|
||||
/ "485" ; Ambiguous
|
||||
/ "486" ; Busy Here
|
||||
/ "487" ; Request Terminated
|
||||
/ "488" ; Not Acceptable Here
|
||||
/ "491" ; Request Pending
|
||||
/ "493" ; Undecipherable
|
||||
|
||||
Server-Error = "500" ; Internal Server Error
|
||||
/ "501" ; Not Implemented
|
||||
/ "502" ; Bad Gateway
|
||||
/ "503" ; Service Unavailable
|
||||
/ "504" ; Server Time-out
|
||||
/ "505" ; SIP Version not supported
|
||||
/ "513" ; Message Too Large
|
||||
Global-Failure = "600" ; Busy Everywhere
|
||||
/ "603" ; Decline
|
||||
/ "604" ; Does not exist anywhere
|
||||
/ "606" ; Not Acceptable
|
||||
|
||||
```
|
||||
18
sip/body.go
18
sip/body.go
@@ -1,18 +0,0 @@
|
||||
package sip
|
||||
|
||||
//sip message body
|
||||
//xml解析的字段
|
||||
const (
|
||||
MESSAGE_CATALOG = "Catalog"
|
||||
MESSAGE_DEVICE_INFO = "DeviceInfo"
|
||||
MESSAGE_BROADCAST = "Broadcast"
|
||||
MESSAGE_DEVICE_STATUS = "DeviceStatus"
|
||||
MESSAGE_KEEP_ALIVE = "Keepalive"
|
||||
MESSAGE_MOBILE_POSITION = "MobilePosition"
|
||||
MESSAGE_MOBILE_POSITION_INTERVAL = "Interval"
|
||||
|
||||
ELEMENT_DEVICE_ID = "DeviceID"
|
||||
ELEMENT_DEVICE_LIST = "DeviceList"
|
||||
ELEMENT_NAME = "Name"
|
||||
ELEMENT_STATUS = "Status"
|
||||
)
|
||||
56
sip/demo.go
56
sip/demo.go
@@ -1,56 +0,0 @@
|
||||
package sip
|
||||
|
||||
import "fmt"
|
||||
|
||||
func DemoMessage() {
|
||||
registerStr := `REGISTER sip:34020000002000000001@3402000000 SIP/2.0
|
||||
Via: SIP/2.0/UDP 192.168.1.64:5060;rport;branch=z9hG4bK385701375
|
||||
From: <sip:34020000001320000001@3402000000>;tag=1840661473
|
||||
To: <sip:34020000001320000001@3402000000>
|
||||
Call-ID: 418133739
|
||||
CSeq: 1 REGISTER
|
||||
Contact: <sip:34020000001320000001@192.168.1.64:5060>
|
||||
Max-Forwards: 70
|
||||
User-Agent: IP Camera
|
||||
Expires: 3600
|
||||
Content-Length: 0`
|
||||
|
||||
fmt.Println("input:")
|
||||
fmt.Println(registerStr)
|
||||
msg, err := Decode([]byte(registerStr))
|
||||
if err != nil {
|
||||
fmt.Println("decode message failed:", err.Error())
|
||||
return
|
||||
}
|
||||
out, err := Encode(msg)
|
||||
if err != nil {
|
||||
fmt.Println("encode message failed:", err.Error())
|
||||
return
|
||||
}
|
||||
fmt.Println("=====================================")
|
||||
fmt.Println("output:")
|
||||
fmt.Println(string(out))
|
||||
}
|
||||
|
||||
func DemoVIA() {
|
||||
str1 := "SIP / 2.0 / UDP first.example.com: 4000;ttl=16 ;maddr=224.2.0.1 ;branch=z9hG4bKa7c6a8dlze.1"
|
||||
str2 := "SIP/2.0/UDP 192.168.1.64:5060;rport;received=192.168.1.64;branch=z9hG4bK1000615294"
|
||||
|
||||
var err error
|
||||
v1 := &Via{}
|
||||
err = v1.Parse(str1)
|
||||
if err != nil {
|
||||
fmt.Println("error:", err.Error())
|
||||
return
|
||||
}
|
||||
fmt.Printf("result:%v\n", v1.String())
|
||||
|
||||
v2 := &Via{}
|
||||
err = v2.Parse(str2)
|
||||
if err != nil {
|
||||
fmt.Println("error:", err.Error())
|
||||
return
|
||||
}
|
||||
fmt.Printf("result:%v\n", v2.String())
|
||||
|
||||
}
|
||||
704
sip/head.go
704
sip/head.go
@@ -1,704 +0,0 @@
|
||||
package sip
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//换行符号:
|
||||
//linux,unix : \r\n
|
||||
//windows : \n
|
||||
//Mac OS : \r
|
||||
const (
|
||||
VERSION = "SIP/2.0" // sip version
|
||||
CRLF = "\r\n" // 0x0D0A
|
||||
CRLFCRLF = "\r\n\r\n" // 0x0D0A0D0A
|
||||
DIGEST_ALGO_MD5 = "MD5"
|
||||
//CRLF = "\n" // 0x0D
|
||||
//CRLFCRLF = "\n\n" // 0x0D0D
|
||||
)
|
||||
|
||||
//SIP消息类型:请求or响应
|
||||
type Mode int
|
||||
|
||||
const (
|
||||
SIP_MESSAGE_REQUEST Mode = 0
|
||||
SIP_MESSAGE_RESPONSE Mode = 1
|
||||
)
|
||||
|
||||
//sip request method
|
||||
type Method string
|
||||
|
||||
const (
|
||||
ACK Method = "ACK"
|
||||
BYE Method = "BYE"
|
||||
CANCEL Method = "CANCEL"
|
||||
INVITE Method = "INVITE"
|
||||
OPTIONS Method = "OPTIONS"
|
||||
REGISTER Method = "REGISTER"
|
||||
NOTIFY Method = "NOTIFY"
|
||||
SUBSCRIBE Method = "SUBSCRIBE"
|
||||
MESSAGE Method = "MESSAGE"
|
||||
REFER Method = "REFER"
|
||||
INFO Method = "INFO"
|
||||
PRACK Method = "PRACK"
|
||||
UPDATE Method = "UPDATE"
|
||||
PUBLISH Method = "PUBLISH"
|
||||
)
|
||||
|
||||
//startline
|
||||
//MESSAGE sip:34020000001320000001@3402000000 SIP/2.0
|
||||
//SIP/2.0 200 OK
|
||||
type StartLine struct {
|
||||
raw string //原始内容
|
||||
|
||||
//request line: method uri version
|
||||
Method Method
|
||||
Uri URI //Request-URI:请求的服务地址,不能包含空白字符或者控制字符,并且禁止用”<>”括上。
|
||||
Version string
|
||||
|
||||
//status line: version code phrase
|
||||
Code int //status code
|
||||
phrase string
|
||||
}
|
||||
|
||||
func (l *StartLine) String() string {
|
||||
if l.Version == "" {
|
||||
l.Version = "SIP/2.0"
|
||||
}
|
||||
var result string
|
||||
if l.Method == "" {
|
||||
result = fmt.Sprintf("%s %d %s", l.Version, l.Code, l.phrase)
|
||||
} else {
|
||||
result = fmt.Sprintf("%s %s %s", l.Method, l.Uri.String(), l.Version)
|
||||
}
|
||||
l.raw = result
|
||||
return l.raw
|
||||
}
|
||||
|
||||
//To From Referto Contact
|
||||
//From: <sip:34020000001320000001@3402000000>;tag=575945878
|
||||
//To: <sip:34020000002000000001@3402000000>
|
||||
//Contact: <sip:34020000001320000001@27.38.49.149:49243>
|
||||
//Contact: <sip:34020000001320000001@192.168.1.64:5060>;expires=0
|
||||
type Contact struct {
|
||||
raw string //原始内容
|
||||
|
||||
Nickname string //可以没有
|
||||
Uri URI //
|
||||
|
||||
//header params
|
||||
Params map[string]string // include tag/q/expires
|
||||
}
|
||||
|
||||
func (c *Contact) String() string {
|
||||
sb := strings.Builder{}
|
||||
|
||||
if c.Nickname != "" {
|
||||
sb.WriteByte('"')
|
||||
sb.WriteString(c.Nickname)
|
||||
sb.WriteByte('"')
|
||||
sb.WriteByte(' ')
|
||||
}
|
||||
urlStr := c.Uri.String()
|
||||
if strings.ContainsAny(urlStr, ",?:") {
|
||||
urlStr = fmt.Sprintf("<%s>", urlStr)
|
||||
}
|
||||
sb.WriteString(urlStr)
|
||||
|
||||
if c.Params != nil {
|
||||
for k, v := range c.Params {
|
||||
sb.WriteString(";")
|
||||
sb.WriteString(k)
|
||||
sb.WriteString("=")
|
||||
sb.WriteString(v)
|
||||
}
|
||||
}
|
||||
|
||||
c.raw = sb.String()
|
||||
return c.raw
|
||||
}
|
||||
|
||||
func (c *Contact) Parse(str string) (err error) {
|
||||
c.raw = str
|
||||
|
||||
if str == "*" {
|
||||
c.Uri.host = "*"
|
||||
return
|
||||
}
|
||||
|
||||
n0 := strings.IndexByte(str, '"')
|
||||
if n0 != -1 {
|
||||
str = str[n0+1:]
|
||||
n1 := strings.IndexByte(str, '"')
|
||||
if n1 == -1 {
|
||||
return errors.New("parse nickname failed")
|
||||
}
|
||||
c.Nickname = str[:n1]
|
||||
str = strings.TrimSpace(str[n1+1:])
|
||||
}
|
||||
|
||||
if len(str) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var uriDone = false
|
||||
if strings.ContainsAny(str, "<>") {
|
||||
n2 := strings.IndexByte(str, '<')
|
||||
n3 := strings.IndexByte(str, '>')
|
||||
if n2 == -1 || n3 == -1 {
|
||||
err = errors.New("parse contact-uri failed")
|
||||
return
|
||||
}
|
||||
c.Uri, err = parseURI(str[n2+1 : n3])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
uriDone = true
|
||||
str = strings.TrimSpace(str[n3+1:])
|
||||
}
|
||||
|
||||
if len(str) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
str = strings.Trim(str, ";")
|
||||
arr1 := strings.Split(str, ";")
|
||||
for idx, one := range arr1 {
|
||||
//如果上面没有通过<>解析出来uri,则用分号split的第一个元素,就是uri字符串
|
||||
if !uriDone && idx == 0 {
|
||||
c.Uri, err = parseURI(one)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
if c.Params == nil {
|
||||
c.Params = make(map[string]string)
|
||||
}
|
||||
arr2 := strings.Split(one, "=")
|
||||
k, v := arr2[0], arr2[1]
|
||||
c.Params[k] = v
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
//Via: SIP/2.0/UDP 192.168.1.64:5060;rport=49243;received=27.38.49.149;branch=z9hG4bK879576192
|
||||
//Params:
|
||||
//Received : IPv4address / IPv6address
|
||||
//RPort : 0-not found, -1-no-value, other-value
|
||||
//Branch : branch参数的值必须用magic cookie "z9hG4bK" 作为开头
|
||||
|
||||
/*
|
||||
Via = ( "Via" / "v" ) HCOLON via-parm *(COMMA via-parm)
|
||||
via-parm = sent-protocol LWS sent-by *( SEMI via-params )
|
||||
via-params = via-ttl / via-maddr
|
||||
/ via-received / via-branch
|
||||
/ via-extension
|
||||
via-ttl = "ttl" EQUAL ttl
|
||||
via-maddr = "maddr" EQUAL host
|
||||
via-received = "received" EQUAL (IPv4address / IPv6address)
|
||||
via-branch = "branch" EQUAL token
|
||||
via-extension = generic-param
|
||||
sent-protocol = protocol-name SLASH protocol-version
|
||||
SLASH transport
|
||||
protocol-name = "SIP" / token
|
||||
protocol-version = token
|
||||
transport = "UDP" / "TCP" / "TLS" / "SCTP"
|
||||
/ other-transport
|
||||
sent-by = host [ COLON port ]
|
||||
ttl = 1*3DIGIT ; 0 to 255
|
||||
*/
|
||||
type Via struct {
|
||||
raw string // 原始内容
|
||||
Version string // sip version: default to SIP/2.0
|
||||
Transport string // UDP,TCP ,TLS , SCTP
|
||||
Host string // sent-by : host:port
|
||||
Port string //
|
||||
//header params
|
||||
Params map[string]string // include branch/rport/received/ttl/maddr
|
||||
}
|
||||
|
||||
func (v *Via) GetBranch() string {
|
||||
return v.Params["branch"]
|
||||
}
|
||||
|
||||
func (v *Via) GetSendBy() string {
|
||||
var host, port string
|
||||
|
||||
sb := strings.Builder{}
|
||||
received := v.Params["received"]
|
||||
rport := v.Params["rport"]
|
||||
|
||||
if received != "" {
|
||||
host = received
|
||||
} else {
|
||||
host = v.Host
|
||||
}
|
||||
|
||||
if rport != "" && rport != "0" && rport != "-1" {
|
||||
port = rport
|
||||
} else if v.Port != "" {
|
||||
port = v.Port
|
||||
} else {
|
||||
if strings.ToUpper(v.Transport) == "UDP" {
|
||||
port = "5060"
|
||||
} else {
|
||||
port = "5061"
|
||||
}
|
||||
}
|
||||
|
||||
sb.WriteString(host)
|
||||
sb.WriteString(":")
|
||||
sb.WriteString(port)
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
func (v *Via) String() string {
|
||||
sb := strings.Builder{}
|
||||
if v.Version == "" {
|
||||
v.Version = "SIP/2.0"
|
||||
}
|
||||
|
||||
if v.Transport == "" {
|
||||
v.Transport = "UDP"
|
||||
}
|
||||
|
||||
sb.WriteString(v.Version)
|
||||
sb.WriteString("/")
|
||||
sb.WriteString(v.Transport)
|
||||
sb.WriteString(" ")
|
||||
sb.WriteString(v.Host)
|
||||
if v.Port != "" {
|
||||
sb.WriteString(":")
|
||||
sb.WriteString(v.Port)
|
||||
}
|
||||
|
||||
if v.Params != nil {
|
||||
for k, v := range v.Params {
|
||||
sb.WriteString(";")
|
||||
sb.WriteString(k)
|
||||
if v == "-1" {
|
||||
//rport 值为-1的时候,没有值
|
||||
continue
|
||||
}
|
||||
sb.WriteString("=")
|
||||
sb.WriteString(v)
|
||||
}
|
||||
}
|
||||
|
||||
v.raw = sb.String()
|
||||
return v.raw
|
||||
}
|
||||
|
||||
//注意via允许以下这种添加空白
|
||||
//Via: SIP / 2.0 / UDP first.example.com: 4000;ttl=16 ;maddr=224.2.0.1 ;branch=z9hG4bKa7c6a8dlze.1
|
||||
//Via: SIP/2.0/UDP 192.168.1.64:5060;rport=5060;received=192.168.1.64;branch=z9hG4bK1000615294
|
||||
func (v *Via) Parse(str string) (err error) {
|
||||
v.raw = str
|
||||
|
||||
str = strings.Trim(str, ";")
|
||||
arr1 := strings.Split(str, ";")
|
||||
part1 := strings.TrimSpace(arr1[0]) //SIP / 2.0 / UDP first.example.com: 4000
|
||||
|
||||
v.Host, v.Port = "", ""
|
||||
if n1 := strings.IndexByte(part1, ':'); n1 != -1 {
|
||||
v.Port = strings.TrimSpace(part1[n1+1:])
|
||||
part1 = strings.TrimSpace(part1[:n1])
|
||||
}
|
||||
|
||||
n2 := strings.LastIndexByte(part1, ' ')
|
||||
if n2 == -1 {
|
||||
v.Host = part1 //error?
|
||||
} else {
|
||||
v.Host = strings.TrimSpace(part1[n2+1:])
|
||||
|
||||
//解析protocol、version和transport,SIP / 2.0 / UDP
|
||||
part2 := part1[:n2]
|
||||
arr2 := strings.Split(part2, "/")
|
||||
if len(arr2) != 3 {
|
||||
err = errors.New("parse contait part1.1 failed:" + part2)
|
||||
return
|
||||
}
|
||||
v.Version = fmt.Sprintf("%s/%s", strings.TrimSpace(arr2[0]), strings.TrimSpace(arr2[1]))
|
||||
v.Transport = strings.TrimSpace(arr2[2])
|
||||
}
|
||||
|
||||
//必须有参数
|
||||
v.Params = make(map[string]string)
|
||||
for i, one := range arr1 {
|
||||
if i == 0 {
|
||||
//arr[0]已经处理
|
||||
continue
|
||||
}
|
||||
one = strings.TrimSpace(one)
|
||||
arr2 := strings.Split(one, "=")
|
||||
//rport 这个参数可能没有 value。 -1:no-value, other-value
|
||||
if len(arr2) == 1 {
|
||||
if arr2[0] == "rport" {
|
||||
v.Params["rport"] = "-1"
|
||||
continue
|
||||
} else {
|
||||
fmt.Println("invalid param:", one)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
k, val := arr2[0], arr2[1]
|
||||
v.Params[k] = val
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
//CSeq: 101 INVITE
|
||||
//CSeq: 2 REGISTER
|
||||
type CSeq struct {
|
||||
raw string //原始内容
|
||||
ID uint32
|
||||
Method Method
|
||||
}
|
||||
|
||||
func (c *CSeq) String() string {
|
||||
c.raw = fmt.Sprintf("%d %s", c.ID, c.Method)
|
||||
return c.raw
|
||||
}
|
||||
|
||||
func (c *CSeq) Parse(str string) error {
|
||||
c.raw = str
|
||||
arr1 := strings.Split(str, " ")
|
||||
n, err := strconv.ParseInt(arr1[0], 10, 64)
|
||||
if err != nil {
|
||||
fmt.Println("parse cseq faield:", str)
|
||||
return err
|
||||
}
|
||||
c.ID = uint32(n)
|
||||
if len(arr1) < 2 {
|
||||
return errors.New("no method: " + str)
|
||||
}
|
||||
c.Method = Method(arr1[1])
|
||||
return nil
|
||||
}
|
||||
|
||||
//sip:user:password@domain;uri-parameters?headers
|
||||
/*
|
||||
RFC3261
|
||||
SIP-URI = "sip:" [ userinfo ] hostport
|
||||
uri-parameters [ headers ]
|
||||
SIPS-URI = "sips:" [ userinfo ] hostport
|
||||
uri-parameters [ headers ]
|
||||
userinfo = ( user / telephone-subscriber ) [ ":" password ] "@"
|
||||
user = 1*( unreserved / escaped / user-unreserved )
|
||||
user-unreserved = "&" / "=" / "+" / "$" / "," / ";" / "?" / "/"
|
||||
password = *( unreserved / escaped /
|
||||
"&" / "=" / "+" / "$" / "," )
|
||||
hostport = host [ ":" port ]
|
||||
host = hostname / IPv4address / IPv6reference
|
||||
hostname = *( domainlabel "." ) toplabel [ "." ]
|
||||
domainlabel = alphanum
|
||||
/ alphanum *( alphanum / "-" ) alphanum
|
||||
toplabel = ALPHA / ALPHA *( alphanum / "-" ) alphanum
|
||||
|
||||
IPv4address = 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT
|
||||
IPv6reference = "[" IPv6address "]"
|
||||
IPv6address = hexpart [ ":" IPv4address ]
|
||||
hexpart = hexseq / hexseq "::" [ hexseq ] / "::" [ hexseq ]
|
||||
hexseq = hex4 *( ":" hex4)
|
||||
hex4 = 1*4HEXDIG
|
||||
port = 1*DIGIT
|
||||
|
||||
uri-parameters = *( ";" uri-parameter)
|
||||
uri-parameter = transport-param / user-param / method-param
|
||||
/ ttl-param / maddr-param / lr-param / other-param
|
||||
transport-param = "transport="
|
||||
( "udp" / "tcp" / "sctp" / "tls"
|
||||
/ other-transport)
|
||||
other-transport = token
|
||||
user-param = "user=" ( "phone" / "ip" / other-user)
|
||||
other-user = token
|
||||
method-param = "method=" Method
|
||||
ttl-param = "ttl=" ttl
|
||||
maddr-param = "maddr=" host
|
||||
lr-param = "lr"
|
||||
other-param = pname [ "=" pvalue ]
|
||||
pname = 1*paramchar
|
||||
pvalue = 1*paramchar
|
||||
paramchar = param-unreserved / unreserved / escaped
|
||||
param-unreserved = "[" / "]" / "/" / ":" / "&" / "+" / "$"
|
||||
|
||||
headers = "?" header *( "&" header )
|
||||
header = hname "=" hvalue
|
||||
hname = 1*( hnv-unreserved / unreserved / escaped )
|
||||
hvalue = *( hnv-unreserved / unreserved / escaped )
|
||||
hnv-unreserved = "[" / "]" / "/" / "?" / ":" / "+" / "$"
|
||||
*/
|
||||
type URI struct {
|
||||
scheme string // sip sips
|
||||
host string // userinfo@domain or userinfo@ip:port
|
||||
method string // uri和method有关?
|
||||
params map[string]string // include branch/maddr/received/ttl/rport
|
||||
headers map[string]string // include branch/maddr/received/ttl/rport
|
||||
}
|
||||
|
||||
func (u *URI) Host() string {
|
||||
return u.host
|
||||
}
|
||||
func (u *URI) UserInfo() string {
|
||||
return strings.Split(u.host, "@")[0]
|
||||
}
|
||||
func (u *URI) Domain() string {
|
||||
return strings.Split(u.host, "@")[1]
|
||||
}
|
||||
func (u *URI) IP() string {
|
||||
t := strings.Split(u.host, "@")
|
||||
if len(t) == 1 {
|
||||
return strings.Split(t[0], ":")[0]
|
||||
}
|
||||
return strings.Split(t[1], ":")[0]
|
||||
}
|
||||
func (u *URI) Port() string {
|
||||
t := strings.Split(u.host, "@")
|
||||
if len(t) == 1 {
|
||||
return strings.Split(t[0], ":")[1]
|
||||
}
|
||||
return strings.Split(t[1], ":")[1]
|
||||
}
|
||||
func (u *URI) String() string {
|
||||
if u.scheme == "" {
|
||||
u.scheme = "sip"
|
||||
}
|
||||
sb := strings.Builder{}
|
||||
sb.WriteString(u.scheme)
|
||||
sb.WriteString(":")
|
||||
sb.WriteString(u.host)
|
||||
if u.params != nil {
|
||||
for k, v := range u.params {
|
||||
sb.WriteString(";")
|
||||
sb.WriteString(k)
|
||||
sb.WriteString("=")
|
||||
sb.WriteString(v)
|
||||
}
|
||||
}
|
||||
|
||||
if u.headers != nil {
|
||||
sb.WriteString("?")
|
||||
for k, v := range u.headers {
|
||||
sb.WriteString("&")
|
||||
sb.WriteString(k)
|
||||
sb.WriteString("=")
|
||||
sb.WriteString(v)
|
||||
}
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
//对于gb28181,request-uri 不带参数
|
||||
func NewURI(host string) URI {
|
||||
return URI{
|
||||
scheme: "sip",
|
||||
host: host,
|
||||
}
|
||||
}
|
||||
func parseURI(str string) (ret URI, err error) {
|
||||
ret = URI{}
|
||||
|
||||
//解析scheme
|
||||
str = strings.TrimSpace(str)
|
||||
n1 := strings.IndexByte(str, ':')
|
||||
if n1 == -1 {
|
||||
err = errors.New("invalid sheme")
|
||||
return
|
||||
}
|
||||
ret.scheme = str[:n1]
|
||||
str = str[n1+1:]
|
||||
if len(str) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
//解析host
|
||||
n2 := strings.IndexByte(str, ';')
|
||||
if n2 == -1 {
|
||||
ret.host = str
|
||||
return
|
||||
}
|
||||
ret.host = str[:n2]
|
||||
|
||||
str = str[n2+1:]
|
||||
if len(str) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
//解析params and headers
|
||||
var paramStr, headerStr = "", ""
|
||||
n3 := strings.IndexByte(str, '?')
|
||||
if n3 == -1 {
|
||||
paramStr = str
|
||||
} else {
|
||||
paramStr = str[:n3]
|
||||
headerStr = str[n3+1:]
|
||||
}
|
||||
|
||||
//k1=v1;k2=v2
|
||||
if paramStr != "" {
|
||||
ret.params = make(map[string]string)
|
||||
paramStr = strings.Trim(paramStr, ";")
|
||||
arr1 := strings.Split(paramStr, ";")
|
||||
for _, one := range arr1 {
|
||||
tmp := strings.Split(one, "=")
|
||||
if len(tmp) == 2 {
|
||||
k, v := tmp[0], tmp[1]
|
||||
ret.params[k] = v
|
||||
} else {
|
||||
ret.params[tmp[0]] = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//k1=v1&k2=v2
|
||||
if headerStr != "" {
|
||||
ret.headers = make(map[string]string)
|
||||
arr2 := strings.Split(paramStr, "&")
|
||||
for _, one := range arr2 {
|
||||
tmp := strings.Split(one, "=")
|
||||
var k, v string
|
||||
if len(tmp) == 2 {
|
||||
k, v = tmp[0], tmp[1]
|
||||
} else {
|
||||
k = tmp[0]
|
||||
}
|
||||
ret.headers[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type WwwAuthenticate struct {
|
||||
realm string
|
||||
nonce string
|
||||
algorithm string
|
||||
//opaque string // gb28181不需要这字段,海康有
|
||||
}
|
||||
|
||||
func NewWwwAuthenticate(realm, nonce, algorithm string) *WwwAuthenticate {
|
||||
return &WwwAuthenticate{
|
||||
realm: realm,
|
||||
nonce: nonce,
|
||||
algorithm: algorithm,
|
||||
}
|
||||
}
|
||||
|
||||
// WWW-Authenticate: Digest realm="hik", nonce="a8afe6fcbee6331d89d3eb0d3d19ce39", opaque="a853e4f25298413f9bf3a9aa6767857d", algorithm=MD5
|
||||
func (w *WwwAuthenticate) String() string {
|
||||
return fmt.Sprintf(`Digest realm="%s", nonce="%s", algorithm=%s`, w.realm, w.nonce, w.algorithm)
|
||||
}
|
||||
|
||||
func (w *WwwAuthenticate) Parse(str string) error {
|
||||
arr := strings.Split(str, ",")
|
||||
for _, s := range arr {
|
||||
tmp := strings.Split(s, "=")
|
||||
if len(tmp) != 2 {
|
||||
continue
|
||||
}
|
||||
v := strings.ReplaceAll(tmp[1], "\"", "")
|
||||
if strings.Contains(tmp[0], "realm") {
|
||||
w.realm = v
|
||||
}
|
||||
if strings.Contains(tmp[0], "nonce") {
|
||||
w.nonce = v
|
||||
}
|
||||
if strings.Contains(tmp[0], "algorithm") {
|
||||
w.algorithm = v
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Authorization struct {
|
||||
username string
|
||||
realm string
|
||||
nonce string
|
||||
uri string
|
||||
response string
|
||||
algorithm string
|
||||
//opaque string // gb28181不需要这字段,海康有
|
||||
}
|
||||
|
||||
// Authorization: Digest username="admin", realm="hik", nonce="a8afe6fcbee6331d89d3eb0d3d19ce39", uri="sip:130909115229300920@10.64.49.44:7100", response="907ddb1bcc25174d7de4a96c947fb066", algorithm=MD5, opaque="a853e4f25298413f"
|
||||
func (a *Authorization) String() string {
|
||||
return fmt.Sprintf(`Digest username="%s", realm="%s", nonce="%s", uri="%s", response="%s", algorithm=%s`,
|
||||
a.username, a.realm, a.nonce, a.uri, a.response, a.algorithm)
|
||||
}
|
||||
|
||||
func (a *Authorization) GetUsername() string {
|
||||
return a.username
|
||||
}
|
||||
|
||||
func (a *Authorization) Parse(str string) error {
|
||||
arr := strings.Split(str, ",")
|
||||
for _, s := range arr {
|
||||
tmp := strings.Split(s, "=")
|
||||
if len(tmp) != 2 {
|
||||
continue
|
||||
}
|
||||
v := strings.ReplaceAll(tmp[1], "\"", "")
|
||||
if strings.Contains(tmp[0], "username") {
|
||||
a.username = v
|
||||
}
|
||||
if strings.Contains(tmp[0], "realm") {
|
||||
a.realm = v
|
||||
}
|
||||
if strings.Contains(tmp[0], "nonce") {
|
||||
a.nonce = v
|
||||
}
|
||||
if strings.Contains(tmp[0], "uri") {
|
||||
a.uri = v
|
||||
}
|
||||
if strings.Contains(tmp[0], "response") {
|
||||
a.response = v
|
||||
}
|
||||
if strings.Contains(tmp[0], "algorithm") {
|
||||
a.algorithm = strings.Trim(v,"H:")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Authorization) Verify(username, passwd, realm, nonce string) bool {
|
||||
|
||||
//1、将 username,realm,password 依次组合获取 1 个字符串,并用算法加密的到密文 r1
|
||||
s1 := fmt.Sprintf("%s:%s:%s", username, realm, passwd)
|
||||
r1 := a.getDigest(s1)
|
||||
//2、将 method,即REGISTER ,uri 依次组合获取 1 个字符串,并对这个字符串使用算法 加密得到密文 r2
|
||||
s2 := fmt.Sprintf("REGISTER:%s", a.uri)
|
||||
r2 := a.getDigest(s2)
|
||||
|
||||
if r1 == "" || r2 == "" {
|
||||
fmt.Println("Authorization algorithm wrong")
|
||||
return false
|
||||
}
|
||||
//3、将密文 1,nonce 和密文 2 依次组合获取 1 个字符串,并对这个字符串使用算法加密,获得密文 r3,即Response
|
||||
s3 := fmt.Sprintf("%s:%s:%s", r1, nonce, r2)
|
||||
r3 := a.getDigest(s3)
|
||||
|
||||
//4、计算服务端和客户端上报的是否相等
|
||||
if r3 == a.response {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *Authorization) getDigest(raw string) string {
|
||||
switch a.algorithm {
|
||||
case DIGEST_ALGO_MD5:
|
||||
return fmt.Sprintf("%x", md5.Sum([]byte(raw)))
|
||||
default: //如果没有算法,默认使用MD5
|
||||
return fmt.Sprintf("%x", md5.Sum([]byte(raw)))
|
||||
}
|
||||
}
|
||||
171
sip/head_test.go
171
sip/head_test.go
@@ -1,171 +0,0 @@
|
||||
package sip
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestContact(t *testing.T) {
|
||||
str1 := "\"Mr.Watson\" <sip:watson@worcester.bell-telephone.com>;q=0.7; expires=3600,\"Mr.Watson\" <mailto:watson@bell-telephone.com>"
|
||||
//str1 := `"Mr.Watson" <sip:watson@worcester.bell-telephone.com>;q=0.7;`
|
||||
c := &Contact{}
|
||||
err := c.Parse(str1)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
fmt.Println("source:", str1)
|
||||
fmt.Println("result:", c.String())
|
||||
}
|
||||
|
||||
func TestVia(t *testing.T) {
|
||||
str1 := "SIP / 2.0 / UDP first.example.com: 4000;ttl=16 ;maddr=224.2.0.1 ;branch=z9hG4bKa7c6a8dlze.1"
|
||||
str2 := "SIP/2.0/UDP 192.168.1.64:5060;rport;received=192.168.1.64;branch=z9hG4bK1000615294"
|
||||
|
||||
var err error
|
||||
v1 := &Via{}
|
||||
err = v1.Parse(str1)
|
||||
if err != nil {
|
||||
fmt.Println("error:", err.Error())
|
||||
return
|
||||
}
|
||||
fmt.Printf("source:%v\n", str1)
|
||||
fmt.Printf("result:%v\n", v1.String())
|
||||
|
||||
v2 := &Via{}
|
||||
err = v2.Parse(str2)
|
||||
if err != nil {
|
||||
fmt.Println("error:", err.Error())
|
||||
return
|
||||
}
|
||||
fmt.Printf("source:%v\n", str2)
|
||||
fmt.Printf("result:%v\n", v2.String())
|
||||
|
||||
}
|
||||
|
||||
func TestMessage1(t *testing.T) {
|
||||
str1 := `REGISTER sip:34020000002000000001@3402000000 SIP/2.0
|
||||
Via: SIP/2.0/UDP 192.168.1.64:5060;rport;branch=z9hG4bK385701375
|
||||
From: <sip:34020000001320000001@3402000000>;tag=1840661473
|
||||
To: <sip:34020000001320000001@3402000000>
|
||||
Call-ID: 418133739
|
||||
CSeq: 1 REGISTER
|
||||
Contact: <sip:34020000001320000001@192.168.1.64:5060>
|
||||
Max-Forwards: 70
|
||||
User-Agent: IP Camera
|
||||
Expires: 3600
|
||||
Content-Length: 0`
|
||||
|
||||
fmt.Println("input:")
|
||||
fmt.Println(str1)
|
||||
msg, err := Decode([]byte(str1))
|
||||
if err != nil {
|
||||
fmt.Println("decode message failed:", err.Error())
|
||||
return
|
||||
}
|
||||
out, err := Encode(msg)
|
||||
if err != nil {
|
||||
fmt.Println("encode message failed:", err.Error())
|
||||
return
|
||||
}
|
||||
fmt.Println("=====================================")
|
||||
fmt.Println("output:")
|
||||
fmt.Println(string(out))
|
||||
}
|
||||
|
||||
func TestMessage2(t *testing.T) {
|
||||
str1 := `SIP/2.0 200 OK
|
||||
Via: SIP/2.0/UDP 192.168.1.151:5060;rport=5060;branch=SrsGbB56116414
|
||||
From: <sip:34020000002000000001@3402000000>;tag=SrsGbF72006729
|
||||
To: <sip:34020000001320000001@3402000000>;tag=416442565
|
||||
Call-ID: 202093500940
|
||||
CSeq: 101 INVITE
|
||||
Contact: <sip:34020000001320000001@192.168.1.64:5060>
|
||||
Content-Type: application/sdp
|
||||
User-Agent: IP Camera
|
||||
Content-Length: 185
|
||||
|
||||
v=0
|
||||
o=34020000001320000001 1835 1835 IN IP4 192.168.1.64
|
||||
s=Play
|
||||
c=IN IP4 192.168.1.64
|
||||
t=0 0
|
||||
m=video 15060 RTP/AVP 96
|
||||
a=sendonly
|
||||
a=rtpmap:96 PS/90000
|
||||
a=filesize:0
|
||||
y=0009093131`
|
||||
|
||||
fmt.Println("input:")
|
||||
fmt.Println(str1)
|
||||
msg, err := Decode([]byte(str1))
|
||||
if err != nil {
|
||||
fmt.Println("decode message failed:", err.Error())
|
||||
return
|
||||
}
|
||||
out, err := Encode(msg)
|
||||
if err != nil {
|
||||
fmt.Println("encode message failed:", err.Error())
|
||||
return
|
||||
}
|
||||
fmt.Println("=====================================")
|
||||
fmt.Println("output:")
|
||||
fmt.Println(string(out))
|
||||
}
|
||||
|
||||
func TestAuthorization_Verify(t *testing.T) {
|
||||
type fields struct {
|
||||
username string
|
||||
realm string
|
||||
nonce string
|
||||
uri string
|
||||
response string
|
||||
algorithm string
|
||||
}
|
||||
type args struct {
|
||||
username string
|
||||
passwd string
|
||||
realm string
|
||||
nonce string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "test1",
|
||||
fields: fields{
|
||||
username: "34020000001320000001",
|
||||
realm: "3402000000",
|
||||
nonce: "1628819207",
|
||||
uri: "sip:34020000002000000001@172.165.0.10:15060",
|
||||
response: "fa2b30e05ea42dd0ab69ef05d3a06096",
|
||||
algorithm: "MD5",
|
||||
},
|
||||
args: args{
|
||||
username: "34020000001320000001",
|
||||
passwd: "12345678",
|
||||
realm: "3402000000",
|
||||
nonce: "1628819207",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := &Authorization{
|
||||
username: tt.fields.username,
|
||||
realm: tt.fields.realm,
|
||||
nonce: tt.fields.nonce,
|
||||
uri: tt.fields.uri,
|
||||
response: tt.fields.response,
|
||||
algorithm: tt.fields.algorithm,
|
||||
}
|
||||
if got := a.Verify(tt.args.username, tt.args.passwd, tt.args.realm, tt.args.nonce); got != tt.want {
|
||||
t.Errorf("Verify() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
499
sip/message.go
499
sip/message.go
@@ -1,499 +0,0 @@
|
||||
package sip
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Monibuca/plugin-gb28181/v3/utils"
|
||||
)
|
||||
|
||||
//Content-Type: Application/MANSCDP+xml
|
||||
//Content-Type: Application/SDP
|
||||
//Call-ID: 202081530679
|
||||
//Max-Forwards: 70
|
||||
//User-Agent: SRS/4.0.32(Leo)
|
||||
//Subject: 34020000001320000001:0009093128,34020000002000000001:0
|
||||
//Content-Length: 164
|
||||
|
||||
type Message struct {
|
||||
Mode Mode //0:REQUEST, 1:RESPONSE
|
||||
|
||||
StartLine *StartLine
|
||||
Via *Via //Via
|
||||
From *Contact //From
|
||||
To *Contact //To
|
||||
CallID string //Call-ID
|
||||
CSeq *CSeq //CSeq
|
||||
Contact *Contact //Contact
|
||||
Authorization *Authorization //Authorization
|
||||
MaxForwards int //Max-Forwards
|
||||
UserAgent string //User-Agent
|
||||
Subject string //Subject
|
||||
ContentType string //Content-Type
|
||||
Expires int //Expires
|
||||
ContentLength int //Content-Length
|
||||
Route *Contact
|
||||
Body string
|
||||
Addr string
|
||||
Event string
|
||||
Date time.Time
|
||||
WwwAuthenticate *WwwAuthenticate //gb28181 密码验证 上级发给下级是WwwAuthenticate;下级发给上级是Authorization
|
||||
SourceAdd net.Addr
|
||||
DestAdd net.Addr
|
||||
}
|
||||
|
||||
func (m *Message) BuildResponse(code int) *Message {
|
||||
return m.BuildResponseWithPhrase(code, "")
|
||||
}
|
||||
func (m *Message) BuildOK() *Message {
|
||||
return m.BuildResponseWithPhrase(200, "OK")
|
||||
}
|
||||
func (m *Message) BuildResponseWithPhrase(code int, phrase string) *Message {
|
||||
response := Message{
|
||||
Mode: SIP_MESSAGE_RESPONSE,
|
||||
From: m.From,
|
||||
To: m.To,
|
||||
CallID: m.CallID,
|
||||
CSeq: m.CSeq,
|
||||
Via: m.Via,
|
||||
MaxForwards: m.MaxForwards,
|
||||
UserAgent: "Monibuca",
|
||||
StartLine: &StartLine{
|
||||
Code: code,
|
||||
phrase: phrase,
|
||||
},
|
||||
Date: time.Now(),
|
||||
DestAdd: m.SourceAdd,
|
||||
}
|
||||
return &response
|
||||
}
|
||||
|
||||
//z9hG4bK + 10个随机数字
|
||||
func RandBranch() string {
|
||||
return fmt.Sprintf("z9hG4bK%s", utils.RandNumString(8))
|
||||
}
|
||||
|
||||
func BuildMessageRequest(method Method, transport, sipSerial, sipRealm, username, srcIP string, srcPort uint16, expires, cseq int, body string) *Message {
|
||||
server := fmt.Sprintf("%s@%s", sipSerial, sipRealm)
|
||||
client := fmt.Sprintf("%s@%s", username, sipRealm)
|
||||
|
||||
msg := &Message{
|
||||
Mode: SIP_MESSAGE_REQUEST,
|
||||
MaxForwards: 70,
|
||||
UserAgent: "IPC",
|
||||
Expires: expires,
|
||||
ContentLength: 0,
|
||||
}
|
||||
msg.StartLine = &StartLine{
|
||||
Method: method,
|
||||
Uri: NewURI(server),
|
||||
}
|
||||
msg.Via = &Via{
|
||||
Transport: transport,
|
||||
Host: client,
|
||||
}
|
||||
msg.Via.Params = map[string]string{
|
||||
"branch": RandBranch(),
|
||||
"rport": "-1", //only key,no-value
|
||||
}
|
||||
msg.From = &Contact{
|
||||
Uri: NewURI(client),
|
||||
Params: nil,
|
||||
}
|
||||
msg.From.Params = map[string]string{
|
||||
"tag": utils.RandNumString(10),
|
||||
}
|
||||
msg.To = &Contact{
|
||||
Uri: NewURI(client),
|
||||
}
|
||||
msg.CallID = utils.RandNumString(8)
|
||||
msg.CSeq = &CSeq{
|
||||
ID: uint32(cseq),
|
||||
Method: method,
|
||||
}
|
||||
|
||||
msg.Contact = &Contact{
|
||||
Uri: NewURI(fmt.Sprintf("%s@%s:%d", username, srcIP, srcPort)),
|
||||
}
|
||||
if len(body) > 0 {
|
||||
msg.ContentLength = len(body)
|
||||
msg.Body = body
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
func (m *Message) GetMode() Mode {
|
||||
return m.Mode
|
||||
}
|
||||
|
||||
func (m *Message) IsRequest() bool {
|
||||
return m.Mode == SIP_MESSAGE_REQUEST
|
||||
}
|
||||
|
||||
func (m *Message) IsResponse() bool {
|
||||
return m.Mode == SIP_MESSAGE_RESPONSE
|
||||
}
|
||||
|
||||
func (m *Message) GetMethod() Method {
|
||||
if m.CSeq == nil {
|
||||
return MESSAGE
|
||||
}
|
||||
return m.CSeq.Method
|
||||
}
|
||||
|
||||
//此消息是否使用可靠传输
|
||||
func (m *Message) IsReliable() bool {
|
||||
protocol := strings.ToUpper(m.Via.Transport)
|
||||
return "TCP" == protocol || "TLS" == protocol || "SCTP" == protocol
|
||||
}
|
||||
|
||||
//response code
|
||||
func (m *Message) GetStatusCode() int {
|
||||
return m.StartLine.Code
|
||||
}
|
||||
|
||||
//response code and reason
|
||||
func (m *Message) GetReason() string {
|
||||
return DumpError(m.StartLine.Code)
|
||||
}
|
||||
|
||||
func (m *Message) GetBranch() string {
|
||||
if m.Via == nil {
|
||||
panic("invalid via")
|
||||
}
|
||||
if m.Via.Params == nil {
|
||||
panic("invalid via params")
|
||||
}
|
||||
|
||||
b, ok := m.Via.Params["branch"]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
//构建响应消息的时候,会使用请求消息的 source 和 destination
|
||||
//请求消息的source,格式: host:port
|
||||
func (m *Message) Source() string {
|
||||
if m.Mode == SIP_MESSAGE_RESPONSE {
|
||||
//fmt.Println("only for request message")
|
||||
return ""
|
||||
}
|
||||
|
||||
if m.Via == nil {
|
||||
//fmt.Println("invalid request message")
|
||||
return ""
|
||||
}
|
||||
|
||||
var (
|
||||
host, port string
|
||||
via = m.Via
|
||||
)
|
||||
|
||||
if received, ok := via.Params["received"]; ok && received != "" {
|
||||
host = received
|
||||
} else {
|
||||
host = via.Host
|
||||
}
|
||||
|
||||
if rport, ok := via.Params["rport"]; ok && rport != "-1" && rport != "0" && rport != "" {
|
||||
port = rport
|
||||
} else if via.Port != "" {
|
||||
port = via.Port
|
||||
} else {
|
||||
//如果port为空,则上层构建消息的时候,根据sip服务的默认端口来选择
|
||||
}
|
||||
return fmt.Sprintf("%v:%v", host, port)
|
||||
}
|
||||
|
||||
//目标地址:这个应该是用于通过route头域实现proxy这样的功能,暂时不支持
|
||||
func (m *Message) Destination() string {
|
||||
//TODO:
|
||||
return ""
|
||||
}
|
||||
|
||||
//=======================================================================================================
|
||||
|
||||
func Decode(data []byte) (msg *Message, err error) {
|
||||
msg = &Message{}
|
||||
|
||||
content := string(data)
|
||||
content = strings.Trim(content, CRLFCRLF)
|
||||
msgArr := strings.Split(content, CRLFCRLF)
|
||||
//第一部分:header
|
||||
//第二部分:body
|
||||
if len(msgArr) == 0 {
|
||||
fmt.Println("invalid sip message:", data)
|
||||
err = errors.New("invalid sip message")
|
||||
return
|
||||
}
|
||||
|
||||
headStr := strings.TrimSpace(msgArr[0])
|
||||
if msgArrLen := len(msgArr); msgArrLen > 1 {
|
||||
for i := 1; i < msgArrLen; i++ {
|
||||
msg.Body += strings.TrimSpace(msgArr[i])
|
||||
}
|
||||
}
|
||||
|
||||
headStr = strings.Trim(headStr, CRLF)
|
||||
headArr := strings.Split(headStr, CRLF)
|
||||
for i, line := range headArr {
|
||||
//fmt.Printf("%02d --- %s ---- %d\n", i, line, len(line))
|
||||
if i == 0 {
|
||||
firstline := strings.Trim(line, " ")
|
||||
tmp := strings.Split(firstline, " ")
|
||||
//if len(tmp) != 3 {
|
||||
// fmt.Println("parse first line failed:", firstline)
|
||||
// err = errors.New("invalid first line")
|
||||
// return
|
||||
//}
|
||||
|
||||
if strings.HasPrefix(firstline, VERSION) {
|
||||
//status line
|
||||
//SIP/2.0 200 OK
|
||||
var num int64
|
||||
num, err = strconv.ParseInt(tmp[1], 10, 64)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
msg.Mode = SIP_MESSAGE_RESPONSE
|
||||
msg.StartLine = &StartLine{
|
||||
raw: firstline,
|
||||
Version: VERSION,
|
||||
Code: int(num),
|
||||
phrase: strings.Join(tmp[2:], " "),
|
||||
}
|
||||
} else {
|
||||
//request line
|
||||
//REGISTER sip:34020000002000000001@3402000000 SIP/2.0
|
||||
//MESSAGE sip:34020000002000000001@3402000000 SIP/2.0
|
||||
msg.Mode = SIP_MESSAGE_REQUEST
|
||||
msg.StartLine = &StartLine{
|
||||
raw: firstline,
|
||||
Method: Method(tmp[0]),
|
||||
Version: VERSION,
|
||||
}
|
||||
if len(tmp) > 1 {
|
||||
msg.StartLine.Uri, err = parseURI(tmp[1])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
//fmt.Println(firstline)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
pos := strings.IndexByte(line, ':')
|
||||
if pos == -1 {
|
||||
continue
|
||||
}
|
||||
k := strings.ToLower(strings.TrimSpace(line[:pos]))
|
||||
v := strings.TrimSpace(line[pos+1:])
|
||||
//fmt.Printf("%02d ---k = %s , v = %s\n", i, k, v)
|
||||
|
||||
if len(v) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
switch k {
|
||||
case "via":
|
||||
//Via: SIP/2.0/UDP 192.168.1.64:5060;rport;branch=z9hG4bK385701375
|
||||
msg.Via = &Via{}
|
||||
err = msg.Via.Parse(v)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
case "from":
|
||||
msg.From = &Contact{}
|
||||
err = msg.From.Parse(v)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
case "to":
|
||||
msg.To = &Contact{}
|
||||
err = msg.To.Parse(v)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
case "call-id":
|
||||
msg.CallID = v
|
||||
|
||||
case "cseq":
|
||||
//CSeq: 2 REGISTER
|
||||
msg.CSeq = &CSeq{}
|
||||
err = msg.CSeq.Parse(v)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
case "contact":
|
||||
msg.Contact = &Contact{}
|
||||
err = msg.Contact.Parse(v)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
case "max-forwards":
|
||||
n, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil {
|
||||
fmt.Printf("parse head faield: %s,%s\n", k, v)
|
||||
return nil, err
|
||||
}
|
||||
msg.MaxForwards = int(n)
|
||||
|
||||
case "user-agent":
|
||||
msg.UserAgent = v
|
||||
|
||||
case "expires":
|
||||
n, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil {
|
||||
fmt.Printf("parse head faield: %s,%s\n", k, v)
|
||||
return nil, err
|
||||
}
|
||||
msg.Expires = int(n)
|
||||
|
||||
case "content-length":
|
||||
n, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil {
|
||||
fmt.Printf("parse head faield: %s,%s\n", k, v)
|
||||
return nil, err
|
||||
}
|
||||
msg.ContentLength = int(n)
|
||||
|
||||
case "authorization":
|
||||
msg.Authorization = &Authorization{}
|
||||
msg.Authorization.Parse(v)
|
||||
|
||||
case "content-type":
|
||||
msg.ContentType = v
|
||||
case "route":
|
||||
//msg.Route = new(Contact)
|
||||
//msg.Route.Parse(v)
|
||||
case "www-authenticate":
|
||||
msg.WwwAuthenticate = &WwwAuthenticate{}
|
||||
msg.WwwAuthenticate.Parse(v)
|
||||
case "event":
|
||||
msg.Event = v
|
||||
default:
|
||||
fmt.Printf("invalid sip head: %s,%s\n", k, v)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Encode(msg *Message) ([]byte, error) {
|
||||
sb := strings.Builder{}
|
||||
sb.WriteString(msg.StartLine.String())
|
||||
sb.WriteString(CRLF)
|
||||
|
||||
if msg.Via != nil {
|
||||
sb.WriteString("Via: ")
|
||||
sb.WriteString(msg.Via.String())
|
||||
sb.WriteString(CRLF)
|
||||
}
|
||||
|
||||
if msg.From != nil {
|
||||
sb.WriteString("From: ")
|
||||
sb.WriteString(msg.From.String())
|
||||
sb.WriteString(CRLF)
|
||||
}
|
||||
|
||||
if msg.To != nil {
|
||||
sb.WriteString("To: ")
|
||||
sb.WriteString(msg.To.String())
|
||||
sb.WriteString(CRLF)
|
||||
}
|
||||
|
||||
if msg.CallID != "" {
|
||||
sb.WriteString("Call-ID: ")
|
||||
sb.WriteString(msg.CallID)
|
||||
sb.WriteString(CRLF)
|
||||
}
|
||||
if msg.CSeq != nil {
|
||||
sb.WriteString("CSeq: ")
|
||||
sb.WriteString(msg.CSeq.String())
|
||||
sb.WriteString(CRLF)
|
||||
}
|
||||
|
||||
if msg.Contact != nil {
|
||||
sb.WriteString("Contact: ")
|
||||
sb.WriteString(msg.Contact.String())
|
||||
sb.WriteString(CRLF)
|
||||
}
|
||||
|
||||
if msg.UserAgent != "" {
|
||||
sb.WriteString("User-Agent: ")
|
||||
sb.WriteString(msg.UserAgent)
|
||||
sb.WriteString(CRLF)
|
||||
}
|
||||
|
||||
if msg.ContentType != "" {
|
||||
sb.WriteString("Content-Type: ")
|
||||
sb.WriteString(msg.ContentType)
|
||||
sb.WriteString(CRLF)
|
||||
}
|
||||
|
||||
if msg.Expires != 0 {
|
||||
sb.WriteString("Expires: ")
|
||||
sb.WriteString(strconv.Itoa(msg.Expires))
|
||||
sb.WriteString(CRLF)
|
||||
}
|
||||
|
||||
if msg.Subject != "" {
|
||||
sb.WriteString("Subject: ")
|
||||
sb.WriteString(msg.Subject)
|
||||
sb.WriteString(CRLF)
|
||||
}
|
||||
|
||||
if msg.WwwAuthenticate != nil {
|
||||
sb.WriteString("WWW-Authenticate: ")
|
||||
sb.WriteString(msg.WwwAuthenticate.String())
|
||||
sb.WriteString(CRLF)
|
||||
}
|
||||
if !msg.Date.IsZero() {
|
||||
sb.WriteString("Date: ")
|
||||
sb.WriteString(msg.Date.Format("2006-01-02T15:04:05.999"))
|
||||
sb.WriteString(CRLF)
|
||||
}
|
||||
if msg.Event != "" {
|
||||
sb.WriteString("Event: ")
|
||||
sb.WriteString(msg.Event)
|
||||
sb.WriteString(CRLF)
|
||||
}
|
||||
if msg.IsRequest() {
|
||||
//request only
|
||||
|
||||
sb.WriteString("Max-Forwards: ")
|
||||
sb.WriteString(strconv.Itoa(msg.MaxForwards))
|
||||
sb.WriteString(CRLF)
|
||||
|
||||
if msg.Authorization != nil {
|
||||
sb.WriteString("Authorization: ")
|
||||
sb.WriteString(msg.Authorization.String())
|
||||
sb.WriteString(CRLF)
|
||||
}
|
||||
} else {
|
||||
//response only
|
||||
}
|
||||
|
||||
sb.WriteString("Content-Length: ")
|
||||
sb.WriteString(strconv.Itoa(len(msg.Body)))
|
||||
|
||||
sb.WriteString(CRLFCRLF)
|
||||
|
||||
if msg.Body != "" {
|
||||
sb.WriteString(msg.Body)
|
||||
}
|
||||
|
||||
return []byte(sb.String()), nil
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package sip
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Response Response
|
||||
type Response struct {
|
||||
*Message
|
||||
}
|
||||
// AlarmResponseXML alarm response xml样式
|
||||
var (AlarmResponseXML = `<?xml version="1.0"?>
|
||||
<Response>
|
||||
<CmdType>Alarm</CmdType>
|
||||
<SN>17430</SN>
|
||||
<DeviceID>%s</DeviceID>
|
||||
</Response>
|
||||
`
|
||||
)
|
||||
|
||||
// BuildRecordInfoXML 获取录像文件列表指令
|
||||
func BuildAlarmResponseXML(id string) string {
|
||||
return fmt.Sprintf(AlarmResponseXML, id)
|
||||
}
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
package sip
|
||||
|
||||
import "fmt"
|
||||
|
||||
//transaction sip error
|
||||
var errorMap = map[int]string{
|
||||
//1xx
|
||||
100: "Trying",
|
||||
180: "Ringing",
|
||||
181: "Call Is Being Forwarded",
|
||||
182: "Queued",
|
||||
183: "Session Progress",
|
||||
199: "Early Dialog Terminated",
|
||||
//2xx
|
||||
200: "OK",
|
||||
202: "Accepted",
|
||||
204: "No Notification",
|
||||
//3xx
|
||||
300: "Multiple Choices",
|
||||
301: "Moved Permanently",
|
||||
302: "Moved Temporarily",
|
||||
305: "Use Proxy",
|
||||
380: "Alternative Service",
|
||||
//4xx
|
||||
400: "Bad Request",
|
||||
401: "Unauthorized",
|
||||
402: "Payment Required",
|
||||
403: "Forbidden",
|
||||
404: "Not Found",
|
||||
405: "Method Not Allowed",
|
||||
406: "Not Acceptable",
|
||||
407: "Proxy Authentication Required",
|
||||
408: "Request Timeout",
|
||||
409: "Conflict",
|
||||
410: "Gone",
|
||||
411: "Length Required",
|
||||
412: "Conditional Request Failed",
|
||||
413: "Request Entity Too Large",
|
||||
414: "Request-URI Too Long",
|
||||
415: "Unsupported Media Type",
|
||||
416: "Unsupported URI Scheme",
|
||||
417: "Unknown Resource-Priority",
|
||||
420: "Bad Extension",
|
||||
421: "Extension Required",
|
||||
422: "Session Interval Too Small",
|
||||
423: "Interval Too Brief",
|
||||
424: "Bad Location Information",
|
||||
428: "Use Identity Header",
|
||||
429: "Provide Referrer Identity",
|
||||
430: "Flow Failed",
|
||||
433: "Anonymity Disallowed",
|
||||
436: "Bad Identity Info",
|
||||
437: "Unsupported Credential",
|
||||
438: "Invalid Identity Header",
|
||||
439: "First Hop Lacks Outbound Support",
|
||||
440: "Max-Breadth Exceeded",
|
||||
469: "Bad Info Package",
|
||||
470: "Consent Needed",
|
||||
480: "Temporarily Unavailable",
|
||||
481: "Call/Transaction Does Not Exist",
|
||||
482: "Loop Detected",
|
||||
483: "Too Many Hops",
|
||||
484: "Address Incomplete",
|
||||
485: "Ambiguous",
|
||||
486: "Busy Here",
|
||||
487: "Request Terminated",
|
||||
488: "Not Acceptable Here",
|
||||
489: "Bad Event",
|
||||
491: "Request Pending",
|
||||
493: "Undecipherable",
|
||||
494: "Security Agreement Required",
|
||||
//5xx
|
||||
500: "Server Internal Error",
|
||||
501: "Not Implemented",
|
||||
502: "Bad Gateway",
|
||||
503: "Service Unavailable",
|
||||
504: "Server Time-out",
|
||||
505: "Version Not Supported",
|
||||
513: "Message Too Large",
|
||||
580: "Precondition Failure",
|
||||
//6xx
|
||||
600: "Busy Everywhere",
|
||||
603: "Decline",
|
||||
604: "Does Not Exist Anywhere",
|
||||
606: "Not Acceptable",
|
||||
607: "Unwanted",
|
||||
687: "Dialog Terminated",
|
||||
}
|
||||
|
||||
func DumpError(code int) string {
|
||||
if code == 0 {
|
||||
return "invalid status reason for request"
|
||||
}
|
||||
return fmt.Sprintf("%d %s", code, errorMap[code])
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
#### 事务
|
||||
transaction事务是SIP的基本组成部分。
|
||||
一个事务是客户发送的一个请求事务(通过通讯层)发送到一个服务器事务,连同服务器事务的所有的该请求的应答发送回客户端事务。
|
||||
事务层处理应用服务层的重发,匹配请求的应答,以及应用服务层的超时。
|
||||
任何一个用户代理客户端(user agent client UAC)完成的事情都是由一组事务构成的。
|
||||
用户代理包含一个事务层,来实现有状态的代理服务器。
|
||||
事务层包含一个客户元素(可以认为是一个客户事务)和一个服务器元素(可以认为是一个服务器事务),他们都可以用一个有限状态机来处理特定的请求。
|
||||
|
||||
在状态机层面,事务分为ict、ist、nict、nist四种。
|
||||
但底层事务方面,仅根据 method 等信息,分支处理。
|
||||
|
||||
#### 关于事务管理
|
||||
|
||||
事务在map里面管理,事务ID的选择是要和事务匹配相关。
|
||||
|
||||
```
|
||||
客户端事件的匹配
|
||||
|
||||
当客户端中的传输层收到响应时,它必须确定哪个客户端事务将处理该响应,以便可以进行17.1.1和17.1.2节的处理。头域 Via 字段中的branch参数用于匹配规则。 一个匹配的响应应该满足下面两个条件:
|
||||
1.如果响应的头域 Via头字段中的branch参数值与创建事务的请求的头域 Via头字段中的branch参数值相同。
|
||||
2.如果CSeq标头字段中的method参数与创建事务的请求的方法匹配。(由于CANCEL请求会创建新的事务,但共享相同的branch参数值。所以仅用branch参数是不够的)
|
||||
|
||||
|
||||
服务端事务匹配
|
||||
|
||||
首先要检查请求中的Via头域的branch参数。如果他以”z9hG4bk”开头,那么这个请求一定是由客户端事务根据本规范产生的。因此,branch参数在该客户端发出的所有的事务中都是唯一的。根据下列规则我们可以判定请求是否和事务匹配:
|
||||
|
||||
1、 请求的Via头域的branch参数和创建本事务的请求的Via头域的branch参数一样,并且:
|
||||
2、 请求的Via头域的send-by参数和创建本事务的请求的Via头域的send-by参数一样(可能存在来自不同客户端的branch参数的意外或恶意重复,所以将 send-by 值用作匹配的一部分),并且:
|
||||
3、 请求的方法与创建事务的方法匹配,但ACK除外,在ACK中,创建事务的请求的方法为INVITE。
|
||||
```
|
||||
|
||||
所以,根据匹配规则,事务的ID 使用 branch,然后在匹配逻辑里面,再做条件判断。而因为branch可能会重复,所以如果使用map来简化transaction的管理,key的取值应该:
|
||||
|type| key|
|
||||
|----|----|
|
||||
|客户端事务:| branch+method|
|
||||
|服务端事务:| branch + sendby + method,method中ack还要除外。所以只能用branch + sendby|
|
||||
@@ -1,65 +0,0 @@
|
||||
package transaction
|
||||
|
||||
//SIP服务器静态配置信息
|
||||
|
||||
/*
|
||||
# sip监听udp端口
|
||||
listen 5060;
|
||||
|
||||
# SIP server ID(SIP服务器ID).
|
||||
# 设备端配置编号需要与该值一致,否则无法注册
|
||||
serial 34020000002000000001;
|
||||
|
||||
# SIP server domain(SIP服务器域)
|
||||
realm 3402000000;
|
||||
|
||||
# 服务端发送ack后,接收回应的超时时间,单位为秒
|
||||
# 如果指定时间没有回应,认为失败
|
||||
ack_timeout 30;
|
||||
|
||||
# 设备心跳维持时间,如果指定时间内(秒)没有接收一个心跳
|
||||
# 认为设备离线
|
||||
keepalive_timeout 120;
|
||||
|
||||
# 注册之后是否自动给设备端发送invite
|
||||
# on: 是 off 不是,需要通过api控制
|
||||
auto_play on;
|
||||
# 设备将流发送的端口,是否固定
|
||||
# on 发送流到多路复用端口 如9000
|
||||
# off 自动从rtp_mix_port - rtp_max_port 之间的值中
|
||||
# 选一个可以用的端口
|
||||
invite_port_fixed on;
|
||||
|
||||
# 向设备或下级域查询设备列表的间隔,单位(秒)
|
||||
# 默认60秒
|
||||
query_catalog_interval 60;
|
||||
*/
|
||||
|
||||
type Config struct {
|
||||
//sip服务器的配置
|
||||
SipNetwork string //传输协议,默认UDP,可选TCP
|
||||
SipIP string //sip 服务器公网IP
|
||||
SipPort uint16 //sip 服务器端口,默认 5060
|
||||
Serial string //sip 服务器 id, 默认 34020000002000000001
|
||||
Realm string //sip 服务器域,默认 3402000000
|
||||
Username string //sip 服务器账号
|
||||
Password string //sip 服务器密码
|
||||
|
||||
AckTimeout uint16 //sip 服务应答超时,单位秒
|
||||
RegisterValidity int //注册有效期,单位秒,默认 3600
|
||||
RegisterInterval int //注册间隔,单位秒,默认 60
|
||||
HeartbeatInterval int //心跳间隔,单位秒,默认 60
|
||||
HeartbeatRetry int //心跳超时次数,默认 3
|
||||
|
||||
//媒体服务器配置
|
||||
MediaIP string //媒体服务器地址
|
||||
MediaPort uint16 //媒体服务器端口
|
||||
MediaPortMin uint16
|
||||
MediaPortMax uint16
|
||||
MediaIdleTimeout uint16 //推流超时时间,超过则断开链接,让设备重连
|
||||
AudioEnable bool //是否开启音频
|
||||
LogVerbose bool
|
||||
WaitKeyFrame bool //是否等待关键帧,如果等待,则在收到第一个关键帧之前,忽略所有媒体流
|
||||
RemoveBanInterval int //移除禁止设备间隔
|
||||
UdpCacheSize int //udp缓存大小
|
||||
}
|
||||
@@ -1,250 +0,0 @@
|
||||
package transaction
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
. "github.com/Monibuca/plugin-gb28181/v3/sip"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/transport"
|
||||
. "github.com/Monibuca/utils/v3"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type RequestHandler func(req *Request, tx *GBTx)
|
||||
|
||||
//Core: transactions manager
|
||||
//管理所有 transactions,以及相关全局参数、运行状态机
|
||||
type Core struct {
|
||||
ctx context.Context //上下文
|
||||
hmu *sync.RWMutex
|
||||
requestHandlers map[Method]RequestHandler
|
||||
txs *GBTxs
|
||||
tp transport.ITransport //transport
|
||||
*Config //sip server配置信息
|
||||
OnRegister func(msg *Request, tx *GBTx)
|
||||
OnMessage func(msg *Request, tx *GBTx)
|
||||
udpaddr net.Addr
|
||||
}
|
||||
|
||||
//初始化一个 Core,需要能响应请求,也要能发起请求
|
||||
//client 发起请求
|
||||
//server 响应请求
|
||||
//TODO:根据角色,增加相关配置信息
|
||||
//TODO:通过context管理子线程
|
||||
//TODO:单元测试
|
||||
func NewCore(config *Config) *Core {
|
||||
ActiveTX = &GBTxs{
|
||||
Txs: map[string]*GBTx{},
|
||||
RWM: &sync.RWMutex{},
|
||||
}
|
||||
|
||||
core := &Core{
|
||||
requestHandlers: map[Method]RequestHandler{},
|
||||
txs: ActiveTX,
|
||||
Config: config,
|
||||
ctx: context.Background(),
|
||||
hmu: &sync.RWMutex{},
|
||||
}
|
||||
|
||||
if config.SipNetwork == "TCP" {
|
||||
core.tp = transport.NewTCPServer(config.SipPort, true)
|
||||
} else {
|
||||
core.tp = transport.NewUDPServer(config.SipPort)
|
||||
}
|
||||
|
||||
return core
|
||||
}
|
||||
|
||||
func (c *Core) StartAndWait() {
|
||||
go c.handlerListen()
|
||||
|
||||
_ = c.tp.StartAndWait()
|
||||
}
|
||||
|
||||
func (c *Core) handlerListen() {
|
||||
//阻塞读取消息
|
||||
for p := range c.tp.ReadPacketChan() {
|
||||
if len(p.Data) < 5 {
|
||||
continue
|
||||
}
|
||||
if c.LogVerbose {
|
||||
Println("Received: \n", string(p.Data))
|
||||
}
|
||||
if err := c.HandleReceiveMessage(p); err != nil {
|
||||
fmt.Println("handler sip response message failed:", err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//接收到的消息处理
|
||||
//收到消息有两种:1、请求消息 2、响应消息
|
||||
//请求消息则直接响应处理。
|
||||
//响应消息则需要匹配到请求,让请求的transaction来处理。
|
||||
//TODO:参考srs和osip的流程,以及文档,做最终处理。需要将逻辑分成两层:TU 层和 transaction 层
|
||||
func (c *Core) HandleReceiveMessage(p *transport.Packet) (err error) {
|
||||
//Println("packet content:", string(p.Data))
|
||||
//var msg *Message
|
||||
msg, err := Decode(p.Data)
|
||||
if err != nil {
|
||||
fmt.Println("parse sip message failed:", err.Error())
|
||||
return ErrorParse
|
||||
}
|
||||
if msg.Via == nil || msg.From == nil {
|
||||
return ErrorParse
|
||||
}
|
||||
//这里不处理超过MTU的包,不处理半包
|
||||
err = checkMessage(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if msg.IsRequest() {
|
||||
req := &Request{Message: msg}
|
||||
req.SourceAdd = p.Addr
|
||||
req.DestAdd = c.udpaddr
|
||||
c.handlerRequest(req)
|
||||
} else {
|
||||
//TODO:对于uac,收到response消息,是否要检查 rport 和 received 呢?因为uas可能对此做了修改
|
||||
resp := &Response{Message: msg}
|
||||
resp.SourceAdd = p.Addr
|
||||
resp.DestAdd = c.udpaddr
|
||||
c.handlerResponse(resp)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Core) NewTX(key string) *GBTx {
|
||||
tx := c.txs.NewTX(key, *c.tp.Conn())
|
||||
tx.Core = c
|
||||
return tx
|
||||
}
|
||||
func (c *Core) GetTX(key string) *GBTx {
|
||||
return c.txs.GetTX(key)
|
||||
}
|
||||
func (c *Core) MustTX(key string) *GBTx {
|
||||
tx := c.txs.GetTX(key)
|
||||
if tx == nil {
|
||||
tx = c.NewTX(key)
|
||||
}
|
||||
return tx
|
||||
}
|
||||
|
||||
func (c *Core) handlerRequest(msg *Request) {
|
||||
tx := c.MustTX(GetTXKey(msg.Message))
|
||||
|
||||
//Println("receive request from:", msg.Source(), ",method:", msg.GetMethod(), "txKey:", tx.Key(), "message: \n", string(encode))
|
||||
c.hmu.RLock()
|
||||
handler, ok := c.requestHandlers[msg.GetMethod()]
|
||||
c.hmu.RUnlock()
|
||||
if !ok {
|
||||
encode, _ := Encode(msg.Message)
|
||||
Println("not found handler func,requestMethod:", msg.GetMethod(), msg.Event, encode)
|
||||
go handlerMethodNotAllowed(msg, tx)
|
||||
return
|
||||
}
|
||||
|
||||
go handler(msg, tx)
|
||||
}
|
||||
|
||||
func (c *Core) handlerResponse(msg *Response) {
|
||||
tx := c.GetTX(GetTXKey(msg.Message))
|
||||
|
||||
if tx == nil {
|
||||
str, _ := Encode(msg.Message)
|
||||
Println("not found tx. receive response from:", msg.Source(), "message: \n", string(str))
|
||||
} else {
|
||||
tx.ReceiveResponse(msg)
|
||||
}
|
||||
}
|
||||
func handlerMethodNotAllowed(req *Request, tx *GBTx) {
|
||||
var resp Response
|
||||
resp.Message = req.BuildResponse(http.StatusMethodNotAllowed)
|
||||
resp.DestAdd = req.SourceAdd
|
||||
resp.SourceAdd = req.DestAdd
|
||||
_ = tx.Respond(&resp)
|
||||
}
|
||||
func (c *Core) SipRequestForResponse(req *Request) (response *Response, err error) {
|
||||
var tx *GBTx
|
||||
tx, err = c.Request(req)
|
||||
if err == nil {
|
||||
return tx.SipResponse()
|
||||
}
|
||||
return
|
||||
}
|
||||
func (c *Core) ResolveAddress(msg *Message) (destAddr net.Addr, err error) {
|
||||
addr := msg.Addr
|
||||
|
||||
if addr == "" {
|
||||
viaParams := msg.Via.Params
|
||||
var host, port string
|
||||
var ok1, ok2 bool
|
||||
if host, ok1 = viaParams["maddr"]; !ok1 {
|
||||
if host, ok2 = viaParams["received"]; !ok2 {
|
||||
host = msg.Via.Host
|
||||
}
|
||||
}
|
||||
//port
|
||||
port = viaParams["rport"]
|
||||
if port == "" || port == "0" || port == "-1" {
|
||||
port = msg.Via.Port
|
||||
}
|
||||
|
||||
if port == "" {
|
||||
port = "5060"
|
||||
}
|
||||
|
||||
addr = fmt.Sprintf("%s:%s", host, port)
|
||||
}
|
||||
|
||||
// fmt.Println("dest addr:", addr)
|
||||
var err1, err2 error
|
||||
|
||||
if msg.Via.Transport == "UDP" {
|
||||
destAddr, err2 = net.ResolveUDPAddr("udp", addr)
|
||||
} else {
|
||||
destAddr, err2 = net.ResolveTCPAddr("tcp", addr)
|
||||
}
|
||||
|
||||
if err1 != nil {
|
||||
return nil, err1
|
||||
}
|
||||
|
||||
if err2 != nil {
|
||||
return nil, err2
|
||||
}
|
||||
|
||||
return destAddr, nil
|
||||
}
|
||||
|
||||
// Request Request
|
||||
func (c *Core) Request(req *Request) (*GBTx, error) {
|
||||
if req.Via == nil {
|
||||
var viaHop Via
|
||||
viaHop.Host = c.SipIP
|
||||
viaHop.Port = strconv.Itoa(int(c.SipPort))
|
||||
viaHop.Params = make(map[string]string)
|
||||
viaHop.Params["branch"] = RandBranch()
|
||||
viaHop.Params["rport"] = ""
|
||||
req.Via = &viaHop
|
||||
}
|
||||
tx := c.MustTX(GetTXKey(req.Message))
|
||||
return tx, tx.Request(req)
|
||||
}
|
||||
|
||||
// Request Request
|
||||
func (c *Core) Respond(resp *Response) (*GBTx, error) {
|
||||
|
||||
tx := c.MustTX(GetTXKey(resp.Message))
|
||||
return tx, tx.Respond(resp)
|
||||
}
|
||||
|
||||
// RegistHandler RegistHandler
|
||||
func (c *Core) RegistHandler(method Method, handler RequestHandler) {
|
||||
c.hmu.Lock()
|
||||
c.requestHandlers[method] = handler
|
||||
c.hmu.Unlock()
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package transaction
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
//transaction 的错误定义
|
||||
var (
|
||||
ErrorSyntax = errors.New("message syntax error")
|
||||
ErrorCheck = errors.New("message check failed")
|
||||
ErrorParse = errors.New("message parse failed")
|
||||
ErrorUnknown = errors.New("message unknown")
|
||||
)
|
||||
@@ -1,27 +0,0 @@
|
||||
package transaction
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestTimeout(t *testing.T) {
|
||||
// Pass a context with a timeout to tell a blocking function that it
|
||||
// should abandon its work after the timeout elapses.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
||||
defer cancel()
|
||||
go func() {
|
||||
select {
|
||||
case <-time.After(1 * time.Second):
|
||||
fmt.Println("overslept")
|
||||
case <-ctx.Done():
|
||||
fmt.Println(ctx.Err()) // prints "context deadline exceeded"
|
||||
default:
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
fmt.Println("fuck gc.")
|
||||
}
|
||||
}()
|
||||
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
package transaction
|
||||
|
||||
import (
|
||||
"github.com/Monibuca/plugin-gb28181/v3/sip"
|
||||
. "github.com/Monibuca/plugin-gb28181/v3/transport"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/utils"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ActiveTX *GBTxs
|
||||
|
||||
// GBTxs a GBTxs stands for a Gb28181 Transaction collection
|
||||
type GBTxs struct {
|
||||
Txs map[string]*GBTx
|
||||
RWM *sync.RWMutex
|
||||
}
|
||||
|
||||
func (txs *GBTxs) NewTX(key string, conn Connection) *GBTx {
|
||||
tx := NewTransaction(key, conn)
|
||||
txs.RWM.Lock()
|
||||
txs.Txs[key] = tx
|
||||
txs.RWM.Unlock()
|
||||
return tx
|
||||
}
|
||||
|
||||
func (txs *GBTxs) GetTX(key string) *GBTx {
|
||||
txs.RWM.RLock()
|
||||
tx, ok := txs.Txs[key]
|
||||
if !ok {
|
||||
tx = nil
|
||||
}
|
||||
txs.RWM.RUnlock()
|
||||
return tx
|
||||
}
|
||||
|
||||
func (txs *GBTxs) rmTX(tx *GBTx) {
|
||||
txs.RWM.Lock()
|
||||
delete(txs.Txs, tx.key)
|
||||
txs.RWM.Unlock()
|
||||
}
|
||||
|
||||
// GBTx Gb28181 Transaction
|
||||
type GBTx struct {
|
||||
conn Connection
|
||||
key string
|
||||
resp chan *sip.Response
|
||||
active chan int
|
||||
*Core
|
||||
}
|
||||
|
||||
// NewTransaction create a new GBtx
|
||||
func NewTransaction(key string, conn Connection) *GBTx {
|
||||
tx := &GBTx{conn: conn, key: key, resp: make(chan *sip.Response, 10), active: make(chan int, 1)}
|
||||
go tx.watch()
|
||||
return tx
|
||||
}
|
||||
|
||||
// Key returns the GBTx Key
|
||||
func (tx *GBTx) Key() string {
|
||||
return tx.key
|
||||
}
|
||||
|
||||
func (tx *GBTx) watch() {
|
||||
for {
|
||||
select {
|
||||
case <-tx.active:
|
||||
//Println("active tx", tx.Key(), time.Now().Format("2006-01-02 15:04:05"))
|
||||
case <-time.After(20 * time.Second):
|
||||
tx.Close()
|
||||
//Println("watch closed tx", tx.key, time.Now().Format("2006-01-02 15:04:05"))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetResponse GetResponse
|
||||
func (tx *GBTx) GetResponse() *sip.Response {
|
||||
for {
|
||||
res := <-tx.resp
|
||||
if res == nil {
|
||||
return res
|
||||
}
|
||||
tx.active <- 2
|
||||
//Println("response tx", tx.key, time.Now().Format("2006-01-02 15:04:05"))
|
||||
if res.GetStatusCode() == http.StatusContinue || res.GetStatusCode() == http.StatusSwitchingProtocols {
|
||||
// Trying and Dialog Establishement 等待下一个返回
|
||||
continue
|
||||
}
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
// Close the Close function closes the GBTx
|
||||
func (tx *GBTx) Close() {
|
||||
//Printf("closed tx: %s %s TXs: %d", tx.key, time.Now().Format("2006-01-02 15:04:05"), len(ActiveTX.Txs))
|
||||
ActiveTX.rmTX(tx)
|
||||
close(tx.resp)
|
||||
close(tx.active)
|
||||
}
|
||||
|
||||
// ReceiveResponse receive a Response
|
||||
func (tx *GBTx) ReceiveResponse(msg *sip.Response) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
//Println("send to closed channel, txkey:", tx.key, "message: \n", msg)
|
||||
}
|
||||
}()
|
||||
//Println("receiveResponse tx", tx.Key(), time.Now().Format("2006-01-02 15:04:05"))
|
||||
tx.resp <- msg
|
||||
tx.active <- 1
|
||||
}
|
||||
|
||||
// Respond Respond
|
||||
func (tx *GBTx) Respond(res *sip.Response) error {
|
||||
str, _ := sip.Encode(res.Message)
|
||||
//Println("send response,to:", (res.DestAdd).String(), "txkey:", tx.key, "message: \n", string(str))
|
||||
_, err := tx.conn.WriteTo(str, res.DestAdd)
|
||||
return err
|
||||
}
|
||||
|
||||
// Request Request
|
||||
func (tx *GBTx) Request(req *sip.Request) error {
|
||||
str, _ := sip.Encode(req.Message)
|
||||
//Println("send Request,to:", (req.DestAdd).String(), "txkey:", tx.key, "message: \n", string(str))
|
||||
_, err := tx.conn.WriteTo(str, req.DestAdd)
|
||||
return err
|
||||
}
|
||||
|
||||
func GetTXKey(msg *sip.Message) (key string) {
|
||||
if len(msg.CallID) > 0 {
|
||||
key = msg.CallID
|
||||
} else {
|
||||
key = utils.RandString(10)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (tx *GBTx) SipResponse() (*sip.Response, error) {
|
||||
response := tx.GetResponse()
|
||||
if response == nil {
|
||||
return nil, utils.NewError(nil, "response timeout", "tx key:", tx.Key())
|
||||
}
|
||||
if response.GetStatusCode() != http.StatusOK {
|
||||
return response, utils.NewError(nil, "response fail", response.GetStatusCode(), response.GetReason(), "tx key:", tx.Key())
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (tx *GBTx) SipRequestForResponse(req *sip.Request) (response *sip.Response, err error) {
|
||||
err = tx.Request(req)
|
||||
if err == nil {
|
||||
return tx.SipResponse()
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package transaction
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/sip"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//=====================================================sip message utils
|
||||
//The branch ID parameter in the Via header field values serves as a transaction identifier,
|
||||
//and is used by proxies to detect loops.
|
||||
//The branch parameter in the topmost Via header field of the request
|
||||
// is examined. If it is present and begins with the magic cookie
|
||||
// "z9hG4bK", the request was generated by a client transaction
|
||||
// compliant to this specification.
|
||||
//参考RFC3261
|
||||
func getMessageTransactionID(m *sip.Message) string {
|
||||
if m.GetMethod() == sip.ACK {
|
||||
//TODO:在匹配服务端事务的ACK中,创建事务的请求的方法为INVITE。所以ACK消息匹配事务的时候需要注意????
|
||||
}
|
||||
return string(m.GetMethod()) + "_" + m.GetBranch()
|
||||
}
|
||||
|
||||
func checkMessage(msg *sip.Message) error {
|
||||
//TODO:sip消息解析成功之后,检查必要元素,如果失败,则返回 ErrorCheckMessage
|
||||
|
||||
//检查头域字段:callID via startline 等
|
||||
//检查seq、method等
|
||||
//不可以有router?
|
||||
//是否根据消息是接收还是发送检查?
|
||||
if msg == nil {
|
||||
return ErrorCheck
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//fix via header,add send-by info,
|
||||
func fixReceiveMessageViaParams(msg *sip.Message, addr net.Addr) {
|
||||
rport := msg.Via.Params["rport"]
|
||||
if rport == "" || rport == "0" || rport == "-1" {
|
||||
arr := strings.Split(addr.String(), ":")
|
||||
if len(arr) == 2 {
|
||||
msg.Via.Params["rport"] = arr[1]
|
||||
if msg.Via.Host != arr[0] {
|
||||
msg.Via.Params["received"] = arr[0]
|
||||
}
|
||||
} else {
|
||||
//TODO:数据报的地址错误??
|
||||
fmt.Println("packet handle > invalid addr :", addr.String())
|
||||
}
|
||||
} else {
|
||||
fmt.Println("sip message has have send-by info:", msg.Via.GetSendBy())
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
|
||||
#### 介绍
|
||||
|
||||
transport 包括客户端和服务器端,仅实现tcp和udp的传输层,不负责具体消息的解析和处理。不负责粘包、半包、消息解析、心跳处理、状态管理等工作。
|
||||
|
||||
比如设备关闭或者离线,要修改缓存状态、数据库状态、发送离线通知、执行离线回调等等,都在上层处理。tcp server 和 udp server、tcp client 和 udp client , 消息的接收和发送都在外面处理。
|
||||
|
||||
tcp是流传输,需要注意粘包和半包的处理。在上层处理tcp包的时候,可以尝试使用 ring buffer
|
||||
|
||||
|
||||
#### usage
|
||||
|
||||
参考 example.go
|
||||
|
||||
#### TODO
|
||||
|
||||
- sip协议的传输层,TCP和UDP有所不同,比如重传以及超时的错误信息等。所以需要在 transaction上面,处理消息重传、错误上报等。具体参考RFC3261。
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
. "github.com/Monibuca/utils/v3"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Connection Wrapper around net.Conn.
|
||||
type Connection interface {
|
||||
net.Conn
|
||||
Network() string
|
||||
// String() string
|
||||
ReadFrom(buf []byte) (num int, raddr net.Addr, err error)
|
||||
WriteTo(buf []byte, raddr net.Addr) (num int, err error)
|
||||
}
|
||||
|
||||
// Connection implementation.
|
||||
type connection struct {
|
||||
baseConn net.Conn
|
||||
laddr net.Addr
|
||||
raddr net.Addr
|
||||
mu sync.RWMutex
|
||||
logKey string
|
||||
Online bool
|
||||
ReconnectCount int64 //重连次数
|
||||
}
|
||||
|
||||
func newUDPConnection(baseConn net.Conn) Connection {
|
||||
conn := &connection{
|
||||
baseConn: baseConn,
|
||||
laddr: baseConn.LocalAddr(),
|
||||
raddr: baseConn.RemoteAddr(),
|
||||
logKey: "udpConnection",
|
||||
}
|
||||
return conn
|
||||
}
|
||||
func newTCPConnection(baseConn net.Conn) Connection {
|
||||
conn := &connection{
|
||||
baseConn: baseConn,
|
||||
laddr: baseConn.LocalAddr(),
|
||||
raddr: baseConn.RemoteAddr(),
|
||||
logKey: "udpConnection",
|
||||
}
|
||||
return conn
|
||||
}
|
||||
|
||||
func (conn *connection) Read(buf []byte) (int, error) {
|
||||
var (
|
||||
num int
|
||||
err error
|
||||
)
|
||||
|
||||
num, err = conn.baseConn.Read(buf)
|
||||
|
||||
return num, err
|
||||
}
|
||||
|
||||
func (conn *connection) ReadFrom(buf []byte) (num int, raddr net.Addr, err error) {
|
||||
num, raddr, err = conn.baseConn.(net.PacketConn).ReadFrom(buf)
|
||||
if err != nil {
|
||||
return num, raddr, err
|
||||
}
|
||||
Printf("readFrom %d , %s -> %s \n %s", num, raddr, conn.LocalAddr(), string(buf[:num]))
|
||||
return num, raddr, err
|
||||
}
|
||||
|
||||
func (conn *connection) Write(buf []byte) (int, error) {
|
||||
var (
|
||||
num int
|
||||
err error
|
||||
)
|
||||
num, err = conn.baseConn.Write(buf)
|
||||
return num, err
|
||||
}
|
||||
|
||||
func (conn *connection) WriteTo(buf []byte, raddr net.Addr) (num int, err error) {
|
||||
num, err = conn.baseConn.(net.PacketConn).WriteTo(buf, raddr)
|
||||
if err != nil {
|
||||
return num, err
|
||||
}
|
||||
//Printf("writeTo %d , %s -> %s \n %s", num, conn.baseConn.LocalAddr(), raddr.String(), string(buf[:num]))
|
||||
return num, err
|
||||
}
|
||||
|
||||
func (conn *connection) LocalAddr() net.Addr {
|
||||
return conn.baseConn.LocalAddr()
|
||||
}
|
||||
|
||||
func (conn *connection) RemoteAddr() net.Addr {
|
||||
return conn.baseConn.RemoteAddr()
|
||||
}
|
||||
|
||||
func (conn *connection) Close() error {
|
||||
err := conn.baseConn.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
func (conn *connection) Network() string {
|
||||
return strings.ToUpper(conn.baseConn.LocalAddr().Network())
|
||||
}
|
||||
|
||||
func (conn *connection) SetDeadline(t time.Time) error {
|
||||
return conn.baseConn.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (conn *connection) SetReadDeadline(t time.Time) error {
|
||||
return conn.baseConn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (conn *connection) SetWriteDeadline(t time.Time) error {
|
||||
return conn.baseConn.SetWriteDeadline(t)
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/utils"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
//默认端口:TCP/UDP是5060,5061是在TCP上的TLS
|
||||
//对于服务器监听UDP的任何端口和界面,都必须在TCP上也进行同样的监听。这是因为可能消息还需要通过TCP进行传输,比如消息过大的情况。
|
||||
const SipHost string = "127.0.0.1"
|
||||
const SipPort uint16 = 5060
|
||||
|
||||
func RunServerTCP() {
|
||||
tcp := NewTCPServer(SipPort, true)
|
||||
go PacketHandler(tcp)
|
||||
go func() {
|
||||
_ = tcp.StartAndWait()
|
||||
}()
|
||||
|
||||
select {}
|
||||
}
|
||||
|
||||
//测试通讯,客户端先发一条消息
|
||||
func RunClientTCP() {
|
||||
c := NewTCPClient(SipHost, SipPort)
|
||||
go PacketHandler(c)
|
||||
go func() {
|
||||
_ = c.StartAndWait()
|
||||
}()
|
||||
|
||||
//发送测试数据
|
||||
fmt.Println("send test data")
|
||||
go func() {
|
||||
for {
|
||||
c.WritePacket(&Packet{Data: []byte("from client : " + time.Now().String())})
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}()
|
||||
select {}
|
||||
}
|
||||
func PacketHandler(s ITransport) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
fmt.Println("packet handler panic: ", err)
|
||||
utils.PrintStack()
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
fmt.Println("PacketHandler ========== ", s.Name())
|
||||
|
||||
ch := s.ReadPacketChan()
|
||||
//阻塞读取消息
|
||||
for {
|
||||
select {
|
||||
case p := <-ch:
|
||||
fmt.Println("packet content:", string(p.Data))
|
||||
//TODO:message parse
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
|
||||
func RunServerUDP() {
|
||||
udp := NewUDPServer(SipPort)
|
||||
|
||||
go PacketHandler(udp)
|
||||
go func() {
|
||||
_ = udp.StartAndWait()
|
||||
}()
|
||||
|
||||
select {}
|
||||
}
|
||||
|
||||
func RunClientUDP() {
|
||||
c := NewUDPClient(SipHost, SipPort)
|
||||
go PacketHandler(c)
|
||||
go func() {
|
||||
_ = c.StartAndWait()
|
||||
}()
|
||||
//发送测试数据
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(1 * time.Second)
|
||||
c.WritePacket(&Packet{
|
||||
Data: []byte("hello " + time.Now().String())})
|
||||
}
|
||||
}()
|
||||
|
||||
select {}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
type TCPClient struct {
|
||||
Statistic
|
||||
host string
|
||||
port uint16
|
||||
conn Connection
|
||||
readChan chan *Packet
|
||||
writeChan chan *Packet
|
||||
remoteAddr net.Addr
|
||||
localAddr net.Addr
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func NewTCPClient(host string, port uint16) IClient {
|
||||
return &TCPClient{
|
||||
host: host,
|
||||
port: port,
|
||||
readChan: make(chan *Packet, 10),
|
||||
writeChan: make(chan *Packet, 10),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *TCPClient) IsReliable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *TCPClient) Name() string {
|
||||
return fmt.Sprintf("tcp client to:%s", fmt.Sprintf("%s:%d", c.host, c.port))
|
||||
}
|
||||
|
||||
func (c *TCPClient) LocalAddr() net.Addr {
|
||||
return c.localAddr
|
||||
}
|
||||
|
||||
func (c *TCPClient) RemoteAddr() net.Addr {
|
||||
return c.remoteAddr
|
||||
}
|
||||
|
||||
func (c *TCPClient) StartAndWait() error {
|
||||
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", c.host, c.port))
|
||||
if err != nil {
|
||||
fmt.Println("dial tcp server failed :", err.Error())
|
||||
return err
|
||||
} else {
|
||||
fmt.Println("start tcp client")
|
||||
}
|
||||
|
||||
c.conn = newTCPConnection(conn)
|
||||
c.remoteAddr = conn.RemoteAddr()
|
||||
c.localAddr = conn.LocalAddr()
|
||||
|
||||
//开启写线程
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case p := <-c.writeChan:
|
||||
_, err := c.conn.Write(p.Data)
|
||||
if err != nil {
|
||||
fmt.Println("client write failed:", err.Error())
|
||||
_ = c.Close()
|
||||
return
|
||||
}
|
||||
case <-c.done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
fmt.Println("start tcp client")
|
||||
fmt.Printf("remote addr: %s, local addr: %s\n", conn.RemoteAddr().String(), conn.LocalAddr().String())
|
||||
|
||||
//读线程,阻塞
|
||||
for {
|
||||
buf := make([]byte, 2048)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
fmt.Println("tcp client read error:", err.Error())
|
||||
return err
|
||||
}
|
||||
c.readChan <- &Packet{
|
||||
Addr: c.remoteAddr,
|
||||
Data: buf[:n],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *TCPClient) ReadPacketChan() <-chan *Packet {
|
||||
return c.readChan
|
||||
}
|
||||
|
||||
func (c *TCPClient) WritePacket(packet *Packet) {
|
||||
c.writeChan <- packet
|
||||
}
|
||||
|
||||
func (c *TCPClient) Close() error {
|
||||
close(c.done)
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
//外部定期调用此接口,实现心跳
|
||||
func (c *TCPClient) Heartbeat(p *Packet) {
|
||||
if p == nil {
|
||||
p = &Packet{
|
||||
Data: []byte("ping"),
|
||||
}
|
||||
}
|
||||
c.WritePacket(p)
|
||||
}
|
||||
|
||||
func (s *TCPClient) Conn() *Connection {
|
||||
return &s.conn
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TCPServer struct {
|
||||
Statistic
|
||||
addr string
|
||||
listener net.Listener
|
||||
readChan chan *Packet
|
||||
writeChan chan *Packet
|
||||
done chan struct{}
|
||||
Keepalive bool
|
||||
sessions sync.Map //key 是 remote-addr , value:*Connection。
|
||||
}
|
||||
|
||||
func NewTCPServer(port uint16, keepalive bool) IServer {
|
||||
tcpAddr := fmt.Sprintf(":%d", port)
|
||||
|
||||
return &TCPServer{
|
||||
addr: tcpAddr,
|
||||
Keepalive: keepalive,
|
||||
readChan: make(chan *Packet, 10),
|
||||
writeChan: make(chan *Packet, 10),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TCPServer) IsReliable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *TCPServer) Name() string {
|
||||
return fmt.Sprintf("tcp server at:%s", s.addr)
|
||||
}
|
||||
func (s *TCPServer) IsKeepalive() bool {
|
||||
return s.Keepalive
|
||||
}
|
||||
|
||||
func (s *TCPServer) StartAndWait() error {
|
||||
//监听端口
|
||||
//开启tcp连接线程
|
||||
var err error
|
||||
s.listener, err = net.Listen("tcp", s.addr)
|
||||
//s.listener, err = tls.Listen("tcp", s.tcpAddr, tlsConfig)
|
||||
if err != nil {
|
||||
fmt.Println("TCP Listen failed:", err)
|
||||
return err
|
||||
}
|
||||
defer s.listener.Close()
|
||||
|
||||
fmt.Println("start tcp server at: ", s.addr)
|
||||
|
||||
//心跳线程
|
||||
if s.Keepalive {
|
||||
//TODO:start heartbeat thread
|
||||
}
|
||||
//写线程
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case p := <-s.writeChan:
|
||||
val, ok := s.sessions.Load(p.Addr.String())
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
c := *val.(*Connection)
|
||||
_, _ = c.Write(p.Data)
|
||||
case <-s.done:
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
}()
|
||||
|
||||
//读线程
|
||||
for {
|
||||
conn, err := s.listener.Accept()
|
||||
if err != nil {
|
||||
var tempDelay time.Duration // how long to sleep on accept failure
|
||||
fmt.Println("accept err :", err.Error())
|
||||
// 重连。参考http server
|
||||
if ne, ok := err.(net.Error); ok && ne.Temporary() {
|
||||
if tempDelay == 0 {
|
||||
tempDelay = 5 * time.Millisecond
|
||||
} else {
|
||||
tempDelay *= 2
|
||||
}
|
||||
|
||||
if max := 1 * time.Second; tempDelay > max {
|
||||
tempDelay = max
|
||||
}
|
||||
|
||||
time.Sleep(tempDelay)
|
||||
continue
|
||||
}
|
||||
fmt.Println("accept error, retry failed & exit.")
|
||||
return err
|
||||
}
|
||||
|
||||
// conn.SetReadDeadline(time.Now().Add(600 * time.Second))
|
||||
session := newTCPConnection(conn)
|
||||
address := session.RemoteAddr().String()
|
||||
s.sessions.Store(address, session)
|
||||
|
||||
fmt.Println(fmt.Sprintf("new tcp client remoteAddr: %v", address))
|
||||
go s.handlerSession(&session)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TCPServer) handlerSession(c *Connection) {
|
||||
conn := *c
|
||||
addrStr := conn.RemoteAddr().String()
|
||||
|
||||
//recovery from panic
|
||||
defer func() {
|
||||
s.CloseOne(addrStr)
|
||||
if err := recover(); err != nil {
|
||||
fmt.Println("client receiver handler panic: ", err)
|
||||
}
|
||||
}()
|
||||
|
||||
buf := make([]byte, 2048)
|
||||
for {
|
||||
n, err := conn.Read(buf)
|
||||
switch {
|
||||
case err == nil:
|
||||
p := &Packet{
|
||||
Addr: conn.RemoteAddr(),
|
||||
Data: buf[:n],
|
||||
}
|
||||
s.readChan <- p
|
||||
case err == io.EOF:
|
||||
fmt.Println(fmt.Sprintf("io.EOF,client close --- remoteAddr: %v", conn.RemoteAddr()))
|
||||
return
|
||||
case err != nil:
|
||||
fmt.Println("client other err: ", err)
|
||||
fmt.Println(fmt.Sprintf("client other err --- remoteAddr: %v", addrStr))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TCPServer) CloseOne(addr string) {
|
||||
val, ok := s.sessions.Load(addr)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
c := *val.(*Connection)
|
||||
_ = c.Close()
|
||||
s.sessions.Delete(addr)
|
||||
}
|
||||
|
||||
func (s *TCPServer) ReadPacketChan() <-chan *Packet {
|
||||
return s.readChan
|
||||
}
|
||||
func (s *TCPServer) WritePacket(packet *Packet) {
|
||||
s.writeChan <- packet
|
||||
}
|
||||
|
||||
func (s *TCPServer) Close() error {
|
||||
//TODO:TCP服务退出之前,需要先close掉所有客户端的连接
|
||||
s.sessions.Range(func(key, value interface{}) bool {
|
||||
c := *value.(*Connection)
|
||||
_ = c.Close()
|
||||
s.sessions.Delete(key)
|
||||
return true
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *TCPServer) Conn() *Connection {
|
||||
return nil
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
transport层仅实现数据的读写,连接的关闭
|
||||
|
||||
TCP和UDP的区别
|
||||
|
||||
- TCP面向链接,在服务关闭的时候,要先close掉所有客户端连接。所以使用一个map简单做session管理,key 是 remote address。
|
||||
- udp不需要管理session。
|
||||
*/
|
||||
|
||||
//transport server and client interface
|
||||
//对于面向连接的服务,需要有两个关闭接口:Close and CloseOne
|
||||
//非面向连接的服务,不必实现
|
||||
//TODO:心跳管理,使用timewheel
|
||||
|
||||
type ITransport interface {
|
||||
Name() string
|
||||
ReadPacketChan() <-chan *Packet //读消息,消息处理器需在循环中阻塞读取
|
||||
WritePacket(packet *Packet) //写消息
|
||||
StartAndWait() error //开启连接,阻塞接收消息
|
||||
Close() error //关闭连接
|
||||
IsReliable() bool //是否可靠传输
|
||||
Conn() *Connection
|
||||
}
|
||||
|
||||
type IServer interface {
|
||||
ITransport
|
||||
CloseOne(addr string) //对于关闭某个客户端连接,比如没有鉴权的非法链接,心跳超时等
|
||||
IsKeepalive() bool //persistent connection or not
|
||||
}
|
||||
|
||||
//transport 需要实现的接口如下
|
||||
type IClient interface {
|
||||
ITransport
|
||||
LocalAddr() net.Addr //本地地址
|
||||
RemoteAddr() net.Addr //远程地址
|
||||
Heartbeat(packet *Packet) //客户端需要定期发送心跳包到服务器端
|
||||
}
|
||||
|
||||
type Packet struct {
|
||||
Type string //消息类型,预留字段,对于客户端主动关闭等消息的上报、心跳超时等。如果为空,则仅透传消息。
|
||||
Addr net.Addr
|
||||
Data []byte
|
||||
}
|
||||
|
||||
//通讯统计
|
||||
type Statistic struct {
|
||||
startTime time.Time
|
||||
stopTime time.Time
|
||||
recvCount int64
|
||||
sendCount int64
|
||||
errCount int64
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
)
|
||||
|
||||
type UDPClient struct {
|
||||
Statistic
|
||||
host string
|
||||
port uint16
|
||||
conn Connection
|
||||
readChan chan *Packet
|
||||
writeChan chan *Packet
|
||||
done chan struct{}
|
||||
remoteAddr net.Addr
|
||||
localAddr net.Addr
|
||||
}
|
||||
|
||||
func NewUDPClient(host string, port uint16) IClient {
|
||||
return &UDPClient{
|
||||
host: host,
|
||||
port: port,
|
||||
readChan: make(chan *Packet, 10),
|
||||
writeChan: make(chan *Packet, 10),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *UDPClient) IsReliable() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *UDPClient) Name() string {
|
||||
return fmt.Sprintf("udp client to:%s", fmt.Sprintf("%s:%d", c.host, c.port))
|
||||
}
|
||||
func (c *UDPClient) LocalAddr() net.Addr {
|
||||
return c.localAddr
|
||||
}
|
||||
|
||||
func (c *UDPClient) RemoteAddr() net.Addr {
|
||||
return c.remoteAddr
|
||||
}
|
||||
|
||||
func (c *UDPClient) StartAndWait() error {
|
||||
addrStr := fmt.Sprintf("%s:%d", c.host, c.port)
|
||||
addr, err := net.ResolveUDPAddr("udp", addrStr)
|
||||
if err != nil {
|
||||
fmt.Println("Can't resolve address: ", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
conn, err := net.DialUDP("udp", nil, addr)
|
||||
if err != nil {
|
||||
fmt.Println("Can't dial: ", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
c.remoteAddr = conn.RemoteAddr()
|
||||
c.localAddr = conn.LocalAddr()
|
||||
|
||||
fmt.Println("udp client remote addr:", conn.RemoteAddr().String())
|
||||
fmt.Println("udp client local addr:", conn.LocalAddr().String())
|
||||
|
||||
//写线程
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case p := <-c.writeChan:
|
||||
_, err = conn.Write(p.Data)
|
||||
if err != nil {
|
||||
fmt.Println("udp client write failed:", err.Error())
|
||||
continue
|
||||
}
|
||||
case <-c.done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
buf := make([]byte, 4096)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
fmt.Println("failed to read UDP msg because of ", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
c.readChan <- &Packet{
|
||||
Addr: c.remoteAddr,
|
||||
Data: buf[:n],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *UDPClient) ReadPacketChan() <-chan *Packet {
|
||||
return c.readChan
|
||||
}
|
||||
|
||||
func (c *UDPClient) WritePacket(packet *Packet) {
|
||||
c.writeChan <- packet
|
||||
}
|
||||
|
||||
func (c *UDPClient) Close() error {
|
||||
close(c.done)
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
//外部定期调用此接口,实现心跳
|
||||
func (c *UDPClient) Heartbeat(p *Packet) {
|
||||
if p == nil {
|
||||
p = &Packet{
|
||||
Data: []byte("ping"),
|
||||
}
|
||||
}
|
||||
c.WritePacket(p)
|
||||
}
|
||||
|
||||
func (c *UDPClient) Conn() *Connection {
|
||||
return &c.conn
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
)
|
||||
|
||||
type UDPServer struct {
|
||||
Statistic
|
||||
addr string
|
||||
conn *Connection
|
||||
readChan chan *Packet
|
||||
writeChan chan *Packet
|
||||
done chan struct{}
|
||||
Keepalive bool
|
||||
//Sessions sync.Map // key is remote-addr的string , value:*Connection。UDP不需要
|
||||
}
|
||||
|
||||
func NewUDPServer(port uint16) IServer {
|
||||
addrStr := fmt.Sprintf(":%d", port)
|
||||
|
||||
return &UDPServer{
|
||||
addr: addrStr,
|
||||
readChan: make(chan *Packet, 1024),
|
||||
writeChan: make(chan *Packet, 1024),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *UDPServer) IsReliable() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *UDPServer) Name() string {
|
||||
return fmt.Sprintf("udp client to:%s", s.addr)
|
||||
}
|
||||
|
||||
func (s *UDPServer) IsKeepalive() bool {
|
||||
return s.Keepalive
|
||||
}
|
||||
|
||||
func (s *UDPServer) StartAndWait() error {
|
||||
addr, err := net.ResolveUDPAddr("udp", s.addr)
|
||||
if err != nil {
|
||||
fmt.Println("Can't resolve address: ", err)
|
||||
return err
|
||||
}
|
||||
|
||||
conn, err := net.ListenUDP("udp", addr)
|
||||
if err != nil {
|
||||
fmt.Println("Error listenUDP :", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer func() {
|
||||
_ = conn.Close()
|
||||
}()
|
||||
udpConnection := newUDPConnection(conn)
|
||||
s.conn = &udpConnection
|
||||
fmt.Println("start udp server at: ", s.addr)
|
||||
|
||||
//心跳线程
|
||||
if s.Keepalive {
|
||||
//TODO:start heartbeat thread
|
||||
}
|
||||
|
||||
//读线程
|
||||
for {
|
||||
data := make([]byte, 4096)
|
||||
n, remoteAddr, err := conn.ReadFromUDP(data)
|
||||
if err != nil {
|
||||
fmt.Println("failed to read UDP msg because of ", err.Error())
|
||||
continue
|
||||
}
|
||||
s.readChan <- &Packet{
|
||||
Addr: remoteAddr,
|
||||
Data: data[:n],
|
||||
}
|
||||
}
|
||||
}
|
||||
func (s *UDPServer) ReadPacketChan() <-chan *Packet {
|
||||
return s.readChan
|
||||
}
|
||||
func (s *UDPServer) WritePacket(packet *Packet) {
|
||||
s.writeChan <- packet
|
||||
}
|
||||
|
||||
func (s *UDPServer) Close() error {
|
||||
//所有session离线和关闭处理
|
||||
return nil
|
||||
}
|
||||
func (s *UDPServer) CloseOne(addr string) {
|
||||
//处理某设备离线
|
||||
}
|
||||
|
||||
func (s *UDPServer) Conn() *Connection {
|
||||
return s.conn
|
||||
}
|
||||
24
tu/README.md
24
tu/README.md
@@ -1,24 +0,0 @@
|
||||
|
||||
Transaction User(TU)事务用户:在transaction 层之上的协议层。TU包括了UAC core、UAS core,和proxy core。
|
||||
tu处理业务逻辑,并对事务层进行操作。
|
||||
|
||||
#### 类型
|
||||
|
||||
SIP服务器典型有以下几类:
|
||||
|
||||
a. 注册服务器 -即只管Register消息,这里相当于location也在这里了
|
||||
|
||||
b. 重定向服务器 -给ua回一条302后,转给其它的服务器,这样保证全系统统一接入
|
||||
|
||||
c. 代理服务器 -只做proxy,即对SIP消息转发
|
||||
|
||||
d. 媒体服务器-只做rtp包相关处理,即media server
|
||||
|
||||
e. B2BUA - 这个里包实际一般是可以含以上几种服务器类型
|
||||
|
||||
暂时仅处理gb28181 相关
|
||||
|
||||
#### TU
|
||||
|
||||
tu负责根据应用层需求,发起操作。
|
||||
比如注册到sip服务器、发起会话、取消会话等。
|
||||
95
tu/client.go
95
tu/client.go
@@ -1,95 +0,0 @@
|
||||
package tu
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/sip"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/transaction"
|
||||
)
|
||||
|
||||
//sip server和client的配置,可以得到sip URI:sip
|
||||
//格式:user:password@host:port;uri-parameters?headers
|
||||
//在这些URI里边包含了足够的信息来发起和维持到这个资源的一个通讯会话。
|
||||
//client静态配置
|
||||
type ClientStatic struct {
|
||||
LocalIP string //设备本地IP
|
||||
LocalPort uint16 //客户端SIP端口
|
||||
Username string //SIP用户名,一般是取通道ID,默认 34020000001320000001
|
||||
AuthID string //SIP用户认证ID,一般是通道ID, 默认 34020000001320000001
|
||||
Password string //密码
|
||||
}
|
||||
|
||||
//client运行时信息
|
||||
type ClientRuntime struct {
|
||||
RemoteAddress string //设备的公网的IP和端口,格式x.x.x.x:x
|
||||
Online bool //在线状态
|
||||
Branch string //branch
|
||||
Cseq int //消息序列号,发送消息递增, uint32
|
||||
FromTag string //from tag
|
||||
ToTag string //to tag
|
||||
Received string //remote ip
|
||||
Rport string //remote port
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
*transaction.Core //transaction manager
|
||||
static *ClientStatic //静态配置
|
||||
runtime *ClientRuntime //运行时信息
|
||||
}
|
||||
|
||||
//config:sip信令服务器配置
|
||||
//static:sip客户端配置
|
||||
func NewClient(config *transaction.Config, static *ClientStatic) *Client {
|
||||
return &Client{
|
||||
Core: transaction.NewCore(config),
|
||||
static: static,
|
||||
runtime: &ClientRuntime{},
|
||||
}
|
||||
}
|
||||
|
||||
//TODO:对于一个TU,开启之后
|
||||
//运行一个sip client
|
||||
func RunClient() {
|
||||
config := &transaction.Config{
|
||||
SipIP: "192.168.1.102",
|
||||
SipPort: 5060,
|
||||
SipNetwork: "UDP",
|
||||
Serial: "34020000002000000001",
|
||||
Realm: "3402000000",
|
||||
AckTimeout: 10,
|
||||
|
||||
RegisterValidity: 3600,
|
||||
RegisterInterval: 60,
|
||||
HeartbeatInterval: 60,
|
||||
HeartbeatRetry: 3,
|
||||
|
||||
AudioEnable: true,
|
||||
WaitKeyFrame: true,
|
||||
MediaPortMin: 58200,
|
||||
MediaPortMax: 58300,
|
||||
MediaIdleTimeout: 30,
|
||||
}
|
||||
static := &ClientStatic{
|
||||
LocalIP: "192.168.1.65",
|
||||
LocalPort: 5060,
|
||||
Username: "34020000001320000001",
|
||||
AuthID: "34020000001320000001",
|
||||
Password: "123456",
|
||||
}
|
||||
c := NewClient(config, static)
|
||||
|
||||
go c.StartAndWait()
|
||||
|
||||
//TODO:先发起注册
|
||||
//TODO:build sip message
|
||||
msg := BuildMessageRequest("", "", "", "", "", "",
|
||||
0, 0, 0, "")
|
||||
tx := c.MustTX(transaction.GetTXKey(msg))
|
||||
|
||||
resp, err := tx.SipRequestForResponse(&sip.Request{Message: msg})
|
||||
if err != nil {
|
||||
fmt.Println("request failed, ", err)
|
||||
}
|
||||
fmt.Println("response: ", resp.Body)
|
||||
|
||||
select {}
|
||||
}
|
||||
75
tu/common.go
75
tu/common.go
@@ -1,75 +0,0 @@
|
||||
package tu
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/sip"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/utils"
|
||||
)
|
||||
|
||||
//根据参数构建各种消息
|
||||
//参数来自于session/transaction等会话管理器
|
||||
/*
|
||||
method:请求方法
|
||||
transport:UDP/TCP
|
||||
sipSerial: sip server ID
|
||||
sipRealm: sip domain
|
||||
username: 用户名/设备序列号
|
||||
srcIP: 源IP
|
||||
srcPort:源端口
|
||||
expires: 过期时间
|
||||
cseq:消息序列号,当前对话递增
|
||||
*/
|
||||
//构建消息:以客户端(可能是IPC,也可能是SIP Server)的角度
|
||||
func BuildMessageRequest(method sip.Method, transport, sipSerial, sipRealm, username, srcIP string, srcPort uint16, expires, cseq int, body string) *sip.Message {
|
||||
server := fmt.Sprintf("%s@%s", sipSerial, sipRealm)
|
||||
client := fmt.Sprintf("%s@%s", username, sipRealm)
|
||||
|
||||
msg := &sip.Message{
|
||||
Mode: sip.SIP_MESSAGE_REQUEST,
|
||||
MaxForwards: 70,
|
||||
UserAgent: "IPC",
|
||||
Expires: expires,
|
||||
ContentLength: 0,
|
||||
}
|
||||
msg.StartLine = &sip.StartLine{
|
||||
Method: method,
|
||||
Uri: sip.NewURI(server),
|
||||
}
|
||||
msg.Via = &sip.Via{
|
||||
Transport: transport,
|
||||
Host: client,
|
||||
}
|
||||
msg.Via.Params = map[string]string{
|
||||
"branch": randBranch(),
|
||||
"rport": "-1", //only key,no-value
|
||||
}
|
||||
msg.From = &sip.Contact{
|
||||
Uri: sip.NewURI(client),
|
||||
Params: nil,
|
||||
}
|
||||
msg.From.Params = map[string]string{
|
||||
"tag": utils.RandNumString(10),
|
||||
}
|
||||
msg.To = &sip.Contact{
|
||||
Uri: sip.NewURI(client),
|
||||
}
|
||||
msg.CallID = utils.RandNumString(8)
|
||||
msg.CSeq = &sip.CSeq{
|
||||
ID: uint32(cseq),
|
||||
Method: method,
|
||||
}
|
||||
|
||||
msg.Contact = &sip.Contact{
|
||||
Uri: sip.NewURI(fmt.Sprintf("%s@%s:%d", username, srcIP, srcPort)),
|
||||
}
|
||||
if len(body) > 0 {
|
||||
msg.ContentLength = len(body)
|
||||
msg.Body = body
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
//z9hG4bK + 10个随机数字
|
||||
func randBranch() string {
|
||||
return fmt.Sprintf("z9hG4bK%s", utils.RandNumString(8))
|
||||
}
|
||||
49
tu/server.go
49
tu/server.go
@@ -1,49 +0,0 @@
|
||||
package tu
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/Monibuca/plugin-gb28181/v3/transaction"
|
||||
)
|
||||
|
||||
//TODO:参考http服务,使用者仅需要根据需要实现某些handler,替换某些header fileds or body信息。其他的处理都由库来实现。
|
||||
type Server struct {
|
||||
*transaction.Core //SIP transaction manager
|
||||
registers sync.Map //管理所有已经注册的设备端
|
||||
//routers:TODO:消息路由,应用层可以处理消息体,或者针对某些消息的callback
|
||||
}
|
||||
|
||||
//提供config参数
|
||||
func NewServer(config *transaction.Config) *Server {
|
||||
return &Server{
|
||||
Core: transaction.NewCore(config),
|
||||
}
|
||||
}
|
||||
|
||||
//运行一个sip server
|
||||
func RunServer() {
|
||||
config := &transaction.Config{
|
||||
SipIP: "192.168.1.102",
|
||||
SipPort: 5060,
|
||||
SipNetwork: "UDP",
|
||||
Serial: "34020000002000000001",
|
||||
Realm: "3402000000",
|
||||
AckTimeout: 10,
|
||||
|
||||
RegisterValidity: 3600,
|
||||
RegisterInterval: 60,
|
||||
HeartbeatInterval: 60,
|
||||
HeartbeatRetry: 3,
|
||||
|
||||
AudioEnable: true,
|
||||
WaitKeyFrame: true,
|
||||
MediaPortMin: 58200,
|
||||
MediaPortMax: 58300,
|
||||
MediaIdleTimeout: 30,
|
||||
}
|
||||
s := NewServer(config)
|
||||
|
||||
s.StartAndWait()
|
||||
|
||||
select {}
|
||||
}
|
||||
33
utils/ps.go
33
utils/ps.go
@@ -1,8 +1,10 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"github.com/Monibuca/utils/v3"
|
||||
"fmt"
|
||||
|
||||
"github.com/logrusorgru/aurora"
|
||||
)
|
||||
|
||||
@@ -157,7 +159,7 @@ again:
|
||||
return err
|
||||
}
|
||||
var video []byte
|
||||
var nextStartCode, videoTs, videoCts uint32
|
||||
var nextStartCode uint32
|
||||
loop:
|
||||
for err == nil {
|
||||
if nextStartCode, err = dec.Uint32(); err != nil {
|
||||
@@ -170,23 +172,18 @@ loop:
|
||||
case StartCodeMAP:
|
||||
err = dec.decProgramStreamMap()
|
||||
case StartCodeVideo:
|
||||
var cts uint32
|
||||
if err = dec.decPESPacket(); err == nil {
|
||||
if len(video) == 0 {
|
||||
if dec.PTS == 0 {
|
||||
dec.PTS = ts
|
||||
}
|
||||
if dec.DTS != 0 {
|
||||
cts = dec.PTS - dec.DTS
|
||||
} else {
|
||||
dec.DTS = dec.PTS
|
||||
}
|
||||
videoTs = dec.DTS / 90
|
||||
videoCts = cts / 90
|
||||
// if dec.DTS == 0 {
|
||||
// dec.DTS = dec.PTS
|
||||
// }
|
||||
}
|
||||
video = append (video,dec.Payload...)
|
||||
video = append(video, dec.Payload...)
|
||||
} else {
|
||||
utils.Println("video", err)
|
||||
fmt.Println("video", err)
|
||||
}
|
||||
case StartCodeAudio:
|
||||
if err = dec.decPESPacket(); err == nil {
|
||||
@@ -196,7 +193,7 @@ loop:
|
||||
}
|
||||
pusher.PushAudio(ts, dec.Payload)
|
||||
} else {
|
||||
utils.Println("audio", err)
|
||||
fmt.Println("audio", err)
|
||||
}
|
||||
case StartCodePS:
|
||||
break loop
|
||||
@@ -205,10 +202,10 @@ loop:
|
||||
}
|
||||
}
|
||||
if len(video) > 0 {
|
||||
pusher.PushVideo(videoTs, videoCts, video)
|
||||
pusher.PushVideo(dec.PTS, dec.DTS, video)
|
||||
}
|
||||
if nextStartCode == StartCodePS {
|
||||
utils.Println(aurora.Red("StartCodePS recursion..."), err)
|
||||
fmt.Println(aurora.Red("StartCodePS recursion..."), err)
|
||||
goto again
|
||||
}
|
||||
return err
|
||||
@@ -251,10 +248,10 @@ func (dec *DecPSPackage) decProgramStreamMap() error {
|
||||
}
|
||||
l := len(psm)
|
||||
index := 2
|
||||
programStreamInfoLen := utils.BigEndian.Uint16(psm[index:])
|
||||
programStreamInfoLen := binary.BigEndian.Uint16(psm[index:])
|
||||
index += 2
|
||||
index += int(programStreamInfoLen)
|
||||
programStreamMapLen := utils.BigEndian.Uint16(psm[index:])
|
||||
programStreamMapLen := binary.BigEndian.Uint16(psm[index:])
|
||||
index += 2
|
||||
for programStreamMapLen > 0 {
|
||||
if l <= index+1 {
|
||||
@@ -272,7 +269,7 @@ func (dec *DecPSPackage) decProgramStreamMap() error {
|
||||
if l <= index+1 {
|
||||
break
|
||||
}
|
||||
elementaryStreamInfoLength := utils.BigEndian.Uint16(psm[index:])
|
||||
elementaryStreamInfoLength := binary.BigEndian.Uint16(psm[index:])
|
||||
index += 2
|
||||
index += int(elementaryStreamInfoLength)
|
||||
programStreamMapLen -= 4 + elementaryStreamInfoLength
|
||||
|
||||
@@ -3,7 +3,8 @@ package utils
|
||||
import (
|
||||
"container/heap"
|
||||
"errors"
|
||||
"github.com/pion/rtp"
|
||||
|
||||
"github.com/pion/rtp/v2"
|
||||
)
|
||||
|
||||
const MaxRtpDiff = 65000 //相邻两个包之间的最大差值
|
||||
|
||||
Reference in New Issue
Block a user