Søren Rasmussen
07a23c1845
Some checks reported errors
continuous-integration/drone/push Build encountered an error
254 lines
7.1 KiB
Go
254 lines
7.1 KiB
Go
package hci
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/JuulLabs-OSS/ble/linux/hci/cmd"
|
|
)
|
|
|
|
// Signal ...
|
|
type Signal interface {
|
|
Code() int
|
|
Marshal() ([]byte, error)
|
|
Unmarshal([]byte) error
|
|
}
|
|
|
|
type sigCmd []byte
|
|
|
|
func (s sigCmd) code() int { return int(s[0]) }
|
|
func (s sigCmd) id() uint8 { return s[1] }
|
|
func (s sigCmd) len() int { return int(binary.LittleEndian.Uint16(s[2:4])) }
|
|
func (s sigCmd) data() []byte { return s[4 : 4+s.len()] }
|
|
|
|
// Signal ...
|
|
func (c *Conn) Signal(req Signal, rsp Signal) error {
|
|
data, err := req.Marshal()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
buf := bytes.NewBuffer(make([]byte, 0))
|
|
if err := binary.Write(buf, binary.LittleEndian, uint16(4+len(data))); err != nil {
|
|
return err
|
|
}
|
|
if err := binary.Write(buf, binary.LittleEndian, cidLESignal); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := binary.Write(buf, binary.LittleEndian, uint8(req.Code())); err != nil {
|
|
return err
|
|
}
|
|
if err := binary.Write(buf, binary.LittleEndian, uint8(c.sigID)); err != nil {
|
|
return err
|
|
}
|
|
if err := binary.Write(buf, binary.LittleEndian, uint16(len(data))); err != nil {
|
|
return err
|
|
}
|
|
if err := binary.Write(buf, binary.LittleEndian, data); err != nil {
|
|
return err
|
|
}
|
|
|
|
c.sigSent = make(chan []byte)
|
|
defer close(c.sigSent)
|
|
if _, err := c.writePDU(buf.Bytes()); err != nil {
|
|
return err
|
|
}
|
|
var s sigCmd
|
|
select {
|
|
case s = <-c.sigSent:
|
|
case <-time.After(time.Second):
|
|
// TODO: Find the proper timed out defined in spec, if any.
|
|
return errors.New("signaling request timed out")
|
|
}
|
|
|
|
if s.code() != req.Code() {
|
|
return errors.New("mismatched signaling response")
|
|
}
|
|
if s.id() != c.sigID {
|
|
return errors.New("mismatched signaling id")
|
|
}
|
|
c.sigID++
|
|
if rsp == nil {
|
|
return nil
|
|
}
|
|
return rsp.Unmarshal(s.data())
|
|
}
|
|
|
|
func (c *Conn) sendResponse(code uint8, id uint8, r Signal) (int, error) {
|
|
data, err := r.Marshal()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
buf := bytes.NewBuffer(make([]byte, 0))
|
|
if err := binary.Write(buf, binary.LittleEndian, uint16(4+len(data))); err != nil {
|
|
return 0, err
|
|
}
|
|
if err := binary.Write(buf, binary.LittleEndian, cidLESignal); err != nil {
|
|
return 0, err
|
|
}
|
|
if err := binary.Write(buf, binary.LittleEndian, code); err != nil {
|
|
return 0, err
|
|
}
|
|
if err := binary.Write(buf, binary.LittleEndian, id); err != nil {
|
|
return 0, err
|
|
}
|
|
if err := binary.Write(buf, binary.LittleEndian, uint16(len(data))); err != nil {
|
|
return 0, err
|
|
}
|
|
if err := binary.Write(buf, binary.LittleEndian, data); err != nil {
|
|
return 0, err
|
|
}
|
|
logger.Debug("sig", "send", fmt.Sprintf("[%X]", buf.Bytes()))
|
|
return c.writePDU(buf.Bytes())
|
|
}
|
|
|
|
func (c *Conn) handleSignal(p pdu) error {
|
|
logger.Debug("sig", "recv", fmt.Sprintf("[%X]", p))
|
|
// When multiple commands are included in an L2CAP packet and the packet
|
|
// exceeds the signaling MTU (MTUsig) of the receiver, a single Command Reject
|
|
// packet shall be sent in response. The identifier shall match the first Request
|
|
// command in the L2CAP packet. If only Responses are recognized, the packet
|
|
// shall be silently discarded. [Vol3, Part A, 4.1]
|
|
if p.dlen() > c.sigRxMTU {
|
|
_, err := c.sendResponse(
|
|
SignalCommandReject,
|
|
sigCmd(p.payload()).id(),
|
|
&CommandReject{
|
|
Reason: 0x0001, // Signaling MTU exceeded.
|
|
Data: []byte{uint8(c.sigRxMTU), uint8(c.sigRxMTU >> 8)}, // Actual MTUsig.
|
|
})
|
|
if err != nil {
|
|
_ = logger.Error("send repsonse", fmt.Sprintf("%v", err))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
s := sigCmd(p.payload())
|
|
for len(s) > 0 {
|
|
// Check if it's a supported request.
|
|
switch s.code() {
|
|
case SignalDisconnectRequest:
|
|
c.handleDisconnectRequest(s)
|
|
case SignalConnectionParameterUpdateRequest:
|
|
c.handleConnectionParameterUpdateRequest(s)
|
|
case SignalLECreditBasedConnectionRequest:
|
|
c.LECreditBasedConnectionRequest(s)
|
|
case SignalLEFlowControlCredit:
|
|
c.LEFlowControlCredit(s)
|
|
default:
|
|
// Check if it's a response to a sent command.
|
|
select {
|
|
case c.sigSent <- s:
|
|
continue
|
|
default:
|
|
}
|
|
|
|
c.sendResponse(
|
|
SignalCommandReject,
|
|
s.id(),
|
|
&CommandReject{
|
|
Reason: 0x0000, // Command not understood.
|
|
})
|
|
}
|
|
s = s[4+s.len():] // advance to next the packet.
|
|
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DisconnectRequest implements Disconnect Request (0x06) [Vol 3, Part A, 4.6].
|
|
func (c *Conn) handleDisconnectRequest(s sigCmd) {
|
|
var req DisconnectRequest
|
|
if err := req.Unmarshal(s.data()); err != nil {
|
|
return
|
|
}
|
|
|
|
// Send Command Reject when the DCID is unrecognized.
|
|
if req.DestinationCID != cidLEAtt {
|
|
endpoints := make([]byte, 4)
|
|
binary.LittleEndian.PutUint16(endpoints, req.SourceCID)
|
|
binary.LittleEndian.PutUint16(endpoints, req.DestinationCID)
|
|
c.sendResponse(
|
|
SignalCommandReject,
|
|
s.id(),
|
|
&CommandReject{
|
|
Reason: 0x0002, // Invalid CID in request
|
|
Data: endpoints,
|
|
})
|
|
return
|
|
}
|
|
|
|
// Silently discard the request if SCID failed to find the same match.
|
|
if req.SourceCID != cidLEAtt {
|
|
return
|
|
}
|
|
|
|
c.sendResponse(
|
|
SignalDisconnectResponse,
|
|
s.id(),
|
|
&DisconnectResponse{
|
|
DestinationCID: req.DestinationCID,
|
|
SourceCID: req.SourceCID,
|
|
})
|
|
}
|
|
|
|
// ConnectionParameterUpdateRequest implements Connection Parameter Update Request (0x12) [Vol 3, Part A, 4.20].
|
|
func (c *Conn) handleConnectionParameterUpdateRequest(s sigCmd) {
|
|
// This command shall only be sent from the LE slave device to the LE master
|
|
// device and only if one or more of the LE slave Controller, the LE master
|
|
// Controller, the LE slave Host and the LE master Host do not support the
|
|
// Connection Parameters Request Link Layer Control Procedure ([Vol. 6] Part B,
|
|
// Section 5.1.7). If an LE slave Host receives a Connection Parameter Update
|
|
// Request packet it shall respond with a Command Reject packet with reason
|
|
// 0x0000 (Command not understood).
|
|
if c.param.Role() != roleMaster {
|
|
c.sendResponse(
|
|
SignalCommandReject,
|
|
s.id(),
|
|
&CommandReject{
|
|
Reason: 0x0000, // Command not understood.
|
|
})
|
|
|
|
return
|
|
}
|
|
var req ConnectionParameterUpdateRequest
|
|
if err := req.Unmarshal(s.data()); err != nil {
|
|
return
|
|
}
|
|
|
|
// LE Connection Update (0x08|0x0013) [Vol 2, Part E, 7.8.18]
|
|
c.hci.Send(&cmd.LEConnectionUpdate{
|
|
ConnectionHandle: c.param.ConnectionHandle(),
|
|
ConnIntervalMin: req.IntervalMin,
|
|
ConnIntervalMax: req.IntervalMax,
|
|
ConnLatency: req.SlaveLatency,
|
|
SupervisionTimeout: req.TimeoutMultiplier,
|
|
MinimumCELength: 0, // Informational, and spec doesn't specify the use.
|
|
MaximumCELength: 0, // Informational, and spec doesn't specify the use.
|
|
}, nil)
|
|
|
|
// Currently, we (as a slave host) accept all the parameters and forward
|
|
// it to the controller. The controller might update all, partial or even
|
|
// none (ignore) of the parameters. The slave(remote) host will be indicated
|
|
// by its controller if the update actually happens.
|
|
// TODO: allow users to implement what parameters to accept.
|
|
c.sendResponse(
|
|
SignalConnectionParameterUpdateResponse,
|
|
s.id(),
|
|
&ConnectionParameterUpdateResponse{
|
|
Result: 0, // Accept.
|
|
})
|
|
}
|
|
|
|
// LECreditBasedConnectionRequest ...
|
|
func (c *Conn) LECreditBasedConnectionRequest(s sigCmd) {
|
|
// TODO:
|
|
}
|
|
|
|
// LEFlowControlCredit ...
|
|
func (c *Conn) LEFlowControlCredit(s sigCmd) {
|
|
// TODO:
|
|
}
|