Søren Rasmussen
07a23c1845
Some checks reported errors
continuous-integration/drone/push Build encountered an error
358 lines
9.8 KiB
Go
358 lines
9.8 KiB
Go
package hci
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
|
|
"github.com/JuulLabs-OSS/ble"
|
|
"github.com/JuulLabs-OSS/ble/linux/hci/cmd"
|
|
"github.com/JuulLabs-OSS/ble/linux/hci/evt"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// Conn ...
|
|
type Conn struct {
|
|
hci *HCI
|
|
ctx context.Context
|
|
|
|
param evt.LEConnectionComplete
|
|
|
|
// While MTU is the maximum size of payload data that the upper layer (ATT)
|
|
// can accept, the MPS is the maximum PDU payload size this L2CAP implementation
|
|
// supports. When segmantation is not used, the MPS should be made to the same
|
|
// values of MTUs [Vol 3, Part A, 1.4].
|
|
//
|
|
// For LE-U logical transport, the L2CAP implementations should support
|
|
// a minimum of 23 bytes, which are also the default values before the
|
|
// upper layer (ATT) optionally reconfigures them [Vol 3, Part A, 3.2.8].
|
|
rxMTU int
|
|
txMTU int
|
|
rxMPS int
|
|
|
|
// Signaling MTUs are The maximum size of command information that the
|
|
// L2CAP layer entity is capable of accepting.
|
|
// A L2CAP implementations supporting LE-U should support at least 23 bytes.
|
|
// Currently, we support 512 bytes, which should be more than sufficient.
|
|
// The sigTxMTU is discovered via when we sent a signaling pkt that is
|
|
// larger thean the remote device can handle, and get a response of "Command
|
|
// Reject" indicating "Signaling MTU exceeded" along with the actual
|
|
// signaling MTU [Vol 3, Part A, 4.1].
|
|
sigRxMTU int
|
|
sigTxMTU int
|
|
|
|
sigSent chan []byte
|
|
// smpSent chan []byte
|
|
|
|
chInPkt chan packet
|
|
chInPDU chan pdu
|
|
|
|
chDone chan struct{}
|
|
// Host to Controller Data Flow Control pkt-based Data flow control for LE-U [Vol 2, Part E, 4.1.1]
|
|
// chSentBufs tracks the HCI buffer occupied by this connection.
|
|
txBuffer *Client
|
|
|
|
// sigID is used to match responses with signaling requests.
|
|
// The requesting device sets this field and the responding device uses the
|
|
// same value in its response. Within each signalling channel a different
|
|
// Identifier shall be used for each successive command. [Vol 3, Part A, 4]
|
|
sigID uint8
|
|
|
|
// leFrame is set to be true when the LE Credit based flow control is used.
|
|
leFrame bool
|
|
}
|
|
|
|
func newConn(h *HCI, param evt.LEConnectionComplete) *Conn {
|
|
c := &Conn{
|
|
hci: h,
|
|
ctx: context.Background(),
|
|
param: param,
|
|
|
|
rxMTU: ble.DefaultMTU,
|
|
txMTU: ble.DefaultMTU,
|
|
|
|
rxMPS: ble.DefaultMTU,
|
|
|
|
sigRxMTU: ble.MaxMTU,
|
|
sigTxMTU: ble.DefaultMTU,
|
|
|
|
chInPkt: make(chan packet, 16),
|
|
chInPDU: make(chan pdu, 16),
|
|
|
|
txBuffer: NewClient(h.pool),
|
|
|
|
chDone: make(chan struct{}),
|
|
}
|
|
|
|
go func() {
|
|
for {
|
|
if err := c.recombine(); err != nil {
|
|
if err != io.EOF {
|
|
// TODO: wrap and pass the error up.
|
|
// err := errors.Wrap(err, "recombine failed")
|
|
_ = logger.Error("recombine failed: ", "err", err)
|
|
}
|
|
close(c.chInPDU)
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
return c
|
|
}
|
|
|
|
// Context returns the context that is used by this Conn.
|
|
func (c *Conn) Context() context.Context {
|
|
return c.ctx
|
|
}
|
|
|
|
// SetContext sets the context that is used by this Conn.
|
|
func (c *Conn) SetContext(ctx context.Context) {
|
|
c.ctx = ctx
|
|
}
|
|
|
|
// Read copies re-assembled L2CAP PDUs into sdu.
|
|
func (c *Conn) Read(sdu []byte) (n int, err error) {
|
|
p, ok := <-c.chInPDU
|
|
if !ok {
|
|
return 0, errors.Wrap(io.ErrClosedPipe, "input channel closed")
|
|
}
|
|
if len(p) == 0 {
|
|
return 0, errors.Wrap(io.ErrUnexpectedEOF, "received empty packet")
|
|
}
|
|
|
|
// Assume it's a B-Frame.
|
|
slen := p.dlen()
|
|
data := p.payload()
|
|
if c.leFrame {
|
|
// LE-Frame.
|
|
slen = leFrameHdr(p).slen()
|
|
data = leFrameHdr(p).payload()
|
|
}
|
|
if cap(sdu) < slen {
|
|
return 0, errors.Wrapf(io.ErrShortBuffer, "payload received exceeds sdu buffer")
|
|
}
|
|
buf := bytes.NewBuffer(sdu)
|
|
buf.Reset()
|
|
buf.Write(data)
|
|
for buf.Len() < slen {
|
|
p := <-c.chInPDU
|
|
buf.Write(p.payload())
|
|
}
|
|
return slen, nil
|
|
}
|
|
|
|
// Write breaks down a L2CAP SDU into segmants [Vol 3, Part A, 7.3.1]
|
|
func (c *Conn) Write(sdu []byte) (int, error) {
|
|
if len(sdu) > c.txMTU {
|
|
return 0, errors.Wrap(io.ErrShortWrite, "payload exceeds mtu")
|
|
}
|
|
|
|
plen := len(sdu)
|
|
if plen > c.txMTU {
|
|
plen = c.txMTU
|
|
}
|
|
b := make([]byte, 4+plen)
|
|
binary.LittleEndian.PutUint16(b[0:2], uint16(len(sdu)))
|
|
binary.LittleEndian.PutUint16(b[2:4], cidLEAtt)
|
|
if c.leFrame {
|
|
binary.LittleEndian.PutUint16(b[4:6], uint16(len(sdu)))
|
|
copy(b[6:], sdu)
|
|
} else {
|
|
copy(b[4:], sdu)
|
|
}
|
|
sent, err := c.writePDU(b)
|
|
if err != nil {
|
|
return sent, err
|
|
}
|
|
sdu = sdu[plen:]
|
|
|
|
for len(sdu) > 0 {
|
|
plen := len(sdu)
|
|
if plen > c.txMTU {
|
|
plen = c.txMTU
|
|
}
|
|
n, err := c.writePDU(sdu[:plen])
|
|
sent += n
|
|
if err != nil {
|
|
return sent, err
|
|
}
|
|
sdu = sdu[plen:]
|
|
}
|
|
return sent, nil
|
|
}
|
|
|
|
// writePDU breaks down a L2CAP PDU into fragments if it's larger than the HCI buffer size. [Vol 3, Part A, 7.2.1]
|
|
func (c *Conn) writePDU(pdu []byte) (int, error) {
|
|
sent := 0
|
|
flags := uint16(pbfHostToControllerStart << 4) // ACL boundary flags
|
|
|
|
// All L2CAP fragments associated with an L2CAP PDU shall be processed for
|
|
// transmission by the Controller before any other L2CAP PDU for the same
|
|
// logical transport shall be processed.
|
|
c.txBuffer.LockPool()
|
|
defer c.txBuffer.UnlockPool()
|
|
|
|
// Fail immediately if the connection is already closed
|
|
// Check this with the pool locked to avoid race conditions
|
|
// with handleDisconnectionComplete
|
|
select {
|
|
case <-c.chDone:
|
|
return 0, io.ErrClosedPipe
|
|
default:
|
|
}
|
|
|
|
for len(pdu) > 0 {
|
|
// Get a buffer from our pre-allocated and flow-controlled pool.
|
|
pkt := c.txBuffer.Get() // ACL pkt
|
|
flen := len(pdu) // fragment length
|
|
if flen > pkt.Cap()-1-4 {
|
|
flen = pkt.Cap() - 1 - 4
|
|
}
|
|
|
|
// Prepare the Headers
|
|
|
|
// HCI Header: pkt Type
|
|
if err := binary.Write(pkt, binary.LittleEndian, pktTypeACLData); err != nil {
|
|
return 0, err
|
|
}
|
|
// ACL Header: handle and flags
|
|
if err := binary.Write(pkt, binary.LittleEndian, c.param.ConnectionHandle()|(flags<<8)); err != nil {
|
|
return 0, err
|
|
}
|
|
// ACL Header: data len
|
|
if err := binary.Write(pkt, binary.LittleEndian, uint16(flen)); err != nil {
|
|
return 0, err
|
|
}
|
|
// Append payload
|
|
if err := binary.Write(pkt, binary.LittleEndian, pdu[:flen]); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// Flush the pkt to HCI
|
|
select {
|
|
case <-c.chDone:
|
|
return 0, io.ErrClosedPipe
|
|
default:
|
|
}
|
|
|
|
if _, err := c.hci.skt.Write(pkt.Bytes()); err != nil {
|
|
return sent, err
|
|
}
|
|
sent += flen
|
|
|
|
flags = (pbfContinuing << 4) // Set "continuing" in the boundary flags for the rest of fragments, if any.
|
|
pdu = pdu[flen:] // Advence the point
|
|
}
|
|
return sent, nil
|
|
}
|
|
|
|
// Recombines fragments into a L2CAP PDU. [Vol 3, Part A, 7.2.2]
|
|
func (c *Conn) recombine() error {
|
|
pkt, ok := <-c.chInPkt
|
|
if !ok {
|
|
return io.EOF
|
|
}
|
|
|
|
p := pdu(pkt.data())
|
|
|
|
// Currently, check for LE-U only. For channels that we don't recognizes,
|
|
// re-combine them anyway, and discard them later when we dispatch the PDU
|
|
// according to CID.
|
|
if p.cid() == cidLEAtt && p.dlen() > c.rxMPS {
|
|
return fmt.Errorf("fragment size (%d) larger than rxMPS (%d)", p.dlen(), c.rxMPS)
|
|
}
|
|
|
|
// If this pkt is not a complete PDU, and we'll be receiving more
|
|
// fragments, re-allocate the whole PDU (including Header).
|
|
if len(p.payload()) < p.dlen() {
|
|
p = make([]byte, 0, 4+p.dlen())
|
|
p = append(p, pdu(pkt.data())...)
|
|
}
|
|
for len(p) < 4+p.dlen() {
|
|
if pkt, ok = <-c.chInPkt; !ok || (pkt.pbf()&pbfContinuing) == 0 {
|
|
return io.ErrUnexpectedEOF
|
|
}
|
|
p = append(p, pdu(pkt.data())...)
|
|
}
|
|
|
|
// TODO: support dynamic or assigned channels for LE-Frames.
|
|
switch p.cid() {
|
|
case cidLEAtt:
|
|
c.chInPDU <- p
|
|
case cidLESignal:
|
|
c.handleSignal(p)
|
|
case cidSMP:
|
|
c.handleSMP(p)
|
|
default:
|
|
logger.Info("recombine()", "unrecognized CID", fmt.Sprintf("%04X, [%X]", p.cid(), p))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Disconnected returns a receiving channel, which is closed when the connection disconnects.
|
|
func (c *Conn) Disconnected() <-chan struct{} {
|
|
return c.chDone
|
|
}
|
|
|
|
// Close disconnects the connection by sending hci disconnect command to the device.
|
|
func (c *Conn) Close() error {
|
|
select {
|
|
case <-c.chDone:
|
|
// Return if it's already closed.
|
|
return nil
|
|
default:
|
|
c.hci.Send(&cmd.Disconnect{
|
|
ConnectionHandle: c.param.ConnectionHandle(),
|
|
Reason: 0x13,
|
|
}, nil)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// LocalAddr returns local device's MAC address.
|
|
func (c *Conn) LocalAddr() ble.Addr { return c.hci.Addr() }
|
|
|
|
// RemoteAddr returns remote device's MAC address.
|
|
func (c *Conn) RemoteAddr() ble.Addr {
|
|
a := c.param.PeerAddress()
|
|
return net.HardwareAddr([]byte{a[5], a[4], a[3], a[2], a[1], a[0]})
|
|
}
|
|
|
|
// RxMTU returns the MTU which the upper layer is capable of accepting.
|
|
func (c *Conn) RxMTU() int { return c.rxMTU }
|
|
|
|
// SetRxMTU sets the MTU which the upper layer is capable of accepting.
|
|
func (c *Conn) SetRxMTU(mtu int) { c.rxMTU, c.rxMPS = mtu, mtu }
|
|
|
|
// TxMTU returns the MTU which the remote device is capable of accepting.
|
|
func (c *Conn) TxMTU() int { return c.txMTU }
|
|
|
|
// SetTxMTU sets the MTU which the remote device is capable of accepting.
|
|
func (c *Conn) SetTxMTU(mtu int) { c.txMTU = mtu }
|
|
|
|
// pkt implements HCI ACL Data Packet [Vol 2, Part E, 5.4.2]
|
|
// Packet boundary flags , bit[5:6] of handle field's MSB
|
|
// Broadcast flags. bit[7:8] of handle field's MSB
|
|
// Not used in LE-U. Leave it as 0x00 (Point-to-Point).
|
|
// Broadcasting in LE uses ADVB logical transport.
|
|
type packet []byte
|
|
|
|
func (a packet) handle() uint16 { return uint16(a[0]) | (uint16(a[1]&0x0f) << 8) }
|
|
func (a packet) pbf() int { return (int(a[1]) >> 4) & 0x3 }
|
|
func (a packet) bcf() int { return (int(a[1]) >> 6) & 0x3 }
|
|
func (a packet) dlen() int { return int(a[2]) | (int(a[3]) << 8) }
|
|
func (a packet) data() []byte { return a[4:] }
|
|
|
|
type pdu []byte
|
|
|
|
func (p pdu) dlen() int { return int(binary.LittleEndian.Uint16(p[0:2])) }
|
|
func (p pdu) cid() uint16 { return binary.LittleEndian.Uint16(p[2:4]) }
|
|
func (p pdu) payload() []byte { return p[4:] }
|
|
|
|
type leFrameHdr pdu
|
|
|
|
func (f leFrameHdr) slen() int { return int(binary.LittleEndian.Uint16(f[4:6])) }
|
|
func (f leFrameHdr) payload() []byte { return f[6:] }
|