| // +build linux |
| |
| package socket |
| |
| import ( |
| "fmt" |
| "io" |
| "sync" |
| "unsafe" |
| |
| "github.com/pkg/errors" |
| "golang.org/x/sys/unix" |
| ) |
| |
| func ioR(t, nr, size uintptr) uintptr { |
| return (2 << 30) | (t << 8) | nr | (size << 16) |
| } |
| |
| func ioW(t, nr, size uintptr) uintptr { |
| return (1 << 30) | (t << 8) | nr | (size << 16) |
| } |
| |
| func ioctl(fd, op, arg uintptr) error { |
| if _, _, ep := unix.Syscall(unix.SYS_IOCTL, fd, op, arg); ep != 0 { |
| return ep |
| } |
| return nil |
| } |
| |
| const ( |
| ioctlSize = 4 |
| hciMaxDevices = 16 |
| typHCI = 72 // 'H' |
| ) |
| |
| var ( |
| hciUpDevice = ioW(typHCI, 201, ioctlSize) // HCIDEVUP |
| hciDownDevice = ioW(typHCI, 202, ioctlSize) // HCIDEVDOWN |
| hciResetDevice = ioW(typHCI, 203, ioctlSize) // HCIDEVRESET |
| hciGetDeviceList = ioR(typHCI, 210, ioctlSize) // HCIGETDEVLIST |
| hciGetDeviceInfo = ioR(typHCI, 211, ioctlSize) // HCIGETDEVINFO |
| ) |
| |
| type devListRequest struct { |
| devNum uint16 |
| devRequest [hciMaxDevices]struct { |
| id uint16 |
| opt uint32 |
| } |
| } |
| |
| // Socket implements a HCI User Channel as ReadWriteCloser. |
| type Socket struct { |
| fd int |
| closed chan struct{} |
| rmu sync.Mutex |
| wmu sync.Mutex |
| } |
| |
| // NewSocket returns a HCI User Channel of specified device id. |
| // If id is -1, the first available HCI device is returned. |
| func NewSocket(id int) (*Socket, error) { |
| var err error |
| // Create RAW HCI Socket. |
| fd, err := unix.Socket(unix.AF_BLUETOOTH, unix.SOCK_RAW, unix.BTPROTO_HCI) |
| if err != nil { |
| return nil, errors.Wrap(err, "can't create socket") |
| } |
| |
| if id != -1 { |
| return open(fd, id) |
| } |
| |
| req := devListRequest{devNum: hciMaxDevices} |
| if err = ioctl(uintptr(fd), hciGetDeviceList, uintptr(unsafe.Pointer(&req))); err != nil { |
| return nil, errors.Wrap(err, "can't get device list") |
| } |
| var msg string |
| for id := 0; id < int(req.devNum); id++ { |
| s, err := open(fd, id) |
| if err == nil { |
| return s, nil |
| } |
| msg = msg + fmt.Sprintf("(hci%d: %s)", id, err) |
| } |
| return nil, errors.Errorf("no devices available: %s", msg) |
| } |
| |
| func open(fd, id int) (*Socket, error) { |
| // Reset the device in case previous session didn't cleanup properly. |
| if err := ioctl(uintptr(fd), hciDownDevice, uintptr(id)); err != nil { |
| return nil, errors.Wrap(err, "can't down device") |
| } |
| if err := ioctl(uintptr(fd), hciUpDevice, uintptr(id)); err != nil { |
| return nil, errors.Wrap(err, "can't up device") |
| } |
| |
| // HCI User Channel requires exclusive access to the device. |
| // The device has to be down at the time of binding. |
| if err := ioctl(uintptr(fd), hciDownDevice, uintptr(id)); err != nil { |
| return nil, errors.Wrap(err, "can't down device") |
| } |
| |
| // Bind the RAW socket to HCI User Channel |
| sa := unix.SockaddrHCI{Dev: uint16(id), Channel: unix.HCI_CHANNEL_USER} |
| if err := unix.Bind(fd, &sa); err != nil { |
| return nil, errors.Wrap(err, "can't bind socket to hci user channel") |
| } |
| |
| // poll for 20ms to see if any data becomes available, then clear it |
| pfds := []unix.PollFd{{Fd: int32(fd), Events: unix.POLLIN}} |
| unix.Poll(pfds, 20) |
| if pfds[0].Revents&unix.POLLIN > 0 { |
| b := make([]byte, 100) |
| unix.Read(fd, b) |
| } |
| |
| return &Socket{fd: fd, closed: make(chan struct{})}, nil |
| } |
| |
| func (s *Socket) Read(p []byte) (int, error) { |
| select { |
| case <-s.closed: |
| return 0, io.EOF |
| default: |
| } |
| s.rmu.Lock() |
| defer s.rmu.Unlock() |
| n, err := unix.Read(s.fd, p) |
| return n, errors.Wrap(err, "can't read hci socket") |
| } |
| |
| func (s *Socket) Write(p []byte) (int, error) { |
| s.wmu.Lock() |
| defer s.wmu.Unlock() |
| n, err := unix.Write(s.fd, p) |
| return n, errors.Wrap(err, "can't write hci socket") |
| } |
| |
| func (s *Socket) Close() error { |
| close(s.closed) |
| s.Write([]byte{0x01, 0x09, 0x10, 0x00}) |
| s.rmu.Lock() |
| defer s.rmu.Unlock() |
| return errors.Wrap(unix.Close(s.fd), "can't close hci socket") |
| } |