// Copyright © 2011-2014, Esko Luontola <www.orfjackal.net> // This software is released under the Apache License 2.0. // The license text is at http://www.apache.org/licenses/LICENSE-2.0 package fi.jumi.core.ipc.channel; import fi.jumi.actors.eventizers.Event; import fi.jumi.core.ipc.buffer.IpcBuffer; import fi.jumi.core.ipc.encoding.MessageEncoding; import fi.jumi.core.util.MemoryBarrier; import javax.annotation.concurrent.NotThreadSafe; import java.nio.charset.StandardCharsets; import java.util.Arrays; import static fi.jumi.core.ipc.encoding.StringEncoding.*; @NotThreadSafe public class IpcProtocol<T> implements IpcReader<T>, IpcWriter<T> { private static final byte[] HEADER_MAGIC_BYTES = "Jumi".getBytes(StandardCharsets.US_ASCII); private static final int PROTOCOL_VERSION = 1; private static final byte STATUS_EMPTY = 0; private static final byte STATUS_EXISTS = 1; private static final byte STATUS_END_OF_STREAM = 2; private final MemoryBarrier memoryBarrier = new MemoryBarrier(); private final IpcBuffer buffer; private final MessageEncoding<T> messageEncoding; public IpcProtocol(IpcBuffer buffer, EncodingFactory<T> encodingFactory) { this.buffer = buffer; this.messageEncoding = encodingFactory.create(buffer); } // write operations public void start() { writeHeader(); } @Override public void send(Event<T> message) { int currentMessage = writeStatusEmpty(); messageEncoding.encode(message); initNextMessage(); memoryBarrier.storeStore(); setStatusExists(currentMessage); } @Override public void close() { writeStatusEndOfStream(); } // read operations @Override public PollResult poll(T target) { int index = buffer.position(); byte status = readStatus(); if (status == STATUS_EMPTY) { buffer.position(index); return PollResult.NO_NEW_MESSAGES; } if (status == STATUS_END_OF_STREAM) { return PollResult.END_OF_STREAM; } memoryBarrier.loadLoad(); if (index == 0) { // For the header, the first byte works both as the status (when zero), // and part of the magic bytes (when non-zero), so we must not consume the byte before readHeader(). buffer.position(index); readHeader(); } else { assert status == STATUS_EXISTS : "unexpected status: " + status; messageEncoding.decode(target); } return PollResult.HAD_SOME_MESSAGES; } // header private void writeHeader() { // first byte is zero to signify that the whole header has not yet been written buffer.writeByte((byte) 0); for (int i = 1; i < HEADER_MAGIC_BYTES.length; i++) { buffer.writeByte(HEADER_MAGIC_BYTES[i]); } buffer.writeInt(PROTOCOL_VERSION); writeString(buffer, messageEncoding.getInterfaceName()); buffer.writeInt(messageEncoding.getInterfaceVersion()); // all done memoryBarrier.storeStore(); buffer.setByte(0, HEADER_MAGIC_BYTES[0]); } private void readHeader() { checkMagicBytes(); checkProtocolVersion(); checkInterface(); checkInterfaceVersion(); } private void checkMagicBytes() { byte[] actual = new byte[HEADER_MAGIC_BYTES.length]; for (int i = 0; i < actual.length; i++) { actual[i] = buffer.readByte(); } if (!Arrays.equals(actual, HEADER_MAGIC_BYTES)) { throw new IllegalArgumentException("wrong header: expected " + format(HEADER_MAGIC_BYTES) + " but was " + format(actual)); } } private String format(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) { sb.append(String.format("%02X ", b)); } return sb.toString().trim(); } private void checkProtocolVersion() { int actual = buffer.readInt(); if (actual != PROTOCOL_VERSION) { throw new IllegalArgumentException("unsupported protocol version: " + actual); } } private void checkInterface() { String actual = readString(buffer); if (!actual.equals(messageEncoding.getInterfaceName())) { throw new IllegalArgumentException("wrong interface: expected " + messageEncoding.getInterfaceName() + " but was " + actual); } } private void checkInterfaceVersion() { int actual = buffer.readInt(); if (actual != messageEncoding.getInterfaceVersion()) { throw new IllegalArgumentException("unsupported interface version: " + actual); } } // messages private byte readStatus() { return buffer.readByte(); } private int writeStatusEmpty() { int index = buffer.position(); buffer.writeByte(STATUS_EMPTY); return index; } private void initNextMessage() { // Write empty status for next message, so that the producer // is the first to touch a new segment, thus determining its size. buffer.setByte(buffer.position(), STATUS_EMPTY); } private void setStatusExists(int index) { buffer.setByte(index, STATUS_EXISTS); } private void writeStatusEndOfStream() { buffer.writeByte(STATUS_END_OF_STREAM); } public interface EncodingFactory<T> { MessageEncoding<T> create(IpcBuffer buffer); } }