forked from go-ble/ble
/
signal.go
254 lines (230 loc) · 7.08 KB
/
signal.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
package hci
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"time"
"github.com/Flips01/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:
}