blob: d6c39fc1c849c2a4dff6d8486cbcf8004aa40c8c [file] [log] [blame]
package hci
import (
"bytes"
"context"
"encoding/binary"
"fmt"
"io"
"net"
"github.com/go-ble/ble"
"github.com/go-ble/ble/linux/hci/cmd"
"github.com/go-ble/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()
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:] }