# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Implements raw HID interface on NetBSD."""

from __future__ import absolute_import

import errno
import os
import select
import struct
import sys

from fcntl import ioctl

from pyu2f.hid import base
from pyu2f.hid import linux


# struct usb_ctl_report_desc {
# 	int		ucrd_size;
# 	unsigned char	ucrd_data[1024];
# };

SIZEOF_USB_CTL_REPORT_DESC = 4 + 1024
USB_GET_REPORT_DESC = 0x44045515 # _IOR('U', 21, struct usb_ctl_report_desc)

SIZEOF_USB_DEVICE_INFO = 1268
USB_DEVICE_INFO_PRODUCT = 1168
USB_DEVICE_INFO_VENDOR = 1170
USB_GET_DEVICE_INFO = 0x44f45570 # _IOR('U', 112, struct usb_device_info)

USB_HID_SET_RAW = 0x80046802 # _IOW('h', 2, int)


FIDO_USAGE_PAGE = 0xf1d0
FIDO_USAGE_U2FHID = 0x01


class NetBSDHidDevice(linux.LinuxHidDevice):
    """Implementation of HID device for NetBSD.
    """

    @classmethod
    def _setup(cls, fd, path):
        devinfo = bytearray([0] * SIZEOF_USB_DEVICE_INFO)
        ioctl(fd, USB_GET_DEVICE_INFO, devinfo, True)
        vendor = devinfo[USB_DEVICE_INFO_VENDOR:USB_DEVICE_INFO_VENDOR + 2]
        product = devinfo[USB_DEVICE_INFO_PRODUCT:USB_DEVICE_INFO_PRODUCT + 2]
        descbuf = bytearray([0] * SIZEOF_USB_CTL_REPORT_DESC)
        ioctl(fd, USB_GET_REPORT_DESC, descbuf, True)
        descsize = struct.unpack('@i', descbuf[0:4])[0]
        descdata = descbuf[4:4 + descsize]
        desc = base.DeviceDescriptor()
        desc.path = path
        desc.vendor_id = struct.unpack('@H', vendor)[0]
        desc.product_id = struct.unpack('@H', product)[0]
        linux.ParseReportDescriptor(descdata, desc)
        if desc.usage_page != FIDO_USAGE_PAGE:
            raise Exception('usage page != fido')
        if desc.usage != FIDO_USAGE_U2FHID:
            raise Exception('fido usage != u2fhid')
        ioctl(fd, USB_HID_SET_RAW, struct.pack('@i', 1))
        ping = bytearray([0] * 64)
        ping[0:7] = bytearray([0xff,0xff,0xff,0xff,0x81,0,1])
        for i in range(10):
            os.write(fd, ping)
            poll = select.poll()
            poll.register(fd, select.POLLIN)
            if poll.poll(100):
                os.read(fd, 64 + 1)
                break
        else:
            raise Exception('u2f ping timeout')
        return desc

    @classmethod
    def Enumerate(cls):
        for i in range(100):
            path = '/dev/uhid{}'.format(i)
            fd = None
            try:
                fd = os.open(path, os.O_RDWR|os.O_CLOEXEC)
                desc = cls._setup(fd, path)
            except OSError as e:
                if e.errno == errno.ENOENT:
                    break
                continue
            finally:
                if fd is not None:
                    os.close(fd)
            yield desc.ToPublicDict()

    def __init__(self, path):
        base.HidDevice.__init__(self, path)
        self.dev = os.open(path, os.O_RDWR)
        self.desc = self._setup(self.dev, path)

    def __del__(self):
        os.close(self.dev)

    def Write(self, packet):
        os.write(self.dev, bytearray(packet))
