@Override
  public int read(byte b[], int off, int len) throws IOException {
    if (b == null) {
      throw new NullPointerException();
    } else if (off < 0 || len < 0 || len > b.length - off) {
      throw new IndexOutOfBoundsException();
    } else if (len == 0) {
      return 0;
    }
    int totalRead = 0;

    if (buffer != null && buffer.remaining() > 0) {
      int bytesToRead = Math.min(buffer.remaining(), len);
      buffer.get(b, off, bytesToRead);
      totalRead += bytesToRead;
    }
    if (stream != null) {
      if (streamRead < streamLength && (len - totalRead) > 0) {
        long bytesToRead = Math.min(streamLength - streamRead, len - totalRead);
        int readFromStream = stream.read(b, off + totalRead, (int) bytesToRead);
        streamRead += readFromStream;
        totalRead += readFromStream;
      }

      if (streamRead == streamLength) {
        if (crc.position() == 0) {
          crc.putLong(stream.getValue());
          crc.flip();
        }
        int bytesToRead = Math.min(crc.remaining(), len - totalRead);
        crc.get(b, off + totalRead, bytesToRead);
        totalRead += bytesToRead;
      }
    }
    return totalRead > 0 ? totalRead : -1;
  }
Converts a set of message inputs into the right message format.
This provides the base implementation for all types of messages
that need to be persisted. The bytebuffer is mandatory for
all derived classes. It is used to store all data except and
stream based data. The stream is an optional payload used to
stream contents from an input stream.
ambry-messageformat/src/main/java/com/github/ambry/messageformat/MessageFormatInputStream.java