mirror of
https://github.com/AlexxIT/go2rtc.git
synced 2026-04-22 23:57:20 +08:00
213 lines
4.5 KiB
Go
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
|
|
}
|