Bug 1468349 - Web Authentication - Add FreeBSD Support
☠☠ backed out by a6c7994970b7 ☠ ☠
authorGreg V <greg@unrelenting.technology>
Tue, 12 Jun 2018 16:59:10 +0000
changeset 476564 80190d88549cf49b79fea1d350e65d8828f33810
parent 476563 69ffa8eab9d9a58a4916b6ce3dae1e70ea2ead28
child 476565 627722819abbb5d7c74fa97bf42f98c9924d10ef
push id9374
push userjlund@mozilla.com
push dateMon, 18 Jun 2018 21:43:20 +0000
treeherdermozilla-beta@160e085dfb0b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1468349
milestone62.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1468349 - Web Authentication - Add FreeBSD Support Upstream PR: https://github.com/jcjones/u2f-hid-rs/pull/62 * Extract hidproto module from linux::hidraw Make the protocol parts independent of Linux code, in preparation for adding FreeBSD support. * Add FreeBSD (uhid + devd) support Tested with a YubiKey 4. Differential Revision: https://phabricator.services.mozilla.com/D1636
dom/webauthn/u2f-hid-rs/Cargo.toml
dom/webauthn/u2f-hid-rs/README.md
dom/webauthn/u2f-hid-rs/src/freebsd/device.rs
dom/webauthn/u2f-hid-rs/src/freebsd/mod.rs
dom/webauthn/u2f-hid-rs/src/freebsd/monitor.rs
dom/webauthn/u2f-hid-rs/src/freebsd/transaction.rs
dom/webauthn/u2f-hid-rs/src/freebsd/uhid.rs
dom/webauthn/u2f-hid-rs/src/hidproto.rs
dom/webauthn/u2f-hid-rs/src/lib.rs
dom/webauthn/u2f-hid-rs/src/linux/hidraw.rs
dom/webauthn/u2f-hid-rs/src/macos/iokit.rs
dom/webauthn/u2f-hid-rs/src/macos/monitor.rs
dom/webauthn/u2f-hid-rs/src/macos/transaction.rs
dom/webauthn/u2f-hid-rs/src/statemachine.rs
dom/webauthn/u2f-hid-rs/src/util.rs
--- a/dom/webauthn/u2f-hid-rs/Cargo.toml
+++ b/dom/webauthn/u2f-hid-rs/Cargo.toml
@@ -1,16 +1,19 @@
 [package]
 name = "u2fhid"
-version = "0.1.0"
+version = "0.2.0"
 authors = ["Kyle Machulis <kyle@nonpolynomial.com>", "J.C. Jones <jc@mozilla.com>", "Tim Taubert <ttaubert@mozilla.com>"]
 
 [target.'cfg(target_os = "linux")'.dependencies]
 libudev = "^0.2"
 
+[target.'cfg(target_os = "freebsd")'.dependencies]
+devd-rs = "0.2.1"
+
 [target.'cfg(target_os = "macos")'.dependencies]
 core-foundation-sys = "0.6.0"
 core-foundation = "0.6.0"
 
 [target.'cfg(target_os = "windows")'.dependencies]
 winapi = "0.2.8"
 
 [dependencies]
--- a/dom/webauthn/u2f-hid-rs/README.md
+++ b/dom/webauthn/u2f-hid-rs/README.md
@@ -1,16 +1,16 @@
 # A Rust HID library for interacting with U2F Security Keys
 
 [![Build Status](https://travis-ci.org/jcjones/u2f-hid-rs.svg?branch=master)](https://travis-ci.org/jcjones/u2f-hid-rs)
 ![Maturity Level](https://img.shields.io/badge/maturity-beta-yellow.svg)
 
 This is a cross-platform library for interacting with U2F Security Key-type devices via Rust.
 
-* **Supported Platforms**: Windows, Linux, and Mac OS X.
+* **Supported Platforms**: Windows, Linux, FreeBSD, and macOS.
 * **Supported HID Transports**: USB.
 * **Supported Protocols**: [FIDO U2F over USB](https://fidoalliance.org/specs/fido-u2f-v1.1-id-20160915/fido-u2f-raw-message-formats-v1.1-id-20160915.html).
 
 This library currently focuses on U2F security keys, but is expected to be extended to
 support additional protocols and transports.
 
 ## Usage
 
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/freebsd/device.rs
@@ -0,0 +1,88 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+extern crate libc;
+
+use std::ffi::{CString, OsString};
+use std::io;
+use std::io::{Read, Write};
+use std::os::unix::prelude::*;
+
+use consts::CID_BROADCAST;
+use platform::uhid;
+use u2ftypes::U2FDevice;
+use util::from_unix_result;
+
+#[derive(Debug)]
+pub struct Device {
+    path: OsString,
+    fd: libc::c_int,
+    cid: [u8; 4],
+}
+
+impl Device {
+    pub fn new(path: OsString) -> io::Result<Self> {
+        let cstr = CString::new(path.as_bytes())?;
+        let fd = unsafe { libc::open(cstr.as_ptr(), libc::O_RDWR) };
+        let fd = from_unix_result(fd)?;
+        Ok(Self {
+            path,
+            fd,
+            cid: CID_BROADCAST,
+        })
+    }
+
+    pub fn is_u2f(&self) -> bool {
+        uhid::is_u2f_device(self.fd)
+    }
+}
+
+impl Drop for Device {
+    fn drop(&mut self) {
+        // Close the fd, ignore any errors.
+        let _ = unsafe { libc::close(self.fd) };
+    }
+}
+
+impl PartialEq for Device {
+    fn eq(&self, other: &Device) -> bool {
+        self.path == other.path
+    }
+}
+
+impl Read for Device {
+    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+        let bufp = buf.as_mut_ptr() as *mut libc::c_void;
+        let rv = unsafe { libc::read(self.fd, bufp, buf.len()) };
+        from_unix_result(rv as usize)
+    }
+}
+
+impl Write for Device {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        let report_id = buf[0] as i64;
+        // Skip report number when not using numbered reports.
+        let start = if report_id == 0x0 { 1 } else { 0 };
+        let data = &buf[start..];
+
+        let data_ptr = data.as_ptr() as *const libc::c_void;
+        let rv = unsafe { libc::write(self.fd, data_ptr, data.len()) };
+        from_unix_result(rv as usize + 1)
+    }
+
+    // USB HID writes don't buffer, so this will be a nop.
+    fn flush(&mut self) -> io::Result<()> {
+        Ok(())
+    }
+}
+
+impl U2FDevice for Device {
+    fn get_cid<'a>(&'a self) -> &'a [u8; 4] {
+        &self.cid
+    }
+
+    fn set_cid(&mut self, cid: [u8; 4]) {
+        self.cid = cid;
+    }
+}
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/freebsd/mod.rs
@@ -0,0 +1,9 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub mod device;
+pub mod transaction;
+
+mod monitor;
+mod uhid;
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/freebsd/monitor.rs
@@ -0,0 +1,139 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use devd_rs;
+use std::collections::HashMap;
+use std::ffi::OsString;
+use std::sync::Arc;
+use std::{fs, io};
+
+use runloop::RunLoop;
+
+const POLL_TIMEOUT: usize = 100;
+
+pub enum Event {
+    Add(OsString),
+    Remove(OsString),
+}
+
+impl Event {
+    fn from_devd(event: devd_rs::Event) -> Option<Self> {
+        match event {
+            devd_rs::Event::Attach {
+                ref dev,
+                parent: _,
+                location: _,
+            } if dev.starts_with("uhid") =>
+            {
+                Some(Event::Add(("/dev/".to_owned() + dev).into()))
+            }
+            devd_rs::Event::Detach {
+                ref dev,
+                parent: _,
+                location: _,
+            } if dev.starts_with("uhid") =>
+            {
+                Some(Event::Remove(("/dev/".to_owned() + dev).into()))
+            }
+            _ => None,
+        }
+    }
+}
+
+fn convert_error(e: devd_rs::Error) -> io::Error {
+    e.into()
+}
+
+pub struct Monitor<F>
+where
+    F: Fn(OsString, &Fn() -> bool) + Sync,
+{
+    runloops: HashMap<OsString, RunLoop>,
+    new_device_cb: Arc<F>,
+}
+
+impl<F> Monitor<F>
+where
+    F: Fn(OsString, &Fn() -> bool) + Send + Sync + 'static,
+{
+    pub fn new(new_device_cb: F) -> Self {
+        Self {
+            runloops: HashMap::new(),
+            new_device_cb: Arc::new(new_device_cb),
+        }
+    }
+
+    pub fn run(&mut self, alive: &Fn() -> bool) -> io::Result<()> {
+        let mut ctx = devd_rs::Context::new().map_err(convert_error)?;
+
+        // Iterate all existing devices.
+        for dev in fs::read_dir("/dev")? {
+            if let Ok(dev) = dev {
+                let filename_ = dev.file_name();
+                let filename = filename_.to_str().unwrap_or("");
+                if filename.starts_with("uhid") {
+                    self.add_device(("/dev/".to_owned() + filename).into());
+                }
+            }
+        }
+
+        // Loop until we're stopped by the controlling thread, or fail.
+        while alive() {
+            // Wait for new events, break on failure.
+            match ctx.wait_for_event(POLL_TIMEOUT) {
+                Err(devd_rs::Error::Timeout) => (),
+                Err(e) => return Err(e.into()),
+                Ok(event) => {
+                    if let Some(event) = Event::from_devd(event) {
+                        self.process_event(event);
+                    }
+                }
+            }
+        }
+
+        // Remove all tracked devices.
+        self.remove_all_devices();
+
+        Ok(())
+    }
+
+    fn process_event(&mut self, event: Event) {
+        match event {
+            Event::Add(path) => {
+                self.add_device(path);
+            }
+            Event::Remove(path) => {
+                self.remove_device(path);
+            }
+        }
+    }
+
+    fn add_device(&mut self, path: OsString) {
+        let f = self.new_device_cb.clone();
+        let key = path.clone();
+
+        let runloop = RunLoop::new(move |alive| {
+            if alive() {
+                f(path, alive);
+            }
+        });
+
+        if let Ok(runloop) = runloop {
+            self.runloops.insert(key, runloop);
+        }
+    }
+
+    fn remove_device(&mut self, path: OsString) {
+        if let Some(runloop) = self.runloops.remove(&path) {
+            runloop.cancel();
+        }
+    }
+
+    fn remove_all_devices(&mut self) {
+        while !self.runloops.is_empty() {
+            let path = self.runloops.keys().next().unwrap().clone();
+            self.remove_device(path);
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/freebsd/transaction.rs
@@ -0,0 +1,48 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use platform::monitor::Monitor;
+use runloop::RunLoop;
+use std::ffi::OsString;
+use util::OnceCallback;
+
+pub struct Transaction {
+    // Handle to the thread loop.
+    thread: Option<RunLoop>,
+}
+
+impl Transaction {
+    pub fn new<F, T>(
+        timeout: u64,
+        callback: OnceCallback<T>,
+        new_device_cb: F,
+    ) -> Result<Self, ::Error>
+    where
+        F: Fn(OsString, &Fn() -> bool) + Sync + Send + 'static,
+        T: 'static,
+    {
+        let thread = RunLoop::new_with_timeout(
+            move |alive| {
+                // Create a new device monitor.
+                let mut monitor = Monitor::new(new_device_cb);
+
+                // Start polling for new devices.
+                try_or!(monitor.run(alive), |_| callback.call(Err(::Error::Unknown)));
+
+                // Send an error, if the callback wasn't called already.
+                callback.call(Err(::Error::NotAllowed));
+            },
+            timeout,
+        ).map_err(|_| ::Error::Unknown)?;
+
+        Ok(Self {
+            thread: Some(thread),
+        })
+    }
+
+    pub fn cancel(&mut self) {
+        // This must never be None.
+        self.thread.take().unwrap().cancel();
+    }
+}
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/freebsd/uhid.rs
@@ -0,0 +1,92 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+extern crate libc;
+
+use std::io;
+use std::os::unix::io::RawFd;
+use std::ptr;
+
+use hidproto::*;
+use util::from_unix_result;
+
+#[allow(non_camel_case_types)]
+#[repr(C)]
+#[derive(Debug)]
+pub struct GenDescriptor {
+    ugd_data: *mut u8,
+    ugd_lang_id: u16,
+    ugd_maxlen: u16,
+    ugd_actlen: u16,
+    ugd_offset: u16,
+    ugd_config_index: u8,
+    ugd_string_index: u8,
+    ugd_iface_index: u8,
+    ugd_altif_index: u8,
+    ugd_endpt_index: u8,
+    ugd_report_index: u8,
+    reserved: [u8; 16],
+}
+
+impl Default for GenDescriptor {
+    fn default() -> GenDescriptor {
+        GenDescriptor {
+            ugd_data: ptr::null_mut(),
+            ugd_lang_id: 0,
+            ugd_maxlen: 65535,
+            ugd_actlen: 0,
+            ugd_offset: 0,
+            ugd_config_index: 0,
+            ugd_string_index: 0,
+            ugd_iface_index: 0,
+            ugd_altif_index: 0,
+            ugd_endpt_index: 0,
+            ugd_report_index: 0,
+            reserved: [0; 16],
+        }
+    }
+}
+
+const IOWR: u32 = 0x40000000 | 0x80000000;
+
+const IOCPARM_SHIFT: u32 = 13;
+const IOCPARM_MASK: u32 = ((1 << IOCPARM_SHIFT) - 1);
+
+const TYPESHIFT: u32 = 8;
+const SIZESHIFT: u32 = 16;
+
+macro_rules! ioctl {
+    ($dir:expr, $name:ident, $ioty:expr, $nr:expr, $size:expr; $ty:ty) => {
+        pub unsafe fn $name(fd: libc::c_int, val: *mut $ty) -> io::Result<libc::c_int> {
+            let ioc = ($dir as u32)
+                | (($size as u32 & IOCPARM_MASK) << SIZESHIFT)
+                | (($ioty as u32) << TYPESHIFT)
+                | ($nr as u32);
+            from_unix_result(libc::ioctl(fd, ioc as libc::c_ulong, val))
+        }
+    };
+}
+
+// https://github.com/freebsd/freebsd/blob/master/sys/dev/usb/usb_ioctl.h
+ioctl!(IOWR, usb_get_report_desc, b'U', 21, 32; /*struct*/ GenDescriptor);
+
+fn read_report_descriptor(fd: RawFd) -> io::Result<ReportDescriptor> {
+    let mut desc = GenDescriptor::default();
+    let _ = unsafe { usb_get_report_desc(fd, &mut desc)? };
+    desc.ugd_maxlen = desc.ugd_actlen;
+    let mut value = Vec::with_capacity(desc.ugd_actlen as usize);
+    unsafe {
+        value.set_len(desc.ugd_actlen as usize);
+    }
+    desc.ugd_data = value.as_mut_ptr();
+    let _ = unsafe { usb_get_report_desc(fd, &mut desc)? };
+    Ok(ReportDescriptor { value })
+}
+
+pub fn is_u2f_device(fd: RawFd) -> bool {
+    match read_report_descriptor(fd) {
+        Ok(desc) => has_fido_usage(desc),
+        Err(_) => false, // Upon failure, just say it's not a U2F device.
+    }
+}
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/hidproto.rs
@@ -0,0 +1,158 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Shared code for platforms that use raw HID access (Linux, FreeBSD, etc.)
+
+use std::mem;
+
+use consts::{FIDO_USAGE_U2FHID, FIDO_USAGE_PAGE};
+
+// The 4 MSBs (the tag) are set when it's a long item.
+const HID_MASK_LONG_ITEM_TAG: u8 = 0b11110000;
+// The 2 LSBs denote the size of a short item.
+const HID_MASK_SHORT_ITEM_SIZE: u8 = 0b00000011;
+// The 6 MSBs denote the tag (4) and type (2).
+const HID_MASK_ITEM_TAGTYPE: u8 = 0b11111100;
+// tag=0000, type=10 (local)
+const HID_ITEM_TAGTYPE_USAGE: u8 = 0b00001000;
+// tag=0000, type=01 (global)
+const HID_ITEM_TAGTYPE_USAGE_PAGE: u8 = 0b00000100;
+
+pub struct ReportDescriptor {
+    pub value: Vec<u8>,
+}
+
+impl ReportDescriptor {
+    fn iter(self) -> ReportDescriptorIterator {
+        ReportDescriptorIterator::new(self)
+    }
+}
+
+#[derive(Debug)]
+pub enum Data {
+    UsagePage { data: u32 },
+    Usage { data: u32 },
+}
+
+pub struct ReportDescriptorIterator {
+    desc: ReportDescriptor,
+    pos: usize,
+}
+
+impl ReportDescriptorIterator {
+    fn new(desc: ReportDescriptor) -> Self {
+        Self { desc, pos: 0 }
+    }
+
+    fn next_item(&mut self) -> Option<Data> {
+        let item = get_hid_item(&self.desc.value[self.pos..]);
+        if item.is_none() {
+            self.pos = self.desc.value.len(); // Close, invalid data.
+            return None;
+        }
+
+        let (tag_type, key_len, data) = item.unwrap();
+
+        // Advance if we have a valid item.
+        self.pos += key_len + data.len();
+
+        // We only check short items.
+        if key_len > 1 {
+            return None; // Check next item.
+        }
+
+        // Short items have max. length of 4 bytes.
+        assert!(data.len() <= mem::size_of::<u32>());
+
+        // Convert data bytes to a uint.
+        let data = read_uint_le(data);
+
+        match tag_type {
+            HID_ITEM_TAGTYPE_USAGE_PAGE => Some(Data::UsagePage { data }),
+            HID_ITEM_TAGTYPE_USAGE => Some(Data::Usage { data }),
+            _ => None,
+        }
+    }
+}
+
+impl Iterator for ReportDescriptorIterator {
+    type Item = Data;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.pos >= self.desc.value.len() {
+            return None;
+        }
+
+        self.next_item().or_else(|| self.next())
+    }
+}
+
+fn get_hid_item<'a>(buf: &'a [u8]) -> Option<(u8, usize, &'a [u8])> {
+    if (buf[0] & HID_MASK_LONG_ITEM_TAG) == HID_MASK_LONG_ITEM_TAG {
+        get_hid_long_item(buf)
+    } else {
+        get_hid_short_item(buf)
+    }
+}
+
+fn get_hid_long_item<'a>(buf: &'a [u8]) -> Option<(u8, usize, &'a [u8])> {
+    // A valid long item has at least three bytes.
+    if buf.len() < 3 {
+        return None;
+    }
+
+    let len = buf[1] as usize;
+
+    // Ensure that there are enough bytes left in the buffer.
+    if len > buf.len() - 3 {
+        return None;
+    }
+
+    Some((buf[2], 3 /* key length */, &buf[3..]))
+}
+
+fn get_hid_short_item<'a>(buf: &'a [u8]) -> Option<(u8, usize, &'a [u8])> {
+    // This is a short item. The bottom two bits of the key
+    // contain the length of the data section (value) for this key.
+    let len = match buf[0] & HID_MASK_SHORT_ITEM_SIZE {
+        s @ 0...2 => s as usize,
+        _ => 4, /* _ == 3 */
+    };
+
+    // Ensure that there are enough bytes left in the buffer.
+    if len > buf.len() - 1 {
+        return None;
+    }
+
+    Some((
+        buf[0] & HID_MASK_ITEM_TAGTYPE,
+        1, /* key length */
+        &buf[1..1 + len],
+    ))
+}
+
+fn read_uint_le(buf: &[u8]) -> u32 {
+    assert!(buf.len() <= 4);
+    // Parse the number in little endian byte order.
+    buf.iter().rev().fold(0, |num, b| (num << 8) | (*b as u32))
+}
+
+pub fn has_fido_usage(desc: ReportDescriptor) -> bool {
+    let mut usage_page = None;
+    let mut usage = None;
+
+    for data in desc.iter() {
+        match data {
+            Data::UsagePage { data } => usage_page = Some(data),
+            Data::Usage { data } => usage = Some(data),
+        }
+
+        // Check the values we found.
+        if let (Some(usage_page), Some(usage)) = (usage_page, usage) {
+            return usage_page == FIDO_USAGE_PAGE as u32 && usage == FIDO_USAGE_U2FHID as u32;
+        }
+    }
+
+    false
+}
--- a/dom/webauthn/u2f-hid-rs/src/lib.rs
+++ b/dom/webauthn/u2f-hid-rs/src/lib.rs
@@ -1,37 +1,51 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #[macro_use]
 mod util;
 
+#[cfg(any(target_os = "linux", target_os = "freebsd"))]
+pub mod hidproto;
+
 #[cfg(any(target_os = "linux"))]
 extern crate libudev;
 
 #[cfg(any(target_os = "linux"))]
 #[path = "linux/mod.rs"]
 pub mod platform;
 
+#[cfg(any(target_os = "freebsd"))]
+extern crate devd_rs;
+
+#[cfg(any(target_os = "freebsd"))]
+#[path = "freebsd/mod.rs"]
+pub mod platform;
+
 #[cfg(any(target_os = "macos"))]
 extern crate core_foundation_sys;
 
 #[cfg(any(target_os = "macos"))]
 extern crate core_foundation;
 
 #[cfg(any(target_os = "macos"))]
 #[path = "macos/mod.rs"]
 pub mod platform;
 
 #[cfg(any(target_os = "windows"))]
 #[path = "windows/mod.rs"]
 pub mod platform;
 
-#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
+#[cfg(
+    not(
+        any(target_os = "linux", target_os = "freebsd", target_os = "macos", target_os = "windows")
+    )
+)]
 #[path = "stub/mod.rs"]
 pub mod platform;
 
 extern crate boxfnonce;
 extern crate libc;
 #[macro_use]
 extern crate log;
 extern crate rand;
--- a/dom/webauthn/u2f-hid-rs/src/linux/hidraw.rs
+++ b/dom/webauthn/u2f-hid-rs/src/linux/hidraw.rs
@@ -3,218 +3,78 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 extern crate libc;
 
 use std::io;
 use std::mem;
 use std::os::unix::io::RawFd;
 
-use consts::{FIDO_USAGE_U2FHID, FIDO_USAGE_PAGE};
+use hidproto::*;
 use util::{from_unix_result, io_err};
 
 #[allow(non_camel_case_types)]
 #[repr(C)]
-pub struct ReportDescriptor {
+pub struct LinuxReportDescriptor {
     size: ::libc::c_int,
     value: [u8; 4096],
 }
 
-impl ReportDescriptor {
-    fn iter(self) -> ReportDescriptorIterator {
-        ReportDescriptorIterator::new(self)
-    }
-}
-
 const NRBITS: u32 = 8;
 const TYPEBITS: u32 = 8;
 
 const READ: u8 = 2;
 const SIZEBITS: u8 = 14;
 
 const NRSHIFT: u32 = 0;
 const TYPESHIFT: u32 = NRSHIFT + NRBITS as u32;
 const SIZESHIFT: u32 = TYPESHIFT + TYPEBITS as u32;
 const DIRSHIFT: u32 = SIZESHIFT + SIZEBITS as u32;
 
-// The 4 MSBs (the tag) are set when it's a long item.
-const HID_MASK_LONG_ITEM_TAG: u8 = 0b11110000;
-// The 2 LSBs denote the size of a short item.
-const HID_MASK_SHORT_ITEM_SIZE: u8 = 0b00000011;
-// The 6 MSBs denote the tag (4) and type (2).
-const HID_MASK_ITEM_TAGTYPE: u8 = 0b11111100;
-// tag=0000, type=10 (local)
-const HID_ITEM_TAGTYPE_USAGE: u8 = 0b00001000;
-// tag=0000, type=01 (global)
-const HID_ITEM_TAGTYPE_USAGE_PAGE: u8 = 0b00000100;
-
 // https://github.com/torvalds/linux/blob/master/include/uapi/linux/hid.h
 const HID_MAX_DESCRIPTOR_SIZE: usize = 4096;
 
 macro_rules! ioctl {
     ($dir:expr, $name:ident, $ioty:expr, $nr:expr; $ty:ty) => {
         pub unsafe fn $name(fd: libc::c_int, val: *mut $ty) -> io::Result<libc::c_int> {
             let size = mem::size_of::<$ty>();
-            let ioc = (($dir as u32) << DIRSHIFT) | (($ioty as u32) << TYPESHIFT)
-                | (($nr as u32) << NRSHIFT) | ((size as u32) << SIZESHIFT);
+            let ioc = (($dir as u32) << DIRSHIFT)
+                | (($ioty as u32) << TYPESHIFT)
+                | (($nr as u32) << NRSHIFT)
+                | ((size as u32) << SIZESHIFT);
 
             #[cfg(not(target_env = "musl"))]
             type IocType = libc::c_ulong;
             #[cfg(target_env = "musl")]
             type IocType = libc::c_int;
 
             from_unix_result(libc::ioctl(fd, ioc as IocType, val))
         }
     };
 }
 
 // https://github.com/torvalds/linux/blob/master/include/uapi/linux/hidraw.h
 ioctl!(READ, hidiocgrdescsize, b'H', 0x01; ::libc::c_int);
-ioctl!(READ, hidiocgrdesc, b'H', 0x02; /*struct*/ ReportDescriptor);
-
-enum Data {
-    UsagePage { data: u32 },
-    Usage { data: u32 },
-}
-
-struct ReportDescriptorIterator {
-    desc: ReportDescriptor,
-    pos: usize,
-}
-
-impl ReportDescriptorIterator {
-    fn new(desc: ReportDescriptor) -> Self {
-        Self { desc, pos: 0 }
-    }
-
-    fn next_item(&mut self) -> Option<Data> {
-        let item = get_hid_item(&self.desc.value[self.pos..]);
-        if item.is_none() {
-            self.pos = self.desc.size as usize; // Close, invalid data.
-            return None;
-        }
-
-        let (tag_type, key_len, data) = item.unwrap();
-
-        // Advance if we have a valid item.
-        self.pos += key_len + data.len();
-
-        // We only check short items.
-        if key_len > 1 {
-            return None; // Check next item.
-        }
-
-        // Short items have max. length of 4 bytes.
-        assert!(data.len() <= mem::size_of::<u32>());
-
-        // Convert data bytes to a uint.
-        let data = read_uint_le(data);
-
-        match tag_type {
-            HID_ITEM_TAGTYPE_USAGE_PAGE => Some(Data::UsagePage { data }),
-            HID_ITEM_TAGTYPE_USAGE => Some(Data::Usage { data }),
-            _ => None,
-        }
-    }
-}
-
-impl Iterator for ReportDescriptorIterator {
-    type Item = Data;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        if self.pos >= self.desc.size as usize {
-            return None;
-        }
-
-        self.next_item().or_else(|| self.next())
-    }
-}
-
-fn get_hid_item<'a>(buf: &'a [u8]) -> Option<(u8, usize, &'a [u8])> {
-    if (buf[0] & HID_MASK_LONG_ITEM_TAG) == HID_MASK_LONG_ITEM_TAG {
-        get_hid_long_item(buf)
-    } else {
-        get_hid_short_item(buf)
-    }
-}
-
-fn get_hid_long_item<'a>(buf: &'a [u8]) -> Option<(u8, usize, &'a [u8])> {
-    // A valid long item has at least three bytes.
-    if buf.len() < 3 {
-        return None;
-    }
-
-    let len = buf[1] as usize;
-
-    // Ensure that there are enough bytes left in the buffer.
-    if len > buf.len() - 3 {
-        return None;
-    }
-
-    Some((buf[2], 3 /* key length */, &buf[3..]))
-}
-
-fn get_hid_short_item<'a>(buf: &'a [u8]) -> Option<(u8, usize, &'a [u8])> {
-    // This is a short item. The bottom two bits of the key
-    // contain the length of the data section (value) for this key.
-    let len = match buf[0] & HID_MASK_SHORT_ITEM_SIZE {
-        s @ 0...2 => s as usize,
-        _ => 4, /* _ == 3 */
-    };
-
-    // Ensure that there are enough bytes left in the buffer.
-    if len > buf.len() - 1 {
-        return None;
-    }
-
-    Some((
-        buf[0] & HID_MASK_ITEM_TAGTYPE,
-        1, /* key length */
-        &buf[1..1 + len],
-    ))
-}
-
-fn read_uint_le(buf: &[u8]) -> u32 {
-    assert!(buf.len() <= 4);
-    // Parse the number in little endian byte order.
-    buf.iter().rev().fold(0, |num, b| (num << 8) | (*b as u32))
-}
+ioctl!(READ, hidiocgrdesc, b'H', 0x02; /*struct*/ LinuxReportDescriptor);
 
 pub fn is_u2f_device(fd: RawFd) -> bool {
     match read_report_descriptor(fd) {
         Ok(desc) => has_fido_usage(desc),
         Err(_) => false, // Upon failure, just say it's not a U2F device.
     }
 }
 
 fn read_report_descriptor(fd: RawFd) -> io::Result<ReportDescriptor> {
-    let mut desc = ReportDescriptor {
+    let mut desc = LinuxReportDescriptor {
         size: 0,
         value: [0; HID_MAX_DESCRIPTOR_SIZE],
     };
 
     let _ = unsafe { hidiocgrdescsize(fd, &mut desc.size)? };
     if desc.size == 0 || desc.size as usize > desc.value.len() {
         return Err(io_err("unexpected hidiocgrdescsize() result"));
     }
 
     let _ = unsafe { hidiocgrdesc(fd, &mut desc)? };
-    Ok(desc)
+    let mut value = Vec::from(&desc.value[..]);
+    value.truncate(desc.size as usize);
+    Ok(ReportDescriptor { value })
 }
-
-fn has_fido_usage(desc: ReportDescriptor) -> bool {
-    let mut usage_page = None;
-    let mut usage = None;
-
-    for data in desc.iter() {
-        match data {
-            Data::UsagePage { data } => usage_page = Some(data),
-            Data::Usage { data } => usage = Some(data),
-        }
-
-        // Check the values we found.
-        if let (Some(usage_page), Some(usage)) = (usage_page, usage) {
-            return usage_page == FIDO_USAGE_PAGE as u32 && usage == FIDO_USAGE_U2FHID as u32;
-        }
-    }
-
-    false
-}
--- a/dom/webauthn/u2f-hid-rs/src/macos/iokit.rs
+++ b/dom/webauthn/u2f-hid-rs/src/macos/iokit.rs
@@ -3,25 +3,25 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #![allow(non_snake_case, non_camel_case_types, non_upper_case_globals)]
 
 extern crate core_foundation_sys;
 extern crate libc;
 
 use consts::{FIDO_USAGE_U2FHID, FIDO_USAGE_PAGE};
+use core_foundation::dictionary::*;
+use core_foundation::number::*;
+use core_foundation::string::*;
 use core_foundation_sys::base::*;
 use core_foundation_sys::dictionary::*;
 use core_foundation_sys::runloop::*;
 use core_foundation_sys::string::*;
-use core_foundation::dictionary::*;
-use core_foundation::string::*;
-use core_foundation::number::*;
+use std::ops::Deref;
 use std::os::raw::c_void;
-use std::ops::Deref;
 
 type IOOptionBits = u32;
 
 pub type IOReturn = libc::c_int;
 
 pub type IOHIDManagerRef = *mut __IOHIDManager;
 pub type IOHIDManagerOptions = IOOptionBits;
 
@@ -163,17 +163,17 @@ impl IOHIDDeviceMatcher {
             (
                 CFString::from_static_string("DeviceUsage"),
                 CFNumber::from(FIDO_USAGE_U2FHID as i32),
             ),
             (
                 CFString::from_static_string("DeviceUsagePage"),
                 CFNumber::from(FIDO_USAGE_PAGE as i32),
             ),
-            ]);
+        ]);
         Self { dict }
     }
 }
 
 #[link(name = "IOKit", kind = "framework")]
 extern "C" {
     // CFRunLoop
     pub fn CFRunLoopObserverCreate(
--- a/dom/webauthn/u2f-hid-rs/src/macos/monitor.rs
+++ b/dom/webauthn/u2f-hid-rs/src/macos/monitor.rs
@@ -1,22 +1,22 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 extern crate libc;
 extern crate log;
 
+use core_foundation::base::TCFType;
 use core_foundation_sys::base::*;
 use core_foundation_sys::runloop::*;
-use core_foundation::base::TCFType;
-use std::os::raw::c_void;
 use platform::iokit::*;
 use runloop::RunLoop;
 use std::collections::HashMap;
+use std::os::raw::c_void;
 use std::sync::mpsc::{channel, Receiver, Sender};
 use std::{io, slice};
 use util::io_err;
 
 struct DeviceData {
     tx: Sender<Vec<u8>>,
     runloop: RunLoop,
 }
--- a/dom/webauthn/u2f-hid-rs/src/macos/transaction.rs
+++ b/dom/webauthn/u2f-hid-rs/src/macos/transaction.rs
@@ -1,18 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 extern crate libc;
 
 use core_foundation_sys::runloop::*;
-use std::os::raw::c_void;
 use platform::iokit::{CFRunLoopEntryObserver, IOHIDDeviceRef, SendableRunLoop};
 use platform::monitor::Monitor;
+use std::os::raw::c_void;
 use std::sync::mpsc::{channel, Receiver, Sender};
 use std::thread;
 use util::OnceCallback;
 
 // A transaction will run the given closure in a new thread, thereby using a
 // separate per-thread state machine for each HID. It will either complete or
 // fail through user action, timeout, or be cancelled when overridden by a new
 // transaction.
--- a/dom/webauthn/u2f-hid-rs/src/statemachine.rs
+++ b/dom/webauthn/u2f-hid-rs/src/statemachine.rs
@@ -160,19 +160,17 @@ impl StateMachine {
                 find_valid_key_handles(&app_ids, &key_handles, |app_id, key_handle| {
                     u2f_is_keyhandle_valid(dev, &challenge, app_id, &key_handle.credential)
                         .unwrap_or(false) /* no match on failure */
                 });
 
             // Aggregate distinct transports from all given credentials.
             let transports = key_handles
                 .iter()
-                .fold(::AuthenticatorTransports::empty(), |t, k| {
-                    t | k.transports
-                });
+                .fold(::AuthenticatorTransports::empty(), |t, k| t | k.transports);
 
             // We currently only support USB. If the RP specifies transports
             // and doesn't include USB it's probably lying.
             if !is_valid_transport(transports) {
                 return;
             }
 
             while alive() {
--- a/dom/webauthn/u2f-hid-rs/src/util.rs
+++ b/dom/webauthn/u2f-hid-rs/src/util.rs
@@ -41,16 +41,26 @@ pub fn from_unix_result<T: Signed>(rv: T
     if rv.is_negative() {
         let errno = unsafe { *libc::__errno_location() };
         Err(io::Error::from_raw_os_error(errno))
     } else {
         Ok(rv)
     }
 }
 
+#[cfg(any(target_os = "freebsd"))]
+pub fn from_unix_result<T: Signed>(rv: T) -> io::Result<T> {
+    if rv.is_negative() {
+        let errno = unsafe { *libc::__error() };
+        Err(io::Error::from_raw_os_error(errno))
+    } else {
+        Ok(rv)
+    }
+}
+
 pub fn io_err(msg: &str) -> io::Error {
     io::Error::new(io::ErrorKind::Other, msg)
 }
 
 pub struct OnceCallback<T> {
     callback: Arc<Mutex<Option<SendBoxFnOnce<(Result<T, ::Error>,)>>>>,
 }