dnp3_al_process_object(tvbuff_t *tvb, packet_info *pinfo, int offset,
                       proto_tree *robj_tree, gboolean header_only,
                       guint16 *al_objtype, nstime_t *al_cto)
{

  guint8      al_objq, al_objq_prefix, al_objq_range, al_oct_len = 0, bitindex;
  guint16     al_obj, temp;
  guint32     al_ptaddr = 0;
  int         num_items = 0;
  int         orig_offset, rangebytes = 0;
  proto_item *object_item, *range_item;
  proto_tree *object_tree, *qualifier_tree, *range_tree;

  orig_offset = offset;

  /* Application Layer Objects in this Message */
  *al_objtype =
  al_obj = tvb_get_ntohs(tvb, offset);

  /* Special handling for Octet string objects as the variation is the length of the string */
  temp = al_obj & 0xFF00;
  if ((temp == AL_OBJ_OCT) || (temp == AL_OBJ_OCT_EVT )) {
    al_oct_len = al_obj & 0xFF;
    al_obj = temp;
  }

  /* Create Data Objects Detail Tree */
  if (AL_OBJ_GROUP(al_obj) == 0x0) {
    object_item = proto_tree_add_uint_format(robj_tree, hf_dnp3_al_obj, tvb, offset, 2, al_obj,
                                             "Object(s): %s (0x%04x)",
                                             val_to_str_ext_const(al_obj, &dnp3_al_obj_vals_ext, "Unknown group 0 Variation"),
                                             al_obj);
    if (try_val_to_str_ext(al_obj, &dnp3_al_obj_vals_ext) == NULL) {
      expert_add_info(pinfo, object_item, &ei_dnp3_unknown_group0_variation);
    }
  }
  else {
    object_item = proto_tree_add_uint_format(robj_tree, hf_dnp3_al_obj, tvb, offset, 2, al_obj,
                                             "Object(s): %s (0x%04x)",
                                             val_to_str_ext_const(al_obj, &dnp3_al_obj_vals_ext, "Unknown Object\\Variation"),
                                             al_obj);
    if (try_val_to_str_ext(al_obj, &dnp3_al_obj_vals_ext) == NULL) {
      expert_add_info(pinfo, object_item, &ei_dnp3_unknown_object);
    }
  }
  object_tree = proto_item_add_subtree(object_item, ett_dnp3_al_obj);

  offset += 2;

  /* Object Qualifier */
  al_objq = tvb_get_guint8(tvb, offset);
  al_objq_prefix = al_objq & AL_OBJQ_PREFIX;
  al_objq_prefix = al_objq_prefix >> 4;
  al_objq_range = al_objq & AL_OBJQ_RANGE;

  qualifier_tree = proto_tree_add_subtree_format(object_tree, tvb, offset, 1, ett_dnp3_al_obj_qualifier, NULL,
    "Qualifier Field, Prefix: %s, Range: %s",
    val_to_str_ext_const(al_objq_prefix, &dnp3_al_objq_prefix_vals_ext, "Unknown Prefix Type"),
    val_to_str_ext_const(al_objq_range, &dnp3_al_objq_range_vals_ext, "Unknown Range Type"));
  proto_tree_add_item(qualifier_tree, hf_dnp3_al_objq_prefix, tvb, offset, 1, ENC_BIG_ENDIAN);
  proto_tree_add_item(qualifier_tree, hf_dnp3_al_objq_range, tvb, offset, 1, ENC_BIG_ENDIAN);

  offset += 1;

  /* Create (possibly synthesized) number of items and range field tree */
  range_tree = proto_tree_add_subtree(object_tree, tvb, offset, 0, ett_dnp3_al_obj_range, &range_item, "Number of Items: ");

  switch (al_objq_range)
  {
    case AL_OBJQL_RANGE_SSI8:           /* 8-bit Start and Stop Indices in Range Field */
      num_items = ( tvb_get_guint8(tvb, offset+1) - tvb_get_guint8(tvb, offset) + 1);
      proto_item_set_generated(range_item);
      al_ptaddr = tvb_get_guint8(tvb, offset);
      proto_tree_add_item(range_tree, hf_dnp3_al_range_start8, tvb, offset, 1, ENC_LITTLE_ENDIAN);
      proto_tree_add_item(range_tree, hf_dnp3_al_range_stop8, tvb, offset + 1, 1, ENC_LITTLE_ENDIAN);
      rangebytes = 2;
      break;
    case AL_OBJQL_RANGE_SSI16:          /* 16-bit Start and Stop Indices in Range Field */
      num_items = ( tvb_get_letohs(tvb, offset+2) - tvb_get_letohs(tvb, (offset)) + 1);
      proto_item_set_generated(range_item);
      al_ptaddr = tvb_get_letohs(tvb, offset);
      proto_tree_add_item(range_tree, hf_dnp3_al_range_start16, tvb, offset, 2, ENC_LITTLE_ENDIAN);
      proto_tree_add_item(range_tree, hf_dnp3_al_range_stop16, tvb, offset + 2, 2, ENC_LITTLE_ENDIAN);
      rangebytes = 4;
      break;
    case AL_OBJQL_RANGE_SSI32:          /* 32-bit Start and Stop Indices in Range Field */
      num_items = ( tvb_get_letohl(tvb, offset+4) - tvb_get_letohl(tvb, offset) + 1);
      proto_item_set_generated(range_item);
      al_ptaddr = tvb_get_letohl(tvb, offset);
      proto_tree_add_item(range_tree, hf_dnp3_al_range_start32, tvb, offset, 4, ENC_LITTLE_ENDIAN);
      proto_tree_add_item(range_tree, hf_dnp3_al_range_stop32, tvb, offset + 4, 4, ENC_LITTLE_ENDIAN);
      rangebytes = 8;
      break;
    case AL_OBJQL_RANGE_AA8:            /* 8-bit Absolute Address in Range Field */
      num_items = 1;
      proto_item_set_generated(range_item);
      al_ptaddr = tvb_get_guint8(tvb, offset);
      proto_tree_add_item(range_tree, hf_dnp3_al_range_abs8, tvb, offset, 1, ENC_LITTLE_ENDIAN);
      rangebytes = 1;
      break;
    case AL_OBJQL_RANGE_AA16:           /* 16-bit Absolute Address in Range Field */
      num_items = 1;
      proto_item_set_generated(range_item);
      al_ptaddr = tvb_get_letohs(tvb, offset);
      proto_tree_add_item(range_tree, hf_dnp3_al_range_abs16, tvb, offset, 2, ENC_LITTLE_ENDIAN);
      rangebytes = 2;
      break;
    case AL_OBJQL_RANGE_AA32:           /* 32-bit Absolute Address in Range Field */
      num_items = 1;
      proto_item_set_generated(range_item);
      al_ptaddr = tvb_get_letohl(tvb, offset);
      proto_tree_add_item(range_tree, hf_dnp3_al_range_abs32, tvb, offset, 4, ENC_LITTLE_ENDIAN);
      rangebytes = 4;
      break;
    case AL_OBJQL_RANGE_SF8:            /* 8-bit Single Field Quantity in Range Field */
      num_items = tvb_get_guint8(tvb, offset);
      proto_tree_add_item(range_tree, hf_dnp3_al_range_quant8, tvb, offset, 1, ENC_LITTLE_ENDIAN);
      rangebytes = 1;
      proto_item_set_len(range_item, rangebytes);
      break;
    case AL_OBJQL_RANGE_SF16:           /* 16-bit Single Field Quantity in Range Field */
      num_items = tvb_get_letohs(tvb, offset);
      proto_tree_add_item(range_tree, hf_dnp3_al_range_quant16, tvb, offset, 2, ENC_LITTLE_ENDIAN);
      rangebytes = 2;
      proto_item_set_len(range_item, rangebytes);
      break;
    case AL_OBJQL_RANGE_SF32:           /* 32-bit Single Field Quantity in Range Field */
      num_items = tvb_get_letohl(tvb, offset);
      proto_tree_add_item(range_tree, hf_dnp3_al_range_quant32, tvb, offset, 4, ENC_LITTLE_ENDIAN);
      rangebytes = 4;
      proto_item_set_len(range_item, rangebytes);
      break;
    case AL_OBJQL_RANGE_FF:            /* 8 bit object count in Range Field */
      num_items = tvb_get_guint8(tvb, offset);
      proto_tree_add_item(range_tree, hf_dnp3_al_range_quant8, tvb, offset, 1, ENC_LITTLE_ENDIAN);
      rangebytes = 1;
      proto_item_set_len(range_item, rangebytes);
  }
  if (num_items > 0) {
    proto_item_append_text(object_item, ", %d point%s", num_items, plurality(num_items, "", "s"));
  }
  proto_item_append_text(range_item, "%d", num_items);

  /* A negative number of items is an error */
  if (num_items < 0) {
    proto_item_append_text(range_item, " (bogus)");
    expert_add_info(pinfo, range_item, &ei_dnp_num_items_neg);
    return tvb_captured_length(tvb);
  }

  /* Move offset past any range field */
  offset += rangebytes;

  bitindex = 0; /* Temp variable for cycling through points when object values are encoded into
                   bits; primarily objects 0x0101, 0x0301 & 0x1001 */

  /* Only process the point information for replies or items with point index lists */
  if (!header_only || al_objq_prefix > 0) {
    int item_num;
    int start_offset;

    start_offset = offset;
    for (item_num = 0; item_num < num_items; item_num++)
    {
      proto_item *point_item;
      proto_tree *point_tree;
      guint       data_pos;
      int         prefixbytes;

      /* Create Point item and process prefix */
      if (al_objq_prefix <= AL_OBJQL_PREFIX_4O) {
        point_tree = proto_tree_add_subtree(object_tree, tvb, offset, -1, ett_dnp3_al_obj_point, &point_item, "Point Number");
      }
      else {
        point_tree = proto_tree_add_subtree(object_tree, tvb, offset, -1, ett_dnp3_al_obj_point, &point_item, "Object: Size");
      }

      data_pos   = offset;
      prefixbytes = dnp3_al_obj_procprefix(tvb, offset, al_objq_prefix, &al_ptaddr, point_tree);

      /* If this is an 'empty' object type and the num_items field is not equal to zero,
         then the packet is potentially malicious */
      if ((num_items != 0) && (dnp3_al_empty_obj(al_obj))) {
        proto_item_append_text(range_item, " (bogus)");
        expert_add_info(pinfo, range_item, &ei_dnp3_num_items_invalid);
        num_items = 0;
      }

      proto_item_append_text(point_item, " %u", al_ptaddr);
      proto_item_set_len(point_item, prefixbytes);
      data_pos += prefixbytes;

      if (!header_only || (AL_OBJQL_PREFIX_1OS <= al_objq_prefix && al_objq_prefix <= AL_OBJQL_PREFIX_4OS)) {
        /* Process the object values */
        guint8       al_2bit, al_ptflags, al_bi_val, al_tcc_code;
        gint16       al_val_int16;
        guint16      al_val_uint16, al_ctlobj_stat;
        guint16      al_relms, al_filename_len, al_file_ctrl_mode;
        gint32       al_val_int32;
        guint32      al_val_uint32, file_data_size;
        nstime_t     al_reltime, al_abstime;
        gboolean     al_bit;
        gfloat       al_valflt;
        gdouble      al_valdbl;
        const gchar *ctl_status_str;

        /* Device Attributes (g0) all have a type code, use that rather than the individual variation */
        if (AL_OBJ_GROUP(al_obj) == 0x0) {
          guint32 data_type;
          guint8 da_len;

          /* Add and retrieve the data type */
          proto_tree_add_item_ret_uint(point_tree, hf_dnp3_al_datatype, tvb, data_pos, 1, ENC_LITTLE_ENDIAN, &data_type);
          data_pos++;

          /* If a valid data type process it */
          if (try_val_to_str(data_type, dnp3_al_data_type_vals) != NULL) {
            switch(data_type) {
              case AL_DATA_TYPE_NONE:
                break;
              case AL_DATA_TYPE_VSTR:
                da_len = tvb_get_guint8(tvb, data_pos);
                proto_tree_add_item(point_tree, hf_dnp3_al_da_length, tvb, data_pos, 1, ENC_LITTLE_ENDIAN);
                data_pos++;
                const guint8* da_value;
                proto_tree_add_item_ret_string(point_tree, hf_dnp3_al_da_value, tvb, data_pos, da_len, ENC_ASCII|ENC_NA, wmem_packet_scope(), &da_value);
                proto_item_append_text(object_item, ", Value: %s", da_value);
                data_pos += da_len;
                break;
              case AL_DATA_TYPE_UINT:
                da_len = tvb_get_guint8(tvb, data_pos);
                proto_tree_add_item(point_tree, hf_dnp3_al_da_length, tvb, data_pos, 1, ENC_LITTLE_ENDIAN);
                data_pos++;
                if (da_len == 1) {
                  proto_tree_add_item(point_tree, hf_dnp3_al_da_uint8, tvb, data_pos, 1, ENC_LITTLE_ENDIAN);
                  proto_item_append_text(object_item, ", Value: %u", tvb_get_guint8(tvb, data_pos));
                  data_pos++;
                }
                else if (da_len == 2) {
                  proto_tree_add_item(point_tree, hf_dnp3_al_da_uint16, tvb, data_pos, 2, ENC_LITTLE_ENDIAN);
                  proto_item_append_text(object_item, ", Value: %u", tvb_get_letohs(tvb, data_pos));
                  data_pos += 2;
                }
                else if (da_len == 4) {
                  proto_tree_add_item(point_tree, hf_dnp3_al_da_uint32, tvb, data_pos, 4, ENC_LITTLE_ENDIAN);
                  proto_item_append_text(object_item, ", Value: %u", tvb_get_letohl(tvb, data_pos));
                  data_pos += 4;
                }
                break;
              case AL_DATA_TYPE_INT:
                da_len = tvb_get_guint8(tvb, data_pos);
                proto_tree_add_item(point_tree, hf_dnp3_al_da_length, tvb, data_pos, 1, ENC_LITTLE_ENDIAN);
                data_pos++;
                if (da_len == 1) {
                  proto_tree_add_item(point_tree, hf_dnp3_al_da_int8, tvb, data_pos, 1, ENC_LITTLE_ENDIAN);
                  proto_item_append_text(object_item, ", Value: %d", tvb_get_guint8(tvb, data_pos));
                  data_pos++;
                }
                else if (da_len == 2) {
                  proto_tree_add_item(point_tree, hf_dnp3_al_da_int16, tvb, data_pos, 2, ENC_LITTLE_ENDIAN);
                  proto_item_append_text(object_item, ", Value: %d", tvb_get_letohs(tvb, data_pos));
                  data_pos += 2;
                }
                else if (da_len == 4) {
                  proto_tree_add_item(point_tree, hf_dnp3_al_da_int32, tvb, data_pos, 4, ENC_LITTLE_ENDIAN);
                  proto_item_append_text(object_item, ", Value: %d", tvb_get_letohl(tvb, data_pos));
                  data_pos += 4;
                }
                break;
              case AL_DATA_TYPE_FLT:
                da_len = tvb_get_guint8(tvb, data_pos);
                proto_tree_add_item(point_tree, hf_dnp3_al_da_length, tvb, data_pos, 1, ENC_LITTLE_ENDIAN);
                data_pos++;
                if (da_len == 4) {
                  proto_tree_add_item(point_tree, hf_dnp3_al_da_flt, tvb, data_pos, 4, ENC_LITTLE_ENDIAN);
                  proto_item_append_text(object_item, ", Value: %g", tvb_get_letohieee_float(tvb, data_pos));
                  data_pos += 4;
                }
                else if (da_len == 8) {
                  proto_tree_add_item(point_tree, hf_dnp3_al_da_dbl, tvb, data_pos, 8, ENC_LITTLE_ENDIAN);
                  proto_item_append_text(object_item, ", Value: %g", tvb_get_letohieee_double(tvb, data_pos));
                  data_pos += 8;
                }
                break;
              case AL_DATA_TYPE_OSTR:
                break;
              case AL_DATA_TYPE_BSTR:
                break;
              case AL_DATA_TYPE_TIME:
                break;
              case AL_DATA_TYPE_UNCD:
                break;
              case AL_DATA_TYPE_U8BS8LIST:
                break;
              case AL_DATA_TYPE_U8BS8EXLIST:
                break;
            }
          }
          offset = data_pos;
        }
        else {

          /* All other objects are handled here, by their variations */
          switch (al_obj)
          {

            /* There is nothing to handle for the default variations */
            case AL_OBJ_BI_ALL:      /* Binary Input Default Variation (Obj:01, Var:Default) */
            case AL_OBJ_BIC_ALL:     /* Binary Input Change Default Variation (Obj:02, Var:Default) */
            case AL_OBJ_BOC_ALL:     /* Binary Output Event Default Variation (Obj:11, Var:Default) */
            case AL_OBJ_2BI_ALL:     /* Double-bit Input Default Variation (Obj:03, Var:Default) */
            case AL_OBJ_2BIC_ALL:    /* Double-bit Input Change Default Variation (Obj:04, Var:Default) */
            case AL_OBJ_CTR_ALL:     /* Binary Counter Default Variation (Obj:20, Var:Default) */
            case AL_OBJ_CTRC_ALL:    /* Binary Counter Change Default Variation (Obj:22 Var:Default) */
            case AL_OBJ_AI_ALL:      /* Analog Input Default Variation (Obj:30, Var:Default) */
            case AL_OBJ_AIC_ALL:     /* Analog Input Change Default Variation (Obj:32 Var:Default) */
            case AL_OBJ_AIDB_ALL:    /* Analog Input Deadband Default Variation (Obj:34, Var:Default) */
            case AL_OBJ_AOC_ALL:     /* Analog Output Event Default Variation (Obj:42 Var:Default) */

              offset = data_pos;
              break;

            /* Bit-based Data objects here */
            case AL_OBJ_BI_1BIT:    /* Single-Bit Binary Input (Obj:01, Var:01) */
            case AL_OBJ_BO:         /* Binary Output (Obj:10, Var:01) */
            case AL_OBJ_CTL_PMASK:  /* Pattern Mask (Obj:12, Var:03) */
            case AL_OBJ_IIN:        /* Internal Indications - IIN (Obj: 80, Var:01) */

              /* Extract the bit from the packed byte */
              al_bi_val = tvb_get_guint8(tvb, data_pos);
              al_bit = (al_bi_val & 1) > 0;
              if (al_obj == AL_OBJ_IIN) {
                /* For an IIN bit, work out the IIN constant value for the bit position to get the name of the bit */
                guint16 iin_bit = 0;
                if (al_ptaddr < 8) {
                  iin_bit = 0x100 << al_ptaddr;
                }
                else {
                  iin_bit = 1 << (al_ptaddr - 8);
                }
                proto_item_append_text(point_item, " (%s), Value: %u",
                                       val_to_str_const(iin_bit, dnp3_al_iin_vals, "Invalid IIN bit"), al_bit);
              }
              else
              {
                if (al_objq_prefix != AL_OBJQL_PREFIX_NI) {
                  /* Each item has an index prefix, in this case bump
                     the bitindex to force the correct offset adjustment */
                  bitindex = 7;
                }
                else {
                  /* Regular packed bits, get the value at the appropriate bit index */
                  al_bit = (al_bi_val & (1 << bitindex)) > 0;
                }
                proto_item_append_text(point_item, ", Value: %u", al_bit);
              }
              proto_tree_add_boolean(point_tree, hf_dnp3_al_bit, tvb, data_pos, 1, al_bit);
              proto_item_set_len(point_item, prefixbytes + 1);

              /* Increment the bit index for next cycle */
              bitindex++;

              /* If we have counted 8 bits or read the last item,
                 reset bit index and move onto the next byte */
              if ((bitindex > 7) || (item_num == (num_items-1)))
              {
                bitindex = 0;
                offset += (prefixbytes + 1);
              }
              break;

            case AL_OBJ_2BI_NF:    /* Double-bit Input No Flags (Obj:03, Var:01) */

              if (bitindex > 3)
              {
                bitindex = 0;
                offset += (prefixbytes + 1);
              }

              /* Extract the Double-bit from the packed byte */
              al_bi_val = tvb_get_guint8(tvb, offset);
              al_2bit = ((al_bi_val >> (bitindex << 1)) & 3);

              proto_item_append_text(point_item, ", Value: %u", al_2bit);
              proto_tree_add_uint(point_tree, hf_dnp3_al_2bit, tvb, offset, 1, al_2bit);
              proto_item_set_len(point_item, prefixbytes + 1);

              /* If we've read the last item, then move the offset past this byte */
              if (item_num == (num_items-1))
              {
                offset += (prefixbytes + 1);
              }

              /* Increment the bit index for next cycle */
              bitindex++;
              break;

            case AL_OBJ_BI_STAT:    /* Binary Input With Status (Obj:01, Var:02) */
            case AL_OBJ_BIC_NOTIME: /* Binary Input Change Without Time (Obj:02, Var:01) */
            case AL_OBJ_BO_STAT:    /* Binary Output Status (Obj:10, Var:02) */
            case AL_OBJ_BOC_NOTIME: /* Binary Output Change Without Time (Obj:11, Var:01) */

              /* Get Point Flags */
              al_ptflags = tvb_get_guint8(tvb, data_pos);

              switch (al_obj) {
                case AL_OBJ_BI_STAT:
                case AL_OBJ_BIC_NOTIME:
                  dnp3_al_obj_quality(tvb, data_pos, al_ptflags, point_tree, point_item, BIN_IN);
                  break;
                case AL_OBJ_BO_STAT:
                case AL_OBJ_BOC_NOTIME:
                  dnp3_al_obj_quality(tvb, data_pos, al_ptflags, point_tree, point_item, BIN_OUT);
                  break;
              }
              data_pos += 1;

              al_bit = (al_ptflags & AL_OBJ_BI_FLAG7) > 0;
              proto_item_append_text(point_item, ", Value: %u", al_bit);

              proto_item_set_len(point_item, data_pos - offset);

              offset = data_pos;
              break;

            case AL_OBJ_2BI_STAT:    /* Double-bit Input With Status (Obj:03, Var:02) */
            case AL_OBJ_2BIC_NOTIME: /* Double-bit Input Change Without Time (Obj:04, Var:01) */

              /* Get Point Flags */
              al_ptflags = tvb_get_guint8(tvb, data_pos);
              dnp3_al_obj_quality(tvb, data_pos, al_ptflags, point_tree, point_item, BIN_IN);
              data_pos += 1;

              al_2bit = (al_ptflags >> 6) & 3;
              proto_item_append_text(point_item, ", Value: %u", al_2bit);
              proto_item_set_len(point_item, data_pos - offset);

              offset = data_pos;
              break;

            case AL_OBJ_BIC_TIME:   /* Binary Input Change w/ Time (Obj:02, Var:02)  */
            case AL_OBJ_BOC_TIME:   /* Binary Output Change w/ Time (Obj:11, Var:02)  */

              /* Get Point Flags */
              al_ptflags = tvb_get_guint8(tvb, data_pos);
              switch (al_obj) {
                case AL_OBJ_BIC_TIME:
                  dnp3_al_obj_quality(tvb, data_pos, al_ptflags, point_tree, point_item, BIN_IN);
                  break;
                case AL_OBJ_BOC_TIME:
                  dnp3_al_obj_quality(tvb, data_pos, al_ptflags, point_tree, point_item, BIN_OUT);
                  break;
              }
              data_pos += 1;

              /* Get timestamp */
              dnp3_al_get_timestamp(&al_abstime, tvb, data_pos);
              proto_tree_add_time(point_tree, hf_dnp3_al_timestamp, tvb, data_pos, 6, &al_abstime);
              data_pos += 6;

              al_bit = (al_ptflags & AL_OBJ_BI_FLAG7) >> 7; /* bit shift 1xxxxxxx -> xxxxxxx1 */
              proto_item_append_text(point_item, ", Value: %u, Timestamp: %s",
                                     al_bit, abs_time_to_str(wmem_packet_scope(), &al_abstime, ABSOLUTE_TIME_UTC, FALSE));
              proto_item_set_len(point_item, data_pos - offset);

              offset = data_pos;
              break;

            case AL_OBJ_2BIC_TIME:   /* Double-bit Input Change w/ Time (Obj:04, Var:02)  */

              /* Get Point Flags */
              al_ptflags = tvb_get_guint8(tvb, data_pos);
              dnp3_al_obj_quality(tvb, (offset+prefixbytes), al_ptflags, point_tree, point_item, BIN_IN);
              data_pos += 1;

              /* Get timestamp */
              dnp3_al_get_timestamp(&al_abstime, tvb, data_pos);
              proto_tree_add_time(point_tree, hf_dnp3_al_timestamp, tvb, data_pos, 6, &al_abstime);
              data_pos += 6;

              al_2bit = (al_ptflags >> 6) & 3; /* bit shift 11xxxxxx -> 00000011 */
              proto_item_append_text(point_item, ", Value: %u, Timestamp: %s",
                                     al_2bit, abs_time_to_str(wmem_packet_scope(), &al_abstime, ABSOLUTE_TIME_UTC, FALSE));
              proto_item_set_len(point_item, data_pos - offset);

              offset = data_pos;
              break;

            case AL_OBJ_BIC_RTIME:   /* Binary Input Change w/ Relative Time (Obj:02, Var:03)  */

              /* Get Point Flags */
              al_ptflags = tvb_get_guint8(tvb, data_pos);
              dnp3_al_obj_quality(tvb, data_pos, al_ptflags, point_tree, point_item, BIN_IN);
              data_pos += 1;

              /* Get relative time in ms, and convert to ns_time */
              al_relms = tvb_get_letohs(tvb, data_pos);
              al_reltime.secs = al_relms / 1000;
              al_reltime.nsecs = (al_relms % 1000) * 1000000;
              /* Now add to CTO time */
              nstime_sum(&al_abstime, al_cto, &al_reltime);
              proto_tree_add_time(point_tree, hf_dnp3_al_rel_timestamp, tvb, data_pos, 2, &al_reltime);
              data_pos += 2;

              al_bit = (al_ptflags & AL_OBJ_BI_FLAG7) >> 7; /* bit shift 1xxxxxxx -> xxxxxxx1 */
              proto_item_append_text(point_item, ", Value: %u, Timestamp: %s",
                                     al_bit, abs_time_to_str(wmem_packet_scope(), &al_abstime, ABSOLUTE_TIME_UTC, FALSE));
              proto_item_set_len(point_item, data_pos - offset);

              offset = data_pos;
              break;

            case AL_OBJ_CTLOP_BLK: /* Control Relay Output Block (Obj:12, Var:01) */
            case AL_OBJ_CTL_PCB: /* Pattern Control Block (Obj:12, Var:02) */
            {
              proto_tree  *tcc_tree;

              /* Add a expand/collapse for TCC */
              al_tcc_code = tvb_get_guint8(tvb, data_pos);
              tcc_tree = proto_tree_add_subtree_format(point_tree, tvb, data_pos, 1,
                          ett_dnp3_al_obj_point_tcc, NULL, "Control Code [0x%02x]",al_tcc_code);

              /* Add the Control Code to the Point number list for quick visual reference as to the operation */
              proto_item_append_text(point_item, " [%s]", val_to_str_const((al_tcc_code & AL_OBJCTLC_CODE),
                                                                           dnp3_al_ctlc_code_vals,
                                                                           "Invalid Operation"));

              /* Add Trip/Close qualifier (if applicable) to previously appended quick visual reference */
              proto_item_append_text(point_item, " [%s]", val_to_str_const((al_tcc_code & AL_OBJCTLC_TC) >> 6,
                                                                           dnp3_al_ctlc_tc_vals,
                                                                           "Invalid Qualifier"));

              /* Control Code 'Operation Type' */
              proto_tree_add_item(tcc_tree, hf_dnp3_ctlobj_code_c, tvb, data_pos, 1, ENC_LITTLE_ENDIAN);

              /* Control Code Misc Values */
              proto_tree_add_item(tcc_tree, hf_dnp3_ctlobj_code_m, tvb, data_pos, 1, ENC_LITTLE_ENDIAN);

              /* Control Code 'Trip Close Code' */
              proto_tree_add_item(tcc_tree, hf_dnp3_ctlobj_code_tc, tvb, data_pos, 1, ENC_LITTLE_ENDIAN);
              data_pos += 1;

              /* Get "Count" Field */
              proto_tree_add_item(point_tree, hf_dnp3_al_count, tvb, data_pos, 1, ENC_LITTLE_ENDIAN);
              data_pos += 1;

              /* Get "On Time" Field */
              proto_tree_add_item(point_tree, hf_dnp3_al_on_time, tvb, data_pos, 4, ENC_LITTLE_ENDIAN);
              data_pos += 4;

              /* Get "Off Time" Field */
              proto_tree_add_item(point_tree, hf_dnp3_al_off_time, tvb, data_pos, 4, ENC_LITTLE_ENDIAN);
              data_pos += 4;

              /* Get "Control Status" Field */
              proto_tree_add_item(point_tree, hf_dnp3_al_ctrlstatus, tvb, data_pos, 1, ENC_LITTLE_ENDIAN);
              data_pos += 1;

              proto_item_set_len(point_item, data_pos - offset);

              offset = data_pos;
              break;
            }

            case AL_OBJ_BOE_NOTIME: /* Binary Command Event (Obj:13, Var:01) */
            case AL_OBJ_BOE_TIME:   /* Binary Command Event with time (Obj:13, Var:02) */
            case AL_OBJ_AOC_32EVNT:   /* 32-bit Analog Command Event (Obj:43, Var:01) */
            case AL_OBJ_AOC_16EVNT:   /* 16-bit Analog Command Event (Obj:43, Var:02) */
            case AL_OBJ_AOC_32EVTT:   /* 32-bit Analog Command Event with time (Obj:43, Var:03) */
            case AL_OBJ_AOC_16EVTT:   /* 16-bit Analog Command Event with time (Obj:43, Var:04) */
            case AL_OBJ_AOC_FLTEVNT:   /* 32-bit Floating Point Analog Command Event (Obj:43, Var:05) */
            case AL_OBJ_AOC_DBLEVNT:   /* 64-bit Floating Point Analog Command Event (Obj:43, Var:06) */
            case AL_OBJ_AOC_FLTEVTT:   /* 32-bit Floating Point Analog Command Event with time (Obj:43, Var:07) */
            case AL_OBJ_AOC_DBLEVTT:   /* 64-bit Floating Point Analog Command Event with time (Obj:43, Var:08) */
            {
              /* Get the status code */
              al_ctlobj_stat = tvb_get_guint8(tvb, data_pos) & AL_OBJCTL_STATUS_MASK;
              ctl_status_str = val_to_str_ext(al_ctlobj_stat, &dnp3_al_ctl_status_vals_ext, "Invalid Status (0x%02x)");
              proto_item_append_text(point_item, " [Status: %s (0x%02x)]", ctl_status_str, al_ctlobj_stat);
              proto_tree_add_item(point_tree, hf_dnp3_al_ctrlstatus, tvb, data_pos, 1, ENC_LITTLE_ENDIAN);

              /* Get the command value */
              switch(al_obj)
              {
                case AL_OBJ_BOE_NOTIME:
                case AL_OBJ_BOE_TIME:
                  proto_tree_add_item(point_tree, hf_dnp3_bocs_bit, tvb, data_pos, 1, ENC_LITTLE_ENDIAN);
                  data_pos += 1;
                  break;
                case AL_OBJ_AOC_32EVNT:
                case AL_OBJ_AOC_32EVTT:
                  data_pos += 1; /* Step past status */
                  al_val_int32 = tvb_get_letohl(tvb, data_pos);
                  proto_item_append_text(point_item, ", Value: %d", al_val_int32);
                  proto_tree_add_item(point_tree, hf_dnp3_al_anaout32, tvb, data_pos, 4, ENC_LITTLE_ENDIAN);
                  break;
                case AL_OBJ_AOC_16EVNT:
                case AL_OBJ_AOC_16EVTT:
                  data_pos += 1; /* Step past status */
                  al_val_int16 = tvb_get_letohs(tvb, data_pos);
                  proto_item_append_text(point_item, ", Value: %d", al_val_int16);
                  proto_tree_add_item(point_tree, hf_dnp3_al_anaout16, tvb, data_pos, 2, ENC_LITTLE_ENDIAN);
                  data_pos += 2;
                  break;
                case AL_OBJ_AOC_FLTEVNT:
                case AL_OBJ_AOC_FLTEVTT:
                  data_pos += 1; /* Step past status */
                  al_valflt = tvb_get_letohieee_float(tvb, data_pos);
                  proto_item_append_text(point_item, ", Value: %g", al_valflt);
                  proto_tree_add_item(point_tree, hf_dnp3_al_anaoutflt, tvb, data_pos, 4, ENC_LITTLE_ENDIAN);
                  data_pos += 4;
                  break;
                case AL_OBJ_AOC_DBLEVNT:
                case AL_OBJ_AOC_DBLEVTT:
                  data_pos += 1; /* Step past status */
                  al_valdbl = tvb_get_letohieee_double(tvb, data_pos);
                  proto_item_append_text(point_item, ", Value: %g", al_valdbl);
                  proto_tree_add_item(point_tree, hf_dnp3_al_anaoutdbl, tvb, data_pos, 8, ENC_LITTLE_ENDIAN);
                  data_pos += 8;
                  break;
              }

              /* Get the timestamp */
              switch(al_obj)
              {
                case AL_OBJ_BOE_TIME:   /* Binary Command Event with time (Obj:13, Var:02) */
                case AL_OBJ_AOC_32EVTT:   /* 32-bit Analog Command Event with time (Obj:43, Var:03) */
                case AL_OBJ_AOC_16EVTT:   /* 16-bit Analog Command Event with time (Obj:43, Var:04) */
                case AL_OBJ_AOC_FLTEVTT:   /* 32-bit Floating Point Analog Command Event with time (Obj:43, Var:07) */
                case AL_OBJ_AOC_DBLEVTT:   /* 64-bit Floating Point Analog Command Event with time (Obj:43, Var:08) */
                  dnp3_al_get_timestamp(&al_abstime, tvb, data_pos);
                  proto_item_append_text(point_item, ", Timestamp: %s", abs_time_to_str(wmem_packet_scope(), &al_abstime, ABSOLUTE_TIME_UTC, FALSE));
                  proto_tree_add_time(point_tree, hf_dnp3_al_timestamp, tvb, data_pos, 6, &al_abstime);
                  data_pos += 6;
                break;
              }

              proto_item_set_len(point_item, data_pos - offset);
              offset = data_pos;
              break;
            }

            case AL_OBJ_AO_32OPB:   /* 32-Bit Analog Output Block (Obj:41, Var:01) */
            case AL_OBJ_AO_16OPB:   /* 16-Bit Analog Output Block (Obj:41, Var:02) */
            case AL_OBJ_AO_FLTOPB:  /* 32-Bit Floating Point Output Block (Obj:41, Var:03) */
            case AL_OBJ_AO_DBLOPB:  /* 64-Bit Floating Point Output Block (Obj:41, Var:04) */

              switch (al_obj)
              {
                case AL_OBJ_AO_32OPB:
                  al_val_int32 = tvb_get_letohl(tvb, data_pos);
                  proto_item_append_text(point_item, ", Value: %d", al_val_int32);
                  proto_tree_add_item(point_tree, hf_dnp3_al_anaout32, tvb, data_pos, 4, ENC_LITTLE_ENDIAN);
                  data_pos += 4;
                  break;
                case AL_OBJ_AO_16OPB:
                  al_val_int16 = tvb_get_letohs(tvb, data_pos);
                  proto_item_append_text(point_item, ", Value: %d", al_val_int16);
                  proto_tree_add_item(point_tree, hf_dnp3_al_anaout16, tvb, data_pos, 2, ENC_LITTLE_ENDIAN);
                  data_pos += 2;
                  break;
                case AL_OBJ_AO_FLTOPB:
                  al_valflt = tvb_get_letohieee_float(tvb, data_pos);
                  proto_item_append_text(point_item, ", Value: %g", al_valflt);
                  proto_tree_add_item(point_tree, hf_dnp3_al_anaoutflt, tvb, data_pos, 4, ENC_LITTLE_ENDIAN);
                  data_pos += 4;
                  break;
                case AL_OBJ_AO_DBLOPB:
                  al_valdbl = tvb_get_letohieee_double(tvb, data_pos);
                  proto_item_append_text(point_item, ", Value: %g", al_valdbl);
                  proto_tree_add_item(point_tree, hf_dnp3_al_anaoutdbl, tvb, data_pos, 8, ENC_LITTLE_ENDIAN);
                  data_pos += 8;
                  break;
              }

              /* Get control status */
              al_ctlobj_stat = tvb_get_guint8(tvb, data_pos) & AL_OBJCTL_STATUS_MASK;
              ctl_status_str = val_to_str_ext(al_ctlobj_stat, &dnp3_al_ctl_status_vals_ext, "Invalid Status (0x%02x)");
              proto_item_append_text(point_item, " [Status: %s (0x%02x)]", ctl_status_str, al_ctlobj_stat);
              proto_tree_add_item(point_tree, hf_dnp3_al_ctrlstatus, tvb, data_pos, 1, ENC_LITTLE_ENDIAN);
              data_pos += 1;

              proto_item_set_len(point_item, data_pos - offset);

              offset = data_pos;
              break;

            case AL_OBJ_CTR_32:     /* 32-Bit Binary Counter (Obj:20, Var:01) */
            case AL_OBJ_CTR_16:     /* 16-Bit Binary Counter (Obj:20, Var:02) */
            case AL_OBJ_DCTR_32:    /* 32-Bit Binary Delta Counter (Obj:20, Var:03) */
            case AL_OBJ_DCTR_16:    /* 16-Bit Binary Delta Counter (Obj:20, Var:04) */
            case AL_OBJ_CTR_32NF:   /* 32-Bit Binary Counter Without Flag (Obj:20, Var:05) */
            case AL_OBJ_CTR_16NF:   /* 16-Bit Binary Counter Without Flag (Obj:20, Var:06) */
            case AL_OBJ_DCTR_32NF:  /* 32-Bit Binary Delta Counter Without Flag (Obj:20, Var:07) */
            case AL_OBJ_DCTR_16NF:  /* 16-Bit Binary Delta Counter Without Flag (Obj:20, Var:08) */
            case AL_OBJ_FCTR_32:    /* 32-Bit Frozen Counter (Obj:21, Var:01) */
            case AL_OBJ_FCTR_16:    /* 16-Bit Frozen Counter (Obj:21, Var:02) */
            case AL_OBJ_FDCTR_32:   /* 21 03 32-Bit Frozen Delta Counter */
            case AL_OBJ_FDCTR_16:   /* 21 04 16-Bit Frozen Delta Counter */
            case AL_OBJ_FCTR_32T:   /* 32-Bit Frozen Counter w/ Time of Freeze (Obj:21 Var:05 ) */
            case AL_OBJ_FCTR_16T:   /* 16-Bit Frozen Counter w/ Time of Freeze (Obj:21 Var:06) */
            case AL_OBJ_FDCTR_32T:  /* 32-Bit Frozen Delta Counter w/ Time of Freeze (Obj:21 Var:07) */
            case AL_OBJ_FDCTR_16T:  /* 16-Bit Frozen Delta Counter w/ Time of Freeze (Obj:21 Var:08) */
            case AL_OBJ_FCTR_32NF:  /* 32-Bit Frozen Counter Without Flag (Obj:21 Var:09) */
            case AL_OBJ_FCTR_16NF:  /* 16-Bit Frozen Counter Without Flag (Obj:21 Var:10) */
            case AL_OBJ_FDCTR_32NF: /* 32-Bit Frozen Delta Counter Without Flag (Obj:21 Var:11) */
            case AL_OBJ_FDCTR_16NF: /* 16-Bit Frozen Delta Counter Without Flag (Obj:21 Var:12) */
            case AL_OBJ_CTRC_32:    /* 32-Bit Counter Change Event w/o Time (Obj:22, Var:01) */
            case AL_OBJ_CTRC_16:    /* 16-Bit Counter Change Event w/o Time (Obj:22, Var:02) */
            case AL_OBJ_DCTRC_32:   /* 32-Bit Delta Counter Change Event w/o Time (Obj:22, Var:03) */
            case AL_OBJ_DCTRC_16:   /* 16-Bit Delta Counter Change Event w/o Time (Obj:22, Var:04) */
            case AL_OBJ_CTRC_32T:   /* 32-Bit Counter Change Event with Time (Obj:22, Var:05) */
            case AL_OBJ_CTRC_16T:   /* 16-Bit Counter Change Event with Time (Obj:22, Var:06) */
            case AL_OBJ_DCTRC_32T:  /* 32-Bit Delta Counter Change Event with Time (Obj:22, Var:07) */
            case AL_OBJ_DCTRC_16T:  /* 16-Bit Delta Counter Change Event with Time (Obj:22, Var:08) */
            case AL_OBJ_FCTRC_32:   /* 32-Bit Frozen Counter Change Event (Obj:23 Var:01) */
            case AL_OBJ_FCTRC_16:   /* 16-Bit Frozen Counter Change Event (Obj:23 Var:02) */
            case AL_OBJ_FDCTRC_32:  /* 32-Bit Frozen Delta Counter Change Event (Obj:23 Var:03) */
            case AL_OBJ_FDCTRC_16:  /* 16-Bit Frozen Delta Counter Change Event (Obj:23 Var:04) */
            case AL_OBJ_FCTRC_32T:  /* 32-Bit Frozen Counter Change Event w/ Time of Freeze (Obj:23 Var:05) */
            case AL_OBJ_FCTRC_16T:  /* 16-Bit Frozen Counter Change Event w/ Time of Freeze (Obj:23 Var:06) */
            case AL_OBJ_FDCTRC_32T: /* 32-Bit Frozen Delta Counter Change Event w/ Time of Freeze (Obj:23 Var:07) */
            case AL_OBJ_FDCTRC_16T: /* 16-Bit Frozen Delta Counter Change Event w/ Time of Freeze (Obj:23 Var:08) */

              /* Get Point Flags for those types that have them, it's easier to block out those that don't have flags */
              switch (al_obj)
              {
                case AL_OBJ_CTR_32NF:
                case AL_OBJ_CTR_16NF:
                case AL_OBJ_DCTR_32NF:
                case AL_OBJ_DCTR_16NF:
                case AL_OBJ_FCTR_32NF:
                case AL_OBJ_FCTR_16NF:
                case AL_OBJ_FDCTR_32NF:
                case AL_OBJ_FDCTR_16NF:
                  break;

                default:
                  al_ptflags = tvb_get_guint8(tvb, data_pos);
                  dnp3_al_obj_quality(tvb, data_pos, al_ptflags, point_tree, point_item, COUNTER);
                  data_pos += 1;
                  break;
              }

              /* Get Counter values */
              switch (al_obj)
              {
                case AL_OBJ_CTR_32:
                case AL_OBJ_DCTR_32:
                case AL_OBJ_CTR_32NF:
                case AL_OBJ_DCTR_32NF:
                case AL_OBJ_FCTR_32:
                case AL_OBJ_FDCTR_32:
                case AL_OBJ_FCTR_32T:
                case AL_OBJ_FDCTR_32T:
                case AL_OBJ_FCTR_32NF:
                case AL_OBJ_FDCTR_32NF:
                case AL_OBJ_CTRC_32:
                case AL_OBJ_DCTRC_32:
                case AL_OBJ_CTRC_32T:
                case AL_OBJ_DCTRC_32T:
                case AL_OBJ_FCTRC_32:
                case AL_OBJ_FDCTRC_32:
                case AL_OBJ_FCTRC_32T:
                case AL_OBJ_FDCTRC_32T:

                  al_val_uint32 = tvb_get_letohl(tvb, data_pos);
                  proto_item_append_text(point_item, ", Count: %u", al_val_uint32);
                  proto_tree_add_item(point_tree, hf_dnp3_al_cnt32, tvb, data_pos, 4, ENC_LITTLE_ENDIAN);
                  data_pos += 4;
                  break;

                case AL_OBJ_CTR_16:
                case AL_OBJ_DCTR_16:
                case AL_OBJ_CTR_16NF:
                case AL_OBJ_DCTR_16NF:
                case AL_OBJ_FCTR_16:
                case AL_OBJ_FDCTR_16:
                case AL_OBJ_FCTR_16T:
                case AL_OBJ_FDCTR_16T:
                case AL_OBJ_FCTR_16NF:
                case AL_OBJ_FDCTR_16NF:
                case AL_OBJ_CTRC_16:
                case AL_OBJ_DCTRC_16:
                case AL_OBJ_CTRC_16T:
                case AL_OBJ_DCTRC_16T:
                case AL_OBJ_FCTRC_16:
                case AL_OBJ_FDCTRC_16:
                case AL_OBJ_FCTRC_16T:
                case AL_OBJ_FDCTRC_16T:

                  al_val_uint16 = tvb_get_letohs(tvb, data_pos);
                  proto_item_append_text(point_item, ", Count: %u", al_val_uint16);
                  proto_tree_add_item(point_tree, hf_dnp3_al_cnt16, tvb, data_pos, 2, ENC_LITTLE_ENDIAN);
                  data_pos += 2;
                  break;
              }

              /* Get the time for those points that have it */
              switch (al_obj)
              {
                case AL_OBJ_FCTR_32T:
                case AL_OBJ_FCTR_16T:
                case AL_OBJ_FDCTR_32T:
                case AL_OBJ_FDCTR_16T:
                case AL_OBJ_CTRC_32T:
                case AL_OBJ_CTRC_16T:
                case AL_OBJ_DCTRC_32T:
                case AL_OBJ_DCTRC_16T:
                case AL_OBJ_FCTRC_32T:
                case AL_OBJ_FCTRC_16T:
                case AL_OBJ_FDCTRC_32T:
                case AL_OBJ_FDCTRC_16T:
                  dnp3_al_get_timestamp(&al_abstime, tvb, data_pos);
                  proto_item_append_text(point_item, ", Timestamp: %s", abs_time_to_str(wmem_packet_scope(), &al_abstime, ABSOLUTE_TIME_UTC, FALSE));
                  proto_tree_add_time(point_tree, hf_dnp3_al_timestamp, tvb, data_pos, 6, &al_abstime);
                  data_pos += 6;
                  break;
              }

              proto_item_set_len(point_item, data_pos - offset);
              offset = data_pos;
              break;

            case AL_OBJ_AI_32:        /* 32-Bit Analog Input (Obj:30, Var:01) */
            case AL_OBJ_AI_16:        /* 16-Bit Analog Input (Obj:30, Var:02) */
            case AL_OBJ_AI_32NF:      /* 32-Bit Analog Input Without Flag (Obj:30, Var:03) */
            case AL_OBJ_AI_16NF:      /* 16-Bit Analog Input Without Flag (Obj:30, Var:04) */
            case AL_OBJ_AI_FLT:       /* 32-Bit Floating Point Input (Obj:30, Var:05) */
            case AL_OBJ_AI_DBL:       /* 64-Bit Floating Point Input (Obj:30, Var:06) */
            case AL_OBJ_AIF_FLT:      /* 32-Bit Frozen Floating Point Input (Obj:31, Var:07) */
            case AL_OBJ_AIF_DBL:      /* 64-Bit Frozen Floating Point Input (Obj:31, Var:08) */
            case AL_OBJ_AIC_32NT:     /* 32-Bit Analog Change Event w/o Time (Obj:32, Var:01) */
            case AL_OBJ_AIC_16NT:     /* 16-Bit Analog Change Event w/o Time (Obj:32, Var:02) */
            case AL_OBJ_AIC_32T:      /* 32-Bit Analog Change Event with Time (Obj:32, Var:03) */
            case AL_OBJ_AIC_16T:      /* 16-Bit Analog Change Event with Time (Obj:32, Var:04) */
            case AL_OBJ_AIC_FLTNT:    /* 32-Bit Floating Point Change Event w/o Time (Obj:32, Var:05) */
            case AL_OBJ_AIC_DBLNT:    /* 64-Bit Floating Point Change Event w/o Time (Obj:32, Var:06) */
            case AL_OBJ_AIC_FLTT:     /* 32-Bit Floating Point Change Event w/ Time (Obj:32, Var:07) */
            case AL_OBJ_AIC_DBLT:     /* 64-Bit Floating Point Change Event w/ Time (Obj:32, Var:08) */
            case AL_OBJ_AIFC_FLTNT:   /* 32-Bit Floating Point Frozen Change Event w/o Time (Obj:33, Var:05) */
            case AL_OBJ_AIFC_DBLNT:   /* 64-Bit Floating Point Frozen Change Event w/o Time (Obj:33, Var:06) */
            case AL_OBJ_AIFC_FLTT:    /* 32-Bit Floating Point Frozen Change Event w/ Time (Obj:33, Var:07) */
            case AL_OBJ_AIFC_DBLT:    /* 64-Bit Floating Point Frozen Change Event w/ Time (Obj:33, Var:08) */
            case AL_OBJ_AIDB_16:      /* 16-Bit Analog Input Deadband (Obj:34, Var:01) */
            case AL_OBJ_AIDB_32:      /* 32-Bit Analog Input Deadband (Obj:34, Var:02) */
            case AL_OBJ_AIDB_FLT:     /* 32-Bit Floating Point Analog Input Deadband (Obj:34, Var:03) */

              /* Get Point Flags for those types that have them */
              switch (al_obj)
              {
                case AL_OBJ_AI_32NF:
                case AL_OBJ_AI_16NF:
                case AL_OBJ_AIDB_16:
                case AL_OBJ_AIDB_32:
                case AL_OBJ_AIDB_FLT:
                  break;

                default:
                  al_ptflags = tvb_get_guint8(tvb, data_pos);
                  dnp3_al_obj_quality(tvb, data_pos, al_ptflags, point_tree, point_item, ANA_IN);
                  data_pos += 1;
                  break;
              }

              switch (al_obj)
              {
                case AL_OBJ_AI_32:
                case AL_OBJ_AI_32NF:
                case AL_OBJ_AIC_32NT:
                case AL_OBJ_AIC_32T:
                case AL_OBJ_AIDB_32:

                  al_val_int32 = tvb_get_letohl(tvb, data_pos);
                  proto_item_append_text(point_item, ", Value: %d", al_val_int32);
                  proto_tree_add_item(point_tree, hf_dnp3_al_ana32, tvb, data_pos, 4, ENC_LITTLE_ENDIAN);
                  data_pos += 4;
                  break;

                case AL_OBJ_AI_16:
                case AL_OBJ_AI_16NF:
                case AL_OBJ_AIC_16NT:
                case AL_OBJ_AIC_16T:
                case AL_OBJ_AIDB_16:

                  al_val_int16 = tvb_get_letohs(tvb, data_pos);
                  proto_item_append_text(point_item, ", Value: %d", al_val_int16);
                  proto_tree_add_item(point_tree, hf_dnp3_al_ana16, tvb, data_pos, 2, ENC_LITTLE_ENDIAN);
                  data_pos += 2;
                  break;

                case AL_OBJ_AI_FLT:
                case AL_OBJ_AIF_FLT:
                case AL_OBJ_AIC_FLTNT:
                case AL_OBJ_AIC_FLTT:
                case AL_OBJ_AIFC_FLTNT:
                case AL_OBJ_AIFC_FLTT:
                case AL_OBJ_AIDB_FLT:

                  al_valflt = tvb_get_letohieee_float(tvb, data_pos);
                  proto_item_append_text(point_item, ", Value: %g", al_valflt);
                  proto_tree_add_item(point_tree, hf_dnp3_al_anaflt, tvb, data_pos, 4, ENC_LITTLE_ENDIAN);
                  data_pos += 4;
                  break;

                case AL_OBJ_AI_DBL:
                case AL_OBJ_AIF_DBL:
                case AL_OBJ_AIC_DBLNT:
                case AL_OBJ_AIC_DBLT:
                case AL_OBJ_AIFC_DBLNT:
                case AL_OBJ_AIFC_DBLT:

                  al_valdbl = tvb_get_letohieee_double(tvb, data_pos);
                  proto_item_append_text(point_item, ", Value: %g", al_valdbl);
                  proto_tree_add_item(point_tree, hf_dnp3_al_anadbl, tvb, data_pos, 8, ENC_LITTLE_ENDIAN);
                  data_pos += 8;
                  break;
              }

              /* Get timestamp */
              switch (al_obj)
              {
                case AL_OBJ_AIC_32T:
                case AL_OBJ_AIC_16T:
                case AL_OBJ_AIC_FLTT:
                case AL_OBJ_AIC_DBLT:
                case AL_OBJ_AIFC_FLTT:
                case AL_OBJ_AIFC_DBLT:
                  dnp3_al_get_timestamp(&al_abstime, tvb, data_pos);
                  proto_item_append_text(point_item, ", Timestamp: %s", abs_time_to_str(wmem_packet_scope(), &al_abstime, ABSOLUTE_TIME_UTC, FALSE));
                  proto_tree_add_time(point_tree, hf_dnp3_al_timestamp, tvb, data_pos, 6, &al_abstime);
                  data_pos += 6;
                  break;
              }

              proto_item_set_len(point_item, data_pos - offset);

              offset = data_pos;
              break;

            case AL_OBJ_AO_32:     /* 32-Bit Analog Output Status (Obj:40, Var:01) */
            case AL_OBJ_AO_16:     /* 16-Bit Analog Output Status (Obj:40, Var:02) */
            case AL_OBJ_AO_FLT:    /* 32-Bit Floating Point Output Status (Obj:40, Var:03) */
            case AL_OBJ_AO_DBL:    /* 64-Bit Floating Point Output Status (Obj:40, Var:04) */
            case AL_OBJ_AOC_32NT:  /* 32-Bit Analog Output Event w/o Time (Obj:42, Var:01) */
            case AL_OBJ_AOC_16NT:  /* 16-Bit Analog Output Event w/o Time (Obj:42, Var:02) */
            case AL_OBJ_AOC_32T:   /* 32-Bit Analog Output Event with Time (Obj:42, Var:03) */
            case AL_OBJ_AOC_16T:   /* 16-Bit Analog Output Event with Time (Obj:42, Var:04) */
            case AL_OBJ_AOC_FLTNT: /* 32-Bit Floating Point Output Event w/o Time (Obj:42, Var:05) */
            case AL_OBJ_AOC_DBLNT: /* 64-Bit Floating Point Output Event w/o Time (Obj:42, Var:06) */
            case AL_OBJ_AOC_FLTT:  /* 32-Bit Floating Point Output Event w/ Time (Obj:42, Var:07) */
            case AL_OBJ_AOC_DBLT:  /* 64-Bit Floating Point Output Event w/ Time (Obj:42, Var:08) */

              /* Get Point Flags */
              al_ptflags = tvb_get_guint8(tvb, data_pos);
              dnp3_al_obj_quality(tvb, data_pos, al_ptflags, point_tree, point_item, ANA_OUT);
              data_pos += 1;

              switch (al_obj)
              {
                case AL_OBJ_AO_32:     /* 32-Bit Analog Output Status (Obj:40, Var:01) */
                case AL_OBJ_AOC_32NT:  /* 32-Bit Analog Output Event w/o Time (Obj:42, Var:01) */
                case AL_OBJ_AOC_32T:   /* 32-Bit Analog Output Event with Time (Obj:42, Var:03) */

                  al_val_int32 = tvb_get_letohl(tvb, data_pos);
                  proto_item_append_text(point_item, ", Value: %d", al_val_int32);
                  proto_tree_add_item(point_tree, hf_dnp3_al_anaout32, tvb, data_pos, 4, ENC_LITTLE_ENDIAN);
                  data_pos += 4;
                  break;

                case AL_OBJ_AO_16:     /* 16-Bit Analog Output Status (Obj:40, Var:02) */
                case AL_OBJ_AOC_16NT:  /* 16-Bit Analog Output Event w/o Time (Obj:42, Var:02) */
                case AL_OBJ_AOC_16T:   /* 16-Bit Analog Output Event with Time (Obj:42, Var:04) */

                  al_val_int16 = tvb_get_letohs(tvb, data_pos);
                  proto_item_append_text(point_item, ", Value: %d", al_val_int16);
                  proto_tree_add_item(point_tree, hf_dnp3_al_anaout16, tvb, data_pos, 2, ENC_LITTLE_ENDIAN);
                  data_pos += 2;
                  break;

                case AL_OBJ_AO_FLT:     /* 32-Bit Floating Point Output Status (Obj:40, Var:03) */
                case AL_OBJ_AOC_FLTNT:  /* 32-Bit Floating Point Output Event w/o Time (Obj:42, Var:05) */
                case AL_OBJ_AOC_FLTT:   /* 32-Bit Floating Point Output Event w/ Time (Obj:42, Var:07) */

                  al_valflt = tvb_get_letohieee_float(tvb, data_pos);
                  proto_item_append_text(point_item, ", Value: %g", al_valflt);
                  proto_tree_add_item(point_tree, hf_dnp3_al_anaoutflt, tvb, data_pos, 4, ENC_LITTLE_ENDIAN);
                  data_pos += 4;
                  break;

                case AL_OBJ_AO_DBL:     /* 64-Bit Floating Point Output Status (Obj:40, Var:04) */
                case AL_OBJ_AOC_DBLNT:  /* 64-Bit Floating Point Output Event w/o Time (Obj:42, Var:06) */
                case AL_OBJ_AOC_DBLT:   /* 64-Bit Floating Point Output Event w/ Time (Obj:42, Var:08) */

                  al_valdbl = tvb_get_letohieee_double(tvb, data_pos);
                  proto_item_append_text(point_item, ", Value: %g", al_valdbl);
                  proto_tree_add_item(point_tree, hf_dnp3_al_anaoutdbl, tvb, data_pos, 8, ENC_LITTLE_ENDIAN);
                  data_pos += 8;
                  break;
              }

              /* Get timestamp */
              switch (al_obj)
              {
                case AL_OBJ_AOC_32T:
                case AL_OBJ_AOC_16T:
                case AL_OBJ_AOC_FLTT:
                case AL_OBJ_AOC_DBLT:
                  dnp3_al_get_timestamp(&al_abstime, tvb, data_pos);
                  proto_item_append_text(point_item, ", Timestamp: %s", abs_time_to_str(wmem_packet_scope(), &al_abstime, ABSOLUTE_TIME_UTC, FALSE));
                  proto_tree_add_time(point_tree, hf_dnp3_al_timestamp, tvb, data_pos, 6, &al_abstime);
                  data_pos += 6;
                  break;
              }

              proto_item_set_len(point_item, data_pos - offset);
              offset = data_pos;
              break;

            case AL_OBJ_TD:    /* Time and Date (Obj:50, Var:01) */
            case AL_OBJ_TDR:   /* Time and Date at Last Recorded Time (Obj:50, Var:03) */
            case AL_OBJ_TDCTO: /* Time and Date CTO (Obj:51, Var:01) */

              dnp3_al_get_timestamp(&al_abstime, tvb, data_pos);
              proto_tree_add_time(object_tree, hf_dnp3_al_timestamp, tvb, data_pos, 6, &al_abstime);
              data_pos += 6;
              proto_item_set_len(point_item, data_pos - offset);

              if (al_obj == AL_OBJ_TDCTO) {
                /* Copy the time object to the CTO for any other relative time objects in this response */
                nstime_copy(al_cto, &al_abstime);
              }

              offset = data_pos;
              break;

            case AL_OBJ_TDELAYF: /* Time Delay - Fine (Obj:52, Var:02) */

              proto_tree_add_item(object_tree, hf_dnp3_al_time_delay, tvb, data_pos, 2, ENC_LITTLE_ENDIAN);
              data_pos += 2;
              proto_item_set_len(point_item, data_pos - offset);

              offset = data_pos;
              break;

            case AL_OBJ_CLASS0:  /* Class Data Objects */
            case AL_OBJ_CLASS1:
            case AL_OBJ_CLASS2:
            case AL_OBJ_CLASS3:

              /* No data here */
              offset = data_pos;
              break;

            case AL_OBJ_FILE_CMD: /* File Control - File Command (Obj:70, Var:03) */
              /* File name offset and length */
              proto_tree_add_item(point_tree, hf_dnp3_al_file_string_offset, tvb, data_pos, 2, ENC_LITTLE_ENDIAN);
              data_pos += 2;
              al_filename_len = tvb_get_letohs(tvb, data_pos);
              proto_tree_add_item(point_tree, hf_dnp3_al_file_string_length, tvb, data_pos, 2, ENC_LITTLE_ENDIAN);
              data_pos += 2;

              /* Grab the mode as it determines if some of the following fields are relevant */
              al_file_ctrl_mode = tvb_get_letohs(tvb, data_pos + 16);

              /* Creation Time */
              if (al_file_ctrl_mode == AL_OBJ_FILE_MODE_WRITE) {
                dnp3_al_get_timestamp(&al_abstime, tvb, data_pos);
                proto_tree_add_time(point_tree, hf_dnp3_al_timestamp, tvb, data_pos, 6, &al_abstime);
              }
              data_pos += 6;

              /* Perms */
              if (al_file_ctrl_mode == AL_OBJ_FILE_MODE_WRITE) {
                proto_item *perms_item;
                proto_tree *perms_tree;

                perms_item = proto_tree_add_item(point_tree, hf_dnp3_al_file_perms, tvb, offset, 2, ENC_LITTLE_ENDIAN);

                perms_tree = proto_item_add_subtree(perms_item, ett_dnp3_al_obj_point_perms);
                proto_tree_add_item(perms_tree, hf_dnp3_al_file_perms_read_owner,  tvb, offset, 2, ENC_LITTLE_ENDIAN);
                proto_tree_add_item(perms_tree, hf_dnp3_al_file_perms_write_owner, tvb, offset, 2, ENC_LITTLE_ENDIAN);
                proto_tree_add_item(perms_tree, hf_dnp3_al_file_perms_exec_owner,  tvb, offset, 2, ENC_LITTLE_ENDIAN);
                proto_tree_add_item(perms_tree, hf_dnp3_al_file_perms_read_group,  tvb, offset, 2, ENC_LITTLE_ENDIAN);
                proto_tree_add_item(perms_tree, hf_dnp3_al_file_perms_write_group, tvb, offset, 2, ENC_LITTLE_ENDIAN);
                proto_tree_add_item(perms_tree, hf_dnp3_al_file_perms_exec_group,  tvb, offset, 2, ENC_LITTLE_ENDIAN);
                proto_tree_add_item(perms_tree, hf_dnp3_al_file_perms_read_world,  tvb, offset, 2, ENC_LITTLE_ENDIAN);
                proto_tree_add_item(perms_tree, hf_dnp3_al_file_perms_write_world, tvb, offset, 2, ENC_LITTLE_ENDIAN);
                proto_tree_add_item(perms_tree, hf_dnp3_al_file_perms_exec_world,  tvb, offset, 2, ENC_LITTLE_ENDIAN);
              }
              data_pos += 2;

              /* Auth Key */
              proto_tree_add_item(point_tree, hf_dnp3_al_file_auth, tvb, data_pos, 4, ENC_LITTLE_ENDIAN);
              data_pos += 4;

              /* File Size */
              if (al_file_ctrl_mode == AL_OBJ_FILE_MODE_WRITE || al_file_ctrl_mode == AL_OBJ_FILE_MODE_APPEND) {
                proto_tree_add_item(point_tree, hf_dnp3_al_file_size, tvb, data_pos, 4, ENC_LITTLE_ENDIAN);
              }
              data_pos += 4;

              /* Mode */
              proto_tree_add_item(point_tree, hf_dnp3_al_file_mode, tvb, data_pos, 2, ENC_LITTLE_ENDIAN);
              data_pos += 2;

              /* Max Block Size */
              proto_tree_add_item(point_tree, hf_dnp3_al_file_maxblk, tvb, data_pos, 2, ENC_LITTLE_ENDIAN);
              data_pos += 2;

              /* Request ID */
              proto_tree_add_item(point_tree, hf_dnp3_al_file_reqID, tvb, data_pos, 2, ENC_LITTLE_ENDIAN);
              data_pos += 2;

              /* Filename */
              if (al_filename_len > 0) {
                proto_tree_add_item(point_tree, hf_dnp3_al_file_name, tvb, data_pos, al_filename_len, ENC_ASCII|ENC_NA);
              }
              data_pos += al_filename_len;
              proto_item_set_len(point_item, data_pos - offset);

              offset = data_pos;
              break;

            case AL_OBJ_FILE_STAT: /* File Control - File Status (Obj:70, Var:04) */

              /* File Handle */
              proto_tree_add_item(point_tree, hf_dnp3_al_file_handle, tvb, data_pos, 4, ENC_LITTLE_ENDIAN);
              data_pos += 4;

              /* File Size */
              proto_tree_add_item(point_tree, hf_dnp3_al_file_size,   tvb, data_pos, 4, ENC_LITTLE_ENDIAN);
              data_pos += 4;

              /* Max Block Size */
              proto_tree_add_item(point_tree, hf_dnp3_al_file_maxblk, tvb, data_pos, 2, ENC_LITTLE_ENDIAN);
              data_pos += 2;

              /* Request ID */
              proto_tree_add_item(point_tree, hf_dnp3_al_file_reqID,  tvb, data_pos, 2, ENC_LITTLE_ENDIAN);
              data_pos += 2;

              /* Status code */
              proto_tree_add_item(point_tree, hf_dnp3_al_file_status, tvb, data_pos, 1, ENC_LITTLE_ENDIAN);
              data_pos += 1;

              /* Optional text */
              file_data_size = al_ptaddr - (data_pos - offset - prefixbytes);
              if ((file_data_size) > 0) {
                proto_tree_add_item(point_tree, hf_dnp3_al_file_data, tvb, data_pos, file_data_size, ENC_NA);
                data_pos += file_data_size;
              }

              proto_item_set_len(point_item, data_pos - offset);

              offset = data_pos;
              break;

            case AL_OBJ_FILE_TRANS: /* File Control - File Transport (Obj:70, Var:05) */

              /* File Handle */
              proto_tree_add_item(point_tree, hf_dnp3_al_file_handle, tvb, data_pos, 4, ENC_LITTLE_ENDIAN);
              data_pos += 4;

              /* File block (bits 0 - 30) and last block flag (bit 31) */
              proto_tree_add_item(point_tree, hf_dnp3_al_file_blocknum,  tvb, data_pos, 4, ENC_LITTLE_ENDIAN);
              proto_tree_add_item(point_tree, hf_dnp3_al_file_lastblock, tvb, data_pos, 4, ENC_LITTLE_ENDIAN);
              data_pos += 4;

              /* File data */
              file_data_size = al_ptaddr - (data_pos - offset - prefixbytes);
              if ((file_data_size) > 0) {
                proto_tree_add_item(point_tree, hf_dnp3_al_file_data, tvb, data_pos, file_data_size, ENC_NA);
                data_pos += file_data_size;
              }

              proto_item_set_len(point_item, data_pos - offset);

              offset = data_pos;
              break;

            case AL_OBJ_FILE_TRAN_ST: /* File Control Tansport Status (Obj:70, Var:06) */

              /* File Handle */
              proto_tree_add_item(point_tree, hf_dnp3_al_file_handle, tvb, data_pos, 4, ENC_LITTLE_ENDIAN);
              data_pos += 4;

              /* File block (bits 0 - 30) and last block flag (bit 31) */
              proto_tree_add_item(point_tree, hf_dnp3_al_file_blocknum,  tvb, data_pos, 4, ENC_LITTLE_ENDIAN);
              proto_tree_add_item(point_tree, hf_dnp3_al_file_lastblock, tvb, data_pos, 4, ENC_LITTLE_ENDIAN);
              data_pos += 4;

              /* Status code */
              proto_tree_add_item(point_tree, hf_dnp3_al_file_status, tvb, data_pos, 1, ENC_LITTLE_ENDIAN);
              data_pos += 1;

              /* Optional text */
              file_data_size = al_ptaddr - (data_pos - offset - prefixbytes);
              if ((file_data_size) > 0) {
                proto_tree_add_item(point_tree, hf_dnp3_al_file_data, tvb, data_pos, file_data_size, ENC_NA);
                data_pos += file_data_size;
              }

              proto_item_set_len(point_item, data_pos - offset);

              offset = data_pos;
              break;

            case AL_OBJ_OCT:      /* Octet string */
            case AL_OBJ_OCT_EVT:  /* Octet string event */

              /* read the number of bytes defined by the variation */
              if (al_oct_len > 0) {
                proto_tree_add_item(object_tree, hf_dnp3_al_octet_string, tvb, data_pos, al_oct_len, ENC_NA);
                data_pos += al_oct_len;
                proto_item_set_len(point_item, data_pos - offset);
              }

              offset = data_pos;
              break;

            default:             /* In case of unknown object */

              proto_tree_add_item(object_tree, hf_dnp3_unknown_data_chunk, tvb, offset, -1, ENC_NA);
              offset = tvb_captured_length(tvb); /* Finish decoding if unknown object is encountered... */
              break;
          }
        }

        /* And increment the point address, may be overwritten by an index value */
        al_ptaddr++;
      }
      else {
        /* No objects, just prefixes, move past prefix values */
        offset = data_pos;
      }
      if (start_offset > offset) {
        expert_add_info(pinfo, point_item, &ei_dnp_invalid_length);
        offset = tvb_captured_length(tvb); /* Finish decoding if unknown object is encountered... */
      }
    }
  }
  proto_item_set_len(object_item, offset - orig_offset);

  return offset;
}


dnp: plug a memory leak.

If we're throwing away the data, *throw away the data* - free it, as
we're not using it as the backing data for a tvbuff.

dnp3_append_2item_text(proto_item *item1, proto_item *item2, const gchar *text)
{
  proto_item_append_text(item1, "%s", text);
  proto_item_append_text(item2, "%s", text);
}

Crash in DNP dissector in Wireshark 3.4.0 to 3.4.6 and 3.2.0 to 3.2.14 allows denial of service via packet injection or crafted capture file


dnp3_udp_check_header_heur(packet_info *pinfo _U_, tvbuff_t *tvb, int offset _U_, void *data _U_)
{
    return check_dnp3_header(tvb, TRUE);
}


dnp3_al_empty_obj(guint16 al_obj)
{

  /* return a TRUE if we expect an empty object (default var, class object, etc) */
  switch (al_obj)
  {
    case AL_OBJ_BI_ALL:      /* Binary Input Default Variation (Obj:01, Var:Default) */
    case AL_OBJ_BIC_ALL:     /* Binary Input Change Default Variation (Obj:02, Var:Default) */
    case AL_OBJ_BOC_ALL:     /* Binary Output Event Default Variation (Obj:11, Var:Default) */
    case AL_OBJ_2BI_ALL:     /* Double-bit Input Default Variation (Obj:03, Var:Default) */
    case AL_OBJ_2BIC_ALL:    /* Double-bit Input Change Default Variation (Obj:04, Var:Default) */
    case AL_OBJ_CTR_ALL:     /* Binary Counter Default Variation (Obj:20, Var:Default) */
    case AL_OBJ_CTRC_ALL:    /* Binary Counter Change Default Variation (Obj:22 Var:Default) */
    case AL_OBJ_AI_ALL:      /* Analog Input Default Variation (Obj:30, Var:Default) */
    case AL_OBJ_AIC_ALL:     /* Analog Input Change Default Variation (Obj:32 Var:Default) */
    case AL_OBJ_AIDB_ALL:    /* Analog Input Deadband Default Variation (Obj:34, Var:Default) */
    case AL_OBJ_AOC_ALL:     /* Analog Output Event Default Variation (Obj:42 Var:Default) */
    case AL_OBJ_CLASS0:      /* Class Data Objects */
    case AL_OBJ_CLASS1:
    case AL_OBJ_CLASS2:
    case AL_OBJ_CLASS3:
      return TRUE;
      break;
    default:
      return FALSE;
      break;
  }
}


dissect_dnp3_udp_heur(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
{
  return (udp_dissect_pdus(tvb, pinfo, tree, DNP_HDR_LEN, dnp3_udp_check_header_heur,
                   get_dnp3_message_len, dissect_dnp3_message, data) != 0);

}


dissect_dnp3_udp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
{
  return udp_dissect_pdus(tvb, pinfo, tree, DNP_HDR_LEN, dnp3_udp_check_header,
                   get_dnp3_message_len, dissect_dnp3_message, data);
}


dissect_dnp3_message(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
{
  proto_item  *ti, *tdl, *tc, *hidden_item;
  proto_tree  *dnp3_tree, *dl_tree, *field_tree;
  int          offset = 0, temp_offset = 0;
  gboolean     dl_prm;
  guint8       dl_len, dl_ctl, dl_func;
  const gchar *func_code_str;
  guint16      dl_dst, dl_src, calc_dl_crc;

  /* Make entries in Protocol column and Info column on summary display */
  col_set_str(pinfo->cinfo, COL_PROTOCOL, "DNP 3.0");
  col_clear(pinfo->cinfo, COL_INFO);

  /* Skip "0x0564" header bytes */
  temp_offset += 2;

  dl_len = tvb_get_guint8(tvb, temp_offset);
  temp_offset += 1;

  dl_ctl = tvb_get_guint8(tvb, temp_offset);
  temp_offset += 1;

  dl_dst = tvb_get_letohs(tvb, temp_offset);
  temp_offset += 2;

  dl_src = tvb_get_letohs(tvb, temp_offset);

  dl_func = dl_ctl & DNP3_CTL_FUNC;
  dl_prm = dl_ctl & DNP3_CTL_PRM;
  func_code_str = val_to_str(dl_func, dl_prm ? dnp3_ctl_func_pri_vals : dnp3_ctl_func_sec_vals,
           "Unknown function (0x%02x)");

  /* Make sure source and dest are always in the info column */
  col_append_fstr(pinfo->cinfo, COL_INFO, "from %u to %u", dl_src, dl_dst);
  col_append_sep_fstr(pinfo->cinfo, COL_INFO, NULL, "len=%u, %s", dl_len, func_code_str);

  /* create display subtree for the protocol */
  ti = proto_tree_add_item(tree, proto_dnp3, tvb, offset, -1, ENC_NA);
  dnp3_tree = proto_item_add_subtree(ti, ett_dnp3);

  /* Create Subtree for Data Link Layer */
  dl_tree = proto_tree_add_subtree_format(dnp3_tree, tvb, offset, DNP_HDR_LEN, ett_dnp3_dl, &tdl,
        "Data Link Layer, Len: %u, From: %u, To: %u, ", dl_len, dl_src, dl_dst);
  if (dl_prm) {
    if (dl_ctl & DNP3_CTL_DIR) proto_item_append_text(tdl, "DIR, ");
    if (dl_ctl & DNP3_CTL_PRM) proto_item_append_text(tdl, "PRM, ");
    if (dl_ctl & DNP3_CTL_FCB) proto_item_append_text(tdl, "FCB, ");
    if (dl_ctl & DNP3_CTL_FCV) proto_item_append_text(tdl, "FCV, ");
  }
  else {
    if (dl_ctl & DNP3_CTL_DIR) proto_item_append_text(tdl, "DIR, ");
    if (dl_ctl & DNP3_CTL_PRM) proto_item_append_text(tdl, "PRM, ");
    if (dl_ctl & DNP3_CTL_RES) proto_item_append_text(tdl, "RES, ");
    if (dl_ctl & DNP3_CTL_DFC) proto_item_append_text(tdl, "DFC, ");
  }
  proto_item_append_text(tdl, "%s", func_code_str);

  /* start bytes */
  proto_tree_add_item(dl_tree, hf_dnp3_start, tvb, offset, 2, ENC_BIG_ENDIAN);
  offset += 2;

  /* add length field */
  proto_tree_add_item(dl_tree, hf_dnp3_len, tvb, offset, 1, ENC_BIG_ENDIAN);
  offset += 1;

  /* Add Control Byte Subtree */
  tc = proto_tree_add_uint_format_value(dl_tree, hf_dnp3_ctl, tvb, offset, 1, dl_ctl,
          "0x%02x (", dl_ctl);
  /* Add Text to Control Byte Subtree Header */
  if (dl_prm) {
    if (dl_ctl & DNP3_CTL_DIR) proto_item_append_text(tc, "DIR, ");
    if (dl_ctl & DNP3_CTL_PRM) proto_item_append_text(tc, "PRM, ");
    if (dl_ctl & DNP3_CTL_FCB) proto_item_append_text(tc, "FCB, ");
    if (dl_ctl & DNP3_CTL_FCV) proto_item_append_text(tc, "FCV, ");
  }
  else {
    if (dl_ctl & DNP3_CTL_DIR) proto_item_append_text(tc, "DIR, ");
    if (dl_ctl & DNP3_CTL_PRM) proto_item_append_text(tc, "PRM, ");
    if (dl_ctl & DNP3_CTL_RES) proto_item_append_text(tc, "RES, ");
    if (dl_ctl & DNP3_CTL_DFC) proto_item_append_text(tc, "DFC, ");
  }
  proto_item_append_text(tc, "%s)", func_code_str );
  field_tree = proto_item_add_subtree(tc, ett_dnp3_dl_ctl);

  /* Add Control Byte Subtree Items */
  if (dl_prm) {
    proto_tree_add_item(field_tree, hf_dnp3_ctl_dir, tvb, offset, 1, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(field_tree, hf_dnp3_ctl_prm, tvb, offset, 1, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(field_tree, hf_dnp3_ctl_fcb, tvb, offset, 1, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(field_tree, hf_dnp3_ctl_fcv, tvb, offset, 1, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(field_tree, hf_dnp3_ctl_prifunc, tvb, offset, 1, ENC_BIG_ENDIAN);
  }
  else {
    proto_tree_add_item(field_tree, hf_dnp3_ctl_dir, tvb, offset, 1, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(field_tree, hf_dnp3_ctl_prm, tvb, offset, 1, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(field_tree, hf_dnp3_ctl_dfc, tvb, offset, 1, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(field_tree, hf_dnp3_ctl_secfunc, tvb, offset, 1, ENC_BIG_ENDIAN);
  }
    offset += 1;

  /* add destination and source addresses */
  proto_tree_add_item(dl_tree, hf_dnp3_dst, tvb, offset, 2, ENC_LITTLE_ENDIAN);
  hidden_item = proto_tree_add_item(dl_tree, hf_dnp3_addr, tvb, offset, 2, ENC_LITTLE_ENDIAN);
  proto_item_set_hidden(hidden_item);
  offset += 2;
  proto_tree_add_item(dl_tree, hf_dnp3_src, tvb, offset, 2, ENC_LITTLE_ENDIAN);
  hidden_item = proto_tree_add_item(dl_tree, hf_dnp3_addr, tvb, offset, 2, ENC_LITTLE_ENDIAN);
  proto_item_set_hidden(hidden_item);
  offset += 2;

  /* and header CRC */
  calc_dl_crc = calculateCRCtvb(tvb, 0, DNP_HDR_LEN - 2);
  proto_tree_add_checksum(dl_tree, tvb, offset, hf_dnp3_data_hdr_crc,
                          hf_dnp3_data_hdr_crc_status, &ei_dnp3_data_hdr_crc_incorrect,
                          pinfo, calc_dl_crc, ENC_LITTLE_ENDIAN, PROTO_CHECKSUM_VERIFY);
  offset += 2;

  /* If the DataLink function is 'Request Link Status' or 'Status of Link',
     or 'Reset Link' we don't expect any Transport or Application Layer Data
     NOTE: This code should probably check what DOES have TR or AL data */
  if ((dl_func != DL_FUNC_LINK_STAT) && (dl_func != DL_FUNC_STAT_LINK) &&
      (dl_func != DL_FUNC_RESET_LINK) && (dl_func != DL_FUNC_ACK))
  {
    proto_tree *data_tree;
    proto_item *data_ti;
    guint8      tr_ctl, tr_seq;
    gboolean    tr_fir, tr_fin;
    guint8     *al_buffer, *al_buffer_ptr;
    guint8      data_len;
    int         data_start = offset;
    int         tl_offset;
    gboolean    crc_OK = FALSE;
    tvbuff_t   *next_tvb;
    guint       i;
    static int * const transport_flags[] = {
      &hf_dnp3_tr_fin,
      &hf_dnp3_tr_fir,
      &hf_dnp3_tr_seq,
      NULL
    };

    /* get the transport layer byte */
    tr_ctl = tvb_get_guint8(tvb, offset);
    tr_seq = tr_ctl & DNP3_TR_SEQ;
    tr_fir = tr_ctl & DNP3_TR_FIR;
    tr_fin = tr_ctl & DNP3_TR_FIN;

    /* Add Transport Layer Tree */
    tc = proto_tree_add_bitmask(dnp3_tree, tvb, offset, hf_dnp3_tr_ctl, ett_dnp3_tr_ctl, transport_flags, ENC_BIG_ENDIAN);
    proto_item_append_text(tc, "(");
    if (tr_fir) proto_item_append_text(tc, "FIR, ");
    if (tr_fin) proto_item_append_text(tc, "FIN, ");
    proto_item_append_text(tc, "Sequence %u)", tr_seq);

    /* Add data chunk tree */
    data_tree = proto_tree_add_subtree(dnp3_tree, tvb, offset, -1, ett_dnp3_dl_data, &data_ti, "Data Chunks");

    /* extract the application layer data, validating the CRCs */

    /* XXX - check for dl_len <= 5 */
    data_len = dl_len - 5;
    al_buffer = (guint8 *)wmem_alloc(pinfo->pool, data_len);
    al_buffer_ptr = al_buffer;
    i = 0;
    tl_offset = 1;  /* skip the initial transport layer byte when assembling chunks for the application layer tvb */
    while (data_len > 0)
    {
      guint8        chk_size;
      const guint8 *chk_ptr;
      proto_tree   *chk_tree;
      proto_item   *chk_len_ti;
      guint16       calc_crc, act_crc;

      chk_size = MIN(data_len, AL_MAX_CHUNK_SIZE);
      chk_ptr  = tvb_get_ptr(tvb, offset, chk_size);
      memcpy(al_buffer_ptr, chk_ptr + tl_offset, chk_size - tl_offset);
      al_buffer_ptr += chk_size - tl_offset;

      chk_tree = proto_tree_add_subtree_format(data_tree, tvb, offset, chk_size + 2, ett_dnp3_dl_chunk, NULL, "Data Chunk: %u", i);
      proto_tree_add_item(chk_tree, hf_dnp3_data_chunk, tvb, offset, chk_size, ENC_NA);
      chk_len_ti = proto_tree_add_uint(chk_tree, hf_dnp3_data_chunk_len, tvb, offset, 0, chk_size);
      proto_item_set_generated(chk_len_ti);

      offset  += chk_size;

      calc_crc = calculateCRC(chk_ptr, chk_size);
      proto_tree_add_checksum(chk_tree, tvb, offset, hf_dnp3_data_chunk_crc,
                              hf_dnp3_data_chunk_crc_status, &ei_dnp3_data_chunk_crc_incorrect,
                              pinfo, calc_crc, ENC_LITTLE_ENDIAN, PROTO_CHECKSUM_VERIFY);
      act_crc  = tvb_get_letohs(tvb, offset);
      offset  += 2;
      crc_OK   = calc_crc == act_crc;
      if (!crc_OK)
      {
        /* Don't trust the rest of the data, get out of here */
        break;
      }
      data_len -= chk_size;
      i++;
      tl_offset = 0;  /* copy all the data in the rest of the chunks */
    }
    proto_item_set_len(data_ti, offset - data_start);

    /* if crc OK, set up new tvb */
    if (crc_OK)
    {
      tvbuff_t *al_tvb;
      gboolean  save_fragmented;

      al_tvb = tvb_new_child_real_data(tvb, al_buffer, (guint) (al_buffer_ptr-al_buffer), (gint) (al_buffer_ptr-al_buffer));

      /* Check for fragmented packet */
      save_fragmented = pinfo->fragmented;

      /* Reassemble AL fragments */
      static guint al_max_fragments = 60;
      static guint al_fragment_aging = 64; /* sequence numbers only 6 bit */
      fragment_head *frag_al = NULL;
      pinfo->fragmented = TRUE;
      if (!pinfo->fd->visited)
      {
        frag_al = fragment_add_seq_single_aging(&al_reassembly_table,
            al_tvb, 0, pinfo, tr_seq, NULL,
            tvb_reported_length(al_tvb), /* As this is a constructed tvb, all of it is ok */
            tr_fir, tr_fin,
            al_max_fragments, al_fragment_aging);
      }
      else
      {
        frag_al = fragment_get_reassembled_id(&al_reassembly_table, pinfo, tr_seq);
      }
      next_tvb = process_reassembled_data(al_tvb, 0, pinfo,
          "Reassembled DNP 3.0 Application Layer message", frag_al, &dnp3_frag_items,
          NULL, dnp3_tree);

      if (frag_al)
      {
        if (pinfo->num == frag_al->reassembled_in && pinfo->curr_layer_num == frag_al->reas_in_layer_num)
        {
          /* As a complete AL message will have cleared the info column,
             make sure source and dest are always in the info column */
          //col_append_fstr(pinfo->cinfo, COL_INFO, "from %u to %u", dl_src, dl_dst);
          //col_set_fence(pinfo->cinfo, COL_INFO);
          dissect_dnp3_al(next_tvb, pinfo, dnp3_tree);
        }
        else
        {
          /* Lock any column info set by the DL and TL */
          col_set_fence(pinfo->cinfo, COL_INFO);
          col_append_fstr(pinfo->cinfo, COL_INFO,
              " (Application Layer fragment %u, reassembled in packet %u)",
              tr_seq, frag_al->reassembled_in);
          proto_tree_add_item(dnp3_tree, hf_al_frag_data, al_tvb, 0, -1, ENC_NA);
        }
      }
      else
      {
        col_append_fstr(pinfo->cinfo, COL_INFO,
            " (Application Layer Unreassembled fragment %u)",
            tr_seq);
        proto_tree_add_item(dnp3_tree, hf_al_frag_data, al_tvb, 0, -1, ENC_NA);
      }

      pinfo->fragmented = save_fragmented;
    }
    else
    {
      /* CRC error - throw away the data. */
      g_free(al_buffer);
      next_tvb = NULL;
    }
  }

  /* Set the length of the message */
  proto_item_set_len(ti, offset);
  return offset;
}


check_dnp3_header(tvbuff_t *tvb, gboolean dnp3_heuristics)
{
  /* Assume the CRC will be bad */
  gboolean goodCRC = FALSE;

  /* How big is the actual buffer */
  gint length = tvb_captured_length(tvb);

  /* Calculate the header CRC if the bytes are available */
  if (length >= DNP_HDR_LEN) {
    guint16 calc_crc = calculateCRCtvb(tvb, 0, DNP_HDR_LEN - 2);
    goodCRC = (calc_crc == tvb_get_letohs(tvb, 8));
  }

  /* For a heuristic match we must have at least a header, beginning with 0x0564
     and a valid header CRC */
  if (dnp3_heuristics) {
    if ( !goodCRC || (tvb_get_ntohs(tvb, 0) != 0x0564)) {
      return FALSE;
    }
  }
  else {
    /* For a non-heuristic match, at least the first byte is 0x05 and if available
       the second byte is 64 and if available the CRC is valid */
    if (tvb_get_guint8(tvb, 0) != 0x05) {
      return FALSE;
    }
    if ((length > 1) && (tvb_get_guint8(tvb, 1) != 0x64)) {
      return FALSE;
    }
    if ((length >= DNP_HDR_LEN) && !goodCRC) {
      return FALSE;
    }
  }
  return TRUE;
}


dissect_dnp3_al(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
  guint8        al_ctl, al_seq, al_func, al_class = 0, i;
  guint16       bytes, obj_type = 0;
  guint         data_len = 0, offset = 0;
  proto_item   *ti, *tc;
  proto_tree   *al_tree, *robj_tree;
  const gchar  *func_code_str, *obj_type_str;
  nstime_t      al_cto;
  static int * const control_flags[] = {
    &hf_dnp3_al_fir,
    &hf_dnp3_al_fin,
    &hf_dnp3_al_con,
    &hf_dnp3_al_uns,
    &hf_dnp3_al_seq,
    NULL
  };

  nstime_set_zero (&al_cto);

  data_len = tvb_captured_length(tvb);

  /* Handle the control byte and function code */
  al_ctl = tvb_get_guint8(tvb, offset);
  al_seq = al_ctl & DNP3_AL_SEQ;
  al_func = tvb_get_guint8(tvb, (offset+1));
  func_code_str = val_to_str_ext(al_func, &dnp3_al_func_vals_ext, "Unknown function (0x%02x)");

  /* Clear out lower layer info */
  col_clear(pinfo->cinfo, COL_INFO);
  col_append_sep_str(pinfo->cinfo, COL_INFO, NULL, func_code_str);
  col_set_fence(pinfo->cinfo, COL_INFO);

  /* format up the text representation */
  al_tree = proto_tree_add_subtree(tree, tvb, offset, data_len, ett_dnp3_al, &ti, "Application Layer: (");
  if (al_ctl & DNP3_AL_FIR)  proto_item_append_text(ti, "FIR, ");
  if (al_ctl & DNP3_AL_FIN)  proto_item_append_text(ti, "FIN, ");
  if (al_ctl & DNP3_AL_CON)  proto_item_append_text(ti, "CON, ");
  if (al_ctl & DNP3_AL_UNS)  proto_item_append_text(ti, "UNS, ");
  proto_item_append_text(ti, "Sequence %u, %s)", al_seq, func_code_str);

  /* Application Layer control byte subtree */
  tc = proto_tree_add_bitmask(al_tree, tvb, offset, hf_dnp3_al_ctl, ett_dnp3_al_ctl, control_flags, ENC_BIG_ENDIAN);
  proto_item_append_text(tc, "(");
  if (al_ctl & DNP3_AL_FIR)  proto_item_append_text(tc, "FIR, ");
  if (al_ctl & DNP3_AL_FIN)  proto_item_append_text(tc, "FIN, ");
  if (al_ctl & DNP3_AL_CON)  proto_item_append_text(tc, "CON, ");
  if (al_ctl & DNP3_AL_UNS)  proto_item_append_text(tc, "UNS, ");
  proto_item_append_text(tc, "Sequence %u)", al_seq);
  offset += 1;

#if 0
  /* If this packet is NOT the final Application Layer Message, exit and continue
     processing the remaining data in the fragment. */
  if (!(al_ctl & DNP3_AL_FIN)) {
    t_robj = proto_tree_add_expert(al_tree, pinfo, &ei_dnp3_buffering_user_data_until_final_frame_is_received, tvb, offset, -1);
    return 1;
  }
#endif

  /* Application Layer Function Code Byte  */
  proto_tree_add_uint_format(al_tree, hf_dnp3_al_func, tvb, offset, 1, al_func,
    "Function Code: %s (0x%02x)", func_code_str, al_func);
  offset += 1;

  switch (al_func)
  {
    case AL_FUNC_READ:     /* Read Function Code 0x01 */

      /* Create Read Request Data Objects Tree */
      robj_tree = proto_tree_add_subtree(al_tree, tvb, offset, -1, ett_dnp3_al_objdet, NULL, "READ Request Data Objects");

      /* Process Data Object Details */
      while (offset <= (data_len-2))  {  /* 2 octet object code + CRC32 */
        offset = dnp3_al_process_object(tvb, pinfo, offset, robj_tree, TRUE, &obj_type, &al_cto);

        /* Update class type for each object that was a class read */
        switch(obj_type) {
          case AL_OBJ_CLASS0:
          case AL_OBJ_CLASS1:
          case AL_OBJ_CLASS2:
          case AL_OBJ_CLASS3:
            al_class |= (1 << ((obj_type & 0x0f) - 1));
            break;
          default:
            /* For reads for specific object types, bit-mask out the first byte and add the generic obj description to the column info */
            obj_type_str = val_to_str_ext((obj_type & 0xFF00), &dnp3_al_read_obj_vals_ext, "Unknown Object Type");
            col_append_sep_str(pinfo->cinfo, COL_INFO, NULL, obj_type_str);
            break;
        }

      }

      /* Update the col info if there were class reads */
      if (al_class != 0) {
        col_append_sep_str(pinfo->cinfo, COL_INFO, NULL, "Class ");
        for (i = 0; i < 4; i++) {
          if (al_class & (1 << i)) {
            col_append_fstr(pinfo->cinfo, COL_INFO, "%u", i);
          }
        }
      }

      break;

    case AL_FUNC_WRITE:     /* Write Function Code 0x02 */

      /* Create Write Request Data Objects Tree */
      robj_tree = proto_tree_add_subtree(al_tree, tvb, offset, -1, ett_dnp3_al_objdet, NULL, "WRITE Request Data Objects");

      /* Process Data Object Details */
      while (offset <= (data_len-2))  {  /* 2 octet object code + CRC32 */
        offset = dnp3_al_process_object(tvb, pinfo, offset, robj_tree, FALSE, &obj_type, &al_cto);

        /* For writes for specific object types, bit-mask out the first byte and add the generic obj description to the column info */
        obj_type_str = val_to_str_ext((obj_type & 0xFF00), &dnp3_al_write_obj_vals_ext, "Unknown Object Type");
        col_append_sep_str(pinfo->cinfo, COL_INFO, NULL, obj_type_str);

      }

      break;

    case AL_FUNC_SELECT:     /* Select Function Code 0x03 */

      /* Create Select Request Data Objects Tree */
      robj_tree = proto_tree_add_subtree(al_tree, tvb, offset, -1, ett_dnp3_al_objdet, NULL, "SELECT Request Data Objects");

      /* Process Data Object Details */
      while (offset <= (data_len-2))  {  /* 2 octet object code + CRC32 */
        offset = dnp3_al_process_object(tvb, pinfo, offset, robj_tree, FALSE, &obj_type, &al_cto);
      }

      break;

    case AL_FUNC_OPERATE:    /* Operate Function Code 0x04 */
      /* Functionally identical to 'SELECT' Function Code */

      /* Create Operate Request Data Objects Tree */
      robj_tree = proto_tree_add_subtree(al_tree, tvb, offset, -1, ett_dnp3_al_objdet, NULL, "OPERATE Request Data Objects");

      /* Process Data Object Details */
      while (offset <= (data_len-2))  {  /* 2 octet object code + CRC32 */
        offset = dnp3_al_process_object(tvb, pinfo, offset, robj_tree, FALSE, &obj_type, &al_cto);
      }

      break;

    case AL_FUNC_DIROP:        /* Direct Operate Function Code 0x05 */
    case AL_FUNC_DIROPNACK:    /* Direct Operate No ACK Function Code 0x06 */
      /* Functionally identical to 'SELECT' Function Code */

      /* Create Direct Operate Request Data Objects Tree */
      robj_tree = proto_tree_add_subtree(al_tree, tvb, offset, -1, ett_dnp3_al_objdet, NULL, "DIRECT OPERATE Request Data Objects");

      /* Process Data Object Details */
      while (offset <= (data_len-2))  {  /* 2 octet object code + CRC32 */
        offset = dnp3_al_process_object(tvb, pinfo, offset, robj_tree, FALSE, &obj_type, &al_cto);
      }

      break;

    case AL_FUNC_FRZ:        /* Immediate Freeze Function Code 0x07 */
    case AL_FUNC_FRZNACK:    /* Immediate Freeze No ACK Function Code 0x08 */
    case AL_FUNC_FRZCLR:     /* Freeze and Clear Function Code 0x09 */
    case AL_FUNC_FRZCLRNACK: /* Freeze and Clear No ACK Function Code 0x0A */

      /* Create Freeze Request Data Objects Tree */
      robj_tree = proto_tree_add_subtree(al_tree, tvb, offset, -1, ett_dnp3_al_objdet, NULL, "Freeze Request Data Objects");

      /* Process Data Object Details */
      while (offset <= (data_len-2))  {  /* 2 octet object code + CRC32 */
        offset = dnp3_al_process_object(tvb, pinfo, offset, robj_tree, TRUE, &obj_type, &al_cto);
      }

      break;

    case AL_FUNC_ENSPMSG:   /* Enable Spontaneous Messages Function Code 0x14 */

      /* Create Enable Spontaneous Messages Data Objects Tree */
      robj_tree = proto_tree_add_subtree(al_tree, tvb, offset, -1, ett_dnp3_al_objdet, NULL, "Enable Spontaneous Msg's Data Objects");

      /* Process Data Object Details */
      while (offset <= (data_len-2))  {  /* 2 octet object code + CRC32 */
        offset = dnp3_al_process_object(tvb, pinfo, offset, robj_tree, FALSE, &obj_type, &al_cto);
      }

      break;

    case AL_FUNC_DISSPMSG:   /* Disable Spontaneous Messages Function Code 0x15 */

      /* Create Disable Spontaneous Messages Data Objects Tree */
      robj_tree = proto_tree_add_subtree(al_tree, tvb, offset, -1, ett_dnp3_al_objdet, NULL, "Disable Spontaneous Msg's Data Objects");

      /* Process Data Object Details */
      while (offset <= (data_len-2))  {  /* 2 octet object code + CRC32 */
        offset = dnp3_al_process_object(tvb, pinfo, offset, robj_tree, FALSE, &obj_type, &al_cto);
      }

      break;

    case AL_FUNC_DELAYMST:  /* Delay Measurement Function Code 0x17 */

      break;

    case AL_FUNC_OPENFILE:        /* Open File Function Code 0x19 */
    case AL_FUNC_CLOSEFILE:       /* Close File Function Code 0x1A */
    case AL_FUNC_DELETEFILE:      /* Delete File Function Code 0x1B */

      /* Create File Data Objects Tree */
      robj_tree = proto_tree_add_subtree(al_tree, tvb, offset, -1, ett_dnp3_al_objdet, NULL, "File Data Objects");

      /* Process Data Object Details */
      while (offset <= (data_len-2))  {  /* 2 octet object code + CRC32 */
        offset = dnp3_al_process_object(tvb, pinfo, offset, robj_tree, FALSE, &obj_type, &al_cto);
      }

      break;

    case AL_FUNC_RESPON:   /* Response Function Code 0x81 */
    case AL_FUNC_UNSOLI:   /* Unsolicited Response Function Code 0x82 */

      /* Application Layer IIN bits req'd if message is a response */
      dnp3_al_process_iin(tvb, pinfo, offset, al_tree);
      offset += 2;

      /* Ensure there is actual data remaining in the message.
         A response will not contain data following the IIN bits,
         if there is none available */
      bytes = tvb_reported_length_remaining(tvb, offset);
      if (bytes > 0)
      {
        /* Create Response Data Objects Tree */
        robj_tree = proto_tree_add_subtree(al_tree, tvb, offset, -1, ett_dnp3_al_objdet, NULL, "RESPONSE Data Objects");

        /* Process Data Object Details */
        while (offset <= (data_len-2)) {  /* 2 octet object code + CRC32 */
          offset = dnp3_al_process_object(tvb, pinfo, offset, robj_tree, FALSE, &obj_type, &al_cto);
        }

        break;
      }

    default:    /* Unknown Function */

      break;
  }

  return 0;
}


dissect_dnp3_tcp_heur(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
{
  if (!check_dnp3_header(tvb, TRUE)) {
    return FALSE;
  }

  tcp_dissect_pdus(tvb, pinfo, tree, TRUE, DNP_HDR_LEN,
                   get_dnp3_message_len, dissect_dnp3_message, data);

  return TRUE;
}


dnp3_al_get_timestamp(nstime_t *timestamp, tvbuff_t *tvb, int data_pos)
{

  guint32 hi, lo;
  guint64 time_ms;

  lo = tvb_get_letohs(tvb, data_pos);
  hi = tvb_get_letohl(tvb, data_pos + 2);

  time_ms = (guint64)hi * 0x10000 + lo;

  timestamp->secs  = (long)(time_ms / 1000);
  timestamp->nsecs = (int)(time_ms % 1000) * 1000000;
}


calculateCRC(const void *buf, guint len) {
  guint16 crc = crc16_0x3D65_seed((const guint8 *)buf, len, 0);
  return ~crc;
}


Bug 700153: restore: always check available stack

Previously, we were checking there was enough stack space available when the
restore operation required a device change, but since we have to use
Postscript to reset the userparams (ick!), we need the stack check even when
not changing the device.

restore_page_device(i_ctx_t *i_ctx_p, const gs_gstate * pgs_old, const gs_gstate * pgs_new)
{
    gx_device *dev_old = gs_currentdevice(pgs_old);
    gx_device *dev_new;
    gx_device *dev_t1;
    gx_device *dev_t2;
    bool samepagedevice = obj_eq(dev_old->memory, &gs_int_gstate(pgs_old)->pagedevice,
        &gs_int_gstate(pgs_new)->pagedevice);
    bool LockSafetyParams = dev_old->LockSafetyParams;

    if ((dev_t1 = (*dev_proc(dev_old, get_page_device)) (dev_old)) == 0)
        return 0;
    /* If we are going to putdeviceparams in a callout, we need to */
    /* unlock temporarily.  The device will be re-locked as needed */
    /* by putdeviceparams from the pgs_old->pagedevice dict state. */
    if (!samepagedevice)
        dev_old->LockSafetyParams = false;
    dev_new = gs_currentdevice(pgs_new);
    if (dev_old != dev_new) {
        if ((dev_t2 = (*dev_proc(dev_new, get_page_device)) (dev_new)) == 0)
            samepagedevice = true;
        else if (dev_t1 != dev_t2)
            samepagedevice = false;
    }

    if (LockSafetyParams) {
        const int required_ops = 512;
        const int required_es = 32;

        /* The %grestorepagedevice must complete: the biggest danger
           is operand stack overflow. As we use get/putdeviceparams
           that means pushing all the device params onto the stack,
           pdfwrite having by far the largest number of parameters
           at (currently) 212 key/value pairs - thus needing (currently)
           424 entries on the op stack. Allowing for working stack
           space, and safety margin.....
         */
        if (required_ops + ref_stack_count(&o_stack) >= ref_stack_max_count(&o_stack)) {
           gs_currentdevice(pgs_old)->LockSafetyParams = LockSafetyParams;
           return_error(gs_error_stackoverflow);
        }
        /* We also want enough exec stack space - 32 is an overestimate of
           what we need to complete the Postscript call out.
         */
        if (required_es + ref_stack_count(&e_stack) >= ref_stack_max_count(&e_stack)) {
           gs_currentdevice(pgs_old)->LockSafetyParams = LockSafetyParams;
           return_error(gs_error_execstackoverflow);
        }
    }
    /*
     * The current implementation of setpagedevice just sets new
     * parameters in the same device object, so we have to check
     * whether the page device dictionaries are the same.
     */
    return samepagedevice ? 0 : 1;
}

psi/zdevice2.c in Artifex Ghostscript before 9.26 allows remote attackers to bypass intended access restrictions because available stack space is not checked when the device remains the same.


Fix bug #78256 (heap-buffer-overflow on exif_process_user_comment)

(cherry picked from commit aeb6d13185a2ea4f1496ede2697469faed98ce05)

static int exif_process_IFD_in_MAKERNOTE(image_info_type *ImageInfo, char * value_ptr, int value_len, char *offset_base, size_t IFDlength, size_t displacement)
{
	size_t i;
	int de, section_index = SECTION_MAKERNOTE;
	int NumDirEntries, old_motorola_intel;
#ifdef KALLE_0
	int offset_diff;
#endif
	const maker_note_type *maker_note;
	char *dir_start;
	int data_len;

	for (i=0; i<=sizeof(maker_note_array)/sizeof(maker_note_type); i++) {
		if (i==sizeof(maker_note_array)/sizeof(maker_note_type)) {
#ifdef EXIF_DEBUG
			exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "No maker note data found. Detected maker: %s (length = %d)", ImageInfo->make, strlen(ImageInfo->make));
#endif
			/* unknown manufacturer, not an error, use it as a string */
			return TRUE;
		}

		maker_note = maker_note_array+i;

		/*exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "check (%s)", maker_note->make?maker_note->make:"");*/
		if (maker_note->make && (!ImageInfo->make || strcmp(maker_note->make, ImageInfo->make)))
			continue;
		if (maker_note->id_string && strncmp(maker_note->id_string, value_ptr, maker_note->id_string_len))
			continue;
		break;
	}

	if (value_len < 2 || maker_note->offset >= value_len - 1) {
		/* Do not go past the value end */
		exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "IFD data too short: 0x%04X offset 0x%04X", value_len, maker_note->offset);
		return FALSE;
	}

	dir_start = value_ptr + maker_note->offset;

#ifdef EXIF_DEBUG
	exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Process %s @x%04X + 0x%04X=%d: %s", exif_get_sectionname(section_index), (int)dir_start-(int)offset_base+maker_note->offset+displacement, value_len, value_len, exif_char_dump(value_ptr, value_len, (int)dir_start-(int)offset_base+maker_note->offset+displacement));
#endif

	ImageInfo->sections_found |= FOUND_MAKERNOTE;

	old_motorola_intel = ImageInfo->motorola_intel;
	switch (maker_note->byte_order) {
		case MN_ORDER_INTEL:
			ImageInfo->motorola_intel = 0;
			break;
		case MN_ORDER_MOTOROLA:
			ImageInfo->motorola_intel = 1;
			break;
		default:
		case MN_ORDER_NORMAL:
			break;
	}

	NumDirEntries = php_ifd_get16u(dir_start, ImageInfo->motorola_intel);

	switch (maker_note->offset_mode) {
		case MN_OFFSET_MAKER:
			offset_base = value_ptr;
			data_len = value_len;
			break;
#ifdef KALLE_0
		case MN_OFFSET_GUESS:
			if (maker_note->offset + 10 + 4 >= value_len) {
				/* Can not read dir_start+10 since it's beyond value end */
				exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "IFD data too short: 0x%04X", value_len);
				return FALSE;
			}
			offset_diff = 2 + NumDirEntries*12 + 4 - php_ifd_get32u(dir_start+10, ImageInfo->motorola_intel);
#ifdef EXIF_DEBUG
			exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Using automatic offset correction: 0x%04X", ((int)dir_start-(int)offset_base+maker_note->offset+displacement) + offset_diff);
#endif
			if (offset_diff < 0 || offset_diff >= value_len ) {
				exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "IFD data bad offset: 0x%04X length 0x%04X", offset_diff, value_len);
				return FALSE;
			}
			offset_base = value_ptr + offset_diff;
			data_len = value_len - offset_diff;
			break;
#endif
		default:
		case MN_OFFSET_NORMAL:
			data_len = value_len;
			break;
	}

	if ((2+NumDirEntries*12) > value_len) {
		exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Illegal IFD size: 2 + 0x%04X*12 = 0x%04X > 0x%04X", NumDirEntries, 2+NumDirEntries*12, value_len);
		return FALSE;
	}
	if ((dir_start - value_ptr) > value_len - (2+NumDirEntries*12)) {
		exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Illegal IFD size: 0x%04X > 0x%04X", (dir_start - value_ptr) + (2+NumDirEntries*12), value_len);
		return FALSE;
	}

	for (de=0;de<NumDirEntries;de++) {
		if (!exif_process_IFD_TAG(ImageInfo, dir_start + 2 + 12 * de,
								  offset_base, data_len, displacement, section_index, 0, maker_note->tag_table)) {
			return FALSE;
		}
	}
	ImageInfo->motorola_intel = old_motorola_intel;
/*	NextDirOffset (must be NULL) = php_ifd_get32u(dir_start+2+12*de, ImageInfo->motorola_intel);*/
#ifdef EXIF_DEBUG
	exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Subsection %s done", exif_get_sectionname(SECTION_MAKERNOTE));
#endif
	return TRUE;
}

When PHP EXIF extension is parsing EXIF information from an image, e.g. via exif_read_data() function, in PHP versions 7.1.x below 7.1.31, 7.2.x below 7.2.21 and 7.3.x below 7.3.8 it is possible to supply it with data what will cause it to read past the allocated buffer. This may lead to information disclosure or crash.