class Framer {

  @VisibleForTesting
  void frameMessage(final MessageData message, final ByteBuf buf) {
    final int frameSize = message.getSize() + LENGTH_MESSAGE_ID;
    final int pad = padding16(frameSize);

    final byte id = (byte) message.getCode();

    // Generate the header data.
    final byte[] h = new byte[LENGTH_HEADER_DATA];
    h[0] = (byte) ((frameSize >> 16) & 0xff);
    h[1] = (byte) ((frameSize >> 8) & 0xff);
    h[2] = (byte) (frameSize & 0xff);
    System.arraycopy(PROTOCOL_HEADER, 0, h, LENGTH_FRAME_SIZE, PROTOCOL_HEADER.length);
    Arrays.fill(h, LENGTH_FRAME_SIZE + PROTOCOL_HEADER.length, h.length - 1, (byte) 0x00);
    encryptor.processBytes(h, 0, LENGTH_HEADER_DATA, h, 0);

    // Generate the header MAC.
    byte[] hMac = Arrays.copyOf(secrets.getEgressMac(), LENGTH_MAC);
    macEncryptor.processBlock(hMac, 0, hMac, 0);
    hMac = secrets.updateEgress(xor(h, hMac)).getEgressMac();
    hMac = Arrays.copyOf(hMac, LENGTH_MAC);
    buf.writeBytes(h).writeBytes(hMac);

    // Encrypt payload.
    final MutableBytes f = MutableBytes.create(frameSize + pad);

    final Bytes bv = id == 0 ? RLP.NULL : RLP.encodeOne(Bytes.of(id));
    assert bv.size() == 1;
    f.set(0, bv.get(0));

    // Zero-padded to 16-byte boundary.
    message.getData().copyTo(f, 1);
    encryptor.processBytes(f.toArrayUnsafe(), 0, f.size(), f.toArrayUnsafe(), 0);

    // Calculate the frame MAC.
    final byte[] fMacSeed =
        Arrays.copyOf(secrets.updateEgress(f.toArrayUnsafe()).getEgressMac(), LENGTH_MAC);
    byte[] fMac = new byte[16];
    macEncryptor.processBlock(fMacSeed, 0, fMac, 0);
    fMac = Arrays.copyOf(secrets.updateEgress(xor(fMac, fMacSeed)).getEgressMac(), LENGTH_MAC);

    buf.writeBytes(f.toArrayUnsafe()).writeBytes(fMac);
  }

  public  Framer(final HandshakeSecrets secrets);

}