Skip to content

Commit

Permalink
Allow the update of a characteristic value to fail brutella/hc#163
Browse files Browse the repository at this point in the history
  • Loading branch information
brutella committed Feb 28, 2022
1 parent b5014fb commit 93fd032
Show file tree
Hide file tree
Showing 12 changed files with 251 additions and 46 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@ If you want to support `hap`, please purchase Home from the [App Store][home-app
This library is a rewrite of [hc](https://github.com/brutella/hc).
If you want to migrate from `hc`, consider the following changes.

- Instead of `hc.NewIPTransport(...)` you now call `hap.NewServer(...)` to create a server.
- You can provide your own persistent store by implementing the [Store](store.go) interface.
- You can define your own http handlers. [hc#212](https://github.com/brutella/hc/issues/212)
- Instead of `hc.NewIPTransport(...)` you now call [hap.NewServer(...)](https://pkg.go.dev/github.com/brutella/hap#NewServer) to create a server.
- You can create your own persistent storage by implementing the [Store](store.go) interface.
- Setting the value of a characteristic can now fail. Fixes [hc#163](https://github.com/brutella/hc/issues/163)
- You can define custom http handlers. Fixes [hc#212](https://github.com/brutella/hc/issues/212)
```go
server.ServeMux().HandleFunc("/ping", func(res http.ResponseWriter, req *http.Request) {
res.Write([]byte("pong"))
Expand Down
7 changes: 6 additions & 1 deletion characteristic/bool.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ func (c *Bool) SetValue(v bool) {

// Value returns the value of c as bool.
func (c *Bool) Value() bool {
return c.C.value(nil).(bool)
v, _ := c.C.valueRequest(nil)
if v == nil {
return false
}

return v.(bool)
}

// OnValueRemoteUpdate calls fn when the value of the characteristic was updated.
Expand Down
7 changes: 6 additions & 1 deletion characteristic/bytes.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ func (c *Bytes) SetValue(v []byte) {

// Value returns the value of c as byte array.
func (c *Bytes) Value() []byte {
str := c.C.value(nil).(string)
v, _ := c.C.valueRequest(nil)
if v == nil {
return []byte{}
}

str := v.(string)
if b, err := base64.StdEncoding.DecodeString(str); err != nil {
return []byte{}
} else {
Expand Down
79 changes: 50 additions & 29 deletions characteristic/c.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,20 @@ type C struct {
// Stores which connected client has events enabled for this characteristic.
Events map[string]bool

// ValFunc returns the value of C.
// If no nil, the return value of this function is used instead of Val.
// req is non nil, if the value is requested from a request.
ValFunc func(req *http.Request) interface{}
// ValueRequestFunc is called when the value of C is requested.
// The http request is non-nil, if the value of C is requested.
// by an HTTP request coming from a paired controller.
// The first return value of this function is the value of C.
// If the second argument is non-zero, the server responds with the
// HTTP status code 500 Internal Server Error.
ValueRequestFunc func(request *http.Request) (interface{}, int)

// SetValueRequestFunc is called when the value of C is updated.
// The first argument "value" is the new value of C.
// The second argument "request" is non-nil, if the value of C is
// updated from an HTTP request coming from a paired controller.
// An error is inidcated if the return value is non-zero.
SetValueRequestFunc func(value interface{}, request *http.Request) int

// A list of update value functions.
// There are called when the value of the characteristic is updated.
Expand Down Expand Up @@ -110,14 +120,23 @@ func (c *C) OnCValueUpdate(fn ValueUpdateFunc) {

// Sets the value of c to v.
// The function is called if the value is updated from an http request.
func (c *C) SetValueRequest(v interface{}, req *http.Request) {
func (c *C) SetValueRequest(v interface{}, req *http.Request) int {
// check write permission
if !c.IsWritable() {
log.Info.Printf("writing %v by %s not allowed\n", v, req.RemoteAddr)
return
return -70404
}

if c.SetValueRequestFunc != nil {
if s := c.SetValueRequestFunc(v, req); s != 0 {
return s
}
}

c.setValue(v, req)

// no error
return 0
}

func (c *C) setValue(v interface{}, req *http.Request) {
Expand Down Expand Up @@ -148,25 +167,25 @@ func (c *C) setValue(v interface{}, req *http.Request) {
}
}

// ValueRequest returns the value of the characteristic for
// a http request.
func (c *C) ValueRequest(req *http.Request) interface{} {
// ValueRequest returns the value of C and a status code.
// if the characteristic is not readable, the status code -70405 is returned.
func (c *C) ValueRequest(req *http.Request) (interface{}, int) {
// check write permission
if !c.IsReadable() {
log.Info.Printf("reading %d by %s not allowed\n", c.Id, req.RemoteAddr)
return nil
return nil, -70405
}

return c.value(req)
return c.valueRequest(req)
}

// value returns the value of the characteristic.
func (c *C) value(req *http.Request) interface{} {
if c.ValFunc != nil {
return c.ValFunc(req)
// value returns the value of C and a status code.
func (c *C) valueRequest(req *http.Request) (interface{}, int) {
if c.ValueRequestFunc != nil {
return c.ValueRequestFunc(req)
}

return c.Val
return c.Val, 0
}

func (c *C) IsWritable() bool {
Expand Down Expand Up @@ -203,7 +222,7 @@ func (c *C) IsWriteOnly() bool {
return len(c.Permissions) == 1 && c.Permissions[0] == PermissionWrite
}

func (ch *C) MarshalJSON() ([]byte, error) {
func (c *C) MarshalJSON() ([]byte, error) {
d := struct {
Id uint64 `json:"iid"` // managed by accessory
Type string `json:"type"`
Expand All @@ -219,20 +238,22 @@ func (ch *C) MarshalJSON() ([]byte, error) {
MinValue interface{} `json:"minValue,omitempty"`
StepValue interface{} `json:"minStep,omitempty"`
}{
Id: ch.Id,
Type: ch.Type,
Permissions: ch.Permissions,
Description: ch.Description,
Format: ch.Format,
Unit: ch.Unit,
MaxLen: ch.MaxLen,
MaxValue: ch.MaxVal,
MinValue: ch.MinVal,
StepValue: ch.StepVal,
Id: c.Id,
Type: c.Type,
Permissions: c.Permissions,
Description: c.Description,
Format: c.Format,
Unit: c.Unit,
MaxLen: c.MaxLen,
MaxValue: c.MaxVal,
MinValue: c.MinVal,
StepValue: c.StepVal,
}

if ch.IsReadable() {
d.Value = ch.value(nil)
if c.IsReadable() {
if v, _ := c.valueRequest(nil); v != nil {
d.Value = v
}
}

return json.Marshal(&d)
Expand Down
26 changes: 24 additions & 2 deletions characteristic/c_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ func TestCharacteristicValue(t *testing.T) {
c.Val = 0

n := 0
c.ValFunc = func(*http.Request) interface{} {
c.ValueRequestFunc = func(*http.Request) (interface{}, int) {
n++
return n
return n, 0
}

if is, want := c.Value(), 1; is != want {
Expand Down Expand Up @@ -139,3 +139,25 @@ func TestReadOnly(t *testing.T) {
t.Fatalf("is=%v want=%v", is, want)
}
}

func TestSetValueRequestFunc(t *testing.T) {
c := NewBrightness()

c.SetValue(100)
c.SetValueRequestFunc = func(v interface{}, r *http.Request) int {
if r != nil {
return -70408
}

return 0
}

s := c.SetValueRequest(50, &http.Request{})
if is, want := s, -70408; is != want {
t.Fatalf("%v != %v", is, want)
}

if is, want := c.Value(), 100; is != want {
t.Fatalf("is=%v want=%v", is, want)
}
}
7 changes: 6 additions & 1 deletion characteristic/float.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ func (c *Float) SetStepValue(v float64) {

// Value returns the value of c as float64.
func (c *Float) Value() float64 {
return c.C.value(nil).(float64)
v, _ := c.C.valueRequest(nil)
if v == nil {
return 0
}

return v.(float64)
}

func (c *Float) MinValue() float64 {
Expand Down
7 changes: 6 additions & 1 deletion characteristic/int.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ func (c *Int) SetStepValue(v int) {

// Value returns the value of c as integer.
func (c *Int) Value() int {
return c.C.value(nil).(int)
v, _ := c.C.valueRequest(nil)
if v == nil {
return 0
}

return v.(int)
}

func (c *Int) MinValue() int {
Expand Down
7 changes: 6 additions & 1 deletion characteristic/string.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ func (c *String) SetValue(v string) {

// Value returns the value of c as string.
func (c *String) Value() string {
return c.C.value(nil).(string)
v, _ := c.C.valueRequest(nil)
if v == nil {
return ""
}

return v.(string)
}

// OnValueRemoteUpdate calls fn when the value of the characteristic was updated.
Expand Down
28 changes: 22 additions & 6 deletions characteristics.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,13 @@ func (srv *Server) getCharacteristics(res http.ResponseWriter, req *http.Request
continue
}

cdata.Value = c.ValueRequest(req)
v, s := c.ValueRequest(req)
if s != 0 {
err = true
cdata.Status = &s
} else {
cdata.Value = v
}

if meta {
cdata.Format = &c.Format
Expand Down Expand Up @@ -156,13 +162,19 @@ func (srv *Server) putCharacteristics(res http.ResponseWriter, req *http.Request
continue
}

if d.Response != nil {
cdata.Value = c.ValueRequest(req)
arr = append(arr, cdata)
if d.Value != nil {
s := c.SetValueRequest(d.Value, req)
if s != 0 {
cdata.Status = &s
}
}

if d.Value != nil {
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 d.Events != nil {
Expand All @@ -174,6 +186,10 @@ func (srv *Server) putCharacteristics(res http.ResponseWriter, req *http.Request
c.Events[req.RemoteAddr] = *d.Events
}
}

if cdata.Status != nil || cdata.Value != nil {
arr = append(arr, cdata)
}
}

if len(arr) == 0 {
Expand Down
1 change: 0 additions & 1 deletion identify.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (

func (srv *Server) identify(res http.ResponseWriter, req *http.Request) {
if srv.isPaired() {
res.WriteHeader(http.StatusBadRequest)
jsonError(res, JsonStatusInsufficientPrivileges)
return
}
Expand Down
1 change: 1 addition & 0 deletions json.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func jsonError(res http.ResponseWriter, status int) error {
return err
}

res.WriteHeader(http.StatusBadRequest)
_, err = res.Write(b)
return err
}
Expand Down
Loading

0 comments on commit 93fd032

Please sign in to comment.