From 90bfca07243792f3c8937f729150c3fd0f9c24f2 Mon Sep 17 00:00:00 2001 From: Alexis Couvreur Date: Mon, 16 Mar 2026 19:21:28 -0400 Subject: [PATCH 1/2] feat: add attribute protocol errors This will allow to return protocol level errors --- errors.go | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 errors.go diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..c3b02d5 --- /dev/null +++ b/errors.go @@ -0,0 +1,81 @@ +package bluetooth + +import "fmt" + +// AttributeProtocolError represents an error from the Attribute Protocol (ATT) +// as defined in the Bluetooth Core Specification, Section 3.4.1.1 (ATT_ERROR_RSP). +type AttributeProtocolError struct { + // Code is the 1-byte ATT error code. + Code uint8 + // 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, + 0x04: ErrAttInvalidPDU, + 0x05: ErrAttInsufficientAuthentication, + 0x06: ErrAttRequestNotSupported, + 0x07: ErrAttInvalidOffset, + 0x08: ErrAttInsufficientAuthorization, + 0x09: ErrAttPrepareQueueFull, + 0x0A: ErrAttNotFound, + 0x0B: ErrAttNotLong, + 0x0C: ErrAttInsufficientEncKeySize, + 0x0D: ErrAttInvalidLength, + 0x0E: ErrAttUnlikelyError, + 0x0F: ErrAttInsufficientEncryption, + 0x10: ErrAttUnsupportedGroupType, + 0x11: ErrAttInsufficientResources, + 0x12: ErrAttOutOfSync, + 0x13: ErrAttValueNotAllowed, +} + +// ATTErrorFromCode returns an *AttributeProtocolError for the given ATT error code. +// For known codes it returns the predefined variable; for application errors (0x80–0x9F) +// and common profile/service errors (0xE0–0xFF) it returns a dynamically created error. +// Unknown codes return a generic ATT error. +func ATTErrorFromCode(code uint8) *AttributeProtocolError { + if err, ok := attErrorsByCode[code]; ok { + return err + } + if code >= 0x80 && code <= 0x9F { + return &AttributeProtocolError{code, "Application Error", fmt.Sprintf("Application error code defined by a higher layer specification (0x%02X).", code)} + } + if code >= 0xE0 && code <= 0xFF { + return &AttributeProtocolError{code, "Common Profile and Service Error", fmt.Sprintf("Common profile and service error code (0x%02X).", code)} + } + return &AttributeProtocolError{code, "Reserved", fmt.Sprintf("Reserved for future use (0x%02X).", code)} +} From 4e7beb9817b556e29e98d0010e5c5c512abd6b75 Mon Sep 17 00:00:00 2001 From: Alexis Couvreur Date: Tue, 17 Mar 2026 08:38:45 -0400 Subject: [PATCH 2/2] code review --- errors.go | 155 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 89 insertions(+), 66 deletions(-) diff --git a/errors.go b/errors.go index c3b02d5..5596452 100644 --- a/errors.go +++ b/errors.go @@ -1,81 +1,104 @@ package bluetooth -import "fmt" +import ( + "encoding/hex" + "strings" +) -// AttributeProtocolError represents an error from the Attribute Protocol (ATT) -// as defined in the Bluetooth Core Specification, Section 3.4.1.1 (ATT_ERROR_RSP). -type AttributeProtocolError struct { - // Code is the 1-byte ATT error code. - Code uint8 - // Name is a short identifier for the error. - Name string - // Description is the human-readable description from the spec. - Description string -} +// AttributeProtocolError represents an ATT error code as defined in the +// Bluetooth Core Specification, Section 3.4.1.1 (ATT_ERROR_RSP), Table 3.4. +type AttributeProtocolError uint8 -func (e *AttributeProtocolError) Error() string { - return fmt.Sprintf("ATT error 0x%02X (%s): %s", e.Code, e.Name, e.Description) +// attErrorDetails holds the name and description for an ATT error code. +type attErrorDetails struct { + // name is a short identifier for the error. + name string + // description is the human-readable description from the spec. + description string } // 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."} +const ( + ErrAttInvalidHandle AttributeProtocolError = 0x01 // The attribute handle given was not valid on this server. + ErrAttReadNotPermitted AttributeProtocolError = 0x02 // The attribute cannot be read. + ErrAttWriteNotPermitted AttributeProtocolError = 0x03 // The attribute cannot be written. + ErrAttInvalidPDU AttributeProtocolError = 0x04 // The attribute PDU was invalid. + ErrAttInsufficientAuthentication AttributeProtocolError = 0x05 // The attribute requires authentication before it can be read or written. + ErrAttRequestNotSupported AttributeProtocolError = 0x06 // ATT Server does not support the request received from the client. + ErrAttInvalidOffset AttributeProtocolError = 0x07 // Offset specified was past the end of the attribute. + ErrAttInsufficientAuthorization AttributeProtocolError = 0x08 // The attribute requires authorization before it can be read or written. + ErrAttPrepareQueueFull AttributeProtocolError = 0x09 // Too many prepare writes have been queued. + ErrAttNotFound AttributeProtocolError = 0x0A // No attribute found within the given attribute handle range. + ErrAttNotLong AttributeProtocolError = 0x0B // The attribute cannot be read using the ATT_READ_BLOB_REQ PDU. + ErrAttInsufficientEncKeySize AttributeProtocolError = 0x0C // The Encryption Key Size used for encrypting this link is too short. + ErrAttInvalidLength AttributeProtocolError = 0x0D // The attribute value length is invalid for the operation. + ErrAttUnlikelyError AttributeProtocolError = 0x0E // The attribute request has encountered an error that was unlikely, and therefore could not be completed as requested. + ErrAttInsufficientEncryption AttributeProtocolError = 0x0F // The attribute requires encryption before it can be read or written. + ErrAttUnsupportedGroupType AttributeProtocolError = 0x10 // The attribute type is not a supported grouping attribute as defined by a higher layer specification. + ErrAttInsufficientResources AttributeProtocolError = 0x11 // Insufficient Resources to complete the request. + ErrAttOutOfSync AttributeProtocolError = 0x12 // The server requests the client to rediscover the database. + ErrAttValueNotAllowed AttributeProtocolError = 0x13 // 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, - 0x04: ErrAttInvalidPDU, - 0x05: ErrAttInsufficientAuthentication, - 0x06: ErrAttRequestNotSupported, - 0x07: ErrAttInvalidOffset, - 0x08: ErrAttInsufficientAuthorization, - 0x09: ErrAttPrepareQueueFull, - 0x0A: ErrAttNotFound, - 0x0B: ErrAttNotLong, - 0x0C: ErrAttInsufficientEncKeySize, - 0x0D: ErrAttInvalidLength, - 0x0E: ErrAttUnlikelyError, - 0x0F: ErrAttInsufficientEncryption, - 0x10: ErrAttUnsupportedGroupType, - 0x11: ErrAttInsufficientResources, - 0x12: ErrAttOutOfSync, - 0x13: ErrAttValueNotAllowed, +var attErrors = map[AttributeProtocolError]attErrorDetails{ + ErrAttInvalidHandle: {"Invalid Handle", "The attribute handle given was not valid on this server."}, + ErrAttReadNotPermitted: {"Read Not Permitted", "The attribute cannot be read."}, + ErrAttWriteNotPermitted: {"Write Not Permitted", "The attribute cannot be written."}, + ErrAttInvalidPDU: {"Invalid PDU", "The attribute PDU was invalid."}, + ErrAttInsufficientAuthentication: {"Insufficient Authentication", "The attribute requires authentication before it can be read or written."}, + ErrAttRequestNotSupported: {"Request Not Supported", "ATT Server does not support the request received from the client."}, + ErrAttInvalidOffset: {"Invalid Offset", "Offset specified was past the end of the attribute."}, + ErrAttInsufficientAuthorization: {"Insufficient Authorization", "The attribute requires authorization before it can be read or written."}, + ErrAttPrepareQueueFull: {"Prepare Queue Full", "Too many prepare writes have been queued."}, + ErrAttNotFound: {"Attribute Not Found", "No attribute found within the given attribute handle range."}, + ErrAttNotLong: {"Attribute Not Long", "The attribute cannot be read using the ATT_READ_BLOB_REQ PDU."}, + ErrAttInsufficientEncKeySize: {"Encryption Key Size Too Short", "The Encryption Key Size used for encrypting this link is too short."}, + ErrAttInvalidLength: {"Invalid Attribute Value Length", "The attribute value length is invalid for the operation."}, + ErrAttUnlikelyError: {"Unlikely Error", "The attribute request has encountered an error that was unlikely, and therefore could not be completed as requested."}, + ErrAttInsufficientEncryption: {"Insufficient Encryption", "The attribute requires encryption before it can be read or written."}, + ErrAttUnsupportedGroupType: {"Unsupported Group Type", "The attribute type is not a supported grouping attribute as defined by a higher layer specification."}, + ErrAttInsufficientResources: {"Insufficient Resources", "Insufficient Resources to complete the request."}, + ErrAttOutOfSync: {"Database Out Of Sync", "The server requests the client to rediscover the database."}, + ErrAttValueNotAllowed: {"Value Not Allowed", "The attribute parameter value was not allowed."}, } -// ATTErrorFromCode returns an *AttributeProtocolError for the given ATT error code. -// For known codes it returns the predefined variable; for application errors (0x80–0x9F) -// and common profile/service errors (0xE0–0xFF) it returns a dynamically created error. -// Unknown codes return a generic ATT error. -func ATTErrorFromCode(code uint8) *AttributeProtocolError { - if err, ok := attErrorsByCode[code]; ok { - return err +// Code returns the raw ATT error code. +func (e AttributeProtocolError) Code() uint8 { + return uint8(e) +} + +func (e AttributeProtocolError) details() attErrorDetails { + if d, ok := attErrors[e]; ok { + return d } - if code >= 0x80 && code <= 0x9F { - return &AttributeProtocolError{code, "Application Error", fmt.Sprintf("Application error code defined by a higher layer specification (0x%02X).", code)} + code := hex.EncodeToString([]byte{uint8(e)}) + if e >= 0x80 && e <= 0x9F { + return attErrorDetails{"Application Error", "Application error code defined by a higher layer specification (0x" + code + ")."} } - if code >= 0xE0 && code <= 0xFF { - return &AttributeProtocolError{code, "Common Profile and Service Error", fmt.Sprintf("Common profile and service error code (0x%02X).", code)} + if e >= 0xE0 && e <= 0xFF { + return attErrorDetails{"Common Profile and Service Error", "Common profile and service error code (0x" + code + ")."} } - return &AttributeProtocolError{code, "Reserved", fmt.Sprintf("Reserved for future use (0x%02X).", code)} + return attErrorDetails{"Reserved", "Reserved for future use (0x" + code + ")."} +} + +// Name returns a short human-readable name for this ATT error code. +func (e AttributeProtocolError) Name() string { + return e.details().name +} + +// Description returns the full description from the Bluetooth Core Specification. +func (e AttributeProtocolError) Description() string { + return e.details().description +} + +func (e AttributeProtocolError) Error() string { + d := e.details() + var b strings.Builder + b.WriteString("ATT error 0x") + b.WriteString(hex.EncodeToString([]byte{uint8(e)})) + b.WriteString(" (") + b.WriteString(d.name) + b.WriteString("): ") + b.WriteString(d.description) + return b.String() }