Files
gb28181/pkg/gbs/register.go
2025-04-20 05:36:35 +08:00

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
})
}