mirror of
https://github.com/AlexxIT/go2rtc.git
synced 2025-09-26 20:31:11 +08:00
266 lines
6.4 KiB
Go
266 lines
6.4 KiB
Go
package homekit
|
|
|
|
import (
|
|
"crypto/ed25519"
|
|
"crypto/sha512"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"net"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/AlexxIT/go2rtc/internal/app"
|
|
"github.com/AlexxIT/go2rtc/internal/ffmpeg"
|
|
srtp2 "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/hap/tlv8"
|
|
"github.com/AlexxIT/go2rtc/pkg/homekit"
|
|
"github.com/AlexxIT/go2rtc/pkg/magic"
|
|
"github.com/AlexxIT/go2rtc/pkg/mdns"
|
|
"github.com/AlexxIT/go2rtc/pkg/srtp"
|
|
)
|
|
|
|
type server struct {
|
|
stream string // stream name from YAML
|
|
hap *hap.Server // server for HAP connection and encryption
|
|
mdns *mdns.ServiceEntry
|
|
srtp *srtp.Server
|
|
accessory *hap.Accessory // HAP accessory
|
|
pairings []string // pairings list
|
|
|
|
streams map[string]*homekit.Consumer
|
|
consumer *homekit.Consumer
|
|
}
|
|
|
|
func (s *server) UpdateStatus() {
|
|
// true status is important, or device may be offline in Apple Home
|
|
if len(s.pairings) == 0 {
|
|
s.mdns.Info[hap.TXTStatusFlags] = hap.StatusNotPaired
|
|
} else {
|
|
s.mdns.Info[hap.TXTStatusFlags] = hap.StatusPaired
|
|
}
|
|
}
|
|
|
|
func (s *server) GetAccessories(_ net.Conn) []*hap.Accessory {
|
|
return []*hap.Accessory{s.accessory}
|
|
}
|
|
|
|
func (s *server) GetCharacteristic(conn net.Conn, aid uint8, iid uint64) any {
|
|
log.Trace().Msgf("[homekit] %s: get char aid=%d iid=0x%x", conn.RemoteAddr(), aid, iid)
|
|
|
|
char := s.accessory.GetCharacterByID(iid)
|
|
if char == nil {
|
|
log.Warn().Msgf("[homekit] get unknown characteristic: %d", iid)
|
|
return nil
|
|
}
|
|
|
|
switch char.Type {
|
|
case camera.TypeSetupEndpoints:
|
|
if s.consumer == nil {
|
|
return nil
|
|
}
|
|
|
|
answer := s.consumer.GetAnswer()
|
|
v, err := tlv8.MarshalBase64(answer)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
return v
|
|
}
|
|
|
|
return char.Value
|
|
}
|
|
|
|
func (s *server) SetCharacteristic(conn net.Conn, aid uint8, iid uint64, value any) {
|
|
log.Trace().Msgf("[homekit] %s: set char aid=%d iid=0x%x value=%v", conn.RemoteAddr(), aid, iid, value)
|
|
|
|
char := s.accessory.GetCharacterByID(iid)
|
|
if char == nil {
|
|
log.Warn().Msgf("[homekit] set unknown characteristic: %d", iid)
|
|
return
|
|
}
|
|
|
|
switch char.Type {
|
|
case camera.TypeSetupEndpoints:
|
|
var offer camera.SetupEndpoints
|
|
if err := tlv8.UnmarshalBase64(value, &offer); err != nil {
|
|
return
|
|
}
|
|
|
|
s.consumer = homekit.NewConsumer(conn, srtp2.Server)
|
|
s.consumer.SetOffer(&offer)
|
|
|
|
case camera.TypeSelectedStreamConfiguration:
|
|
var conf camera.SelectedStreamConfig
|
|
if err := tlv8.UnmarshalBase64(value, &conf); err != nil {
|
|
return
|
|
}
|
|
|
|
log.Trace().Msgf("[homekit] %s stream id=%x cmd=%d", conn.RemoteAddr(), conf.Control.SessionID, conf.Control.Command)
|
|
|
|
switch conf.Control.Command {
|
|
case camera.SessionCommandEnd:
|
|
if consumer := s.streams[conf.Control.SessionID]; consumer != nil {
|
|
_ = consumer.Stop()
|
|
}
|
|
|
|
case camera.SessionCommandStart:
|
|
if s.consumer == nil {
|
|
return
|
|
}
|
|
|
|
if !s.consumer.SetConfig(&conf) {
|
|
log.Warn().Msgf("[homekit] wrong config")
|
|
return
|
|
}
|
|
|
|
if s.streams == nil {
|
|
s.streams = map[string]*homekit.Consumer{}
|
|
}
|
|
|
|
s.streams[conf.Control.SessionID] = s.consumer
|
|
|
|
stream := streams.Get(s.stream)
|
|
if err := stream.AddConsumer(s.consumer); err != nil {
|
|
return
|
|
}
|
|
|
|
go func() {
|
|
_, _ = s.consumer.WriteTo(nil)
|
|
stream.RemoveConsumer(s.consumer)
|
|
|
|
delete(s.streams, conf.Control.SessionID)
|
|
}()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *server) GetImage(conn net.Conn, width, height int) []byte {
|
|
log.Trace().Msgf("[homekit] %s: get image width=%d height=%d", conn.RemoteAddr(), width, height)
|
|
|
|
stream := streams.Get(s.stream)
|
|
cons := magic.NewKeyframe()
|
|
|
|
if err := stream.AddConsumer(cons); err != nil {
|
|
return nil
|
|
}
|
|
|
|
once := &core.OnceBuffer{} // init and first frame
|
|
_, _ = cons.WriteTo(once)
|
|
b := once.Buffer()
|
|
|
|
stream.RemoveConsumer(cons)
|
|
|
|
switch cons.CodecName() {
|
|
case core.CodecH264, core.CodecH265:
|
|
var err error
|
|
if b, err = ffmpeg.JPEGWithScale(b, width, height); err != nil {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return b
|
|
}
|
|
|
|
func (s *server) GetPair(conn net.Conn, id string) []byte {
|
|
log.Trace().Msgf("[homekit] %s: get pair id=%s", conn.RemoteAddr(), id)
|
|
|
|
for _, pairing := range s.pairings {
|
|
if !strings.Contains(pairing, id) {
|
|
continue
|
|
}
|
|
|
|
query, err := url.ParseQuery(pairing)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
if query.Get("client_id") != id {
|
|
continue
|
|
}
|
|
|
|
s := query.Get("client_public")
|
|
b, _ := hex.DecodeString(s)
|
|
return b
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *server) AddPair(conn net.Conn, id string, public []byte, permissions byte) {
|
|
log.Trace().Msgf("[homekit] %s: add pair id=%s public=%x perm=%d", conn.RemoteAddr(), id, public, permissions)
|
|
|
|
query := url.Values{
|
|
"client_id": []string{id},
|
|
"client_public": []string{hex.EncodeToString(public)},
|
|
"permissions": []string{string('0' + permissions)},
|
|
}
|
|
if s.GetPair(conn, id) == nil {
|
|
s.pairings = append(s.pairings, query.Encode())
|
|
s.UpdateStatus()
|
|
s.PatchConfig()
|
|
}
|
|
}
|
|
|
|
func (s *server) DelPair(conn net.Conn, id string) {
|
|
log.Trace().Msgf("[homekit] %s: del pair id=%s", conn.RemoteAddr(), id)
|
|
|
|
id = "client_id=" + id
|
|
for i, pairing := range s.pairings {
|
|
if !strings.Contains(pairing, id) {
|
|
continue
|
|
}
|
|
|
|
s.pairings = append(s.pairings[:i], s.pairings[i+1:]...)
|
|
s.UpdateStatus()
|
|
s.PatchConfig()
|
|
break
|
|
}
|
|
}
|
|
|
|
func (s *server) PatchConfig() {
|
|
if err := app.PatchConfig([]string{"homekit", s.stream, "pairings"}, s.pairings); err != nil {
|
|
log.Error().Err(err).Msgf(
|
|
"[homekit] can't save %s pairings=%v", s.stream, s.pairings,
|
|
)
|
|
}
|
|
}
|
|
|
|
func calcName(name, seed string) string {
|
|
if name != "" {
|
|
return name
|
|
}
|
|
b := sha512.Sum512([]byte(seed))
|
|
return fmt.Sprintf("go2rtc-%02X%02X", b[0], b[2])
|
|
}
|
|
|
|
func calcDeviceID(deviceID, seed string) string {
|
|
if deviceID != "" {
|
|
if len(deviceID) >= 17 {
|
|
// 1. Returd device_id as is (ex. AA:BB:CC:DD:EE:FF)
|
|
return deviceID
|
|
}
|
|
// 2. Use device_id as seed if not zero
|
|
seed = deviceID
|
|
}
|
|
b := sha512.Sum512([]byte(seed))
|
|
return fmt.Sprintf("%02X:%02X:%02X:%02X:%02X:%02X", b[32], b[34], b[36], b[38], b[40], b[42])
|
|
}
|
|
|
|
func calcDevicePrivate(private, seed string) []byte {
|
|
if private != "" {
|
|
// 1. Decode private from HEX string
|
|
if b, _ := hex.DecodeString(private); len(b) == ed25519.PrivateKeySize {
|
|
// 2. Return if OK
|
|
return b
|
|
}
|
|
// 3. Use private as seed if not zero
|
|
seed = private
|
|
}
|
|
b := sha512.Sum512([]byte(seed))
|
|
return ed25519.NewKeyFromSeed(b[:ed25519.SeedSize])
|
|
}
|