blob: f6fe88ef11bfc3ab3f761a57b82e9752d4378348 [file] [log] [blame]
package att
import (
"encoding/binary"
"fmt"
"time"
"github.com/go-ble/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)
}
}
}