Code refactoring for HomeKit server
This commit is contained in:
+67
-38
@@ -3,6 +3,7 @@ package homekit
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -14,56 +15,84 @@ import (
|
|||||||
"github.com/AlexxIT/go2rtc/pkg/mdns"
|
"github.com/AlexxIT/go2rtc/pkg/mdns"
|
||||||
)
|
)
|
||||||
|
|
||||||
func apiHandler(w http.ResponseWriter, r *http.Request) {
|
func apiDiscovery(w http.ResponseWriter, r *http.Request) {
|
||||||
|
sources, err := discovery()
|
||||||
|
if err != nil {
|
||||||
|
api.Error(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
urls := findHomeKitURLs()
|
||||||
|
for id, u := range urls {
|
||||||
|
deviceID := u.Query().Get("device_id")
|
||||||
|
for _, source := range sources {
|
||||||
|
if strings.Contains(source.URL, deviceID) {
|
||||||
|
source.Location = id
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, source := range sources {
|
||||||
|
if source.Location == "" {
|
||||||
|
source.Location = " "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
api.ResponseSources(w, sources)
|
||||||
|
}
|
||||||
|
|
||||||
|
func apiHomekit(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case "GET":
|
case "GET":
|
||||||
sources, err := discovery()
|
if id := r.Form.Get("id"); id != "" {
|
||||||
if err != nil {
|
api.ResponsePrettyJSON(w, servers[id])
|
||||||
api.Error(w, err)
|
} else {
|
||||||
return
|
api.ResponsePrettyJSON(w, servers)
|
||||||
}
|
}
|
||||||
|
|
||||||
urls := findHomeKitURLs()
|
|
||||||
for id, u := range urls {
|
|
||||||
deviceID := u.Query().Get("device_id")
|
|
||||||
for _, source := range sources {
|
|
||||||
if strings.Contains(source.URL, deviceID) {
|
|
||||||
source.Location = id
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, source := range sources {
|
|
||||||
if source.Location == "" {
|
|
||||||
source.Location = " "
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
api.ResponseSources(w, sources)
|
|
||||||
|
|
||||||
case "POST":
|
case "POST":
|
||||||
if err := r.ParseMultipartForm(1024); err != nil {
|
id := r.Form.Get("id")
|
||||||
api.Error(w, err)
|
rawURL := r.Form.Get("src") + "&pin=" + r.Form.Get("pin")
|
||||||
return
|
if err := apiPair(id, rawURL); err != nil {
|
||||||
}
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
||||||
if err := apiPair(r.Form.Get("id"), r.Form.Get("url")); err != nil {
|
|
||||||
api.Error(w, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case "DELETE":
|
case "DELETE":
|
||||||
if err := r.ParseMultipartForm(1024); err != nil {
|
id := r.Form.Get("id")
|
||||||
api.Error(w, err)
|
if err := apiUnpair(id); err != nil {
|
||||||
return
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
}
|
|
||||||
|
|
||||||
if err := apiUnpair(r.Form.Get("id")); err != nil {
|
|
||||||
api.Error(w, err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func apiHomekitAccessories(w http.ResponseWriter, r *http.Request) {
|
||||||
|
src := r.URL.Query().Get("src")
|
||||||
|
stream := streams.Get(src)
|
||||||
|
rawURL := findHomeKitURL(stream.Sources())
|
||||||
|
|
||||||
|
client, err := hap.Dial(rawURL)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
res, err := client.Get(hap.PathAccessories)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", api.MimeJSON)
|
||||||
|
_, _ = io.Copy(w, res.Body)
|
||||||
|
}
|
||||||
|
|
||||||
func discovery() ([]*api.Source, error) {
|
func discovery() ([]*api.Source, error) {
|
||||||
var sources []*api.Source
|
var sources []*api.Source
|
||||||
|
|
||||||
|
|||||||
+25
-51
@@ -2,8 +2,6 @@ package homekit
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -35,12 +33,15 @@ func Init() {
|
|||||||
|
|
||||||
streams.HandleFunc("homekit", streamHandler)
|
streams.HandleFunc("homekit", streamHandler)
|
||||||
|
|
||||||
api.HandleFunc("api/homekit", apiHandler)
|
api.HandleFunc("api/homekit", apiHomekit)
|
||||||
|
api.HandleFunc("api/homekit/accessories", apiHomekitAccessories)
|
||||||
|
api.HandleFunc("api/discovery/homekit", apiDiscovery)
|
||||||
|
|
||||||
if cfg.Mod == nil {
|
if cfg.Mod == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hosts = map[string]*server{}
|
||||||
servers = map[string]*server{}
|
servers = map[string]*server{}
|
||||||
var entries []*mdns.ServiceEntry
|
var entries []*mdns.ServiceEntry
|
||||||
|
|
||||||
@@ -66,33 +67,14 @@ func Init() {
|
|||||||
|
|
||||||
srv := &server{
|
srv := &server{
|
||||||
stream: id,
|
stream: id,
|
||||||
srtp: srtp.Server,
|
|
||||||
pairings: conf.Pairings,
|
pairings: conf.Pairings,
|
||||||
}
|
}
|
||||||
|
|
||||||
srv.hap = &hap.Server{
|
srv.hap = &hap.Server{
|
||||||
Pin: pin,
|
Pin: pin,
|
||||||
DeviceID: deviceID,
|
DeviceID: deviceID,
|
||||||
DevicePrivate: calcDevicePrivate(conf.DevicePrivate, id),
|
DevicePrivate: calcDevicePrivate(conf.DevicePrivate, id),
|
||||||
GetPair: srv.GetPair,
|
GetClientPublic: srv.GetPair,
|
||||||
AddPair: srv.AddPair,
|
|
||||||
Handler: homekit.ServerHandler(srv),
|
|
||||||
}
|
|
||||||
|
|
||||||
if url := findHomeKitURL(stream.Sources()); 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{
|
srv.mdns = &mdns.ServiceEntry{
|
||||||
@@ -114,15 +96,24 @@ func Init() {
|
|||||||
|
|
||||||
srv.UpdateStatus()
|
srv.UpdateStatus()
|
||||||
|
|
||||||
|
if url := findHomeKitURL(stream.Sources()); url != "" {
|
||||||
|
// 1. Act as transparent proxy for HomeKit camera
|
||||||
|
srv.proxyURL = url
|
||||||
|
} else {
|
||||||
|
// 2. Act as basic HomeKit camera
|
||||||
|
srv.accessory = camera.NewAccessory("AlexxIT", "go2rtc", name, "-", app.Version)
|
||||||
|
}
|
||||||
|
|
||||||
host := srv.mdns.Host(mdns.ServiceHAP)
|
host := srv.mdns.Host(mdns.ServiceHAP)
|
||||||
servers[host] = srv
|
hosts[host] = srv
|
||||||
|
servers[id] = srv
|
||||||
|
|
||||||
|
log.Trace().Msgf("[homekit] new server: %s", srv.mdns)
|
||||||
}
|
}
|
||||||
|
|
||||||
api.HandleFunc(hap.PathPairSetup, hapHandler)
|
api.HandleFunc(hap.PathPairSetup, hapHandler)
|
||||||
api.HandleFunc(hap.PathPairVerify, hapHandler)
|
api.HandleFunc(hap.PathPairVerify, hapHandler)
|
||||||
|
|
||||||
log.Trace().Msgf("[homekit] mdns: %s", entries)
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if err := mdns.Serve(mdns.ServiceHAP, entries); err != nil {
|
if err := mdns.Serve(mdns.ServiceHAP, entries); err != nil {
|
||||||
log.Error().Err(err).Caller().Send()
|
log.Error().Err(err).Caller().Send()
|
||||||
@@ -131,6 +122,7 @@ func Init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var log zerolog.Logger
|
var log zerolog.Logger
|
||||||
|
var hosts map[string]*server
|
||||||
var servers map[string]*server
|
var servers map[string]*server
|
||||||
|
|
||||||
func streamHandler(rawURL string) (core.Producer, error) {
|
func streamHandler(rawURL string) (core.Producer, error) {
|
||||||
@@ -149,45 +141,27 @@ func streamHandler(rawURL string) (core.Producer, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func resolve(host string) *server {
|
func resolve(host string) *server {
|
||||||
if len(servers) == 1 {
|
if len(hosts) == 1 {
|
||||||
for _, srv := range servers {
|
for _, srv := range hosts {
|
||||||
return srv
|
return srv
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if srv, ok := servers[host]; ok {
|
if srv, ok := hosts[host]; ok {
|
||||||
return srv
|
return srv
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func hapHandler(w http.ResponseWriter, r *http.Request) {
|
func hapHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
conn, rw, err := w.(http.Hijacker).Hijack()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
// Can support multiple HomeKit cameras on single port ONLY for Apple devices.
|
// Can support multiple HomeKit cameras on single port ONLY for Apple devices.
|
||||||
// Doesn't support Home Assistant and any other open source projects
|
// Doesn't support Home Assistant and any other open source projects
|
||||||
// because they don't send the host header in requests.
|
// because they don't send the host header in requests.
|
||||||
srv := resolve(r.Host)
|
srv := resolve(r.Host)
|
||||||
if srv == nil {
|
if srv == nil {
|
||||||
log.Error().Msg("[homekit] unknown host: " + r.Host)
|
log.Error().Msg("[homekit] unknown host: " + r.Host)
|
||||||
_ = hap.WriteBackoff(rw)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
srv.Handle(w, r)
|
||||||
switch r.RequestURI {
|
|
||||||
case hap.PathPairSetup:
|
|
||||||
err = srv.hap.PairSetup(r, rw, conn)
|
|
||||||
case hap.PathPairVerify:
|
|
||||||
err = srv.hap.PairVerify(r, rw, conn)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil && err != io.EOF {
|
|
||||||
log.Error().Err(err).Caller().Send()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func findHomeKitURL(sources []string) string {
|
func findHomeKitURL(sources []string) string {
|
||||||
|
|||||||
+206
-93
@@ -4,10 +4,16 @@ import (
|
|||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/internal/app"
|
"github.com/AlexxIT/go2rtc/internal/app"
|
||||||
"github.com/AlexxIT/go2rtc/internal/ffmpeg"
|
"github.com/AlexxIT/go2rtc/internal/ffmpeg"
|
||||||
@@ -16,23 +22,133 @@ import (
|
|||||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/hap"
|
"github.com/AlexxIT/go2rtc/pkg/hap"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/hap/camera"
|
"github.com/AlexxIT/go2rtc/pkg/hap/camera"
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/hap/hds"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/hap/tlv8"
|
"github.com/AlexxIT/go2rtc/pkg/hap/tlv8"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/homekit"
|
"github.com/AlexxIT/go2rtc/pkg/homekit"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/magic"
|
"github.com/AlexxIT/go2rtc/pkg/magic"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/mdns"
|
"github.com/AlexxIT/go2rtc/pkg/mdns"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/srtp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type server struct {
|
type server struct {
|
||||||
stream string // stream name from YAML
|
hap *hap.Server // server for HAP connection and encryption
|
||||||
hap *hap.Server // server for HAP connection and encryption
|
mdns *mdns.ServiceEntry
|
||||||
mdns *mdns.ServiceEntry
|
|
||||||
srtp *srtp.Server
|
|
||||||
accessory *hap.Accessory // HAP accessory
|
|
||||||
pairings []string // pairings list
|
|
||||||
|
|
||||||
streams map[string]*homekit.Consumer
|
pairings []string // pairings list
|
||||||
consumer *homekit.Consumer
|
conns []any
|
||||||
|
mu sync.Mutex
|
||||||
|
|
||||||
|
accessory *hap.Accessory // HAP accessory
|
||||||
|
consumer *homekit.Consumer
|
||||||
|
proxyURL string
|
||||||
|
stream string // stream name from YAML
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) MarshalJSON() ([]byte, error) {
|
||||||
|
v := struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
DeviceID string `json:"device_id"`
|
||||||
|
Paired int `json:"paired"`
|
||||||
|
Conns []any `json:"connections"`
|
||||||
|
}{
|
||||||
|
Name: s.mdns.Name,
|
||||||
|
DeviceID: s.mdns.Info[hap.TXTDeviceID],
|
||||||
|
Paired: len(s.pairings),
|
||||||
|
Conns: s.conns,
|
||||||
|
}
|
||||||
|
return json.Marshal(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) Handle(w http.ResponseWriter, r *http.Request) {
|
||||||
|
conn, rw, err := w.(http.Hijacker).Hijack()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
// Fix reading from Body after Hijack.
|
||||||
|
r.Body = io.NopCloser(rw)
|
||||||
|
|
||||||
|
switch r.RequestURI {
|
||||||
|
case hap.PathPairSetup:
|
||||||
|
id, key, err := s.hap.PairSetup(r, rw)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Caller().Send()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.AddPair(id, key, hap.PermissionAdmin)
|
||||||
|
|
||||||
|
case hap.PathPairVerify:
|
||||||
|
id, key, err := s.hap.PairVerify(r, rw)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug().Err(err).Caller().Send()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().Str("stream", s.stream).Str("client_id", id).Msgf("[homekit] %s: new conn", conn.RemoteAddr())
|
||||||
|
|
||||||
|
controller, err := hap.NewConn(conn, rw, key, false)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Caller().Send()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.AddConn(controller)
|
||||||
|
defer s.DelConn(controller)
|
||||||
|
|
||||||
|
var handler homekit.HandlerFunc
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case s.accessory != nil:
|
||||||
|
handler = homekit.ServerHandler(s)
|
||||||
|
case s.proxyURL != "":
|
||||||
|
client, err := hap.Dial(s.proxyURL)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Caller().Send()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handler = homekit.ProxyHandler(s, client.Conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If your iPhone goes to sleep, it will be an EOF error.
|
||||||
|
if err = handler(controller); err != nil && !errors.Is(err, io.EOF) {
|
||||||
|
log.Error().Err(err).Caller().Send()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type logger struct {
|
||||||
|
v any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l logger) String() string {
|
||||||
|
switch v := l.v.(type) {
|
||||||
|
case *hap.Conn:
|
||||||
|
return "hap " + v.RemoteAddr().String()
|
||||||
|
case *hds.Conn:
|
||||||
|
return "hds " + v.RemoteAddr().String()
|
||||||
|
case *homekit.Consumer:
|
||||||
|
return "rtp " + v.RemoteAddr
|
||||||
|
}
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) AddConn(v any) {
|
||||||
|
log.Trace().Str("stream", s.stream).Msgf("[homekit] add conn %s", logger{v})
|
||||||
|
s.mu.Lock()
|
||||||
|
s.conns = append(s.conns, v)
|
||||||
|
s.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) DelConn(v any) {
|
||||||
|
log.Trace().Str("stream", s.stream).Msgf("[homekit] del conn %s", logger{v})
|
||||||
|
s.mu.Lock()
|
||||||
|
if i := slices.Index(s.conns, v); i >= 0 {
|
||||||
|
s.conns = slices.Delete(s.conns, i, i+1)
|
||||||
|
}
|
||||||
|
s.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) UpdateStatus() {
|
func (s *server) UpdateStatus() {
|
||||||
@@ -44,12 +160,68 @@ func (s *server) UpdateStatus() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *server) pairIndex(id string) int {
|
||||||
|
id = "client_id=" + id
|
||||||
|
for i, pairing := range s.pairings {
|
||||||
|
if strings.HasPrefix(pairing, id) {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) GetPair(id string) []byte {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
if i := s.pairIndex(id); i >= 0 {
|
||||||
|
query, _ := url.ParseQuery(s.pairings[i])
|
||||||
|
b, _ := hex.DecodeString(query.Get("client_public"))
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) AddPair(id string, public []byte, permissions byte) {
|
||||||
|
log.Debug().Str("stream", s.stream).Msgf("[homekit] add pair id=%s public=%x perm=%d", id, public, permissions)
|
||||||
|
|
||||||
|
s.mu.Lock()
|
||||||
|
if s.pairIndex(id) < 0 {
|
||||||
|
s.pairings = append(s.pairings, fmt.Sprintf(
|
||||||
|
"client_id=%s&client_public=%x&permissions=%d", id, public, permissions,
|
||||||
|
))
|
||||||
|
s.UpdateStatus()
|
||||||
|
s.PatchConfig()
|
||||||
|
}
|
||||||
|
s.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) DelPair(id string) {
|
||||||
|
log.Debug().Str("stream", s.stream).Msgf("[homekit] del pair id=%s", id)
|
||||||
|
|
||||||
|
s.mu.Lock()
|
||||||
|
if i := s.pairIndex(id); i >= 0 {
|
||||||
|
s.pairings = append(s.pairings[:i], s.pairings[i+1:]...)
|
||||||
|
s.UpdateStatus()
|
||||||
|
s.PatchConfig()
|
||||||
|
}
|
||||||
|
s.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (s *server) GetAccessories(_ net.Conn) []*hap.Accessory {
|
func (s *server) GetAccessories(_ net.Conn) []*hap.Accessory {
|
||||||
return []*hap.Accessory{s.accessory}
|
return []*hap.Accessory{s.accessory}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) GetCharacteristic(conn net.Conn, aid uint8, iid uint64) any {
|
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)
|
log.Trace().Str("stream", s.stream).Msgf("[homekit] get char aid=%d iid=0x%x", aid, iid)
|
||||||
|
|
||||||
char := s.accessory.GetCharacterByID(iid)
|
char := s.accessory.GetCharacterByID(iid)
|
||||||
if char == nil {
|
if char == nil {
|
||||||
@@ -59,11 +231,12 @@ func (s *server) GetCharacteristic(conn net.Conn, aid uint8, iid uint64) any {
|
|||||||
|
|
||||||
switch char.Type {
|
switch char.Type {
|
||||||
case camera.TypeSetupEndpoints:
|
case camera.TypeSetupEndpoints:
|
||||||
if s.consumer == nil {
|
consumer := s.consumer
|
||||||
|
if consumer == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
answer := s.consumer.GetAnswer()
|
answer := consumer.GetAnswer()
|
||||||
v, err := tlv8.MarshalBase64(answer)
|
v, err := tlv8.MarshalBase64(answer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -76,7 +249,7 @@ func (s *server) GetCharacteristic(conn net.Conn, aid uint8, iid uint64) any {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) SetCharacteristic(conn net.Conn, aid uint8, iid uint64, value any) {
|
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)
|
log.Trace().Str("stream", s.stream).Msgf("[homekit] set char aid=%d iid=0x%x value=%v", aid, iid, value)
|
||||||
|
|
||||||
char := s.accessory.GetCharacterByID(iid)
|
char := s.accessory.GetCharacterByID(iid)
|
||||||
if char == nil {
|
if char == nil {
|
||||||
@@ -91,8 +264,9 @@ func (s *server) SetCharacteristic(conn net.Conn, aid uint8, iid uint64, value a
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.consumer = homekit.NewConsumer(conn, srtp2.Server)
|
consumer := homekit.NewConsumer(conn, srtp2.Server)
|
||||||
s.consumer.SetOffer(&offer)
|
consumer.SetOffer(&offer)
|
||||||
|
s.consumer = consumer
|
||||||
|
|
||||||
case camera.TypeSelectedStreamConfiguration:
|
case camera.TypeSelectedStreamConfiguration:
|
||||||
var conf camera.SelectedStreamConfiguration
|
var conf camera.SelectedStreamConfiguration
|
||||||
@@ -100,47 +274,49 @@ func (s *server) SetCharacteristic(conn net.Conn, aid uint8, iid uint64, value a
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Trace().Msgf("[homekit] %s stream id=%x cmd=%d", conn.RemoteAddr(), conf.Control.SessionID, conf.Control.Command)
|
log.Trace().Str("stream", s.stream).Msgf("[homekit] stream id=%x cmd=%d", conf.Control.SessionID, conf.Control.Command)
|
||||||
|
|
||||||
switch conf.Control.Command {
|
switch conf.Control.Command {
|
||||||
case camera.SessionCommandEnd:
|
case camera.SessionCommandEnd:
|
||||||
if consumer := s.streams[conf.Control.SessionID]; consumer != nil {
|
for _, consumer := range s.conns {
|
||||||
_ = consumer.Stop()
|
if consumer, ok := consumer.(*homekit.Consumer); ok {
|
||||||
|
if consumer.SessionID() == conf.Control.SessionID {
|
||||||
|
_ = consumer.Stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case camera.SessionCommandStart:
|
case camera.SessionCommandStart:
|
||||||
if s.consumer == nil {
|
consumer := s.consumer
|
||||||
|
if consumer == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !s.consumer.SetConfig(&conf) {
|
if !consumer.SetConfig(&conf) {
|
||||||
log.Warn().Msgf("[homekit] wrong config")
|
log.Warn().Msgf("[homekit] wrong config")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.streams == nil {
|
s.AddConn(consumer)
|
||||||
s.streams = map[string]*homekit.Consumer{}
|
|
||||||
}
|
|
||||||
|
|
||||||
s.streams[conf.Control.SessionID] = s.consumer
|
|
||||||
|
|
||||||
stream := streams.Get(s.stream)
|
stream := streams.Get(s.stream)
|
||||||
if err := stream.AddConsumer(s.consumer); err != nil {
|
if err := stream.AddConsumer(consumer); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
_, _ = s.consumer.WriteTo(nil)
|
_, _ = consumer.WriteTo(nil)
|
||||||
stream.RemoveConsumer(s.consumer)
|
stream.RemoveConsumer(consumer)
|
||||||
|
|
||||||
delete(s.streams, conf.Control.SessionID)
|
s.DelConn(consumer)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) GetImage(conn net.Conn, width, height int) []byte {
|
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)
|
log.Trace().Str("stream", s.stream).Msgf("[homekit] get image width=%d height=%d", width, height)
|
||||||
|
|
||||||
stream := streams.Get(s.stream)
|
stream := streams.Get(s.stream)
|
||||||
cons := magic.NewKeyframe()
|
cons := magic.NewKeyframe()
|
||||||
@@ -166,69 +342,6 @@ func (s *server) GetImage(conn net.Conn, width, height int) []byte {
|
|||||||
return b
|
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 {
|
func calcName(name, seed string) string {
|
||||||
if name != "" {
|
if name != "" {
|
||||||
return name
|
return name
|
||||||
|
|||||||
+7
-3
@@ -18,7 +18,6 @@ import (
|
|||||||
"github.com/AlexxIT/go2rtc/pkg/hap/curve25519"
|
"github.com/AlexxIT/go2rtc/pkg/hap/curve25519"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/hap/ed25519"
|
"github.com/AlexxIT/go2rtc/pkg/hap/ed25519"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/hap/hkdf"
|
"github.com/AlexxIT/go2rtc/pkg/hap/hkdf"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/hap/secure"
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/hap/tlv8"
|
"github.com/AlexxIT/go2rtc/pkg/hap/tlv8"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/mdns"
|
"github.com/AlexxIT/go2rtc/pkg/mdns"
|
||||||
)
|
)
|
||||||
@@ -46,7 +45,7 @@ type Client struct {
|
|||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(rawURL string) (*Client, error) {
|
func Dial(rawURL string) (*Client, error) {
|
||||||
u, err := url.Parse(rawURL)
|
u, err := url.Parse(rawURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -61,6 +60,10 @@ func NewClient(rawURL string) (*Client, error) {
|
|||||||
ClientPrivate: DecodeKey(query.Get("client_private")),
|
ClientPrivate: DecodeKey(query.Get("client_private")),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = c.Dial(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,6 +99,7 @@ func (c *Client) Dial() (err error) {
|
|||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// TODO: close conn on error
|
||||||
if c.Conn, err = net.DialTimeout("tcp", c.DeviceAddress, ConnDialTimeout); err != nil {
|
if c.Conn, err = net.DialTimeout("tcp", c.DeviceAddress, ConnDialTimeout); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -219,7 +223,7 @@ func (c *Client) Dial() (err error) {
|
|||||||
rw := bufio.NewReadWriter(c.reader, bufio.NewWriter(c.Conn))
|
rw := bufio.NewReadWriter(c.reader, bufio.NewWriter(c.Conn))
|
||||||
|
|
||||||
// like tls.Client wrapper over net.Conn
|
// like tls.Client wrapper over net.Conn
|
||||||
if c.Conn, err = secure.Client(c.Conn, rw, sessionShared, true); err != nil {
|
if c.Conn, err = NewConn(c.Conn, rw, sessionShared, true); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// new reader for new conn
|
// new reader for new conn
|
||||||
|
|||||||
@@ -121,9 +121,7 @@ func (c *Client) Pair(feature, pin string) (err error) {
|
|||||||
username := []byte("Pair-Setup")
|
username := []byte("Pair-Setup")
|
||||||
|
|
||||||
// Stanford Secure Remote Password (SRP) / Password Authenticated Key Exchange (PAKE)
|
// Stanford Secure Remote Password (SRP) / Password Authenticated Key Exchange (PAKE)
|
||||||
pake, err := srp.NewSRP(
|
pake, err := srp.NewSRP("rfc5054.3072", sha512.New, keyDerivativeFuncRFC2945(username))
|
||||||
"rfc5054.3072", sha512.New, keyDerivativeFuncRFC2945(username),
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -132,6 +130,7 @@ func (c *Client) Pair(feature, pin string) (err error) {
|
|||||||
|
|
||||||
// username: "Pair-Setup", password: PIN (with dashes)
|
// username: "Pair-Setup", password: PIN (with dashes)
|
||||||
session := pake.NewClientSession(username, []byte(pin))
|
session := pake.NewClientSession(username, []byte(pin))
|
||||||
|
|
||||||
sessionShared, err := session.ComputeKey([]byte(plainM2.Salt), []byte(plainM2.SessionKey))
|
sessionShared, err := session.ComputeKey([]byte(plainM2.Salt), []byte(plainM2.SessionKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
package secure
|
package hap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/hap/chacha20poly1305"
|
"github.com/AlexxIT/go2rtc/pkg/hap/chacha20poly1305"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/hap/hkdf"
|
"github.com/AlexxIT/go2rtc/pkg/hap/hkdf"
|
||||||
)
|
)
|
||||||
@@ -15,16 +18,33 @@ import (
|
|||||||
type Conn struct {
|
type Conn struct {
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
rw *bufio.ReadWriter
|
rw *bufio.ReadWriter
|
||||||
|
wmu sync.Mutex
|
||||||
|
|
||||||
encryptKey []byte
|
encryptKey []byte
|
||||||
decryptKey []byte
|
decryptKey []byte
|
||||||
encryptCnt uint64
|
encryptCnt uint64
|
||||||
decryptCnt uint64
|
decryptCnt uint64
|
||||||
|
|
||||||
|
//ClientID string
|
||||||
SharedKey []byte
|
SharedKey []byte
|
||||||
|
|
||||||
|
recv int
|
||||||
|
send int
|
||||||
}
|
}
|
||||||
|
|
||||||
func Client(conn net.Conn, rw *bufio.ReadWriter, sharedKey []byte, isClient bool) (*Conn, error) {
|
func (c *Conn) MarshalJSON() ([]byte, error) {
|
||||||
|
conn := core.Connection{
|
||||||
|
ID: core.ID(c),
|
||||||
|
FormatName: "homekit",
|
||||||
|
Protocol: "hap",
|
||||||
|
RemoteAddr: c.conn.RemoteAddr().String(),
|
||||||
|
Recv: c.recv,
|
||||||
|
Send: c.send,
|
||||||
|
}
|
||||||
|
return json.Marshal(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConn(conn net.Conn, rw *bufio.ReadWriter, sharedKey []byte, isClient bool) (*Conn, error) {
|
||||||
key1, err := hkdf.Sha512(sharedKey, "Control-Salt", "Control-Read-Encryption-Key")
|
key1, err := hkdf.Sha512(sharedKey, "Control-Salt", "Control-Read-Encryption-Key")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -52,8 +72,8 @@ func Client(conn net.Conn, rw *bufio.ReadWriter, sharedKey []byte, isClient bool
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// PacketSizeMax is the max length of encrypted packets
|
// packetSizeMax is the max length of encrypted packets
|
||||||
PacketSizeMax = 0x400
|
packetSizeMax = 0x400
|
||||||
|
|
||||||
VerifySize = 2
|
VerifySize = 2
|
||||||
NonceSize = 8
|
NonceSize = 8
|
||||||
@@ -61,18 +81,18 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (c *Conn) Read(b []byte) (n int, err error) {
|
func (c *Conn) Read(b []byte) (n int, err error) {
|
||||||
if cap(b) < PacketSizeMax {
|
if cap(b) < packetSizeMax {
|
||||||
return 0, errors.New("hap: read buffer is too small")
|
return 0, errors.New("hap: read buffer is too small")
|
||||||
}
|
}
|
||||||
|
|
||||||
verify := make([]byte, 2) // verify = plain message size
|
verify := make([]byte, VerifySize) // verify = plain message size
|
||||||
if _, err = io.ReadFull(c.rw, verify); err != nil {
|
if _, err = io.ReadFull(c.rw, verify); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
n = int(binary.LittleEndian.Uint16(verify))
|
n = int(binary.LittleEndian.Uint16(verify))
|
||||||
ciphertext := make([]byte, n+Overhead)
|
|
||||||
|
|
||||||
|
ciphertext := make([]byte, n+Overhead)
|
||||||
if _, err = io.ReadFull(c.rw, ciphertext); err != nil {
|
if _, err = io.ReadFull(c.rw, ciphertext); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -82,18 +102,23 @@ func (c *Conn) Read(b []byte) (n int, err error) {
|
|||||||
c.decryptCnt++
|
c.decryptCnt++
|
||||||
|
|
||||||
_, err = chacha20poly1305.DecryptAndVerify(c.decryptKey, b[:0], nonce, ciphertext, verify)
|
_, err = chacha20poly1305.DecryptAndVerify(c.decryptKey, b[:0], nonce, ciphertext, verify)
|
||||||
|
|
||||||
|
c.recv += n
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) Write(b []byte) (n int, err error) {
|
func (c *Conn) Write(b []byte) (n int, err error) {
|
||||||
buf := make([]byte, 0, PacketSizeMax+Overhead)
|
c.wmu.Lock()
|
||||||
|
defer c.wmu.Unlock()
|
||||||
|
|
||||||
|
buf := make([]byte, 0, packetSizeMax+Overhead)
|
||||||
nonce := make([]byte, NonceSize)
|
nonce := make([]byte, NonceSize)
|
||||||
verify := make([]byte, VerifySize)
|
verify := make([]byte, VerifySize)
|
||||||
|
|
||||||
for len(b) > 0 {
|
for len(b) > 0 {
|
||||||
size := len(b)
|
size := len(b)
|
||||||
if size > PacketSizeMax {
|
if size > packetSizeMax {
|
||||||
size = PacketSizeMax
|
size = packetSizeMax
|
||||||
}
|
}
|
||||||
|
|
||||||
binary.LittleEndian.PutUint16(verify, uint16(size))
|
binary.LittleEndian.PutUint16(verify, uint16(size))
|
||||||
@@ -118,6 +143,8 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = c.rw.Flush()
|
err = c.rw.Flush()
|
||||||
|
|
||||||
|
c.send += n
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
+27
-6
@@ -4,16 +4,18 @@ package hds
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/hap"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/hap/chacha20poly1305"
|
"github.com/AlexxIT/go2rtc/pkg/hap/chacha20poly1305"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/hap/hkdf"
|
"github.com/AlexxIT/go2rtc/pkg/hap/hkdf"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/hap/secure"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Client(conn net.Conn, key []byte, salt string, controller bool) (*Conn, error) {
|
func NewConn(conn net.Conn, key []byte, salt string, controller bool) (*Conn, error) {
|
||||||
writeKey, err := hkdf.Sha512(key, salt, "HDS-Write-Encryption-Key")
|
writeKey, err := hkdf.Sha512(key, salt, "HDS-Write-Encryption-Key")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -49,6 +51,21 @@ type Conn struct {
|
|||||||
encryptKey []byte
|
encryptKey []byte
|
||||||
decryptCnt uint64
|
decryptCnt uint64
|
||||||
encryptCnt uint64
|
encryptCnt uint64
|
||||||
|
|
||||||
|
recv int
|
||||||
|
send int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) MarshalJSON() ([]byte, error) {
|
||||||
|
conn := core.Connection{
|
||||||
|
ID: core.ID(c),
|
||||||
|
FormatName: "homekit",
|
||||||
|
Protocol: "hds",
|
||||||
|
RemoteAddr: c.conn.RemoteAddr().String(),
|
||||||
|
Recv: c.recv,
|
||||||
|
Send: c.send,
|
||||||
|
}
|
||||||
|
return json.Marshal(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) Read(p []byte) (n int, err error) {
|
func (c *Conn) Read(p []byte) (n int, err error) {
|
||||||
@@ -59,16 +76,18 @@ func (c *Conn) Read(p []byte) (n int, err error) {
|
|||||||
|
|
||||||
n = int(binary.BigEndian.Uint32(verify) & 0xFFFFFF)
|
n = int(binary.BigEndian.Uint32(verify) & 0xFFFFFF)
|
||||||
|
|
||||||
ciphertext := make([]byte, n+secure.Overhead)
|
ciphertext := make([]byte, n+hap.Overhead)
|
||||||
if _, err = io.ReadFull(c.rd, ciphertext); err != nil {
|
if _, err = io.ReadFull(c.rd, ciphertext); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
nonce := make([]byte, secure.NonceSize)
|
nonce := make([]byte, hap.NonceSize)
|
||||||
binary.LittleEndian.PutUint64(nonce, c.decryptCnt)
|
binary.LittleEndian.PutUint64(nonce, c.decryptCnt)
|
||||||
c.decryptCnt++
|
c.decryptCnt++
|
||||||
|
|
||||||
_, err = chacha20poly1305.DecryptAndVerify(c.decryptKey, p[:0], nonce, ciphertext, verify)
|
_, err = chacha20poly1305.DecryptAndVerify(c.decryptKey, p[:0], nonce, ciphertext, verify)
|
||||||
|
|
||||||
|
c.recv += n
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,11 +100,11 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
nonce := make([]byte, secure.NonceSize)
|
nonce := make([]byte, hap.NonceSize)
|
||||||
binary.LittleEndian.PutUint64(nonce, c.encryptCnt)
|
binary.LittleEndian.PutUint64(nonce, c.encryptCnt)
|
||||||
c.encryptCnt++
|
c.encryptCnt++
|
||||||
|
|
||||||
buf := make([]byte, n+secure.Overhead)
|
buf := make([]byte, n+hap.Overhead)
|
||||||
if _, err = chacha20poly1305.EncryptAndSeal(c.encryptKey, buf[:0], nonce, b, verify); err != nil {
|
if _, err = chacha20poly1305.EncryptAndSeal(c.encryptKey, buf[:0], nonce, b, verify); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -95,6 +114,8 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = c.wr.Flush()
|
err = c.wr.Flush()
|
||||||
|
|
||||||
|
c.send += n
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+272
-42
@@ -6,28 +6,23 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/hap/chacha20poly1305"
|
"github.com/AlexxIT/go2rtc/pkg/hap/chacha20poly1305"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/hap/curve25519"
|
"github.com/AlexxIT/go2rtc/pkg/hap/curve25519"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/hap/ed25519"
|
"github.com/AlexxIT/go2rtc/pkg/hap/ed25519"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/hap/hkdf"
|
"github.com/AlexxIT/go2rtc/pkg/hap/hkdf"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/hap/secure"
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/hap/tlv8"
|
"github.com/AlexxIT/go2rtc/pkg/hap/tlv8"
|
||||||
|
"github.com/tadglines/go-pkgs/crypto/srp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HandlerFunc func(net.Conn) error
|
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
Pin string
|
Pin string
|
||||||
DeviceID string
|
DeviceID string
|
||||||
DevicePrivate []byte
|
DevicePrivate []byte
|
||||||
|
|
||||||
GetPair func(conn net.Conn, id string) []byte
|
// GetClientPublic may be nil, so client validation will be disabled
|
||||||
AddPair func(conn net.Conn, id string, public []byte, permissions byte)
|
GetClientPublic func(id string) []byte
|
||||||
|
|
||||||
Handler HandlerFunc
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) ServerPublic() []byte {
|
func (s *Server) ServerPublic() []byte {
|
||||||
@@ -48,37 +43,240 @@ func (s *Server) SetupHash() string {
|
|||||||
return base64.StdEncoding.EncodeToString(b[:4])
|
return base64.StdEncoding.EncodeToString(b[:4])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) PairVerify(req *http.Request, rw *bufio.ReadWriter, conn net.Conn) error {
|
func (s *Server) PairSetup(req *http.Request, rw *bufio.ReadWriter) (id string, publicKey []byte, err error) {
|
||||||
// Request from iPhone
|
// STEP 1. Request from iPhone
|
||||||
var plainM1 struct {
|
var plainM1 struct {
|
||||||
PublicKey string `tlv8:"3"`
|
State byte `tlv8:"6"`
|
||||||
State byte `tlv8:"6"`
|
Method byte `tlv8:"0"`
|
||||||
|
Flags uint32 `tlv8:"19"`
|
||||||
}
|
}
|
||||||
if err := tlv8.UnmarshalReader(req.Body, req.ContentLength, &plainM1); err != nil {
|
if err = tlv8.UnmarshalReader(req.Body, req.ContentLength, &plainM1); err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
if plainM1.State != StateM1 {
|
if plainM1.State != StateM1 {
|
||||||
return newRequestError(plainM1)
|
err = newRequestError(plainM1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
username := []byte("Pair-Setup")
|
||||||
|
|
||||||
|
// Stanford Secure Remote Password (SRP) / Password Authenticated Key Exchange (PAKE)
|
||||||
|
pake, err := srp.NewSRP("rfc5054.3072", sha512.New, keyDerivativeFuncRFC2945(username))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pake.SaltLength = 16
|
||||||
|
|
||||||
|
salt, verifier, err := pake.ComputeVerifier([]byte(s.Pin))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session := pake.NewServerSession(username, salt, verifier)
|
||||||
|
|
||||||
|
// STEP 2. Response to iPhone
|
||||||
|
plainM2 := struct {
|
||||||
|
State byte `tlv8:"6"`
|
||||||
|
PublicKey string `tlv8:"3"`
|
||||||
|
Salt string `tlv8:"2"`
|
||||||
|
}{
|
||||||
|
State: StateM2,
|
||||||
|
PublicKey: string(session.GetB()),
|
||||||
|
Salt: string(salt),
|
||||||
|
}
|
||||||
|
body, err := tlv8.Marshal(plainM2)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = WriteResponse(rw.Writer, http.StatusOK, MimeTLV8, body); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP 3. Request from iPhone
|
||||||
|
if req, err = http.ReadRequest(rw.Reader); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var plainM3 struct {
|
||||||
|
State byte `tlv8:"6"`
|
||||||
|
PublicKey string `tlv8:"3"`
|
||||||
|
Proof string `tlv8:"4"`
|
||||||
|
}
|
||||||
|
if err = tlv8.UnmarshalReader(req.Body, req.ContentLength, &plainM3); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if plainM3.State != StateM3 {
|
||||||
|
err = newRequestError(plainM3)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// important to compute key before verify client
|
||||||
|
sessionShared, err := session.ComputeKey([]byte(plainM3.PublicKey))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !session.VerifyClientAuthenticator([]byte(plainM3.Proof)) {
|
||||||
|
err = errors.New("hap: VerifyClientAuthenticator")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
proof := session.ComputeAuthenticator([]byte(plainM3.Proof)) // server proof
|
||||||
|
|
||||||
|
// STEP 4. Response to iPhone
|
||||||
|
payloadM4 := struct {
|
||||||
|
State byte `tlv8:"6"`
|
||||||
|
Proof string `tlv8:"4"`
|
||||||
|
}{
|
||||||
|
State: StateM4,
|
||||||
|
Proof: string(proof),
|
||||||
|
}
|
||||||
|
if body, err = tlv8.Marshal(payloadM4); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = WriteResponse(rw.Writer, http.StatusOK, MimeTLV8, body); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP 5. Request from iPhone
|
||||||
|
if req, err = http.ReadRequest(rw.Reader); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var cipherM5 struct {
|
||||||
|
State byte `tlv8:"6"`
|
||||||
|
EncryptedData string `tlv8:"5"`
|
||||||
|
}
|
||||||
|
if err = tlv8.UnmarshalReader(req.Body, req.ContentLength, &cipherM5); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cipherM5.State != StateM5 {
|
||||||
|
err = newRequestError(cipherM5)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// decrypt message using session shared
|
||||||
|
encryptKey, err := hkdf.Sha512(sessionShared, "Pair-Setup-Encrypt-Salt", "Pair-Setup-Encrypt-Info")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := chacha20poly1305.Decrypt(encryptKey, "PS-Msg05", []byte(cipherM5.EncryptedData))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// unpack message from TLV8
|
||||||
|
var plainM5 struct {
|
||||||
|
Identifier string `tlv8:"1"`
|
||||||
|
PublicKey string `tlv8:"3"`
|
||||||
|
Signature string `tlv8:"10"`
|
||||||
|
}
|
||||||
|
if err = tlv8.Unmarshal(b, &plainM5); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. verify client ID and Public
|
||||||
|
remoteSign, err := hkdf.Sha512(
|
||||||
|
sessionShared, "Pair-Setup-Controller-Sign-Salt", "Pair-Setup-Controller-Sign-Info",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b = Append(remoteSign, plainM5.Identifier, plainM5.PublicKey)
|
||||||
|
if !ed25519.ValidateSignature([]byte(plainM5.PublicKey), b, []byte(plainM5.Signature)) {
|
||||||
|
err = errors.New("hap: ValidateSignature")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. generate signature to our ID and Public
|
||||||
|
localSign, err := hkdf.Sha512(
|
||||||
|
sessionShared, "Pair-Setup-Accessory-Sign-Salt", "Pair-Setup-Accessory-Sign-Info",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b = Append(localSign, s.DeviceID, s.ServerPublic()) // ServerPublic
|
||||||
|
signature, err := ed25519.Signature(s.DevicePrivate, b)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. pack our ID and Public
|
||||||
|
plainM6 := struct {
|
||||||
|
Identifier string `tlv8:"1"`
|
||||||
|
PublicKey string `tlv8:"3"`
|
||||||
|
Signature string `tlv8:"10"`
|
||||||
|
}{
|
||||||
|
Identifier: s.DeviceID,
|
||||||
|
PublicKey: string(s.ServerPublic()),
|
||||||
|
Signature: string(signature),
|
||||||
|
}
|
||||||
|
if b, err = tlv8.Marshal(plainM6); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. encrypt message
|
||||||
|
b, err = chacha20poly1305.Encrypt(encryptKey, "PS-Msg06", b)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP 6. Response to iPhone
|
||||||
|
cipherM6 := struct {
|
||||||
|
State byte `tlv8:"6"`
|
||||||
|
EncryptedData string `tlv8:"5"`
|
||||||
|
}{
|
||||||
|
State: StateM6,
|
||||||
|
EncryptedData: string(b),
|
||||||
|
}
|
||||||
|
if body, err = tlv8.Marshal(cipherM6); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = WriteResponse(rw.Writer, http.StatusOK, MimeTLV8, body); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id = plainM5.Identifier
|
||||||
|
publicKey = []byte(plainM5.PublicKey)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) PairVerify(req *http.Request, rw *bufio.ReadWriter) (id string, sessionKey []byte, err error) {
|
||||||
|
// Request from iPhone
|
||||||
|
var plainM1 struct {
|
||||||
|
State byte `tlv8:"6"`
|
||||||
|
PublicKey string `tlv8:"3"`
|
||||||
|
}
|
||||||
|
if err = tlv8.UnmarshalReader(req.Body, req.ContentLength, &plainM1); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if plainM1.State != StateM1 {
|
||||||
|
err = newRequestError(plainM1)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate the key pair
|
// Generate the key pair
|
||||||
sessionPublic, sessionPrivate := curve25519.GenerateKeyPair()
|
sessionPublic, sessionPrivate := curve25519.GenerateKeyPair()
|
||||||
sessionShared, err := curve25519.SharedSecret(sessionPrivate, []byte(plainM1.PublicKey))
|
sessionShared, err := curve25519.SharedSecret(sessionPrivate, []byte(plainM1.PublicKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptKey, err := hkdf.Sha512(
|
encryptKey, err := hkdf.Sha512(
|
||||||
sessionShared, "Pair-Verify-Encrypt-Salt", "Pair-Verify-Encrypt-Info",
|
sessionShared, "Pair-Verify-Encrypt-Salt", "Pair-Verify-Encrypt-Info",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
b := Append(sessionPublic, s.DeviceID, plainM1.PublicKey)
|
b := Append(sessionPublic, s.DeviceID, plainM1.PublicKey)
|
||||||
signature, err := ed25519.Signature(s.DevicePrivate, b)
|
signature, err := ed25519.Signature(s.DevicePrivate, b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// STEP M2. Response to iPhone
|
// STEP M2. Response to iPhone
|
||||||
@@ -90,12 +288,12 @@ func (s *Server) PairVerify(req *http.Request, rw *bufio.ReadWriter, conn net.Co
|
|||||||
Signature: string(signature),
|
Signature: string(signature),
|
||||||
}
|
}
|
||||||
if b, err = tlv8.Marshal(plainM2); err != nil {
|
if b, err = tlv8.Marshal(plainM2); err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err = chacha20poly1305.Encrypt(encryptKey, "PV-Msg02", b)
|
b, err = chacha20poly1305.Encrypt(encryptKey, "PV-Msg02", b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cipherM2 := struct {
|
cipherM2 := struct {
|
||||||
@@ -109,30 +307,32 @@ func (s *Server) PairVerify(req *http.Request, rw *bufio.ReadWriter, conn net.Co
|
|||||||
}
|
}
|
||||||
body, err := tlv8.Marshal(cipherM2)
|
body, err := tlv8.Marshal(cipherM2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
if err = WriteResponse(rw.Writer, http.StatusOK, MimeTLV8, body); err != nil {
|
if err = WriteResponse(rw.Writer, http.StatusOK, MimeTLV8, body); err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// STEP M3. Request from iPhone
|
// STEP M3. Request from iPhone
|
||||||
if req, err = http.ReadRequest(rw.Reader); err != nil {
|
if req, err = http.ReadRequest(rw.Reader); err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var cipherM3 struct {
|
var cipherM3 struct {
|
||||||
EncryptedData string `tlv8:"5"`
|
|
||||||
State byte `tlv8:"6"`
|
State byte `tlv8:"6"`
|
||||||
|
EncryptedData string `tlv8:"5"`
|
||||||
}
|
}
|
||||||
if err = tlv8.UnmarshalReader(req.Body, req.ContentLength, &cipherM3); err != nil {
|
if err = tlv8.UnmarshalReader(req.Body, req.ContentLength, &cipherM3); err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
if cipherM3.State != StateM3 {
|
if cipherM3.State != StateM3 {
|
||||||
return newRequestError(cipherM3)
|
err = newRequestError(cipherM3)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if b, err = chacha20poly1305.Decrypt(encryptKey, "PV-Msg03", []byte(cipherM3.EncryptedData)); err != nil {
|
b, err = chacha20poly1305.Decrypt(encryptKey, "PV-Msg03", []byte(cipherM3.EncryptedData))
|
||||||
return err
|
if err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var plainM3 struct {
|
var plainM3 struct {
|
||||||
@@ -140,17 +340,21 @@ func (s *Server) PairVerify(req *http.Request, rw *bufio.ReadWriter, conn net.Co
|
|||||||
Signature string `tlv8:"10"`
|
Signature string `tlv8:"10"`
|
||||||
}
|
}
|
||||||
if err = tlv8.Unmarshal(b, &plainM3); err != nil {
|
if err = tlv8.Unmarshal(b, &plainM3); err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
clientPublic := s.GetPair(conn, plainM3.Identifier)
|
if s.GetClientPublic != nil {
|
||||||
if clientPublic == nil {
|
clientPublic := s.GetClientPublic(plainM3.Identifier)
|
||||||
return fmt.Errorf("hap: PairVerify from: %s, with unknown client_id: %s", conn.RemoteAddr(), plainM3.Identifier)
|
if clientPublic == nil {
|
||||||
}
|
err = errors.New("hap: PairVerify with unknown client_id: " + plainM3.Identifier)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
b = Append(plainM1.PublicKey, plainM3.Identifier, sessionPublic)
|
b = Append(plainM1.PublicKey, plainM3.Identifier, sessionPublic)
|
||||||
if !ed25519.ValidateSignature(clientPublic, b, []byte(plainM3.Signature)) {
|
if !ed25519.ValidateSignature(clientPublic, b, []byte(plainM3.Signature)) {
|
||||||
return errors.New("new: ValidateSignature")
|
err = errors.New("hap: ValidateSignature")
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// STEP M4. Response to iPhone
|
// STEP M4. Response to iPhone
|
||||||
@@ -160,15 +364,41 @@ func (s *Server) PairVerify(req *http.Request, rw *bufio.ReadWriter, conn net.Co
|
|||||||
State: StateM4,
|
State: StateM4,
|
||||||
}
|
}
|
||||||
if body, err = tlv8.Marshal(payloadM4); err != nil {
|
if body, err = tlv8.Marshal(payloadM4); err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
if err = WriteResponse(rw.Writer, http.StatusOK, MimeTLV8, body); err != nil {
|
if err = WriteResponse(rw.Writer, http.StatusOK, MimeTLV8, body); err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if conn, err = secure.Client(conn, rw, sessionShared, false); err != nil {
|
id = plainM3.Identifier
|
||||||
return err
|
sessionKey = sessionShared
|
||||||
}
|
|
||||||
|
|
||||||
return s.Handler(conn)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WriteResponse(w *bufio.Writer, statusCode int, contentType string, body []byte) error {
|
||||||
|
header := fmt.Sprintf(
|
||||||
|
"HTTP/1.1 %d %s\r\nContent-Type: %s\r\nContent-Length: %d\r\n\r\n",
|
||||||
|
statusCode, http.StatusText(statusCode), contentType, len(body),
|
||||||
|
)
|
||||||
|
body = append([]byte(header), body...)
|
||||||
|
if _, err := w.Write(body); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return w.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
//func WriteBackoff(rw *bufio.ReadWriter) error {
|
||||||
|
// plainM2 := struct {
|
||||||
|
// State byte `tlv8:"6"`
|
||||||
|
// Error byte `tlv8:"7"`
|
||||||
|
// }{
|
||||||
|
// State: StateM2,
|
||||||
|
// Error: 3, // BackoffError
|
||||||
|
// }
|
||||||
|
// body, err := tlv8.Marshal(plainM2)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// return WriteResponse(rw.Writer, http.StatusOK, MimeTLV8, body)
|
||||||
|
//}
|
||||||
|
|||||||
@@ -1,266 +0,0 @@
|
|||||||
package hap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"crypto/sha512"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/hap/chacha20poly1305"
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/hap/ed25519"
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/hap/hkdf"
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/hap/tlv8"
|
|
||||||
"github.com/tadglines/go-pkgs/crypto/srp"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PairMethodSetup = iota
|
|
||||||
PairMethodSetupWithAuth
|
|
||||||
PairMethodVerify
|
|
||||||
PairMethodAdd
|
|
||||||
PairMethodRemove
|
|
||||||
PairMethodList
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *Server) HandleConn(conn net.Conn) error {
|
|
||||||
rd := bufio.NewReader(conn)
|
|
||||||
req, err := http.ReadRequest(rd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
rw := bufio.NewReadWriter(rd, bufio.NewWriter(conn))
|
|
||||||
|
|
||||||
switch req.RequestURI {
|
|
||||||
case PathPairSetup:
|
|
||||||
return s.PairSetup(req, rw, conn)
|
|
||||||
case PathPairVerify:
|
|
||||||
return s.PairVerify(req, rw, conn)
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.New("hap: unsupported request uri: " + req.RequestURI)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) PairSetup(req *http.Request, rw *bufio.ReadWriter, conn net.Conn) error {
|
|
||||||
// STEP 1. Request from iPhone
|
|
||||||
var plainM1 struct {
|
|
||||||
Method byte `tlv8:"0"`
|
|
||||||
State byte `tlv8:"6"`
|
|
||||||
Flags uint32 `tlv8:"19"`
|
|
||||||
}
|
|
||||||
if err := tlv8.UnmarshalReader(req.Body, req.ContentLength, &plainM1); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if plainM1.State != StateM1 {
|
|
||||||
return newRequestError(plainM1)
|
|
||||||
}
|
|
||||||
|
|
||||||
username := []byte("Pair-Setup")
|
|
||||||
|
|
||||||
// Stanford Secure Remote Password (SRP) / Password Authenticated Key Exchange (PAKE)
|
|
||||||
pake, err := srp.NewSRP(
|
|
||||||
"rfc5054.3072", sha512.New, keyDerivativeFuncRFC2945(username),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pake.SaltLength = 16
|
|
||||||
|
|
||||||
salt, verifier, err := pake.ComputeVerifier([]byte(s.Pin))
|
|
||||||
|
|
||||||
session := pake.NewServerSession(username, salt, verifier)
|
|
||||||
|
|
||||||
// STEP 2. Response to iPhone
|
|
||||||
plainM2 := struct {
|
|
||||||
Salt string `tlv8:"2"`
|
|
||||||
PublicKey string `tlv8:"3"`
|
|
||||||
State byte `tlv8:"6"`
|
|
||||||
}{
|
|
||||||
State: StateM2,
|
|
||||||
PublicKey: string(session.GetB()),
|
|
||||||
Salt: string(salt),
|
|
||||||
}
|
|
||||||
body, err := tlv8.Marshal(plainM2)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = WriteResponse(rw.Writer, http.StatusOK, MimeTLV8, body); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// STEP 3. Request from iPhone
|
|
||||||
if req, err = http.ReadRequest(rw.Reader); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var plainM3 struct {
|
|
||||||
SessionKey string `tlv8:"3"`
|
|
||||||
Proof string `tlv8:"4"`
|
|
||||||
State byte `tlv8:"6"`
|
|
||||||
}
|
|
||||||
if err = tlv8.UnmarshalReader(req.Body, req.ContentLength, &plainM3); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if plainM3.State != StateM3 {
|
|
||||||
return newRequestError(plainM3)
|
|
||||||
}
|
|
||||||
|
|
||||||
// important to compute key before verify client
|
|
||||||
sessionShared, err := session.ComputeKey([]byte(plainM3.SessionKey))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !session.VerifyClientAuthenticator([]byte(plainM3.Proof)) {
|
|
||||||
return errors.New("hap: VerifyClientAuthenticator")
|
|
||||||
}
|
|
||||||
|
|
||||||
proof := session.ComputeAuthenticator([]byte(plainM3.Proof)) // server proof
|
|
||||||
|
|
||||||
// STEP 4. Response to iPhone
|
|
||||||
payloadM4 := struct {
|
|
||||||
Proof string `tlv8:"4"`
|
|
||||||
State byte `tlv8:"6"`
|
|
||||||
}{
|
|
||||||
Proof: string(proof),
|
|
||||||
State: StateM4,
|
|
||||||
}
|
|
||||||
if body, err = tlv8.Marshal(payloadM4); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = WriteResponse(rw.Writer, http.StatusOK, MimeTLV8, body); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// STEP 5. Request from iPhone
|
|
||||||
if req, err = http.ReadRequest(rw.Reader); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var cipherM5 struct {
|
|
||||||
EncryptedData string `tlv8:"5"`
|
|
||||||
State byte `tlv8:"6"`
|
|
||||||
}
|
|
||||||
if err = tlv8.UnmarshalReader(req.Body, req.ContentLength, &cipherM5); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if cipherM5.State != StateM5 {
|
|
||||||
return newRequestError(cipherM5)
|
|
||||||
}
|
|
||||||
|
|
||||||
// decrypt message using session shared
|
|
||||||
encryptKey, err := hkdf.Sha512(sessionShared, "Pair-Setup-Encrypt-Salt", "Pair-Setup-Encrypt-Info")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := chacha20poly1305.Decrypt(encryptKey, "PS-Msg05", []byte(cipherM5.EncryptedData))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// unpack message from TLV8
|
|
||||||
var plainM5 struct {
|
|
||||||
Identifier string `tlv8:"1"`
|
|
||||||
PublicKey string `tlv8:"3"`
|
|
||||||
Signature string `tlv8:"10"`
|
|
||||||
}
|
|
||||||
if err = tlv8.Unmarshal(b, &plainM5); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. verify client ID and Public
|
|
||||||
remoteSign, err := hkdf.Sha512(
|
|
||||||
sessionShared, "Pair-Setup-Controller-Sign-Salt", "Pair-Setup-Controller-Sign-Info",
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
b = Append(remoteSign, plainM5.Identifier, plainM5.PublicKey)
|
|
||||||
if !ed25519.ValidateSignature([]byte(plainM5.PublicKey), b, []byte(plainM5.Signature)) {
|
|
||||||
return errors.New("hap: ValidateSignature")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. generate signature to our ID and Public
|
|
||||||
localSign, err := hkdf.Sha512(
|
|
||||||
sessionShared, "Pair-Setup-Accessory-Sign-Salt", "Pair-Setup-Accessory-Sign-Info",
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
b = Append(localSign, s.DeviceID, s.ServerPublic()) // ServerPublic
|
|
||||||
signature, err := ed25519.Signature(s.DevicePrivate, b)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. pack our ID and Public
|
|
||||||
plainM6 := struct {
|
|
||||||
Identifier string `tlv8:"1"`
|
|
||||||
PublicKey string `tlv8:"3"`
|
|
||||||
Signature string `tlv8:"10"`
|
|
||||||
}{
|
|
||||||
Identifier: s.DeviceID,
|
|
||||||
PublicKey: string(s.ServerPublic()),
|
|
||||||
Signature: string(signature),
|
|
||||||
}
|
|
||||||
if b, err = tlv8.Marshal(plainM6); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. encrypt message
|
|
||||||
b, err = chacha20poly1305.Encrypt(encryptKey, "PS-Msg06", b)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// STEP 6. Response to iPhone
|
|
||||||
cipherM6 := struct {
|
|
||||||
EncryptedData string `tlv8:"5"`
|
|
||||||
State byte `tlv8:"6"`
|
|
||||||
}{
|
|
||||||
State: StateM6,
|
|
||||||
EncryptedData: string(b),
|
|
||||||
}
|
|
||||||
if body, err = tlv8.Marshal(cipherM6); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = WriteResponse(rw.Writer, http.StatusOK, MimeTLV8, body); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.AddPair(conn, plainM5.Identifier, []byte(plainM5.PublicKey), PermissionAdmin)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func WriteResponse(w *bufio.Writer, statusCode int, contentType string, body []byte) error {
|
|
||||||
header := fmt.Sprintf(
|
|
||||||
"HTTP/1.1 %d %s\r\nContent-Type: %s\r\nContent-Length: %d\r\n\r\n",
|
|
||||||
statusCode, http.StatusText(statusCode), contentType, len(body),
|
|
||||||
)
|
|
||||||
body = append([]byte(header), body...)
|
|
||||||
if _, err := w.Write(body); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return w.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
func WriteBackoff(rw *bufio.ReadWriter) error {
|
|
||||||
plainM2 := struct {
|
|
||||||
State byte `tlv8:"6"`
|
|
||||||
Error byte `tlv8:"7"`
|
|
||||||
}{
|
|
||||||
State: StateM2,
|
|
||||||
Error: 3, // BackoffError
|
|
||||||
}
|
|
||||||
body, err := tlv8.Marshal(plainM2)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return WriteResponse(rw.Writer, http.StatusOK, MimeTLV8, body)
|
|
||||||
}
|
|
||||||
@@ -49,7 +49,7 @@ func NewConsumer(conn net.Conn, server *srtp.Server) *Consumer {
|
|||||||
Connection: core.Connection{
|
Connection: core.Connection{
|
||||||
ID: core.NewID(),
|
ID: core.NewID(),
|
||||||
FormatName: "homekit",
|
FormatName: "homekit",
|
||||||
Protocol: "udp",
|
Protocol: "rtp",
|
||||||
RemoteAddr: conn.RemoteAddr().String(),
|
RemoteAddr: conn.RemoteAddr().String(),
|
||||||
Medias: medias,
|
Medias: medias,
|
||||||
Transport: conn,
|
Transport: conn,
|
||||||
@@ -59,6 +59,10 @@ func NewConsumer(conn net.Conn, server *srtp.Server) *Consumer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Consumer) SessionID() string {
|
||||||
|
return c.sessionID
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Consumer) SetOffer(offer *camera.SetupEndpointsRequest) {
|
func (c *Consumer) SetOffer(offer *camera.SetupEndpointsRequest) {
|
||||||
c.sessionID = offer.SessionID
|
c.sessionID = offer.SessionID
|
||||||
c.videoSession = &srtp.Session{
|
c.videoSession = &srtp.Session{
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Debug(v any) {
|
||||||
|
switch v := v.(type) {
|
||||||
|
case *http.Request:
|
||||||
|
if v == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v.ContentLength != 0 {
|
||||||
|
b, err := io.ReadAll(v.Body)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
v.Body = io.NopCloser(bytes.NewReader(b))
|
||||||
|
log.Printf("[homekit] request: %s %s\n%s", v.Method, v.RequestURI, b)
|
||||||
|
} else {
|
||||||
|
log.Printf("[homekit] request: %s %s <nobody>", v.Method, v.RequestURI)
|
||||||
|
}
|
||||||
|
case *http.Response:
|
||||||
|
if v == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v.Header.Get("Content-Type") == "image/jpeg" {
|
||||||
|
log.Printf("[homekit] response: %d <jpeg>", v.StatusCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v.ContentLength != 0 {
|
||||||
|
b, err := io.ReadAll(v.Body)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
v.Body = io.NopCloser(bytes.NewReader(b))
|
||||||
|
log.Printf("[homekit] response: %s %d\n%s", v.Proto, v.StatusCode, b)
|
||||||
|
} else {
|
||||||
|
log.Printf("[homekit] response: %s %d <nobody>", v.Proto, v.StatusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-15
@@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
@@ -34,24 +33,11 @@ type Client struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Dial(rawURL string, server *srtp.Server) (*Client, error) {
|
func Dial(rawURL string, server *srtp.Server) (*Client, error) {
|
||||||
u, err := url.Parse(rawURL)
|
conn, err := hap.Dial(rawURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
query := u.Query()
|
|
||||||
conn := &hap.Client{
|
|
||||||
DeviceAddress: u.Host,
|
|
||||||
DeviceID: query.Get("device_id"),
|
|
||||||
DevicePublic: hap.DecodeKey(query.Get("device_public")),
|
|
||||||
ClientID: query.Get("client_id"),
|
|
||||||
ClientPrivate: hap.DecodeKey(query.Get("client_private")),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = conn.Dial(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
client := &Client{
|
client := &Client{
|
||||||
Connection: core.Connection{
|
Connection: core.Connection{
|
||||||
ID: core.NewID(),
|
ID: core.NewID(),
|
||||||
|
|||||||
+30
-24
@@ -4,31 +4,30 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/hap"
|
"github.com/AlexxIT/go2rtc/pkg/hap"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/hap/camera"
|
"github.com/AlexxIT/go2rtc/pkg/hap/camera"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/hap/hds"
|
"github.com/AlexxIT/go2rtc/pkg/hap/hds"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/hap/secure"
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/hap/tlv8"
|
"github.com/AlexxIT/go2rtc/pkg/hap/tlv8"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ProxyHandler(pair ServerPair, dial func() (net.Conn, error)) hap.HandlerFunc {
|
type ServerProxy interface {
|
||||||
|
ServerPair
|
||||||
|
AddConn(conn any)
|
||||||
|
DelConn(conn any)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProxyHandler(srv ServerProxy, acc net.Conn) HandlerFunc {
|
||||||
return func(con net.Conn) error {
|
return func(con net.Conn) error {
|
||||||
defer con.Close()
|
defer con.Close()
|
||||||
|
|
||||||
acc, err := dial()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer acc.Close()
|
|
||||||
|
|
||||||
pr := &Proxy{
|
pr := &Proxy{
|
||||||
con: con.(*secure.Conn),
|
con: con.(*hap.Conn),
|
||||||
acc: acc.(*secure.Conn),
|
acc: acc.(*hap.Conn),
|
||||||
res: make(chan *http.Response),
|
res: make(chan *http.Response),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,17 +35,17 @@ func ProxyHandler(pair ServerPair, dial func() (net.Conn, error)) hap.HandlerFun
|
|||||||
go pr.handleAcc()
|
go pr.handleAcc()
|
||||||
|
|
||||||
// controller => accessory
|
// controller => accessory
|
||||||
return pr.handleCon(pair)
|
return pr.handleCon(srv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Proxy struct {
|
type Proxy struct {
|
||||||
con *secure.Conn
|
con *hap.Conn
|
||||||
acc *secure.Conn
|
acc *hap.Conn
|
||||||
res chan *http.Response
|
res chan *http.Response
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Proxy) handleCon(pair ServerPair) error {
|
func (p *Proxy) handleCon(srv ServerProxy) error {
|
||||||
var hdsCharIID uint64
|
var hdsCharIID uint64
|
||||||
|
|
||||||
rd := bufio.NewReader(p.con)
|
rd := bufio.NewReader(p.con)
|
||||||
@@ -61,7 +60,7 @@ func (p *Proxy) handleCon(pair ServerPair) error {
|
|||||||
switch {
|
switch {
|
||||||
case req.Method == "POST" && req.URL.Path == hap.PathPairings:
|
case req.Method == "POST" && req.URL.Path == hap.PathPairings:
|
||||||
var res *http.Response
|
var res *http.Response
|
||||||
if res, err = handlePairings(p.con, req, pair); err != nil {
|
if res, err = handlePairings(req, srv); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err = res.Write(p.con); err != nil {
|
if err = res.Write(p.con); err != nil {
|
||||||
@@ -117,7 +116,7 @@ func (p *Proxy) handleCon(pair ServerPair) error {
|
|||||||
hdsPort := int(hdsRes.TransportTypeSessionParameters.TCPListeningPort)
|
hdsPort := int(hdsRes.TransportTypeSessionParameters.TCPListeningPort)
|
||||||
|
|
||||||
// swtich accPort to conPort
|
// swtich accPort to conPort
|
||||||
hdsPort, err = p.listenHDS(hdsPort, hdsConSalt+hdsAccSalt)
|
hdsPort, err = p.listenHDS(srv, hdsPort, hdsConSalt+hdsAccSalt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -166,7 +165,8 @@ func (p *Proxy) handleAcc() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Proxy) listenHDS(accPort int, salt string) (int, error) {
|
func (p *Proxy) listenHDS(srv ServerProxy, accPort int, salt string) (int, error) {
|
||||||
|
// The TCP port range for HDS must be >= 32768.
|
||||||
ln, err := net.ListenTCP("tcp", nil)
|
ln, err := net.ListenTCP("tcp", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
@@ -175,30 +175,36 @@ func (p *Proxy) listenHDS(accPort int, salt string) (int, error) {
|
|||||||
go func() {
|
go func() {
|
||||||
defer ln.Close()
|
defer ln.Close()
|
||||||
|
|
||||||
|
_ = ln.SetDeadline(time.Now().Add(30 * time.Second))
|
||||||
|
|
||||||
// raw controller conn
|
// raw controller conn
|
||||||
con, err := ln.Accept()
|
conn1, err := ln.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer con.Close()
|
|
||||||
|
defer conn1.Close()
|
||||||
|
|
||||||
// secured controller conn (controlle=false because we are accessory)
|
// secured controller conn (controlle=false because we are accessory)
|
||||||
con, err = hds.Client(con, p.con.SharedKey, salt, false)
|
con, err := hds.NewConn(conn1, p.con.SharedKey, salt, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
srv.AddConn(con)
|
||||||
|
defer srv.DelConn(con)
|
||||||
|
|
||||||
accIP := p.acc.RemoteAddr().(*net.TCPAddr).IP
|
accIP := p.acc.RemoteAddr().(*net.TCPAddr).IP
|
||||||
|
|
||||||
// raw accessory conn
|
// raw accessory conn
|
||||||
acc, err := net.Dial("tcp", fmt.Sprintf("%s:%d", accIP, accPort))
|
conn2, err := net.DialTCP("tcp", nil, &net.TCPAddr{IP: accIP, Port: accPort})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer acc.Close()
|
defer conn2.Close()
|
||||||
|
|
||||||
// secured accessory conn (controller=true because we are controller)
|
// secured accessory conn (controller=true because we are controller)
|
||||||
acc, err = hds.Client(acc, p.acc.SharedKey, salt, true)
|
acc, err := hds.NewConn(conn2, p.acc.SharedKey, salt, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-46
@@ -15,15 +15,17 @@ import (
|
|||||||
"github.com/AlexxIT/go2rtc/pkg/hap/tlv8"
|
"github.com/AlexxIT/go2rtc/pkg/hap/tlv8"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type HandlerFunc func(net.Conn) error
|
||||||
|
|
||||||
type Server interface {
|
type Server interface {
|
||||||
ServerPair
|
ServerPair
|
||||||
ServerAccessory
|
ServerAccessory
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServerPair interface {
|
type ServerPair interface {
|
||||||
GetPair(conn net.Conn, id string) []byte
|
GetPair(id string) []byte
|
||||||
AddPair(conn net.Conn, id string, public []byte, permissions byte)
|
AddPair(id string, public []byte, permissions byte)
|
||||||
DelPair(conn net.Conn, id string)
|
DelPair(id string)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServerAccessory interface {
|
type ServerAccessory interface {
|
||||||
@@ -33,11 +35,11 @@ type ServerAccessory interface {
|
|||||||
GetImage(conn net.Conn, width, height int) []byte
|
GetImage(conn net.Conn, width, height int) []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func ServerHandler(server Server) hap.HandlerFunc {
|
func ServerHandler(server Server) HandlerFunc {
|
||||||
return handleRequest(func(conn net.Conn, req *http.Request) (*http.Response, error) {
|
return handleRequest(func(conn net.Conn, req *http.Request) (*http.Response, error) {
|
||||||
switch req.URL.Path {
|
switch req.URL.Path {
|
||||||
case hap.PathPairings:
|
case hap.PathPairings:
|
||||||
return handlePairings(conn, req, server)
|
return handlePairings(req, server)
|
||||||
|
|
||||||
case hap.PathAccessories:
|
case hap.PathAccessories:
|
||||||
body := hap.JSONAccessories{Value: server.GetAccessories(conn)}
|
body := hap.JSONAccessories{Value: server.GetAccessories(conn)}
|
||||||
@@ -103,7 +105,7 @@ func ServerHandler(server Server) hap.HandlerFunc {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleRequest(handle func(conn net.Conn, req *http.Request) (*http.Response, error)) hap.HandlerFunc {
|
func handleRequest(handle func(conn net.Conn, req *http.Request) (*http.Response, error)) HandlerFunc {
|
||||||
return func(conn net.Conn) error {
|
return func(conn net.Conn) error {
|
||||||
rw := bufio.NewReaderSize(conn, 16*1024)
|
rw := bufio.NewReaderSize(conn, 16*1024)
|
||||||
wr := bufio.NewWriterSize(conn, 16*1024)
|
wr := bufio.NewWriterSize(conn, 16*1024)
|
||||||
@@ -130,7 +132,7 @@ func handleRequest(handle func(conn net.Conn, req *http.Request) (*http.Response
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handlePairings(conn net.Conn, req *http.Request, pair ServerPair) (*http.Response, error) {
|
func handlePairings(req *http.Request, srv ServerPair) (*http.Response, error) {
|
||||||
cmd := struct {
|
cmd := struct {
|
||||||
Method byte `tlv8:"0"`
|
Method byte `tlv8:"0"`
|
||||||
Identifier string `tlv8:"1"`
|
Identifier string `tlv8:"1"`
|
||||||
@@ -145,9 +147,9 @@ func handlePairings(conn net.Conn, req *http.Request, pair ServerPair) (*http.Re
|
|||||||
|
|
||||||
switch cmd.Method {
|
switch cmd.Method {
|
||||||
case 3: // add
|
case 3: // add
|
||||||
pair.AddPair(conn, cmd.Identifier, []byte(cmd.PublicKey), cmd.Permissions)
|
srv.AddPair(cmd.Identifier, []byte(cmd.PublicKey), cmd.Permissions)
|
||||||
case 4: // delete
|
case 4: // delete
|
||||||
pair.DelPair(conn, cmd.Identifier)
|
srv.DelPair(cmd.Identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
body := struct {
|
body := struct {
|
||||||
@@ -190,40 +192,3 @@ func makeResponse(mime string, v any) (*http.Response, error) {
|
|||||||
}
|
}
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//func debug(v any) {
|
|
||||||
// switch v := v.(type) {
|
|
||||||
// case *http.Request:
|
|
||||||
// if v == nil {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// if v.ContentLength != 0 {
|
|
||||||
// b, err := io.ReadAll(v.Body)
|
|
||||||
// if err != nil {
|
|
||||||
// panic(err)
|
|
||||||
// }
|
|
||||||
// v.Body = io.NopCloser(bytes.NewReader(b))
|
|
||||||
// log.Printf("[homekit] request: %s %s\n%s", v.Method, v.RequestURI, b)
|
|
||||||
// } else {
|
|
||||||
// log.Printf("[homekit] request: %s %s <nobody>", v.Method, v.RequestURI)
|
|
||||||
// }
|
|
||||||
// case *http.Response:
|
|
||||||
// if v == nil {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// if v.Header.Get("Content-Type") == "image/jpeg" {
|
|
||||||
// log.Printf("[homekit] response: %d <jpeg>", v.StatusCode)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// if v.ContentLength != 0 {
|
|
||||||
// b, err := io.ReadAll(v.Body)
|
|
||||||
// if err != nil {
|
|
||||||
// panic(err)
|
|
||||||
// }
|
|
||||||
// v.Body = io.NopCloser(bytes.NewReader(b))
|
|
||||||
// log.Printf("[homekit] response: %d\n%s", v.StatusCode, b)
|
|
||||||
// } else {
|
|
||||||
// log.Printf("[homekit] response: %d <nobody>", v.StatusCode)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|||||||
+7
-8
@@ -100,7 +100,7 @@
|
|||||||
<div class="module">
|
<div class="module">
|
||||||
<form id="homekit-pair" style="margin-bottom: 10px">
|
<form id="homekit-pair" style="margin-bottom: 10px">
|
||||||
<input type="text" name="id" placeholder="stream id" size="20">
|
<input type="text" name="id" placeholder="stream id" size="20">
|
||||||
<input type="text" name="url" placeholder="url" size="40">
|
<input type="text" name="src" placeholder="src" size="40">
|
||||||
<input type="text" name="pin" placeholder="pin" size="10">
|
<input type="text" name="pin" placeholder="pin" size="10">
|
||||||
<input type="submit" value="Pair">
|
<input type="submit" value="Pair">
|
||||||
</form>
|
</form>
|
||||||
@@ -112,7 +112,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
async function reloadHomeKit() {
|
async function reloadHomeKit() {
|
||||||
await getSources('homekit-table', 'api/homekit');
|
await getSources('homekit-table', 'api/discovery/homekit');
|
||||||
|
|
||||||
const rows = document.querySelectorAll('#homekit-table tr');
|
const rows = document.querySelectorAll('#homekit-table tr');
|
||||||
rows.forEach((row, i) => {
|
rows.forEach((row, i) => {
|
||||||
@@ -147,11 +147,8 @@
|
|||||||
document.getElementById('homekit-pair').addEventListener('submit', async ev => {
|
document.getElementById('homekit-pair').addEventListener('submit', async ev => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
||||||
const body = new FormData(ev.target);
|
const params = new URLSearchParams(new FormData(ev.target));
|
||||||
body.set('url', body.get('url') + '&pin=' + body.get('pin'));
|
const r = await fetch('api/homekit', {method: 'POST', body: params});
|
||||||
body.delete('pin');
|
|
||||||
|
|
||||||
const r = await fetch('api/homekit', {method: 'POST', body: body});
|
|
||||||
alert(r.ok ? 'OK' : 'ERROR: ' + await r.text());
|
alert(r.ok ? 'OK' : 'ERROR: ' + await r.text());
|
||||||
|
|
||||||
await reloadHomeKit();
|
await reloadHomeKit();
|
||||||
@@ -159,7 +156,9 @@
|
|||||||
|
|
||||||
document.getElementById('homekit-unpair').addEventListener('submit', async ev => {
|
document.getElementById('homekit-unpair').addEventListener('submit', async ev => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
const r = await fetch('api/homekit', {method: 'DELETE', body: new FormData(ev.target)});
|
|
||||||
|
const params = new URLSearchParams(new FormData(ev.target));
|
||||||
|
const r = await fetch('api/homekit?' + params.toString(), {method: 'DELETE'});
|
||||||
alert(r.ok ? 'OK' : 'ERROR: ' + await r.text());
|
alert(r.ok ? 'OK' : 'ERROR: ' + await r.text());
|
||||||
|
|
||||||
await reloadHomeKit();
|
await reloadHomeKit();
|
||||||
|
|||||||
Reference in New Issue
Block a user