feat: add attribute protocol errors#421
Conversation
This will allow to return protocol level errors
This will be used to retrieve the error details from an error within a AsyncOperationCompletedHandler. Related work: tinygo-org/bluetooth#421
|
Note that:
|
errors.go
Outdated
| } | ||
|
|
||
| func (e *AttributeProtocolError) Error() string { | ||
| return fmt.Sprintf("ATT error 0x%02X (%s): %s", e.Code, e.Name, e.Description) |
There was a problem hiding this comment.
fmt on embedded targets can increase the binary size significantly. strings.Builder and hex.EncodeToString would be much more efficient.
| return fmt.Sprintf("ATT error 0x%02X (%s): %s", e.Code, e.Name, e.Description) | |
| b := strings.Builder{} | |
| b.WriteString("ATT error 0x") | |
| b.WriteString(hex.EncodeToString([]byte{e.Code})) | |
| b.WriteString(" (") | |
| b.WriteString(e.Name) | |
| b.WriteString("): ") | |
| b.WriteString(e.Description) | |
| return b.String() |
it does not look pretty, but the code size and allocation count difference is significant:
strings.Builder
tinygo build -print-allocs=. -size short -target nicenano
./test/main.go:24.1,24.23 1 0
./tinygo/src/runtime/string.go:76.1,76.45 1 0
./tinygo/src/runtime/baremetal.go:44.1,44.24 1 0
./tinygo/src/runtime/slice.go:39.1,39.38 1 0
./tinygo/src/internal/task/task_stack.go:43.1,43.39 1 0
./tinygo/src/internal/task/task_stack.go:75.1,75.13 1 0
code data bss | flash ram
7296 116 4736 | 7412 4852
fmt.Sprintf
tinygo build -print-allocs=. -size short -target nicenano
test/main.go:36.1,36.54 1 0
test/main.go:34.1,34.19 1 0
test/main.go:24.1,24.79 1 0
test/main.go:25.1,25.23 1 0
test/main.go:24.1,24.79 1 0
test/main.go:24.1,24.79 1 0
/usr/local/go/src/fmt/print.go:509.1,509.63 1 0
/usr/local/go/src/fmt/print.go:881.1,881.34 1 0
/usr/local/go/src/fmt/format.go:539.1,539.21 1 0
/usr/local/go/src/fmt/format.go:211.1,211.28 1 0
/usr/local/go/src/fmt/format.go:150.1,150.28 1 0
/usr/local/go/src/fmt/format.go:75.1,75.34 1 0
/usr/local/go/src/fmt/print.go:147.1,147.36 1 0
/usr/local/go/src/strconv/ftoa.go:287.1,287.22 1 0
/usr/local/go/src/strconv/ftoa.go:268.1,268.22 1 0
/usr/local/go/src/strconv/ftoa.go:166.1,166.18 1 0
/usr/local/go/src/strconv/ftoa.go:147.1,147.18 1 0
/usr/local/go/src/strconv/ftoa.go:119.1,119.18 1 0
/usr/local/go/src/strconv/quote.go:35.1,35.53 1 0
/usr/local/go/src/internal/fmtsort/sort.go:134.1,134.49 1 0
/usr/local/go/src/internal/fmtsort/sort.go:57.1,57.32 1 0
tinygo/src/runtime/string.go:76.1,76.45 1 0
tinygo/src/runtime/string.go:63.1,63.47 1 0
tinygo/src/runtime/hashmap.go:49.1,49.44 1 0
tinygo/src/runtime/baremetal.go:44.1,44.24 1 0
tinygo/src/runtime/slice.go:39.1,39.38 1 0
tinygo/src/internal/reflectlite/strconv.go:239.1,239.35 1 0
tinygo/src/internal/reflectlite/type.go:418.1,418.22 1 0
tinygo/src/internal/reflectlite/value.go:1751.1,1751.35 1 0
tinygo/src/internal/reflectlite/value.go:882.1,882.53 1 0
tinygo/src/internal/reflectlite/value.go:875.1,875.52 1 0
tinygo/src/internal/reflectlite/value.go:273.1,273.61 1 0
tinygo/src/internal/reflectlite/value.go:247.1,247.53 1 0
tinygo/src/internal/reflectlite/value.go:1041.1,1041.53 1 0
tinygo/src/internal/reflectlite/value.go:806.1,806.51 1 0
tinygo/src/internal/reflectlite/value.go:441.1,441.52 1 0
tinygo/src/internal/reflectlite/value.go:650.1,650.54 1 0
tinygo/src/internal/reflectlite/value.go:658.1,658.54 1 0
tinygo/src/internal/reflectlite/value.go:667.1,667.52 1 0
tinygo/src/internal/reflectlite/value.go:598.1,598.53 1 0
tinygo/src/internal/reflectlite/value.go:541.1,541.52 1 0
tinygo/src/internal/reflectlite/value.go:488.1,488.51 1 0
tinygo/src/internal/reflectlite/value.go:630.1,630.55 1 0
tinygo/src/internal/reflectlite/value.go:1163.1,1163.19 1 0
tinygo/src/internal/reflectlite/value.go:1218.1,1218.56 1 0
tinygo/src/internal/task/task_stack.go:43.1,43.39 1 0
tinygo/src/internal/task/task_stack.go:75.1,75.13 1 0
code data bss | flash ram
55288 1508 4744 | 56796 6252
There was a problem hiding this comment.
I did not know that, should we add golangci lint with a rule for fmt ?
errors.go
Outdated
| // Name is a short identifier for the error. | ||
| Name string | ||
| // Description is the human-readable description from the spec. | ||
| Description string | ||
| } | ||
|
|
||
| func (e *AttributeProtocolError) Error() string { | ||
| return fmt.Sprintf("ATT error 0x%02X (%s): %s", e.Code, e.Name, e.Description) | ||
| } | ||
|
|
||
| // ATT error codes from the Bluetooth Core Specification, Table 3.4. | ||
| var ( | ||
| ErrAttInvalidHandle = &AttributeProtocolError{0x01, "Invalid Handle", "The attribute handle given was not valid on this server."} | ||
| ErrAttReadNotPermitted = &AttributeProtocolError{0x02, "Read Not Permitted", "The attribute cannot be read."} | ||
| ErrAttWriteNotPermitted = &AttributeProtocolError{0x03, "Write Not Permitted", "The attribute cannot be written."} | ||
| ErrAttInvalidPDU = &AttributeProtocolError{0x04, "Invalid PDU", "The attribute PDU was invalid."} | ||
| ErrAttInsufficientAuthentication = &AttributeProtocolError{0x05, "Insufficient Authentication", "The attribute requires authentication before it can be read or written."} | ||
| ErrAttRequestNotSupported = &AttributeProtocolError{0x06, "Request Not Supported", "ATT Server does not support the request received from the client."} | ||
| ErrAttInvalidOffset = &AttributeProtocolError{0x07, "Invalid Offset", "Offset specified was past the end of the attribute."} | ||
| ErrAttInsufficientAuthorization = &AttributeProtocolError{0x08, "Insufficient Authorization", "The attribute requires authorization before it can be read or written."} | ||
| ErrAttPrepareQueueFull = &AttributeProtocolError{0x09, "Prepare Queue Full", "Too many prepare writes have been queued."} | ||
| ErrAttNotFound = &AttributeProtocolError{0x0A, "Attribute Not Found", "No attribute found within the given attribute handle range."} | ||
| ErrAttNotLong = &AttributeProtocolError{0x0B, "Attribute Not Long", "The attribute cannot be read using the ATT_READ_BLOB_REQ PDU."} | ||
| ErrAttInsufficientEncKeySize = &AttributeProtocolError{0x0C, "Encryption Key Size Too Short", "The Encryption Key Size used for encrypting this link is too short."} | ||
| ErrAttInvalidLength = &AttributeProtocolError{0x0D, "Invalid Attribute Value Length", "The attribute value length is invalid for the operation."} | ||
| ErrAttUnlikelyError = &AttributeProtocolError{0x0E, "Unlikely Error", "The attribute request that was requested has encountered an error that was unlikely, and therefore could not be completed as requested."} | ||
| ErrAttInsufficientEncryption = &AttributeProtocolError{0x0F, "Insufficient Encryption", "The attribute requires encryption before it can be read or written."} | ||
| ErrAttUnsupportedGroupType = &AttributeProtocolError{0x10, "Unsupported Group Type", "The attribute type is not a supported grouping attribute as defined by a higher layer specification."} | ||
| ErrAttInsufficientResources = &AttributeProtocolError{0x11, "Insufficient Resources", "Insufficient Resources to complete the request."} | ||
| ErrAttOutOfSync = &AttributeProtocolError{0x12, "Database Out Of Sync", "The server requests the client to rediscover the database."} | ||
| ErrAttValueNotAllowed = &AttributeProtocolError{0x13, "Value Not Allowed", "The attribute parameter value was not allowed."} | ||
| ) | ||
|
|
||
| // attErrorsByCode maps ATT error codes to their predefined error values. | ||
| var attErrorsByCode = map[uint8]*AttributeProtocolError{ | ||
| 0x01: ErrAttInvalidHandle, | ||
| 0x02: ErrAttReadNotPermitted, | ||
| 0x03: ErrAttWriteNotPermitted, |
There was a problem hiding this comment.
having global exported variables like this can potentially lead to issues if for example somewhere in the user code these variables were unintentionally mutated.
Maybe the code could be the error type which could be const:
type AttributeProtocolError uint8
const (
ErrAttInvalidHandle = AttributeProtocolError(0x01)
ErrAttReadNotPermitted = AttributeProtocolError(0x02)
ErrAttWriteNotPermitted = AttributeProtocolError(0x03)
...
)
Then have a map from error code to error details:
type errorDetails struct {
// name is a short identifier for the error.
name string
// description is the human-readable description from the spec.
description string
}
// this map can also potentially be a slice
var attErrorDetails = map[AttributeProtocolError]errorDetails{
ErrAttInvalidHandle: errorDetails{"Invalid Handle", "The attribute handle given was not valid on this server."},
ErrAttReadNotPermitted: errorDetails{"Read Not Permitted", "The attribute cannot be read."},
ErrAttWriteNotPermitted: errorDetails{"Write Not Permitted", "The attribute cannot be written."},
...
}
func detailsFromCode(code AttributeProtocolError) errorDetails {
if err, ok := attErrorDetails[code]; ok {
return err
}
...
}
If Name and Description need to be exposed directly, they could be methods on AttributeProtocolError
I don't really like the fact that those helper methods must do a map lookup everytime, even though it is a very small map. What do you think ? Is there some optimization to do ?
|
This will be used to retrieve the error details from an error within a AsyncOperationCompletedHandler. Related work: tinygo-org/bluetooth#421
This will allow to return protocol level errors
My goal is to add support for error code extraction from returned errors.
Especially in windows where you only get the async result code (aborted or failure).
I have tested the extraction of the attribute protocol error code successfully but the code is not included in this pull request because it required changes in the winrt-go library.
The errors have been implemented from https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host/attribute-protocol--att-.html#UUID-eefd3e8d-9b16-3af8-1fb4-fa90f52262e8
Wether or not we actually use them in the code, I thing they are useful just like the generated services and characteristics UUIDs in this repo