mirror of
https://github.com/gowvp/gb28181.git
synced 2025-09-26 19:41:19 +08:00
192 lines
4.7 KiB
Go
192 lines
4.7 KiB
Go
package gbs
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/gowvp/gb28181/internal/conf"
|
|
"github.com/gowvp/gb28181/internal/core/gb28181"
|
|
"github.com/gowvp/gb28181/internal/core/sms"
|
|
"github.com/gowvp/gb28181/pkg/gbs/sip"
|
|
"github.com/ixugo/goddd/pkg/conc"
|
|
"github.com/ixugo/goddd/pkg/orm"
|
|
)
|
|
|
|
const ignorePassword = "#"
|
|
|
|
type GB28181API struct {
|
|
cfg *conf.SIP
|
|
core gb28181.GB28181
|
|
|
|
catalog *sip.Collector[Channels]
|
|
|
|
// TODO: 待替换成 redis
|
|
streams *conc.Map[string, *Streams]
|
|
|
|
svr *Server
|
|
|
|
sms *sms.NodeManager
|
|
}
|
|
|
|
func NewGB28181API(cfg *conf.Bootstrap, store gb28181.GB28181, sms *sms.NodeManager) *GB28181API {
|
|
g := GB28181API{
|
|
cfg: &cfg.Sip,
|
|
core: store,
|
|
sms: sms,
|
|
catalog: sip.NewCollector(func(c1, c2 *Channels) bool {
|
|
return c1.ChannelID == c2.ChannelID
|
|
}),
|
|
streams: &conc.Map[string, *Streams]{},
|
|
}
|
|
go g.catalog.Start(func(s string, channel []*Channels) {
|
|
// 零值不做变更,没有通道又何必注册上来
|
|
if len(channel) == 0 {
|
|
return
|
|
}
|
|
|
|
// ipc, ok := g.svr.devices.Load(s)
|
|
// if ok {
|
|
// ipc.channels.Clear()
|
|
// for _, ch := range c {
|
|
|
|
// }
|
|
// }
|
|
|
|
ipc, ok := g.svr.memoryStorer.Load(s)
|
|
if ok {
|
|
for _, ch := range channel {
|
|
ch := Channel{
|
|
ChannelID: ch.ChannelID,
|
|
device: ipc,
|
|
}
|
|
ch.init(g.cfg.Domain)
|
|
ipc.Channels.Store(ch.ChannelID, &ch)
|
|
}
|
|
}
|
|
|
|
out := make([]*gb28181.Channel, len(channel))
|
|
for i, ch := range channel {
|
|
out[i] = &gb28181.Channel{
|
|
DeviceID: s,
|
|
ChannelID: ch.ChannelID,
|
|
Name: ch.Name,
|
|
IsOnline: ch.Status == "OK" || ch.Status == "ON",
|
|
Ext: gb28181.DeviceExt{
|
|
Manufacturer: ch.Manufacturer,
|
|
Model: ch.Model,
|
|
},
|
|
}
|
|
}
|
|
g.core.SaveChannels(out)
|
|
})
|
|
return &g
|
|
}
|
|
|
|
func (g *GB28181API) handlerRegister(ctx *sip.Context) {
|
|
if len(ctx.DeviceID) < 18 {
|
|
ctx.String(http.StatusBadRequest, "device id too short")
|
|
return
|
|
}
|
|
|
|
dev, err := g.core.GetDeviceByDeviceID(ctx.DeviceID)
|
|
if err != nil {
|
|
ctx.Log.Error("GetDeviceByDeviceID", "err", err)
|
|
ctx.String(http.StatusInternalServerError, "server db error")
|
|
return
|
|
}
|
|
g.svr.memoryStorer.LoadOrStore(ctx.DeviceID, &Device{
|
|
conn: ctx.Request.GetConnection(),
|
|
source: ctx.Source,
|
|
to: ctx.To,
|
|
})
|
|
|
|
password := dev.Password
|
|
if password == "" {
|
|
password = g.cfg.Password
|
|
}
|
|
// 免鉴权
|
|
if dev.Password == ignorePassword {
|
|
password = ""
|
|
}
|
|
if password != "" {
|
|
hdrs := ctx.Request.GetHeaders("Authorization")
|
|
if len(hdrs) == 0 {
|
|
resp := sip.NewResponseFromRequest("", ctx.Request, http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized), nil)
|
|
resp.AppendHeader(&sip.GenericHeader{HeaderName: "WWW-Authenticate", Contents: fmt.Sprintf("Digest nonce=\"%s\", algorithm=MD5, realm=\"%s\",qop=\"auth\"", sip.RandString(32), g.cfg.Domain)})
|
|
_ = ctx.Tx.Respond(resp)
|
|
return
|
|
}
|
|
authenticateHeader := hdrs[0].(*sip.GenericHeader)
|
|
auth := sip.AuthFromValue(authenticateHeader.Contents)
|
|
auth.SetPassword(password)
|
|
auth.SetUsername(dev.DeviceID)
|
|
auth.SetMethod(ctx.Request.Method())
|
|
auth.SetURI(auth.Get("uri"))
|
|
if auth.CalcResponse() != auth.Get("response") {
|
|
ctx.Log.Info("设备注册鉴权失败")
|
|
ctx.String(http.StatusUnauthorized, "wrong password")
|
|
return
|
|
}
|
|
}
|
|
|
|
respFn := func() {
|
|
resp := sip.NewResponseFromRequest("", ctx.Request, http.StatusOK, "OK", nil)
|
|
resp.AppendHeader(&sip.GenericHeader{
|
|
HeaderName: "Date",
|
|
Contents: time.Now().Format("2006-01-02T15:04:05.000"),
|
|
})
|
|
_ = ctx.Tx.Respond(resp)
|
|
}
|
|
|
|
expire := ctx.GetHeader("Expires")
|
|
if expire == "0" {
|
|
ctx.Log.Info("设备注销")
|
|
g.logout(ctx.DeviceID, func(b *gb28181.Device) {
|
|
b.IsOnline = false
|
|
b.Address = ctx.Source.String()
|
|
})
|
|
respFn()
|
|
return
|
|
}
|
|
g.login(ctx, expire)
|
|
|
|
// conn := ctx.Request.GetConnection()
|
|
// fmt.Printf(">>> %p\n", conn)
|
|
|
|
ctx.Log.Info("设备注册成功")
|
|
// ctx.Log.Debug("device info", "source", ctx.Source, "host", ctx.Host)
|
|
|
|
respFn()
|
|
|
|
g.QueryDeviceInfo(ctx)
|
|
_ = g.QueryCatalog(dev.DeviceID)
|
|
_ = g.QueryConfigDownloadBasic(dev.DeviceID)
|
|
}
|
|
|
|
func (g GB28181API) login(ctx *sip.Context, expire string) {
|
|
slog.Info("status change 设备上线", "device_id", ctx.DeviceID)
|
|
g.svr.memoryStorer.Change(ctx.DeviceID, func(d *gb28181.Device) {
|
|
d.IsOnline = true
|
|
d.RegisteredAt = orm.Now()
|
|
d.KeepaliveAt = orm.Now()
|
|
d.Expires, _ = strconv.Atoi(expire)
|
|
}, func(d *Device) {
|
|
d.conn = ctx.Request.GetConnection()
|
|
d.source = ctx.Source
|
|
d.to = ctx.To
|
|
})
|
|
}
|
|
|
|
func (g GB28181API) logout(deviceID string, changeFn func(*gb28181.Device)) error {
|
|
slog.Info("status change 设备离线", "device_id", deviceID)
|
|
return g.svr.memoryStorer.Change(deviceID, changeFn, func(d *Device) {
|
|
d.conn = nil
|
|
d.source = nil
|
|
d.to = nil
|
|
d.Expires = 0
|
|
})
|
|
}
|