fermentord/vendor/github.com/JuulLabs-OSS/ble/linux/att/client.go
Søren Rasmussen 07a23c1845
Some checks reported errors
continuous-integration/drone/push Build encountered an error
Upgrade to go 1.20 and add vendor catalog
2023-04-22 10:37:23 +02:00

560 lines
16 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 clients 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)
}
}
}