| 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:] } |