#include "config.h"

#include <epan/packet.h>
#include <epan/proto.h>

#include "packet-usb.h"

#define REQUEST_REGISTER 0x0c
#define REQUEST_BUFFER 0x04

#define VALUE_BUFFER 0x82
#define VALUE_SET_REGISTER 0x83
#define VALUE_READ_REGISTER 0x84
#define VALUE_WRITE_REGISTER 0x85
#define VALUE_GET_REGISTER 0x8e
#define VALUE_BUF_ENDACCESS 0x8c

static dissector_handle_t gl843_handle;

static int proto_gl843;
static int ett_gl843;

void proto_register_gl843(void);
void proto_reg_handoff_gl843(void);

static const value_string command_type_vals[] = {
    {REQUEST_BUFFER, "BUFFER"},
    {REQUEST_REGISTER, "REGISTER"},
    {0, NULL},
};

static const value_string operation_vals[] = {
    {VALUE_BUFFER, "read buffer"},
    {VALUE_SET_REGISTER, "set"},
    {VALUE_READ_REGISTER, "read"},
    {VALUE_WRITE_REGISTER, "write"},
    {VALUE_GET_REGISTER, "get"},
    {VALUE_BUF_ENDACCESS, "buf_end"},
    {0, NULL},
};

typedef struct
{
  const char *name;
  int handle;
  const uint8_t *addresses;
  const uint8_t *masks;
} reg_range_t;

#define _reg_range(name, addrs, masks)

static const uint8_t expr_addrs[] = {0x10, 0x11, 0};
static const uint8_t expr_masks[] = {0xff, 0xff, 0};
static const uint8_t expg_addrs[] = {0x12, 0x13, 0};
static const uint8_t expg_masks[] = {0xff, 0xff, 0};
static const uint8_t expb_addrs[] = {0x14, 0x15, 0};
static const uint8_t expb_masks[] = {0xff, 0xff, 0};
static const uint8_t lincnt_addrs[] = {0x25, 0x26, 0x27, 0};
static const uint8_t lincnt_masks[] = {0x0F, 0xFF, 0xFF, 0};
static const uint8_t dpiset_addrs[] = {0x2c, 0x2d, 0};
static const uint8_t dpiset_masks[] = {0x3f, 0xff, 0};
static const uint8_t strpixel_addrs[] = {0x30, 0x31, 0};
static const uint8_t strpixel_masks[] = {0xff, 0xff, 0};
static const uint8_t endpixel_addrs[] = {0x32, 0x33, 0};
static const uint8_t endpixel_masks[] = {0xff, 0xff, 0};
static const uint8_t maxwd_addrs[] = {0x35, 0x36, 0x37, 0};
static const uint8_t maxwd_masks[] = {0xff, 0xff, 0xff, 0};
static const uint8_t lperiod_addrs[] = {0x38, 0x39, 0};
static const uint8_t lperiod_masks[] = {0xFF, 0xFF, 0};
static const uint8_t fewrdata_addrs[] = {0x3a, 0x3b, 0};
static const uint8_t fewrdata_masks[] = {0x01, 0xff, 0};
static reg_range_t reg_ranges[] = {
    {"EXPR", 0, expr_addrs, expr_masks},
    {"EXPG", 0, expg_addrs, expg_masks},
    {"EXPB", 0, expb_addrs, expb_masks},
    {"LINCNT", 0, lincnt_addrs, lincnt_masks},
    {"DPISET", 0, dpiset_addrs, dpiset_masks},
    {"STRPIXEL", 0, strpixel_addrs, strpixel_masks},
    {"ENDPIXEL", 0, endpixel_addrs, endpixel_masks},
    {"MAXWD", 0, maxwd_addrs, maxwd_masks},
    {"LPERIOD", 0, lperiod_addrs, lperiod_masks},
    {"FEWRDATA", 0, fewrdata_addrs, fewrdata_masks},
};

#define _range_field(idx, name, base)                           \
  {                                                             \
    &(reg_ranges[idx].handle),                                  \
    {                                                           \
      #name, "gl843.register." #name, FT_UINT32, base, NULL, 0, \
          "computed value for register " #name, HFILL           \
    }                                                           \
  }

/* frontend registers */
typedef struct
{
  uint8_t addr;
  char *name;
  int *handle;
  uint16_t mask;
} fe_reg_data_t;

#define _fe_rf_dat(addr, name, mask) \
  static int fe_rf_##name;           \
  static const fe_reg_data_t fe_reg_dat_##name = {addr, #name, &fe_rf_##name, mask}
#define _fe_rf_idx(name) &fe_reg_dat_##name

#define _fe_rf_field(name)                                                      \
  {                                                                             \
    &fe_rf_##name,                                                              \
    {                                                                           \
      "FERegister[" #name "]", "gl843.fe_register." #name, FT_UINT16, BASE_HEX, \
          NULL, 0x01FF, "gl843 frontend register " #name, HFILL,                \
    }                                                                           \
  }

_fe_rf_dat(0x00, FE_INPUT_RNG, 1 << 7);
_fe_rf_dat(0x00, FE_VREF, 1 << 6);
_fe_rf_dat(0x00, FE_3CH, 1 << 5);
_fe_rf_dat(0x00, FE_CDS, 1 << 4);
_fe_rf_dat(0x00, FE_CLAMP, 1 << 3);
_fe_rf_dat(0x00, FE_PWRDN, 1 << 2);
_fe_rf_dat(0x01, FE_RGB_BGR, 1 << 7);
_fe_rf_dat(0x01, FE_RED, 1 << 6);
_fe_rf_dat(0x01, FE_GREEN, 1 << 5);
_fe_rf_dat(0x01, FE_BLUE, 1 << 4);
_fe_rf_dat(0x02, FE_RED_PGA, 0x3F);
_fe_rf_dat(0x03, FE_GREEN_PGA, 0x3F);
_fe_rf_dat(0x04, FE_BLUE_PGA, 0x3F);
_fe_rf_dat(0x05, FE_RED_OFFSET, 0x1FF);
_fe_rf_dat(0x06, FE_GREEN_OFFSET, 0x1FF);
_fe_rf_dat(0x07, FE_BLUE_OFFSET, 0x1FF);

static const fe_reg_data_t *fe_reg_index[] = {
    _fe_rf_idx(FE_INPUT_RNG),
    _fe_rf_idx(FE_VREF),
    _fe_rf_idx(FE_3CH),
    _fe_rf_idx(FE_CDS),
    _fe_rf_idx(FE_CLAMP),
    _fe_rf_idx(FE_PWRDN),
    _fe_rf_idx(FE_RGB_BGR),
    _fe_rf_idx(FE_RED),
    _fe_rf_idx(FE_GREEN),
    _fe_rf_idx(FE_BLUE),
    _fe_rf_idx(FE_RED_PGA),
    _fe_rf_idx(FE_GREEN_PGA),
    _fe_rf_idx(FE_BLUE_PGA),
    _fe_rf_idx(FE_RED_OFFSET),
    _fe_rf_idx(FE_GREEN_OFFSET),
    _fe_rf_idx(FE_BLUE_OFFSET),
};

/* end frontend registers */

typedef struct
{
  uint8_t addr;
  char *name;
  int *handle;
  const uint8_t mask;
} reg_data_t;

// create the static variables used for this register field: a non-const integer
// that will hold the wireshark handle; a bit-mask for it; and the data struct
// used when auto-adding the register.
#define _rf_dat(addr, name, mask)                    \
  static int rf_##name;                              \
  static const uint8_t reg_dat_##name##_mask = mask; \
  static const reg_data_t reg_dat_##name = {addr, #name, &rf_##name, mask}

#define _rf_dat2(addr, a, b, n) \
  _rf_dat(addr, a, 1 << (n));   \
  _rf_dat(addr, b, 1 << (n - 1));

#define _rf_dat4(addr, a, b, c, d, n) \
  _rf_dat2(addr, a, b, n);            \
  _rf_dat2(addr, c, d, n - 2);

#define _rf_dat6(addr, a, b, c, d, e, f, n) \
  _rf_dat4(addr, a, b, c, d, n);            \
  _rf_dat2(addr, e, f, n - 4);

#define __rf_dat8(addr, a, b, c, d, e, f, g, h) \
  _rf_dat4(addr, a, b, c, d, 7);                \
  _rf_dat4(addr, e, f, g, h, 3);

// variadic arguments is required here for some reason so that we can pass a
// macro as a list of register names, e.g., _rf_dat8(REG01)
#define _rf_dat8(...) __rf_dat8(__VA_ARGS__)

#define _rf_idx(name) &reg_dat_##name
#define _rf_idx2(a, b) _rf_idx(a), _rf_idx(b)
#define _rf_idx4(a, b, c, d) _rf_idx2(a, b), _rf_idx2(c, d)
#define _rf_idx6(a, b, c, d, e, f) _rf_idx4(a, b, c, d), _rf_idx2(e, f)
#define __rf_idx8(a, b, c, d, e, f, g, h) _rf_idx4(a, b, c, d), _rf_idx4(e, f, g, h)

#define _rf_idx8(...) __rf_idx8(__VA_ARGS__)

#define _rf_field(name)                                                   \
  {                                                                       \
    &rf_##name,                                                           \
    {                                                                     \
      "Register[" #name "]", "gl843.register." #name, FT_UINT8, BASE_HEX, \
          NULL, reg_dat_##name##_mask, "gl843 register " #name, HFILL,    \
    }                                                                     \
  }

#define _rf_field2(a, b) _rf_field(a), _rf_field(b)
#define _rf_field4(a, b, c, d) _rf_field2(a, b), _rf_field2(c, d)
#define _rf_field6(a, b, c, d, e, f) _rf_field4(a, b, c, d), _rf_field2(e, f)
#define __rf_field8(a, b, c, d, e, f, g, h) _rf_field4(a, b, c, d), _rf_field4(e, f, g, h)

#define _rf_field8(...) __rf_field8(__VA_ARGS__)

// gl843 registers

#define GPIO3 GPIO24, GPIO23, GPIO22, GPIO21, GPIO20, GPIO19, GPIO18, GPIO17

#define REG0d \
  JAMPCMD, DOCCMD, CCDCMD, FULLSTP, SEND, CLRMCNT, CLRDOCJM, CLRLNCNT
#define REG41 \
  REG41_PWRBIT, BUFEMPTY, FEEDFSH, SCANFSH, HOMESNR, LAMPSTS, FEBUSY, MOTORENB

#define REG01 CISSET, DOGENB, DVDSET, STAGGER, COMPENB, TRUEGRAY, SHDAREA, SCAN
_rf_dat8(0x01, REG01);

#define REG02 NOTHOME, ACDCDIS, AGOHOME, MTRPWR, FASTFED, MTRREV, HOMENEG, LONGCURV
_rf_dat8(0x02, REG02);

_rf_dat4(0x03, LAMPDOG, AVEENB, XPASEL, LAMPPWR, 7);
_rf_dat(0x03, LAMPTIM, 1 << 3 | 1 << 2 | 1 << 1 | 1 << 0);

_rf_dat2(0x04, LINEART, BITSET, 7);
_rf_dat(0x04, AFEMOD, 1 << 5 | 1 << 4);
_rf_dat(0x04, FILTER, 1 << 3 | 1 << 2);
_rf_dat(0x04, FESET, 1 << 1 | 1 << 0);

_rf_dat(0x05, DPIHW, 1 << 7 | 1 << 6);
_rf_dat(0x05, MTLLAMP, 1 << 5 | 1 << 4);
_rf_dat(0x05, GMMENB, 1 << 3);
_rf_dat(0x05, ENB20M, 1 << 2);
_rf_dat(0x05, MTLBASE, 1 << 1 | 1 << 0);

_rf_dat(0x06, SCANMOD, 1 << 7 | 1 << 6 | 1 << 5);
_rf_dat(0x06, PWRBIT, 1 << 4);
_rf_dat(0x06, GAIN4, 1 << 3);
_rf_dat(0x06, OPTEST, 0b111);

#define REG07 LAMPSIM, CCDCTL, DRAMCTL, MOVCTL, SRAMSEL, FASTDMA, DMASEL, DMARDWR
_rf_dat8(0x07, REG07);

#define REG08 REG0807, DECFLAG, GMMFFR, GMMFFG, GMMFFB, GMMZR, GMMZG, GMMZB
_rf_dat8(0x08, REG08);

// 0x09
_rf_dat(0x09, MCNTSET, 1 << 7 | 1 << 6);
_rf_dat6(0x09, EVEN1ST, BLINE1ST, BACKSCAN, ENHANCE, SHORTTG, NWAIT, 5);

_rf_dat(0x0b, CLKSET, 1 << 7 | 1 << 6 | 1 << 5);
_rf_dat(0x0b, RFHDIS, 1 << 4);
_rf_dat(0x0b, ENBDRAM, 1 << 3);
_rf_dat(0x0b, DRAMSEL, 1 << 2 | 1 << 1 | 1 << 0);
_rf_dat8(0x0d, REG0d);
_rf_dat(0x10, EXPRH, 0xFF);
_rf_dat(0x11, EXPRL, 0xFF);
_rf_dat(0x12, EXPGH, 0xFF);
_rf_dat(0x13, EXPGL, 0xFF);
_rf_dat(0x14, EXPBH, 0xFF);
_rf_dat(0x15, EXPBL, 0xFF);
_rf_dat(0x18, CKSEL, 1 << 1 | 1 << 0);
_rf_dat(0x1b, GRAYSET, 1 << 7);
_rf_dat(0x20, BUFSEL, 0xFF);
_rf_dat(0x2c, DPISET13_8, 0x3F);
_rf_dat(0x2d, DPISET7_0, 0xFF);
_rf_dat(0x2e, BWHI, 0xFF);
_rf_dat(0x2f, BWLOW, 0xFF);
_rf_dat(0x35, MAXWD23_16, 0xFF);
_rf_dat(0x36, MAXWD15_8, 0xFF);
_rf_dat(0x37, MAXWD7_0, 0xFF);
_rf_dat(0x38, LPERIOD15_8, 0xFF);
_rf_dat(0x39, LPERIOD7_0, 0xFF);
_rf_dat(0x3a, FEWRDATA8, 1 << 0);
_rf_dat(0x3b, FEWRDATA7_0, 0xFF);
_rf_dat8(0x41, REG41);
_rf_dat(0x50, FERDA, 1 << 5 | 1 << 4 | 1 << 3 | 1 << 2 | 1 << 1 | 1 << 0);
_rf_dat(0x51, FEWRA, 1 << 5 | 1 << 4 | 1 << 3 | 1 << 2 | 1 << 1 | 1 << 0);
_rf_dat(0xa2, RFHSET, 0x1F);
_rf_dat8(0xa6, GPIO3);

static const reg_data_t *register_index[] = {
    _rf_idx8(REG01),
    _rf_idx8(REG02),
    // 0x03
    _rf_idx4(LAMPDOG, AVEENB, XPASEL, LAMPPWR),
    _rf_idx(LAMPTIM),

    // 0x04
    _rf_idx2(LINEART, BITSET),
    _rf_idx(AFEMOD),
    _rf_idx(FILTER),
    _rf_idx(FESET),

    // 0x05
    _rf_idx(DPIHW),
    _rf_idx(MTLLAMP),
    _rf_idx(GMMENB),
    _rf_idx(ENB20M),
    _rf_idx(MTLBASE),

    // 0x06
    _rf_idx(SCANMOD),
    _rf_idx(PWRBIT),
    _rf_idx(GAIN4),
    _rf_idx(OPTEST),

    _rf_idx8(REG07),

    _rf_idx8(REG08),

    // 0x09
    _rf_idx(MCNTSET),
    _rf_idx6(EVEN1ST, BLINE1ST, BACKSCAN, ENHANCE, SHORTTG, NWAIT),

    _rf_idx(CLKSET),
    _rf_idx(RFHDIS),
    _rf_idx(ENBDRAM),
    _rf_idx(DRAMSEL),
    _rf_idx8(REG0d),
    _rf_idx(EXPRH),
    _rf_idx(EXPRL),
    _rf_idx(EXPGH),
    _rf_idx(EXPGL),
    _rf_idx(EXPBH),
    _rf_idx(EXPBL),
    _rf_idx(CKSEL),
    _rf_idx(GRAYSET),
    _rf_idx(BUFSEL),
    _rf_idx(DPISET13_8),
    _rf_idx(DPISET7_0),
    _rf_idx(BWHI),
    _rf_idx(BWLOW),
    _rf_idx(MAXWD23_16),
    _rf_idx(MAXWD15_8),
    _rf_idx(MAXWD7_0),
    _rf_idx(LPERIOD15_8),
    _rf_idx(LPERIOD7_0),
    _rf_idx(FEWRDATA8),
    _rf_idx(FEWRDATA7_0),
    _rf_idx8(REG41),
    _rf_idx(FERDA),
    _rf_idx(FEWRA),
    _rf_idx(RFHSET),
    _rf_idx8(GPIO3),
};

static int hf_field_gl843, hf_field_command_type, hf_field_operation,
    hf_field_register, hf_field_register_missing, hf_field_register_count;
static hf_register_info hf[] = {
    {&hf_field_gl843,
     {"Genesys Logic GL843", "gl843", FT_PROTOCOL, 0, NULL, 0,
      "packet belongs to gl843", HFILL}},
    {&hf_field_command_type,
     {"Command Type", "gl843.command_type", FT_UINT8, BASE_HEX,
      command_type_vals, 0x0, "command type is REGISTER or BUFFER", HFILL}},
    {&hf_field_operation,
     {"Operation", "gl843.operation", FT_UINT16, BASE_HEX, operation_vals, 0x0,
      "e.g., read register, write register, etc.", HFILL}},
    {&hf_field_register,
     {"Register", "gl843.register", FT_UINT8, BASE_HEX, NULL, 0x0,
      "address of the register the operation is targeting", HFILL}},
    {&hf_field_register_count,
     {"Register Count", "gl843.register_count", FT_UINT16, BASE_DEC, NULL, 0x0,
      "number of registers referenced in packet", HFILL}},
    {&hf_field_register_missing,
     {"Register Missing", "gl843.register_missing", FT_BOOLEAN, BASE_NONE,
      TFS(&tfs_yes_no), 0x0,
      "flag indicating that dissector did not have data for a referenced "
      "register",
      HFILL}},
    _range_field(0, EXPR, BASE_HEX_DEC),
    _range_field(1, EXPG, BASE_HEX_DEC),
    _range_field(2, EXPB, BASE_HEX_DEC),
    _range_field(3, LINCNT, BASE_HEX_DEC),
    _range_field(4, DPISET, BASE_HEX_DEC),
    _range_field(5, STRPIXEL, BASE_HEX_DEC),
    _range_field(6, ENDPIXEL, BASE_HEX_DEC),
    _range_field(7, MAXWD, BASE_HEX_DEC),
    _range_field(8, LPERIOD, BASE_HEX_DEC),
    _range_field(9, FEWRDATA, BASE_HEX_DEC),
    _rf_field8(REG01),
    _rf_field8(REG02),

    // 0x03
    _rf_field4(LAMPDOG, AVEENB, XPASEL, LAMPPWR),
    _rf_field(LAMPTIM),

    // 0x04
    _rf_field2(LINEART, BITSET),
    _rf_field(AFEMOD),
    _rf_field(FILTER),
    _rf_field(FESET),

    // 0x05
    _rf_field(DPIHW),
    _rf_field(MTLLAMP),
    _rf_field(GMMENB),
    _rf_field(ENB20M),
    _rf_field(MTLBASE),

    // 0x06
    _rf_field(SCANMOD),
    _rf_field(PWRBIT),
    _rf_field(GAIN4),
    _rf_field(OPTEST),

    _rf_field8(REG07),

    _rf_field8(REG08),

    // 0x09
    _rf_field(MCNTSET),
    _rf_field6(EVEN1ST, BLINE1ST, BACKSCAN, ENHANCE, SHORTTG, NWAIT),

    _rf_field(CLKSET),
    _rf_field(RFHDIS),
    _rf_field(ENBDRAM),
    _rf_field(DRAMSEL),
    _rf_field8(REG0d),
    _rf_field(EXPRH),
    _rf_field(EXPRL),
    _rf_field(EXPGH),
    _rf_field(EXPGL),
    _rf_field(EXPBH),
    _rf_field(EXPBL),
    _rf_field(CKSEL),
    _rf_field(GRAYSET),
    _rf_field(BUFSEL),
    _rf_field(DPISET13_8),
    _rf_field(DPISET7_0),
    _rf_field(BWHI),
    _rf_field(BWLOW),
    _rf_field(MAXWD23_16),
    _rf_field(MAXWD15_8),
    _rf_field(MAXWD7_0),
    _rf_field(LPERIOD15_8),
    _rf_field(LPERIOD7_0),
    _rf_field(FEWRDATA8),
    _rf_field(FEWRDATA7_0),
    _rf_field8(REG41),
    _rf_field(FERDA),
    _rf_field(FEWRA),
    _rf_field(RFHSET),
    _rf_field8(GPIO3),

    /* frontend registers */
    _fe_rf_field(FE_INPUT_RNG),
    _fe_rf_field(FE_VREF),
    _fe_rf_field(FE_3CH),
    _fe_rf_field(FE_CDS),
    _fe_rf_field(FE_CLAMP),
    _fe_rf_field(FE_PWRDN),
    _fe_rf_field(FE_RGB_BGR),
    _fe_rf_field(FE_RED),
    _fe_rf_field(FE_GREEN),
    _fe_rf_field(FE_BLUE),
    _fe_rf_field(FE_RED_PGA),
    _fe_rf_field(FE_GREEN_PGA),
    _fe_rf_field(FE_BLUE_PGA),
    _fe_rf_field(FE_RED_OFFSET),
    _fe_rf_field(FE_GREEN_OFFSET),
    _fe_rf_field(FE_BLUE_OFFSET),
};

static void add_frontend_registers(proto_tree *tree, tvbuff_t *tvb, uint8_t packet_addrs[], uint8_t packet_vals[])
{
  uint8_t addr = 0xFF;
  uint16_t value = 0x00;

  for (size_t packet_idx = 0; packet_addrs[packet_idx] != 0; packet_idx++)
  {
    const uint8_t packet_addr = packet_addrs[packet_idx];
    const uint8_t packet_val = packet_vals[packet_idx];

    if (packet_addr == reg_dat_FEWRA.addr)
      addr = packet_val;
    if (packet_addr == reg_dat_FEWRDATA8.addr)
      value |= (packet_val & 0x01) << 8;
    if (packet_addr == reg_dat_FEWRDATA7_0.addr)
      value |= packet_val;
  }

  if (addr == 0xFF)
    return;
  for (size_t fe_reg_idx = 0; fe_reg_idx < array_length(fe_reg_index); fe_reg_idx++)
  {
    const fe_reg_data_t *fe_reg = fe_reg_index[fe_reg_idx];
    if (addr == fe_reg->addr)
    {
      proto_item_set_generated(proto_tree_add_uint(tree, *fe_reg->handle, tvb, 11, 2, value & fe_reg->mask));
    }
  }
}

static void add_register_ranges(proto_tree *tree, tvbuff_t *tvb,
                                uint8_t packet_addrs[], uint8_t packet_val[])
{
  // check each range in turn
  for (size_t range_idx = 0; range_idx < array_length(reg_ranges);
       range_idx++)
  {
    uint32_t range_val = 0;
    const reg_range_t range = reg_ranges[range_idx];
    bool match = false;
    for (unsigned int addr_idx = 0; range.addresses[addr_idx] != 0;
         addr_idx++)
    {
      range_val = range_val << 8;
      const uint8_t range_addr = range.addresses[addr_idx];
      const uint8_t range_mask = range.masks[addr_idx];

      // for each register address in the packet
      for (unsigned int packet_addr_idx = 0; packet_addrs[packet_addr_idx] != 0;
           packet_addr_idx++)
      {
        // skip packet registers that are not this range's address
        if (range_addr != packet_addrs[packet_addr_idx])
          continue;

        // assign the packet value (masked with the range mask) to the lowest
        // bits of range_val (we'll shift leftward as we scan through the range)
        range_val |= (packet_val[packet_addr_idx] & range_mask);
        match = true;
        break;
      }
    }
    if (!match)
      continue;
    proto_item_set_generated(
        proto_tree_add_uint(tree, range.handle, tvb, 0, 0, range_val));
  }
}

static void add_registers(proto_tree *tree, tvbuff_t *tvb, uint8_t offset,
                          uint8_t value, column_info *col)
{
  const uint8_t addr = tvb_get_uint8(tvb, offset);
  proto_tree_add_uint(tree, hf_field_register, tvb, offset, 1, addr);
  if (col)
    col_append_str(col, COL_INFO, " ");

  uint8_t first = true;
  for (size_t search_idx = 0; search_idx < array_length(register_index);
       search_idx++)
  {
    const __auto_type dat = register_index[search_idx];
    if (dat->addr == addr)
    {
      proto_tree_add_uint(tree, *dat->handle, tvb, offset, 1,
                          value & dat->mask);
      if (first)
      {
        first = false;
      }
      else if (col)
        col_append_str(col, COL_INFO, "/");

      if (col)
        col_append_str(col, COL_INFO, dat->name);
    }
  }
  if (first)
  {
    proto_tree_add_boolean(tree, hf_field_register_missing, tvb, offset, 1,
                           TRUE);
    if (col)
    {
      col_append_fstr(col, COL_INFO, "Reg(0x%02x)", addr);
    }
  }
}

static int dissect_gl843(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
                         void *data)
{
  urb_info_t *urb = (urb_info_t *)data;
  if (!urb)
  {
    return 0;
  }

  usb_trans_info_t *usb_trans_info = urb->usb_trans_info;

  if (!usb_trans_info)
  {
    return 0;
  }

  const uint8_t commandType = usb_trans_info->setup.request,
                operation = usb_trans_info->setup.wValue;

  if (commandType != REQUEST_REGISTER && commandType != REQUEST_BUFFER)
  {
    return 0;
  }

  col_set_str(pinfo->cinfo, COL_PROTOCOL, "gl843");
  __auto_type proto = proto_tree_add_protocol_format(
      tree, hf_field_gl843, tvb, 0, 0, "Genesys Logic GL843");
  __auto_type subtree = proto_item_add_subtree(proto, ett_gl843);
  proto_tree_add_uint(subtree, hf_field_command_type, tvb, 0, 1, commandType);
  proto_tree_add_uint(subtree, hf_field_operation, tvb, 1, 2, operation);

  if (urb->direction == 1)
  {
    col_set_str(pinfo->cinfo, COL_INFO, "IN  ");
  }
  else
  {
    col_set_str(pinfo->cinfo, COL_INFO, "OUT ");
  }

  col_append_str(pinfo->cinfo, COL_INFO,
                 val_to_str_const(commandType, command_type_vals, "cmd(%x)"));
  col_append_str(pinfo->cinfo, COL_INFO, " ");
  col_append_str(pinfo->cinfo, COL_INFO,
                 val_to_str_const(operation, operation_vals, "operation(%x)"));

  if (commandType == REQUEST_REGISTER)
  {
    if (operation == VALUE_SET_REGISTER && urb->is_request)
    {
      add_registers(subtree, tvb, 7, 0, pinfo->cinfo);
    }

    if (operation == VALUE_GET_REGISTER)
    {
      if (urb->is_request)
      {
        if (urb->direction == 1)
        {
          col_set_str(pinfo->cinfo, COL_INFO, "IN  ");
        }
        else
        {
          col_set_str(pinfo->cinfo, COL_INFO, "OUT ");
        }

        const uint8_t cmd = tvb_get_uint8(tvb, 3);
        switch (cmd)
        {
        case 0x20:
          col_append_str(pinfo->cinfo, COL_INFO,
                         "write front-end (ADC) register");
          break;
        case 0x21:
          col_append_str(pinfo->cinfo, COL_INFO,
                         "read front-end (ADC) register");
          break;
        }
      }
      else
      {
        col_append_fstr(pinfo->cinfo, COL_INFO, " len=%d",
                        tvb_get_uint8(tvb, 0));
      }
    }
    if (operation == VALUE_READ_REGISTER && !urb->is_request)
    {
      col_append_str(pinfo->cinfo, COL_INFO, " ");
      uint8_t b = tvb_get_uint8(tvb, 0);
      col_append_fstr(pinfo->cinfo, COL_INFO,
                      "[%d%d%d%d %d%d%d%d] [0x%02x] [%d]", (b & 1 << 7) >> 7,
                      (b & 1 << 6) >> 6, (b & 1 << 5) >> 5, (b & 1 << 4) >> 4,
                      (b & 1 << 3) >> 3, (b & 1 << 2) >> 2, (b & 1 << 1) >> 1,
                      (b & 1 << 0) >> 0, b, b);
    }
  }

  if (commandType == REQUEST_BUFFER)
  {
    if (operation == VALUE_SET_REGISTER && urb->is_request)
    {
      // bytes of data
      const uint8_t len = tvb_get_uint8(tvb, 5) / 2;
      uint8_t *addrs = malloc((len + 1) * sizeof(uint8_t));
      addrs[len] = 0;
      uint8_t *values = malloc((len + 1) * sizeof(uint8_t));
      values[len] = 0;

      for (uint8_t i = 0; i < len; i++)
      {
        const uint8_t addr = tvb_get_uint8(tvb, 7 + i * 2);
        const uint8_t val = tvb_get_uint8(tvb, 8 + i * 2);
        addrs[i] = addr;
        values[i] = val;
        add_registers(subtree, tvb, 7 + i * 2, val, pinfo->cinfo);
      }
      add_register_ranges(subtree, tvb, addrs, values);
      add_frontend_registers(subtree, tvb, addrs, values);
      // TODO(tricia) render certain buffer sets (like FEWRA) more directly
    }
  }

  return tvb_reported_length(tvb);
}

void proto_register_gl843(void)
{
  proto_gl843 = proto_register_protocol("gl843", "gl843", "gl843");

  proto_register_field_array(proto_gl843, hf, array_length(hf));
  static int *ett[] = {&ett_gl843};
  proto_register_subtree_array(ett, array_length(ett));

  gl843_handle = register_dissector("gl843", dissect_gl843, proto_gl843);
}

void proto_reg_handoff_gl843(void)
{
  dissector_add_for_decode_as("usb.product", gl843_handle);
  dissector_add_uint("usb.product", 0x07b30c3a, gl843_handle);
}
