Files
go2rtc/pkg/hap/conn.go
T
2025-11-09 21:58:44 +03:00

174 lines
3.2 KiB
Go

package hap
import (
"bufio"
"encoding/binary"
"encoding/json"
"errors"
"io"
"net"
"sync"
"time"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/hap/chacha20poly1305"
"github.com/AlexxIT/go2rtc/pkg/hap/hkdf"
)
type Conn struct {
conn net.Conn
rw *bufio.ReadWriter
wmu sync.Mutex
encryptKey []byte
decryptKey []byte
encryptCnt uint64
decryptCnt uint64
//ClientID string
SharedKey []byte
recv int
send int
}
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")
if err != nil {
return nil, err
}
key2, err := hkdf.Sha512(sharedKey, "Control-Salt", "Control-Write-Encryption-Key")
if err != nil {
return nil, err
}
c := &Conn{
conn: conn,
rw: rw,
SharedKey: sharedKey,
}
if isClient {
c.encryptKey, c.decryptKey = key2, key1
} else {
c.encryptKey, c.decryptKey = key1, key2
}
return c, nil
}
const (
// packetSizeMax is the max length of encrypted packets
packetSizeMax = 0x400
VerifySize = 2
NonceSize = 8
Overhead = 16 // chacha20poly1305.Overhead
)
func (c *Conn) Read(b []byte) (n int, err error) {
if cap(b) < packetSizeMax {
return 0, errors.New("hap: read buffer is too small")
}
verify := make([]byte, VerifySize) // verify = plain message size
if _, err = io.ReadFull(c.rw, verify); err != nil {
return
}
n = int(binary.LittleEndian.Uint16(verify))
ciphertext := make([]byte, n+Overhead)
if _, err = io.ReadFull(c.rw, ciphertext); err != nil {
return
}
nonce := make([]byte, NonceSize)
binary.LittleEndian.PutUint64(nonce, c.decryptCnt)
c.decryptCnt++
_, err = chacha20poly1305.DecryptAndVerify(c.decryptKey, b[:0], nonce, ciphertext, verify)
c.recv += n
return
}
func (c *Conn) Write(b []byte) (n int, err error) {
c.wmu.Lock()
defer c.wmu.Unlock()
buf := make([]byte, 0, packetSizeMax+Overhead)
nonce := make([]byte, NonceSize)
verify := make([]byte, VerifySize)
for len(b) > 0 {
size := len(b)
if size > packetSizeMax {
size = packetSizeMax
}
binary.LittleEndian.PutUint16(verify, uint16(size))
if _, err = c.rw.Write(verify); err != nil {
return
}
binary.LittleEndian.PutUint64(nonce, c.encryptCnt)
c.encryptCnt++
_, err = chacha20poly1305.EncryptAndSeal(c.encryptKey, buf, nonce, b[:size], verify)
if err != nil {
return
}
if _, err = c.rw.Write(buf[:size+Overhead]); err != nil {
return
}
b = b[size:]
n += size
}
err = c.rw.Flush()
c.send += n
return
}
func (c *Conn) Close() error {
return c.conn.Close()
}
func (c *Conn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *Conn) RemoteAddr() net.Addr {
return c.conn.RemoteAddr()
}
func (c *Conn) SetDeadline(t time.Time) error {
return c.conn.SetDeadline(t)
}
func (c *Conn) SetReadDeadline(t time.Time) error {
return c.conn.SetReadDeadline(t)
}
func (c *Conn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
}