mirror of
https://github.com/AlexxIT/go2rtc.git
synced 2025-10-04 16:02:43 +08:00
230 lines
4.9 KiB
Go
230 lines
4.9 KiB
Go
package homekit
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/AlexxIT/go2rtc/internal/api"
|
|
"github.com/AlexxIT/go2rtc/internal/app"
|
|
"github.com/AlexxIT/go2rtc/internal/srtp"
|
|
"github.com/AlexxIT/go2rtc/internal/streams"
|
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
|
"github.com/AlexxIT/go2rtc/pkg/hap"
|
|
"github.com/AlexxIT/go2rtc/pkg/hap/camera"
|
|
"github.com/AlexxIT/go2rtc/pkg/homekit"
|
|
"github.com/AlexxIT/go2rtc/pkg/mdns"
|
|
"github.com/rs/zerolog"
|
|
)
|
|
|
|
func Init() {
|
|
var cfg struct {
|
|
Mod map[string]struct {
|
|
Pin string `yaml:"pin"`
|
|
Name string `yaml:"name"`
|
|
DeviceID string `yaml:"device_id"`
|
|
DevicePrivate string `yaml:"device_private"`
|
|
Pairings []string `yaml:"pairings"`
|
|
} `yaml:"homekit"`
|
|
}
|
|
app.LoadConfig(&cfg)
|
|
|
|
log = app.GetLogger("homekit")
|
|
|
|
streams.HandleFunc("homekit", streamHandler)
|
|
|
|
api.HandleFunc("api/homekit", apiHandler)
|
|
|
|
if cfg.Mod == nil {
|
|
return
|
|
}
|
|
|
|
servers = map[string]*server{}
|
|
var entries []*mdns.ServiceEntry
|
|
|
|
for id, conf := range cfg.Mod {
|
|
stream := streams.Get(id)
|
|
if stream == nil {
|
|
log.Warn().Msgf("[homekit] missing stream: %s", id)
|
|
continue
|
|
}
|
|
|
|
if conf.Pin == "" {
|
|
conf.Pin = "19550224" // default PIN
|
|
}
|
|
|
|
pin, err := hap.SanitizePin(conf.Pin)
|
|
if err != nil {
|
|
log.Error().Err(err).Caller().Send()
|
|
continue
|
|
}
|
|
|
|
deviceID := calcDeviceID(conf.DeviceID, id) // random MAC-address
|
|
name := calcName(conf.Name, deviceID)
|
|
|
|
srv := &server{
|
|
stream: id,
|
|
srtp: srtp.Server,
|
|
pairings: conf.Pairings,
|
|
}
|
|
|
|
srv.hap = &hap.Server{
|
|
Pin: pin,
|
|
DeviceID: deviceID,
|
|
DevicePrivate: calcDevicePrivate(conf.DevicePrivate, id),
|
|
GetPair: srv.GetPair,
|
|
AddPair: srv.AddPair,
|
|
Handler: homekit.ServerHandler(srv),
|
|
}
|
|
|
|
if url := findHomeKitURL(stream); url != "" {
|
|
// 1. Act as transparent proxy for HomeKit camera
|
|
dial := func() (net.Conn, error) {
|
|
client, err := homekit.Dial(url, srtp.Server)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return client.Conn(), nil
|
|
}
|
|
srv.hap.Handler = homekit.ProxyHandler(srv, dial)
|
|
} else {
|
|
// 2. Act as basic HomeKit camera
|
|
srv.accessory = camera.NewAccessory("AlexxIT", "go2rtc", name, "-", app.Version)
|
|
srv.hap.Handler = homekit.ServerHandler(srv)
|
|
}
|
|
|
|
srv.mdns = &mdns.ServiceEntry{
|
|
Name: name,
|
|
Port: uint16(api.Port),
|
|
Info: map[string]string{
|
|
hap.TXTConfigNumber: "1",
|
|
hap.TXTFeatureFlags: "0",
|
|
hap.TXTDeviceID: deviceID,
|
|
hap.TXTModel: app.UserAgent,
|
|
hap.TXTProtoVersion: "1.1",
|
|
hap.TXTStateNumber: "1",
|
|
hap.TXTStatusFlags: hap.StatusNotPaired,
|
|
hap.TXTCategory: hap.CategoryCamera,
|
|
hap.TXTSetupHash: srv.hap.SetupHash(),
|
|
},
|
|
}
|
|
entries = append(entries, srv.mdns)
|
|
|
|
srv.UpdateStatus()
|
|
|
|
host := srv.mdns.Host(mdns.ServiceHAP)
|
|
servers[host] = srv
|
|
}
|
|
|
|
api.HandleFunc(hap.PathPairSetup, hapPairSetup)
|
|
api.HandleFunc(hap.PathPairVerify, hapPairVerify)
|
|
|
|
log.Trace().Msgf("[homekit] mdns: %s", entries)
|
|
|
|
go func() {
|
|
if err := mdns.Serve(mdns.ServiceHAP, entries); err != nil {
|
|
log.Error().Err(err).Caller().Send()
|
|
}
|
|
}()
|
|
}
|
|
|
|
var log zerolog.Logger
|
|
var servers map[string]*server
|
|
|
|
func streamHandler(rawURL string) (core.Producer, error) {
|
|
if srtp.Server == nil {
|
|
return nil, errors.New("homekit: can't work without SRTP server")
|
|
}
|
|
|
|
rawURL, rawQuery, _ := strings.Cut(rawURL, "#")
|
|
client, err := homekit.Dial(rawURL, srtp.Server)
|
|
if client != nil && rawQuery != "" {
|
|
query := streams.ParseQuery(rawQuery)
|
|
client.Bitrate = parseBitrate(query.Get("bitrate"))
|
|
}
|
|
|
|
return client, err
|
|
}
|
|
|
|
func hapPairSetup(w http.ResponseWriter, r *http.Request) {
|
|
srv, ok := servers[r.Host]
|
|
if !ok {
|
|
log.Error().Msg("[homekit] unknown host: " + r.Host)
|
|
return
|
|
}
|
|
|
|
conn, rw, err := w.(http.Hijacker).Hijack()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
defer conn.Close()
|
|
|
|
if err = srv.hap.PairSetup(r, rw, conn); err != nil {
|
|
log.Error().Err(err).Caller().Send()
|
|
}
|
|
}
|
|
|
|
func hapPairVerify(w http.ResponseWriter, r *http.Request) {
|
|
srv, ok := servers[r.Host]
|
|
if !ok {
|
|
log.Error().Msg("[homekit] unknown host: " + r.Host)
|
|
return
|
|
}
|
|
|
|
conn, rw, err := w.(http.Hijacker).Hijack()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
defer conn.Close()
|
|
|
|
if err = srv.hap.PairVerify(r, rw, conn); err != nil && err != io.EOF {
|
|
log.Error().Err(err).Caller().Send()
|
|
}
|
|
}
|
|
|
|
func findHomeKitURL(stream *streams.Stream) string {
|
|
sources := stream.Sources()
|
|
if len(sources) == 0 {
|
|
return ""
|
|
}
|
|
|
|
url := sources[0]
|
|
if strings.HasPrefix(url, "homekit") {
|
|
return url
|
|
}
|
|
|
|
if strings.HasPrefix(url, "hass") {
|
|
location, _ := streams.Location(url)
|
|
if strings.HasPrefix(location, "homekit") {
|
|
return url
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func parseBitrate(s string) int {
|
|
n := len(s)
|
|
if n == 0 {
|
|
return 0
|
|
}
|
|
|
|
var k int
|
|
switch n--; s[n] {
|
|
case 'K':
|
|
k = 1024
|
|
s = s[:n]
|
|
case 'M':
|
|
k = 1024 * 1024
|
|
s = s[:n]
|
|
default:
|
|
k = 1
|
|
}
|
|
|
|
return k * core.Atoi(s)
|
|
}
|