// Package ble provides BLE scanning via raw HCI sockets package ble import ( "encoding/binary" "fmt" "syscall" "unsafe" ) // HCI constants const ( AF_BLUETOOTH = 31 BTPROTO_HCI = 1 SOL_HCI = 0 HCI_FILTER = 2 // HCI packet types HCI_COMMAND_PKT = 0x01 HCI_EVENT_PKT = 0x04 // HCI events EVT_LE_META = 0x3E EVT_CMD_COMPLETE = 0x0E // LE Meta sub-events EVT_LE_ADVERTISING_REPORT = 0x02 // OGF (Opcode Group Field) OGF_LE_CTL = 0x08 // OCF (Opcode Command Field) OCF_LE_SET_SCAN_PARAMETERS = 0x000B OCF_LE_SET_SCAN_ENABLE = 0x000C // Scan types SCAN_TYPE_PASSIVE = 0x00 SCAN_TYPE_ACTIVE = 0x01 ) // sockaddrHCI is the HCI socket address type sockaddrHCI struct { Family uint16 Dev uint16 Channel uint16 } // hciFilter is the HCI event filter type hciFilter struct { TypeMask uint32 EventMask [2]uint32 Opcode uint16 } // HCISocket represents a raw HCI socket type HCISocket struct { fd int devID int } // NewHCISocket creates a new HCI socket for the given device func NewHCISocket(devID int) (*HCISocket, error) { fd, err := syscall.Socket(AF_BLUETOOTH, syscall.SOCK_RAW|syscall.SOCK_CLOEXEC, BTPROTO_HCI) if err != nil { return nil, fmt.Errorf("socket: %w", err) } // Bind to device addr := sockaddrHCI{ Family: AF_BLUETOOTH, Dev: uint16(devID), Channel: 0, // HCI_CHANNEL_RAW } _, _, errno := syscall.Syscall( syscall.SYS_BIND, uintptr(fd), uintptr(unsafe.Pointer(&addr)), unsafe.Sizeof(addr), ) if errno != 0 { syscall.Close(fd) return nil, fmt.Errorf("bind: %v", errno) } return &HCISocket{fd: fd, devID: devID}, nil } // SetEventFilter sets the HCI event filter to receive LE Meta events func (h *HCISocket) SetEventFilter() error { var filter hciFilter // Enable HCI_EVENT_PKT filter.TypeMask = 1 << HCI_EVENT_PKT // Enable EVT_LE_META (0x3E = 62) and EVT_CMD_COMPLETE (0x0E = 14) events // EventMask is split: [0] = events 0-31, [1] = events 32-63 filter.EventMask[0] = 1 << EVT_CMD_COMPLETE // bit 14 filter.EventMask[1] = 1 << (EVT_LE_META - 32) // bit 30 in second word (62-32=30) _, _, errno := syscall.Syscall6( syscall.SYS_SETSOCKOPT, uintptr(h.fd), SOL_HCI, HCI_FILTER, uintptr(unsafe.Pointer(&filter)), unsafe.Sizeof(filter), 0, ) if errno != 0 { return fmt.Errorf("setsockopt HCI_FILTER: %v", errno) } return nil } // SendCommand sends an HCI command func (h *HCISocket) SendCommand(ogf, ocf uint16, params []byte) error { opcode := uint16(ocf) | (uint16(ogf) << 10) buf := make([]byte, 4+len(params)) buf[0] = HCI_COMMAND_PKT binary.LittleEndian.PutUint16(buf[1:3], opcode) buf[3] = byte(len(params)) copy(buf[4:], params) _, err := syscall.Write(h.fd, buf) return err } // SetScanParameters sets LE scan parameters func (h *HCISocket) SetScanParameters(scanType byte, interval, window uint16) error { params := make([]byte, 7) params[0] = scanType binary.LittleEndian.PutUint16(params[1:3], interval) // interval (units of 0.625ms) binary.LittleEndian.PutUint16(params[3:5], window) // window (units of 0.625ms) params[5] = 0x00 // own address type: public params[6] = 0x00 // filter policy: accept all return h.SendCommand(OGF_LE_CTL, OCF_LE_SET_SCAN_PARAMETERS, params) } // SetScanEnable enables or disables LE scanning func (h *HCISocket) SetScanEnable(enable bool, filterDuplicates bool) error { params := make([]byte, 2) if enable { params[0] = 0x01 } if filterDuplicates { params[1] = 0x01 } return h.SendCommand(OGF_LE_CTL, OCF_LE_SET_SCAN_ENABLE, params) } // Read reads an HCI event packet func (h *HCISocket) Read(buf []byte) (int, error) { return syscall.Read(h.fd, buf) } // Close closes the HCI socket func (h *HCISocket) Close() error { return syscall.Close(h.fd) } // Fd returns the file descriptor func (h *HCISocket) Fd() int { return h.fd }