Merge branch 'master' of https://github.com/AlexxIT/go2rtc into tuya-new

This commit is contained in:
seydx
2025-11-19 00:09:34 +01:00
64 changed files with 2211 additions and 1714 deletions
+1 -1
View File
@@ -34,7 +34,7 @@ func (m *Muxer) GetInit() []byte {
switch codec.Name {
case core.CodecH264:
b[4] |= FlagsVideo
obj["videocodecid"] = CodecAVC
obj["videocodecid"] = CodecH264
case core.CodecAAC:
b[4] |= FlagsAudio
+11 -6
View File
@@ -44,7 +44,9 @@ const (
TagData = 18
CodecAAC = 10
CodecAVC = 7
CodecH264 = 7
CodecHEVC = 12
)
const (
@@ -207,15 +209,18 @@ func (c *Producer) probe() error {
} else {
_ = pkt.Payload[0] >> 4 // FrameType
if codecID := pkt.Payload[0] & 0b1111; codecID != CodecAVC {
continue
}
if packetType := pkt.Payload[1]; packetType != PacketTypeAVCHeader { // check if header
continue
}
codec = h264.ConfigToCodec(pkt.Payload[5:])
switch codecID := pkt.Payload[0] & 0b1111; codecID {
case CodecH264:
codec = h264.ConfigToCodec(pkt.Payload[5:])
case CodecHEVC:
codec = h265.ConfigToCodec(pkt.Payload[5:])
default:
continue
}
}
media := &core.Media{
+3
View File
@@ -0,0 +1,3 @@
## Useful links
- https://github.com/bauer-andreas/secure-video-specification
+13 -13
View File
@@ -49,17 +49,17 @@ func ServiceCameraRTPStreamManagement() *hap.Service {
val120, _ := tlv8.MarshalBase64(StreamingStatus{
Status: StreamingStatusAvailable,
})
val114, _ := tlv8.MarshalBase64(SupportedVideoStreamConfig{
Codecs: []VideoCodec{
val114, _ := tlv8.MarshalBase64(SupportedVideoStreamConfiguration{
Codecs: []VideoCodecConfiguration{
{
CodecType: VideoCodecTypeH264,
CodecParams: []VideoParams{
CodecParams: []VideoCodecParameters{
{
ProfileID: []byte{VideoCodecProfileMain},
Level: []byte{VideoCodecLevel31, VideoCodecLevel40},
},
},
VideoAttrs: []VideoAttrs{
VideoAttrs: []VideoCodecAttributes{
{Width: 1920, Height: 1080, Framerate: 30},
{Width: 1280, Height: 720, Framerate: 30}, // important for iPhones
{Width: 320, Height: 240, Framerate: 15}, // apple watch
@@ -67,23 +67,23 @@ func ServiceCameraRTPStreamManagement() *hap.Service {
},
},
})
val115, _ := tlv8.MarshalBase64(SupportedAudioStreamConfig{
Codecs: []AudioCodec{
val115, _ := tlv8.MarshalBase64(SupportedAudioStreamConfiguration{
Codecs: []AudioCodecConfiguration{
{
CodecType: AudioCodecTypeOpus,
CodecParams: []AudioParams{
CodecParams: []AudioCodecParameters{
{
Channels: 1,
Bitrate: AudioCodecBitrateVariable,
SampleRate: []byte{AudioCodecSampleRate16Khz},
Channels: 1,
BitrateMode: AudioCodecBitrateVariable,
SampleRate: []byte{AudioCodecSampleRate16Khz},
},
},
},
},
ComfortNoise: 0,
ComfortNoiseSupport: 0,
})
val116, _ := tlv8.MarshalBase64(SupportedRTPConfig{
CryptoType: []byte{CryptoAES_CM_128_HMAC_SHA1_80},
val116, _ := tlv8.MarshalBase64(SupportedRTPConfiguration{
SRTPCryptoType: []byte{CryptoAES_CM_128_HMAC_SHA1_80},
})
service := &hap.Service{
+39 -39
View File
@@ -63,19 +63,19 @@ func TestAqaraG3(t *testing.T) {
{
name: "114",
value: "AaoBAQACEQEBAQIBAAAAAgECAwEABAEAAwsBAoAHAgI4BAMBHgAAAwsBAgAFAgLQAgMBHgAAAwsBAoACAgJoAQMBHgAAAwsBAuABAgIOAQMBHgAAAwsBAkABAgK0AAMBHgAAAwsBAgAFAgLAAwMBHgAAAwsBAgAEAgIAAwMBHgAAAwsBAoACAgLgAQMBHgAAAwsBAuABAgJoAQMBHgAAAwsBAkABAgLwAAMBHg==",
actual: &SupportedVideoStreamConfig{},
expect: &SupportedVideoStreamConfig{
Codecs: []VideoCodec{
actual: &SupportedVideoStreamConfiguration{},
expect: &SupportedVideoStreamConfiguration{
Codecs: []VideoCodecConfiguration{
{
CodecType: VideoCodecTypeH264,
CodecParams: []VideoParams{
CodecParams: []VideoCodecParameters{
{
ProfileID: []byte{VideoCodecProfileMain},
Level: []byte{VideoCodecLevel31, VideoCodecLevel40},
CVOEnabled: []byte{0},
},
},
VideoAttrs: []VideoAttrs{
VideoAttrs: []VideoCodecAttributes{
{Width: 1920, Height: 1080, Framerate: 30},
{Width: 1280, Height: 720, Framerate: 30},
{Width: 640, Height: 360, Framerate: 30},
@@ -94,29 +94,29 @@ func TestAqaraG3(t *testing.T) {
{
name: "115",
value: "AQ4BAQICCQEBAQIBAAMBAQIBAA==",
actual: &SupportedAudioStreamConfig{},
expect: &SupportedAudioStreamConfig{
Codecs: []AudioCodec{
actual: &SupportedAudioStreamConfiguration{},
expect: &SupportedAudioStreamConfiguration{
Codecs: []AudioCodecConfiguration{
{
CodecType: AudioCodecTypeAACELD,
CodecParams: []AudioParams{
CodecParams: []AudioCodecParameters{
{
Channels: 1,
Bitrate: AudioCodecBitrateVariable,
SampleRate: []byte{AudioCodecSampleRate16Khz},
Channels: 1,
BitrateMode: AudioCodecBitrateVariable,
SampleRate: []byte{AudioCodecSampleRate16Khz},
},
},
},
},
ComfortNoise: 0,
ComfortNoiseSupport: 0,
},
},
{
name: "116",
value: "AgEAAAACAQEAAAIBAg==",
actual: &SupportedRTPConfig{},
expect: &SupportedRTPConfig{
CryptoType: []byte{CryptoAES_CM_128_HMAC_SHA1_80, CryptoAES_CM_256_HMAC_SHA1_80, CryptoNone},
actual: &SupportedRTPConfiguration{},
expect: &SupportedRTPConfiguration{
SRTPCryptoType: []byte{CryptoAES_CM_128_HMAC_SHA1_80, CryptoAES_CM_256_HMAC_SHA1_80, CryptoDisabled},
},
},
}
@@ -130,18 +130,18 @@ func TestHomebridge(t *testing.T) {
{
name: "114",
value: "AcUBAQACHQEBAAAAAQEBAAABAQICAQAAAAIBAQAAAgECAwEAAwsBAkABAgK0AAMBHgAAAwsBAkABAgLwAAMBDwAAAwsBAkABAgLwAAMBHgAAAwsBAuABAgIOAQMBHgAAAwsBAuABAgJoAQMBHgAAAwsBAoACAgJoAQMBHgAAAwsBAoACAgLgAQMBHgAAAwsBAgAFAgLQAgMBHgAAAwsBAgAFAgLAAwMBHgAAAwsBAoAHAgI4BAMBHgAAAwsBAkAGAgKwBAMBHg==",
actual: &SupportedVideoStreamConfig{},
expect: &SupportedVideoStreamConfig{
Codecs: []VideoCodec{
actual: &SupportedVideoStreamConfiguration{},
expect: &SupportedVideoStreamConfiguration{
Codecs: []VideoCodecConfiguration{
{
CodecType: VideoCodecTypeH264,
CodecParams: []VideoParams{
CodecParams: []VideoCodecParameters{
{
ProfileID: []byte{VideoCodecProfileConstrainedBaseline, VideoCodecProfileMain, VideoCodecProfileHigh},
Level: []byte{VideoCodecLevel31, VideoCodecLevel32, VideoCodecLevel40},
},
},
VideoAttrs: []VideoAttrs{
VideoAttrs: []VideoCodecAttributes{
{Width: 320, Height: 180, Framerate: 30},
{Width: 320, Height: 240, Framerate: 15},
@@ -162,9 +162,9 @@ func TestHomebridge(t *testing.T) {
{
name: "116",
value: "AgEA",
actual: &SupportedRTPConfig{},
expect: &SupportedRTPConfig{
CryptoType: []byte{CryptoAES_CM_128_HMAC_SHA1_80},
actual: &SupportedRTPConfiguration{},
expect: &SupportedRTPConfiguration{
SRTPCryptoType: []byte{CryptoAES_CM_128_HMAC_SHA1_80},
},
},
}
@@ -178,18 +178,18 @@ func TestScrypted(t *testing.T) {
{
name: "114",
value: "AVIBAQACEwEBAQIBAAAAAgEBAAACAQIDAQADCwECAA8CAnAIAwEeAAADCwECgAcCAjgEAwEeAAADCwECAAUCAtACAwEeAAADCwECQAECAvAAAwEP",
actual: &SupportedVideoStreamConfig{},
expect: &SupportedVideoStreamConfig{
Codecs: []VideoCodec{
actual: &SupportedVideoStreamConfiguration{},
expect: &SupportedVideoStreamConfiguration{
Codecs: []VideoCodecConfiguration{
{
CodecType: VideoCodecTypeH264,
CodecParams: []VideoParams{
CodecParams: []VideoCodecParameters{
{
ProfileID: []byte{VideoCodecProfileMain},
Level: []byte{VideoCodecLevel31, VideoCodecLevel32, VideoCodecLevel40},
},
},
VideoAttrs: []VideoAttrs{
VideoAttrs: []VideoCodecAttributes{
{Width: 3840, Height: 2160, Framerate: 30},
{Width: 1920, Height: 1080, Framerate: 30},
{Width: 1280, Height: 720, Framerate: 30},
@@ -202,15 +202,15 @@ func TestScrypted(t *testing.T) {
{
name: "115",
value: "AScBAQMCIgEBAQIBAAMBAAAAAwEAAAADAQEAAAMBAQAAAwECAAADAQICAQA=",
actual: &SupportedAudioStreamConfig{},
expect: &SupportedAudioStreamConfig{
Codecs: []AudioCodec{
actual: &SupportedAudioStreamConfiguration{},
expect: &SupportedAudioStreamConfiguration{
Codecs: []AudioCodecConfiguration{
{
CodecType: AudioCodecTypeOpus,
CodecParams: []AudioParams{
CodecParams: []AudioCodecParameters{
{
Channels: 1,
Bitrate: AudioCodecBitrateVariable,
Channels: 1,
BitrateMode: AudioCodecBitrateVariable,
SampleRate: []byte{
AudioCodecSampleRate8Khz, AudioCodecSampleRate8Khz,
AudioCodecSampleRate16Khz, AudioCodecSampleRate16Khz,
@@ -220,15 +220,15 @@ func TestScrypted(t *testing.T) {
},
},
},
ComfortNoise: 0,
ComfortNoiseSupport: 0,
},
},
{
name: "116",
value: "AgEAAAACAQI=",
actual: &SupportedRTPConfig{},
expect: &SupportedRTPConfig{
CryptoType: []byte{CryptoAES_CM_128_HMAC_SHA1_80, CryptoNone},
actual: &SupportedRTPConfiguration{},
expect: &SupportedRTPConfiguration{
SRTPCryptoType: []byte{CryptoAES_CM_128_HMAC_SHA1_80, CryptoDisabled},
},
},
}
+10 -10
View File
@@ -2,15 +2,15 @@ package camera
const TypeSupportedVideoStreamConfiguration = "114"
type SupportedVideoStreamConfig struct {
Codecs []VideoCodec `tlv8:"1"`
type SupportedVideoStreamConfiguration struct {
Codecs []VideoCodecConfiguration `tlv8:"1"`
}
type VideoCodec struct {
CodecType byte `tlv8:"1"`
CodecParams []VideoParams `tlv8:"2"`
VideoAttrs []VideoAttrs `tlv8:"3"`
RTPParams []RTPParams `tlv8:"4"`
type VideoCodecConfiguration struct {
CodecType byte `tlv8:"1"`
CodecParams []VideoCodecParameters `tlv8:"2"`
VideoAttrs []VideoCodecAttributes `tlv8:"3"`
RTPParams []RTPParams `tlv8:"4"`
}
//goland:noinspection ALL
@@ -31,15 +31,15 @@ const (
VideoCodecCvoSuppported = 1
)
type VideoParams struct {
type VideoCodecParameters struct {
ProfileID []byte `tlv8:"1"` // 0 - baseline, 1 - main, 2 - high
Level []byte `tlv8:"2"` // 0 - 3.1, 1 - 3.2, 2 - 4.0
PacketizationMode byte `tlv8:"3"` // only 0 - non interleaved
CVOEnabled []byte `tlv8:"4"` // 0 - not supported, 1 - supported
CVOID []byte `tlv8:"5"` // ???
CVOID []byte `tlv8:"5"` // ID for CVO RTP extensio
}
type VideoAttrs struct {
type VideoCodecAttributes struct {
Width uint16 `tlv8:"1"`
Height uint16 `tlv8:"2"`
Framerate uint8 `tlv8:"3"`
+13 -13
View File
@@ -2,9 +2,9 @@ package camera
const TypeSupportedAudioStreamConfiguration = "115"
type SupportedAudioStreamConfig struct {
Codecs []AudioCodec `tlv8:"1"`
ComfortNoise byte `tlv8:"2"`
type SupportedAudioStreamConfiguration struct {
Codecs []AudioCodecConfiguration `tlv8:"1"`
ComfortNoiseSupport byte `tlv8:"2"`
}
//goland:noinspection ALL
@@ -31,16 +31,16 @@ const (
RTPTimeAACLD24 = 40 // 24000/1000*40=960
)
type AudioCodec struct {
CodecType byte `tlv8:"1"`
CodecParams []AudioParams `tlv8:"2"`
RTPParams []RTPParams `tlv8:"3"`
ComfortNoise []byte `tlv8:"4"`
type AudioCodecConfiguration struct {
CodecType byte `tlv8:"1"`
CodecParams []AudioCodecParameters `tlv8:"2"`
RTPParams []RTPParams `tlv8:"3"`
ComfortNoise []byte `tlv8:"4"`
}
type AudioParams struct {
Channels uint8 `tlv8:"1"`
Bitrate byte `tlv8:"2"` // 0 - variable, 1 - constant
SampleRate []byte `tlv8:"3"` // 0 - 8000, 1 - 16000, 2 - 24000
RTPTime []uint8 `tlv8:"4"` // 20, 30, 40, 60
type AudioCodecParameters struct {
Channels uint8 `tlv8:"1"`
BitrateMode byte `tlv8:"2"` // 0 - variable, 1 - constant
SampleRate []byte `tlv8:"3"` // 0 - 8000, 1 - 16000, 2 - 24000
RTPTime []uint8 `tlv8:"4"` // 20, 30, 40, 60
}
@@ -6,9 +6,9 @@ const TypeSupportedRTPConfiguration = "116"
const (
CryptoAES_CM_128_HMAC_SHA1_80 = 0
CryptoAES_CM_256_HMAC_SHA1_80 = 1
CryptoNone = 2
CryptoDisabled = 2
)
type SupportedRTPConfig struct {
CryptoType []byte `tlv8:"2"`
type SupportedRTPConfiguration struct {
SRTPCryptoType []byte `tlv8:"2"`
}
+4 -4
View File
@@ -2,10 +2,10 @@ package camera
const TypeSelectedStreamConfiguration = "117"
type SelectedStreamConfig struct {
Control SessionControl `tlv8:"1"`
VideoCodec VideoCodec `tlv8:"2"`
AudioCodec AudioCodec `tlv8:"3"`
type SelectedStreamConfiguration struct {
Control SessionControl `tlv8:"1"`
VideoCodec VideoCodecConfiguration `tlv8:"2"`
AudioCodec AudioCodecConfiguration `tlv8:"3"`
}
//goland:noinspection ALL
+20 -13
View File
@@ -2,25 +2,32 @@ package camera
const TypeSetupEndpoints = "118"
type SetupEndpoints struct {
SessionID string `tlv8:"1"`
Status []byte `tlv8:"2"`
Address Addr `tlv8:"3"`
VideoCrypto CryptoSuite `tlv8:"4"`
AudioCrypto CryptoSuite `tlv8:"5"`
VideoSSRC []uint32 `tlv8:"6"`
AudioSSRC []uint32 `tlv8:"7"`
type SetupEndpointsRequest struct {
SessionID string `tlv8:"1"`
Address Address `tlv8:"3"`
VideoCrypto SRTPCryptoSuite `tlv8:"4"`
AudioCrypto SRTPCryptoSuite `tlv8:"5"`
}
type Addr struct {
type SetupEndpointsResponse struct {
SessionID string `tlv8:"1"`
Status byte `tlv8:"2"`
Address Address `tlv8:"3"`
VideoCrypto SRTPCryptoSuite `tlv8:"4"`
AudioCrypto SRTPCryptoSuite `tlv8:"5"`
VideoSSRC uint32 `tlv8:"6"`
AudioSSRC uint32 `tlv8:"7"`
}
type Address struct {
IPVersion byte `tlv8:"1"`
IPAddr string `tlv8:"2"`
VideoRTPPort uint16 `tlv8:"3"`
AudioRTPPort uint16 `tlv8:"4"`
}
type CryptoSuite struct {
CryptoType byte `tlv8:"1"`
MasterKey string `tlv8:"2"` // 16 (AES_CM_128) or 32 (AES_256_CM)
MasterSalt string `tlv8:"3"` // 14 byte
type SRTPCryptoSuite struct {
CryptoSuite byte `tlv8:"1"`
MasterKey string `tlv8:"2"` // 16 (AES_CM_128) or 32 (AES_256_CM)
MasterSalt string `tlv8:"3"` // 14 byte
}
+1 -1
View File
@@ -9,6 +9,6 @@ type StreamingStatus struct {
//goland:noinspection ALL
const (
StreamingStatusAvailable = 0
StreamingStatusBusy = 1
StreamingStatusInUse = 1
StreamingStatusUnavailable = 2
)
@@ -0,0 +1,11 @@
package camera
const TypeSupportedDataStreamTransportConfiguration = "130"
type SupportedDataStreamTransportConfiguration struct {
Configs []TransferTransportConfiguration `tlv8:"1"`
}
type TransferTransportConfiguration struct {
TransportType byte `tlv8:"1"`
}
+2 -2
View File
@@ -2,13 +2,13 @@ package camera
const TypeSetupDataStreamTransport = "131"
type SetupDataStreamRequest struct {
type SetupDataStreamTransportRequest struct {
SessionCommandType byte `tlv8:"1"`
TransportType byte `tlv8:"2"`
ControllerKeySalt string `tlv8:"3"`
}
type SetupDataStreamResponse struct {
type SetupDataStreamTransportResponse struct {
Status byte `tlv8:"1"`
TransportTypeSessionParameters struct {
TCPListeningPort uint16 `tlv8:"1"`
+18
View File
@@ -0,0 +1,18 @@
package camera
const TypeSupportedCameraRecordingConfiguration = "205"
type SupportedCameraRecordingConfiguration struct {
PrebufferLength uint32 `tlv8:"1"`
EventTriggerOptions uint64 `tlv8:"2"`
MediaContainerConfigurations `tlv8:"3"`
}
type MediaContainerConfigurations struct {
MediaContainerType uint8 `tlv8:"1"`
MediaContainerParameters `tlv8:"2"`
}
type MediaContainerParameters struct {
FragmentLength uint32 `tlv8:"1"`
}
+20
View File
@@ -0,0 +1,20 @@
package camera
const TypeSupportedVideoRecordingConfiguration = "206"
type SupportedVideoRecordingConfiguration struct {
CodecConfigs []VideoRecordingCodecConfiguration `tlv8:"1"`
}
type VideoRecordingCodecConfiguration struct {
CodecType uint8 `tlv8:"1"`
CodecParams VideoRecordingCodecParameters `tlv8:"2"`
CodecAttrs VideoCodecAttributes `tlv8:"3"`
}
type VideoRecordingCodecParameters struct {
ProfileID uint8 `tlv8:"1"`
Level uint8 `tlv8:"2"`
Bitrate uint32 `tlv8:"3"`
IFrameInterval uint32 `tlv8:"4"`
}
+19
View File
@@ -0,0 +1,19 @@
package camera
const TypeSupportedAudioRecordingConfiguration = "207"
type SupportedAudioRecordingConfiguration struct {
CodecConfigs []AudioRecordingCodecConfiguration `tlv8:"1"`
}
type AudioRecordingCodecConfiguration struct {
CodecType byte `tlv8:"1"`
CodecParams []AudioRecordingCodecParameters `tlv8:"2"`
}
type AudioRecordingCodecParameters struct {
Channels uint8 `tlv8:"1"`
BitrateMode []byte `tlv8:"2"`
SampleRate []byte `tlv8:"3"`
MaxAudioBitrate []uint32 `tlv8:"4"`
}
+9
View File
@@ -0,0 +1,9 @@
package camera
const TypeSelectedCameraRecordingConfiguration = "209"
type SelectedCameraRecordingConfiguration struct {
GeneralConfig SupportedCameraRecordingConfiguration `tlv8:"1"`
VideoConfig SupportedVideoRecordingConfiguration `tlv8:"2"`
AudioConfig SupportedAudioRecordingConfiguration `tlv8:"3"`
}
+11 -11
View File
@@ -15,7 +15,7 @@ type Stream struct {
}
func NewStream(
client *hap.Client, videoCodec *VideoCodec, audioCodec *AudioCodec,
client *hap.Client, videoCodec *VideoCodecConfiguration, audioCodec *AudioCodecConfiguration,
videoSession, audioSession *srtp.Session, bitrate int,
) (*Stream, error) {
stream := &Stream{
@@ -58,7 +58,7 @@ func NewStream(
}
audioCodec.ComfortNoise = []byte{0}
config := &SelectedStreamConfig{
config := &SelectedStreamConfiguration{
Control: SessionControl{
SessionID: stream.id,
Command: SessionCommandStart,
@@ -103,19 +103,19 @@ func (s *Stream) GetFreeStream() error {
}
func (s *Stream) ExchangeEndpoints(videoSession, audioSession *srtp.Session) error {
req := SetupEndpoints{
req := SetupEndpointsRequest{
SessionID: s.id,
Address: Addr{
Address: Address{
IPVersion: 0,
IPAddr: videoSession.Local.Addr,
VideoRTPPort: videoSession.Local.Port,
AudioRTPPort: audioSession.Local.Port,
},
VideoCrypto: CryptoSuite{
VideoCrypto: SRTPCryptoSuite{
MasterKey: string(videoSession.Local.MasterKey),
MasterSalt: string(videoSession.Local.MasterSalt),
},
AudioCrypto: CryptoSuite{
AudioCrypto: SRTPCryptoSuite{
MasterKey: string(audioSession.Local.MasterKey),
MasterSalt: string(audioSession.Local.MasterSalt),
},
@@ -129,7 +129,7 @@ func (s *Stream) ExchangeEndpoints(videoSession, audioSession *srtp.Session) err
return err
}
var res SetupEndpoints
var res SetupEndpointsResponse
if err := s.client.GetCharacter(char); err != nil {
return err
}
@@ -142,7 +142,7 @@ func (s *Stream) ExchangeEndpoints(videoSession, audioSession *srtp.Session) err
Port: res.Address.VideoRTPPort,
MasterKey: []byte(res.VideoCrypto.MasterKey),
MasterSalt: []byte(res.VideoCrypto.MasterSalt),
SSRC: res.VideoSSRC[0],
SSRC: res.VideoSSRC,
}
audioSession.Remote = &srtp.Endpoint{
@@ -150,13 +150,13 @@ func (s *Stream) ExchangeEndpoints(videoSession, audioSession *srtp.Session) err
Port: res.Address.AudioRTPPort,
MasterKey: []byte(res.AudioCrypto.MasterKey),
MasterSalt: []byte(res.AudioCrypto.MasterSalt),
SSRC: res.AudioSSRC[0],
SSRC: res.AudioSSRC,
}
return nil
}
func (s *Stream) SetStreamConfig(config *SelectedStreamConfig) error {
func (s *Stream) SetStreamConfig(config *SelectedStreamConfiguration) error {
char := s.service.GetCharacter(TypeSelectedStreamConfiguration)
if err := char.Write(config); err != nil {
return err
@@ -169,7 +169,7 @@ func (s *Stream) SetStreamConfig(config *SelectedStreamConfig) error {
}
func (s *Stream) Close() error {
config := &SelectedStreamConfig{
config := &SelectedStreamConfiguration{
Control: SessionControl{
SessionID: s.id,
Command: SessionCommandEnd,
+11 -5
View File
@@ -18,7 +18,6 @@ import (
"github.com/AlexxIT/go2rtc/pkg/hap/curve25519"
"github.com/AlexxIT/go2rtc/pkg/hap/ed25519"
"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/mdns"
)
@@ -46,7 +45,7 @@ type Client struct {
err error
}
func NewClient(rawURL string) (*Client, error) {
func Dial(rawURL string) (*Client, error) {
u, err := url.Parse(rawURL)
if err != nil {
return nil, err
@@ -61,6 +60,10 @@ func NewClient(rawURL string) (*Client, error) {
ClientPrivate: DecodeKey(query.Get("client_private")),
}
if err = c.Dial(); err != nil {
return nil, err
}
return c, nil
}
@@ -96,6 +99,7 @@ func (c *Client) Dial() (err error) {
return false
})
// TODO: close conn on error
if c.Conn, err = net.DialTimeout("tcp", c.DeviceAddress, ConnDialTimeout); err != nil {
return
}
@@ -124,7 +128,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,15 +213,17 @@ 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 {
return newResponseError(cipherM3, plainM4)
}
rw := bufio.NewReadWriter(c.reader, bufio.NewWriter(c.Conn))
// like tls.Client wrapper over net.Conn
if c.Conn, err = secure.Client(c.Conn, sessionShared, true); err != nil {
if c.Conn, err = NewConn(c.Conn, rw, sessionShared, true); err != nil {
return
}
// new reader for new conn
+17
View File
@@ -82,3 +82,20 @@ func ReadResponse(r *bufio.Reader, req *http.Request) (*http.Response, error) {
return res, nil
}
func WriteEvent(w io.Writer, res *http.Response) error {
return res.Write(&eventWriter{w: w})
}
type eventWriter struct {
w io.Writer
done bool
}
func (e *eventWriter) Write(p []byte) (n int, err error) {
if !e.done {
p = append([]byte("EVENT/1.0"), p[8:]...)
e.done = true
}
return e.w.Write(p)
}
+8 -9
View File
@@ -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 {
@@ -121,9 +121,7 @@ func (c *Client) Pair(feature, pin string) (err error) {
username := []byte("Pair-Setup")
// Stanford Secure Remote Password (SRP) / Password Authenticated Key Exchange (PAKE)
pake, err := srp.NewSRP(
"rfc5054.3072", sha512.New, keyDerivativeFuncRFC2945(username),
)
pake, err := srp.NewSRP("rfc5054.3072", sha512.New, keyDerivativeFuncRFC2945(username))
if err != nil {
return
}
@@ -132,6 +130,7 @@ func (c *Client) Pair(feature, pin string) (err error) {
// username: "Pair-Setup", password: PIN (with dashes)
session := pake.NewClientSession(username, []byte(pin))
sessionShared, err := session.ComputeKey([]byte(plainM2.Salt), []byte(plainM2.SessionKey))
if err != nil {
return
@@ -159,7 +158,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 +231,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 +295,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 +328,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 +353,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 {
+44 -20
View File
@@ -1,32 +1,50 @@
package secure
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
rd *bufio.Reader
wr *bufio.Writer
rw *bufio.ReadWriter
wmu sync.Mutex
encryptKey []byte
decryptKey []byte
encryptCnt uint64
decryptCnt uint64
//ClientID string
SharedKey []byte
recv int
send int
}
func Client(conn net.Conn, sharedKey []byte, isClient bool) (net.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")
if err != nil {
return nil, err
@@ -39,8 +57,7 @@ func Client(conn net.Conn, sharedKey []byte, isClient bool) (net.Conn, error) {
c := &Conn{
conn: conn,
rd: bufio.NewReaderSize(conn, 32*1024),
wr: bufio.NewWriterSize(conn, 32*1024),
rw: rw,
SharedKey: sharedKey,
}
@@ -55,8 +72,8 @@ func Client(conn net.Conn, sharedKey []byte, isClient bool) (net.Conn, error) {
}
const (
// PacketSizeMax is the max length of encrypted packets
PacketSizeMax = 0x400
// packetSizeMax is the max length of encrypted packets
packetSizeMax = 0x400
VerifySize = 2
NonceSize = 8
@@ -64,19 +81,19 @@ const (
)
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")
}
verify := make([]byte, 2) // verify = plain message size
if _, err = io.ReadFull(c.rd, verify); err != nil {
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.rd, ciphertext); err != nil {
ciphertext := make([]byte, n+Overhead)
if _, err = io.ReadFull(c.rw, ciphertext); err != nil {
return
}
@@ -85,22 +102,27 @@ func (c *Conn) Read(b []byte) (n int, err error) {
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) {
buf := make([]byte, 0, PacketSizeMax+Overhead)
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
if size > packetSizeMax {
size = packetSizeMax
}
binary.LittleEndian.PutUint16(verify, uint16(size))
if _, err = c.wr.Write(verify); err != nil {
if _, err = c.rw.Write(verify); err != nil {
return
}
@@ -112,7 +134,7 @@ func (c *Conn) Write(b []byte) (n int, err error) {
return
}
if _, err = c.wr.Write(buf[:size+Overhead]); err != nil {
if _, err = c.rw.Write(buf[:size+Overhead]); err != nil {
return
}
@@ -120,7 +142,9 @@ func (c *Conn) Write(b []byte) (n int, err error) {
n += size
}
err = c.wr.Flush()
err = c.rw.Flush()
c.send += n
return
}
+62 -9
View File
@@ -4,16 +4,19 @@ package hds
import (
"bufio"
"encoding/binary"
"encoding/json"
"errors"
"io"
"net"
"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/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")
if err != nil {
return nil, err
@@ -49,43 +52,91 @@ type Conn struct {
encryptKey []byte
decryptCnt uint64
encryptCnt uint64
recv int
send int
}
func (c *Conn) Read(p []byte) (n int, err error) {
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() (b []byte, err error) {
verify := make([]byte, 4)
if _, err = io.ReadFull(c.rd, verify); err != nil {
return
}
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 {
return
}
nonce := make([]byte, secure.NonceSize)
nonce := make([]byte, hap.NonceSize)
binary.LittleEndian.PutUint64(nonce, c.decryptCnt)
c.decryptCnt++
_, err = chacha20poly1305.DecryptAndVerify(c.decryptKey, p[:0], nonce, ciphertext, verify)
c.recv += n
return chacha20poly1305.DecryptAndVerify(c.decryptKey, ciphertext[:0], nonce, ciphertext, verify)
}
func (c *Conn) Read(p []byte) (n int, err error) {
b, err := c.read()
if err != nil {
return 0, err
}
n = copy(p, b)
if len(b) > n {
err = errors.New("hds: read buffer too small")
}
return
}
func (c *Conn) WriteTo(w io.Writer) (int64, error) {
var total int64
for {
b, err := c.read()
if err != nil {
return total, err
}
n, err := w.Write(b)
total += int64(n)
if err != nil {
return total, err
}
}
}
func (c *Conn) Write(b []byte) (n int, err error) {
n = len(b)
if n > 0xFFFFFF {
return 0, errors.New("hds: write buffer too big")
}
verify := make([]byte, 4)
binary.BigEndian.PutUint32(verify, 0x01000000|uint32(n))
if _, err = c.wr.Write(verify); err != nil {
return
}
nonce := make([]byte, secure.NonceSize)
nonce := make([]byte, hap.NonceSize)
binary.LittleEndian.PutUint64(nonce, 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 {
return
}
@@ -95,6 +146,8 @@ func (c *Conn) Write(b []byte) (n int, err error) {
}
err = c.wr.Flush()
c.send += n
return
}
+8
View File
@@ -3,6 +3,8 @@ package hap
import (
"crypto/ed25519"
"crypto/rand"
"crypto/sha512"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
@@ -99,6 +101,12 @@ func GenerateUUID() string {
return s[:8] + "-" + s[8:12] + "-" + s[12:16] + "-" + s[16:20] + "-" + s[20:]
}
func SetupHash(setupID, deviceID string) string {
// should be setup_id (random 4 alphanum) + device_id (mac address)
b := sha512.Sum512([]byte(setupID + deviceID))
return base64.StdEncoding.EncodeToString(b[:4])
}
func Append(items ...any) (b []byte) {
for _, item := range items {
switch v := item.(type) {
+273 -52
View File
@@ -3,32 +3,25 @@ package hap
import (
"bufio"
"crypto/sha512"
"encoding/base64"
"errors"
"fmt"
"io"
"net"
"net/http"
"github.com/AlexxIT/go2rtc/pkg/hap/chacha20poly1305"
"github.com/AlexxIT/go2rtc/pkg/hap/curve25519"
"github.com/AlexxIT/go2rtc/pkg/hap/ed25519"
"github.com/AlexxIT/go2rtc/pkg/hap/hkdf"
"github.com/AlexxIT/go2rtc/pkg/hap/secure"
"github.com/AlexxIT/go2rtc/pkg/hap/tlv8"
"github.com/tadglines/go-pkgs/crypto/srp"
)
type HandlerFunc func(net.Conn) error
type Server struct {
Pin string
DeviceID string
DevicePrivate []byte
GetPair func(conn net.Conn, id string) []byte
AddPair func(conn net.Conn, id string, public []byte, permissions byte)
Handler HandlerFunc
// GetClientPublic may be nil, so client validation will be disabled
GetClientPublic func(id string) []byte
}
func (s *Server) ServerPublic() []byte {
@@ -42,44 +35,240 @@ func (s *Server) ServerPublic() []byte {
// return StatusPaired
//}
func (s *Server) SetupHash() string {
// should be setup_id (random 4 alphanum) + device_id (mac address)
// but device_id is random, so OK
b := sha512.Sum512([]byte(s.DeviceID))
return base64.StdEncoding.EncodeToString(b[:4])
}
func (s *Server) PairVerify(req *http.Request, rw *bufio.ReadWriter, conn net.Conn) error {
// Request from iPhone
func (s *Server) PairSetup(req *http.Request, rw *bufio.ReadWriter) (id string, publicKey []byte, err error) {
// STEP 1. Request from iPhone
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(io.LimitReader(rw, req.ContentLength), &plainM1); err != nil {
return err
if err = tlv8.UnmarshalReader(req.Body, req.ContentLength, &plainM1); err != nil {
return
}
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
sessionPublic, sessionPrivate := curve25519.GenerateKeyPair()
sessionShared, err := curve25519.SharedSecret(sessionPrivate, []byte(plainM1.PublicKey))
if err != nil {
return err
return
}
encryptKey, err := hkdf.Sha512(
sessionShared, "Pair-Verify-Encrypt-Salt", "Pair-Verify-Encrypt-Info",
)
if err != nil {
return err
return
}
b := Append(sessionPublic, s.DeviceID, plainM1.PublicKey)
signature, err := ed25519.Signature(s.DevicePrivate, b)
if err != nil {
return err
return
}
// STEP M2. Response to iPhone
@@ -91,12 +280,12 @@ func (s *Server) PairVerify(req *http.Request, rw *bufio.ReadWriter, conn net.Co
Signature: string(signature),
}
if b, err = tlv8.Marshal(plainM2); err != nil {
return err
return
}
b, err = chacha20poly1305.Encrypt(encryptKey, "PV-Msg02", b)
if err != nil {
return err
return
}
cipherM2 := struct {
@@ -110,30 +299,32 @@ func (s *Server) PairVerify(req *http.Request, rw *bufio.ReadWriter, conn net.Co
}
body, err := tlv8.Marshal(cipherM2)
if err != nil {
return err
return
}
if err = WriteResponse(rw.Writer, http.StatusOK, MimeTLV8, body); err != nil {
return err
return
}
// STEP M3. Request from iPhone
if req, err = http.ReadRequest(rw.Reader); err != nil {
return err
return
}
var cipherM3 struct {
EncryptedData string `tlv8:"5"`
State byte `tlv8:"6"`
EncryptedData string `tlv8:"5"`
}
if err = tlv8.UnmarshalReader(req.Body, &cipherM3); err != nil {
return err
if err = tlv8.UnmarshalReader(req.Body, req.ContentLength, &cipherM3); err != nil {
return
}
if cipherM3.State != StateM3 {
return newRequestError(cipherM3)
err = newRequestError(cipherM3)
return
}
if b, err = chacha20poly1305.Decrypt(encryptKey, "PV-Msg03", []byte(cipherM3.EncryptedData)); err != nil {
return err
b, err = chacha20poly1305.Decrypt(encryptKey, "PV-Msg03", []byte(cipherM3.EncryptedData))
if err != nil {
return
}
var plainM3 struct {
@@ -141,17 +332,21 @@ func (s *Server) PairVerify(req *http.Request, rw *bufio.ReadWriter, conn net.Co
Signature string `tlv8:"10"`
}
if err = tlv8.Unmarshal(b, &plainM3); err != nil {
return err
return
}
clientPublic := s.GetPair(conn, plainM3.Identifier)
if clientPublic == nil {
return fmt.Errorf("hap: PairVerify from: %s, with unknown client_id: %s", conn.RemoteAddr(), plainM3.Identifier)
}
if s.GetClientPublic != nil {
clientPublic := s.GetClientPublic(plainM3.Identifier)
if clientPublic == nil {
err = errors.New("hap: PairVerify with unknown client_id: " + plainM3.Identifier)
return
}
b = Append(plainM1.PublicKey, plainM3.Identifier, sessionPublic)
if !ed25519.ValidateSignature(clientPublic, b, []byte(plainM3.Signature)) {
return errors.New("new: ValidateSignature")
b = Append(plainM1.PublicKey, plainM3.Identifier, sessionPublic)
if !ed25519.ValidateSignature(clientPublic, b, []byte(plainM3.Signature)) {
err = errors.New("hap: ValidateSignature")
return
}
}
// STEP M4. Response to iPhone
@@ -161,15 +356,41 @@ func (s *Server) PairVerify(req *http.Request, rw *bufio.ReadWriter, conn net.Co
State: StateM4,
}
if body, err = tlv8.Marshal(payloadM4); err != nil {
return err
return
}
if err = WriteResponse(rw.Writer, http.StatusOK, MimeTLV8, body); err != nil {
return err
return
}
if conn, err = secure.Client(conn, sessionShared, false); err != nil {
return err
}
id = plainM3.Identifier
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)
//}
-252
View File
@@ -1,252 +0,0 @@
package hap
import (
"bufio"
"crypto/sha512"
"errors"
"fmt"
"io"
"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) 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")
}
// 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 {
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, &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, &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)
}
+32
View File
@@ -0,0 +1,32 @@
package setup
import (
"strconv"
"strings"
)
const (
FlagNFC = 1
FlagIP = 2
FlagBLE = 4
FlagWAC = 8 // Wireless Accessory Configuration (WAC)/Apples MFi
)
func GenerateSetupURI(category, pin, setupID string) string {
c, _ := strconv.Atoi(category)
p, _ := strconv.Atoi(strings.ReplaceAll(pin, "-", ""))
payload := int64(c&0xFF)<<31 | int64(FlagIP&0xF)<<27 | int64(p&0x7FFFFFF)
return "X-HM://" + FormatInt36(payload, 9) + setupID
}
// FormatInt36 equal to strings.ToUpper(fmt.Sprintf("%0"+strconv.Itoa(n)+"s", strconv.FormatInt(value, 36)))
func FormatInt36(value int64, n int) string {
b := make([]byte, n)
for i := n - 1; 0 <= i; i-- {
b[i] = digits[value%36]
value /= 36
}
return string(b)
}
const digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+18
View File
@@ -0,0 +1,18 @@
package setup
import (
"fmt"
"strconv"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
func TestFormatAlphaNum(t *testing.T) {
value := int64(999)
n := 5
s1 := strings.ToUpper(fmt.Sprintf("%0"+strconv.Itoa(n)+"s", strconv.FormatInt(value, 36)))
s2 := FormatInt36(value, n)
require.Equal(t, s1, s2)
}
+21 -2
View File
@@ -112,6 +112,10 @@ func appendValue(b []byte, tag byte, value reflect.Value) ([]byte, error) {
v := value.Uint()
return append(b, tag, 4, byte(v), byte(v>>8), byte(v>>16), byte(v>>24)), nil
case reflect.Uint64:
v := value.Uint()
return binary.LittleEndian.AppendUint64(append(b, tag, 8), v), nil
case reflect.Float32:
v := math.Float32bits(float32(value.Float()))
return append(b, tag, 4, byte(v), byte(v>>8), byte(v>>16), byte(v>>24)), nil
@@ -170,11 +174,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)
}
@@ -301,6 +314,12 @@ func unmarshalValue(v []byte, value reflect.Value) error {
}
value.SetUint(uint64(v[0]) | uint64(v[1])<<8 | uint64(v[2])<<16 | uint64(v[3])<<24)
case reflect.Uint64:
if len(v) != 8 {
return errors.New("tlv8: wrong size: " + value.Type().Name())
}
value.SetUint(binary.LittleEndian.Uint64(v))
case reflect.Float32:
f := math.Float32frombits(binary.LittleEndian.Uint32(v))
value.SetFloat(float64(f))
+15 -11
View File
@@ -49,7 +49,7 @@ func NewConsumer(conn net.Conn, server *srtp.Server) *Consumer {
Connection: core.Connection{
ID: core.NewID(),
FormatName: "homekit",
Protocol: "udp",
Protocol: "rtp",
RemoteAddr: conn.RemoteAddr().String(),
Medias: medias,
Transport: conn,
@@ -59,7 +59,11 @@ func NewConsumer(conn net.Conn, server *srtp.Server) *Consumer {
}
}
func (c *Consumer) SetOffer(offer *camera.SetupEndpoints) {
func (c *Consumer) SessionID() string {
return c.sessionID
}
func (c *Consumer) SetOffer(offer *camera.SetupEndpointsRequest) {
c.sessionID = offer.SessionID
c.videoSession = &srtp.Session{
Remote: &srtp.Endpoint{
@@ -79,32 +83,32 @@ func (c *Consumer) SetOffer(offer *camera.SetupEndpoints) {
}
}
func (c *Consumer) GetAnswer() *camera.SetupEndpoints {
func (c *Consumer) GetAnswer() *camera.SetupEndpointsResponse {
c.videoSession.Local = c.srtpEndpoint()
c.audioSession.Local = c.srtpEndpoint()
return &camera.SetupEndpoints{
return &camera.SetupEndpointsResponse{
SessionID: c.sessionID,
Status: []byte{0},
Address: camera.Addr{
Status: camera.StreamingStatusAvailable,
Address: camera.Address{
IPAddr: c.videoSession.Local.Addr,
VideoRTPPort: c.videoSession.Local.Port,
AudioRTPPort: c.audioSession.Local.Port,
},
VideoCrypto: camera.CryptoSuite{
VideoCrypto: camera.SRTPCryptoSuite{
MasterKey: string(c.videoSession.Local.MasterKey),
MasterSalt: string(c.videoSession.Local.MasterSalt),
},
AudioCrypto: camera.CryptoSuite{
AudioCrypto: camera.SRTPCryptoSuite{
MasterKey: string(c.audioSession.Local.MasterKey),
MasterSalt: string(c.audioSession.Local.MasterSalt),
},
VideoSSRC: []uint32{c.videoSession.Local.SSRC},
AudioSSRC: []uint32{c.audioSession.Local.SSRC},
VideoSSRC: c.videoSession.Local.SSRC,
AudioSSRC: c.audioSession.Local.SSRC,
}
}
func (c *Consumer) SetConfig(conf *camera.SelectedStreamConfig) bool {
func (c *Consumer) SetConfig(conf *camera.SelectedStreamConfiguration) bool {
if c.sessionID != conf.Control.SessionID {
return false
}
+13 -10
View File
@@ -13,7 +13,7 @@ var videoCodecs = [...]string{core.CodecH264}
var videoProfiles = [...]string{"4200", "4D00", "6400"}
var videoLevels = [...]string{"1F", "20", "28"}
func videoToMedia(codecs []camera.VideoCodec) *core.Media {
func videoToMedia(codecs []camera.VideoCodecConfiguration) *core.Media {
media := &core.Media{
Kind: core.KindVideo, Direction: core.DirectionRecvonly,
}
@@ -39,7 +39,7 @@ func videoToMedia(codecs []camera.VideoCodec) *core.Media {
var audioCodecs = [...]string{core.CodecPCMU, core.CodecPCMA, core.CodecELD, core.CodecOpus}
var audioSampleRates = [...]uint32{8000, 16000, 24000}
func audioToMedia(codecs []camera.AudioCodec) *core.Media {
func audioToMedia(codecs []camera.AudioCodecConfiguration) *core.Media {
media := &core.Media{
Kind: core.KindAudio, Direction: core.DirectionRecvonly,
}
@@ -67,10 +67,10 @@ func audioToMedia(codecs []camera.AudioCodec) *core.Media {
return media
}
func trackToVideo(track *core.Receiver, video0 *camera.VideoCodec) *camera.VideoCodec {
func trackToVideo(track *core.Receiver, video0 *camera.VideoCodecConfiguration, maxWidth, maxHeight int) *camera.VideoCodecConfiguration {
profileID := video0.CodecParams[0].ProfileID[0]
level := video0.CodecParams[0].Level[0]
attrs := video0.VideoAttrs[0]
var attrs camera.VideoCodecAttributes
if track != nil {
profile := h264.GetProfileLevelID(track.Codec.FmtpLine)
@@ -90,25 +90,28 @@ func trackToVideo(track *core.Receiver, video0 *camera.VideoCodec) *camera.Video
}
for _, s := range video0.VideoAttrs {
if (maxWidth > 0 && int(s.Width) > maxWidth) || (maxHeight > 0 && int(s.Height) > maxHeight) {
continue
}
if s.Width > attrs.Width || s.Height > attrs.Height {
attrs = s
}
}
}
return &camera.VideoCodec{
return &camera.VideoCodecConfiguration{
CodecType: video0.CodecType,
CodecParams: []camera.VideoParams{
CodecParams: []camera.VideoCodecParameters{
{
ProfileID: []byte{profileID},
Level: []byte{level},
},
},
VideoAttrs: []camera.VideoAttrs{attrs},
VideoAttrs: []camera.VideoCodecAttributes{attrs},
}
}
func trackToAudio(track *core.Receiver, audio0 *camera.AudioCodec) *camera.AudioCodec {
func trackToAudio(track *core.Receiver, audio0 *camera.AudioCodecConfiguration) *camera.AudioCodecConfiguration {
codecType := audio0.CodecType
channels := audio0.CodecParams[0].Channels
sampleRate := audio0.CodecParams[0].SampleRate[0]
@@ -131,9 +134,9 @@ func trackToAudio(track *core.Receiver, audio0 *camera.AudioCodec) *camera.Audio
}
}
return &camera.AudioCodec{
return &camera.AudioCodecConfiguration{
CodecType: codecType,
CodecParams: []camera.AudioParams{
CodecParams: []camera.AudioCodecParameters{
{
Channels: channels,
SampleRate: []byte{sampleRate},
+45
View File
@@ -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)
}
}
}
+7 -19
View File
@@ -5,7 +5,6 @@ import (
"fmt"
"math/rand"
"net"
"net/url"
"time"
"github.com/AlexxIT/go2rtc/pkg/core"
@@ -22,36 +21,25 @@ type Client struct {
hap *hap.Client
srtp *srtp.Server
videoConfig camera.SupportedVideoStreamConfig
audioConfig camera.SupportedAudioStreamConfig
videoConfig camera.SupportedVideoStreamConfiguration
audioConfig camera.SupportedAudioStreamConfiguration
videoSession *srtp.Session
audioSession *srtp.Session
stream *camera.Stream
Bitrate int // in bits/s
MaxWidth int `json:"-"`
MaxHeight int `json:"-"`
Bitrate int `json:"-"` // in bits/s
}
func Dial(rawURL string, server *srtp.Server) (*Client, error) {
u, err := url.Parse(rawURL)
conn, err := hap.Dial(rawURL)
if err != nil {
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{
Connection: core.Connection{
ID: core.NewID(),
@@ -129,7 +117,7 @@ func (c *Client) Start() error {
}
videoTrack := c.trackByKind(core.KindVideo)
videoCodec := trackToVideo(videoTrack, &c.videoConfig.Codecs[0])
videoCodec := trackToVideo(videoTrack, &c.videoConfig.Codecs[0], c.MaxWidth, c.MaxHeight)
audioTrack := c.trackByKind(core.KindAudio)
audioCodec := trackToAudio(audioTrack, &c.audioConfig.Codecs[0])
+33 -27
View File
@@ -4,31 +4,30 @@ import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"time"
"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 {
type ServerProxy interface {
ServerPair
AddConn(conn any)
DelConn(conn any)
}
func ProxyHandler(srv ServerProxy, acc net.Conn) 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),
con: con.(*hap.Conn),
acc: acc.(*hap.Conn),
res: make(chan *http.Response),
}
@@ -36,17 +35,17 @@ func ProxyHandler(pair ServerPair, dial func() (net.Conn, error)) hap.HandlerFun
go pr.handleAcc()
// controller => accessory
return pr.handleCon(pair)
return pr.handleCon(srv)
}
}
type Proxy struct {
con *secure.Conn
acc *secure.Conn
con *hap.Conn
acc *hap.Conn
res chan *http.Response
}
func (p *Proxy) handleCon(pair ServerPair) error {
func (p *Proxy) handleCon(srv ServerProxy) error {
var hdsCharIID uint64
rd := bufio.NewReader(p.con)
@@ -61,7 +60,7 @@ func (p *Proxy) handleCon(pair ServerPair) error {
switch {
case req.Method == "POST" && req.URL.Path == hap.PathPairings:
var res *http.Response
if res, err = handlePairings(p.con, req, pair); err != nil {
if res, err = handlePairings(req, srv); err != nil {
return err
}
if err = res.Write(p.con); err != nil {
@@ -74,7 +73,7 @@ func (p *Proxy) handleCon(pair ServerPair) error {
_ = json.Unmarshal(body, &v)
for _, char := range v.Value {
if char.IID == hdsCharIID {
var hdsReq camera.SetupDataStreamRequest
var hdsReq camera.SetupDataStreamTransportRequest
_ = tlv8.UnmarshalBase64(char.Value, &hdsReq)
hdsConSalt = hdsReq.ControllerKeySalt
break
@@ -110,14 +109,14 @@ func (p *Proxy) handleCon(pair ServerPair) error {
_ = json.Unmarshal(body, &v)
for i, char := range v.Value {
if char.IID == hdsCharIID {
var hdsRes camera.SetupDataStreamResponse
var hdsRes camera.SetupDataStreamTransportResponse
_ = tlv8.UnmarshalBase64(char.Value, &hdsRes)
hdsAccSalt := hdsRes.AccessoryKeySalt
hdsPort := int(hdsRes.TransportTypeSessionParameters.TCPListeningPort)
// swtich accPort to conPort
hdsPort, err = p.listenHDS(hdsPort, hdsConSalt+hdsAccSalt)
hdsPort, err = p.listenHDS(srv, hdsPort, hdsConSalt+hdsAccSalt)
if err != nil {
return err
}
@@ -149,7 +148,7 @@ func (p *Proxy) handleAcc() error {
}
if res.Proto == hap.ProtoEvent {
if err = res.Write(p.con); err != nil {
if err = hap.WriteEvent(p.con, res); err != nil {
return err
}
continue
@@ -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)
if err != nil {
return 0, err
@@ -175,30 +175,36 @@ func (p *Proxy) listenHDS(accPort int, salt string) (int, error) {
go func() {
defer ln.Close()
_ = ln.SetDeadline(time.Now().Add(30 * time.Second))
// raw controller conn
con, err := ln.Accept()
conn1, err := ln.Accept()
if err != nil {
return
}
defer con.Close()
defer conn1.Close()
// 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 {
return
}
srv.AddConn(con)
defer srv.DelConn(con)
accIP := p.acc.RemoteAddr().(*net.TCPAddr).IP
// 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 {
return
}
defer acc.Close()
defer conn2.Close()
// 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 {
return
}
+12 -47
View File
@@ -15,15 +15,17 @@ import (
"github.com/AlexxIT/go2rtc/pkg/hap/tlv8"
)
type HandlerFunc func(net.Conn) error
type Server interface {
ServerPair
ServerAccessory
}
type ServerPair interface {
GetPair(conn net.Conn, id string) []byte
AddPair(conn net.Conn, id string, public []byte, permissions byte)
DelPair(conn net.Conn, id string)
GetPair(id string) []byte
AddPair(id string, public []byte, permissions byte)
DelPair(id string)
}
type ServerAccessory interface {
@@ -33,11 +35,11 @@ type ServerAccessory interface {
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) {
switch req.URL.Path {
case hap.PathPairings:
return handlePairings(conn, req, server)
return handlePairings(req, server)
case hap.PathAccessories:
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 {
rw := bufio.NewReaderSize(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 {
Method byte `tlv8:"0"`
Identifier string `tlv8:"1"`
@@ -139,15 +141,15 @@ 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
}
switch cmd.Method {
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
pair.DelPair(conn, cmd.Identifier)
srv.DelPair(cmd.Identifier)
}
body := struct {
@@ -190,40 +192,3 @@ func makeResponse(mime string, v any) (*http.Response, error) {
}
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)
// }
// }
//}