Files
go2rtc/pkg/homekit/proxy.go
T
2025-03-12 22:28:30 +03:00

213 lines
4.5 KiB
Go

package homekit
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"github.com/AlexxIT/go2rtc/pkg/hap"
"github.com/AlexxIT/go2rtc/pkg/hap/camera"
"github.com/AlexxIT/go2rtc/pkg/hap/hds"
"github.com/AlexxIT/go2rtc/pkg/hap/secure"
"github.com/AlexxIT/go2rtc/pkg/hap/tlv8"
)
func ProxyHandler(pair ServerPair, dial func() (net.Conn, error)) hap.HandlerFunc {
return func(con net.Conn) error {
defer con.Close()
acc, err := dial()
if err != nil {
return err
}
defer acc.Close()
pr := &Proxy{
con: con.(*secure.Conn),
acc: acc.(*secure.Conn),
res: make(chan *http.Response),
}
// accessory (ex. Camera) => controller (ex. iPhone)
go pr.handleAcc()
// controller => accessory
return pr.handleCon(pair)
}
}
type Proxy struct {
con *secure.Conn
acc *secure.Conn
res chan *http.Response
}
func (p *Proxy) handleCon(pair ServerPair) error {
var hdsCharIID uint64
rd := bufio.NewReader(p.con)
for {
req, err := http.ReadRequest(rd)
if err != nil {
return err
}
var hdsConSalt string
switch {
case req.Method == "POST" && req.URL.Path == hap.PathPairings:
var res *http.Response
if res, err = handlePairings(p.con, req, pair); err != nil {
return err
}
if err = res.Write(p.con); err != nil {
return err
}
continue
case req.Method == "PUT" && req.URL.Path == hap.PathCharacteristics && hdsCharIID != 0:
body, _ := io.ReadAll(req.Body)
var v hap.JSONCharacters
_ = json.Unmarshal(body, &v)
for _, char := range v.Value {
if char.IID == hdsCharIID {
var hdsReq camera.SetupDataStreamRequest
_ = tlv8.UnmarshalBase64(char.Value, &hdsReq)
hdsConSalt = hdsReq.ControllerKeySalt
break
}
}
req.Body = io.NopCloser(bytes.NewReader(body))
}
if err = req.Write(p.acc); err != nil {
return err
}
res := <-p.res
switch {
case req.Method == "GET" && req.URL.Path == hap.PathAccessories:
body, _ := io.ReadAll(res.Body)
var v hap.JSONAccessories
if err = json.Unmarshal(body, &v); err != nil {
return err
}
for _, acc := range v.Value {
if char := acc.GetCharacter(camera.TypeSetupDataStreamTransport); char != nil {
hdsCharIID = char.IID
}
break
}
res.Body = io.NopCloser(bytes.NewReader(body))
case hdsConSalt != "":
body, _ := io.ReadAll(res.Body)
var v hap.JSONCharacters
_ = json.Unmarshal(body, &v)
for i, char := range v.Value {
if char.IID == hdsCharIID {
var hdsRes camera.SetupDataStreamResponse
_ = tlv8.UnmarshalBase64(char.Value, &hdsRes)
hdsAccSalt := hdsRes.AccessoryKeySalt
hdsPort := int(hdsRes.TransportTypeSessionParameters.TCPListeningPort)
// swtich accPort to conPort
hdsPort, err = p.listenHDS(hdsPort, hdsConSalt+hdsAccSalt)
if err != nil {
return err
}
hdsRes.TransportTypeSessionParameters.TCPListeningPort = uint16(hdsPort)
if v.Value[i].Value, err = tlv8.MarshalBase64(hdsRes); err != nil {
return err
}
body, _ = json.Marshal(v)
res.ContentLength = int64(len(body))
break
}
}
res.Body = io.NopCloser(bytes.NewReader(body))
}
if err = res.Write(p.con); err != nil {
return err
}
}
}
func (p *Proxy) handleAcc() error {
rd := bufio.NewReader(p.acc)
for {
res, err := hap.ReadResponse(rd, nil)
if err != nil {
return err
}
if res.Proto == hap.ProtoEvent {
if err = res.Write(p.con); err != nil {
return err
}
continue
}
// important to read body before next read response
body, err := io.ReadAll(res.Body)
if err != nil {
return err
}
res.Body = io.NopCloser(bytes.NewReader(body))
p.res <- res
}
}
func (p *Proxy) listenHDS(accPort int, salt string) (int, error) {
ln, err := net.ListenTCP("tcp", nil)
if err != nil {
return 0, err
}
go func() {
defer ln.Close()
// raw controller conn
con, err := ln.Accept()
if err != nil {
return
}
defer con.Close()
// secured controller conn (controlle=false because we are accessory)
con, err = hds.Client(con, p.con.SharedKey, salt, false)
if err != nil {
return
}
accIP := p.acc.RemoteAddr().(*net.TCPAddr).IP
// raw accessory conn
acc, err := net.Dial("tcp", fmt.Sprintf("%s:%d", accIP, accPort))
if err != nil {
return
}
defer acc.Close()
// secured accessory conn (controller=true because we are controller)
acc, err = hds.Client(acc, p.acc.SharedKey, salt, true)
if err != nil {
return
}
go io.Copy(con, acc)
_, _ = io.Copy(acc, con)
}()
conPort := ln.Addr().(*net.TCPAddr).Port
return conPort, nil
}