From ee38c2da13c65e3afa21b6195a74848480e4ba57 Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Mon, 7 Mar 2022 16:24:33 +0100 Subject: [PATCH 01/58] Update github.com/brutella/dnssd to v1.2.2 --- go.mod | 2 +- go.sum | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 1a36c02..6fea573 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/brutella/hap go 1.16 require ( - github.com/brutella/dnssd v1.2.1 + github.com/brutella/dnssd v1.2.2 github.com/go-chi/chi v1.5.4 github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9 github.com/xiam/to v0.0.0-20200126224905-d60d31e03561 diff --git a/go.sum b/go.sum index bef6f03..835f666 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,15 @@ github.com/brutella/dnssd v1.2.1 h1:1xG+5itx/SDEP6ukYfAcBnox5WACTNvxZ+SMkAmSrFU= github.com/brutella/dnssd v1.2.1/go.mod h1:FpJqlQ8+XU6w1vbnG1zJiQPTRE5fvQIRdrcBojMVuuQ= +github.com/brutella/dnssd v1.2.2 h1:9T6YPay8kD9+nSiX2AG9YfILAbwUll95sh7HhFShatI= +github.com/brutella/dnssd v1.2.2/go.mod h1:JjeBG7F+bH5iIKQ42H9QH0foPmP0L/nHZZaFMbrD5Wg= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs= github.com/go-chi/chi v1.5.4/go.mod h1:uaf8YgoFazUOkPBG7fxPftUylNumIev9awIWOENIuEg= github.com/miekg/dns v1.1.1 h1:DVkblRdiScEnEr0LR9nTnEQqHYycjkXW9bOjd+2EL2o= github.com/miekg/dns v1.1.1/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.46 h1:uzwpxRtSVxtcIZmz/4Uz6/Rn7G11DvsaslXoy5LxQio= +github.com/miekg/dns v1.1.46/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -15,23 +19,37 @@ github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9 h1:aeN+ghOV0b2VC github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9/go.mod h1:roo6cZ/uqpwKMuvPG0YmzI5+AmUiMWfjCBZpGXqbTxE= github.com/xiam/to v0.0.0-20200126224905-d60d31e03561 h1:SVoNK97S6JlaYlHcaC+79tg3JUlQABcc0dH2VQ4Y+9s= github.com/xiam/to v0.0.0-20200126224905-d60d31e03561/go.mod h1:cqbG7phSzrbdg3aj+Kn63bpVruzwDZi58CpxlZkjwzw= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 h1:71vQrMauZZhcTVK6KdYM+rklehEEwb3E+ZhaE5jrPrE= golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -40,6 +58,13 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 h1:BonxutuHCTL0rBDnZlKjpGIQFTjyUVTexFOdWkB6Fg0= +golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From 41890c5bf8a3a0e1a37f04de02b17d4cae34260f Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Mon, 7 Mar 2022 16:26:03 +0100 Subject: [PATCH 02/58] Run go mod tidy --- go.sum | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/go.sum b/go.sum index 835f666..2ecccba 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,9 @@ -github.com/brutella/dnssd v1.2.1 h1:1xG+5itx/SDEP6ukYfAcBnox5WACTNvxZ+SMkAmSrFU= -github.com/brutella/dnssd v1.2.1/go.mod h1:FpJqlQ8+XU6w1vbnG1zJiQPTRE5fvQIRdrcBojMVuuQ= github.com/brutella/dnssd v1.2.2 h1:9T6YPay8kD9+nSiX2AG9YfILAbwUll95sh7HhFShatI= github.com/brutella/dnssd v1.2.2/go.mod h1:JjeBG7F+bH5iIKQ42H9QH0foPmP0L/nHZZaFMbrD5Wg= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs= github.com/go-chi/chi v1.5.4/go.mod h1:uaf8YgoFazUOkPBG7fxPftUylNumIev9awIWOENIuEg= -github.com/miekg/dns v1.1.1 h1:DVkblRdiScEnEr0LR9nTnEQqHYycjkXW9bOjd+2EL2o= -github.com/miekg/dns v1.1.1/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.46 h1:uzwpxRtSVxtcIZmz/4Uz6/Rn7G11DvsaslXoy5LxQio= github.com/miekg/dns v1.1.46/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -29,24 +25,20 @@ golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From c46848d5fe5bc3597810402a40ea5df4c05f28db Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Mon, 21 Mar 2022 16:39:15 +0100 Subject: [PATCH 03/58] Programmable switch event response is always null --- characteristic/c.go | 24 ++++++++++++---- characteristic/programmable_switch_event.go | 11 +++++-- characteristics.go | 19 ++++++++---- server.go | 2 ++ server_test.go | 32 +++++++++++++++++++++ 5 files changed, 75 insertions(+), 13 deletions(-) diff --git a/characteristic/c.go b/characteristic/c.go index 7e24488..0c9a916 100644 --- a/characteristic/c.go +++ b/characteristic/c.go @@ -241,12 +241,11 @@ func (c *C) MarshalJSON() ([]byte, error) { Id uint64 `json:"iid"` // managed by accessory Type string `json:"type"` Permissions []string `json:"perms"` - Description string `json:"description,omitempty"` // manufacturer description (optional) - - Value interface{} `json:"value,omitempty"` // nil for write-only characteristics - Format string `json:"format"` - Unit string `json:"unit,omitempty"` + Format string `json:"format"` + Value *V `json:"value,omitempty"` + Description string `json:"description,omitempty"` // manufacturer description (optional) + Unit string `json:"unit,omitempty"` MaxLen int `json:"maxLen,omitempty"` MaxValue interface{} `json:"maxValue,omitempty"` MinValue interface{} `json:"minValue,omitempty"` @@ -268,13 +267,26 @@ func (c *C) MarshalJSON() ([]byte, error) { ValidRange: c.ValidRange, } + // If the characteristic is readable, the value + // must be present in the json representation. if c.IsReadable() { - d.Value = c.value() + // 2022-03-21 (mah) FIXME provide a http request instead of nil + if v, s := c.ValueRequest(nil); s == 0 { + d.Value = &V{v} + } } return json.Marshal(&d) } +type V struct { + Value interface{} +} + +func (v V) MarshalJSON() ([]byte, error) { + return json.Marshal(v.Value) +} + func (c *C) clampFloat(value float64) interface{} { min, minOK := c.MinVal.(float64) max, maxOK := c.MaxVal.(float64) diff --git a/characteristic/programmable_switch_event.go b/characteristic/programmable_switch_event.go index caade19..aec07ae 100644 --- a/characteristic/programmable_switch_event.go +++ b/characteristic/programmable_switch_event.go @@ -1,6 +1,8 @@ package characteristic -// THIS FILE IS AUTO-GENERATED +import ( + "net/http" +) const ( ProgrammableSwitchEventSinglePress int = 0 @@ -18,8 +20,13 @@ func NewProgrammableSwitchEvent() *ProgrammableSwitchEvent { c := NewInt(TypeProgrammableSwitchEvent) c.Format = FormatUInt8 c.Permissions = []string{PermissionRead, PermissionEvents} - c.SetValue(0) + + // always return nil (HAP 9.75) + c.ValueRequestFunc = func(*http.Request) (interface{}, int) { + return nil, 0 + } + c.updateOnSameValue = true return &ProgrammableSwitchEvent{c} diff --git a/characteristics.go b/characteristics.go index 27bd1a7..9c03fe1 100644 --- a/characteristics.go +++ b/characteristics.go @@ -14,7 +14,7 @@ import ( type characteristicData struct { Aid uint64 `json:"aid"` Iid uint64 `json:"iid"` - Value interface{} `json:"value,omitempty"` + Value interface{} `json:"value"` // optional values Type *string `json:"type,omitempty"` @@ -29,6 +29,15 @@ type characteristicData struct { MaxLen *int `json:"maxLen,omitempty"` ValidValues []int `json:"valid-values,omitempty"` ValidRange []int `json:"valid-values-range,omitempty"` +} + +type putCharacteristicData struct { + Aid uint64 `json:"aid"` + Iid uint64 `json:"iid"` + + Value interface{} `json:"value,omitempty"` + Status *int `json:"status,omitempty"` + Events *bool `json:"ev,omitempty"` Remote *bool `json:"remote,omitempty"` Response *bool `json:"r,omitempty"` @@ -147,7 +156,7 @@ func (srv *Server) putCharacteristics(res http.ResponseWriter, req *http.Request } data := struct { - Cs []characteristicData `json:"characteristics"` + Cs []putCharacteristicData `json:"characteristics"` }{} err := json.NewDecoder(req.Body).Decode(&data) if err != nil { @@ -157,10 +166,10 @@ func (srv *Server) putCharacteristics(res http.ResponseWriter, req *http.Request log.Debug.Println(toJSON(data)) - arr := []*characteristicData{} + arr := []*putCharacteristicData{} for _, d := range data.Cs { c := srv.findC(d.Aid, d.Iid) - cdata := &characteristicData{ + cdata := &putCharacteristicData{ Aid: d.Aid, Iid: d.Iid, } @@ -208,7 +217,7 @@ func (srv *Server) putCharacteristics(res http.ResponseWriter, req *http.Request } resp := struct { - Characteristics []*characteristicData `json:"characteristics"` + Characteristics []*putCharacteristicData `json:"characteristics"` }{arr} log.Debug.Println(toJSON(resp)) diff --git a/server.go b/server.go index 89c01a1..1495495 100644 --- a/server.go +++ b/server.go @@ -65,6 +65,8 @@ type ServeMux interface { Handle(pattern string, handler http.Handler) // HandleFuncs registers the handler function for the given pattern. HandleFunc(pattern string, handler http.HandlerFunc) + // Mount attaches another http.Handler along ./pattern/* + Mount(pattern string, handler http.Handler) } // NewServer returns a new server given a store (to persist data) and accessories. diff --git a/server_test.go b/server_test.go index 1878dd0..b66f0a8 100644 --- a/server_test.go +++ b/server_test.go @@ -167,3 +167,35 @@ func TestSetValueRequestFailure(t *testing.T) { t.Fatalf("%v != %v", is, want) } } + +func TestGetProgrammableSwitchEvent(t *testing.T) { + a := accessory.New(accessory.Info{Name: "ABC"}, accessory.TypeProgrammableSwitch) + s := service.NewStatelessProgrammableSwitch() + c := s.ProgrammableSwitchEvent + a.AddS(s.S) + srv, err := NewServer(NewMemStore(), a) + if err != nil { + t.Fatal(err) + } + + req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/characteristics?id=%d.%d", a.Id, c.Id), nil) + w := httptest.NewRecorder() + + setSession(req.RemoteAddr, &session{}) + srv.ss.Handler.ServeHTTP(w, req) + + r := w.Result() + if is, want := r.StatusCode, http.StatusOK; is != want { + t.Fatalf("%v != %v", is, want) + } + + b, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Fatal(err) + } + + body := fmt.Sprintf("{\"characteristics\":[{\"aid\":%d,\"iid\":%d,\"value\":null}]}", a.Id, c.Id) + if is, want := string(b), body; is != want { + t.Fatalf("%v != %v", is, want) + } +} From 0e64553efafdc364e04813c4591f748f5e803f17 Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Mon, 11 Apr 2022 11:08:19 +0200 Subject: [PATCH 04/58] Reorder imports --- server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.go b/server.go index 1495495..c2a54fb 100644 --- a/server.go +++ b/server.go @@ -7,6 +7,7 @@ import ( "github.com/brutella/hap/log" "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" + "github.com/xiam/to" "golang.org/x/text/secure/precis" "golang.org/x/text/transform" "golang.org/x/text/unicode/norm" @@ -17,7 +18,6 @@ import ( "encoding/base64" "errors" "fmt" - "github.com/xiam/to" "net" "net/http" "reflect" From 7cb78d82b148f2276e83bbd137a7d88bcb719bca Mon Sep 17 00:00:00 2001 From: Robert Blenkinsopp Date: Fri, 13 May 2022 16:52:32 +0100 Subject: [PATCH 05/58] Made accessories code more consistent Changes to a single style for all accessories setup, including using AddS() rather than a manual append(). Also cleans up spacing and formatting. --- accessory/air_purifier.go | 1 + accessory/camera.go | 1 + accessory/colored_lightbulb.go | 12 +++++------- accessory/cooler.go | 1 + accessory/dehumidifier.go | 13 +++++-------- accessory/door.go | 13 +++++-------- accessory/fan.go | 13 +++++-------- accessory/faucet.go | 13 +++++-------- accessory/garage_door_opener.go | 13 +++++-------- accessory/heater.go | 1 + accessory/humidifier.go | 13 +++++-------- accessory/lightbulb.go | 12 +++++------- accessory/outlet.go | 13 +++++-------- accessory/security_system.go | 13 +++++-------- accessory/switch.go | 1 + accessory/television.go | 5 +++-- accessory/thermometer.go | 3 +-- accessory/thermostat.go | 3 +-- accessory/window.go | 1 + accessory/window_covering.go | 1 + 20 files changed, 62 insertions(+), 84 deletions(-) diff --git a/accessory/air_purifier.go b/accessory/air_purifier.go index 18c0589..0c8219a 100644 --- a/accessory/air_purifier.go +++ b/accessory/air_purifier.go @@ -13,6 +13,7 @@ type AirPurifier struct { func NewAirPurifier(info Info) *AirPurifier { a := AirPurifier{} a.A = New(info, TypeAirPurifier) + a.AirPurifier = service.NewAirPurifier() a.AddS(a.AirPurifier.S) diff --git a/accessory/camera.go b/accessory/camera.go index a301c6d..eb8e530 100644 --- a/accessory/camera.go +++ b/accessory/camera.go @@ -16,6 +16,7 @@ type Camera struct { func NewCamera(info Info) *Camera { a := Camera{} a.A = New(info, TypeIPCamera) + a.Control = service.NewCameraControl() a.AddS(a.Control.S) diff --git a/accessory/colored_lightbulb.go b/accessory/colored_lightbulb.go index 3bdc9f1..eafcd97 100644 --- a/accessory/colored_lightbulb.go +++ b/accessory/colored_lightbulb.go @@ -11,13 +11,11 @@ type ColoredLightbulb struct { // NewLightbulb returns an light bulb accessory. func NewColoredLightbulb(info Info) *ColoredLightbulb { - a := New(info, TypeLightbulb) + a := ColoredLightbulb{} + a.A = New(info, TypeLightbulb) - l := service.NewColoredLightbulb() - a.Ss = append(a.Ss, l.S) + a.Lightbulb = service.NewColoredLightbulb() + a.AddS(a.Lightbulb.S) - return &ColoredLightbulb{ - A: a, - Lightbulb: l, - } + return &a } diff --git a/accessory/cooler.go b/accessory/cooler.go index 38e86b5..e10c48b 100644 --- a/accessory/cooler.go +++ b/accessory/cooler.go @@ -13,6 +13,7 @@ type Cooler struct { func NewCooler(info Info) *Cooler { a := Cooler{} a.A = New(info, TypeAirConditioner) + a.Cooler = service.NewCooler() a.AddS(a.Cooler.S) diff --git a/accessory/dehumidifier.go b/accessory/dehumidifier.go index 9557ec0..a0e4648 100644 --- a/accessory/dehumidifier.go +++ b/accessory/dehumidifier.go @@ -6,19 +6,16 @@ import ( type Dehumidifier struct { *A - Dehumidifier *service.Dehumidifier } // NewDehumidifier returns an outlet accessory. func NewDehumidifier(info Info) *Dehumidifier { - a := New(info, TypeDehumidifier) + a := Dehumidifier{} + a.A = New(info, TypeDehumidifier) - d := service.NewDehumidifier() - a.Ss = append(a.Ss, d.S) + a.Dehumidifier = service.NewDehumidifier() + a.AddS(a.Dehumidifier.S) - return &Dehumidifier{ - A: a, - Dehumidifier: d, - } + return &a } diff --git a/accessory/door.go b/accessory/door.go index 2b197da..29bce51 100644 --- a/accessory/door.go +++ b/accessory/door.go @@ -6,19 +6,16 @@ import ( type Door struct { *A - Door *service.Door } // NewDoor returns a door accessory. func NewDoor(info Info) *Door { - a := New(info, TypeDoor) + a := Door{} + a.A = New(info, TypeDoor) - door := service.NewDoor() - a.Ss = append(a.Ss, door.S) + a.Door = service.NewDoor() + a.AddS(a.Door.S) - return &Door{ - A: a, - Door: door, - } + return &a } diff --git a/accessory/fan.go b/accessory/fan.go index 8882796..5409d5d 100644 --- a/accessory/fan.go +++ b/accessory/fan.go @@ -6,19 +6,16 @@ import ( type Fan struct { *A - Fan *service.Fan } // NewFan returns a fan accessory. func NewFan(info Info) *Fan { - a := New(info, TypeFan) + a := Fan{} + a.A = New(info, TypeFan) - fan := service.NewFan() - a.Ss = append(a.Ss, fan.S) + a.Fan = service.NewFan() + a.AddS(a.Fan.S) - return &Fan{ - A: a, - Fan: fan, - } + return &a } diff --git a/accessory/faucet.go b/accessory/faucet.go index ef4b405..810c0ab 100644 --- a/accessory/faucet.go +++ b/accessory/faucet.go @@ -6,19 +6,16 @@ import ( type Faucet struct { *A - Faucet *service.Faucet } // NewFaucet returns an outlet accessory. func NewFaucet(info Info) *Faucet { - a := New(info, TypeFaucet) + a := Faucet{} + a.A = New(info, TypeFaucet) - faucet := service.NewFaucet() - a.Ss = append(a.Ss, faucet.S) + a.Faucet = service.NewFaucet() + a.AddS(a.Faucet.S) - return &Faucet{ - A: a, - Faucet: faucet, - } + return &a } diff --git a/accessory/garage_door_opener.go b/accessory/garage_door_opener.go index 121d8e3..ed1c31e 100644 --- a/accessory/garage_door_opener.go +++ b/accessory/garage_door_opener.go @@ -6,19 +6,16 @@ import ( type GarageDoorOpener struct { *A - GarageDoorOpener *service.GarageDoorOpener } // NewGarageDoorOpener returns a garage door opener accessory. func NewGarageDoorOpener(info Info) *GarageDoorOpener { - a := New(info, TypeGarageDoorOpener) + a := GarageDoorOpener{} + a.A = New(info, TypeGarageDoorOpener) - garage := service.NewGarageDoorOpener() - a.Ss = append(a.Ss, garage.S) + a.GarageDoorOpener = service.NewGarageDoorOpener() + a.AddS(a.GarageDoorOpener.S) - return &GarageDoorOpener{ - A: a, - GarageDoorOpener: garage, - } + return &a } diff --git a/accessory/heater.go b/accessory/heater.go index 5446f1c..be0d8aa 100644 --- a/accessory/heater.go +++ b/accessory/heater.go @@ -13,6 +13,7 @@ type Heater struct { func NewHeater(info Info) *Heater { a := Heater{} a.A = New(info, TypeHeater) + a.Heater = service.NewHeater() a.AddS(a.Heater.S) diff --git a/accessory/humidifier.go b/accessory/humidifier.go index cd97cdd..42ffd8e 100644 --- a/accessory/humidifier.go +++ b/accessory/humidifier.go @@ -6,19 +6,16 @@ import ( type Humidifier struct { *A - Humidifier *service.Humidifier } // NewHumidifier returns an outlet accessory. func NewHumidifier(info Info) *Humidifier { - a := New(info, TypeHumidifier) + a := Humidifier{} + a.A = New(info, TypeHumidifier) - h := service.NewHumidifier() - a.Ss = append(a.Ss, h.S) + a.Humidifier = service.NewHumidifier() + a.AddS(a.Humidifier.S) - return &Humidifier{ - A: a, - Humidifier: h, - } + return &a } diff --git a/accessory/lightbulb.go b/accessory/lightbulb.go index 162f182..308a667 100644 --- a/accessory/lightbulb.go +++ b/accessory/lightbulb.go @@ -11,13 +11,11 @@ type Lightbulb struct { // NewLightbulb returns an light bulb accessory. func NewLightbulb(info Info) *Lightbulb { - a := New(info, TypeLightbulb) + a := Lightbulb{} + a.A = New(info, TypeLightbulb) - l := service.NewLightbulb() - a.AddS(l.S) + a.Lightbulb = service.NewLightbulb() + a.AddS(a.Lightbulb.S) - return &Lightbulb{ - A: a, - Lightbulb: l, - } + return &a } diff --git a/accessory/outlet.go b/accessory/outlet.go index 08e666c..bb91136 100644 --- a/accessory/outlet.go +++ b/accessory/outlet.go @@ -6,19 +6,16 @@ import ( type Outlet struct { *A - Outlet *service.Outlet } // NewOutlet returns an outlet accessory. func NewOutlet(info Info) *Outlet { - a := New(info, TypeOutlet) + a := Outlet{} + a.A = New(info, TypeOutlet) - outlet := service.NewOutlet() - a.Ss = append(a.Ss, outlet.S) + a.Outlet = service.NewOutlet() + a.AddS(a.Outlet.S) - return &Outlet{ - A: a, - Outlet: outlet, - } + return &a } diff --git a/accessory/security_system.go b/accessory/security_system.go index 4640435..d8f8f7b 100644 --- a/accessory/security_system.go +++ b/accessory/security_system.go @@ -6,19 +6,16 @@ import ( type SecuritySystem struct { *A - SecuritySystem *service.SecuritySystem } // NewSecuritySystem returns a security system accessory. func NewSecuritySystem(info Info) *SecuritySystem { - a := New(info, TypeSecuritySystem) + a := SecuritySystem{} + a.A = New(info, TypeSecuritySystem) - garage := service.NewSecuritySystem() - a.Ss = append(a.Ss, garage.S) + a.SecuritySystem = service.NewSecuritySystem() + a.AddS(a.SecuritySystem.S) - return &SecuritySystem{ - A: a, - SecuritySystem: garage, - } + return &a } diff --git a/accessory/switch.go b/accessory/switch.go index 758b119..8fcfacf 100644 --- a/accessory/switch.go +++ b/accessory/switch.go @@ -13,6 +13,7 @@ type Switch struct { func NewSwitch(info Info) *Switch { a := Switch{} a.A = New(info, TypeSwitch) + a.Switch = service.NewSwitch() a.AddS(a.Switch.S) diff --git a/accessory/television.go b/accessory/television.go index ad10f1a..2a40512 100644 --- a/accessory/television.go +++ b/accessory/television.go @@ -14,10 +14,11 @@ type Television struct { func NewTelevision(info Info) *Television { a := Television{} a.A = New(info, TypeTelevision) - a.Television = service.NewTelevision() - a.Speaker = service.NewSpeaker() + a.Television = service.NewTelevision() a.AddS(a.Television.S) + + a.Speaker = service.NewSpeaker() a.AddS(a.Speaker.S) return &a diff --git a/accessory/thermometer.go b/accessory/thermometer.go index 38d7a26..3f94340 100644 --- a/accessory/thermometer.go +++ b/accessory/thermometer.go @@ -6,7 +6,6 @@ import ( type Thermometer struct { *A - TempSensor *service.TemperatureSensor } @@ -14,8 +13,8 @@ type Thermometer struct { func NewTemperatureSensor(info Info) *Thermometer { a := Thermometer{} a.A = New(info, TypeThermostat) - a.TempSensor = service.NewTemperatureSensor() + a.TempSensor = service.NewTemperatureSensor() a.AddS(a.TempSensor.S) return &a diff --git a/accessory/thermostat.go b/accessory/thermostat.go index edc691b..43c50de 100644 --- a/accessory/thermostat.go +++ b/accessory/thermostat.go @@ -6,7 +6,6 @@ import ( type Thermostat struct { *A - Thermostat *service.Thermostat } @@ -14,8 +13,8 @@ type Thermostat struct { func NewThermostat(info Info) *Thermostat { a := Thermostat{} a.A = New(info, TypeThermostat) - a.Thermostat = service.NewThermostat() + a.Thermostat = service.NewThermostat() a.AddS(a.Thermostat.S) return &a diff --git a/accessory/window.go b/accessory/window.go index 9935344..6e6a0ec 100644 --- a/accessory/window.go +++ b/accessory/window.go @@ -13,6 +13,7 @@ type Window struct { func NewWindow(info Info) *Window { a := Window{} a.A = New(info, TypeWindow) + a.Window = service.NewWindow() a.AddS(a.Window.S) diff --git a/accessory/window_covering.go b/accessory/window_covering.go index 2042f42..f94af60 100644 --- a/accessory/window_covering.go +++ b/accessory/window_covering.go @@ -13,6 +13,7 @@ type WindowCovering struct { func NewWindowCovering(info Info) *WindowCovering { a := WindowCovering{} a.A = New(info, TypeWindowCovering) + a.WindowCovering = service.NewWindowCovering() a.AddS(a.WindowCovering.S) From 963e98336c4006d6ac6d09184a9e1d6ebe50fcff Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Tue, 7 Jun 2022 15:15:03 +0200 Subject: [PATCH 06/58] Change SetValueRequestFunc to returns a response --- characteristic/bool.go | 6 +++--- characteristic/bytes.go | 6 +++--- characteristic/c.go | 22 ++++++++++++---------- characteristic/c_test.go | 8 ++++---- characteristic/float.go | 6 +++--- characteristic/int.go | 8 ++++---- characteristic/string.go | 6 +++--- characteristics.go | 19 +++++++++---------- server_test.go | 8 ++++---- 9 files changed, 45 insertions(+), 44 deletions(-) diff --git a/characteristic/bool.go b/characteristic/bool.go index 0c9aebd..bcda748 100644 --- a/characteristic/bool.go +++ b/characteristic/bool.go @@ -32,12 +32,12 @@ func (c *Bool) Value() bool { // If the function returns an error, the code -70402 is // included in the HTTP response. func (c *Bool) OnSetRemoteValue(fn func(v bool) error) { - c.SetValueRequestFunc = func(v interface{}, r *http.Request) int { + c.SetValueRequestFunc = func(v interface{}, r *http.Request) (interface{}, int) { if err := fn(v.(bool)); err != nil { log.Debug.Println(err) - return -70402 + return c.Val, -70402 } - return 0 + return c.Val, 0 } } diff --git a/characteristic/bytes.go b/characteristic/bytes.go index 80db518..9d5c45a 100644 --- a/characteristic/bytes.go +++ b/characteristic/bytes.go @@ -37,13 +37,13 @@ func (c *Bytes) Value() []byte { // If the function returns an error, the code -70402 is // included in the HTTP response. func (c *Bytes) OnSetRemoteValue(fn func(v []byte) error) { - c.SetValueRequestFunc = func(v interface{}, r *http.Request) int { + c.SetValueRequestFunc = func(v interface{}, r *http.Request) (interface{}, int) { str, _ := base64.StdEncoding.DecodeString(v.(string)) if err := fn(str); err != nil { log.Debug.Println(err) - return -70402 + return c.Value(), -70402 } - return 0 + return c.Value(), 0 } } diff --git a/characteristic/c.go b/characteristic/c.go index 0c9a916..790349e 100644 --- a/characteristic/c.go +++ b/characteristic/c.go @@ -101,7 +101,7 @@ type C struct { // If the communication fails, you can return a code != 0. // In this case, the server responds with the HTTP status code 500 and the code // in the response body (as defined in HAP-R2 6.7.1.4 HAP Status Codes). - SetValueRequestFunc func(value interface{}, request *http.Request) int + SetValueRequestFunc func(value interface{}, request *http.Request) (response interface{}, code int) // A list of update value functions. // There are called when the value of the characteristic is updated. @@ -129,19 +129,19 @@ func (c *C) OnCValueUpdate(fn ValueUpdateFunc) { // Sets the value of c to val and returns a status code. // The server invokes this function when the value is updated by an http request. -func (c *C) SetValueRequest(val interface{}, req *http.Request) int { +func (c *C) SetValueRequest(val interface{}, req *http.Request) (interface{}, int) { // check write permission if !c.IsWritable() { log.Info.Printf("writing %v by %s not allowed\n", val, req.RemoteAddr) - return -70404 + return val, -70404 } return c.setValue(val, req) } -func (c *C) setValue(v interface{}, req *http.Request) int { +func (c *C) setValue(v interface{}, req *http.Request) (interface{}, int) { newVal := c.convert(v) - + response := newVal // Value must be within min and max switch c.Format { case FormatFloat: @@ -153,17 +153,19 @@ func (c *C) setValue(v interface{}, req *http.Request) int { // ignore the same newVal if c.Val == newVal && !c.updateOnSameValue { // no error - return 0 + return nil, 0 } if !c.validVal(newVal) { - return -70410 + return nil, -70410 } if c.SetValueRequestFunc != nil && req != nil { - if s := c.SetValueRequestFunc(newVal, req); s != 0 { - return s + v, c := c.SetValueRequestFunc(newVal, req) + if c != 0 { + return v, c } + response = v } // reference old value @@ -177,7 +179,7 @@ func (c *C) setValue(v interface{}, req *http.Request) int { fn(c, newVal, oldVal, req) } - return 0 + return response, 0 } // ValueRequest returns the value of C and a status code. diff --git a/characteristic/c_test.go b/characteristic/c_test.go index 6363962..c1e591b 100644 --- a/characteristic/c_test.go +++ b/characteristic/c_test.go @@ -124,15 +124,15 @@ func TestSetValueRequestFunc(t *testing.T) { c := NewBrightness() c.SetValue(100) - c.SetValueRequestFunc = func(v interface{}, r *http.Request) int { + c.SetValueRequestFunc = func(v interface{}, r *http.Request) (response interface{}, status int) { if r != nil { - return -70408 + status = -70408 } - return 0 + return } - s := c.SetValueRequest(50, &http.Request{}) + _, s := c.SetValueRequest(50, &http.Request{}) if is, want := s, -70408; is != want { t.Fatalf("%v != %v", is, want) } diff --git a/characteristic/float.go b/characteristic/float.go index 22c5ea6..1e06f6c 100644 --- a/characteristic/float.go +++ b/characteristic/float.go @@ -54,12 +54,12 @@ func (c *Float) StepValue() float64 { // If the function returns an error, the code -70402 is // included in the HTTP response. func (c *Float) OnSetRemoteValue(fn func(v float64) error) { - c.SetValueRequestFunc = func(v interface{}, r *http.Request) int { + c.SetValueRequestFunc = func(v interface{}, r *http.Request) (interface{}, int) { if err := fn(v.(float64)); err != nil { log.Debug.Println(err) - return -70402 + return c.Val, -70402 } - return 0 + return c.Val, 0 } } diff --git a/characteristic/int.go b/characteristic/int.go index 509e064..f0751a3 100644 --- a/characteristic/int.go +++ b/characteristic/int.go @@ -19,7 +19,7 @@ func NewInt(t string) *Int { // SetValue sets a value func (c *Int) SetValue(v int) error { - code := c.setValue(v, nil) + _, code := c.setValue(v, nil) switch code { case -70410: return fmt.Errorf("invalid value %d", v) @@ -63,12 +63,12 @@ func (c *Int) StepValue() int { // If the function returns an error, the code -70402 is // included in the HTTP response. func (c *Int) OnSetRemoteValue(fn func(v int) error) { - c.SetValueRequestFunc = func(v interface{}, r *http.Request) int { + c.SetValueRequestFunc = func(v interface{}, r *http.Request) (interface{}, int) { if err := fn(v.(int)); err != nil { log.Debug.Println(err) - return -70402 + return c.Val, -70402 } - return 0 + return c.Val, 0 } } diff --git a/characteristic/string.go b/characteristic/string.go index bb0b1b5..d04d82e 100644 --- a/characteristic/string.go +++ b/characteristic/string.go @@ -32,12 +32,12 @@ func (c *String) Value() string { // If the function returns an error, the code -70402 is // included in the HTTP response. func (c *String) OnSetRemoteValue(fn func(v string) error) { - c.SetValueRequestFunc = func(v interface{}, r *http.Request) int { + c.SetValueRequestFunc = func(v interface{}, r *http.Request) (interface{}, int) { if err := fn(v.(string)); err != nil { log.Debug.Println(err) - return -70402 + return c.Val, -70402 } - return 0 + return c.Val, 0 } } diff --git a/characteristics.go b/characteristics.go index 9c03fe1..1f6f72d 100644 --- a/characteristics.go +++ b/characteristics.go @@ -181,19 +181,18 @@ func (srv *Server) putCharacteristics(res http.ResponseWriter, req *http.Request continue } + var value interface{} + var status int if d.Value != nil { - s := c.SetValueRequest(d.Value, req) - if s != 0 { - cdata.Status = &s - } + value, status = c.SetValueRequest(d.Value, req) } - if d.Response != nil { - if v, s := c.ValueRequest(req); s != 0 { - cdata.Status = &s - } else { - cdata.Value = v - } + if status != 0 { + cdata.Status = &status + } + + if d.Response != nil && value != nil { + cdata.Value = value } if d.Events != nil { diff --git a/server_test.go b/server_test.go index b66f0a8..6cafba5 100644 --- a/server_test.go +++ b/server_test.go @@ -87,13 +87,13 @@ func TestSetValueRequestSuccess(t *testing.T) { setSession(req.RemoteAddr, &session{}) var setValueRequestFunc, onValueUpdateFunc bool - a.Outlet.On.SetValueRequestFunc = func(v interface{}, r *http.Request) int { + a.Outlet.On.SetValueRequestFunc = func(v interface{}, r *http.Request) (interface{}, int) { if is, want := v.(bool), true; is != want { t.Fatalf("%v != %v", is, want) } setValueRequestFunc = true - return 0 + return v, 0 } a.Outlet.On.OnValueUpdate(func(new bool, old bool, r *http.Request) { @@ -142,8 +142,8 @@ func TestSetValueRequestFailure(t *testing.T) { setSession(req.RemoteAddr, &session{}) - a.Outlet.On.SetValueRequestFunc = func(v interface{}, r *http.Request) int { - return JsonStatusResourceBusy + a.Outlet.On.SetValueRequestFunc = func(v interface{}, r *http.Request) (interface{}, int) { + return nil, JsonStatusResourceBusy } s.ss.Handler.ServeHTTP(w, req) From f2675ff9cc74bb790b13fa11c8bad85a57af4b79 Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Tue, 21 Jun 2022 10:07:07 +0200 Subject: [PATCH 07/58] Fix issue where response is the old value of C --- characteristic/bool.go | 4 ++-- characteristic/bytes.go | 4 ++-- characteristic/c.go | 5 ++++- characteristic/c_test.go | 20 +++++++++++++++++++- characteristic/float.go | 4 ++-- characteristic/int.go | 4 ++-- characteristic/string.go | 4 ++-- 7 files changed, 33 insertions(+), 12 deletions(-) diff --git a/characteristic/bool.go b/characteristic/bool.go index bcda748..126f4f4 100644 --- a/characteristic/bool.go +++ b/characteristic/bool.go @@ -35,9 +35,9 @@ func (c *Bool) OnSetRemoteValue(fn func(v bool) error) { c.SetValueRequestFunc = func(v interface{}, r *http.Request) (interface{}, int) { if err := fn(v.(bool)); err != nil { log.Debug.Println(err) - return c.Val, -70402 + return nil, -70402 } - return c.Val, 0 + return nil, 0 } } diff --git a/characteristic/bytes.go b/characteristic/bytes.go index 9d5c45a..e847dda 100644 --- a/characteristic/bytes.go +++ b/characteristic/bytes.go @@ -41,9 +41,9 @@ func (c *Bytes) OnSetRemoteValue(fn func(v []byte) error) { str, _ := base64.StdEncoding.DecodeString(v.(string)) if err := fn(str); err != nil { log.Debug.Println(err) - return c.Value(), -70402 + return nil, -70402 } - return c.Value(), 0 + return nil, 0 } } diff --git a/characteristic/c.go b/characteristic/c.go index 790349e..3e01d59 100644 --- a/characteristic/c.go +++ b/characteristic/c.go @@ -165,7 +165,10 @@ func (c *C) setValue(v interface{}, req *http.Request) (interface{}, int) { if c != 0 { return v, c } - response = v + + if v != nil { + response = v + } } // reference old value diff --git a/characteristic/c_test.go b/characteristic/c_test.go index c1e591b..29d34e7 100644 --- a/characteristic/c_test.go +++ b/characteristic/c_test.go @@ -120,7 +120,7 @@ func TestReadOnly(t *testing.T) { } } -func TestSetValueRequestFunc(t *testing.T) { +func TestSetValueRequestFuncError(t *testing.T) { c := NewBrightness() c.SetValue(100) @@ -142,6 +142,24 @@ func TestSetValueRequestFunc(t *testing.T) { } } +func TestOnSetRemoteValue(t *testing.T) { + c := NewBrightness() + + c.SetValue(100) + c.OnSetRemoteValue(func(v int) error { + return nil + }) + + v, s := c.SetValueRequest(50, &http.Request{}) + if is, want := s, 0; is != want { + t.Fatalf("%v != %v", is, want) + } + + if is, want := v, 50; is != want { + t.Fatalf("is=%v want=%v", is, want) + } +} + func TestValidValues(t *testing.T) { c := NewTargetHeaterCoolerState() c.ValidVals = []int{TargetHeaterCoolerStateAuto, TargetHeaterCoolerStateHeat} diff --git a/characteristic/float.go b/characteristic/float.go index 1e06f6c..582316a 100644 --- a/characteristic/float.go +++ b/characteristic/float.go @@ -57,9 +57,9 @@ func (c *Float) OnSetRemoteValue(fn func(v float64) error) { c.SetValueRequestFunc = func(v interface{}, r *http.Request) (interface{}, int) { if err := fn(v.(float64)); err != nil { log.Debug.Println(err) - return c.Val, -70402 + return nil, -70402 } - return c.Val, 0 + return nil, 0 } } diff --git a/characteristic/int.go b/characteristic/int.go index f0751a3..2381a18 100644 --- a/characteristic/int.go +++ b/characteristic/int.go @@ -66,9 +66,9 @@ func (c *Int) OnSetRemoteValue(fn func(v int) error) { c.SetValueRequestFunc = func(v interface{}, r *http.Request) (interface{}, int) { if err := fn(v.(int)); err != nil { log.Debug.Println(err) - return c.Val, -70402 + return nil, -70402 } - return c.Val, 0 + return nil, 0 } } diff --git a/characteristic/string.go b/characteristic/string.go index d04d82e..04ca235 100644 --- a/characteristic/string.go +++ b/characteristic/string.go @@ -35,9 +35,9 @@ func (c *String) OnSetRemoteValue(fn func(v string) error) { c.SetValueRequestFunc = func(v interface{}, r *http.Request) (interface{}, int) { if err := fn(v.(string)); err != nil { log.Debug.Println(err) - return c.Val, -70402 + return nil, -70402 } - return c.Val, 0 + return nil, 0 } } From 947153d27526ff5b2e2140d6e973efe7310a35d6 Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Tue, 21 Jun 2022 12:20:02 +0200 Subject: [PATCH 08/58] Revert dnssd to v1.2.1 --- go.mod | 2 +- go.sum | 35 +++++++++-------------------------- 2 files changed, 10 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index 6fea573..1a36c02 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/brutella/hap go 1.16 require ( - github.com/brutella/dnssd v1.2.2 + github.com/brutella/dnssd v1.2.1 github.com/go-chi/chi v1.5.4 github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9 github.com/xiam/to v0.0.0-20200126224905-d60d31e03561 diff --git a/go.sum b/go.sum index 2ecccba..bef6f03 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,11 @@ -github.com/brutella/dnssd v1.2.2 h1:9T6YPay8kD9+nSiX2AG9YfILAbwUll95sh7HhFShatI= -github.com/brutella/dnssd v1.2.2/go.mod h1:JjeBG7F+bH5iIKQ42H9QH0foPmP0L/nHZZaFMbrD5Wg= +github.com/brutella/dnssd v1.2.1 h1:1xG+5itx/SDEP6ukYfAcBnox5WACTNvxZ+SMkAmSrFU= +github.com/brutella/dnssd v1.2.1/go.mod h1:FpJqlQ8+XU6w1vbnG1zJiQPTRE5fvQIRdrcBojMVuuQ= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs= github.com/go-chi/chi v1.5.4/go.mod h1:uaf8YgoFazUOkPBG7fxPftUylNumIev9awIWOENIuEg= -github.com/miekg/dns v1.1.46 h1:uzwpxRtSVxtcIZmz/4Uz6/Rn7G11DvsaslXoy5LxQio= -github.com/miekg/dns v1.1.46/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= +github.com/miekg/dns v1.1.1 h1:DVkblRdiScEnEr0LR9nTnEQqHYycjkXW9bOjd+2EL2o= +github.com/miekg/dns v1.1.1/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -15,33 +15,23 @@ github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9 h1:aeN+ghOV0b2VC github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9/go.mod h1:roo6cZ/uqpwKMuvPG0YmzI5+AmUiMWfjCBZpGXqbTxE= github.com/xiam/to v0.0.0-20200126224905-d60d31e03561 h1:SVoNK97S6JlaYlHcaC+79tg3JUlQABcc0dH2VQ4Y+9s= github.com/xiam/to v0.0.0-20200126224905-d60d31e03561/go.mod h1:cqbG7phSzrbdg3aj+Kn63bpVruzwDZi58CpxlZkjwzw= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 h1:71vQrMauZZhcTVK6KdYM+rklehEEwb3E+ZhaE5jrPrE= golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -50,13 +40,6 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 h1:BonxutuHCTL0rBDnZlKjpGIQFTjyUVTexFOdWkB6Fg0= -golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From cc1edd42740b96416b7747d776cd1a1e128e6b47 Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Mon, 4 Jul 2022 21:05:23 +0200 Subject: [PATCH 09/58] Update to dnssd v1.2.3 --- go.mod | 2 +- go.sum | 38 ++++++++++++++++++++++++++------------ 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index 1a36c02..a693f78 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/brutella/hap go 1.16 require ( - github.com/brutella/dnssd v1.2.1 + github.com/brutella/dnssd v1.2.3 github.com/go-chi/chi v1.5.4 github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9 github.com/xiam/to v0.0.0-20200126224905-d60d31e03561 diff --git a/go.sum b/go.sum index bef6f03..18a6cc0 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,11 @@ -github.com/brutella/dnssd v1.2.1 h1:1xG+5itx/SDEP6ukYfAcBnox5WACTNvxZ+SMkAmSrFU= -github.com/brutella/dnssd v1.2.1/go.mod h1:FpJqlQ8+XU6w1vbnG1zJiQPTRE5fvQIRdrcBojMVuuQ= +github.com/brutella/dnssd v1.2.3 h1:4fBLjZjPH7SbcHhEcIJhZcC9nOhIDZ0m3rn9bjl1/i0= +github.com/brutella/dnssd v1.2.3/go.mod h1:JoW2sJUrmVIef25G6lrLj7HS6Xdwh6q8WUIvMkkBYXs= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs= github.com/go-chi/chi v1.5.4/go.mod h1:uaf8YgoFazUOkPBG7fxPftUylNumIev9awIWOENIuEg= -github.com/miekg/dns v1.1.1 h1:DVkblRdiScEnEr0LR9nTnEQqHYycjkXW9bOjd+2EL2o= -github.com/miekg/dns v1.1.1/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= +github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -15,24 +15,31 @@ github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9 h1:aeN+ghOV0b2VC github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9/go.mod h1:roo6cZ/uqpwKMuvPG0YmzI5+AmUiMWfjCBZpGXqbTxE= github.com/xiam/to v0.0.0-20200126224905-d60d31e03561 h1:SVoNK97S6JlaYlHcaC+79tg3JUlQABcc0dH2VQ4Y+9s= github.com/xiam/to v0.0.0-20200126224905-d60d31e03561/go.mod h1:cqbG7phSzrbdg3aj+Kn63bpVruzwDZi58CpxlZkjwzw= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 h1:71vQrMauZZhcTVK6KdYM+rklehEEwb3E+ZhaE5jrPrE= golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -40,6 +47,13 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 h1:BonxutuHCTL0rBDnZlKjpGIQFTjyUVTexFOdWkB6Fg0= +golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From d570e21a5b37a646946a924cc8496baa6fc665cd Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Tue, 26 Jul 2022 10:58:42 +0200 Subject: [PATCH 10/58] Fix connection issue when running multiple servers #8 --- pair-setup.go | 8 ++--- pair-verify.go | 6 ++-- pairings.go | 4 +-- server.go | 94 +++++++++++++++++++++++++++++++++++++++++++++----- server_test.go | 8 ++--- session.go | 71 -------------------------------------- 6 files changed, 99 insertions(+), 92 deletions(-) diff --git a/pair-setup.go b/pair-setup.go index c834570..444c3c7 100644 --- a/pair-setup.go +++ b/pair-setup.go @@ -45,7 +45,7 @@ func (srv *Server) pairSetup(res http.ResponseWriter, req *http.Request) { } // pair-setup can only be run by one controller simultaneously - for addr, _ := range sessions() { + for addr, _ := range srv.sessions() { if addr != req.RemoteAddr { log.Info.Printf("simulatenous pairings are not allowed") tlv8Error(res, M2, TlvErrorBusy) @@ -115,7 +115,7 @@ func (srv *Server) pairSetupM1(res http.ResponseWriter, req *http.Request, data tlv8Error(res, M2, TlvErrorUnknown) return } - setSession(req.RemoteAddr, ss) + srv.setSession(req.RemoteAddr, ss) resp := pairSetupM2Payload{ Salt: ss.Salt, @@ -126,7 +126,7 @@ func (srv *Server) pairSetupM1(res http.ResponseWriter, req *http.Request, data } func (srv *Server) pairSetupM3(res http.ResponseWriter, req *http.Request, data pairSetupPayload) { - ses, err := getPairSetupSession(req.RemoteAddr) + ses, err := srv.getPairSetupSession(req.RemoteAddr) if err != nil { log.Info.Println(err) res.WriteHeader(http.StatusInternalServerError) @@ -162,7 +162,7 @@ func (srv *Server) pairSetupM3(res http.ResponseWriter, req *http.Request, data } func (srv *Server) pairSetupM5(res http.ResponseWriter, req *http.Request, data pairSetupPayload) { - ses, err := getPairSetupSession(req.RemoteAddr) + ses, err := srv.getPairSetupSession(req.RemoteAddr) if err != nil { log.Info.Println(err) res.WriteHeader(http.StatusInternalServerError) diff --git a/pair-verify.go b/pair-verify.go index ff0442b..9362904 100644 --- a/pair-verify.go +++ b/pair-verify.go @@ -118,12 +118,12 @@ func (srv *Server) pairVerifyM1(res http.ResponseWriter, req *http.Request, data SharedKey: sharedKey, EncryptionKey: encKey, } - setSession(req.RemoteAddr, ses) + srv.setSession(req.RemoteAddr, ses) } func (srv *Server) pairVerifyM3(res http.ResponseWriter, req *http.Request, data pairVerifyPayload) { // Get the session for the request. - ses, err := getPairVerifySession(req.RemoteAddr) + ses, err := srv.getPairVerifySession(req.RemoteAddr) if err != nil { log.Info.Println(err) res.WriteHeader(http.StatusInternalServerError) @@ -182,7 +182,7 @@ func (srv *Server) pairVerifyM3(res http.ResponseWriter, req *http.Request, data } // Store the session for the request. - setSession(req.RemoteAddr, ss) + srv.setSession(req.RemoteAddr, ss) conn := getConn(req) if conn == nil { diff --git a/pairings.go b/pairings.go index 2200a04..689b638 100644 --- a/pairings.go +++ b/pairings.go @@ -21,7 +21,7 @@ func (srv *Server) pairings(res http.ResponseWriter, req *http.Request) { return } - ss, err := getSession(req.RemoteAddr) + ss, err := srv.getSession(req.RemoteAddr) if err != nil { log.Info.Println(err) res.WriteHeader(http.StatusInternalServerError) @@ -126,7 +126,7 @@ func (srv *Server) pairings(res http.ResponseWriter, req *http.Request) { // Close connection of deleted controller for addr, conn := range conns() { - ss, err := getSession(addr) + ss, err := srv.getSession(addr) if err != nil { log.Debug.Println("no session for", addr, err) continue diff --git a/server.go b/server.go index c2a54fb..20e60e6 100644 --- a/server.go +++ b/server.go @@ -1,6 +1,8 @@ package hap import ( + "sync" + "github.com/brutella/dnssd" "github.com/brutella/hap/accessory" "github.com/brutella/hap/characteristic" @@ -57,6 +59,10 @@ type Server struct { // for dnssd stuff responder dnssd.Responder handle dnssd.ServiceHandle + + mux *sync.Mutex + sess map[string]interface{} + cons map[string]*conn } // A ServeMux lets you attach handlers to http url paths. @@ -81,13 +87,16 @@ func NewServer(store Store, a *accessory.A, as ...*accessory.A) (*Server, error) } s := &Server{ - ss: &http.Server{ - Handler: r, - ConnState: connStateEvent, - }, - st: st, - a: a, - as: as, + st: st, + a: a, + as: as, + mux: &sync.Mutex{}, + sess: make(map[string]interface{}), + cons: make(map[string]*conn), + } + s.ss = &http.Server{ + Handler: r, + ConnState: s.connStateEvent, } // Load the stored uuid or generate a new one. @@ -148,7 +157,7 @@ func (s *Server) ServeMux() ServeMux { // IsAuthorized returns true if the provided // request is authorized to access accessory data. func (s *Server) IsAuthorized(request *http.Request) bool { - ss, _ := getSession(request.RemoteAddr) + ss, _ := s.getSession(request.RemoteAddr) return ss != nil } @@ -324,6 +333,75 @@ func (s *Server) prepare() error { return nil } +func (s *Server) connStateEvent(conn net.Conn, event http.ConnState) { + if event == http.StateClosed { + addr := conn.RemoteAddr().String() + s.mux.Lock() + delete(s.sess, addr) + delete(s.cons, addr) + s.mux.Unlock() + } +} + +func (s *Server) getSession(addr string) (*session, error) { + s.mux.Lock() + defer s.mux.Unlock() + + if v, ok := s.sess[addr]; ok { + if s, ok := v.(*session); ok { + return s, nil + } + return nil, fmt.Errorf("unexpected session %T", v) + } + + return nil, fmt.Errorf("no session for %s", addr) +} + +func (s *Server) getPairVerifySession(addr string) (*pairVerifySession, error) { + s.mux.Lock() + defer s.mux.Unlock() + + if v, ok := s.sess[addr]; ok { + if s, ok := v.(*pairVerifySession); ok { + return s, nil + } + return nil, fmt.Errorf("unexpected session %T", v) + } + + return nil, fmt.Errorf("no session for %s", addr) +} + +func (s *Server) getPairSetupSession(addr string) (*pairSetupSession, error) { + s.mux.Lock() + defer s.mux.Unlock() + + if v, ok := s.sess[addr]; ok { + if s, ok := v.(*pairSetupSession); ok { + return s, nil + } + return nil, fmt.Errorf("unexpected session %T", v) + } + + return nil, fmt.Errorf("no session for %s", addr) +} + +func (s *Server) setSession(addr string, v interface{}) { + s.mux.Lock() + s.sess[addr] = v + s.mux.Unlock() +} + +func (s *Server) sessions() map[string]interface{} { + copy := map[string]interface{}{} + s.mux.Lock() + for k, v := range s.sess { + copy[k] = v + } + s.mux.Unlock() + + return copy +} + func (s *Server) savePairing(p Pairing) error { err := s.st.SavePairing(p) if err != nil { diff --git a/server_test.go b/server_test.go index 6cafba5..74c21d9 100644 --- a/server_test.go +++ b/server_test.go @@ -50,7 +50,7 @@ func TestIdentify(t *testing.T) { req := httptest.NewRequest(http.MethodPost, "/identify", nil) w := httptest.NewRecorder() - setSession(req.RemoteAddr, &session{}) + s.setSession(req.RemoteAddr, &session{}) var identified bool a.IdentifyFunc = func(r *http.Request) { @@ -84,7 +84,7 @@ func TestSetValueRequestSuccess(t *testing.T) { req := httptest.NewRequest(http.MethodPut, "/characteristics", bytes.NewBuffer([]byte(body))) w := httptest.NewRecorder() - setSession(req.RemoteAddr, &session{}) + s.setSession(req.RemoteAddr, &session{}) var setValueRequestFunc, onValueUpdateFunc bool a.Outlet.On.SetValueRequestFunc = func(v interface{}, r *http.Request) (interface{}, int) { @@ -140,7 +140,7 @@ func TestSetValueRequestFailure(t *testing.T) { req := httptest.NewRequest(http.MethodPut, "/characteristics", bytes.NewBuffer([]byte(body))) w := httptest.NewRecorder() - setSession(req.RemoteAddr, &session{}) + s.setSession(req.RemoteAddr, &session{}) a.Outlet.On.SetValueRequestFunc = func(v interface{}, r *http.Request) (interface{}, int) { return nil, JsonStatusResourceBusy @@ -181,7 +181,7 @@ func TestGetProgrammableSwitchEvent(t *testing.T) { req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/characteristics?id=%d.%d", a.Id, c.Id), nil) w := httptest.NewRecorder() - setSession(req.RemoteAddr, &session{}) + srv.setSession(req.RemoteAddr, &session{}) srv.ss.Handler.ServeHTTP(w, req) r := w.Result() diff --git a/session.go b/session.go index 90e4491..f3386df 100644 --- a/session.go +++ b/session.go @@ -8,84 +8,13 @@ import ( "encoding/binary" "fmt" "io" - "net" "net/http" "sync" ) var mux = &sync.Mutex{} -var sess = make(map[string]interface{}) var cons = make(map[string]*conn) -func connStateEvent(conn net.Conn, event http.ConnState) { - if event == http.StateClosed { - addr := conn.RemoteAddr().String() - mux.Lock() - delete(sess, addr) - delete(cons, addr) - mux.Unlock() - } -} - -func getSession(addr string) (*session, error) { - mux.Lock() - defer mux.Unlock() - - if v, ok := sess[addr]; ok { - if s, ok := v.(*session); ok { - return s, nil - } - return nil, fmt.Errorf("unexpected session %T", v) - } - - return nil, fmt.Errorf("no session for %s", addr) -} - -func getPairVerifySession(addr string) (*pairVerifySession, error) { - mux.Lock() - defer mux.Unlock() - - if v, ok := sess[addr]; ok { - if s, ok := v.(*pairVerifySession); ok { - return s, nil - } - return nil, fmt.Errorf("unexpected session %T", v) - } - - return nil, fmt.Errorf("no session for %s", addr) -} - -func getPairSetupSession(addr string) (*pairSetupSession, error) { - mux.Lock() - defer mux.Unlock() - - if v, ok := sess[addr]; ok { - if s, ok := v.(*pairSetupSession); ok { - return s, nil - } - return nil, fmt.Errorf("unexpected session %T", v) - } - - return nil, fmt.Errorf("no session for %s", addr) -} - -func setSession(addr string, v interface{}) { - mux.Lock() - sess[addr] = v - mux.Unlock() -} - -func sessions() map[string]interface{} { - copy := map[string]interface{}{} - mux.Lock() - for k, v := range sess { - copy[k] = v - } - mux.Unlock() - - return copy -} - func setConn(addr string, conn *conn) { mux.Lock() defer mux.Unlock() From ec4722115be93a94c658b8e01cc2956498fb3dd0 Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Tue, 26 Jul 2022 11:08:07 +0200 Subject: [PATCH 11/58] Fix broken link in README.md #3 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 097b481..45b964e 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![GoDoc Widget]][GoDoc] [![Travis Widget]][Travis] `hap` (previously [hc](https://github.com/brutella/hc)) is a lightweight library to develop HomeKit accessories in Go. -It abstracts the **H**omeKit **A**ccessory **P**rotocol (HAP) and makes it easy to work with [services](service/README.md) and [characteristics](characteristic/README.md). +It abstracts the **H**omeKit **A**ccessory **P**rotocol (HAP) and makes it easy to work with [services](service/README.md) and [characteristics](characteristic). `hap` handles the underlying communication between HomeKit accessories and clients. You can focus on implementing the business logic for your accessory, without having to worry about the protocol. From f4baf3fb9e826cc0b19291144acec6467643944b Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Tue, 30 Aug 2022 11:28:25 +0200 Subject: [PATCH 12/58] Check for optional flag in tlv8 field tag #10 --- tlv8/decoder.go | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/tlv8/decoder.go b/tlv8/decoder.go index 42db823..1729202 100644 --- a/tlv8/decoder.go +++ b/tlv8/decoder.go @@ -60,13 +60,14 @@ func (d *decoder) decode(v interface{}) error { if tlv8, ok := eType.Field(i).Tag.Lookup("tlv8"); ok { values := strings.Split(tlv8, ",") tag := uint8(to.Uint64(values[0])) + optional := len(values) > 1 && values[1] == "optional" field := eValue.Field(i) switch value := field.Interface().(type) { case uint8: if v, err := d.r.readByte(tag); err == nil { field.SetUint(uint64(v)) - } else if err == io.EOF { + } else if err == io.EOF && optional { continue } else { return err @@ -74,7 +75,7 @@ func (d *decoder) decode(v interface{}) error { case uint16: if v, err := d.r.readUint16(tag); err == nil { field.SetUint(uint64(v)) - } else if err == io.EOF { + } else if err == io.EOF && optional { continue } else { return err @@ -83,7 +84,7 @@ func (d *decoder) decode(v interface{}) error { case int16: if v, err := d.r.readint16(tag); err == nil { field.SetInt(int64(v)) - } else if err == io.EOF { + } else if err == io.EOF && optional { continue } else { return err @@ -92,7 +93,7 @@ func (d *decoder) decode(v interface{}) error { case uint32: if v, err := d.r.readUint32(tag); err == nil { field.SetUint(uint64(v)) - } else if err == io.EOF { + } else if err == io.EOF && optional { continue } else { return err @@ -101,7 +102,7 @@ func (d *decoder) decode(v interface{}) error { case int32: if v, err := d.r.readint32(tag); err == nil { field.SetInt(int64(v)) - } else if err == io.EOF { + } else if err == io.EOF && optional { continue } else { return err @@ -110,7 +111,7 @@ func (d *decoder) decode(v interface{}) error { case int64: if v, err := d.r.readint64(tag); err == nil { field.SetInt(v) - } else if err == io.EOF { + } else if err == io.EOF && optional { continue } else { return err @@ -119,7 +120,7 @@ func (d *decoder) decode(v interface{}) error { case uint64: if v, err := d.r.readUint64(tag); err == nil { field.SetUint(v) - } else if err == io.EOF { + } else if err == io.EOF && optional { continue } else { return err @@ -128,7 +129,7 @@ func (d *decoder) decode(v interface{}) error { case float32: if v, err := d.r.readFloat32(tag); err == nil { field.SetFloat(float64(v)) - } else if err == io.EOF { + } else if err == io.EOF && optional { continue } else { return err @@ -137,7 +138,7 @@ func (d *decoder) decode(v interface{}) error { case []byte: if v, err := d.r.readBytes(tag); err == nil { field.SetBytes(v) - } else if err == io.EOF { + } else if err == io.EOF && optional { continue } else { return err @@ -146,7 +147,7 @@ func (d *decoder) decode(v interface{}) error { case string: if v, err := d.r.readString(tag); err == nil { field.SetString(v) - } else if err == io.EOF { + } else if err == io.EOF && optional { continue } else { return err @@ -154,7 +155,7 @@ func (d *decoder) decode(v interface{}) error { case bool: if v, err := d.r.readBool(tag); err == nil { field.SetBool(v) - } else if err == io.EOF { + } else if err == io.EOF && optional { continue } else { return err @@ -173,7 +174,7 @@ func (d *decoder) decode(v interface{}) error { if tlv8 == "-" { // unnamed slices are inline encoded err = d.decode(v) - if isEmptyStruct(v) { + if err == io.EOF { // step out of loop break } From e64132560a3fd04e71e4a66c9f35d885edf4e143 Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Wed, 28 Sep 2022 09:00:32 +0200 Subject: [PATCH 13/58] Set optional TLV8 fields --- pair-setup.go | 28 ++++++++++----------- pair-verify.go | 12 ++++----- rtp/video_stream_configuration_test.go | 26 +++++++++++++++++++ tlv8/decoder.go | 35 +++++++++++++++++--------- tlv8/encoder.go | 3 ++- 5 files changed, 71 insertions(+), 33 deletions(-) create mode 100644 rtp/video_stream_configuration_test.go diff --git a/pair-setup.go b/pair-setup.go index 444c3c7..251b9ac 100644 --- a/pair-setup.go +++ b/pair-setup.go @@ -20,20 +20,20 @@ const ( ) type pairSetupPayload struct { - Method byte `tlv8:"0"` - Identifier string `tlv8:"1"` - Salt []byte `tlv8:"2"` - PublicKey []byte `tlv8:"3"` - Proof []byte `tlv8:"4"` - EncryptedData []byte `tlv8:"5"` - State byte `tlv8:"6"` - Error byte `tlv8:"7"` - RetryDelay byte `tlv8:"8"` - Certificate []byte `tlv8:"9"` - Signature []byte `tlv8:"10"` - Permissions byte `tlv8:"11"` - FragmentData []byte `tlv8:"13"` - FragmentLast []byte `tlv8:"14"` + Method byte `tlv8:"0,optional"` + Identifier string `tlv8:"1,optional"` + Salt []byte `tlv8:"2,optional"` + PublicKey []byte `tlv8:"3,optional"` + Proof []byte `tlv8:"4,optional"` + EncryptedData []byte `tlv8:"5,optional"` + State byte `tlv8:"6,optional"` + Error byte `tlv8:"7,optional"` + RetryDelay byte `tlv8:"8,optional"` + Certificate []byte `tlv8:"9,optional"` + Signature []byte `tlv8:"10,optional"` + Permissions byte `tlv8:"11,optional"` + FragmentData []byte `tlv8:"13,optional"` + FragmentLast []byte `tlv8:"14,optional"` } func (srv *Server) pairSetup(res http.ResponseWriter, req *http.Request) { diff --git a/pair-verify.go b/pair-verify.go index 9362904..4451d82 100644 --- a/pair-verify.go +++ b/pair-verify.go @@ -12,12 +12,12 @@ import ( ) type pairVerifyPayload struct { - Method byte `tlv8:"0"` - Identifier string `tlv8:"1"` - PublicKey []byte `tlv8:"3"` - EncryptedData []byte `tlv8:"5"` - State byte `tlv8:"6"` - Signature []byte `tlv8:"10"` + Method byte `tlv8:"0,optional"` + Identifier string `tlv8:"1,optional"` + PublicKey []byte `tlv8:"3,optional"` + EncryptedData []byte `tlv8:"5,optional"` + State byte `tlv8:"6,optional"` + Signature []byte `tlv8:"10,optional"` } type pairVerifySession struct { diff --git a/rtp/video_stream_configuration_test.go b/rtp/video_stream_configuration_test.go new file mode 100644 index 0000000..9e9035b --- /dev/null +++ b/rtp/video_stream_configuration_test.go @@ -0,0 +1,26 @@ +package rtp + +import ( + "reflect" + "testing" + + "github.com/brutella/hap/tlv8" +) + +func TestMarhsalUnmarshalDefaultVideoStreamConfiguration(t *testing.T) { + want := DefaultVideoStreamConfiguration() + buf, err := tlv8.Marshal(want) + if err != nil { + t.Fatal(err) + } + + var is VideoStreamConfiguration + err = tlv8.Unmarshal(buf, &is) + if err != nil { + t.Fatal(err) + } + + if reflect.DeepEqual(is, want) == false { + t.Fatalf("is=%+v want=%+v", is, want) + } +} diff --git a/tlv8/decoder.go b/tlv8/decoder.go index 1729202..b4a6e32 100644 --- a/tlv8/decoder.go +++ b/tlv8/decoder.go @@ -1,6 +1,8 @@ package tlv8 import ( + "fmt" + "github.com/xiam/to" "bytes" @@ -9,6 +11,14 @@ import ( "strings" ) +type EOF struct { + field reflect.StructField +} + +func (e *EOF) Error() string { + return fmt.Sprintf("not enough bytes for non-optional field %s", e.field.Name) +} + type decoder struct { r *reader } @@ -57,7 +67,8 @@ func (d *decoder) decode(v interface{}) error { } for i := 0; i < eValue.NumField(); i++ { - if tlv8, ok := eType.Field(i).Tag.Lookup("tlv8"); ok { + typeField := eType.Field(i) + if tlv8, ok := typeField.Tag.Lookup("tlv8"); ok { values := strings.Split(tlv8, ",") tag := uint8(to.Uint64(values[0])) optional := len(values) > 1 && values[1] == "optional" @@ -70,7 +81,7 @@ func (d *decoder) decode(v interface{}) error { } else if err == io.EOF && optional { continue } else { - return err + return &EOF{typeField} } case uint16: if v, err := d.r.readUint16(tag); err == nil { @@ -78,7 +89,7 @@ func (d *decoder) decode(v interface{}) error { } else if err == io.EOF && optional { continue } else { - return err + return &EOF{typeField} } case int16: @@ -87,7 +98,7 @@ func (d *decoder) decode(v interface{}) error { } else if err == io.EOF && optional { continue } else { - return err + return &EOF{typeField} } case uint32: @@ -96,7 +107,7 @@ func (d *decoder) decode(v interface{}) error { } else if err == io.EOF && optional { continue } else { - return err + return &EOF{typeField} } case int32: @@ -105,7 +116,7 @@ func (d *decoder) decode(v interface{}) error { } else if err == io.EOF && optional { continue } else { - return err + return &EOF{typeField} } case int64: @@ -114,7 +125,7 @@ func (d *decoder) decode(v interface{}) error { } else if err == io.EOF && optional { continue } else { - return err + return &EOF{typeField} } case uint64: @@ -123,7 +134,7 @@ func (d *decoder) decode(v interface{}) error { } else if err == io.EOF && optional { continue } else { - return err + return &EOF{typeField} } case float32: @@ -132,7 +143,7 @@ func (d *decoder) decode(v interface{}) error { } else if err == io.EOF && optional { continue } else { - return err + return &EOF{typeField} } case []byte: @@ -141,7 +152,7 @@ func (d *decoder) decode(v interface{}) error { } else if err == io.EOF && optional { continue } else { - return err + return &EOF{typeField} } case string: @@ -150,7 +161,7 @@ func (d *decoder) decode(v interface{}) error { } else if err == io.EOF && optional { continue } else { - return err + return &EOF{typeField} } case bool: if v, err := d.r.readBool(tag); err == nil { @@ -158,7 +169,7 @@ func (d *decoder) decode(v interface{}) error { } else if err == io.EOF && optional { continue } else { - return err + return &EOF{typeField} } default: diff --git a/tlv8/encoder.go b/tlv8/encoder.go index 1e6c449..4201d28 100644 --- a/tlv8/encoder.go +++ b/tlv8/encoder.go @@ -2,8 +2,9 @@ package tlv8 import ( "bytes" - "github.com/xiam/to" "reflect" + + "github.com/xiam/to" ) type encoder struct { From 17df293467ee127ebcde810a53b6f6c127d0b265 Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Wed, 28 Sep 2022 09:47:17 +0200 Subject: [PATCH 14/58] Fix race condition --- conn.go | 18 ++++++++++++++++-- listener.go | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/conn.go b/conn.go index 22ecca9..1adbc60 100644 --- a/conn.go +++ b/conn.go @@ -1,6 +1,8 @@ package hap import ( + "sync" + "github.com/brutella/hap/log" "bufio" @@ -20,14 +22,24 @@ type conn struct { // 2022-02-17 (mah) This workaround is needed because switching to encryption is done // after sending a response. But Write() on http.ResponseWriter is not immediate. // So therefore we wait until the next read. - s *session - ss *session + s *session + smu sync.Mutex + ss *session readBuf io.Reader } +func newConn(c net.Conn) *conn { + return &conn{ + Conn: c, + smu: sync.Mutex{}, + } +} + func (c *conn) Upgrade(s *session) { + c.smu.Lock() c.s = s + c.smu.Unlock() } // Write writes bytes to the connection. @@ -60,10 +72,12 @@ const ( // Read reads bytes from the connection. // The read bytes are decrypted when possible. func (c *conn) Read(b []byte) (int, error) { + c.smu.Lock() if c.s != nil { c.ss = c.s c.s = nil } + c.smu.Unlock() if c.ss == nil { return c.Conn.Read(b) diff --git a/listener.go b/listener.go index 2545275..dc49df7 100644 --- a/listener.go +++ b/listener.go @@ -14,7 +14,7 @@ func (ln *listener) Accept() (con net.Conn, err error) { return } - conn := &conn{Conn: con} + conn := newConn(con) setConn(conn.RemoteAddr().String(), conn) return conn, err From 39fd60f98ec9e7c8135bdb3a72dcee6601b971bd Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Thu, 29 Sep 2022 16:03:09 +0200 Subject: [PATCH 15/58] Hold position always reports new values --- characteristic/hold_position.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/characteristic/hold_position.go b/characteristic/hold_position.go index 967175a..387c2b0 100644 --- a/characteristic/hold_position.go +++ b/characteristic/hold_position.go @@ -13,5 +13,7 @@ func NewHoldPosition() *HoldPosition { c.Format = FormatBool c.Permissions = []string{PermissionWrite} + c.updateOnSameValue = true + return &HoldPosition{c} } From c3dce13258a73effe1899b41d2454e1060207f62 Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Thu, 29 Sep 2022 16:05:29 +0200 Subject: [PATCH 16/58] Require gopkg.in/yaml v2.2.8 --- go.mod | 1 + go.sum | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index a693f78..f73270a 100644 --- a/go.mod +++ b/go.mod @@ -9,4 +9,5 @@ require ( github.com/xiam/to v0.0.0-20200126224905-d60d31e03561 golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 golang.org/x/text v0.3.7 + gopkg.in/yaml.v2 v2.2.8 // indirect ) diff --git a/go.sum b/go.sum index 18a6cc0..121fb57 100644 --- a/go.sum +++ b/go.sum @@ -55,5 +55,6 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From de8012d9e2c5b27fbeabc5770b9bc2eb8763209f Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Mon, 10 Oct 2022 10:13:03 +0200 Subject: [PATCH 17/58] Add accessory runtime information service and related characteristics --- characteristic/activity_interval.go | 18 ++++++++++++++++++ characteristic/heart_beat.go | 16 ++++++++++++++++ characteristic/ping.go | 17 +++++++++++++++++ characteristic/sleep_interval.go | 19 +++++++++++++++++++ service/accessory_runtime_information.go | 21 +++++++++++++++++++++ 5 files changed, 91 insertions(+) create mode 100644 characteristic/activity_interval.go create mode 100644 characteristic/heart_beat.go create mode 100644 characteristic/ping.go create mode 100644 characteristic/sleep_interval.go create mode 100644 service/accessory_runtime_information.go diff --git a/characteristic/activity_interval.go b/characteristic/activity_interval.go new file mode 100644 index 0000000..e306450 --- /dev/null +++ b/characteristic/activity_interval.go @@ -0,0 +1,18 @@ +package characteristic + +const TypeActivityInterval = "23B" + +type ActivityInterval struct { + *Int +} + +func NewActivityInterval() *ActivityInterval { + c := NewInt(TypeActivityInterval) + c.Format = FormatUInt32 + c.Permissions = []string{PermissionRead, PermissionEvents} + c.SetMinValue(0) + c.SetStepValue(1) + c.SetValue(0) + + return &ActivityInterval{c} +} diff --git a/characteristic/heart_beat.go b/characteristic/heart_beat.go new file mode 100644 index 0000000..bb45388 --- /dev/null +++ b/characteristic/heart_beat.go @@ -0,0 +1,16 @@ +package characteristic + +const TypeHeartBeat = "24A" + +type HeartBeat struct { + *Int +} + +func NewHeartBeat() *HeartBeat { + c := NewInt(TypeHeartBeat) + c.Format = FormatUInt32 + c.Permissions = []string{PermissionRead, PermissionEvents} + c.SetValue(0) + + return &HeartBeat{c} +} diff --git a/characteristic/ping.go b/characteristic/ping.go new file mode 100644 index 0000000..2a4d0b0 --- /dev/null +++ b/characteristic/ping.go @@ -0,0 +1,17 @@ +package characteristic + +const TypePing = "23C" + +type Ping struct { + *Bytes +} + +func NewPing() *Ping { + c := NewBytes(TypePing) + c.Format = FormatTLV8 + c.Permissions = []string{PermissionRead} + + c.SetValue([]byte{}) + + return &Ping{c} +} diff --git a/characteristic/sleep_interval.go b/characteristic/sleep_interval.go new file mode 100644 index 0000000..df6fc9b --- /dev/null +++ b/characteristic/sleep_interval.go @@ -0,0 +1,19 @@ +package characteristic + +const TypeSleepInterval = "23A" + +type SleepInterval struct { + *Int +} + +func NewSleepInterval() *SleepInterval { + c := NewInt(TypeSleepInterval) + c.Format = FormatUInt32 + c.Permissions = []string{PermissionRead} + c.SetMinValue(0) + c.SetMaxValue(67108863) + c.SetStepValue(1) + c.SetValue(0) + + return &SleepInterval{c} +} diff --git a/service/accessory_runtime_information.go b/service/accessory_runtime_information.go new file mode 100644 index 0000000..82013da --- /dev/null +++ b/service/accessory_runtime_information.go @@ -0,0 +1,21 @@ +package service + +import "github.com/brutella/hap/characteristic" + +const TypeAccessoryRuntimeInformation = "239" + +type AccessoryRuntimeInformation struct { + *S + + Ping *characteristic.Ping +} + +func NewAccessoryInformation() *AccessoryRuntimeInformation { + s := AccessoryRuntimeInformation{} + s.S = New(TypeAccessoryRuntimeInformation) + + s.Ping = characteristic.NewPing() + s.AddC(s.Ping.C) + + return &s +} From 532497bf3b1bded191d9630b3c47b812369541eb Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Mon, 10 Oct 2022 13:25:39 +0200 Subject: [PATCH 18/58] Define UnitMicrogramsPerCubicMeter --- characteristic/c.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/characteristic/c.go b/characteristic/c.go index 3e01d59..57b3eb3 100644 --- a/characteristic/c.go +++ b/characteristic/c.go @@ -17,12 +17,13 @@ const ( ) const ( - UnitPercentage = "percentage" // % - UnitArcDegrees = "arcdegrees" // ° - UnitCelsius = "celsius" // °C - UnitLux = "lux" // lux - UnitSeconds = "seconds" // sec - UnitPPM = "ppm" // ppm + UnitPercentage = "percentage" // % + UnitArcDegrees = "arcdegrees" // ° + UnitCelsius = "celsius" // °C + UnitLux = "lux" // lux + UnitSeconds = "seconds" // sec + UnitPPM = "ppm" // ppm + UnitMicrogramsPerCubicMeter = "micrograms/m^3" ) const ( From 83a57e823cdc601c673f3d05664e53fce683b9ec Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Thu, 13 Oct 2022 10:04:45 +0200 Subject: [PATCH 19/58] Fix tlv8 decoding of inline array --- tlv8/decoder.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tlv8/decoder.go b/tlv8/decoder.go index b4a6e32..b9b6047 100644 --- a/tlv8/decoder.go +++ b/tlv8/decoder.go @@ -81,7 +81,7 @@ func (d *decoder) decode(v interface{}) error { } else if err == io.EOF && optional { continue } else { - return &EOF{typeField} + return err } case uint16: if v, err := d.r.readUint16(tag); err == nil { @@ -89,7 +89,7 @@ func (d *decoder) decode(v interface{}) error { } else if err == io.EOF && optional { continue } else { - return &EOF{typeField} + return err } case int16: @@ -98,7 +98,7 @@ func (d *decoder) decode(v interface{}) error { } else if err == io.EOF && optional { continue } else { - return &EOF{typeField} + return err } case uint32: @@ -107,7 +107,7 @@ func (d *decoder) decode(v interface{}) error { } else if err == io.EOF && optional { continue } else { - return &EOF{typeField} + return err } case int32: @@ -116,7 +116,7 @@ func (d *decoder) decode(v interface{}) error { } else if err == io.EOF && optional { continue } else { - return &EOF{typeField} + return err } case int64: @@ -125,7 +125,7 @@ func (d *decoder) decode(v interface{}) error { } else if err == io.EOF && optional { continue } else { - return &EOF{typeField} + return err } case uint64: @@ -134,7 +134,7 @@ func (d *decoder) decode(v interface{}) error { } else if err == io.EOF && optional { continue } else { - return &EOF{typeField} + return err } case float32: @@ -143,7 +143,7 @@ func (d *decoder) decode(v interface{}) error { } else if err == io.EOF && optional { continue } else { - return &EOF{typeField} + return err } case []byte: @@ -152,7 +152,7 @@ func (d *decoder) decode(v interface{}) error { } else if err == io.EOF && optional { continue } else { - return &EOF{typeField} + return err } case string: @@ -161,7 +161,7 @@ func (d *decoder) decode(v interface{}) error { } else if err == io.EOF && optional { continue } else { - return &EOF{typeField} + return err } case bool: if v, err := d.r.readBool(tag); err == nil { @@ -169,7 +169,7 @@ func (d *decoder) decode(v interface{}) error { } else if err == io.EOF && optional { continue } else { - return &EOF{typeField} + return err } default: From 684ab1029b7b0e4f3db8148be57be4986e31f927 Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Thu, 13 Oct 2022 10:05:14 +0200 Subject: [PATCH 20/58] Remove unused error type --- tlv8/decoder.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tlv8/decoder.go b/tlv8/decoder.go index b9b6047..eb718b5 100644 --- a/tlv8/decoder.go +++ b/tlv8/decoder.go @@ -1,8 +1,6 @@ package tlv8 import ( - "fmt" - "github.com/xiam/to" "bytes" @@ -11,14 +9,6 @@ import ( "strings" ) -type EOF struct { - field reflect.StructField -} - -func (e *EOF) Error() string { - return fmt.Sprintf("not enough bytes for non-optional field %s", e.field.Name) -} - type decoder struct { r *reader } From 96cc95ddedef77642c3fa540e930c8592c0b7d4e Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Thu, 13 Oct 2022 10:05:33 +0200 Subject: [PATCH 21/58] Rename method --- service/accessory_runtime_information.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/accessory_runtime_information.go b/service/accessory_runtime_information.go index 82013da..f19f9da 100644 --- a/service/accessory_runtime_information.go +++ b/service/accessory_runtime_information.go @@ -10,7 +10,7 @@ type AccessoryRuntimeInformation struct { Ping *characteristic.Ping } -func NewAccessoryInformation() *AccessoryRuntimeInformation { +func NewAccessoryRuntimeInformation() *AccessoryRuntimeInformation { s := AccessoryRuntimeInformation{} s.S = New(TypeAccessoryRuntimeInformation) From 6b2b8731c33732a5b1dd2524740c6870b7d81d6d Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Tue, 3 Jan 2023 11:54:03 +0100 Subject: [PATCH 22/58] Add property to specify interface at which dnssd service is announced --- server.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server.go b/server.go index 20e60e6..5450383 100644 --- a/server.go +++ b/server.go @@ -40,6 +40,10 @@ type Server struct { // If empty, a random port is used. Addr string + // Ifaces specifies at which interface the + // associated dnssd service is announced. + Ifaces []string + MfiCompliant bool // default false Protocol string // default "1.0" SetupId string @@ -480,6 +484,7 @@ func (s *Server) service() (dnssd.Service, error) { Host: strings.Replace(s.uuid, ":", "", -1), // use the id (without the colons) to get unique hostnames Text: s.txtRecords(), Port: s.port, + Ifaces: s.Ifaces, } return dnssd.NewService(cfg) From 341fa924e5f83eed58156f40f61856f067bc2656 Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Tue, 10 Jan 2023 10:40:21 +0100 Subject: [PATCH 23/58] Update to latest dnssd commit in branch ifaces --- go.mod | 2 +- go.sum | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f73270a..abbeece 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/brutella/hap go 1.16 require ( - github.com/brutella/dnssd v1.2.3 + github.com/brutella/dnssd v1.2.5-0.20230110092719-0593ced73aa1 github.com/go-chi/chi v1.5.4 github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9 github.com/xiam/to v0.0.0-20200126224905-d60d31e03561 diff --git a/go.sum b/go.sum index 121fb57..da16b65 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ -github.com/brutella/dnssd v1.2.3 h1:4fBLjZjPH7SbcHhEcIJhZcC9nOhIDZ0m3rn9bjl1/i0= -github.com/brutella/dnssd v1.2.3/go.mod h1:JoW2sJUrmVIef25G6lrLj7HS6Xdwh6q8WUIvMkkBYXs= +github.com/brutella/dnssd v1.2.5-0.20230109163300-e7119f1ad274 h1:i4v5YgQXfkg06lVkvANYUCBI2HcnOXuQ730dtguaqvk= +github.com/brutella/dnssd v1.2.5-0.20230109163300-e7119f1ad274/go.mod h1:JoW2sJUrmVIef25G6lrLj7HS6Xdwh6q8WUIvMkkBYXs= +github.com/brutella/dnssd v1.2.5-0.20230110092719-0593ced73aa1 h1:akgPIHTKYDnvSfU+tFA5gJhXd+YpVemb8pQNZeny6DA= +github.com/brutella/dnssd v1.2.5-0.20230110092719-0593ced73aa1/go.mod h1:JoW2sJUrmVIef25G6lrLj7HS6Xdwh6q8WUIvMkkBYXs= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs= From 1f9daaf7135955820002b8e0c197f7b6a660247c Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Fri, 13 Jan 2023 14:34:42 +0100 Subject: [PATCH 24/58] Remove all pairings when no admin controller is paired anymore --- pairings.go | 6 +++--- server.go | 7 +++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/pairings.go b/pairings.go index 689b638..4573b8b 100644 --- a/pairings.go +++ b/pairings.go @@ -114,14 +114,14 @@ func (srv *Server) pairings(res http.ResponseWriter, req *http.Request) { } tlv8OK(res, resp) - // Close all connections if no - // admin controller is paired anymore + // If no admin controller is paired anymore, + // close all connections and delete all pairings if !srv.pairedWithAdmin() { for addr, conn := range conns() { log.Debug.Println("Closing connection to", addr) conn.Close() } - return + srv.deleteAllPairings() } // Close connection of deleted controller diff --git a/server.go b/server.go index 5450383..ea8824d 100644 --- a/server.go +++ b/server.go @@ -426,6 +426,13 @@ func (s *Server) deletePairing(p Pairing) error { return nil } +func (s *Server) deleteAllPairings() { + for _, p := range s.st.Pairings() { + s.st.DeletePairing(p.Name) + } + s.updateTxtRecords() +} + func (s *Server) isPaired() bool { return len(s.st.Pairings()) > 0 } From 1baa43d81935aabf64f1f7011f8d29bde8a572eb Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Wed, 18 Jan 2023 07:30:33 +0100 Subject: [PATCH 25/58] Use dnssd v1.2.5 --- go.mod | 2 +- go.sum | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index abbeece..87106c2 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/brutella/hap go 1.16 require ( - github.com/brutella/dnssd v1.2.5-0.20230110092719-0593ced73aa1 + github.com/brutella/dnssd v1.2.5 github.com/go-chi/chi v1.5.4 github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9 github.com/xiam/to v0.0.0-20200126224905-d60d31e03561 diff --git a/go.sum b/go.sum index da16b65..b06b996 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ -github.com/brutella/dnssd v1.2.5-0.20230109163300-e7119f1ad274 h1:i4v5YgQXfkg06lVkvANYUCBI2HcnOXuQ730dtguaqvk= -github.com/brutella/dnssd v1.2.5-0.20230109163300-e7119f1ad274/go.mod h1:JoW2sJUrmVIef25G6lrLj7HS6Xdwh6q8WUIvMkkBYXs= -github.com/brutella/dnssd v1.2.5-0.20230110092719-0593ced73aa1 h1:akgPIHTKYDnvSfU+tFA5gJhXd+YpVemb8pQNZeny6DA= -github.com/brutella/dnssd v1.2.5-0.20230110092719-0593ced73aa1/go.mod h1:JoW2sJUrmVIef25G6lrLj7HS6Xdwh6q8WUIvMkkBYXs= +github.com/brutella/dnssd v1.2.5 h1:b8syhho41/5ikw3X2X4baR9NWEBSlpZnfQgujsv7bk4= +github.com/brutella/dnssd v1.2.5/go.mod h1:JoW2sJUrmVIef25G6lrLj7HS6Xdwh6q8WUIvMkkBYXs= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs= From ddef50b53916b559a7de9878243789292a67e41f Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Wed, 18 Jan 2023 08:34:14 +0100 Subject: [PATCH 26/58] Add documentation to C type --- characteristic/c.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/characteristic/c.go b/characteristic/c.go index 57b3eb3..25d1a76 100644 --- a/characteristic/c.go +++ b/characteristic/c.go @@ -203,11 +203,13 @@ func (c *C) ValueRequest(req *http.Request) (interface{}, int) { return c.value(), 0 } -// value returns the value of C and a status code. +// value returns the value of C func (c *C) value() interface{} { return c.Val } +// IsWritable returns true if clients are allowed +// to update the value of the characteristic. func (c *C) IsWritable() bool { for _, p := range c.Permissions { if p == PermissionWrite { @@ -218,6 +220,8 @@ func (c *C) IsWritable() bool { return false } +// IsReadable returns true if clients are allowed +// to read the value of the characteristic. func (c *C) IsReadable() bool { for _, p := range c.Permissions { if p == PermissionRead { @@ -228,6 +232,8 @@ func (c *C) IsReadable() bool { return false } +// IsObservable returns true if clients are allowed +// to observe the value of the characteristic. func (c *C) IsObservable() bool { for _, p := range c.Permissions { if p == PermissionEvents { @@ -238,6 +244,8 @@ func (c *C) IsObservable() bool { return false } +// IsObservable returns true if the value of the +// characteristic can only be updated, but not read. func (c *C) IsWriteOnly() bool { return len(c.Permissions) == 1 && c.Permissions[0] == PermissionWrite } From fa83abe8d5bbd906b25e71e72cc224411c5707d4 Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Mon, 30 Jan 2023 17:15:00 +0100 Subject: [PATCH 27/58] Make method public to check if server is paired with a client --- identify.go | 2 +- pair-setup.go | 2 +- server.go | 11 ++++++----- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/identify.go b/identify.go index 8011722..0465e9f 100644 --- a/identify.go +++ b/identify.go @@ -7,7 +7,7 @@ import ( ) func (srv *Server) identify(res http.ResponseWriter, req *http.Request) { - if srv.isPaired() { + if srv.IsPaired() { log.Info.Printf("request only valid if unpaired") JsonError(res, JsonStatusInsufficientPrivileges) return diff --git a/pair-setup.go b/pair-setup.go index 251b9ac..f232f0e 100644 --- a/pair-setup.go +++ b/pair-setup.go @@ -38,7 +38,7 @@ type pairSetupPayload struct { func (srv *Server) pairSetup(res http.ResponseWriter, req *http.Request) { // pairing is only allowed if the accessory is not paired yet - if srv.isPaired() { + if srv.IsPaired() { log.Info.Println("pairing is not allowed") tlv8Error(res, M2, TlvErrorUnavailable) return diff --git a/server.go b/server.go index ea8824d..b1c90d9 100644 --- a/server.go +++ b/server.go @@ -165,6 +165,11 @@ func (s *Server) IsAuthorized(request *http.Request) bool { return ss != nil } +// IsPaired returns true if the server is paired with a client (iOS). +func (s *Server) IsPaired() bool { + return len(s.st.Pairings()) > 0 +} + // ListenAndServe starts the server. func (s *Server) ListenAndServe(ctx context.Context) error { err := s.prepare() @@ -433,10 +438,6 @@ func (s *Server) deleteAllPairings() { s.updateTxtRecords() } -func (s *Server) isPaired() bool { - return len(s.st.Pairings()) > 0 -} - func (s *Server) pairedWithAdmin() bool { for _, p := range s.st.Pairings() { if p.Permission == PermissionAdmin { @@ -453,7 +454,7 @@ func (s *Server) txtRecords() map[string]string { "id": s.uuid, "c#": fmt.Sprintf("%d", s.version), "s#": "1", - "sf": fmt.Sprintf("%d", to.Int64(!s.isPaired())), + "sf": fmt.Sprintf("%d", to.Int64(!s.IsPaired())), "ff": fmt.Sprintf("%d", to.Int64(s.MfiCompliant)), "md": s.a.Name(), "ci": fmt.Sprintf("%d", s.a.Type), From 82274a8590b588ac4afc461df0d677083c120dd8 Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Thu, 9 Feb 2023 14:47:20 +0100 Subject: [PATCH 28/58] Fix EOF error by making fields optional --- pairings.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pairings.go b/pairings.go index 4573b8b..f3fd1e6 100644 --- a/pairings.go +++ b/pairings.go @@ -32,8 +32,8 @@ func (srv *Server) pairings(res http.ResponseWriter, req *http.Request) { d := struct { Method byte `tlv8:"0"` Identifier string `tlv8:"1"` - PublicKey []byte `tlv8:"3"` - Permission byte `tlv8:"11"` + PublicKey []byte `tlv8:"3,optional"` + Permission byte `tlv8:"11,optional"` State byte `tlv8:"6"` }{} From a5b8dc05451149adb713ea7114a1ebbf7656672a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Feb 2023 10:22:38 +0000 Subject: [PATCH 29/58] Bump golang.org/x/text from 0.3.7 to 0.3.8 Bumps [golang.org/x/text](https://github.com/golang/text) from 0.3.7 to 0.3.8. - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.3.7...v0.3.8) --- updated-dependencies: - dependency-name: golang.org/x/text dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 24 +++++++++++++++++------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 87106c2..c2f5842 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,6 @@ require ( github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9 github.com/xiam/to v0.0.0-20200126224905-d60d31e03561 golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 - golang.org/x/text v0.3.7 + golang.org/x/text v0.3.8 gopkg.in/yaml.v2 v2.2.8 // indirect ) diff --git a/go.sum b/go.sum index b06b996..5b41e8d 100644 --- a/go.sum +++ b/go.sum @@ -16,21 +16,27 @@ github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9/go.mod h1:roo6cZ github.com/xiam/to v0.0.0-20200126224905-d60d31e03561 h1:SVoNK97S6JlaYlHcaC+79tg3JUlQABcc0dH2VQ4Y+9s= github.com/xiam/to v0.0.0-20200126224905-d60d31e03561/go.mod h1:cqbG7phSzrbdg3aj+Kn63bpVruzwDZi58CpxlZkjwzw= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 h1:71vQrMauZZhcTVK6KdYM+rklehEEwb3E+ZhaE5jrPrE= golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -38,21 +44,25 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 h1:BonxutuHCTL0rBDnZlKjpGIQFTjyUVTexFOdWkB6Fg0= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From bc7f201f44cd94a08116e448e7f4a41348e15c00 Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Thu, 30 Mar 2023 15:52:53 +0200 Subject: [PATCH 30/58] Update dnssd to v1.2.6 --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index c2f5842..68c9f07 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/brutella/hap go 1.16 require ( - github.com/brutella/dnssd v1.2.5 + github.com/brutella/dnssd v1.2.6 github.com/go-chi/chi v1.5.4 github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9 github.com/xiam/to v0.0.0-20200126224905-d60d31e03561 From 4b1951af777490aae1cc4e46fefadfe5a298deac Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Thu, 13 Apr 2023 16:53:15 +0200 Subject: [PATCH 31/58] Ignore permissions when setting value using SetValueRequest #25 --- characteristic/c.go | 2 +- characteristic/c_test.go | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/characteristic/c.go b/characteristic/c.go index 25d1a76..0f1c61c 100644 --- a/characteristic/c.go +++ b/characteristic/c.go @@ -132,7 +132,7 @@ func (c *C) OnCValueUpdate(fn ValueUpdateFunc) { // The server invokes this function when the value is updated by an http request. func (c *C) SetValueRequest(val interface{}, req *http.Request) (interface{}, int) { // check write permission - if !c.IsWritable() { + if req != nil && !c.IsWritable() { log.Info.Printf("writing %v by %s not allowed\n", val, req.RemoteAddr) return val, -70404 } diff --git a/characteristic/c_test.go b/characteristic/c_test.go index 29d34e7..07dcd85 100644 --- a/characteristic/c_test.go +++ b/characteristic/c_test.go @@ -118,6 +118,11 @@ func TestReadOnly(t *testing.T) { if is, want := c.Value(), "Matthias"; is != want { t.Fatalf("is=%v want=%v", is, want) } + + c.SetValueRequest("Gottfried", nil) + if is, want := c.Value(), "Gottfried"; is != want { + t.Fatalf("is=%v want=%v", is, want) + } } func TestSetValueRequestFuncError(t *testing.T) { From 80fc9dcbe1c4b0fafd2c6f5f6739ad3d52e7443f Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Mon, 24 Apr 2023 09:13:35 +0200 Subject: [PATCH 32/58] Fix permissions of db files #28 --- fs.go | 33 +++------------------------------ 1 file changed, 3 insertions(+), 30 deletions(-) diff --git a/fs.go b/fs.go index 95f25ae..441ceab 100644 --- a/fs.go +++ b/fs.go @@ -3,7 +3,6 @@ package hap import ( "github.com/brutella/hap/log" - "bytes" "encoding/hex" "encoding/json" "io/ioutil" @@ -20,7 +19,7 @@ func NewFsStore(dir string) Store { // Prepare filesystem directory // Ensure that execute permission bit is set on all created dirs // Read http://unix.stackexchange.com/questions/21251/why-do-directories-need-the-executable-x-permission-to-be-opened - err := os.MkdirAll(dir, 0755) + err := os.MkdirAll(dir, 0750) if err != nil { log.Info.Panic(err) } @@ -29,37 +28,11 @@ func NewFsStore(dir string) Store { } func (fs *fsStore) Set(key string, value []byte) error { - file, err := os.OpenFile(fs.filePathToFile(key), os.O_WRONLY|os.O_CREATE, 0666) - if err != nil { - return err - } - - defer file.Close() - - _, err = file.Write(value) - return err + return os.WriteFile(fs.filePathToFile(key), value, 0640) } func (fs *fsStore) Get(key string) ([]byte, error) { - file, err := os.OpenFile(fs.filePathToFile(key), os.O_RDONLY, 0666) - if err != nil { - return nil, err - } - - defer file.Close() - - var b bytes.Buffer - var buffer = make([]byte, 32) - for { - n, _ := file.Read(buffer) - if n > 0 { - b.Write(buffer[:n]) - } else { - break - } - } - - return b.Bytes(), nil + return os.ReadFile(fs.filePathToFile(key)) } // Delete removes the file for the corresponding key. From f2425cf9afec12c1f7f91d521b779ea29beb0680 Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Thu, 27 Apr 2023 12:25:50 +0200 Subject: [PATCH 33/58] Upgrade dnssd to v1.2.7 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 68c9f07..17eef69 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/brutella/hap go 1.16 require ( - github.com/brutella/dnssd v1.2.6 + github.com/brutella/dnssd v1.2.7 github.com/go-chi/chi v1.5.4 github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9 github.com/xiam/to v0.0.0-20200126224905-d60d31e03561 diff --git a/go.sum b/go.sum index 5b41e8d..84d2fcc 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/brutella/dnssd v1.2.5 h1:b8syhho41/5ikw3X2X4baR9NWEBSlpZnfQgujsv7bk4= -github.com/brutella/dnssd v1.2.5/go.mod h1:JoW2sJUrmVIef25G6lrLj7HS6Xdwh6q8WUIvMkkBYXs= +github.com/brutella/dnssd v1.2.7 h1:Uq2NgLzlUz5JWIzcug9xRU6v0UApHrlxbsREA5B1RrY= +github.com/brutella/dnssd v1.2.7/go.mod h1:JoW2sJUrmVIef25G6lrLj7HS6Xdwh6q8WUIvMkkBYXs= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs= From 8c2be2d33047f22d92d622695b05eeaf84c0ae85 Mon Sep 17 00:00:00 2001 From: Darell Tan Date: Sat, 20 May 2023 21:38:40 +0800 Subject: [PATCH 34/58] Fix emitted characteristics JSON to be compliant Currently the characteristics JSON does not fully comply with the HomeKit protocol when a characteristic returns an error, causing iOS to error out when communicating with the bridge. A characteristic may return an error via ValueRequestFunc, but calling `GET /accessories` must still provide a valid "value" in the JSON. Additionally, when a 207 Multi-Status response is returned because of this, characteristics that are readable must contain a "value" _and_ an explicit "status: 0", whereas characteristics with errors must contain a non-zero "status" and must not contain a "value". Fixes #30. --- characteristic/c.go | 4 ++- characteristic/c_test.go | 57 ++++++++++++++++++++++++++++++++++++++++ characteristics.go | 16 ++++++++--- notification.go | 2 +- server_test.go | 38 +++++++++++++++++++++++++++ 5 files changed, 111 insertions(+), 6 deletions(-) diff --git a/characteristic/c.go b/characteristic/c.go index 0f1c61c..396a866 100644 --- a/characteristic/c.go +++ b/characteristic/c.go @@ -257,7 +257,7 @@ func (c *C) MarshalJSON() ([]byte, error) { Permissions []string `json:"perms"` Format string `json:"format"` - Value *V `json:"value,omitempty"` + Value *V `json:"value"` Description string `json:"description,omitempty"` // manufacturer description (optional) Unit string `json:"unit,omitempty"` MaxLen int `json:"maxLen,omitempty"` @@ -287,6 +287,8 @@ func (c *C) MarshalJSON() ([]byte, error) { // 2022-03-21 (mah) FIXME provide a http request instead of nil if v, s := c.ValueRequest(nil); s == 0 { d.Value = &V{v} + } else { + d.Value = &V{c.Val} // dummy "zero" value } } diff --git a/characteristic/c_test.go b/characteristic/c_test.go index 07dcd85..0e884ea 100644 --- a/characteristic/c_test.go +++ b/characteristic/c_test.go @@ -1,7 +1,9 @@ package characteristic import ( + "encoding/json" "net/http" + "reflect" "testing" ) @@ -189,3 +191,58 @@ func TestValidRange(t *testing.T) { t.Fatal("no error expected") } } + +func TestCharacteristicJson(t *testing.T) { + cs := []*C{ + //NewContactSensorState().C, // int + NewCurrentTemperature().C, // float + NewCurrentTransport().C, // bool + NewAccessoryIdentifier().C, // string + } + + encodeDecodeJson := func(c *C) map[string]interface{} { + j, err := c.MarshalJSON() + if err != nil { + t.Fatal("cannot MarshalJSON: ", err) + } + + var jsonMap map[string]interface{} + err = json.Unmarshal(j, &jsonMap) + if err != nil { + t.Fatal("invalid encoded JSON: ", err) + } + + return jsonMap + } + + for _, c := range cs { + jsonMap := encodeDecodeJson(c) + + // verify properties + if is, want := jsonMap["type"], c.Type; is != want { + t.Fatalf("marshaled type is wrong: is=%v wanted=%v", is, want) + } + if is, want := jsonMap["format"], c.Format; is != want { + t.Fatalf("marshaled format is wrong: is=%v wanted=%v", is, want) + } + if is, want := jsonMap["value"], c.Val; is != want { + t.Fatalf("marshaled value is wrong: is=%v wanted=%v", is, want) + } + + // set a ValueRequestFunc that returns an error + c.ValueRequestFunc = func(r *http.Request) (response interface{}, status int) { + return nil, -70408 + } + + // re-encode + jsonMap = encodeDecodeJson(c) + + jv, exists := jsonMap["value"] + if !exists { + t.Fatalf("errored characteristic is missing \"value\": %+v", jsonMap) + } + if is, want := reflect.TypeOf(jv), reflect.TypeOf(c.Val); is != want { + t.Fatalf("json-encoded value is of wrong type: is=%v want=%v", is, want) + } + } +} diff --git a/characteristics.go b/characteristics.go index 1f6f72d..ac8cbbc 100644 --- a/characteristics.go +++ b/characteristics.go @@ -12,9 +12,9 @@ import ( ) type characteristicData struct { - Aid uint64 `json:"aid"` - Iid uint64 `json:"iid"` - Value interface{} `json:"value"` + Aid uint64 `json:"aid"` + Iid uint64 `json:"iid"` + Value *characteristic.V `json:"value,omitempty"` // optional values Type *string `json:"type,omitempty"` @@ -88,7 +88,7 @@ func (srv *Server) getCharacteristics(res http.ResponseWriter, req *http.Request err = true cdata.Status = &s } else { - cdata.Value = v + cdata.Value = &characteristic.V{v} } if meta { @@ -142,6 +142,14 @@ func (srv *Server) getCharacteristics(res http.ResponseWriter, req *http.Request log.Debug.Println(toJSON(resp)) if err { + // when there's an error somewhere, "status: 0" must now be explicit + noError := 0 + for _, c := range arr { + if c.Status == nil { + c.Status = &noError + } + } + JsonMultiStatus(res, resp) } else { JsonOK(res, resp) diff --git a/notification.go b/notification.go index 2ce0aec..1e2a525 100644 --- a/notification.go +++ b/notification.go @@ -20,7 +20,7 @@ func sendNotification(a *accessory.A, c *characteristic.C, req *http.Request) er characteristicData{ Aid: a.Id, Iid: c.Id, - Value: c.Val, + Value: &characteristic.V{c.Val}, }, }, } diff --git a/server_test.go b/server_test.go index 74c21d9..5ffd087 100644 --- a/server_test.go +++ b/server_test.go @@ -2,6 +2,7 @@ package hap import ( "github.com/brutella/hap/accessory" + "github.com/brutella/hap/characteristic" "github.com/brutella/hap/service" "bytes" @@ -199,3 +200,40 @@ func TestGetProgrammableSwitchEvent(t *testing.T) { t.Fatalf("%v != %v", is, want) } } + +func TestGetValueRequestPartialFailure(t *testing.T) { + a := accessory.NewOutlet(accessory.Info{Name: "ABC"}) + sw1 := a.Outlet.On + sw2 := characteristic.NewOn() + a.Outlet.AddC(sw2.C) + + a.Outlet.On.ValueRequestFunc = func(r *http.Request) (interface{}, int) { + return nil, JsonStatusResourceBusy + } + + srv, err := NewServer(NewMemStore(), a.A) + if err != nil { + t.Fatal(err) + } + + req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/characteristics?id=%d.%d,%[1]d.%[3]d", a.Id, sw1.Id, sw2.Id), nil) + w := httptest.NewRecorder() + + srv.setSession(req.RemoteAddr, &session{}) + srv.ss.Handler.ServeHTTP(w, req) + + r := w.Result() + if is, want := r.StatusCode, http.StatusMultiStatus; is != want { + t.Fatalf("%v != %v", is, want) + } + + b, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Fatal(err) + } + + body := fmt.Sprintf("{\"characteristics\":[{\"aid\":%d,\"iid\":%d,\"status\":%d},{\"aid\":%[1]d,\"iid\":%[4]d,\"value\":false,\"status\":0}]}", a.Id, sw1.Id, JsonStatusResourceBusy, sw2.Id) + if is, want := string(b), body; is != want { + t.Fatalf("%v != %v", is, want) + } +} From 309448cc466ff64d1633e625e7bd3d87df7a256c Mon Sep 17 00:00:00 2001 From: Darell Tan Date: Sat, 20 May 2023 22:27:56 +0800 Subject: [PATCH 35/58] Fixed regression where Identify emitted "value" A special case for the Identify characteristic is that it must not emit a "value" entry in the JSON, due to it being write-only. Also added a test to ensure this doesn't happen in future. --- characteristic/c.go | 2 +- characteristic/c_test.go | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/characteristic/c.go b/characteristic/c.go index 396a866..48ed8af 100644 --- a/characteristic/c.go +++ b/characteristic/c.go @@ -257,7 +257,7 @@ func (c *C) MarshalJSON() ([]byte, error) { Permissions []string `json:"perms"` Format string `json:"format"` - Value *V `json:"value"` + Value *V `json:"value,omitempty"` Description string `json:"description,omitempty"` // manufacturer description (optional) Unit string `json:"unit,omitempty"` MaxLen int `json:"maxLen,omitempty"` diff --git a/characteristic/c_test.go b/characteristic/c_test.go index 0e884ea..03b284c 100644 --- a/characteristic/c_test.go +++ b/characteristic/c_test.go @@ -245,4 +245,12 @@ func TestCharacteristicJson(t *testing.T) { t.Fatalf("json-encoded value is of wrong type: is=%v want=%v", is, want) } } + + // special case /identify must not emit any "value" + id := NewIdentify().C + jsonMap := encodeDecodeJson(id) + + if _, exists := jsonMap["value"]; exists { + t.Fatalf("Identify characteristic cannot emit \"value\": %+v", jsonMap) + } } From dc6d9fb1cdf0e973eca437960d9fa71a8676fc97 Mon Sep 17 00:00:00 2001 From: gaosen <0x5e@sina.cn> Date: Fri, 28 Jul 2023 16:15:22 +0800 Subject: [PATCH 36/58] fix tlv8.Unmarshal failure for rtp.RTPParams --- rtp/stream_configuration.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rtp/stream_configuration.go b/rtp/stream_configuration.go index f0882df..8ce993e 100644 --- a/rtp/stream_configuration.go +++ b/rtp/stream_configuration.go @@ -38,6 +38,6 @@ type RTPParams struct { Ssrc int32 `tlv8:"2"` Bitrate uint16 `tlv8:"3"` Interval float32 `tlv8:"4"` // MinimumRTCP interval - ComfortNoisePayloadType uint8 `tlv8:"5"` // only for audio - MTU uint16 `tlv8:"6"` // only for video + ComfortNoisePayloadType uint8 `tlv8:"5,optional"` // only for audio + MTU uint16 `tlv8:"6,optional"` // only for video } From 4bda4dfb9e4046d4733fc7ac1b534a1a9b2e5c78 Mon Sep 17 00:00:00 2001 From: gaosen <0x5e@sina.cn> Date: Mon, 31 Jul 2023 14:01:01 +0800 Subject: [PATCH 37/58] Change RTPParams.Ssrc to uint32 --- rtp/stream_configuration.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtp/stream_configuration.go b/rtp/stream_configuration.go index 8ce993e..08f4d38 100644 --- a/rtp/stream_configuration.go +++ b/rtp/stream_configuration.go @@ -35,7 +35,7 @@ type AudioParameters struct { type RTPParams struct { PayloadType uint8 `tlv8:"1"` - Ssrc int32 `tlv8:"2"` + Ssrc uint32 `tlv8:"2"` Bitrate uint16 `tlv8:"3"` Interval float32 `tlv8:"4"` // MinimumRTCP interval ComfortNoisePayloadType uint8 `tlv8:"5,optional"` // only for audio From 0f706faec55a01e12c72b6ed254f4a74843a9cb9 Mon Sep 17 00:00:00 2001 From: gaosen <0x5e@sina.cn> Date: Mon, 31 Jul 2023 14:05:11 +0800 Subject: [PATCH 38/58] Add RTPParams testcase --- rtp/stream_configuration_test.go | 71 ++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/rtp/stream_configuration_test.go b/rtp/stream_configuration_test.go index bf76251..033204d 100644 --- a/rtp/stream_configuration_test.go +++ b/rtp/stream_configuration_test.go @@ -21,3 +21,74 @@ func TestSelectedStreamConfiguration(t *testing.T) { fmt.Printf("%+v", cfg) } + +func TestRTPParams(t *testing.T) { + + videoBuf := []byte{ + // tag, len, data + 1, 1, 99, + 2, 4, 26, 144, 146, 159, + 3, 2, 43, 1, + 4, 4, 0, 0, 0, 63, + 5, 2, 98, 5, + } + + var videoParam RTPParams + if err := tlv8.Unmarshal(videoBuf, &videoParam); err != nil { + t.Error(err) + } + + if videoParam.PayloadType != 99 { + t.Error("Video PayloadType wrong:", videoParam.PayloadType) + } + + if videoParam.Ssrc != 2677182490 { + t.Error("Video Ssrc wrong", videoParam.Ssrc) + } + + if videoParam.Bitrate != 299 { + t.Error("Video Bitrate wrong", videoParam.Bitrate) + } + + if videoParam.Interval != 0.5 { + t.Error("Video Interval wrong", videoParam.Interval) + } + + if videoParam.ComfortNoisePayloadType != 98 { + t.Error("Video ComfortNoisePayloadType wrong", videoParam.ComfortNoisePayloadType) + } + + audioBuf := []byte{ + // tag, len, data + 1, 1, 110, + 2, 4, 207, 83, 180, 9, + 3, 2, 24, 0, + 4, 4, 0, 0, 160, 64, + 6, 1, 13, + } + + var audioParam RTPParams + if err := tlv8.Unmarshal(audioBuf, &audioParam); err != nil { + t.Error(err) + } + + if audioParam.PayloadType != 110 { + t.Error("Audio PayloadType wrong:", audioParam.PayloadType) + } + + if audioParam.Ssrc != 162812879 { + t.Error("Audio Ssrc wrong", audioParam.Ssrc) + } + + if audioParam.Bitrate != 24 { + t.Error("Audio Bitrate wrong", audioParam.Bitrate) + } + + if audioParam.Interval != 5 { + t.Error("Audio Interval wrong", audioParam.Interval) + } + + if audioParam.MTU != 13 { + t.Error("Audio MTU wrong", audioParam.MTU) + } +} From 159f576f44eae8598c828dff96530a12de94aa2e Mon Sep 17 00:00:00 2001 From: Darell Tan Date: Mon, 7 Aug 2023 23:53:08 +0800 Subject: [PATCH 39/58] Disable TCP keepalives to avoid constantly waking iOS devices --- listener.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/listener.go b/listener.go index dc49df7..3a4e4af 100644 --- a/listener.go +++ b/listener.go @@ -14,6 +14,11 @@ func (ln *listener) Accept() (con net.Conn, err error) { return } + // disable TCP keepalives + if tcpconn, ok := con.(*net.TCPConn); ok { + tcpconn.SetKeepAlive(false) + } + conn := newConn(con) setConn(conn.RemoteAddr().String(), conn) From 049165819114226d40dc7a6cca33478fd4344e19 Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Mon, 14 Aug 2023 15:42:21 +0200 Subject: [PATCH 40/58] Add protocol information service --- service/protocol_information.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 service/protocol_information.go diff --git a/service/protocol_information.go b/service/protocol_information.go new file mode 100644 index 0000000..5772e73 --- /dev/null +++ b/service/protocol_information.go @@ -0,0 +1,23 @@ +package service + +import ( + "github.com/brutella/hap/characteristic" +) + +const TypeProtocolInformation = "A2" + +type ProtocolInformation struct { + *S + + Version *characteristic.Version +} + +func NewProtocolInformation() *ProtocolInformation { + s := ProtocolInformation{} + s.S = New(TypeProtocolInformation) + + s.Version = characteristic.NewVersion() + s.AddC(s.Version.C) + + return &s +} From 4a65b6f1f3df68e9aa837ed605ddf52100965061 Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Thu, 17 Aug 2023 10:36:28 +0200 Subject: [PATCH 41/58] Check for integer value early on --- characteristic/c.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/characteristic/c.go b/characteristic/c.go index 48ed8af..6250cb8 100644 --- a/characteristic/c.go +++ b/characteristic/c.go @@ -343,6 +343,11 @@ func (c *C) convert(v interface{}) interface{} { } func (c *C) validVal(v interface{}) bool { + iv, ok := v.(int) + if !ok { + return true + } + if len(c.ValidVals) > 0 { for _, val := range c.ValidVals { if val == v { @@ -353,7 +358,7 @@ func (c *C) validVal(v interface{}) bool { return false } - if iv, ok := v.(int); ok && len(c.ValidRange) == 2 { + if len(c.ValidRange) == 2 { return c.ValidRange[0] <= iv && c.ValidRange[1] >= iv } From ed349f153c49db8a375dfd1b2af495716e8316c0 Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Thu, 17 Aug 2023 10:37:01 +0200 Subject: [PATCH 42/58] Format code --- rtp/stream_configuration.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtp/stream_configuration.go b/rtp/stream_configuration.go index 08f4d38..e3e7bd5 100644 --- a/rtp/stream_configuration.go +++ b/rtp/stream_configuration.go @@ -37,7 +37,7 @@ type RTPParams struct { PayloadType uint8 `tlv8:"1"` Ssrc uint32 `tlv8:"2"` Bitrate uint16 `tlv8:"3"` - Interval float32 `tlv8:"4"` // MinimumRTCP interval + Interval float32 `tlv8:"4"` // MinimumRTCP interval ComfortNoisePayloadType uint8 `tlv8:"5,optional"` // only for audio MTU uint16 `tlv8:"6,optional"` // only for video } From 655ec855f38c140329a3be4f6d94ae0d6babf8c1 Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Fri, 25 Aug 2023 12:54:22 +0200 Subject: [PATCH 43/58] Support timed write procedure (HAP 6.7.2.4) --- characteristic/c.go | 17 ++++++++- characteristic/c_test.go | 37 +++++++++--------- characteristics.go | 56 ++++++++++++++++++++++++++- server.go | 23 ++++++++++++ server_test.go | 81 ++++++++++++++++++++++++++++++++++++++++ session.go | 9 +++++ 6 files changed, 201 insertions(+), 22 deletions(-) diff --git a/characteristic/c.go b/characteristic/c.go index 6250cb8..83ebe5d 100644 --- a/characteristic/c.go +++ b/characteristic/c.go @@ -11,9 +11,10 @@ import ( const ( PermissionRead = "pr" // The characteristic can only be read by paired controllers. PermissionWrite = "pw" // The characteristic can only be written by paired controllers. + PermissionTimedWrite = "tw" // The characteristic allows only timed write procedure. PermissionEvents = "ev" // The characteristic supports events. - PermissionHidden = "hd" // The characteristic is hidden from the user - PermissionWriteResponse = "wr" // The characteristic supports write response + PermissionHidden = "hd" // The characteristic is hidden from the user. + PermissionWriteResponse = "wr" // The characteristic supports write response. ) const ( @@ -232,6 +233,18 @@ func (c *C) IsReadable() bool { return false } +// RequiresTimedWrite returns true if the value can +// only be set with a timed write procedure. +func (c *C) RequiresTimedWrite() bool { + for _, p := range c.Permissions { + if p == PermissionTimedWrite { + return true + } + } + + return false +} + // IsObservable returns true if clients are allowed // to observe the value of the characteristic. func (c *C) IsObservable() bool { diff --git a/characteristic/c_test.go b/characteristic/c_test.go index 03b284c..3a33c36 100644 --- a/characteristic/c_test.go +++ b/characteristic/c_test.go @@ -179,6 +179,7 @@ func TestValidValues(t *testing.T) { t.Fatal("no error expected") } } + func TestValidRange(t *testing.T) { c := NewTargetHeaterCoolerState() c.ValidRange = []int{TargetHeaterCoolerStateAuto, TargetHeaterCoolerStateHeat} @@ -192,6 +193,21 @@ func TestValidRange(t *testing.T) { } } +func encodeDecodeJson(c *C, t *testing.T) map[string]interface{} { + j, err := c.MarshalJSON() + if err != nil { + t.Fatal("cannot MarshalJSON: ", err) + } + + var jsonMap map[string]interface{} + err = json.Unmarshal(j, &jsonMap) + if err != nil { + t.Fatal("invalid encoded JSON: ", err) + } + + return jsonMap +} + func TestCharacteristicJson(t *testing.T) { cs := []*C{ //NewContactSensorState().C, // int @@ -200,23 +216,8 @@ func TestCharacteristicJson(t *testing.T) { NewAccessoryIdentifier().C, // string } - encodeDecodeJson := func(c *C) map[string]interface{} { - j, err := c.MarshalJSON() - if err != nil { - t.Fatal("cannot MarshalJSON: ", err) - } - - var jsonMap map[string]interface{} - err = json.Unmarshal(j, &jsonMap) - if err != nil { - t.Fatal("invalid encoded JSON: ", err) - } - - return jsonMap - } - for _, c := range cs { - jsonMap := encodeDecodeJson(c) + jsonMap := encodeDecodeJson(c, t) // verify properties if is, want := jsonMap["type"], c.Type; is != want { @@ -235,7 +236,7 @@ func TestCharacteristicJson(t *testing.T) { } // re-encode - jsonMap = encodeDecodeJson(c) + jsonMap = encodeDecodeJson(c, t) jv, exists := jsonMap["value"] if !exists { @@ -248,7 +249,7 @@ func TestCharacteristicJson(t *testing.T) { // special case /identify must not emit any "value" id := NewIdentify().C - jsonMap := encodeDecodeJson(id) + jsonMap := encodeDecodeJson(id, t) if _, exists := jsonMap["value"]; exists { t.Fatalf("Identify characteristic cannot emit \"value\": %+v", jsonMap) diff --git a/characteristics.go b/characteristics.go index ac8cbbc..c292ef3 100644 --- a/characteristics.go +++ b/characteristics.go @@ -1,6 +1,8 @@ package hap import ( + "time" + "github.com/brutella/hap/accessory" "github.com/brutella/hap/characteristic" "github.com/brutella/hap/log" @@ -164,19 +166,23 @@ func (srv *Server) putCharacteristics(res http.ResponseWriter, req *http.Request } data := struct { - Cs []putCharacteristicData `json:"characteristics"` + Cs []putCharacteristicData `json:"characteristics"` + Pid uint64 `json:"pid"` }{} + err := json.NewDecoder(req.Body).Decode(&data) if err != nil { JsonError(res, JsonStatusInvalidValueInRequest) return } + timedWr := srv.TimedWrite(req) log.Debug.Println(toJSON(data)) arr := []*putCharacteristicData{} for _, d := range data.Cs { c := srv.findC(d.Aid, d.Iid) + cdata := &putCharacteristicData{ Aid: d.Aid, Iid: d.Iid, @@ -191,7 +197,24 @@ func (srv *Server) putCharacteristics(res http.ResponseWriter, req *http.Request var value interface{} var status int - if d.Value != nil { + if c.RequiresTimedWrite() { + if time.Now().After(timedWr.deadline) { + // HAP 6.7.2.4 + // If the accessory receives an Execute Write Request after the TTL has expired it must ignore + // the request and respond with HAP status error code -70410 (HAPIPStatusErrorCodeInvalidWrite). + log.Info.Println("timed write wall time exceeded") + status = -70410 + } + if data.Pid != timedWr.pid { + // HAP 6.7.2.4 + // If the accessory receives a standard write request on a characteristic which requires timed write, + // the accessory must respond with HAP status error code -70410 (HAPIPStatusErrorCodeInvalidWrite). + log.Info.Println("timed write transaction id invalid") + status = -70410 + } + } + + if d.Value != nil && status == 0 { value, status = c.SetValueRequest(d.Value, req) } @@ -218,6 +241,8 @@ func (srv *Server) putCharacteristics(res http.ResponseWriter, req *http.Request } } + srv.DelTimedWrite(req) + if len(arr) == 0 { res.WriteHeader(http.StatusNoContent) return @@ -250,3 +275,30 @@ func (srv *Server) findC(aid, iid uint64) *characteristic.C { return nil } + +func (srv *Server) prepareCharacteristics(res http.ResponseWriter, req *http.Request) { + if !srv.IsAuthorized(req) { + log.Info.Printf("request from %s not authorized\n", req.RemoteAddr) + JsonError(res, JsonStatusInsufficientPrivileges) + return + } + + data := struct { + Ttl uint64 `json:"ttl"` + Pid uint64 `json:"pid"` + }{} + + err := json.NewDecoder(req.Body).Decode(&data) + if err != nil || data.Ttl == 0 || data.Pid == 0 { + JsonError(res, JsonStatusInvalidValueInRequest) + return + } + + srv.SetTimedWrite(data.Ttl, data.Pid, req) + + resp := struct { + Status int `json:"status"` + }{0} + log.Debug.Println(toJSON(resp)) + JsonOK(res, resp) +} diff --git a/server.go b/server.go index b1c90d9..d89d5d6 100644 --- a/server.go +++ b/server.go @@ -2,6 +2,7 @@ package hap import ( "sync" + "time" "github.com/brutella/dnssd" "github.com/brutella/hap/accessory" @@ -147,6 +148,7 @@ func NewServer(store Store, a *accessory.A, as ...*accessory.A) (*Server, error) r.Get("/accessories", s.getAccessories) r.Get("/characteristics", s.getCharacteristics) r.Put("/characteristics", s.putCharacteristics) + r.Put("/prepare", s.prepareCharacteristics) r.Post("/pairings", s.pairings) }) @@ -165,6 +167,27 @@ func (s *Server) IsAuthorized(request *http.Request) bool { return ss != nil } +func (s *Server) TimedWrite(request *http.Request) *TimedWrite { + if ss, _ := s.getSession(request.RemoteAddr); ss != nil { + return ss.twr + } + + return nil +} + +func (s *Server) SetTimedWrite(ttl, pid uint64, request *http.Request) { + if ss, _ := s.getSession(request.RemoteAddr); ss != nil { + t := time.Now().Add(time.Duration(ttl) * time.Millisecond) + ss.twr = &TimedWrite{t, pid} + } +} + +func (s *Server) DelTimedWrite(request *http.Request) { + if ss, _ := s.getSession(request.RemoteAddr); ss != nil { + ss.twr = nil + } +} + // IsPaired returns true if the server is paired with a client (iOS). func (s *Server) IsPaired() bool { return len(s.st.Pairings()) > 0 diff --git a/server_test.go b/server_test.go index 5ffd087..7c8a49d 100644 --- a/server_test.go +++ b/server_test.go @@ -129,6 +129,87 @@ func TestSetValueRequestSuccess(t *testing.T) { } } +func TestPrepareValueRequest(t *testing.T) { + a := accessory.NewOutlet(accessory.Info{Name: "ABC"}) + a.Outlet.On.Permissions = append(a.Outlet.On.Permissions, characteristic.PermissionTimedWrite) + + s, err := NewServer(NewMemStore(), a.A) + if err != nil { + t.Fatal(err) + } + + t.Run("prepare", func(t *testing.T) { + body := fmt.Sprintf("{\"ttl\":500,\"pid\":123456789}") + req := httptest.NewRequest(http.MethodPut, "/prepare", bytes.NewBuffer([]byte(body))) + s.setSession(req.RemoteAddr, &session{}) + + w := httptest.NewRecorder() + + s.ss.Handler.ServeHTTP(w, req) + + r := w.Result() + if is, want := r.StatusCode, http.StatusOK; is != want { + t.Fatalf("%v != %v", is, want) + } + + b, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Fatal(err) + } + + if is, want := string(b), "{\"status\":0}"; is != want { + t.Fatalf("%v != %v", is, want) + } + }) + + t.Run("put", func(t *testing.T) { + body := fmt.Sprintf("{\"characteristics\":[{\"aid\":%d,\"iid\":%d,\"value\":true}],\"pid\":123456789}", a.Id, a.Outlet.On.Id) + req := httptest.NewRequest(http.MethodPut, "/characteristics", bytes.NewBuffer([]byte(body))) + w := httptest.NewRecorder() + + var setValueRequestFunc, onValueUpdateFunc bool + a.Outlet.On.SetValueRequestFunc = func(v interface{}, r *http.Request) (interface{}, int) { + if is, want := v.(bool), true; is != want { + t.Fatalf("%v != %v", is, want) + } + setValueRequestFunc = true + + return v, 0 + } + + a.Outlet.On.OnValueUpdate(func(new bool, old bool, r *http.Request) { + if is, want := new, true; is != want { + t.Fatalf("%v != %v", is, want) + } + + if is, want := old, false; is != want { + t.Fatalf("%v != %v", is, want) + } + + onValueUpdateFunc = true + }) + + s.ss.Handler.ServeHTTP(w, req) + + r := w.Result() + if is, want := r.StatusCode, http.StatusNoContent; is != want { + t.Fatalf("%v != %v", is, want) + } + + if is, want := setValueRequestFunc, true; is != want { + t.Fatalf("%v != %v", is, want) + } + + if is, want := onValueUpdateFunc, true; is != want { + t.Fatalf("%v != %v", is, want) + } + + if is, want := a.Outlet.On.Value(), true; is != want { + t.Fatalf("%v != %v", is, want) + } + }) +} + func TestSetValueRequestFailure(t *testing.T) { a := accessory.NewOutlet(accessory.Info{Name: "ABC"}) diff --git a/session.go b/session.go index f3386df..4f4f2ba 100644 --- a/session.go +++ b/session.go @@ -1,6 +1,8 @@ package hap import ( + "time" + "github.com/brutella/hap/chacha20poly1305" "github.com/brutella/hap/hkdf" @@ -50,6 +52,13 @@ type session struct { decryptKey [32]byte encryptCount uint64 decryptCount uint64 + + twr *TimedWrite +} + +type TimedWrite struct { + deadline time.Time + pid uint64 } func newSession(shared [32]byte, p Pairing) (*session, error) { From b19bd2f1144e98be8f5599a3b9ff132a1c4523d7 Mon Sep 17 00:00:00 2001 From: Darell Tan Date: Sun, 27 Aug 2023 16:56:10 +0800 Subject: [PATCH 44/58] Implement WriteResponse characteristics Some control points require that the response be returned after a write, as opposed to write-then-read. If the SetValueRequestFunc returns a value and has the WriteResponse permission, then the response is relayed back to the client on return. Status code must be explicitly returned in this case, via a MultiStatus return. --- characteristic/c.go | 12 +++++++++++ characteristics.go | 6 +++++- server_test.go | 52 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/characteristic/c.go b/characteristic/c.go index 83ebe5d..1323102 100644 --- a/characteristic/c.go +++ b/characteristic/c.go @@ -245,6 +245,18 @@ func (c *C) RequiresTimedWrite() bool { return false } +// IsWriteResponse returns true if the value can +// return a response on write +func (c *C) IsWriteResponse() bool { + for _, p := range c.Permissions { + if p == PermissionWriteResponse { + return true + } + } + + return false +} + // IsObservable returns true if clients are allowed // to observe the value of the characteristic. func (c *C) IsObservable() bool { diff --git a/characteristics.go b/characteristics.go index c292ef3..4118a5c 100644 --- a/characteristics.go +++ b/characteristics.go @@ -222,8 +222,12 @@ func (srv *Server) putCharacteristics(res http.ResponseWriter, req *http.Request cdata.Status = &status } - if d.Response != nil && value != nil { + if (d.Response != nil || c.IsWriteResponse()) && value != nil { cdata.Value = value + + if c.IsWriteResponse() { + cdata.Status = &status + } } if d.Events != nil { diff --git a/server_test.go b/server_test.go index 7c8a49d..273d2ff 100644 --- a/server_test.go +++ b/server_test.go @@ -129,6 +129,58 @@ func TestSetValueRequestSuccess(t *testing.T) { } } +func TestWriteResponseCharacteristic(t *testing.T) { + a := accessory.NewOutlet(accessory.Info{Name: "ABC"}) + c := characteristic.NewString("18") + c.Permissions = []string{characteristic.PermissionRead, characteristic.PermissionWrite, characteristic.PermissionWriteResponse} + a.Outlet.AddC(c.C) + + s, err := NewServer(NewMemStore(), a.A) + if err != nil { + t.Fatal(err) + } + + t.Run("put", func(t *testing.T) { + body := fmt.Sprintf("{\"characteristics\":[{\"aid\":%d,\"iid\":%d,\"value\":\"ABC\",\"r\":true}],\"pid\":0}", a.Id, c.Id) + req := httptest.NewRequest(http.MethodPut, "/characteristics", bytes.NewBuffer([]byte(body))) + w := httptest.NewRecorder() + + s.setSession(req.RemoteAddr, &session{}) + + setValueRequestFunc := false + c.SetValueRequestFunc = func(v interface{}, r *http.Request) (interface{}, int) { + if is, want := v.(string), "ABC"; is != want { + t.Fatalf("%v != %v", is, want) + } + setValueRequestFunc = true + + return "DEF", 0 + } + + s.ss.Handler.ServeHTTP(w, req) + + r := w.Result() + if is, want := r.StatusCode, http.StatusMultiStatus; is != want { + t.Fatalf("%v != %v", is, want) + } + + if is, want := setValueRequestFunc, true; is != want { + t.Fatalf("%v != %v", is, want) + } + + // check reply body + b, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Fatal(err) + } + + body = fmt.Sprintf("{\"characteristics\":[{\"aid\":%d,\"iid\":%d,\"value\":\"DEF\",\"status\":0}]}", a.Id, c.Id) + if is, want := string(b), body; is != want { + t.Fatalf("%v != %v", is, want) + } + }) +} + func TestPrepareValueRequest(t *testing.T) { a := accessory.NewOutlet(accessory.Info{Name: "ABC"}) a.Outlet.On.Permissions = append(a.Outlet.On.Permissions, characteristic.PermissionTimedWrite) From f60335df1435d2dbe59333a422352974b286bb4a Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Thu, 19 Oct 2023 19:32:55 +0000 Subject: [PATCH 45/58] feat: add motion and contact sensor accessories --- accessory/contact_sensor.go | 19 +++++++++++++++++++ accessory/motion_sensor.go | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 accessory/contact_sensor.go create mode 100644 accessory/motion_sensor.go diff --git a/accessory/contact_sensor.go b/accessory/contact_sensor.go new file mode 100644 index 0000000..d002f44 --- /dev/null +++ b/accessory/contact_sensor.go @@ -0,0 +1,19 @@ +package accessory + +import "github.com/brutella/hap/service" + +type ContactSensor struct { + *A + ContactSensor *service.ContactSensor +} + +// NewContactSensor implements a contact sensor. +func NewContactSensor(info Info) *ContactSensor { + a := ContactSensor{} + a.A = New(info, TypeSensor) + + a.ContactSensor = service.NewContactSensor() + a.AddS(a.ContactSensor.S) + + return &a +} diff --git a/accessory/motion_sensor.go b/accessory/motion_sensor.go new file mode 100644 index 0000000..3a78daa --- /dev/null +++ b/accessory/motion_sensor.go @@ -0,0 +1,19 @@ +package accessory + +import "github.com/brutella/hap/service" + +type MotionSensor struct { + *A + MotionSensor *service.MotionSensor +} + +// NewMotionSensor returns a motion sensor. +func NewMotionSensor(info Info) *MotionSensor { + a := MotionSensor{} + a.A = New(info, TypeSensor) + + a.MotionSensor = service.NewMotionSensor() + a.AddS(a.MotionSensor.S) + + return &a +} From ed802c6b25ee6b562cb19ad6eead58f47c783129 Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Mon, 6 Nov 2023 10:58:51 +0100 Subject: [PATCH 46/58] Require dnssd v1.2.10 --- go.mod | 4 ++-- go.sum | 55 ++++++++++++++++++++++++++++++------------------------- 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index 17eef69..66f2d89 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,11 @@ module github.com/brutella/hap go 1.16 require ( - github.com/brutella/dnssd v1.2.7 + github.com/brutella/dnssd v1.2.10 github.com/go-chi/chi v1.5.4 github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9 github.com/xiam/to v0.0.0-20200126224905-d60d31e03561 golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 - golang.org/x/text v0.3.8 + golang.org/x/text v0.9.0 gopkg.in/yaml.v2 v2.2.8 // indirect ) diff --git a/go.sum b/go.sum index 84d2fcc..04c237b 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,11 @@ -github.com/brutella/dnssd v1.2.7 h1:Uq2NgLzlUz5JWIzcug9xRU6v0UApHrlxbsREA5B1RrY= -github.com/brutella/dnssd v1.2.7/go.mod h1:JoW2sJUrmVIef25G6lrLj7HS6Xdwh6q8WUIvMkkBYXs= +github.com/brutella/dnssd v1.2.10 h1:Gg0k7+NtJp7TbOMS0eUVg0VEjSdftzKOTQ8QQTzQ0x4= +github.com/brutella/dnssd v1.2.10/go.mod h1:yZ+GHHbGhtp5yJeKTnppdFGiy6OhiPoxs0WHW1KUcFA= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs= github.com/go-chi/chi v1.5.4/go.mod h1:uaf8YgoFazUOkPBG7fxPftUylNumIev9awIWOENIuEg= -github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= -github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= +github.com/miekg/dns v1.1.54 h1:5jon9mWcb0sFJGpnI99tOMhCPyJ+RPVz5b63MQG0VWI= +github.com/miekg/dns v1.1.54/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -15,55 +15,60 @@ github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9 h1:aeN+ghOV0b2VC github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9/go.mod h1:roo6cZ/uqpwKMuvPG0YmzI5+AmUiMWfjCBZpGXqbTxE= github.com/xiam/to v0.0.0-20200126224905-d60d31e03561 h1:SVoNK97S6JlaYlHcaC+79tg3JUlQABcc0dH2VQ4Y+9s= github.com/xiam/to v0.0.0-20200126224905-d60d31e03561/go.mod h1:cqbG7phSzrbdg3aj+Kn63bpVruzwDZi58CpxlZkjwzw= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 h1:71vQrMauZZhcTVK6KdYM+rklehEEwb3E+ZhaE5jrPrE= golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= +golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= From efe5bc92ed9208dd11fa4f5a29badca0d1b61edf Mon Sep 17 00:00:00 2001 From: salman Date: Wed, 15 Nov 2023 16:29:50 +0300 Subject: [PATCH 47/58] use correct content-type for /pairings HomeKit expects an application/pairing+tlv8 content type for responses from /pairings, and fails to add the accessory if it gets something different. You can reproduce the error by attempting to add an accessory as a user who is an admin but not the owner of the home. This will result in a flow that will attempt to use the AddPairing method. On all my devices this fails unless the content type is application/pairing+tlv8. --- server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.go b/server.go index d89d5d6..4e8d266 100644 --- a/server.go +++ b/server.go @@ -139,6 +139,7 @@ func NewServer(store Store, a *accessory.A, as ...*accessory.A) (*Server, error) r.Post("/pair-setup", s.pairSetup) r.Post("/pair-verify", s.pairVerify) r.Post("/identify", s.identify) + r.Post("/pairings", s.pairings) }) // The json encoded content is encrypted. The encryption keys @@ -149,7 +150,6 @@ func NewServer(store Store, a *accessory.A, as ...*accessory.A) (*Server, error) r.Get("/characteristics", s.getCharacteristics) r.Put("/characteristics", s.putCharacteristics) r.Put("/prepare", s.prepareCharacteristics) - r.Post("/pairings", s.pairings) }) return s, nil From fdea26395409e8397311794765061cca4e0b01e1 Mon Sep 17 00:00:00 2001 From: ItsNotGoodName Date: Wed, 27 Dec 2023 20:48:12 -0800 Subject: [PATCH 48/58] fix: wrong bytes written for conn when encrypting --- conn.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/conn.go b/conn.go index 1adbc60..603401b 100644 --- a/conn.go +++ b/conn.go @@ -60,9 +60,15 @@ func (c *conn) Write(b []byte) (int, error) { } encB, err := ioutil.ReadAll(enc) - n, err := c.Conn.Write(encB) + if err != nil { + return 0, err + } + _, err = c.Conn.Write(encB) + if err != nil { + return 0, err + } - return n, err + return len(b), nil } const ( From 152c58cbe9f0f09866784279e8e8ddbb9eaf8189 Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Thu, 1 Feb 2024 16:04:35 +0100 Subject: [PATCH 49/58] Add new way of normalizing dnssd server name --- go.mod | 3 +-- go.sum | 17 +++++++++++++---- server.go | 23 ++++------------------- server_test.go | 16 ++++++++++++++++ 4 files changed, 34 insertions(+), 25 deletions(-) diff --git a/go.mod b/go.mod index 66f2d89..aac8198 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,5 @@ require ( github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9 github.com/xiam/to v0.0.0-20200126224905-d60d31e03561 golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 - golang.org/x/text v0.9.0 - gopkg.in/yaml.v2 v2.2.8 // indirect + gopkg.in/Regis24GmbH/go-diacritics.v2 v2.0.3 ) diff --git a/go.sum b/go.sum index 04c237b..2559264 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,8 @@ github.com/brutella/dnssd v1.2.10 h1:Gg0k7+NtJp7TbOMS0eUVg0VEjSdftzKOTQ8QQTzQ0x4= github.com/brutella/dnssd v1.2.10/go.mod h1:yZ+GHHbGhtp5yJeKTnppdFGiy6OhiPoxs0WHW1KUcFA= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs= github.com/go-chi/chi v1.5.4/go.mod h1:uaf8YgoFazUOkPBG7fxPftUylNumIev9awIWOENIuEg= github.com/miekg/dns v1.1.54 h1:5jon9mWcb0sFJGpnI99tOMhCPyJ+RPVz5b63MQG0VWI= @@ -9,8 +10,13 @@ github.com/miekg/dns v1.1.54/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9 h1:aeN+ghOV0b2VCmKKO3gqnDQ8mLbpABZgRR2FVYx4ouI= github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9/go.mod h1:roo6cZ/uqpwKMuvPG0YmzI5+AmUiMWfjCBZpGXqbTxE= github.com/xiam/to v0.0.0-20200126224905-d60d31e03561 h1:SVoNK97S6JlaYlHcaC+79tg3JUlQABcc0dH2VQ4Y+9s= @@ -69,7 +75,10 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/Regis24GmbH/go-diacritics.v2 v2.0.3 h1:rz88vn1OH2B9kKorR+QCrcuw6WbizVwahU2Y9Q09xqU= +gopkg.in/Regis24GmbH/go-diacritics.v2 v2.0.3/go.mod h1:vJmfdx2L0+30M90zUd0GCjLV14Ip3ZgWR5+MV1qljOo= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/server.go b/server.go index 4e8d266..440c048 100644 --- a/server.go +++ b/server.go @@ -11,9 +11,7 @@ import ( "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" "github.com/xiam/to" - "golang.org/x/text/secure/precis" - "golang.org/x/text/transform" - "golang.org/x/text/unicode/norm" + godiacritics "gopkg.in/Regis24GmbH/go-diacritics.v2" "bytes" "context" @@ -26,7 +24,6 @@ import ( "reflect" "strconv" "strings" - "unicode" ) // A server handles incoming HTTP request for an accessory. @@ -507,9 +504,8 @@ func (s *Server) service() (dnssd.Service, error) { // // [Radar] http://openradar.appspot.com/radar?id=4931940373233664 stripped := strings.Replace(s.a.Info.Name.Value(), " ", "_", -1) - cfg := dnssd.Config{ - Name: removeAccentsFromString(stripped), + Name: normalize(stripped), Type: "_hap._tcp", Domain: "local", Host: strings.Replace(s.uuid, ":", "", -1), // use the id (without the colons) to get unique hostnames @@ -544,19 +540,8 @@ func (s *Server) fmtPin() string { return first + "-" + second + "-" + third } -// RemoveAccentsFromString removes accent characters from string -// From https://stackoverflow.com/a/40405242/424814 -func removeAccentsFromString(v string) string { - var loosecompare = precis.NewIdentifier( - precis.AdditionalMapping(func() transform.Transformer { - return transform.Chain(norm.NFD, transform.RemoveFunc(func(r rune) bool { - return unicode.Is(unicode.Mn, r) - })) - }), - precis.Norm(norm.NFC), // This is the default; be explicit though. - ) - p, _ := loosecompare.String(v) - return p +func normalize(str string) string { + return godiacritics.Normalize(str) } func allZero(s []byte) bool { diff --git a/server_test.go b/server_test.go index 273d2ff..3caea74 100644 --- a/server_test.go +++ b/server_test.go @@ -370,3 +370,19 @@ func TestGetValueRequestPartialFailure(t *testing.T) { t.Fatalf("%v != %v", is, want) } } + +func TestStringNormalization(t *testing.T) { + tests := []struct { + is string + want string + }{ + {"daß", "dass"}, + {"Pâté", "Pate"}, + } + + for _, test := range tests { + if is, want := normalize(test.is), test.want; is != want { + t.Fatalf("%v != %v", is, want) + } + } +} From ce6f6140b7d8fc9d21a30e42be51ad00939d13cc Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Mon, 26 Feb 2024 15:41:17 +0100 Subject: [PATCH 50/58] Rename C.value() to C.Value() --- characteristic/bool.go | 2 +- characteristic/bytes.go | 2 +- characteristic/c.go | 26 ++++++++++++++++++-------- characteristic/float.go | 2 +- characteristic/int.go | 2 +- characteristic/string.go | 2 +- rtp/setup_endpoints_test.go | 2 +- rtp/stream_configuration_test.go | 2 +- rtp/stream_controller_test.go | 4 ++-- 9 files changed, 27 insertions(+), 17 deletions(-) diff --git a/characteristic/bool.go b/characteristic/bool.go index 126f4f4..cc59a41 100644 --- a/characteristic/bool.go +++ b/characteristic/bool.go @@ -25,7 +25,7 @@ func (c *Bool) SetValue(v bool) { // Value returns the value of c as bool. func (c *Bool) Value() bool { - return c.C.value().(bool) + return c.C.Value().(bool) } // OnSetRemoteValue set c.SetValueRequestFunc and calls fn. diff --git a/characteristic/bytes.go b/characteristic/bytes.go index e847dda..ae8d1d7 100644 --- a/characteristic/bytes.go +++ b/characteristic/bytes.go @@ -25,7 +25,7 @@ func (c *Bytes) SetValue(v []byte) { // Value returns the value of c as byte array. func (c *Bytes) Value() []byte { - str := c.C.value().(string) + str := c.C.Value().(string) if b, err := base64.StdEncoding.DecodeString(str); err != nil { return []byte{} } else { diff --git a/characteristic/c.go b/characteristic/c.go index 1323102..77c9b8f 100644 --- a/characteristic/c.go +++ b/characteristic/c.go @@ -1,6 +1,8 @@ package characteristic import ( + "sync" + "github.com/brutella/hap/log" "github.com/xiam/to" @@ -113,6 +115,8 @@ type C struct { // when the new value is the same as the old value. // This flag is only used for programmable switch events. updateOnSameValue bool + + m sync.Mutex } // New returns a new characteristic. @@ -152,8 +156,13 @@ func (c *C) setValue(v interface{}, req *http.Request) (interface{}, int) { newVal = c.clampInt(newVal.(int)) } + c.m.Lock() + // reference old value + oldVal := c.Val + c.m.Unlock() + // ignore the same newVal - if c.Val == newVal && !c.updateOnSameValue { + if oldVal == newVal && !c.updateOnSameValue { // no error return nil, 0 } @@ -173,11 +182,10 @@ func (c *C) setValue(v interface{}, req *http.Request) (interface{}, int) { } } - // reference old value - oldVal := c.Val - + c.m.Lock() // update to new value c.Val = newVal + c.m.Unlock() // call update funcs for _, fn := range c.valUpdateFuncs { @@ -201,11 +209,13 @@ func (c *C) ValueRequest(req *http.Request) (interface{}, int) { return c.ValueRequestFunc(req) } - return c.value(), 0 + return c.Value(), 0 } -// value returns the value of C -func (c *C) value() interface{} { +// Value returns the value of C +func (c *C) Value() interface{} { + c.m.Lock() + defer c.m.Unlock() return c.Val } @@ -313,7 +323,7 @@ func (c *C) MarshalJSON() ([]byte, error) { if v, s := c.ValueRequest(nil); s == 0 { d.Value = &V{v} } else { - d.Value = &V{c.Val} // dummy "zero" value + d.Value = &V{c.Value()} // dummy "zero" value } } diff --git a/characteristic/float.go b/characteristic/float.go index 582316a..beaacbf 100644 --- a/characteristic/float.go +++ b/characteristic/float.go @@ -35,7 +35,7 @@ func (c *Float) SetStepValue(v float64) { // Value returns the value of c as float64. func (c *Float) Value() float64 { - return c.C.value().(float64) + return c.C.Value().(float64) } func (c *Float) MinValue() float64 { diff --git a/characteristic/int.go b/characteristic/int.go index 2381a18..5bc7e1d 100644 --- a/characteristic/int.go +++ b/characteristic/int.go @@ -44,7 +44,7 @@ func (c *Int) SetStepValue(v int) { // Value returns the value of c as integer. func (c *Int) Value() int { - return c.C.value().(int) + return c.C.Value().(int) } func (c *Int) MinValue() int { diff --git a/characteristic/string.go b/characteristic/string.go index 04ca235..7f41b52 100644 --- a/characteristic/string.go +++ b/characteristic/string.go @@ -25,7 +25,7 @@ func (c *String) SetValue(v string) { // Value returns the value of c as string. func (c *String) Value() string { - return c.C.value().(string) + return c.C.Value().(string) } // OnSetRemoteValue set c.SetValueRequestFunc and calls fn. diff --git a/rtp/setup_endpoints_test.go b/rtp/setup_endpoints_test.go index be23613..7b30a88 100644 --- a/rtp/setup_endpoints_test.go +++ b/rtp/setup_endpoints_test.go @@ -10,7 +10,7 @@ func TestSetupEndpoints(t *testing.T) { c := characteristic.NewSetupEndpoints() c.Val = "ARBz21VuCupGZre3A62biD8XAxkBAQACDDE5Mi4xNjguMC4xMwMC+OUEAurRBCUCEPpLBUWQEzkfFiGd1qkieqoDDi8cIMO0Vl1+kegzGgnpAQEABSUCEJQ27Ze9EEmuxcIVPhDEs68DDlaHwww6f6d5+NSClT7TAQEA" - b := c.Value() + b := C.Value() var setup SetupEndpoints err := tlv8.Unmarshal(b, &setup) diff --git a/rtp/stream_configuration_test.go b/rtp/stream_configuration_test.go index 033204d..7aaf85a 100644 --- a/rtp/stream_configuration_test.go +++ b/rtp/stream_configuration_test.go @@ -11,7 +11,7 @@ func TestSelectedStreamConfiguration(t *testing.T) { c := characteristic.NewSelectedStreamConfiguration() c.Val = "ARUCAQABEHW8tiJ9E0F4tLlvOURdFCc=" - b := c.Value() + b := C.Value() var cfg StreamConfiguration err := tlv8.Unmarshal(b, &cfg) diff --git a/rtp/stream_controller_test.go b/rtp/stream_controller_test.go index 146af36..c4d8b6e 100644 --- a/rtp/stream_controller_test.go +++ b/rtp/stream_controller_test.go @@ -10,7 +10,7 @@ func TestStreamController(t *testing.T) { c := characteristic.NewSupportedVideoStreamConfiguration() c.Val = "AX8BAQACDAEBAQEBAgIBAAMBAAECgAcCAjgEAwEeAQIABQIC0AIDAR4BAoACAgJoAQMBHgEC4AECAg4BAwEeAQJAAQICtAADAR4BAgAFAgLAAwMBHgECAAQCAgADAwEeAQKAAgIC4AEDAR4BAuABAgJoAQMBHgECQAECAvAAAwEP" - b := c.Value() + b := C.Value() if len(b) == 0 { t.Fatal("Zero length bytes") } @@ -40,7 +40,7 @@ func TestStreamingStatus(t *testing.T) { c := characteristic.NewStreamingStatus() c.Val = "AQEA" - b := c.Value() + b := C.Value() if len(b) == 0 { t.Fatalf("Zero length bytes") } From 73c0a5cca7b3fe218399dbbcd0da8a2630cb5b14 Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Tue, 5 Mar 2024 09:38:56 +0100 Subject: [PATCH 51/58] Guard call on OnCValueUpdate by locking mutex --- characteristic/c.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/characteristic/c.go b/characteristic/c.go index 77c9b8f..b1766c7 100644 --- a/characteristic/c.go +++ b/characteristic/c.go @@ -130,7 +130,9 @@ func New() *C { // OnCValueUpdate register the given function which is called // when the value of the characteristic is updated. func (c *C) OnCValueUpdate(fn ValueUpdateFunc) { + c.m.Lock() c.valUpdateFuncs = append(c.valUpdateFuncs, fn) + c.m.Unlock() } // Sets the value of c to val and returns a status code. @@ -185,10 +187,11 @@ func (c *C) setValue(v interface{}, req *http.Request) (interface{}, int) { c.m.Lock() // update to new value c.Val = newVal + funcs := c.valUpdateFuncs c.m.Unlock() // call update funcs - for _, fn := range c.valUpdateFuncs { + for _, fn := range funcs { fn(c, newVal, oldVal, req) } From 53e3bdeff47261c23a84fe4369237bf479f05c4c Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Tue, 5 Mar 2024 09:39:05 +0100 Subject: [PATCH 52/58] Fix unit tests --- rtp/setup_endpoints_test.go | 5 +++-- rtp/stream_configuration_test.go | 5 +++-- rtp/stream_controller_test.go | 7 ++++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/rtp/setup_endpoints_test.go b/rtp/setup_endpoints_test.go index 7b30a88..9e5dfbc 100644 --- a/rtp/setup_endpoints_test.go +++ b/rtp/setup_endpoints_test.go @@ -1,16 +1,17 @@ package rtp import ( + "testing" + "github.com/brutella/hap/characteristic" "github.com/brutella/hap/tlv8" - "testing" ) func TestSetupEndpoints(t *testing.T) { c := characteristic.NewSetupEndpoints() c.Val = "ARBz21VuCupGZre3A62biD8XAxkBAQACDDE5Mi4xNjguMC4xMwMC+OUEAurRBCUCEPpLBUWQEzkfFiGd1qkieqoDDi8cIMO0Vl1+kegzGgnpAQEABSUCEJQ27Ze9EEmuxcIVPhDEs68DDlaHwww6f6d5+NSClT7TAQEA" - b := C.Value() + b := c.Value() var setup SetupEndpoints err := tlv8.Unmarshal(b, &setup) diff --git a/rtp/stream_configuration_test.go b/rtp/stream_configuration_test.go index 7aaf85a..a4af557 100644 --- a/rtp/stream_configuration_test.go +++ b/rtp/stream_configuration_test.go @@ -2,16 +2,17 @@ package rtp import ( "fmt" + "testing" + "github.com/brutella/hap/characteristic" "github.com/brutella/hap/tlv8" - "testing" ) func TestSelectedStreamConfiguration(t *testing.T) { c := characteristic.NewSelectedStreamConfiguration() c.Val = "ARUCAQABEHW8tiJ9E0F4tLlvOURdFCc=" - b := C.Value() + b := c.Value() var cfg StreamConfiguration err := tlv8.Unmarshal(b, &cfg) diff --git a/rtp/stream_controller_test.go b/rtp/stream_controller_test.go index c4d8b6e..d4c2c6c 100644 --- a/rtp/stream_controller_test.go +++ b/rtp/stream_controller_test.go @@ -1,16 +1,17 @@ package rtp import ( + "testing" + "github.com/brutella/hap/characteristic" "github.com/brutella/hap/tlv8" - "testing" ) func TestStreamController(t *testing.T) { c := characteristic.NewSupportedVideoStreamConfiguration() c.Val = "AX8BAQACDAEBAQEBAgIBAAMBAAECgAcCAjgEAwEeAQIABQIC0AIDAR4BAoACAgJoAQMBHgEC4AECAg4BAwEeAQJAAQICtAADAR4BAgAFAgLAAwMBHgECAAQCAgADAwEeAQKAAgIC4AEDAR4BAuABAgJoAQMBHgECQAECAvAAAwEP" - b := C.Value() + b := c.Value() if len(b) == 0 { t.Fatal("Zero length bytes") } @@ -40,7 +41,7 @@ func TestStreamingStatus(t *testing.T) { c := characteristic.NewStreamingStatus() c.Val = "AQEA" - b := C.Value() + b := c.Value() if len(b) == 0 { t.Fatalf("Zero length bytes") } From 026c7e9ba030d61e74e6b3e11f3dfa6d692b78c3 Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Fri, 10 May 2024 12:09:11 +0200 Subject: [PATCH 53/58] Protect access to encryptCount and decryptCount field --- session.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/session.go b/session.go index 4f4f2ba..e0b1892 100644 --- a/session.go +++ b/session.go @@ -52,6 +52,7 @@ type session struct { decryptKey [32]byte encryptCount uint64 decryptCount uint64 + mu sync.Mutex twr *TimedWrite } @@ -89,8 +90,10 @@ func (s *session) Encrypt(r io.Reader) (io.Reader, error) { var buf bytes.Buffer for _, p := range packets { var nonce [8]byte + s.mu.Lock() binary.LittleEndian.PutUint64(nonce[:], s.encryptCount) s.encryptCount++ + s.mu.Unlock() bLength := make([]byte, 2) binary.LittleEndian.PutUint16(bLength, uint16(p.length)) @@ -131,8 +134,10 @@ func (s *session) Decrypt(r io.Reader) (io.Reader, error) { } var nonce [8]byte + s.mu.Lock() binary.LittleEndian.PutUint64(nonce[:], s.decryptCount) s.decryptCount++ + s.mu.Unlock() lengthBytes := make([]byte, 2) binary.LittleEndian.PutUint16(lengthBytes, uint16(length)) From 87fbe100bc50984c20be8142897b226194937dd1 Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Fri, 10 May 2024 13:02:11 +0200 Subject: [PATCH 54/58] Make sure that access to C.Event (now C.events) protected with a mutex --- characteristic/c.go | 25 +++++++++++++++++++++---- characteristics.go | 7 ++----- notification.go | 2 +- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/characteristic/c.go b/characteristic/c.go index b1766c7..7235e47 100644 --- a/characteristic/c.go +++ b/characteristic/c.go @@ -86,9 +86,6 @@ type C struct { // ValidRange is a 2 element array the valid range start and end. ValidRange []int - // Stores which connected client has events enabled for this characteristic. - Events map[string]bool - // ValueRequestFunc is called when the value of C is requested by a // paired controller via an HTTP request. // If the value of C represents the state of a remote object, you can use @@ -116,13 +113,16 @@ type C struct { // This flag is only used for programmable switch events. updateOnSameValue bool + // Stores which connected client has events enabled for this characteristic. + events map[string]bool + m sync.Mutex } // New returns a new characteristic. func New() *C { return &C{ - Events: make(map[string]bool), + events: make(map[string]bool), valUpdateFuncs: make([]ValueUpdateFunc, 0), } } @@ -222,6 +222,23 @@ func (c *C) Value() interface{} { return c.Val } +func (c *C) SetEvent(remoteAddr string, enable bool) { + c.m.Lock() + defer c.m.Unlock() + c.events[remoteAddr] = enable +} + +func (c *C) HasEventsEnabled(remoteAddr string) bool { + c.m.Lock() + defer c.m.Unlock() + + ev, ok := c.events[remoteAddr] + if ok { + return ev + } + return false +} + // IsWritable returns true if clients are allowed // to update the value of the characteristic. func (c *C) IsWritable() bool { diff --git a/characteristics.go b/characteristics.go index 4118a5c..b59124e 100644 --- a/characteristics.go +++ b/characteristics.go @@ -121,10 +121,7 @@ func (srv *Server) getCharacteristics(res http.ResponseWriter, req *http.Request // Should the response include the events flag? if ev { - var ev bool - if v, ok := c.Events[req.RemoteAddr]; ok { - ev = v - } + ev := c.HasEventsEnabled(req.RemoteAddr) cdata.Events = &ev } @@ -236,7 +233,7 @@ func (srv *Server) putCharacteristics(res http.ResponseWriter, req *http.Request cdata.Status = &status arr = append(arr, cdata) } else { - c.Events[req.RemoteAddr] = *d.Events + c.SetEvent(req.RemoteAddr, *d.Events) } } diff --git a/notification.go b/notification.go index 1e2a525..349ee54 100644 --- a/notification.go +++ b/notification.go @@ -62,7 +62,7 @@ func sendNotification(a *accessory.A, c *characteristic.C, req *http.Request) er } // Check which connection has events enabled. - if ev, ok := c.Events[conn.RemoteAddr().String()]; ok && ev { + if c.HasEventsEnabled(conn.RemoteAddr().String()) { log.Debug.Printf("send event to %s:\n%s\n", conn.RemoteAddr(), string(b)) conn.Write(b) } From 69b7fac4840069e2fd3b2cf992ecad3b98040d7e Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Wed, 24 Jul 2024 14:00:04 +0200 Subject: [PATCH 55/58] Update dnssd to v1.2.11 #57 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index aac8198..0e214bb 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/brutella/hap go 1.16 require ( - github.com/brutella/dnssd v1.2.10 + github.com/brutella/dnssd v1.2.11 github.com/go-chi/chi v1.5.4 github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9 github.com/xiam/to v0.0.0-20200126224905-d60d31e03561 diff --git a/go.sum b/go.sum index 2559264..98de0b0 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/brutella/dnssd v1.2.10 h1:Gg0k7+NtJp7TbOMS0eUVg0VEjSdftzKOTQ8QQTzQ0x4= -github.com/brutella/dnssd v1.2.10/go.mod h1:yZ+GHHbGhtp5yJeKTnppdFGiy6OhiPoxs0WHW1KUcFA= +github.com/brutella/dnssd v1.2.11 h1:kOAT2DY2KKEONJlB4wkmB+16FEpDWGV+HEIocuMnDD4= +github.com/brutella/dnssd v1.2.11/go.mod h1:yZ+GHHbGhtp5yJeKTnppdFGiy6OhiPoxs0WHW1KUcFA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= From b0d60f58892627e3831294939309f351f49880f3 Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Tue, 22 Oct 2024 13:21:57 +0200 Subject: [PATCH 56/58] Update to dnssd v1.2.14 --- go.mod | 4 ++-- go.sum | 74 +++++++++++++++++++++++++++++++++++++++------------------- 2 files changed, 52 insertions(+), 26 deletions(-) diff --git a/go.mod b/go.mod index 0e214bb..46c20b8 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,10 @@ module github.com/brutella/hap go 1.16 require ( - github.com/brutella/dnssd v1.2.11 + github.com/brutella/dnssd v1.2.14 github.com/go-chi/chi v1.5.4 github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9 github.com/xiam/to v0.0.0-20200126224905-d60d31e03561 - golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 + golang.org/x/crypto v0.24.0 gopkg.in/Regis24GmbH/go-diacritics.v2 v2.0.3 ) diff --git a/go.sum b/go.sum index 98de0b0..b0422a1 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,13 @@ -github.com/brutella/dnssd v1.2.11 h1:kOAT2DY2KKEONJlB4wkmB+16FEpDWGV+HEIocuMnDD4= -github.com/brutella/dnssd v1.2.11/go.mod h1:yZ+GHHbGhtp5yJeKTnppdFGiy6OhiPoxs0WHW1KUcFA= +github.com/brutella/dnssd v1.2.14 h1:qLpTnRTm5peo2jA30hqMIbCuWn8x3sFg3e9o9ODOobw= +github.com/brutella/dnssd v1.2.14/go.mod h1:tG4GE8orv6+irE5rdsNgb6MJSxm6cyMUKdC5jmD22gk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs= github.com/go-chi/chi v1.5.4/go.mod h1:uaf8YgoFazUOkPBG7fxPftUylNumIev9awIWOENIuEg= -github.com/miekg/dns v1.1.54 h1:5jon9mWcb0sFJGpnI99tOMhCPyJ+RPVz5b63MQG0VWI= -github.com/miekg/dns v1.1.54/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs= +github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -19,61 +20,86 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9 h1:aeN+ghOV0b2VCmKKO3gqnDQ8mLbpABZgRR2FVYx4ouI= github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9/go.mod h1:roo6cZ/uqpwKMuvPG0YmzI5+AmUiMWfjCBZpGXqbTxE= +github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs= +github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= +github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns= +github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/xiam/to v0.0.0-20200126224905-d60d31e03561 h1:SVoNK97S6JlaYlHcaC+79tg3JUlQABcc0dH2VQ4Y+9s= github.com/xiam/to v0.0.0-20200126224905-d60d31e03561/go.mod h1:cqbG7phSzrbdg3aj+Kn63bpVruzwDZi58CpxlZkjwzw= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 h1:71vQrMauZZhcTVK6KdYM+rklehEEwb3E+ZhaE5jrPrE= -golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= -golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= -golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= -golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/Regis24GmbH/go-diacritics.v2 v2.0.3 h1:rz88vn1OH2B9kKorR+QCrcuw6WbizVwahU2Y9Q09xqU= gopkg.in/Regis24GmbH/go-diacritics.v2 v2.0.3/go.mod h1:vJmfdx2L0+30M90zUd0GCjLV14Ip3ZgWR5+MV1qljOo= From d78dcdd4919f65b025ed435cd05b3a6dad806042 Mon Sep 17 00:00:00 2001 From: Matthias Hochgatterer Date: Tue, 22 Oct 2024 13:23:18 +0200 Subject: [PATCH 57/58] Do not override pre-defined service and characteristic ids You can now specify an id for a service and characteristic. Since this ids have to be unique within an accessory, a check was added to make sure that the ids are actually unique. --- server.go | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/server.go b/server.go index 440c048..bcca897 100644 --- a/server.go +++ b/server.go @@ -278,18 +278,34 @@ func (s *Server) add(as []*accessory.A) error { aid++ } + iids := map[uint64]interface{}{} var iid uint64 = 1 for _, s := range a.Ss { - s.Id = iid - iid++ + if s.Id == 0 { + s.Id = iid + iid++ + } + + if _, alreadyExists := iids[s.Id]; alreadyExists { + return fmt.Errorf("service id %d already exists (%s)", s.Id, a.Name()) + } + iids[s.Id] = struct{}{} for _, c := range s.Cs { // Create a local variable before // capturing them in a function. a := a - c.Id = iid - iid++ + if c.Id == 0 { + c.Id = iid + iid++ + } + + if _, alreadyExists := iids[c.Id]; alreadyExists { + return fmt.Errorf("characteristic id %d already exists (%s)", c.Id, a.Name()) + } + + iids[c.Id] = struct{}{} // If the value of a characteristic changes, we notify all connected clients. // The identify characteristic is a special case where we all accessory.IdentifyFunc. From ae235b37582de6be7db904c124ec51e80bf7e38e Mon Sep 17 00:00:00 2001 From: Hugh O'Brien Date: Fri, 19 Jun 2026 13:25:37 -0700 Subject: [PATCH 58/58] Fix pairings handler rejecting ListPairings with "tlv8: EOF" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The /pairings handler decoded the request into a struct that marked Identifier (tag 1) as required. A ListPairings request carries only Method (tag 0) and State (tag 6) — no Identifier — so the tag-keyed reader returned io.EOF for the missing required field and the handler rejected the request (HTTP 400, "tlv8: EOF"). iOS uses ListPairings to reconcile a home's controllers (resident HomePods/Apple TVs, additional devices). Because the request always failed, iOS could never converge: it retried endlessly, re-attempting RemovePairing for controllers it wanted to drop and leaving others looping on pair-verify ("not paired with yet"). Mark Identifier optional, completing the partial fix in 340cbe4 (#21) which made PublicKey and Permission optional but left Identifier required. Resolves the unmarshal failure reported in #21 and #44. Added a handler-level regression test using request bodies captured from a real iOS controller. Co-Authored-By: Claude Opus 4.8 (1M context) --- pairings.go | 2 +- pairings_test.go | 59 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 pairings_test.go diff --git a/pairings.go b/pairings.go index f3fd1e6..faae9c7 100644 --- a/pairings.go +++ b/pairings.go @@ -31,7 +31,7 @@ func (srv *Server) pairings(res http.ResponseWriter, req *http.Request) { d := struct { Method byte `tlv8:"0"` - Identifier string `tlv8:"1"` + Identifier string `tlv8:"1,optional"` PublicKey []byte `tlv8:"3,optional"` Permission byte `tlv8:"11,optional"` State byte `tlv8:"6"` diff --git a/pairings_test.go b/pairings_test.go new file mode 100644 index 0000000..0ed98b6 --- /dev/null +++ b/pairings_test.go @@ -0,0 +1,59 @@ +package hap + +import ( + "github.com/brutella/hap/accessory" + + "bytes" + "encoding/hex" + "net/http" + "net/http/httptest" + "testing" +) + +// TestPairingsHandlerRequests is a regression test for the recurring +// "pairings.go: tlv8: EOF" reports (#21, #44). +// +// A ListPairings request carries only Method (tag 0) and State (tag 6) — it has +// no Identifier (tag 1). The handler decoded into a struct that marked +// Identifier as required, so tlv8.UnmarshalReader returned io.EOF and the +// handler rejected the request with HTTP 400. iOS uses ListPairings to +// reconcile a home's controllers (e.g. resident HomePods/Apple TVs), so the +// persistent error left it retrying forever and unable to converge. +// +// The bodies below are captured verbatim from a real iOS controller. +func TestPairingsHandlerRequests(t *testing.T) { + a := accessory.New(accessory.Info{Name: "Test"}, accessory.TypeOutlet) + srv, err := NewServer(NewMemStore(), a) + if err != nil { + t.Fatal(err) + } + + const addr = "192.0.2.10:50000" + srv.mux.Lock() + srv.sess[addr] = &session{Pairing: Pairing{Name: "admin", Permission: PermissionAdmin}} + srv.mux.Unlock() + + cases := []struct { + name string + body string // hex, captured off the wire + }{ + {"ListPairings", "000105060101"}, + {"AddPairing", "000103060101012437463141374344452d454645442d343943342d394431462d39373245364645384544413203208d8b2b72ff96811a7e4ab6ef5e3719bcacc5582b293d5d2ea3fc74dbf13a68620b0101"}, + } + + for _, tc := range cases { + raw, err := hex.DecodeString(tc.body) + if err != nil { + t.Fatal(err) + } + req := httptest.NewRequest(http.MethodPost, "/pairings", bytes.NewReader(raw)) + req.RemoteAddr = addr + rec := httptest.NewRecorder() + + srv.pairings(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("%s: handler returned HTTP %d, want 200 (request rejected — tlv8 unmarshal EOF on a valid request)", tc.name, rec.Code) + } + } +}