/*
 * Digital Persona U.are.U 4000/4000B/4500 driver for libfprint
 * Copyright (C) 2007-2008 Daniel Drake <dsd@gentoo.org>
 * Copyright (C) 2012 Timo Teräs <timo.teras@iki.fi>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#define FP_COMPONENT "uru4000"

#include <nss.h>
#include <pk11pub.h>

#include "drivers_api.h"

#define EP_INTR (1 | FPI_USB_ENDPOINT_IN)
#define EP_DATA (2 | FPI_USB_ENDPOINT_IN)
#define USB_RQ 0x04
#define CTRL_IN (LIBUSB_REQUEST_TYPE_VENDOR | FPI_USB_ENDPOINT_IN)
#define CTRL_OUT (LIBUSB_REQUEST_TYPE_VENDOR | FPI_USB_ENDPOINT_OUT)
#define CTRL_TIMEOUT 5000
#define BULK_TIMEOUT 5000
#define IRQ_LENGTH 64
#define CR_LENGTH 16

#define IMAGE_HEIGHT 290
#define IMAGE_WIDTH 384

#define ENC_THRESHOLD 5000

enum {
  IRQDATA_SCANPWR_ON = 0x56aa,
  IRQDATA_FINGER_ON = 0x0101,
  IRQDATA_FINGER_OFF = 0x0200,
  IRQDATA_DEATH = 0x0800,
};

enum {
  REG_HWSTAT = 0x07,
  REG_SCRAMBLE_DATA_INDEX = 0x33,
  REG_SCRAMBLE_DATA_KEY = 0x34,
  REG_MODE = 0x4e,
  REG_DEVICE_INFO = 0xf0,
  /* firmware starts at 0x100 */
  REG_RESPONSE = 0x2000,
  REG_CHALLENGE = 0x2010,
};

enum {
  MODE_INIT = 0x00,
  MODE_AWAIT_FINGER_ON = 0x10,
  MODE_AWAIT_FINGER_OFF = 0x12,
  MODE_CAPTURE = 0x20,
  MODE_CAPTURE_AUX = 0x30,
  MODE_OFF = 0x70,
  MODE_READY = 0x80,
};

enum {
  MS_KBD,
  MS_INTELLIMOUSE,
  MS_STANDALONE,
  MS_STANDALONE_V2,
  DP_URU4000,
  DP_URU4000B,
};

static const struct uru4k_dev_profile
{
  const char *name;
  gboolean    auth_cr;
  gboolean    image_not_flipped;
} uru4k_dev_info[] = {
  [MS_KBD] = {
    .name = "Microsoft Keyboard with Fingerprint Reader",
    .auth_cr = FALSE,
  },
  [MS_INTELLIMOUSE] = {
    .name = "Microsoft Wireless IntelliMouse with Fingerprint Reader",
    .auth_cr = FALSE,
  },
  [MS_STANDALONE] = {
    .name = "Microsoft Fingerprint Reader",
    .auth_cr = FALSE,
  },
  [MS_STANDALONE_V2] = {
    .name = "Microsoft Fingerprint Reader v2",
    .auth_cr = TRUE,
  },
  [DP_URU4000] = {
    .name = "Digital Persona U.are.U 4000",
    .auth_cr = FALSE,
  },
  [DP_URU4000B] = {
    .name = "Digital Persona U.are.U 4000B",
    .auth_cr = FALSE,
    .image_not_flipped = TRUE, /* See comment in the code where it is used. */
  },
};

typedef void (*irq_cb_fn)(FpImageDevice *dev,
                          GError        *error,
                          uint16_t       type,
                          void          *user_data);
typedef void (*irqs_stopped_cb_fn)(FpImageDevice *dev);

struct _FpiDeviceUru4000
{
  FpImageDevice                   parent;

  const struct uru4k_dev_profile *profile;
  uint8_t                         interface;
  FpiImageDeviceState             activate_state;
  unsigned char                   last_reg_rd[16];
  unsigned char                   last_hwstat;

  GCancellable                   *irq_cancellable;
  FpiUsbTransfer                 *img_transfer;
  void                           *img_data;
  int                             img_data_actual_length;
  uint16_t                        img_lines_done, img_block;
  GRand                          *rand;
  uint32_t                        img_enc_seed;

  irq_cb_fn                       irq_cb;
  void                           *irq_cb_data;
  irqs_stopped_cb_fn              irqs_stopped_cb;

  int                             rebootpwr_ctr;
  int                             powerup_ctr;
  unsigned char                   powerup_hwstat;

  int                             scanpwr_irq_timeouts;
  GSource                        *scanpwr_irq_timeout;

  int                             fwfixer_offset;
  unsigned char                   fwfixer_value;

  CK_MECHANISM_TYPE               cipher;
  PK11SlotInfo                   *slot;
  PK11SymKey                     *symkey;
  SECItem                        *param;
};
G_DECLARE_FINAL_TYPE (FpiDeviceUru4000, fpi_device_uru4000, FPI, DEVICE_URU4000,
                      FpImageDevice);
G_DEFINE_TYPE (FpiDeviceUru4000, fpi_device_uru4000, FP_TYPE_IMAGE_DEVICE);

/* For 2nd generation MS devices */
static const unsigned char crkey[] = {
  0x79, 0xac, 0x91, 0x79, 0x5c, 0xa1, 0x47, 0x8e,
  0x98, 0xe0, 0x0f, 0x3c, 0x59, 0x8f, 0x5f, 0x4b,
};

/***** REGISTER I/O *****/

static void
write_regs (FpImageDevice *dev, uint16_t first_reg,
            uint16_t num_regs, unsigned char *values,
            FpiUsbTransferCallback callback,
            void *user_data)
{
  FpiUsbTransfer *transfer = fpi_usb_transfer_new (FP_DEVICE (dev));

  transfer->short_is_error = TRUE;
  fpi_usb_transfer_fill_control (transfer,
                                 G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
                                 G_USB_DEVICE_REQUEST_TYPE_VENDOR,
                                 G_USB_DEVICE_RECIPIENT_DEVICE,
                                 USB_RQ, first_reg, 0,
                                 num_regs);
  memcpy (transfer->buffer, values, num_regs);
  fpi_usb_transfer_submit (transfer, CTRL_TIMEOUT, NULL, callback, user_data);
}

static void
write_reg (FpImageDevice *dev, uint16_t reg,
           unsigned char value,
           FpiUsbTransferCallback callback,
           void *user_data)
{
  write_regs (dev, reg, 1, &value, callback, user_data);
}

static void
read_regs (FpImageDevice *dev, uint16_t first_reg,
           uint16_t num_regs,
           FpiUsbTransferCallback callback,
           void *user_data)
{
  FpiUsbTransfer *transfer = fpi_usb_transfer_new (FP_DEVICE (dev));

  fpi_usb_transfer_fill_control (transfer,
                                 G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST,
                                 G_USB_DEVICE_REQUEST_TYPE_VENDOR,
                                 G_USB_DEVICE_RECIPIENT_DEVICE,
                                 USB_RQ, first_reg, 0, num_regs);
  fpi_usb_transfer_submit (transfer, CTRL_TIMEOUT, NULL, callback, user_data);
}

/*
 * HWSTAT
 *
 * This register has caused me a lot of headaches. It pretty much defines
 * code flow, and if you don't get it right, the pretty lights don't come on.
 * I think the situation is somewhat complicated by the fact that writing it
 * doesn't affect the read results in the way you'd expect -- but then again
 * it does have some obvious effects. Here's what we know
 *
 * BIT 7: LOW POWER MODE
 * When this bit is set, the device is partially turned off or something. Some
 * things, like firmware upload, need to be done in this state. But generally
 * we want to clear this bit during late initialization, which can sometimes
 * be tricky.
 *
 * BIT 2: SOMETHING WENT WRONG
 * Not sure about this, but see the init function, as when we detect it,
 * we reboot the device. Well, we mess with hwstat until this evil bit gets
 * cleared.
 *
 * BIT 1: IRQ PENDING
 * Just had a brainwave. This bit is set when the device is trying to deliver
 * an interrupt to the host. Maybe?
 */

static void
response_cb (FpiUsbTransfer *transfer, FpDevice *dev, void *user_data, GError *error)
{
  /* NOTE: We could use the SSM function instead if we attached the ssm to the transfer! */
  FpiSsm *ssm = user_data;

  if (!error)
    fpi_ssm_next_state (ssm);
  else
    fpi_ssm_mark_failed (ssm, error);
}

static void
challenge_cb (FpiUsbTransfer *transfer, FpDevice *dev, void *user_data, GError *error)
{
  FpiSsm *ssm = user_data;
  FpiDeviceUru4000 *self = FPI_DEVICE_URU4000 (dev);
  unsigned char respdata[CR_LENGTH];
  PK11Context *ctx;
  int outlen;

  if (error)
    {
      fpi_ssm_mark_failed (ssm, error);
      return;
    }

  /* submit response */
  /* produce response from challenge */
  ctx = PK11_CreateContextBySymKey (self->cipher, CKA_ENCRYPT,
                                    self->symkey, self->param);
  if (PK11_CipherOp (ctx, respdata, &outlen, CR_LENGTH, transfer->buffer, CR_LENGTH) != SECSuccess ||
      PK11_Finalize (ctx) != SECSuccess)
    {
      fp_err ("Failed to encrypt challenge data");
      error = fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, "Failed to encrypt challenge data");
    }
  PK11_DestroyContext (ctx, PR_TRUE);

  if (!error)
    write_regs (FP_IMAGE_DEVICE (dev), REG_RESPONSE, CR_LENGTH, respdata, response_cb, ssm);
  else
    fpi_ssm_mark_failed (ssm, error);
}

/*
 * 2nd generation MS devices added an AES-based challenge/response
 * authentication scheme, where the device challenges the authenticity of the
 * driver.
 */
static void
sm_do_challenge_response (FpiSsm        *ssm,
                          FpImageDevice *dev)
{
  G_DEBUG_HERE ();
  read_regs (dev, REG_CHALLENGE, CR_LENGTH, challenge_cb, ssm);
}

/***** INTERRUPT HANDLING *****/

#define IRQ_HANDLER_IS_RUNNING(urudev) ((urudev)->irq_cancellable)

static void start_irq_handler (FpImageDevice *dev);

static void
irq_handler (FpiUsbTransfer *transfer,
             FpDevice       *dev,
             void           *user_data,
             GError         *error)
{
  FpImageDevice *imgdev = FP_IMAGE_DEVICE (dev);
  FpiDeviceUru4000 *urudev = FPI_DEVICE_URU4000 (dev);
  unsigned char *data = transfer->buffer;
  uint16_t type;

  g_clear_object (&urudev->irq_cancellable);

  if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
    {
      fp_dbg ("cancelled");
      if (urudev->irqs_stopped_cb)
        urudev->irqs_stopped_cb (imgdev);
      urudev->irqs_stopped_cb = NULL;
      g_clear_error (&error);
      return;
    }
  else if (error)
    {
      if (urudev->irq_cb)
        {
          urudev->irq_cb (imgdev, error, 0, urudev->irq_cb_data);
        }
      else
        {
          fp_dbg ("ignoring interrupt error: %s", error->message);
          g_clear_error (&error);
        }
      return;
    }

  start_irq_handler (imgdev);

  type = GUINT16_FROM_BE (*((uint16_t *) data));
  fp_dbg ("recv irq type %04x", type);

  /* The 0800 interrupt seems to indicate imminent failure (0 bytes transfer)
   * of the next scan. It still appears on occasion. */
  if (type == IRQDATA_DEATH)
    fp_warn ("oh no! got the interrupt OF DEATH! expect things to go bad");

  if (urudev->irq_cb)
    urudev->irq_cb (imgdev, NULL, type, urudev->irq_cb_data);
  else
    fp_dbg ("ignoring interrupt");
}

static void
start_irq_handler (FpImageDevice *dev)
{
  FpiDeviceUru4000 *self = FPI_DEVICE_URU4000 (dev);
  FpiUsbTransfer *transfer;

  g_assert (self->irq_cancellable == NULL);
  self->irq_cancellable = g_cancellable_new ();
  transfer = fpi_usb_transfer_new (FP_DEVICE (dev));
  transfer->ssm = NULL;
  transfer->short_is_error = TRUE;
  fpi_usb_transfer_fill_interrupt (transfer,
                                   EP_INTR,
                                   IRQ_LENGTH);
  fpi_usb_transfer_submit (transfer, 0, self->irq_cancellable, irq_handler, NULL);
}

static void
stop_irq_handler (FpImageDevice *dev, irqs_stopped_cb_fn cb)
{
  FpiDeviceUru4000 *self = FPI_DEVICE_URU4000 (dev);

  if (self->irq_cancellable)
    {
      g_cancellable_cancel (self->irq_cancellable);
      self->irqs_stopped_cb = cb;
    }
  else
    {
      cb (dev);
    }
}

/***** STATE CHANGING *****/

static void execute_state_change (FpImageDevice *dev);

static void
finger_presence_irq_cb (FpImageDevice *dev,
                        GError        *error,
                        uint16_t       type,
                        void          *user_data)
{
  if (error)
    fpi_image_device_session_error (dev, error);
  else if (type == IRQDATA_FINGER_ON)
    fpi_image_device_report_finger_status (dev, TRUE);
  else if (type == IRQDATA_FINGER_OFF)
    fpi_image_device_report_finger_status (dev, FALSE);
  else if (type != IRQDATA_SCANPWR_ON)
    fp_warn ("ignoring unexpected interrupt %04x", type);
}

static void
change_state_write_reg_cb (FpiUsbTransfer *transfer,
                           FpDevice       *dev,
                           void           *user_data,
                           GError         *error)
{
  if (error)
    fpi_image_device_session_error (FP_IMAGE_DEVICE (dev), error);
}

static void
dev_change_state (FpImageDevice *dev, FpiImageDeviceState state)
{
  FpiDeviceUru4000 *self = FPI_DEVICE_URU4000 (dev);

  self->activate_state = state;
  if (self->img_transfer != NULL)
    return;

  execute_state_change (dev);
}

/***** GENERIC STATE MACHINE HELPER FUNCTIONS *****/

static void
sm_write_reg_cb (FpiUsbTransfer *transfer,
                 FpDevice       *dev,
                 void           *user_data,
                 GError         *error)
{
  FpiSsm *ssm = user_data;

  if (error)
    fpi_ssm_mark_failed (ssm, error);
  else
    fpi_ssm_next_state (ssm);
}

static void
sm_write_regs (FpiSsm        *ssm,
               FpImageDevice *dev,
               uint16_t       first_reg,
               uint16_t       num_regs,
               void          *data)
{
  write_regs (dev, first_reg, num_regs, data, sm_write_reg_cb, ssm);
}

static void
sm_write_reg (FpiSsm        *ssm,
              FpImageDevice *dev,
              uint16_t       reg,
              unsigned char  value)
{
  sm_write_regs (ssm, dev, reg, 1, &value);
}

static void
sm_read_reg_cb (FpiUsbTransfer *transfer,
                FpDevice       *dev,
                void           *user_data,
                GError         *error)
{
  FpiSsm *ssm = user_data;
  FpiDeviceUru4000 *self = FPI_DEVICE_URU4000 (dev);

  if (error)
    {
      fpi_ssm_mark_failed (ssm, error);
    }
  else
    {
      memcpy (self->last_reg_rd, transfer->buffer, transfer->actual_length);
      fp_dbg ("reg value %x", self->last_reg_rd[0]);
      fpi_ssm_next_state (ssm);
    }
}

#define member_size(type, member) sizeof (((type *) 0)->member)

static void
sm_read_regs (FpiSsm        *ssm,
              FpImageDevice *dev,
              uint16_t       reg,
              uint16_t       num_regs)
{
  g_assert (num_regs <= member_size (FpiDeviceUru4000, last_reg_rd));

  fp_dbg ("read %d regs at %x", num_regs, reg);
  read_regs (dev, reg, num_regs, sm_read_reg_cb, ssm);
}

static void
sm_read_reg (FpiSsm        *ssm,
             FpImageDevice *dev,
             uint16_t       reg)
{
  sm_read_regs (ssm, dev, reg, 1);
}

static void
sm_set_hwstat (FpiSsm        *ssm,
               FpImageDevice *dev,
               unsigned char  value)
{
  fp_dbg ("set %02x", value);
  sm_write_reg (ssm, dev, REG_HWSTAT, value);
}

/***** IMAGING LOOP *****/

enum imaging_states {
  IMAGING_CAPTURE,
  IMAGING_SEND_INDEX,
  IMAGING_READ_KEY,
  IMAGING_DECODE,
  IMAGING_REPORT_IMAGE,
  IMAGING_NUM_STATES
};

struct uru4k_image
{
  uint8_t  unknown_00[4];
  uint16_t num_lines;
  uint8_t  key_number;
  uint8_t  unknown_07[9];
  struct
  {
    uint8_t flags;
    uint8_t num_lines;
  } block_info[15];
  uint8_t unknown_2E[18];
  uint8_t data[IMAGE_HEIGHT][IMAGE_WIDTH];
};

static void
image_transfer_cb (FpiUsbTransfer *transfer, FpDevice *dev,
                   gpointer user_data, GError *error)
{
  FpiDeviceUru4000 *self = FPI_DEVICE_URU4000 (dev);
  FpiSsm *ssm = transfer->ssm;

  if (error)
    {
      fp_dbg ("error");
      fpi_ssm_mark_failed (ssm, error);
    }
  else
    {
      self->img_data = g_memdup2 (transfer->buffer, sizeof (struct uru4k_image));
      self->img_data_actual_length = transfer->actual_length;
      fpi_ssm_next_state (ssm);
    }
}

enum {
  BLOCKF_CHANGE_KEY       = 0x80,
  BLOCKF_NO_KEY_UPDATE    = 0x04,
  BLOCKF_ENCRYPTED                = 0x02,
  BLOCKF_NOT_PRESENT      = 0x01,
};

static uint32_t
update_key (uint32_t key)
{
  /* linear feedback shift register
   * taps at bit positions 1 3 4 7 11 13 20 23 26 29 32 */
  uint32_t bit = key & 0x9248144d;

  bit ^= bit << 16;
  bit ^= bit << 8;
  bit ^= bit << 4;
  bit ^= bit << 2;
  bit ^= bit << 1;
  return (bit & 0x80000000) | (key >> 1);
}

static uint32_t
do_decode (uint8_t *data, int num_bytes, uint32_t key)
{
  uint8_t xorbyte;
  int i;

  for (i = 0; i < num_bytes - 1; i++)
    {
      /* calculate xor byte and update key */
      xorbyte  = ((key >>  4) & 1) << 0;
      xorbyte |= ((key >>  8) & 1) << 1;
      xorbyte |= ((key >> 11) & 1) << 2;
      xorbyte |= ((key >> 14) & 1) << 3;
      xorbyte |= ((key >> 18) & 1) << 4;
      xorbyte |= ((key >> 21) & 1) << 5;
      xorbyte |= ((key >> 24) & 1) << 6;
      xorbyte |= ((key >> 29) & 1) << 7;
      key = update_key (key);

      /* decrypt data */
      data[i] = data[i + 1] ^ xorbyte;
    }

  /* the final byte is implicitly zero */
  data[i] = 0;
  return update_key (key);
}

static int
calc_dev2 (struct uru4k_image *img)
{
  uint8_t *b[2] = { NULL, NULL };
  int res = 0, mean = 0, i, r, j, idx;

  for (i = r = idx = 0; i < G_N_ELEMENTS (img->block_info) && idx < 2; i++)
    {
      if (img->block_info[i].flags & BLOCKF_NOT_PRESENT)
        continue;
      for (j = 0; j < img->block_info[i].num_lines && idx < 2; j++)
        b[idx++] = img->data[r++];
    }
  if (!b[0] || !b[1])
    {
      fp_dbg ("NULL! %p %p", b[0], b[1]);
      return 0;
    }
  for (i = 0; i < IMAGE_WIDTH; i++)
    mean += (int) b[0][i] + (int) b[1][i];

  mean /= IMAGE_WIDTH;

  for (i = 0; i < IMAGE_WIDTH; i++)
    {
      int dev = (int) b[0][i] + (int) b[1][i] - mean;
      res += dev * dev;
    }

  return res / IMAGE_WIDTH;
}

static void
imaging_run_state (FpiSsm *ssm, FpDevice *_dev)
{
  FpImageDevice *dev = FP_IMAGE_DEVICE (_dev);
  FpiDeviceUru4000 *self = FPI_DEVICE_URU4000 (_dev);
  struct uru4k_image *img = self->img_data;
  FpImage *fpimg;
  uint32_t key;
  uint8_t flags, num_lines;
  int i, r, to, dev2;
  unsigned char buf[5];

  switch (fpi_ssm_get_cur_state (ssm))
    {
    case IMAGING_CAPTURE:
      self->img_lines_done = 0;
      self->img_block = 0;
      fpi_usb_transfer_submit (fpi_usb_transfer_ref (self->img_transfer),
                               0,
                               NULL,
                               image_transfer_cb,
                               NULL);

      break;

    case IMAGING_SEND_INDEX:
      fp_dbg ("hw header lines %d", img->num_lines);

      if (img->num_lines >= IMAGE_HEIGHT ||
          self->img_data_actual_length < img->num_lines * IMAGE_WIDTH + 64)
        {
          fp_err ("bad captured image (%d lines) or size mismatch %d < %d",
                  img->num_lines,
                  self->img_data_actual_length,
                  img->num_lines * IMAGE_WIDTH + 64);
          fpi_ssm_jump_to_state (ssm, IMAGING_CAPTURE);
          return;
        }

      /* Detect whether image is encrypted (by checking how noisy it is) */
      dev2 = calc_dev2 (img);
      fp_dbg ("dev2: %d", dev2);
      if (dev2 < ENC_THRESHOLD)
        {
          fpi_ssm_jump_to_state (ssm, IMAGING_REPORT_IMAGE);
          return;
        }
      fp_info ("image seems to be encrypted");

      buf[0] = img->key_number;
      buf[1] = self->img_enc_seed;
      buf[2] = self->img_enc_seed >> 8;
      buf[3] = self->img_enc_seed >> 16;
      buf[4] = self->img_enc_seed >> 24;
      sm_write_regs (ssm, dev, REG_SCRAMBLE_DATA_INDEX, 5, buf);
      break;

    case IMAGING_READ_KEY:
      sm_read_regs (ssm, dev, REG_SCRAMBLE_DATA_KEY, 4);
      break;

    case IMAGING_DECODE:
      key  = self->last_reg_rd[0];
      key |= self->last_reg_rd[1] << 8;
      key |= self->last_reg_rd[2] << 16;
      key |= self->last_reg_rd[3] << 24;
      key ^= self->img_enc_seed;

      fp_dbg ("encryption id %02x -> key %08x", img->key_number, key);
      while (self->img_block < G_N_ELEMENTS (img->block_info) &&
             self->img_lines_done < img->num_lines)
        {
          flags = img->block_info[self->img_block].flags;
          num_lines = img->block_info[self->img_block].num_lines;
          if (num_lines == 0)
            break;

          fp_dbg ("%d %02x %d", self->img_block, flags,
                  num_lines);
          if (flags & BLOCKF_CHANGE_KEY)
            {
              fp_dbg ("changing encryption keys.");
              img->block_info[self->img_block].flags &= ~BLOCKF_CHANGE_KEY;
              img->key_number++;
              self->img_enc_seed = g_rand_int_range (self->rand, 0, RAND_MAX);
              fp_dbg ("New image encryption seed: %d", self->img_enc_seed);
              fpi_ssm_jump_to_state (ssm, IMAGING_SEND_INDEX);
              return;
            }
          switch (flags & (BLOCKF_NO_KEY_UPDATE | BLOCKF_ENCRYPTED))
            {
            case BLOCKF_ENCRYPTED:
              fp_dbg ("decoding %d lines", num_lines);
              key = do_decode (&img->data[self->img_lines_done][0],
                               IMAGE_WIDTH * num_lines, key);
              break;

            case 0:
              fp_dbg ("skipping %d lines", num_lines);
              for (r = 0; r < IMAGE_WIDTH * num_lines; r++)
                key = update_key (key);
              break;
            }
          if ((flags & BLOCKF_NOT_PRESENT) == 0)
            self->img_lines_done += num_lines;
          self->img_block++;
        }
      fpi_ssm_next_state (ssm);
      break;

    case IMAGING_REPORT_IMAGE:
      fpimg = fp_image_new (IMAGE_WIDTH, IMAGE_HEIGHT);

      to = r = 0;
      for (i = 0; i < G_N_ELEMENTS (img->block_info) && r < img->num_lines; i++)
        {
          flags = img->block_info[i].flags;
          num_lines = img->block_info[i].num_lines;
          if (num_lines == 0)
            break;
          memcpy (&fpimg->data[to], &img->data[r][0],
                  num_lines * IMAGE_WIDTH);
          if (!(flags & BLOCKF_NOT_PRESENT))
            r += num_lines;
          to += num_lines * IMAGE_WIDTH;
        }

      fpimg->flags = FPI_IMAGE_COLORS_INVERTED;
      /* NOTE: For some reason all but U4000B (or rather U4500?) flipped the
       * image, we retain this behaviour here, but it is not clear whether it
       * is correct.
       * It may be that there are different models with the same USB ID that
       * behave differently.
       */
      if (self->profile->image_not_flipped)
        fpimg->flags |= FPI_IMAGE_V_FLIPPED | FPI_IMAGE_H_FLIPPED;
      fpi_image_device_image_captured (dev, fpimg);

      if (self->activate_state == FPI_IMAGE_DEVICE_STATE_CAPTURE)
        fpi_ssm_jump_to_state (ssm, IMAGING_CAPTURE);
      else
        fpi_ssm_mark_completed (ssm);
      break;
    }
}

static void
imaging_complete (FpiSsm *ssm, FpDevice *dev, GError *error)
{
  FpiDeviceUru4000 *self = FPI_DEVICE_URU4000 (dev);


  /* Report error before exiting imaging loop - the error handler
   * can request state change, which needs to be postponed to end of
   * this function. */
  if (error)
    fpi_image_device_session_error (FP_IMAGE_DEVICE (dev), error);

  g_clear_pointer (&self->img_transfer, fpi_usb_transfer_unref);

  g_free (self->img_data);
  self->img_data = NULL;
  self->img_data_actual_length = 0;

  execute_state_change (FP_IMAGE_DEVICE (dev));
}

/***** INITIALIZATION *****/

/* After closing an app and setting hwstat to 0x80, my ms keyboard gets in a
 * confused state and returns hwstat 0x85. On next app run, we don't get the
 * 56aa interrupt. This is the best way I've found to fix it: mess around
 * with hwstat until it starts returning more recognisable values. This
 * doesn't happen on my other devices: uru4000, uru4000b, ms fp rdr v2
 *
 * The windows driver copes with this OK, but then again it uploads firmware
 * right after reading the 0x85 hwstat, allowing some time to pass before it
 * attempts to tweak hwstat again...
 *
 * This is implemented with a reboot power state machine. the ssm runs during
 * initialization if bits 2 and 7 are set in hwstat. it masks off the 4 high
 * hwstat bits then checks that bit 1 is set. if not, it pauses before reading
 * hwstat again. machine completes when reading hwstat shows bit 1 is set,
 * and fails after 100 tries. */

enum rebootpwr_states {
  REBOOTPWR_SET_HWSTAT = 0,
  REBOOTPWR_GET_HWSTAT,
  REBOOTPWR_CHECK_HWSTAT,
  REBOOTPWR_PAUSE,
  REBOOTPWR_NUM_STATES,
};

static void
rebootpwr_run_state (FpiSsm *ssm, FpDevice *_dev)
{
  FpImageDevice *dev = FP_IMAGE_DEVICE (_dev);
  FpiDeviceUru4000 *self = FPI_DEVICE_URU4000 (_dev);

  switch (fpi_ssm_get_cur_state (ssm))
    {
    case REBOOTPWR_SET_HWSTAT:
      self->rebootpwr_ctr = 100;
      sm_set_hwstat (ssm, dev, self->last_hwstat & 0xf);
      break;

    case REBOOTPWR_GET_HWSTAT:
      sm_read_reg (ssm, dev, REG_HWSTAT);
      break;

    case REBOOTPWR_CHECK_HWSTAT:
      self->last_hwstat = self->last_reg_rd[0];
      if (self->last_hwstat & 0x1)
        fpi_ssm_mark_completed (ssm);
      else
        fpi_ssm_next_state (ssm);
      break;

    case REBOOTPWR_PAUSE:
      if (!--self->rebootpwr_ctr)
        {
          fp_err ("could not reboot device power");
          fpi_ssm_mark_failed (ssm,
                               fpi_device_error_new_msg (FP_DEVICE_ERROR,
                                                         "Could not reboot device"));
        }
      else
        {
          fpi_ssm_jump_to_state_delayed (ssm, 10, REBOOTPWR_GET_HWSTAT);
        }
      break;
    }
}

/* After messing with the device firmware in its low-power state, we have to
 * power it back up and wait for interrupt notification. It's not quite as easy
 * as that: the combination of both modifying firmware *and* doing C-R auth on
 * my ms fp v2 device causes us not to get the 56aa interrupt and
 * for the hwstat write not to take effect. We have to loop a few times,
 * authenticating each time, until the device wakes up.
 *
 * This is implemented as the powerup state machine below. Pseudo-code:

        status = get_hwstat();
        for (i = 0; i < 100; i++) {
                set_hwstat(status & 0xf);
                if ((get_hwstat() & 0x80) == 0)
                        break;

                usleep(10000);
                if (need_auth_cr)
                        auth_cr();
        }

        if (tmp & 0x80)
                error("could not power up device");

 */

enum powerup_states {
  POWERUP_INIT = 0,
  POWERUP_SET_HWSTAT,
  POWERUP_GET_HWSTAT,
  POWERUP_CHECK_HWSTAT,
  POWERUP_PAUSE,
  POWERUP_CHALLENGE_RESPONSE,
  POWERUP_CHALLENGE_RESPONSE_SUCCESS,
  POWERUP_NUM_STATES,
};

static void
powerup_run_state (FpiSsm *ssm, FpDevice *_dev)
{
  FpImageDevice *dev = FP_IMAGE_DEVICE (_dev);
  FpiDeviceUru4000 *self = FPI_DEVICE_URU4000 (_dev);

  switch (fpi_ssm_get_cur_state (ssm))
    {
    case POWERUP_INIT:
      self->powerup_ctr = 100;
      self->powerup_hwstat = self->last_hwstat & 0xf;
      fpi_ssm_next_state (ssm);
      break;

    case POWERUP_SET_HWSTAT:
      sm_set_hwstat (ssm, dev, self->powerup_hwstat);
      break;

    case POWERUP_GET_HWSTAT:
      sm_read_reg (ssm, dev, REG_HWSTAT);
      break;

    case POWERUP_CHECK_HWSTAT:
      self->last_hwstat = self->last_reg_rd[0];
      if ((self->last_reg_rd[0] & 0x80) == 0)
        fpi_ssm_mark_completed (ssm);
      else
        fpi_ssm_next_state (ssm);
      break;

    case POWERUP_PAUSE:
      if (!--self->powerup_ctr)
        {
          fp_err ("could not power device up");
          fpi_ssm_mark_failed (ssm,
                               fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
                                                         "could not power device up"));
        }
      else if (!self->profile->auth_cr)
        {
          fpi_ssm_jump_to_state_delayed (ssm, POWERUP_SET_HWSTAT, 10);
        }
      else
        {
          fpi_ssm_next_state_delayed (ssm, 10);
        }
      break;

    case POWERUP_CHALLENGE_RESPONSE:
      sm_do_challenge_response (ssm, dev);
      break;

    case POWERUP_CHALLENGE_RESPONSE_SUCCESS:
      fpi_ssm_jump_to_state (ssm, POWERUP_SET_HWSTAT);
      break;
    }
}

/*
 * This is the main initialization state machine. As pseudo-code:

        status = get_hwstat();

        // correct device power state
        if ((status & 0x84) == 0x84)
                run_reboot_sm();

        // power device down
        if ((status & 0x80) == 0)
                set_hwstat(status | 0x80);

        // power device up
        run_powerup_sm();
        await_irq(IRQDATA_SCANPWR_ON);
 */

enum init_states {
  INIT_GET_HWSTAT = 0,
  INIT_CHECK_HWSTAT_REBOOT,
  INIT_REBOOT_POWER,
  INIT_CHECK_HWSTAT_POWERDOWN,
  INIT_POWERUP,
  INIT_AWAIT_SCAN_POWER,
  INIT_DONE,
  INIT_GET_VERSION,
  INIT_REPORT_VERSION,
  INIT_NUM_STATES,
};

static void
init_scanpwr_irq_cb (FpImageDevice *dev, GError *error,
                     uint16_t type, void *user_data)
{
  FpiSsm *ssm = user_data;
  FpiDeviceUru4000 *urudev = FPI_DEVICE_URU4000 (dev);

  if (error)
    {
      fpi_ssm_mark_failed (ssm, error);
    }
  else if (type != IRQDATA_SCANPWR_ON)
    {
      fp_dbg ("ignoring interrupt");
    }
  else if (fpi_ssm_get_cur_state (ssm) != INIT_AWAIT_SCAN_POWER)
    {
      fp_dbg ("early scanpwr interrupt");
      urudev->scanpwr_irq_timeouts = -1;
    }
  else
    {
      fp_dbg ("late scanpwr interrupt");
      fpi_ssm_next_state (ssm);
    }
}

static void
init_scanpwr_timeout (FpDevice *dev,
                      void     *user_data)
{
  FpiSsm *ssm = user_data;
  FpiDeviceUru4000 *self = FPI_DEVICE_URU4000 (dev);

  fp_warn ("powerup timed out");
  self->irq_cb = NULL;
  self->scanpwr_irq_timeout = NULL;

  if (++self->scanpwr_irq_timeouts >= 3)
    {
      fp_err ("powerup timed out 3 times, giving up");
      fpi_ssm_mark_failed (ssm,
                           g_error_new_literal (G_USB_DEVICE_ERROR,
                                                G_USB_DEVICE_ERROR_TIMED_OUT,
                                                "Powerup timed out 3 times, giving up"));
    }
  else
    {
      fpi_ssm_jump_to_state (ssm, INIT_GET_HWSTAT);
    }
}

static void
init_run_state (FpiSsm *ssm, FpDevice *_dev)
{
  FpImageDevice *dev = FP_IMAGE_DEVICE (_dev);
  FpiDeviceUru4000 *self = FPI_DEVICE_URU4000 (_dev);

  switch (fpi_ssm_get_cur_state (ssm))
    {
    case INIT_GET_HWSTAT:
      sm_read_reg (ssm, dev, REG_HWSTAT);
      break;

    case INIT_CHECK_HWSTAT_REBOOT:
      self->last_hwstat = self->last_reg_rd[0];
      if ((self->last_hwstat & 0x84) == 0x84)
        fpi_ssm_next_state (ssm);
      else
        fpi_ssm_jump_to_state (ssm, INIT_CHECK_HWSTAT_POWERDOWN);
      break;

    case INIT_REBOOT_POWER:;
      FpiSsm *rebootsm = fpi_ssm_new (FP_DEVICE (dev),
                                      rebootpwr_run_state,
                                      REBOOTPWR_NUM_STATES);
      fpi_ssm_start_subsm (ssm, rebootsm);
      break;

    case INIT_CHECK_HWSTAT_POWERDOWN:
      if ((self->last_hwstat & 0x80) == 0)
        sm_set_hwstat (ssm, dev, self->last_hwstat | 0x80);
      else
        fpi_ssm_next_state (ssm);
      break;

    case INIT_POWERUP:
      if (!IRQ_HANDLER_IS_RUNNING (self))
        {
          fpi_ssm_mark_failed (ssm, fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
                                                              "IRQ handler should be running but is not"));
          return;
        }
      self->irq_cb_data = ssm;
      self->irq_cb = init_scanpwr_irq_cb;

      FpiSsm *powerupsm = fpi_ssm_new (FP_DEVICE (dev),
                                       powerup_run_state,
                                       POWERUP_NUM_STATES);
      fpi_ssm_start_subsm (ssm, powerupsm);
      break;

    case INIT_AWAIT_SCAN_POWER:
      if (self->scanpwr_irq_timeouts < 0)
        {
          fpi_ssm_next_state (ssm);
          break;
        }

      /* sometimes the 56aa interrupt that we are waiting for never arrives,
       * so we include this timeout loop to retry the whole process 3 times
       * if we don't get an irq any time soon. */
      self->scanpwr_irq_timeout = fpi_device_add_timeout (_dev,
                                                          300,
                                                          init_scanpwr_timeout,
                                                          ssm, NULL);
      break;

    case INIT_DONE:
      if (self->scanpwr_irq_timeout)
        {
          g_source_destroy (self->scanpwr_irq_timeout);
          self->scanpwr_irq_timeout = NULL;
        }
      self->irq_cb_data = NULL;
      self->irq_cb = NULL;
      fpi_ssm_next_state (ssm);
      break;

    case INIT_GET_VERSION:
      sm_read_regs (ssm, dev, REG_DEVICE_INFO, 16);
      break;

    case INIT_REPORT_VERSION:
      /* Likely hardware revision, and firmware version.
       * Not sure which is which. */
      fp_info ("Versions %02x%02x and %02x%02x",
               self->last_reg_rd[10], self->last_reg_rd[11],
               self->last_reg_rd[4],  self->last_reg_rd[5]);
      fpi_ssm_mark_completed (ssm);
      break;
    }
}

static void
activate_initsm_complete (FpiSsm *ssm, FpDevice *dev, GError *error)
{
  fpi_image_device_activate_complete (FP_IMAGE_DEVICE (dev), error);
}

static void
dev_activate (FpImageDevice *dev)
{
  FpiDeviceUru4000 *self = FPI_DEVICE_URU4000 (dev);
  FpiSsm *ssm;

  start_irq_handler (dev);

  self->scanpwr_irq_timeouts = 0;
  ssm = fpi_ssm_new (FP_DEVICE (dev), init_run_state, INIT_NUM_STATES);
  fpi_ssm_start (ssm, activate_initsm_complete);
}

/***** DEINITIALIZATION *****/

static void
deactivate_irqs_stopped (FpImageDevice *dev)
{
  fpi_image_device_deactivate_complete (dev, NULL);
}

static void
deactivate_write_reg_cb (FpiUsbTransfer *transfer, FpDevice *dev,
                         gpointer user_data, GError *error)
{
  stop_irq_handler (FP_IMAGE_DEVICE (dev), deactivate_irqs_stopped);
}

static void
dev_deactivate (FpImageDevice *dev)
{
  /* This is started/handled by execute_state_change in order to delay the
   * action until after the image transfer has completed.
   * We just need to override the function so that the complete handler is
   * not called automatically. */
}

static void
execute_state_change (FpImageDevice *dev)
{
  FpiDeviceUru4000 *self = FPI_DEVICE_URU4000 (dev);
  FpiSsm *ssm;

  switch (self->activate_state)
    {
    case FPI_IMAGE_DEVICE_STATE_DEACTIVATING:
      fp_dbg ("deactivating");
      self->irq_cb = NULL;
      self->irq_cb_data = NULL;
      write_reg (dev, REG_MODE, MODE_OFF,
                 deactivate_write_reg_cb, NULL);
      break;

    case FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON:
      fp_dbg ("wait finger on");
      if (!IRQ_HANDLER_IS_RUNNING (self))
        {
          fpi_image_device_session_error (dev,
                                          fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
                                                                    "IRQ handler should be running but is not"));
          return;
        }
      self->irq_cb = finger_presence_irq_cb;
      write_reg (dev, REG_MODE, MODE_AWAIT_FINGER_ON,
                 change_state_write_reg_cb, NULL);
      break;

    case FPI_IMAGE_DEVICE_STATE_CAPTURE:
      fp_dbg ("starting capture");
      self->irq_cb = NULL;

      ssm = fpi_ssm_new (FP_DEVICE (dev), imaging_run_state,
                         IMAGING_NUM_STATES);
      self->img_enc_seed = g_rand_int_range (self->rand, 0, RAND_MAX);
      fp_dbg ("Image encryption seed: %d", self->img_enc_seed);
      self->img_transfer = fpi_usb_transfer_new (FP_DEVICE (dev));
      self->img_transfer->ssm = ssm;
      self->img_transfer->short_is_error = FALSE;
      fpi_usb_transfer_fill_bulk (self->img_transfer,
                                  EP_DATA,
                                  sizeof (struct uru4k_image));

      fpi_ssm_start (ssm, imaging_complete);

      write_reg (dev, REG_MODE, MODE_CAPTURE,
                 change_state_write_reg_cb, NULL);
      break;

    case FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF:
      fp_dbg ("await finger off");
      if (!IRQ_HANDLER_IS_RUNNING (self))
        {
          fpi_image_device_session_error (dev,
                                          fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
                                                                    "IRQ handler should be running but is not"));
          return;
        }
      self->irq_cb = finger_presence_irq_cb;
      write_reg (dev, REG_MODE, MODE_AWAIT_FINGER_OFF,
                 change_state_write_reg_cb, NULL);
      break;

    /* Ignored states */
    case FPI_IMAGE_DEVICE_STATE_IDLE:
    case FPI_IMAGE_DEVICE_STATE_ACTIVATING:
    case FPI_IMAGE_DEVICE_STATE_INACTIVE:
      break;
    }
}

/***** LIBRARY STUFF *****/

static void
dev_init (FpImageDevice *dev)
{
  GError *error = NULL;
  FpiDeviceUru4000 *self;

  g_autoptr(GPtrArray) interfaces = NULL;
  GUsbInterface *iface = NULL;
  guint64 driver_data;
  SECStatus rv;
  SECItem item;
  int i;

  interfaces = g_usb_device_get_interfaces (fpi_device_get_usb_device (FP_DEVICE (dev)), &error);
  if (error)
    {
      fpi_image_device_open_complete (dev, error);
      return;
    }

  /* Find fingerprint interface; TODO: Move this into probe() */
  for (i = 0; i < interfaces->len; i++)
    {
      GUsbInterface *cur_iface = g_ptr_array_index (interfaces, i);

      if (g_usb_interface_get_class (cur_iface) == 255 &&
          g_usb_interface_get_subclass (cur_iface) == 255 &&
          g_usb_interface_get_protocol (cur_iface) == 255)
        {
          iface = cur_iface;
          break;
        }
    }

  if (iface == NULL)
    {
      fp_err ("could not find interface");
      fpi_image_device_open_complete (dev,
                                      fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
                                                                "Could not find interface"));
      return;
    }

  /* TODO: Find/check endpoints; does not seem easily possible with GUsb unfortunately! */
#if 0
  if (iface_desc->bNumEndpoints != 2)
    {
      fp_err ("found %d endpoints!?", iface_desc->bNumEndpoints);
      r = -ENODEV;
      goto out;
    }

  ep = &iface_desc->endpoint[0];
  if (ep->bEndpointAddress != EP_INTR ||
      (ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) !=
      LIBUSB_TRANSFER_TYPE_INTERRUPT)
    {
      fp_err ("unrecognised interrupt endpoint");
      r = -ENODEV;
      goto out;
    }

  ep = &iface_desc->endpoint[1];
  if (ep->bEndpointAddress != EP_DATA ||
      (ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) !=
      LIBUSB_TRANSFER_TYPE_BULK)
    {
      fp_err ("unrecognised bulk endpoint");
      r = -ENODEV;
      goto out;
    }
#endif

  /* Device looks like a supported reader */

  if (!g_usb_device_claim_interface (fpi_device_get_usb_device (FP_DEVICE (dev)),
                                     g_usb_interface_get_number (iface), 0, &error))
    {
      fpi_image_device_open_complete (dev, error);
      return;
    }

  /* Disable loading p11-kit's user configuration */
  g_setenv ("P11_KIT_NO_USER_CONFIG", "1", TRUE);

  /* Initialise NSS early */
  rv = NSS_NoDB_Init (".");
  if (rv != SECSuccess)
    {
      fp_err ("could not initialise NSS");
      fpi_image_device_open_complete (dev,
                                      fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
                                                                "Could not initialise NSS"));
      return;
    }

  self = FPI_DEVICE_URU4000 (dev);

  g_clear_pointer (&self->rand, g_rand_free);
  self->rand = g_rand_new ();
  if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") == 0)
    g_rand_set_seed (self->rand, 0xFACADE);

  driver_data = fpi_device_get_driver_data (FP_DEVICE (dev));
  self->profile = &uru4k_dev_info[driver_data];
  self->interface = g_usb_interface_get_number (iface);

  /* Set up encryption */
  self->cipher = CKM_AES_ECB;
  self->slot = PK11_GetBestSlot (self->cipher, NULL);
  if (self->slot == NULL)
    {
      fp_err ("could not get encryption slot");
      fpi_image_device_open_complete (dev,
                                      fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
                                                                "Could not get encryption slot"));
      return;
    }
  item.type = siBuffer;
  item.data = (unsigned char *) crkey;
  item.len = sizeof (crkey);
  self->symkey = PK11_ImportSymKey (self->slot,
                                    self->cipher,
                                    PK11_OriginUnwrap,
                                    CKA_ENCRYPT,
                                    &item, NULL);
  if (self->symkey == NULL)
    {
      fp_err ("failed to import key into NSS");
      PK11_FreeSlot (self->slot);
      self->slot = NULL;
      fpi_image_device_open_complete (dev,
                                      fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
                                                                "Failed to import key into NSS"));
      return;
    }
  self->param = PK11_ParamFromIV (self->cipher, NULL);

  fpi_image_device_open_complete (dev, NULL);
}

static void
dev_deinit (FpImageDevice *dev)
{
  GError *error = NULL;
  FpiDeviceUru4000 *self = FPI_DEVICE_URU4000 (dev);

  if (self->symkey)
    PK11_FreeSymKey (self->symkey);
  if (self->param)
    SECITEM_FreeItem (self->param, PR_TRUE);
  if (self->slot)
    PK11_FreeSlot (self->slot);

  NSS_Shutdown ();

  g_usb_device_release_interface (fpi_device_get_usb_device (FP_DEVICE (dev)),
                                  self->interface, 0, &error);
  g_clear_pointer (&self->rand, g_rand_free);
  fpi_image_device_close_complete (dev, error);
}

static const FpIdEntry id_table[] = {
  /* ms kbd with fp rdr */
  { .vid = 0x045e,  .pid = 0x00bb, .driver_data = MS_KBD },

  /* ms intellimouse with fp rdr */
  { .vid = 0x045e,  .pid = 0x00bc, .driver_data = MS_INTELLIMOUSE },

  /* ms fp rdr (standalone) */
  { .vid = 0x045e,  .pid = 0x00bd, .driver_data = MS_STANDALONE },

  /* ms fp rdr (standalone) v2 */
  { .vid = 0x045e,  .pid = 0x00ca, .driver_data = MS_STANDALONE_V2 },

  /* dp uru4000 (standalone) */
  { .vid = 0x05ba,  .pid = 0x0007, .driver_data = DP_URU4000 },

  /* dp uru4000 (keyboard) */
  { .vid = 0x05ba,  .pid = 0x0008, .driver_data = DP_URU4000 },

  /* dp uru4000b (standalone) */
  { .vid = 0x05ba,  .pid = 0x000a, .driver_data = DP_URU4000B },

  /* terminating entry */
  { .vid = 0,  .pid = 0,  .driver_data = 0 },
};

static void
fpi_device_uru4000_init (FpiDeviceUru4000 *self)
{
}

static void
fpi_device_uru4000_class_init (FpiDeviceUru4000Class *klass)
{
  FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass);
  FpImageDeviceClass *img_class = FP_IMAGE_DEVICE_CLASS (klass);

  dev_class->id = "uru4000";
  dev_class->full_name = "Digital Persona U.are.U 4000/4000B/4500";
  dev_class->type = FP_DEVICE_TYPE_USB;
  dev_class->id_table = id_table;
  dev_class->scan_type = FP_SCAN_TYPE_PRESS;

  img_class->img_open = dev_init;
  img_class->img_close = dev_deinit;
  img_class->activate = dev_activate;
  img_class->deactivate = dev_deactivate;
  img_class->change_state = dev_change_state;

  img_class->img_width = IMAGE_WIDTH;
  img_class->img_height = IMAGE_HEIGHT;
}
