package att import ( "encoding/binary" "fmt" "time" "github.com/JuulLabs-OSS/ble" "github.com/pkg/errors" ) // NotificationHandler handles notification or indication. type NotificationHandler interface { HandleNotification(req []byte) } // Client implementa an Attribute Protocol Client. type Client struct { l2c ble.Conn rspc chan []byte rxBuf []byte chTxBuf chan []byte chErr chan error handler NotificationHandler } // NewClient returns an Attribute Protocol Client. func NewClient(l2c ble.Conn, h NotificationHandler) *Client { c := &Client{ l2c: l2c, rspc: make(chan []byte), chTxBuf: make(chan []byte, 1), rxBuf: make([]byte, ble.MaxMTU), chErr: make(chan error, 1), handler: h, } c.chTxBuf <- make([]byte, l2c.TxMTU(), l2c.TxMTU()) return c } // ExchangeMTU informs the server of the client’s maximum receive MTU size and // request the server to respond with its maximum receive MTU size. [Vol 3, Part F, 3.4.2.1] func (c *Client) ExchangeMTU(clientRxMTU int) (serverRxMTU int, err error) { if clientRxMTU < ble.DefaultMTU || clientRxMTU > ble.MaxMTU { return 0, ErrInvalidArgument } // Acquire and reuse the txBuf, and release it after usage. // The same txBuf, or a newly allocate one, if the txMTU is changed, // will be released back to the channel. txBuf := <-c.chTxBuf defer func() { c.chTxBuf <- txBuf }() // Let L2CAP know the MTU we can handle. c.l2c.SetRxMTU(clientRxMTU) req := ExchangeMTURequest(txBuf[:3]) req.SetAttributeOpcode() req.SetClientRxMTU(uint16(clientRxMTU)) b, err := c.sendReq(req) if err != nil { return 0, err } // Convert and validate the response. rsp := ExchangeMTUResponse(b) switch { case rsp[0] == ErrorResponseCode && len(rsp) == 5: return 0, ble.ATTError(rsp[4]) case rsp[0] == ErrorResponseCode && len(rsp) != 5: fallthrough case rsp[0] != rsp.AttributeOpcode(): fallthrough case len(rsp) != 3: return 0, ErrInvalidResponse } txMTU := int(rsp.ServerRxMTU()) if len(txBuf) != txMTU { // Let L2CAP know the MTU that the remote device can handle. c.l2c.SetTxMTU(txMTU) // Put a re-allocated txBuf back to the channel. // The txBuf has been captured in deferred function. txBuf = make([]byte, txMTU, txMTU) } return txMTU, nil } // FindInformation obtains the mapping of attribute handles with their associated types. // This allows a Client to discover the list of attributes and their types on a server. // [Vol 3, Part F, 3.4.3.1 & 3.4.3.2] func (c *Client) FindInformation(starth, endh uint16) (fmt int, data []byte, err error) { if starth == 0 || starth > endh { return 0x00, nil, ErrInvalidArgument } // Acquire and reuse the txBuf, and release it after usage. txBuf := <-c.chTxBuf defer func() { c.chTxBuf <- txBuf }() req := FindInformationRequest(txBuf[:5]) req.SetAttributeOpcode() req.SetStartingHandle(starth) req.SetEndingHandle(endh) b, err := c.sendReq(req) if err != nil { return 0x00, nil, err } // Convert and validate the response. rsp := FindInformationResponse(b) switch { case rsp[0] == ErrorResponseCode && len(rsp) == 5: return 0x00, nil, ble.ATTError(rsp[4]) case rsp[0] == ErrorResponseCode && len(rsp) != 5: fallthrough case rsp[0] != rsp.AttributeOpcode(): fallthrough case len(rsp) < 6: fallthrough case rsp.Format() == 0x01 && ((len(rsp)-2)%4) != 0: fallthrough case rsp.Format() == 0x02 && ((len(rsp)-2)%18) != 0: return 0x00, nil, ErrInvalidResponse } return int(rsp.Format()), rsp.InformationData(), nil } // // HandleInformationList ... // type HandleInformationList []byte // // // FoundAttributeHandle ... // func (l HandleInformationList) FoundAttributeHandle() []byte { return l[:2] } // // // GroupEndHandle ... // func (l HandleInformationList) GroupEndHandle() []byte { return l[2:4] } // // // FindByTypeValue ... // func (c *Client) FindByTypeValue(starth, endh, attrType uint16, value []byte) ([]HandleInformationList, error) { // return nil, nil // } // ReadByType obtains the values of attributes where the attribute type is known // but the handle is not known. [Vol 3, Part F, 3.4.4.1 & 3.4.4.2] func (c *Client) ReadByType(starth, endh uint16, uuid ble.UUID) (int, []byte, error) { if starth > endh || (len(uuid) != 2 && len(uuid) != 16) { return 0, nil, ErrInvalidArgument } // Acquire and reuse the txBuf, and release it after usage. txBuf := <-c.chTxBuf defer func() { c.chTxBuf <- txBuf }() req := ReadByTypeRequest(txBuf[:5+len(uuid)]) req.SetAttributeOpcode() req.SetStartingHandle(starth) req.SetEndingHandle(endh) req.SetAttributeType(uuid) b, err := c.sendReq(req) if err != nil { return 0, nil, err } // Convert and validate the response. rsp := ReadByTypeResponse(b) switch { case rsp[0] == ErrorResponseCode && len(rsp) == 5: return 0, nil, ble.ATTError(rsp[4]) case rsp[0] == ErrorResponseCode && len(rsp) != 5: fallthrough case rsp[0] != rsp.AttributeOpcode(): fallthrough case len(rsp) < 4 || len(rsp.AttributeDataList())%int(rsp.Length()) != 0: return 0, nil, ErrInvalidResponse } return int(rsp.Length()), rsp.AttributeDataList(), nil } // Read requests the server to read the value of an attribute and return its // value in a Read Response. [Vol 3, Part F, 3.4.4.3 & 3.4.4.4] func (c *Client) Read(handle uint16) ([]byte, error) { // Acquire and reuse the txBuf, and release it after usage. txBuf := <-c.chTxBuf defer func() { c.chTxBuf <- txBuf }() req := ReadRequest(txBuf[:3]) req.SetAttributeOpcode() req.SetAttributeHandle(handle) b, err := c.sendReq(req) if err != nil { return nil, err } // Convert and validate the response. rsp := ReadResponse(b) switch { case rsp[0] == ErrorResponseCode && len(rsp) == 5: return nil, ble.ATTError(rsp[4]) case rsp[0] == ErrorResponseCode && len(rsp) != 5: fallthrough case rsp[0] != rsp.AttributeOpcode(): fallthrough case len(rsp) < 1: return nil, ErrInvalidResponse } return rsp.AttributeValue(), nil } // ReadBlob requests the server to read part of the value of an attribute at a // given offset and return a specific part of the value in a Read Blob Response. // [Vol 3, Part F, 3.4.4.5 & 3.4.4.6] func (c *Client) ReadBlob(handle, offset uint16) ([]byte, error) { // Acquire and reuse the txBuf, and release it after usage. txBuf := <-c.chTxBuf defer func() { c.chTxBuf <- txBuf }() req := ReadBlobRequest(txBuf[:5]) req.SetAttributeOpcode() req.SetAttributeHandle(handle) req.SetValueOffset(offset) b, err := c.sendReq(req) if err != nil { return nil, err } // Convert and validate the response. rsp := ReadBlobResponse(b) switch { case rsp[0] == ErrorResponseCode && len(rsp) == 5: return nil, ble.ATTError(rsp[4]) case rsp[0] == ErrorResponseCode && len(rsp) != 5: fallthrough case rsp[0] != rsp.AttributeOpcode(): fallthrough case len(rsp) < 1: return nil, ErrInvalidResponse } return rsp.PartAttributeValue(), nil } // ReadMultiple requests the server to read two or more values of a set of // attributes and return their values in a Read Multiple Response. // Only values that have a known fixed size can be read, with the exception of // the last value that can have a variable length. The knowledge of whether // attributes have a known fixed size is defined in a higher layer specification. // [Vol 3, Part F, 3.4.4.7 & 3.4.4.8] func (c *Client) ReadMultiple(handles []uint16) ([]byte, error) { // Should request to read two or more values. if len(handles) < 2 || len(handles)*2 > c.l2c.TxMTU()-1 { return nil, ErrInvalidArgument } // Acquire and reuse the txBuf, and release it after usage. txBuf := <-c.chTxBuf defer func() { c.chTxBuf <- txBuf }() req := ReadMultipleRequest(txBuf[:1+len(handles)*2]) req.SetAttributeOpcode() p := req.SetOfHandles() for _, h := range handles { binary.LittleEndian.PutUint16(p, h) p = p[2:] } b, err := c.sendReq(req) if err != nil { return nil, err } // Convert and validate the response. rsp := ReadMultipleResponse(b) switch { case rsp[0] == ErrorResponseCode && len(rsp) == 5: return nil, ble.ATTError(rsp[4]) case rsp[0] == ErrorResponseCode && len(rsp) != 5: fallthrough case rsp[0] != rsp.AttributeOpcode(): fallthrough case len(rsp) < 1: return nil, ErrInvalidResponse } return rsp.SetOfValues(), nil } // ReadByGroupType obtains the values of attributes where the attribute type is known, // the type of a grouping attribute as defined by a higher layer specification, but // the handle is not known. [Vol 3, Part F, 3.4.4.9 & 3.4.4.10] func (c *Client) ReadByGroupType(starth, endh uint16, uuid ble.UUID) (int, []byte, error) { if starth > endh || (len(uuid) != 2 && len(uuid) != 16) { return 0, nil, ErrInvalidArgument } // Acquire and reuse the txBuf, and release it after usage. txBuf := <-c.chTxBuf defer func() { c.chTxBuf <- txBuf }() req := ReadByGroupTypeRequest(txBuf[:5+len(uuid)]) req.SetAttributeOpcode() req.SetStartingHandle(starth) req.SetEndingHandle(endh) req.SetAttributeGroupType(uuid) b, err := c.sendReq(req) if err != nil { return 0, nil, err } // Convert and validate the response. rsp := ReadByGroupTypeResponse(b) switch { case rsp[0] == ErrorResponseCode && len(rsp) == 5: return 0, nil, ble.ATTError(rsp[4]) case rsp[0] == ErrorResponseCode && len(rsp) != 5: fallthrough case rsp[0] != rsp.AttributeOpcode(): fallthrough case len(rsp) < 4: fallthrough case len(rsp.AttributeDataList())%int(rsp.Length()) != 0: return 0, nil, ErrInvalidResponse } return int(rsp.Length()), rsp.AttributeDataList(), nil } // Write requests the server to write the value of an attribute and acknowledge that // this has been achieved in a Write Response. [Vol 3, Part F, 3.4.5.1 & 3.4.5.2] func (c *Client) Write(handle uint16, value []byte) error { if len(value) > c.l2c.TxMTU()-3 { return ErrInvalidArgument } // Acquire and reuse the txBuf, and release it after usage. txBuf := <-c.chTxBuf defer func() { c.chTxBuf <- txBuf }() req := WriteRequest(txBuf[:3+len(value)]) req.SetAttributeOpcode() req.SetAttributeHandle(handle) req.SetAttributeValue(value) b, err := c.sendReq(req) if err != nil { return err } // Convert and validate the response. rsp := WriteResponse(b) switch { case rsp[0] == ErrorResponseCode && len(rsp) == 5: return ble.ATTError(rsp[4]) case rsp[0] == ErrorResponseCode && len(rsp) != 5: fallthrough case rsp[0] != rsp.AttributeOpcode(): return ErrInvalidResponse } return nil } // WriteCommand requests the server to write the value of an attribute, typically // into a control-point attribute. [Vol 3, Part F, 3.4.5.3] func (c *Client) WriteCommand(handle uint16, value []byte) error { if len(value) > c.l2c.TxMTU()-3 { return ErrInvalidArgument } // Acquire and reuse the txBuf, and release it after usage. txBuf := <-c.chTxBuf defer func() { c.chTxBuf <- txBuf }() req := WriteCommand(txBuf[:3+len(value)]) req.SetAttributeOpcode() req.SetAttributeHandle(handle) req.SetAttributeValue(value) return c.sendCmd(req) } // SignedWrite requests the server to write the value of an attribute with an authentication // signature, typically into a control-point attribute. [Vol 3, Part F, 3.4.5.4] func (c *Client) SignedWrite(handle uint16, value []byte, signature [12]byte) error { if len(value) > c.l2c.TxMTU()-15 { return ErrInvalidArgument } // Acquire and reuse the txBuf, and release it after usage. txBuf := <-c.chTxBuf defer func() { c.chTxBuf <- txBuf }() req := SignedWriteCommand(txBuf[:15+len(value)]) req.SetAttributeOpcode() req.SetAttributeHandle(handle) req.SetAttributeValue(value) req.SetAuthenticationSignature(signature) return c.sendCmd(req) } // PrepareWrite requests the server to prepare to write the value of an attribute. // The server will respond to this request with a Prepare Write Response, so that // the Client can verify that the value was received correctly. // [Vol 3, Part F, 3.4.6.1 & 3.4.6.2] func (c *Client) PrepareWrite(handle uint16, offset uint16, value []byte) (uint16, uint16, []byte, error) { if len(value) > c.l2c.TxMTU()-5 { return 0, 0, nil, ErrInvalidArgument } // Acquire and reuse the txBuf, and release it after usage. txBuf := <-c.chTxBuf defer func() { c.chTxBuf <- txBuf }() req := PrepareWriteRequest(txBuf[:5+len(value)]) req.SetAttributeOpcode() req.SetAttributeHandle(handle) req.SetValueOffset(offset) b, err := c.sendReq(req) if err != nil { return 0, 0, nil, err } // Convert and validate the response. rsp := PrepareWriteResponse(b) switch { case rsp[0] == ErrorResponseCode && len(rsp) == 5: return 0, 0, nil, ble.ATTError(rsp[4]) case rsp[0] == ErrorResponseCode && len(rsp) != 5: fallthrough case rsp[0] != rsp.AttributeOpcode(): fallthrough case len(rsp) < 5: return 0, 0, nil, ErrInvalidResponse } return rsp.AttributeHandle(), rsp.ValueOffset(), rsp.PartAttributeValue(), nil } // ExecuteWrite requests the server to write or cancel the write of all the prepared // values currently held in the prepare queue from this Client. This request shall be // handled by the server as an atomic operation. [Vol 3, Part F, 3.4.6.3 & 3.4.6.4] func (c *Client) ExecuteWrite(flags uint8) error { // Acquire and reuse the txBuf, and release it after usage. txBuf := <-c.chTxBuf defer func() { c.chTxBuf <- txBuf }() req := ExecuteWriteRequest(txBuf[:1]) req.SetAttributeOpcode() req.SetFlags(flags) b, err := c.sendReq(req) if err != nil { return err } // Convert and validate the response. rsp := ExecuteWriteResponse(b) switch { case rsp[0] == ErrorResponseCode && len(rsp) == 5: return ble.ATTError(rsp[4]) case rsp[0] == ErrorResponseCode && len(rsp) == 5: fallthrough case rsp[0] != rsp.AttributeOpcode(): return ErrInvalidResponse } return nil } func (c *Client) sendCmd(b []byte) error { _, err := c.l2c.Write(b) return err } func (c *Client) sendReq(b []byte) (rsp []byte, err error) { logger.Debug("client", "req", fmt.Sprintf("% X", b)) if _, err := c.l2c.Write(b); err != nil { return nil, errors.Wrap(err, "send ATT request failed") } for { select { case rsp := <-c.rspc: if rsp[0] == ErrorResponseCode || rsp[0] == rspOfReq[b[0]] { return rsp, nil } // Sometimes when we connect to an Apple device, it sends // ATT requests asynchronously to us. // In this case, we // returns an ErrReqNotSupp response, and continue to wait // the response to our request. errRsp := newErrorResponse(rsp[0], 0x0000, ble.ErrReqNotSupp) logger.Debug("client", "req", fmt.Sprintf("% X", b)) _, err := c.l2c.Write(errRsp) if err != nil { return nil, errors.Wrap(err, "unexpected ATT response received") } case err := <-c.chErr: return nil, errors.Wrap(err, "ATT request failed") case <-time.After(30 * time.Second): return nil, errors.Wrap(ErrSeqProtoTimeout, "ATT request timeout") } } } // Loop ... func (c *Client) Loop() { type asyncWork struct { handle func([]byte) data []byte } ch := make(chan asyncWork, 16) defer close(ch) go func() { for w := range ch { w.handle(w.data) } }() confirmation := []byte{HandleValueConfirmationCode} for { n, err := c.l2c.Read(c.rxBuf) logger.Debug("client", "rsp", fmt.Sprintf("% X", c.rxBuf[:n])) if err != nil { // We don't expect any error from the bearer (L2CAP ACL-U) // Pass it along to the pending request, if any, and escape. c.chErr <- err return } b := make([]byte, n) copy(b, c.rxBuf) if (b[0] != HandleValueNotificationCode) && (b[0] != HandleValueIndicationCode) { c.rspc <- b continue } // Deliver the full request to upper layer. select { case ch <- asyncWork{handle: c.handler.HandleNotification, data: b}: default: // If this really happens, especially on a slow machine, enlarge the channel buffer. _ = logger.Error("client", "req", "can't enqueue incoming notification.") } // Always write aknowledgement for an indication, even it was an invalid request. if b[0] == HandleValueIndicationCode { logger.Debug("client", "req", fmt.Sprintf("% X", b)) _, _ = c.l2c.Write(confirmation) } } }