From 994e0dc526ab9cddd3685805df39c42143ae027c Mon Sep 17 00:00:00 2001 From: Alex X Date: Tue, 21 Oct 2025 12:26:24 +0300 Subject: [PATCH] Improve homekit tlv8 parsing --- pkg/hap/client.go | 4 ++-- pkg/hap/client_pairing.go | 12 ++++++------ pkg/hap/server.go | 5 ++--- pkg/hap/server_pairing.go | 28 +++++++++++++++++++++------- pkg/hap/tlv8/tlv8.go | 13 +++++++++++-- pkg/homekit/server.go | 2 +- 6 files changed, 43 insertions(+), 21 deletions(-) diff --git a/pkg/hap/client.go b/pkg/hap/client.go index 2c1f7dd3..2801dd9f 100644 --- a/pkg/hap/client.go +++ b/pkg/hap/client.go @@ -124,7 +124,7 @@ func (c *Client) Dial() (err error) { EncryptedData string `tlv8:"5"` State byte `tlv8:"6"` } - if err = tlv8.UnmarshalReader(res.Body, &cipherM2); err != nil { + if err = tlv8.UnmarshalReader(res.Body, res.ContentLength, &cipherM2); err != nil { return err } if cipherM2.State != StateM2 { @@ -209,7 +209,7 @@ func (c *Client) Dial() (err error) { var plainM4 struct { State byte `tlv8:"6"` } - if err = tlv8.UnmarshalReader(res.Body, &plainM4); err != nil { + if err = tlv8.UnmarshalReader(res.Body, res.ContentLength, &plainM4); err != nil { return } if plainM4.State != StateM4 { diff --git a/pkg/hap/client_pairing.go b/pkg/hap/client_pairing.go index baec7be5..a58526d9 100644 --- a/pkg/hap/client_pairing.go +++ b/pkg/hap/client_pairing.go @@ -107,7 +107,7 @@ func (c *Client) Pair(feature, pin string) (err error) { State byte `tlv8:"6"` Error byte `tlv8:"7"` } - if err = tlv8.UnmarshalReader(res.Body, &plainM2); err != nil { + if err = tlv8.UnmarshalReader(res.Body, res.ContentLength, &plainM2); err != nil { return } if plainM2.State != StateM2 { @@ -159,7 +159,7 @@ func (c *Client) Pair(feature, pin string) (err error) { EncryptedData string `tlv8:"5"` // skip EncryptedData validation (for MFi devices) } - if err = tlv8.UnmarshalReader(res.Body, &plainM4); err != nil { + if err = tlv8.UnmarshalReader(res.Body, res.ContentLength, &plainM4); err != nil { return } if plainM4.State != StateM4 { @@ -232,7 +232,7 @@ func (c *Client) Pair(feature, pin string) (err error) { State byte `tlv8:"6"` Error byte `tlv8:"7"` }{} - if err = tlv8.UnmarshalReader(res.Body, &cipherM6); err != nil { + if err = tlv8.UnmarshalReader(res.Body, res.ContentLength, &cipherM6); err != nil { return } if cipherM6.State != StateM6 || cipherM6.Error != 0 { @@ -296,7 +296,7 @@ func (c *Client) ListPairings() error { State byte `tlv8:"6"` Permission byte `tlv8:"11"` } - if err = tlv8.UnmarshalReader(res.Body, &plainM2); err != nil { + if err = tlv8.UnmarshalReader(res.Body, res.ContentLength, &plainM2); err != nil { return err } @@ -329,7 +329,7 @@ func (c *Client) PairingsAdd(clientID string, clientPublic []byte, admin bool) e State byte `tlv8:"6"` Unknown byte `tlv8:"7"` } - if err = tlv8.UnmarshalReader(res.Body, &plainM2); err != nil { + if err = tlv8.UnmarshalReader(res.Body, res.ContentLength, &plainM2); err != nil { return err } @@ -354,7 +354,7 @@ func (c *Client) DeletePairing(id string) error { var plainM2 struct { State byte `tlv8:"6"` } - if err = tlv8.UnmarshalReader(res.Body, &plainM2); err != nil { + if err = tlv8.UnmarshalReader(res.Body, res.ContentLength, &plainM2); err != nil { return err } if plainM2.State != StateM2 { diff --git a/pkg/hap/server.go b/pkg/hap/server.go index 2a912324..a71ab7aa 100644 --- a/pkg/hap/server.go +++ b/pkg/hap/server.go @@ -6,7 +6,6 @@ import ( "encoding/base64" "errors" "fmt" - "io" "net" "net/http" @@ -55,7 +54,7 @@ func (s *Server) PairVerify(req *http.Request, rw *bufio.ReadWriter, conn net.Co PublicKey string `tlv8:"3"` State byte `tlv8:"6"` } - if err := tlv8.UnmarshalReader(io.LimitReader(rw, req.ContentLength), &plainM1); err != nil { + if err := tlv8.UnmarshalReader(req.Body, req.ContentLength, &plainM1); err != nil { return err } if plainM1.State != StateM1 { @@ -125,7 +124,7 @@ func (s *Server) PairVerify(req *http.Request, rw *bufio.ReadWriter, conn net.Co EncryptedData string `tlv8:"5"` State byte `tlv8:"6"` } - if err = tlv8.UnmarshalReader(req.Body, &cipherM3); err != nil { + if err = tlv8.UnmarshalReader(req.Body, req.ContentLength, &cipherM3); err != nil { return err } if cipherM3.State != StateM3 { diff --git a/pkg/hap/server_pairing.go b/pkg/hap/server_pairing.go index 77895c10..571ba7a2 100644 --- a/pkg/hap/server_pairing.go +++ b/pkg/hap/server_pairing.go @@ -5,7 +5,6 @@ import ( "crypto/sha512" "errors" "fmt" - "io" "net" "net/http" @@ -25,18 +24,33 @@ const ( PairMethodList ) -func (s *Server) PairSetup(req *http.Request, rw *bufio.ReadWriter, conn net.Conn) error { - if req.Header.Get("Content-Type") != MimeTLV8 { - return errors.New("hap: wrong content type") +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(io.LimitReader(rw, req.ContentLength), &plainM1); err != nil { + if err := tlv8.UnmarshalReader(req.Body, req.ContentLength, &plainM1); err != nil { return err } if plainM1.State != StateM1 { @@ -87,7 +101,7 @@ func (s *Server) PairSetup(req *http.Request, rw *bufio.ReadWriter, conn net.Con Proof string `tlv8:"4"` State byte `tlv8:"6"` } - if err = tlv8.UnmarshalReader(req.Body, &plainM3); err != nil { + if err = tlv8.UnmarshalReader(req.Body, req.ContentLength, &plainM3); err != nil { return err } if plainM3.State != StateM3 { @@ -129,7 +143,7 @@ func (s *Server) PairSetup(req *http.Request, rw *bufio.ReadWriter, conn net.Con EncryptedData string `tlv8:"5"` State byte `tlv8:"6"` } - if err = tlv8.UnmarshalReader(req.Body, &cipherM5); err != nil { + if err = tlv8.UnmarshalReader(req.Body, req.ContentLength, &cipherM5); err != nil { return err } if cipherM5.State != StateM5 { diff --git a/pkg/hap/tlv8/tlv8.go b/pkg/hap/tlv8/tlv8.go index 7af27ea4..6efe20a6 100644 --- a/pkg/hap/tlv8/tlv8.go +++ b/pkg/hap/tlv8/tlv8.go @@ -170,11 +170,20 @@ func UnmarshalBase64(in any, out any) error { return Unmarshal(data, out) } -func UnmarshalReader(r io.Reader, v any) error { - data, err := io.ReadAll(r) +func UnmarshalReader(r io.Reader, n int64, v any) error { + var data []byte + var err error + + if n > 0 { + data = make([]byte, n) + _, err = io.ReadFull(r, data) + } else { + data, err = io.ReadAll(r) + } if err != nil { return err } + return Unmarshal(data, v) } diff --git a/pkg/homekit/server.go b/pkg/homekit/server.go index 20cfc59d..2d00deab 100644 --- a/pkg/homekit/server.go +++ b/pkg/homekit/server.go @@ -139,7 +139,7 @@ func handlePairings(conn net.Conn, req *http.Request, pair ServerPair) (*http.Re Permissions byte `tlv8:"11"` }{} - if err := tlv8.UnmarshalReader(req.Body, &cmd); err != nil { + if err := tlv8.UnmarshalReader(req.Body, req.ContentLength, &cmd); err != nil { return nil, err }