public int readFragmentCount() throws DataFormatException {
      int off = readBodyOffset() + 1;
      if (readACKsIncluded()) {
        int numACKs = _message[off] & 0xff;
        off++;
        off += 4 * numACKs;
      }
      if (readACKBitfieldsIncluded()) {
        int numBitfields = _message[off] & 0xff;
        off++;

        for (int i = 0; i < numBitfields; i++) {
          PacketACKBitfield bf = new PacketACKBitfield(off);
          off += bf.getByteLength();
        }
      }
      if (readExtendedDataIncluded()) {
        int size = _message[off] & 0xff;
        off++;
        off += size;
      }
      return _message[off];
    }
    @Override
    public String toString() {
      StringBuilder buf = new StringBuilder(512);
      long msAgo = _context.clock().now() - readTimestamp() * 1000;
      buf.append("Data packet sent ").append(msAgo).append("ms ago ");
      buf.append("IV ");
      buf.append(
          Base64.encode(_message, _payloadBeginOffset - UDPPacket.IV_SIZE, UDPPacket.IV_SIZE));
      buf.append(" ");
      int off = readBodyOffset() + 1;
      if (readACKsIncluded()) {
        int numACKs = _message[off] & 0xff;
        off++;
        buf.append("with ACKs for ");
        for (int i = 0; i < numACKs; i++) {
          buf.append(DataHelper.fromLong(_message, off, 4)).append(' ');
          off += 4;
        }
      }
      if (readACKBitfieldsIncluded()) {
        int numBitfields = _message[off] & 0xff;
        off++;
        buf.append("with partial ACKs for ");

        try {
          for (int i = 0; i < numBitfields; i++) {
            PacketACKBitfield bf = new PacketACKBitfield(off);
            buf.append(bf.getMessageId()).append(' ');
            off += bf.getByteLength();
          }
        } catch (DataFormatException dfe) {
          buf.append("CORRUPT");
          return buf.toString();
        }
      }
      if (readExtendedDataIncluded()) {
        int size = _message[off] & 0xff;
        off++;
        buf.append("with extended size of ");
        buf.append(size);
        buf.append(' ');
        off += size;
      }

      int numFragments = _message[off] & 0xff;
      off++;
      buf.append("with fragmentCount of ");
      buf.append(numFragments);
      buf.append(' ');

      for (int i = 0; i < numFragments; i++) {
        buf.append("containing messageId ");
        buf.append(DataHelper.fromLong(_message, off, 4));
        off += 4;
        int fragNum = (_message[off] & 0xFF) >>> 1;
        boolean isLast = (_message[off] & 1) != 0;
        off++;
        buf.append(" frag# ").append(fragNum);
        buf.append(" isLast? ").append(isLast);
        buf.append(" info ").append(_message[off - 1]);
        int size = ((int) DataHelper.fromLong(_message, off, 2)) & 0x3FFF;
        off += 2;
        buf.append(" with ").append(size).append(" bytes; ");
        off += size;
      }

      return buf.toString();
    }