@Override
    public void write(ByteBuf bb, OFFlowStatsEntryVer14 message) {
      int startIndex = bb.writerIndex();
      // length is length of variable message, will be updated at the end
      int lengthIndex = bb.writerIndex();
      bb.writeShort(U16.t(0));

      message.tableId.writeByte(bb);
      // pad: 1 bytes
      bb.writeZero(1);
      bb.writeInt(U32.t(message.durationSec));
      bb.writeInt(U32.t(message.durationNsec));
      bb.writeShort(U16.t(message.priority));
      bb.writeShort(U16.t(message.idleTimeout));
      bb.writeShort(U16.t(message.hardTimeout));
      OFFlowModFlagsSerializerVer14.writeTo(bb, message.flags);
      bb.writeShort(U16.t(message.importance));
      // pad: 2 bytes
      bb.writeZero(2);
      bb.writeLong(message.cookie.getValue());
      bb.writeLong(message.packetCount.getValue());
      bb.writeLong(message.byteCount.getValue());
      message.match.writeTo(bb);
      ChannelUtils.writeList(bb, message.instructions);

      // update length field
      int length = bb.writerIndex() - startIndex;
      bb.setShort(lengthIndex, length);
    }
    @Override
    public OFFlowStatsEntry readFrom(ByteBuf bb) throws OFParseError {
      int start = bb.readerIndex();
      int length = U16.f(bb.readShort());
      if (length < MINIMUM_LENGTH)
        throw new OFParseError(
            "Wrong length: Expected to be >= " + MINIMUM_LENGTH + ", was: " + length);
      if (bb.readableBytes() + (bb.readerIndex() - start) < length) {
        // Buffer does not have all data yet
        bb.readerIndex(start);
        return null;
      }
      if (logger.isTraceEnabled()) logger.trace("readFrom - length={}", length);
      TableId tableId = TableId.readByte(bb);
      // pad: 1 bytes
      bb.skipBytes(1);
      long durationSec = U32.f(bb.readInt());
      long durationNsec = U32.f(bb.readInt());
      int priority = U16.f(bb.readShort());
      int idleTimeout = U16.f(bb.readShort());
      int hardTimeout = U16.f(bb.readShort());
      Set<OFFlowModFlags> flags = OFFlowModFlagsSerializerVer14.readFrom(bb);
      int importance = U16.f(bb.readShort());
      // pad: 2 bytes
      bb.skipBytes(2);
      U64 cookie = U64.ofRaw(bb.readLong());
      U64 packetCount = U64.ofRaw(bb.readLong());
      U64 byteCount = U64.ofRaw(bb.readLong());
      Match match = ChannelUtilsVer14.readOFMatch(bb);
      List<OFInstruction> instructions =
          ChannelUtils.readList(bb, length - (bb.readerIndex() - start), OFInstructionVer14.READER);

      OFFlowStatsEntryVer14 flowStatsEntryVer14 =
          new OFFlowStatsEntryVer14(
              tableId,
              durationSec,
              durationNsec,
              priority,
              idleTimeout,
              hardTimeout,
              flags,
              importance,
              cookie,
              packetCount,
              byteCount,
              match,
              instructions);
      if (logger.isTraceEnabled()) logger.trace("readFrom - read={}", flowStatsEntryVer14);
      return flowStatsEntryVer14;
    }
 @Override
 public void funnel(OFFlowStatsEntryVer14 message, PrimitiveSink sink) {
   // FIXME: skip funnel of length
   message.tableId.putTo(sink);
   // skip pad (1 bytes)
   sink.putLong(message.durationSec);
   sink.putLong(message.durationNsec);
   sink.putInt(message.priority);
   sink.putInt(message.idleTimeout);
   sink.putInt(message.hardTimeout);
   OFFlowModFlagsSerializerVer14.putTo(message.flags, sink);
   sink.putInt(message.importance);
   // skip pad (2 bytes)
   message.cookie.putTo(sink);
   message.packetCount.putTo(sink);
   message.byteCount.putTo(sink);
   message.match.putTo(sink);
   FunnelUtils.putList(message.instructions, sink);
 }