private Object readDynamicGroup(int size, ByteSource in) throws IOException {
    ByteBuf inbuf = (ByteBuf) in;
    int expectedEndPos = inbuf.position() + size;
    // PENDING: currently msgcodec only supports int32 as group id
    int groupId = (int) NativeBlinkInput.readUInt64(inbuf);
    inbuf.skip(4); // discard extension offset (not supported)
    try {
      Object group = readStaticGroup(groupId, inbuf);

      int skip = expectedEndPos - inbuf.position();
      if (skip < 0) {
        throw new DecodeException(
            "Malformed dynamic group. Read " + (-skip) + " bytes beyond group size.");
      } else if (skip > 0) {
        in.skip(skip);
      }
      return group;
    } catch (Exception e) {
      GroupDef groupDef = codec.getSchema().getGroup(groupId);
      if (groupDef != null) {
        throw new GroupDecodeException(groupDef.getName(), e);
      } else {
        throw e;
      }
    }
  }
 @Override
 public void writeDynamicGroup(ByteSink out, Object group)
     throws IOException, IllegalArgumentException {
   if (out instanceof ByteBuf) {
     ByteBuf buf = (ByteBuf) out;
     int start = buf.position();
     buf.skip(4); // size
     writeStaticGroupWithId(buf, group);
     int end = buf.position();
     int size = end - start - 4;
     buf.position(start);
     buf.writeIntLE(size);
     buf.position(end);
   } else {
     byte[] tmpBuf = codec.bufferPool().get();
     try {
       ByteArrayBuf tmpOut = new ByteArrayBuf(tmpBuf);
       writeDynamicGroup(tmpOut, group);
       tmpOut.flip();
       tmpOut.copyTo(out);
     } finally {
       codec.bufferPool().release(tmpBuf);
     }
   }
 }