blob: a70cecf80b43bef07e18cf5506d5ee8976f3438f [file] [log] [blame]
package linux
import (
"fmt"
"io"
"log"
"sync"
"github.com/paypal/gatt/linux/cmd"
"github.com/paypal/gatt/linux/evt"
)
type HCI struct {
AcceptMasterHandler func(pd *PlatData)
AcceptSlaveHandler func(pd *PlatData)
AdvertisementHandler func(pd *PlatData)
d io.ReadWriteCloser
c *cmd.Cmd
e *evt.Evt
plist map[bdaddr]*PlatData
plistmu *sync.Mutex
bufCnt chan struct{}
bufSize int
maxConn int
connsmu *sync.Mutex
conns map[uint16]*conn
adv bool
advmu *sync.Mutex
}
type bdaddr [6]byte
type PlatData struct {
Name string
AddressType uint8
Address [6]byte
Data []byte
Connectable bool
RSSI int8
Conn io.ReadWriteCloser
}
func NewHCI(devID int, chk bool, maxConn int) (*HCI, error) {
d, err := newDevice(devID, chk)
if err != nil {
return nil, err
}
c := cmd.NewCmd(d)
e := evt.NewEvt()
h := &HCI{
d: d,
c: c,
e: e,
plist: make(map[bdaddr]*PlatData),
plistmu: &sync.Mutex{},
bufCnt: make(chan struct{}, 15-1),
bufSize: 27,
maxConn: maxConn,
connsmu: &sync.Mutex{},
conns: map[uint16]*conn{},
advmu: &sync.Mutex{},
}
e.HandleEvent(evt.LEMeta, evt.HandlerFunc(h.handleLEMeta))
e.HandleEvent(evt.DisconnectionComplete, evt.HandlerFunc(h.handleDisconnectionComplete))
e.HandleEvent(evt.NumberOfCompletedPkts, evt.HandlerFunc(h.handleNumberOfCompletedPkts))
e.HandleEvent(evt.CommandComplete, evt.HandlerFunc(c.HandleComplete))
e.HandleEvent(evt.CommandStatus, evt.HandlerFunc(c.HandleStatus))
go h.mainLoop()
h.resetDevice()
return h, nil
}
func (h *HCI) Close() error {
for _, c := range h.conns {
c.Close()
}
return h.d.Close()
}
func (h *HCI) SetAdvertiseEnable(en bool) error {
h.advmu.Lock()
h.adv = en
h.advmu.Unlock()
return h.setAdvertiseEnable(en)
}
func (h *HCI) setAdvertiseEnable(en bool) error {
h.advmu.Lock()
defer h.advmu.Unlock()
if en && h.adv && (len(h.conns) == h.maxConn) {
return nil
}
return h.c.SendAndCheckResp(
cmd.LESetAdvertiseEnable{
AdvertisingEnable: btoi(en),
}, []byte{0x00})
}
func (h *HCI) SendCmdWithAdvOff(c cmd.CmdParam) error {
h.setAdvertiseEnable(false)
err := h.c.SendAndCheckResp(c, nil)
if h.adv {
h.setAdvertiseEnable(true)
}
return err
}
func (h *HCI) SetScanEnable(en bool, dup bool) error {
return h.c.SendAndCheckResp(
cmd.LESetScanEnable{
LEScanEnable: btoi(en),
FilterDuplicates: btoi(!dup),
}, []byte{0x00})
}
func (h *HCI) Connect(pd *PlatData) error {
h.c.Send(
cmd.LECreateConn{
LEScanInterval: 0x0004, // N x 0.625ms
LEScanWindow: 0x0004, // N x 0.625ms
InitiatorFilterPolicy: 0x00, // white list not used
PeerAddressType: pd.AddressType, // public or random
PeerAddress: pd.Address, //
OwnAddressType: 0x00, // public
ConnIntervalMin: 0x0006, // N x 0.125ms
ConnIntervalMax: 0x0006, // N x 0.125ms
ConnLatency: 0x0000, //
SupervisionTimeout: 0x000A, // N x 10ms
MinimumCELength: 0x0000, // N x 0.625ms
MaximumCELength: 0x0000, // N x 0.625ms
})
return nil
}
func (h *HCI) CancelConnection(pd *PlatData) error {
return pd.Conn.Close()
}
func (h *HCI) SendRawCommand(c cmd.CmdParam) ([]byte, error) {
return h.c.Send(c)
}
func btoi(b bool) uint8 {
if b {
return 1
}
return 0
}
func (h *HCI) mainLoop() {
b := make([]byte, 4096)
for {
n, err := h.d.Read(b)
if err != nil {
return
}
if n == 0 {
return
}
p := make([]byte, n)
copy(p, b)
go h.handlePacket(p)
}
}
func (h *HCI) handlePacket(b []byte) {
t, b := packetType(b[0]), b[1:]
var err error
switch t {
case typCommandPkt:
op := uint16(b[0]) | uint16(b[1])<<8
log.Printf("unmanaged cmd: opcode (%04x) [ % X ]\n", op, b)
case typACLDataPkt:
err = h.handleL2CAP(b)
case typSCODataPkt:
err = fmt.Errorf("SCO packet not supported")
case typEventPkt:
err = h.e.Dispatch(b)
case typVendorPkt:
err = fmt.Errorf("Vendor packet not supported")
default:
log.Fatalf("Unknown event: 0x%02X [ % X ]\n", t, b)
}
if err != nil {
log.Printf("hci: %s, [ % X]", err, b)
}
}
func (h *HCI) resetDevice() error {
seq := []cmd.CmdParam{
cmd.Reset{},
cmd.SetEventMask{EventMask: 0x3dbff807fffbffff},
cmd.LESetEventMask{LEEventMask: 0x000000000000001F},
cmd.WriteSimplePairingMode{SimplePairingMode: 1},
cmd.WriteLEHostSupported{LESupportedHost: 1, SimultaneousLEHost: 0},
cmd.WriteInquiryMode{InquiryMode: 2},
cmd.WritePageScanType{PageScanType: 1},
cmd.WriteInquiryScanType{ScanType: 1},
cmd.WriteClassOfDevice{ClassOfDevice: [3]byte{0x40, 0x02, 0x04}},
cmd.WritePageTimeout{PageTimeout: 0x2000},
cmd.WriteDefaultLinkPolicy{DefaultLinkPolicySettings: 0x5},
cmd.HostBufferSize{
HostACLDataPacketLength: 0x1000,
HostSynchronousDataPacketLength: 0xff,
HostTotalNumACLDataPackets: 0x0014,
HostTotalNumSynchronousDataPackets: 0x000a},
cmd.LESetScanParameters{
LEScanType: 0x01, // [0x00]: passive, 0x01: active
LEScanInterval: 0x0010, // [0x10]: 0.625ms * 16
LEScanWindow: 0x0010, // [0x10]: 0.625ms * 16
OwnAddressType: 0x00, // [0x00]: public, 0x01: random
ScanningFilterPolicy: 0x00, // [0x00]: accept all, 0x01: ignore non-white-listed.
},
}
for _, s := range seq {
if err := h.c.SendAndCheckResp(s, []byte{0x00}); err != nil {
return err
}
}
return nil
}
func (h *HCI) handleAdvertisement(b []byte) {
// If no one is interested, don't bother.
if h.AdvertisementHandler == nil {
return
}
ep := &evt.LEAdvertisingReportEP{}
if err := ep.Unmarshal(b); err != nil {
return
}
for i := 0; i < int(ep.NumReports); i++ {
addr := bdaddr(ep.Address[i])
et := ep.EventType[i]
connectable := et == advInd || et == advDirectInd
scannable := et == advInd || et == advScanInd
if et == scanRsp {
h.plistmu.Lock()
pd, ok := h.plist[addr]
h.plistmu.Unlock()
if ok {
pd.Data = append(pd.Data, ep.Data[i]...)
h.AdvertisementHandler(pd)
}
continue
}
pd := &PlatData{
AddressType: ep.AddressType[i],
Address: ep.Address[i],
Data: ep.Data[i],
Connectable: connectable,
RSSI: ep.RSSI[i],
}
h.plistmu.Lock()
h.plist[addr] = pd
h.plistmu.Unlock()
if scannable {
continue
}
h.AdvertisementHandler(pd)
}
}
func (h *HCI) handleNumberOfCompletedPkts(b []byte) error {
ep := &evt.NumberOfCompletedPktsEP{}
if err := ep.Unmarshal(b); err != nil {
return err
}
for _, r := range ep.Packets {
for i := 0; i < int(r.NumOfCompletedPkts); i++ {
<-h.bufCnt
}
}
return nil
}
func (h *HCI) handleConnection(b []byte) {
ep := &evt.LEConnectionCompleteEP{}
if err := ep.Unmarshal(b); err != nil {
return // FIXME
}
hh := ep.ConnectionHandle
c := newConn(h, hh)
h.connsmu.Lock()
h.conns[hh] = c
h.connsmu.Unlock()
h.setAdvertiseEnable(true)
// FIXME: sloppiness. This call should be called by the package user once we
// flesh out the support of l2cap signaling packets (CID:0x0001,0x0005)
if ep.ConnLatency != 0 || ep.ConnInterval > 0x18 {
c.updateConnection()
}
// master connection
if ep.Role == 0x01 {
pd := &PlatData{
Address: ep.PeerAddress,
Conn: c,
}
h.AcceptMasterHandler(pd)
return
}
h.plistmu.Lock()
pd := h.plist[ep.PeerAddress]
h.plistmu.Unlock()
pd.Conn = c
h.AcceptSlaveHandler(pd)
}
func (h *HCI) handleDisconnectionComplete(b []byte) error {
ep := &evt.DisconnectionCompleteEP{}
if err := ep.Unmarshal(b); err != nil {
return err
}
hh := ep.ConnectionHandle
h.connsmu.Lock()
defer h.connsmu.Unlock()
c, found := h.conns[hh]
if !found {
// should not happen, just be cautious for now.
log.Printf("l2conn: disconnecting a disconnected 0x%04X connection", hh)
return nil
}
delete(h.conns, hh)
close(c.aclc)
h.setAdvertiseEnable(true)
return nil
}
func (h *HCI) handleLEMeta(b []byte) error {
code := evt.LEEventCode(b[0])
switch code {
case evt.LEConnectionComplete:
go h.handleConnection(b)
case evt.LEConnectionUpdateComplete:
// anything to do here?
case evt.LEAdvertisingReport:
go h.handleAdvertisement(b)
// case evt.LEReadRemoteUsedFeaturesComplete:
// case evt.LELTKRequest:
// case evt.LERemoteConnectionParameterRequest:
default:
return fmt.Errorf("Unhandled LE event: %s, [ % X ]", code, b)
}
return nil
}
func (h *HCI) handleL2CAP(b []byte) error {
a := &aclData{}
if err := a.unmarshal(b); err != nil {
return err
}
h.connsmu.Lock()
defer h.connsmu.Unlock()
c, found := h.conns[a.attr]
if !found {
// should not happen, just be cautious for now.
log.Printf("l2conn: got data for disconnected handle: 0x%04x", a.attr)
return nil
}
if len(a.b) < 4 {
log.Printf("l2conn: l2cap packet is too short/corrupt, length is %d", len(a.b))
return nil
}
cid := uint16(a.b[2]) | (uint16(a.b[3]) << 8)
if cid == 5 {
c.handleSignal(a)
return nil
}
c.aclc <- a
return nil
}
func (h *HCI) trace(fmt string, v ...interface{}) {
log.Printf(fmt, v)
}