naga-2.1/0000775000175000017500000000000011653321267011542 5ustar mbanckmbancknaga-2.1/build.xml0000664000175000017500000000626211653321234013363 0ustar mbanckmbanck Naga]]>
naga-2.1/src/0000775000175000017500000000000011653321234012323 5ustar mbanckmbancknaga-2.1/src/test/0000775000175000017500000000000011653321234013302 5ustar mbanckmbancknaga-2.1/src/test/naga/0000775000175000017500000000000011653321253014211 5ustar mbanckmbancknaga-2.1/src/test/naga/packetwriter/0000775000175000017500000000000011653321253016715 5ustar mbanckmbancknaga-2.1/src/test/naga/packetwriter/RegularPacketWriterTest.java0000664000175000017500000000372511653321253024355 0ustar mbanckmbanckpackage naga.packetwriter; /** * @author Christoffer Lerno */ import junit.framework.TestCase; public class RegularPacketWriterTest extends TestCase { RegularPacketWriter m_regularPacketWriter; public void testRegularPacketWriter() throws Exception { m_regularPacketWriter = new RegularPacketWriter(3, true); m_regularPacketWriter.setPacket("Foo!".getBytes()); assertEquals(false, m_regularPacketWriter.isEmpty()); assertEquals(0, m_regularPacketWriter.getBuffer().get()); assertEquals(0, m_regularPacketWriter.getBuffer().get()); assertEquals(4, m_regularPacketWriter.getBuffer().get()); byte[] buffer = new byte[4]; m_regularPacketWriter.getBuffer().get(buffer); assertEquals(true, m_regularPacketWriter.isEmpty()); assertEquals("Foo!", new String(buffer)); m_regularPacketWriter = new RegularPacketWriter(3, true); m_regularPacketWriter.setPacket(new byte[0]); assertEquals(false, m_regularPacketWriter.isEmpty()); assertEquals(0, m_regularPacketWriter.getBuffer().get()); assertEquals(0, m_regularPacketWriter.getBuffer().get()); assertEquals(0, m_regularPacketWriter.getBuffer().get()); assertEquals(true, m_regularPacketWriter.isEmpty()); assertEquals(false, m_regularPacketWriter.getBuffer().hasRemaining()); } // See Bug 5 public void testFourByteHeader() throws Exception { m_regularPacketWriter = new RegularPacketWriter(4, true); m_regularPacketWriter.setPacket("Foo!".getBytes()); assertEquals(false, m_regularPacketWriter.isEmpty()); assertEquals(0, m_regularPacketWriter.getBuffer().get()); assertEquals(0, m_regularPacketWriter.getBuffer().get()); assertEquals(0, m_regularPacketWriter.getBuffer().get()); assertEquals(4, m_regularPacketWriter.getBuffer().get()); byte[] buffer = new byte[4]; m_regularPacketWriter.getBuffer().get(buffer); assertEquals(true, m_regularPacketWriter.isEmpty()); assertEquals("Foo!", new String(buffer)); } }naga-2.1/src/test/naga/SocketChannelResponderTest.java0000664000175000017500000002046411653321253022325 0ustar mbanckmbanckpackage naga; /** * @author Christoffer Lerno */ import junit.framework.TestCase; import org.easymock.classextension.EasyMock; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; @SuppressWarnings({"StaticMethodReferencedViaSubclass"}) public class SocketChannelResponderTest extends TestCase { SocketChannelResponder m_socketChannelResponder; SelectionKey m_key; SocketChannel m_channel; protected void setUp() throws Exception { m_channel = EasyMock.createMock(SocketChannel.class); m_key = EasyMock.createMock(SelectionKey.class); } public void testWriteExceedingMax() { EasyMock.expect(m_channel.isConnected()).andReturn(true).once(); EasyMock.expect(m_key.interestOps()).andReturn(0).atLeastOnce(); EasyMock.expect(m_key.interestOps(0)).andReturn(m_key).once(); replay(); m_socketChannelResponder = new SocketChannelResponder(null, m_channel, new InetSocketAddress("localhost", 123)); m_socketChannelResponder.setKey(m_key); m_socketChannelResponder.setMaxQueueSize(3); assertEquals(0, m_socketChannelResponder.getBytesWritten()); assertEquals(0, m_socketChannelResponder.getWriteQueueSize()); verify(); reset(); EasyMock.expect(m_key.interestOps()).andReturn(0).atLeastOnce(); EasyMock.expect(m_key.interestOps(SelectionKey.OP_WRITE)).andReturn(m_key).once(); replay(); // Add a small packet assertEquals(true, m_socketChannelResponder.write("F!".getBytes())); verify(); // This fails because the queue would be too big. assertEquals(false, m_socketChannelResponder.write("OO".getBytes())); } public void testWrite() throws Exception { // Open a writer. PacketWriter writer = EasyMock.createMock(PacketWriter.class); EasyMock.expect(m_channel.isConnected()).andReturn(true).once(); EasyMock.expect(m_key.interestOps(0)).andReturn(m_key).once(); EasyMock.expect(m_key.interestOps()).andReturn(0).atLeastOnce(); replay(); m_socketChannelResponder = new SocketChannelResponder(null, m_channel, new InetSocketAddress("localhost", 123)); m_socketChannelResponder.setKey(m_key); m_socketChannelResponder.setPacketWriter(writer); assertEquals(0, m_socketChannelResponder.getBytesWritten()); assertEquals(0, m_socketChannelResponder.getWriteQueueSize()); verify(); reset(); EasyMock.expect(m_key.interestOps()).andReturn(0).atLeastOnce(); EasyMock.expect(m_key.interestOps(SelectionKey.OP_WRITE)).andReturn(m_key).once(); replay(); // Add a packet byte[] packet = "FOO!".getBytes(); m_socketChannelResponder.write(packet); assertEquals(0, m_socketChannelResponder.getBytesWritten()); assertEquals(4, m_socketChannelResponder.getWriteQueueSize()); verify(); reset(); // Write nothing of the packet. EasyMock.expect(m_key.interestOps(0)).andReturn(m_key).once(); EasyMock.expect(m_key.interestOps()).andReturn(0).atLeastOnce(); EasyMock.expect(m_key.interestOps(SelectionKey.OP_WRITE)).andReturn(m_key).once(); EasyMock.expect(m_channel.write((ByteBuffer) null)).andReturn(0).once(); EasyMock.expect(writer.isEmpty()).andReturn(true).once(); EasyMock.expect(writer.isEmpty()).andReturn(false).times(2); EasyMock.expect(writer.getBuffer()).andReturn(null).once(); writer.setPacket(packet); EasyMock.expectLastCall().once(); replay(); EasyMock.replay(writer); m_socketChannelResponder.socketReadyForWrite(); assertEquals(0, m_socketChannelResponder.getBytesWritten()); assertEquals(0, m_socketChannelResponder.getWriteQueueSize()); EasyMock.verify(writer); EasyMock.reset(writer); verify(); reset(); // Write part of the packet. EasyMock.expect(m_key.interestOps(0)).andReturn(m_key).once(); EasyMock.expect(m_key.interestOps(SelectionKey.OP_WRITE)).andReturn(m_key).once(); EasyMock.expect(m_key.interestOps()).andReturn(0).atLeastOnce(); EasyMock.expect(m_channel.write((ByteBuffer) null)).andReturn(3).once(); EasyMock.expect(m_channel.write((ByteBuffer) null)).andReturn(0).once(); EasyMock.expect(writer.isEmpty()).andReturn(false).times(5); EasyMock.expect(writer.getBuffer()).andReturn(null).times(2); replay(); EasyMock.replay(writer); m_socketChannelResponder.socketReadyForWrite(); assertEquals(3, m_socketChannelResponder.getBytesWritten()); assertEquals(0, m_socketChannelResponder.getWriteQueueSize()); EasyMock.verify(writer); EasyMock.reset(writer); verify(); reset(); // Finish writing the packet. EasyMock.expect(m_key.interestOps(0)).andReturn(m_key).once(); EasyMock.expect(m_channel.write((ByteBuffer) null)).andReturn(1).once(); EasyMock.expect(m_key.interestOps()).andReturn(0).atLeastOnce(); EasyMock.expect(writer.isEmpty()).andReturn(false).times(3); EasyMock.expect(writer.isEmpty()).andReturn(true).times(3); EasyMock.expect(writer.getBuffer()).andReturn(null).times(1); replay(); EasyMock.replay(writer); m_socketChannelResponder.socketReadyForWrite(); assertEquals(4, m_socketChannelResponder.getBytesWritten()); assertEquals(0, m_socketChannelResponder.getWriteQueueSize()); EasyMock.verify(writer); EasyMock.reset(writer); verify(); reset(); // Test Empty read EasyMock.expect(m_key.interestOps(0)).andReturn(m_key).once(); EasyMock.expect(m_key.interestOps()).andReturn(0).atLeastOnce(); EasyMock.expect(writer.isEmpty()).andReturn(true).times(2); replay(); EasyMock.replay(writer); m_socketChannelResponder.socketReadyForWrite(); EasyMock.verify(writer); verify(); } private void reset() { EasyMock.reset(m_channel); EasyMock.reset(m_key); } private void verify() { EasyMock.verify(m_channel); EasyMock.verify(m_key); } private void replay() { EasyMock.replay(m_channel); EasyMock.replay(m_key); } public void testFinishConnectThrowsException() throws IOException { NIOService nioService = new NIOService(); EasyMock.expect(m_key.interestOps(SelectionKey.OP_CONNECT)).andReturn(m_key).times(2); EasyMock.expect(m_key.interestOps()).andReturn(0).atLeastOnce(); m_key.cancel(); EasyMock.expectLastCall().once(); replay(); m_socketChannelResponder = new SocketChannelResponder(nioService, SocketChannel.open(), new InetSocketAddress("localhost", 123)); m_socketChannelResponder.setKey(m_key); m_socketChannelResponder.socketReadyForConnect(); assertEquals(true, m_socketChannelResponder.isOpen()); nioService.selectNonBlocking(); assertEquals(false, m_socketChannelResponder.isOpen()); verify(); } public void testCanReadThrowsException() throws IOException { NIOService nioService = new NIOService(); EasyMock.expect(m_key.interestOps(SelectionKey.OP_CONNECT)).andReturn(m_key).times(2); EasyMock.expect(m_key.interestOps()).andReturn(0).atLeastOnce(); m_key.cancel(); EasyMock.expectLastCall().once(); replay(); m_socketChannelResponder = new SocketChannelResponder(nioService, SocketChannel.open(), new InetSocketAddress("localhost", 123)); m_socketChannelResponder.setKey(m_key); m_socketChannelResponder.socketReadyForRead(); assertEquals(true, m_socketChannelResponder.isOpen()); nioService.selectNonBlocking(); assertEquals(false, m_socketChannelResponder.isOpen()); verify(); } public void testCanWriteThrowsException() throws IOException { NIOService nioService = new NIOService(); EasyMock.expect(m_key.interestOps(SelectionKey.OP_CONNECT)).andReturn(m_key).times(4); EasyMock.expect(m_key.interestOps()).andReturn(0).atLeastOnce(); m_key.cancel(); EasyMock.expectLastCall().once(); replay(); m_socketChannelResponder = new SocketChannelResponder(nioService, SocketChannel.open(), new InetSocketAddress("localhost", 123)); m_socketChannelResponder.setKey(m_key); m_socketChannelResponder.write(new byte[] { 0 }); m_socketChannelResponder.socketReadyForWrite(); assertEquals(true, m_socketChannelResponder.isOpen()); nioService.selectNonBlocking(); assertEquals(false, m_socketChannelResponder.isOpen()); verify(); } public void testSetKey() throws Exception { NIOService nioService = new NIOService(); m_key.cancel(); EasyMock.expectLastCall().once(); replay(); m_socketChannelResponder = new SocketChannelResponder(nioService, null, new InetSocketAddress("localhost", 123)); m_socketChannelResponder.close(); nioService.selectNonBlocking(); m_socketChannelResponder.setKey(m_key); verify(); assertEquals(false, m_socketChannelResponder.isOpen()); } } naga-2.1/src/test/naga/packetreader/0000775000175000017500000000000011653321253016643 5ustar mbanckmbancknaga-2.1/src/test/naga/packetreader/RegularPacketReaderTest.java0000664000175000017500000000141711653321253024225 0ustar mbanckmbanckpackage naga.packetreader; /** * @author Christoffer Lerno */ import junit.framework.TestCase; public class RegularPacketReaderTest extends TestCase { RegularPacketReader m_regularPacketReader; public void testRegularPacketReader() throws Exception { m_regularPacketReader = new RegularPacketReader(3, true); m_regularPacketReader.getBuffer().put(new byte[] { 0, 0, 4 }); assertEquals(null, m_regularPacketReader.getNextPacket()); m_regularPacketReader.getBuffer().put("Foo!".getBytes()); assertEquals("Foo!", new String(m_regularPacketReader.getNextPacket())); m_regularPacketReader = new RegularPacketReader(3, true); m_regularPacketReader.getBuffer().put(new byte[] { 0, 0, 0 }); assertEquals("", new String(m_regularPacketReader.getNextPacket())); } }naga-2.1/src/test/naga/packetreader/DelimiterPacketReaderTest.java0000664000175000017500000000162311653321253024541 0ustar mbanckmbanckpackage naga.packetreader; /** * @author Christoffer Lerno */ import junit.framework.TestCase; public class DelimiterPacketReaderTest extends TestCase { DelimiterPacketReader m_delimiterPacketReader; public void testDelimiterPacketReader() throws Exception { try { new DelimiterPacketReader((byte)0, 0, 1); fail(); } catch (IllegalArgumentException e) { assertEquals("Min buffer must at least be 1 byte.", e.getMessage()); } try { new DelimiterPacketReader((byte)0, 1, 0); fail(); } catch (IllegalArgumentException e) { assertEquals("Read buffer cannot be be larger than the max packet size.", e.getMessage()); } m_delimiterPacketReader = new DelimiterPacketReader((byte)0, 1, 20); assertEquals(20, m_delimiterPacketReader.getMaxPacketSize()); m_delimiterPacketReader.setMaxPacketSize(19); assertEquals(19, m_delimiterPacketReader.getMaxPacketSize()); } }naga-2.1/src/test/naga/packetreader/AsciiLinePacketReaderTest.java0000664000175000017500000000407311653321253024465 0ustar mbanckmbanckpackage naga.packetreader; /** * @author Christoffer Lerno */ import junit.framework.TestCase; import naga.exception.ProtocolViolationException; public class AsciiLinePacketReaderTest extends TestCase { AsciiLinePacketReader m_asciiLinePacketReader; public void testAsciiLinePacketReader() throws Exception { m_asciiLinePacketReader = new AsciiLinePacketReader(); assertEquals(DelimiterPacketReader.DEFAULT_READ_BUFFER_SIZE, m_asciiLinePacketReader.getBuffer().remaining()); byte[] notALine = "Foo".getBytes(); m_asciiLinePacketReader.getBuffer().put(notALine); assertEquals(null, m_asciiLinePacketReader.getNextPacket()); byte[] endOfLine = "\n".getBytes(); m_asciiLinePacketReader.getBuffer().put(endOfLine); assertEquals("Foo", new String(m_asciiLinePacketReader.getNextPacket())); assertEquals(DelimiterPacketReader.DEFAULT_READ_BUFFER_SIZE, m_asciiLinePacketReader.getBuffer().remaining()); byte[] multiLines = "\n\nFoo\n\nBar\nFoobar".getBytes(); m_asciiLinePacketReader.getBuffer().put(multiLines); assertEquals(0, m_asciiLinePacketReader.getNextPacket().length); assertEquals(0, m_asciiLinePacketReader.getNextPacket().length); assertEquals("Foo", new String(m_asciiLinePacketReader.getNextPacket())); assertEquals(0, m_asciiLinePacketReader.getNextPacket().length); assertEquals("Bar", new String(m_asciiLinePacketReader.getNextPacket())); assertEquals(null, m_asciiLinePacketReader.getNextPacket()); } public void testOverflow() throws ProtocolViolationException { m_asciiLinePacketReader = new AsciiLinePacketReader(3, 3); assertEquals(3, m_asciiLinePacketReader.getBuffer().remaining()); byte[] notALine = "Foo".getBytes(); m_asciiLinePacketReader.getBuffer().put(notALine); assertEquals(null, m_asciiLinePacketReader.getNextPacket()); m_asciiLinePacketReader.getBuffer().put("!".getBytes()); assertEquals(null, m_asciiLinePacketReader.getNextPacket()); byte[] endOfLine = "\n".getBytes(); try { m_asciiLinePacketReader.getBuffer().put(endOfLine); fail("Should fail"); } catch (ProtocolViolationException e) {} } }naga-2.1/src/test/naga/NIOUtilsTest.java0000664000175000017500000000774611653321253017400 0ustar mbanckmbanckpackage naga; /** * @author Christoffer Lerno */ import junit.framework.TestCase; import org.easymock.classextension.EasyMock; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.Channel; import java.nio.channels.SelectionKey; public class NIOUtilsTest extends TestCase { @SuppressWarnings({"InstantiationOfUtilityClass"}) public void testInstance() { new NIOUtils(); } public void testGetPacketSizeFromByteBuffer() throws Exception { assertEquals(0xFF00FE, NIOUtils.getPacketSizeFromByteBuffer(ByteBuffer.wrap(new byte[]{(byte) 0xFF, 0x00, (byte) 0xFE}), true)); assertEquals(0xFF00FE, NIOUtils.getPacketSizeFromByteBuffer(ByteBuffer.wrap(new byte[]{(byte) 0xFE, 0x00, (byte) 0xFF}), false)); } public void testGetByteBufferFromPacketSizeTooBig() { try { NIOUtils.getByteBufferFromPacketSize(3, -1, true); fail(); } catch (IllegalArgumentException e) { assertEquals("Payload size is less than 0.", e.getMessage()); } try { NIOUtils.getByteBufferFromPacketSize(3, 0xFFFFFF + 1, true); fail("Should throw exception"); } catch (IllegalArgumentException e) { assertEquals("Payload size cannot be encoded into 3 byte(s).", e.getMessage()); } try { NIOUtils.getByteBufferFromPacketSize(1, 0xFF + 1, true); fail("Payload size cannot be encoded into 3 byte(s)."); } catch (IllegalArgumentException e) { assertEquals("Payload size cannot be encoded into 1 byte(s).", e.getMessage()); } } public void testGetByteBufferFromPacketSize() throws Exception { getByteBufferFromPacketSizeTests(true); getByteBufferFromPacketSizeTests(false); assertEquals(0xFF, NIOUtils.getPacketSizeFromByteBuffer(NIOUtils.getByteBufferFromPacketSize(1, 0xFF, true), false)); assertEquals(0x00, NIOUtils.getPacketSizeFromByteBuffer(NIOUtils.getByteBufferFromPacketSize(3, 0x00, false), true)); } private void getByteBufferFromPacketSizeTests(boolean endian) { assertEquals(0xFFFFFF, NIOUtils.getPacketSizeFromByteBuffer(NIOUtils.getByteBufferFromPacketSize(3, 0xFFFFFF, endian), endian)); assertEquals(0x000000, NIOUtils.getPacketSizeFromByteBuffer(NIOUtils.getByteBufferFromPacketSize(3, 0x000000, endian), endian)); assertEquals(0xFFFFFE, NIOUtils.getPacketSizeFromByteBuffer(NIOUtils.getByteBufferFromPacketSize(3, 0xFFFFFE, endian), endian)); assertEquals(Integer.MAX_VALUE, NIOUtils.getPacketSizeFromByteBuffer(NIOUtils.getByteBufferFromPacketSize(4, Integer.MAX_VALUE, endian), endian)); assertEquals(0x00000000, NIOUtils.getPacketSizeFromByteBuffer(NIOUtils.getByteBufferFromPacketSize(4, 0x00000000, endian), endian)); } public void testCancelKeySilently() throws Exception { SelectionKey key = EasyMock.createMock(SelectionKey.class); key.cancel(); EasyMock.expectLastCall().andThrow(new RuntimeException()); EasyMock.replay(key); NIOUtils.cancelKeySilently(key); EasyMock.verify(key); } public void testCloseChannelSilently() throws Exception { Channel channel = EasyMock.createMock(Channel.class); channel.close(); EasyMock.expectLastCall().andThrow(new IOException()); EasyMock.replay(channel); NIOUtils.closeChannelSilently(channel); EasyMock.verify(channel); } }naga-2.1/src/test/naga/eventmachine/0000775000175000017500000000000011653321253016657 5ustar mbanckmbancknaga-2.1/src/test/naga/eventmachine/DelayedActionTest.java0000664000175000017500000000265711653321234023100 0ustar mbanckmbanckpackage naga.eventmachine; /** * @author Christoffer Lerno */ import junit.framework.TestCase; import java.util.Date; public class DelayedActionTest extends TestCase { DelayedAction m_delayedAction; public void testCompare() { DelayedAction action1 = new DelayedAction(null, 3); DelayedAction action2 = new DelayedAction(null, 5); DelayedAction action3 = new DelayedAction(null, 3); DelayedAction action4 = new DelayedAction(null, 2); assertEquals(0, action1.compareTo(action1)); assertEquals(-1, action1.compareTo(action2)); assertEquals(-1, action1.compareTo(action3)); assertEquals(1, action1.compareTo(action4)); assertEquals(1, action2.compareTo(action1)); assertEquals(0, action2.compareTo(action2)); assertEquals(1, action2.compareTo(action3)); assertEquals(1, action2.compareTo(action4)); assertEquals(1, action3.compareTo(action1)); assertEquals(-1, action3.compareTo(action2)); assertEquals(0, action3.compareTo(action3)); assertEquals(1, action3.compareTo(action4)); assertEquals(-1, action4.compareTo(action1)); assertEquals(-1, action4.compareTo(action2)); assertEquals(-1, action4.compareTo(action3)); assertEquals(0, action4.compareTo(action4)); } public void testDelayedActionToString() throws Exception { long time = System.currentTimeMillis(); Date date = new Date(time); assertEquals("DelayedAction @ " + date + " [Cancelled]", new DelayedAction(null, time).toString()); } }naga-2.1/src/test/naga/eventmachine/EventMachineTest.java0000664000175000017500000001216511653321253022735 0ustar mbanckmbanckpackage naga.eventmachine; /** * @author Christoffer Lerno */ import junit.framework.TestCase; import java.util.Date; import java.util.Queue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; public class EventMachineTest extends TestCase { EventMachine m_eventMachine; @Override protected void setUp() throws Exception { m_eventMachine = new EventMachine(); } public void testExceptionHandling() throws Exception { final StringBuffer exceptions = new StringBuffer(); try { m_eventMachine.setObserver(null); } catch (NullPointerException e) { } assertEquals(ExceptionObserver.DEFAULT, m_eventMachine.getObserver()); m_eventMachine.start(); m_eventMachine.asyncExecute(new Runnable() { public void run() { throw new RuntimeException("Test1"); } }); Thread.sleep(10); m_eventMachine.setObserver(new ExceptionObserver() { public void notifyExceptionThrown(Throwable e) { exceptions.append(e.getMessage()); } }); m_eventMachine.asyncExecute(new Runnable() { public void run() { throw new RuntimeException("Test2"); } }); Thread.sleep(50); assertEquals("Test2", exceptions.toString()); } public void testExecuteLaterOrdering() throws Exception { Runnable a = new Runnable() { public void run() { } }; Runnable b = new Runnable() { public void run() { } }; Runnable c = new Runnable() { public void run() { } }; m_eventMachine.executeLater(a, 20000); m_eventMachine.executeLater(b, 30000); m_eventMachine.executeLater(c, 10000); Queue actions = m_eventMachine.getQueue(); assertEquals(actions.poll().getCall(), c); assertEquals(actions.poll().getCall(), a); assertEquals(actions.poll().getCall(), b); } public void testExecuteAtOrdering() throws Exception { Runnable a = new Runnable() { public void run() { } }; Runnable b = new Runnable() { public void run() { } }; Runnable c = new Runnable() { public void run() { } }; Date d = new Date(); m_eventMachine.executeAt(a, d); m_eventMachine.executeAt(b, d); m_eventMachine.executeAt(c, d); Queue actions = m_eventMachine.getQueue(); assertEquals(actions.poll().getCall(), a); assertEquals(actions.poll().getCall(), b); assertEquals(actions.poll().getCall(), c); } public void testExecute() throws Exception { final CountDownLatch latch = new CountDownLatch(1); m_eventMachine.asyncExecute(new Runnable() { public void run() { latch.countDown(); } }); assertEquals(false, latch.await(50, TimeUnit.MILLISECONDS)); m_eventMachine.start(); assertEquals(true, latch.await(10, TimeUnit.SECONDS)); m_eventMachine.stop(); } public void testExecuteLater() throws Exception { final CountDownLatch latch = new CountDownLatch(1); m_eventMachine.executeLater(new Runnable() { public void run() { latch.countDown(); } }, 5); assertEquals(false, latch.await(50, TimeUnit.MILLISECONDS)); m_eventMachine.start(); assertEquals(true, latch.await(10, TimeUnit.SECONDS)); m_eventMachine.stop(); } public void testExecuteLater2() throws Exception { final CountDownLatch latch = new CountDownLatch(1); m_eventMachine.executeLater(new Runnable() { public void run() { latch.countDown(); } }, 50); m_eventMachine.start(); assertEquals(false, latch.await(20, TimeUnit.MILLISECONDS)); assertEquals(true, latch.await(50, TimeUnit.SECONDS)); m_eventMachine.stop(); } public void testExecuteLaterWith() throws Exception { final CountDownLatch latch = new CountDownLatch(1); m_eventMachine.executeLater(new Runnable() { public void run() { latch.countDown(); } }, 5); assertEquals(false, latch.await(50, TimeUnit.MILLISECONDS)); m_eventMachine.start(); assertEquals(true, latch.await(10, TimeUnit.SECONDS)); m_eventMachine.stop(); } public void testNIO() { assertNotNull(m_eventMachine.getNIOService()); } public void testStartStop() throws Exception { m_eventMachine.start(); try { m_eventMachine.start(); fail(); } catch (IllegalStateException e) { } m_eventMachine.stop(); try { m_eventMachine.stop(); fail(); } catch (IllegalStateException e) { } final CountDownLatch latch = new CountDownLatch(1); m_eventMachine.start(); m_eventMachine.asyncExecute(new Runnable() { public void run() { latch.countDown(); } }); assertEquals(true, latch.await(10, TimeUnit.MILLISECONDS)); } public void testCancelEvent() throws Exception { final AtomicInteger integer = new AtomicInteger(); m_eventMachine.executeLater(new Runnable() { public void run() { integer.incrementAndGet(); } }, 5); DelayedEvent event = m_eventMachine.executeLater(new Runnable() { public void run() { integer.addAndGet(2); } }, 5); event.cancel(); assertEquals(2, m_eventMachine.getQueueSize()); m_eventMachine.start(); Thread.sleep(20); assertEquals(1, integer.intValue()); m_eventMachine.stop(); assertEquals(0, m_eventMachine.getQueueSize()); } }naga-2.1/src/test/naga/NIOServiceTest.java0000664000175000017500000000660011653321234017663 0ustar mbanckmbanckpackage naga; /** * @author Christoffer Lerno */ import junit.framework.TestCase; import org.easymock.classextension.EasyMock; import java.io.IOException; import java.net.InetSocketAddress; public class NIOServiceTest extends TestCase { NIOService m_service; public void setUp() throws IOException { m_service = new NIOService(); } public void testOpenServerSocket() throws Exception { ConnectionAcceptor acceptor = EasyMock.createMock(ConnectionAcceptor.class); EasyMock.expect(acceptor.acceptConnection((InetSocketAddress) EasyMock.anyObject())).andReturn(true).once(); EasyMock.replay(acceptor); final SocketObserver socketObserverClient = EasyMock.createMock(SocketObserver.class); final SocketObserver socketObserverServer = EasyMock.createMock(SocketObserver.class); ServerSocketObserver serverSocketObserver = new ServerSocketObserverAdapter() { public void newConnection(NIOSocket nioSocket) { nioSocket.listen(socketObserverServer); } }; socketObserverServer.connectionOpened((NIOSocket) EasyMock.anyObject()); EasyMock.expectLastCall().once(); socketObserverClient.connectionOpened((NIOSocket) EasyMock.anyObject()); EasyMock.expectLastCall().once(); EasyMock.replay(socketObserverClient); EasyMock.replay(socketObserverServer); NIOServerSocket serverSocket = m_service.openServerSocket(new InetSocketAddress(3133), 0); NIOSocket socket = m_service.openSocket("localhost", 3133); socket.listen(socketObserverClient); serverSocket.listen(serverSocketObserver); serverSocket.setConnectionAcceptor(acceptor); while (serverSocket.getTotalConnections() == 0) { m_service.selectBlocking(); } m_service.selectNonBlocking(); assertEquals("[]", m_service.getQueue().toString()); EasyMock.verify(socketObserverClient); EasyMock.verify(socketObserverServer); EasyMock.verify(acceptor); assertEquals(socket.getPort(), serverSocket.socket().getLocalPort()); assertEquals(1, serverSocket.getTotalConnections()); assertEquals(1, serverSocket.getTotalAcceptedConnections()); } public void testAcceptRefused() throws Exception { ConnectionAcceptor acceptor = EasyMock.createMock(ConnectionAcceptor.class); EasyMock.expect(acceptor.acceptConnection((InetSocketAddress) EasyMock.anyObject())).andReturn(false).once(); EasyMock.replay(acceptor); ServerSocketObserver serverSocketObserver = EasyMock.createMock(ServerSocketObserver.class); EasyMock.replay(serverSocketObserver); SocketObserver socketOwnerClientSide = EasyMock.createMock(SocketObserver.class); socketOwnerClientSide.connectionOpened((NIOSocket) EasyMock.anyObject()); EasyMock.expectLastCall().once(); socketOwnerClientSide.connectionBroken((NIOSocket) EasyMock.anyObject(), (Exception) EasyMock.anyObject()); EasyMock.expectLastCall().once(); EasyMock.replay(socketOwnerClientSide); NIOServerSocket serverSocket = m_service.openServerSocket(new InetSocketAddress(3134), 0); serverSocket.setConnectionAcceptor(acceptor); serverSocket.listen(serverSocketObserver); NIOSocket socket = m_service.openSocket("localhost", 3134); socket.listen(socketOwnerClientSide); while (socket.isOpen()) { m_service.selectBlocking(); } EasyMock.verify(serverSocketObserver); EasyMock.verify(acceptor); EasyMock.verify(socketOwnerClientSide); assertEquals(1, serverSocket.getTotalConnections()); assertEquals(1, serverSocket.getTotalRefusedConnections()); } }naga-2.1/src/main/0000775000175000017500000000000011653321234013247 5ustar mbanckmbancknaga-2.1/src/main/naga/0000775000175000017500000000000011653321253014156 5ustar mbanckmbancknaga-2.1/src/main/naga/packetwriter/0000775000175000017500000000000011653321253016662 5ustar mbanckmbancknaga-2.1/src/main/naga/packetwriter/RegularPacketWriter.java0000664000175000017500000000353411653321253023460 0ustar mbanckmbanckpackage naga.packetwriter; import naga.NIOUtils; import naga.PacketWriter; import java.nio.ByteBuffer; /** * Writes packet of the format *

* * [header 1-4 bytes] => content size *
* [content] => 0-255/0-65535/0-16777215/0-2147483646 *
*

* Note that the maximum size for 4 bytes is a signed 32 bit int, not unsigned. *

* The packet writer will not validate outgoing packets, so make sure that * the packet content size will fit in the header. I.e. make sure that if you have * a 1 byte header, you do not send packets larger than 255 bytes, if two bytes, larger than 65535 and * so on. * * @author Christoffer Lerno */ public class RegularPacketWriter implements PacketWriter { private ByteBuffer m_header; private ByteBuffer m_content; private final boolean m_bigEndian; private final int m_headerSize; /** * Creates a regular packet writer with the given header size. * * @param headerSize the header size, 1 - 4 bytes. * @param bigEndian big endian (largest byte first) or little endian (smallest byte first) */ public RegularPacketWriter(int headerSize, boolean bigEndian) { if (headerSize < 1 || headerSize > 4) throw new IllegalArgumentException("Header must be between 1 and 4 bytes long."); m_bigEndian = bigEndian; m_headerSize = headerSize; m_header = ByteBuffer.allocate(0); m_content = ByteBuffer.allocate(0); } public ByteBuffer getBuffer() { return m_header.hasRemaining() ? m_header : m_content; } public boolean isEmpty() { return !m_header.hasRemaining() && !m_content.hasRemaining(); } public void setPacket(byte[] bytes) { if (!isEmpty()) throw new IllegalStateException("Attempted to add new packet before the previous was sent."); m_header = NIOUtils.getByteBufferFromPacketSize(m_headerSize, bytes.length, m_bigEndian); m_content = ByteBuffer.wrap(bytes); } } naga-2.1/src/main/naga/packetwriter/RawPacketWriter.java0000664000175000017500000000126611653321253022610 0ustar mbanckmbanckpackage naga.packetwriter; import naga.PacketWriter; import java.nio.ByteBuffer; /** * Writes a byte packet to the stream without doing any changes to it. *

* This is the commonly case when one wants to output text or similarly * delimited data. * * @author Christoffer Lerno */ public class RawPacketWriter implements PacketWriter { private ByteBuffer m_buffer; /** * Creates a new writer. */ public RawPacketWriter() { m_buffer = null; } public void setPacket(byte[] bytes) { m_buffer = ByteBuffer.wrap(bytes); } public ByteBuffer getBuffer() { return m_buffer; } public boolean isEmpty() { return m_buffer == null || !m_buffer.hasRemaining(); } } naga-2.1/src/main/naga/packetwriter/package-info.java0000664000175000017500000000016311653321234022050 0ustar mbanckmbanck/** * Package containing various ready-to-use {@code PacketWriter} implementations. */ package naga.packetwriter;naga-2.1/src/main/naga/PacketWriter.java0000664000175000017500000000354111653321253017430 0ustar mbanckmbanckpackage naga; import java.nio.ByteBuffer; /** * Interface for classes implementing packet writing strategies. *

* The method {@link naga.PacketWriter#setPacket(byte[])} initializes for writer for output of a new * packet. Implementing classes can assume that setPacket is never called unless * {@link #isEmpty()} * returns true. The {@link #getBuffer()} * method should return a {@link java.nio.ByteBuffer} containing * the next chunk of data to output on the socket. *

* The implementation is similar to: *

* * while (!packetWriter.isEmpty()) channel.write(packetWriter.getBuffer()); * *

* In other words, it is ok to split a single packet into several byte buffers each are handed in * turn for every call to {@link #getBuffer()}. (See {@link naga.packetwriter.RegularPacketWriter} source code for an example.) * * @author Christoffer Lerno */ public interface PacketWriter { /** * Set the next packet to write. * * @param bytes an array of bytes representing the next packet. */ void setPacket(byte[] bytes); /** * Determines if the packet writer has more data to write. *

* Classes will never invoke {@link #setPacket(byte[])} unless * isEmpty returns true. * * @return true if everything buffered in the writer is writen, false otherwise. */ boolean isEmpty(); /** * The current byte buffer to write to the socket. *

* Note that the socket does no rewinding or similar of the buffer, * the only way it interacts with the buffer is by calling * {@link java.nio.channels.SocketChannel#write(ByteBuffer)}, so the implementing * class needs to make sure that the buffer is in the right state. *

* This code will not be called unless {@link #isEmpty()} returns false. * * @return the byte buffer to send data from to the socket. */ ByteBuffer getBuffer(); } naga-2.1/src/main/naga/exception/0000775000175000017500000000000011653321234016153 5ustar mbanckmbancknaga-2.1/src/main/naga/exception/package-info.java0000664000175000017500000000007311653321234021342 0ustar mbanckmbanck/** * Exceptions used by Naga. */ package naga.exception;naga-2.1/src/main/naga/exception/ProtocolViolationException.java0000664000175000017500000000070211653321234024362 0ustar mbanckmbanckpackage naga.exception; import java.io.IOException; /** * Throw an exception due to unexpected data when reading packets. * * @author Christoffer Lerno */ public class ProtocolViolationException extends IOException { private static final long serialVersionUID = 6869467292395980590L; /** * Create a new exception. * * @param message exception message. */ public ProtocolViolationException(String message) { super(message); } } naga-2.1/src/main/naga/SocketChannelResponder.java0000664000175000017500000001671011653321253021431 0ustar mbanckmbanckpackage naga; import naga.packetreader.RawPacketReader; import naga.packetwriter.RawPacketWriter; import java.io.EOFException; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicLong; /** * @author Christoffer Lerno */ class SocketChannelResponder extends ChannelResponder implements NIOSocket { private final static byte[] CLOSE_PACKET = new byte[0]; private final static IOException CLOSE_EXCEPTION = new IOException("CLOSE"); private int m_bytesRead; private int m_bytesWritten; private int m_maxQueueSize; private long m_timeOpened; private final AtomicLong m_bytesInQueue; private ConcurrentLinkedQueue m_packetQueue; private PacketReader m_packetReader; private PacketWriter m_packetWriter; private volatile SocketObserver m_socketObserver; public SocketChannelResponder(NIOService service, SocketChannel socketChannel, InetSocketAddress address) { super(service, socketChannel, address); m_socketObserver = null; m_maxQueueSize = -1; m_timeOpened = -1; m_packetWriter = new RawPacketWriter(); m_packetReader = new RawPacketReader(); m_bytesInQueue = new AtomicLong(0L); m_packetQueue = new ConcurrentLinkedQueue(); } void keyInitialized() { if (!isConnected()) { addInterest(SelectionKey.OP_CONNECT); } } public void closeAfterWrite() { // Add a null packet signaling close. m_packetQueue.offer(CLOSE_PACKET); // Make sure the interests are set. addInterest(SelectionKey.OP_WRITE); } public boolean write(byte[] packet) { if (packet.length == 0) return true; long currentQueueSize = m_bytesInQueue.addAndGet(packet.length); if (m_maxQueueSize > 0 && currentQueueSize > m_maxQueueSize) { m_bytesInQueue.addAndGet(-packet.length); return false; } // Add the packet. m_packetQueue.offer(packet); addInterest(SelectionKey.OP_WRITE); return true; } public boolean isConnected() { return getChannel().isConnected(); } public void socketReadyForRead() { if (!isOpen()) return; try { if (!isConnected()) throw new IOException("Channel not connected."); int read; while ((read = getChannel().read(m_packetReader.getBuffer())) > 0) { m_bytesRead += read; byte[] packet; while ((packet = m_packetReader.getNextPacket()) != null) { m_socketObserver.packetReceived(this, packet); } } if (read < 0) { throw new EOFException("Buffer read -1"); } } catch (Exception e) { close(e); } } private void fillCurrentOutgoingBuffer() throws IOException { if (m_packetWriter.isEmpty()) { // Retrieve next packet from the queue. byte[] nextPacket = m_packetQueue.poll(); if (nextPacket == CLOSE_PACKET) throw CLOSE_EXCEPTION; if (nextPacket != null) { m_packetWriter.setPacket(nextPacket); // Remove the space reserved in the queue. m_bytesInQueue.addAndGet(-nextPacket.length); } } } public void socketReadyForWrite() { try { deleteInterest(SelectionKey.OP_WRITE); if (!isOpen()) return; fillCurrentOutgoingBuffer(); // Return if there is nothing in the buffer to send. if (m_packetWriter.isEmpty()) return; while (!m_packetWriter.isEmpty()) { int written = getChannel().write(m_packetWriter.getBuffer()); m_bytesWritten += written; if (written == 0) { // Change the interest ops in case we still have things to write. addInterest(SelectionKey.OP_WRITE); return; } if (m_packetWriter.isEmpty()) { fillCurrentOutgoingBuffer(); } } } catch (Exception e) { // Do not close with an exception if this was triggered by // "close after write". close(e == CLOSE_EXCEPTION ? null : e); } } public void socketReadyForConnect() { try { if (!isOpen()) return; if (getChannel().finishConnect()) { deleteInterest(SelectionKey.OP_CONNECT); m_timeOpened = System.currentTimeMillis(); notifyObserverOfConnect(); } } catch (Exception e) { close(e); } } public void notifyWasCancelled() { close(); } public Socket getSocket() { return getChannel().socket(); } public long getBytesRead() { return m_bytesRead; } public long getBytesWritten() { return m_bytesWritten; } public long getTimeOpen() { return m_timeOpened > 0 ? System.currentTimeMillis() - m_timeOpened : -1; } public long getWriteQueueSize() { return m_bytesInQueue.get(); } public String toString() { try { return getSocket().toString(); } catch (Exception e) { return "Closed NIO Socket"; } } /** * @return the current maximum queue size. */ public int getMaxQueueSize() { return m_maxQueueSize; } /** * Sets the maximum number of bytes allowed in the queue for this socket. If this * number is less than 1, the queue is unbounded. * * @param maxQueueSize the new max queue size. A value less than 1 is an unbounded queue. */ public void setMaxQueueSize(int maxQueueSize) { m_maxQueueSize = maxQueueSize; } public void listen(SocketObserver socketObserver) { markObserverSet(); getNIOService().queue(new BeginListenEvent(this, socketObserver == null ? SocketObserver.NULL : socketObserver)); } /** * Notify the observer of our connect, * swallowing exceptions thrown and logging them to stderr. */ @SuppressWarnings({"CallToPrintStackTrace"}) private void notifyObserverOfConnect() { if (m_socketObserver == null) return; try { m_socketObserver.connectionOpened(this); } catch (Exception e) { // We have no way of properly logging this, which is why we log it to stderr e.printStackTrace(); } } /** * Notify the observer of our disconnect, * swallowing exceptions thrown and logging them to stderr. * * @param exception the exception causing the disconnect, or null if this was a clean close. */ @SuppressWarnings({"CallToPrintStackTrace"}) private void notifyObserverOfDisconnect(Exception exception) { if (m_socketObserver == null) return; try { m_socketObserver.connectionBroken(this, exception); } catch (Exception e) { // We have no way of properly logging this, which is why we log it to stderr e.printStackTrace(); } } public void setPacketReader(PacketReader packetReader) { m_packetReader = packetReader; } public void setPacketWriter(PacketWriter packetWriter) { m_packetWriter = packetWriter; } public SocketChannel getChannel() { return (SocketChannel) super.getChannel(); } protected void shutdown(Exception e) { m_timeOpened = -1; m_packetQueue.clear(); m_bytesInQueue.set(0); notifyObserverOfDisconnect(e); } private class BeginListenEvent implements Runnable { private final SocketObserver m_newObserver; private final SocketChannelResponder m_responder; private BeginListenEvent(SocketChannelResponder responder, SocketObserver socketObserver) { m_responder = responder; m_newObserver = socketObserver; } public void run() { m_responder.m_socketObserver = m_newObserver; if (m_responder.isConnected()) { m_responder.notifyObserverOfConnect(); } if (!m_responder.isOpen()) { m_responder.notifyObserverOfDisconnect(null); } m_responder.addInterest(SelectionKey.OP_READ); } @Override public String toString() { return "BeginListen[" + m_newObserver + "]"; } } public Socket socket() { return getChannel().socket(); } } naga-2.1/src/main/naga/packetreader/0000775000175000017500000000000011653321253016610 5ustar mbanckmbancknaga-2.1/src/main/naga/packetreader/RawPacketReader.java0000664000175000017500000000222311653321253022456 0ustar mbanckmbanckpackage naga.packetreader; import naga.PacketReader; import naga.exception.ProtocolViolationException; import java.nio.ByteBuffer; /** * This packet reader reads as many bytes as possible from the stream * and then bundles those bytes into a packet. * * @author Christoffer Lerno */ public class RawPacketReader implements PacketReader { public final static int DEFAULT_BUFFER_SIZE = 256; private final ByteBuffer m_buffer; /** * Create a new reader instance. With a given read buffer size (this is * how many bytes the packet reader will max read in a single pass). * * @param bufferSize the buffer size to use. */ public RawPacketReader(int bufferSize) { m_buffer = ByteBuffer.allocate(bufferSize); } /** * Create a new reader instance with the default buffer size. */ public RawPacketReader() { this(DEFAULT_BUFFER_SIZE); } public ByteBuffer getBuffer() { return m_buffer; } public byte[] getNextPacket() throws ProtocolViolationException { if (m_buffer.position() == 0) return null; m_buffer.flip(); byte[] packet = new byte[m_buffer.remaining()]; m_buffer.get(packet); m_buffer.clear(); return packet; } } naga-2.1/src/main/naga/packetreader/DelimiterPacketReader.java0000664000175000017500000000771511653321253023656 0ustar mbanckmbanckpackage naga.packetreader; import naga.PacketReader; import naga.exception.ProtocolViolationException; import java.nio.ByteBuffer; /** * Class to read a byte stream delimited by a byte marking the end of a packet. *

* Since packets read with delimiters may potentially grow unbounded, you can also supply * a maximum buffer size to prevent an attacker from causing an out of memory * by continously sending data without the delimiter. *

* The delimiter will never appear in the packet itself. * * @author Christoffer Lerno */ public class DelimiterPacketReader implements PacketReader { public final static int DEFAULT_READ_BUFFER_SIZE = 256; private ByteBuffer m_currentBuffer; private volatile int m_maxPacketSize; private byte[] m_buffer; private byte m_delimiter; /** * Create a new reader with the default min buffer size and unlimited max buffer size. * * @param delimiter the byte delimiter to use. */ public DelimiterPacketReader(byte delimiter) { this(delimiter, DEFAULT_READ_BUFFER_SIZE, -1); } /** * Create a new reader with the given min and max buffer size * delimited by the given byte. * * @param delimiter the byte value of the delimiter. * @param readBufferSize the size of the read buffer (i.e. how many * bytes are read in a single pass) - this only has effect on read * efficiency and memory requirements. * @param maxPacketSize the maximum number of bytes read before throwing a * ProtocolException. -1 means the packet has no size limit. * @throws IllegalArgumentException if maxPacketSize < readBufferSize or if * readBufferSize < 1. */ public DelimiterPacketReader(byte delimiter, int readBufferSize, int maxPacketSize) { if (readBufferSize < 1) throw new IllegalArgumentException("Min buffer must at least be 1 byte."); if (maxPacketSize > -1 && readBufferSize > maxPacketSize) { throw new IllegalArgumentException("Read buffer cannot be be larger than the max packet size."); } m_currentBuffer = ByteBuffer.allocate(readBufferSize); m_buffer = null; m_delimiter = delimiter; m_maxPacketSize = maxPacketSize; } /** * Get the current maximum buffer size. * * @return the current maximum size. */ public int getMaxPacketSize() { return m_maxPacketSize; } /** * Set the new maximum packet size. *

* This method is thread-safe, but will not * affect reads in progress. * * @param maxPacketSize the new maximum packet size. */ public void setMaxPacketSize(int maxPacketSize) { m_maxPacketSize = maxPacketSize; } /** * Return the currently used byte buffer. * * @return the byte buffer to use. * @throws ProtocolViolationException if the internal buffer already exceeds the maximum size. */ public ByteBuffer getBuffer() throws ProtocolViolationException { if (m_maxPacketSize > -1 && m_buffer != null && m_buffer.length > m_maxPacketSize) { throw new ProtocolViolationException("Packet size exceeds " + m_maxPacketSize); } return m_currentBuffer; } public byte[] getNextPacket() throws ProtocolViolationException { if (m_currentBuffer.position() > 0) { m_currentBuffer.flip(); int oldBufferLength = m_buffer == null ? 0 : m_buffer.length; byte[] newBuffer = new byte[oldBufferLength + m_currentBuffer.remaining()]; if (m_buffer != null) { System.arraycopy(m_buffer, 0, newBuffer, 0, m_buffer.length); } m_currentBuffer.get(newBuffer, oldBufferLength, m_currentBuffer.remaining()); m_currentBuffer.clear(); m_buffer = newBuffer; } if (m_buffer == null) return null; for (int i = 0; i < m_buffer.length; i++) { if (m_buffer[i] == m_delimiter) { byte[] packet = new byte[i]; System.arraycopy(m_buffer, 0, packet, 0, i); if (i > m_buffer.length - 2) { m_buffer = null; } else { byte[] newBuffer = new byte[m_buffer.length - i - 1]; System.arraycopy(m_buffer, i + 1, newBuffer, 0, newBuffer.length); m_buffer = newBuffer; } return packet; } } return null; } }naga-2.1/src/main/naga/packetreader/AsciiLinePacketReader.java0000664000175000017500000000244111653321253023567 0ustar mbanckmbanckpackage naga.packetreader; /** * Reads a bytestream delimited by '\n'. *

* This can be used for reading lines of ASCII characters. * * @author Christoffer Lerno */ public class AsciiLinePacketReader extends DelimiterPacketReader { /** * Creates a '\n' delimited reader with default min buffer size * and unlimited max buffer size. */ public AsciiLinePacketReader() { super((byte) '\n'); } /** * Creates a '\n' delimited reader with the given max line length * and read buffer size. *

* Exceeding the line length will throw a ProtocolViolationException. * * @param readBufferSize the size of the read buffer (i.e. how many * bytes are read in a single pass) - this only has effect on read * efficiency and memory requirements. * @param maxLineLength maximum line length. */ public AsciiLinePacketReader(int readBufferSize, int maxLineLength) { super((byte) '\n', readBufferSize, maxLineLength); } /** * Creates a '\n' delimited reader with the given max line length * and default read buffer size. *

* Exceeding the line length will throw a ProtocolViolationException. * * @param maxLineLength maximum line length. */ public AsciiLinePacketReader(int maxLineLength) { super((byte) '\n', DEFAULT_READ_BUFFER_SIZE, maxLineLength); } } naga-2.1/src/main/naga/packetreader/ZeroDelimitedPacketReader.java0000664000175000017500000000237411653321253024474 0ustar mbanckmbanckpackage naga.packetreader; /** * Reads a bytestream delimited by 0. * * @author Christoffer Lerno */ public class ZeroDelimitedPacketReader extends DelimiterPacketReader { /** * Creates zero delimited reader with a default read buffer * and unlimited max packet size. */ public ZeroDelimitedPacketReader() { super((byte) 0); } /** * Creates a zero delimited reader with the given max packet size * and read buffer size. *

* Exceeding the packet size will throw a ProtocolViolationException. * * @param readBufferSize the size of the read buffer (i.e. how many * bytes are read in a single pass) - this only has effect on read * efficiency and memory requirements. * @param maxPacketSize the maximum packet size to accept. */ public ZeroDelimitedPacketReader(int readBufferSize, int maxPacketSize) { super((byte)0, readBufferSize, maxPacketSize); } /** * Creates a zero delimited reader with the given max packet size * and the default read buffer size. *

* Exceeding the packet size will throw a ProtocolViolationException. * * @param maxPacketSize the maximum packet size to accept. */ public ZeroDelimitedPacketReader(int maxPacketSize) { super((byte) 0, DEFAULT_READ_BUFFER_SIZE, maxPacketSize); } } naga-2.1/src/main/naga/packetreader/RegularPacketReader.java0000664000175000017500000000544311653321253023335 0ustar mbanckmbanckpackage naga.packetreader; import naga.NIOUtils; import naga.PacketReader; import naga.exception.ProtocolViolationException; import java.nio.ByteBuffer; /** * Reads packet of the format *

* * [header 1-4 bytes] => content size *
* [content] => 0-255/0-65535/0-16777215/0-2147483646 *
*

* Note that the maximum size for 4 bytes is a signed 32 bit int, not unsigned. * * @author Christoffer Lerno */ public class RegularPacketReader implements PacketReader { private final boolean m_bigEndian; private ByteBuffer m_header; private ByteBuffer m_content; private int m_contentSize = -1; /** * Creates a regular packet reader with the given header size. * * @param headerSize the header size, 1 - 4 bytes. * @param bigEndian big endian (largest byte first) or little endian (smallest byte first) */ public RegularPacketReader(int headerSize, boolean bigEndian) { if (headerSize < 1 || headerSize > 4) throw new IllegalArgumentException("Header must be between 1 and 4 bytes long."); m_bigEndian = bigEndian; m_header = ByteBuffer.allocate(headerSize); m_contentSize = -1; m_content = null; } /** * Return the next buffer to use. * * @return the next buffer to use. * @throws ProtocolViolationException if the header was read and the size of the content is * larger or equal to Integer.MAX_VALUE. */ public ByteBuffer getBuffer() throws ProtocolViolationException { if (m_header.hasRemaining()) return m_header; prepareContentBuffer(); return m_content; } /** * Tries to read and parse the header if possible. *

* Makes sure that the content buffer is initialized if the header has finished reading. * * @throws ProtocolViolationException if the header indicates that the size of the * content is equal to or larger than Integer.MAX_VALUE. */ private void prepareContentBuffer() throws ProtocolViolationException { if (m_contentSize < 0 && !m_header.hasRemaining()) { m_contentSize = NIOUtils.getPacketSizeFromByteBuffer(m_header, m_bigEndian); if (m_contentSize < 0 || m_contentSize >= Integer.MAX_VALUE) { throw new ProtocolViolationException("Content size out of range, was: " + m_contentSize); } m_content = ByteBuffer.allocate(m_contentSize); } } /** * Return the next packet or null if no complete packet can be constructed. * * @return the next packet available or null if none is available. * @throws ProtocolViolationException if the size of the packet is larger or equal to Integer.MAX_VALUE. */ public byte[] getNextPacket() throws ProtocolViolationException { prepareContentBuffer(); if (m_contentSize < 0 || m_content.hasRemaining()) { return null; } byte[] content = m_content.array(); m_content = null; m_header.rewind(); m_contentSize = -1; return content; } } naga-2.1/src/main/naga/packetreader/package-info.java0000664000175000017500000000016311653321234021776 0ustar mbanckmbanck/** * Package containing various ready-to-use {@code PacketReader} implementations. */ package naga.packetreader;naga-2.1/src/main/naga/eventmachine/0000775000175000017500000000000011653321253016624 5ustar mbanckmbancknaga-2.1/src/main/naga/eventmachine/DelayedAction.java0000664000175000017500000000373611653321234022204 0ustar mbanckmbanckpackage naga.eventmachine; import java.util.Date; import java.util.concurrent.atomic.AtomicLong; /** * Holds a delayed runnable for the event service. *

* This class implements the DelayedEvent interface and offers * offering the ability to cancel an event before it is executed. * * @author Christoffer Lerno */ class DelayedAction implements Comparable, DelayedEvent { private final static AtomicLong s_nextId = new AtomicLong(0L); private volatile Runnable m_call; private final long m_time; private final long m_id; /** * Creates a new delayed action. * * @param call the Runnable to call at a later point. * @param time the time when the call should execute. */ public DelayedAction(Runnable call, long time) { m_call = call; m_time = time; m_id = s_nextId.getAndIncrement(); } /** * Cancels this delayed action. */ public void cancel() { m_call = null; } void run() { // First extract the runnable and put it in a local // variable since it is possible that m_call is // changed while this method is running. Runnable call = m_call; // If call != null i.e. the action is not cancelled, // execute the encapsulated runnable. if (call != null) call.run(); } /** * Compares one delayed action to another. *

* Comparison is first done by execution time, then on id (i.e. creation order). * * @param o the other delayed action. * @return -1, 0, 1 depending on where this action should be compared to the other action. */ @SuppressWarnings({"AccessingNonPublicFieldOfAnotherObject"}) public int compareTo(DelayedAction o) { if (m_time < o.m_time) return -1; if (m_time > o.m_time) return 1; if (m_id < o.m_id) return -1; return m_id > o.m_id ? 1 : 0; } public Runnable getCall() { return m_call; } public long getTime() { return m_time; } @Override public String toString() { return "DelayedAction @ " + new Date(m_time) + " [" + (m_call == null ? "Cancelled" : m_call) + "]"; } } naga-2.1/src/main/naga/eventmachine/EventMachine.java0000664000175000017500000001540711653321253022044 0ustar mbanckmbanckpackage naga.eventmachine; import naga.NIOService; import java.io.IOException; import java.util.Date; import java.util.PriorityQueue; import java.util.Queue; import java.util.concurrent.PriorityBlockingQueue; /** * EventMachine is a simple event service for driving asynchronous and delayed tasks * together with the a Naga NIOService. *

* Creating and starting an event machine: *

 * EventMachine em = new EventMachine();
 * // Start our event machine thread:
 * em.start();
 * 
* Delayed execution: *
 * em.executeLater(new Runnable() {
 *   public void run()
 *   {
 *      // Code here will execute after 1 second on the nio thread.
 *   }
 * }, 1000);
 * 
* Asynchronous execution, i.e. putting a task from another thread * to be executed on the EventMachine thread. *
 * em.asyncExecute(new Runnable() {
 *   public void run()
 *   {
 *      // Code here will be executed on the nio thread.
 *   }
 * });
 * 
* It is possible to cancel scheduled tasks: *
 * // Schedule an event
 * DelayedEvent event = em.executeLater(new Runnable()
 *   public void run()
 *   {
 *      // Code to run in 1 minute.
 *   }
 * }, 60000);
 * // Cancel the event before it is executed.
 * event.cancel();
 * 
* * @author Christoffer Lerno */ public class EventMachine { private final NIOService m_service; private final Queue m_queue; private Thread m_runThread; private volatile ExceptionObserver m_observer; /** * Creates a new EventMachine with an embedded NIOService. * * @throws IOException if we fail to set up the internal NIOService. */ public EventMachine() throws IOException { m_service = new NIOService(); m_queue = new PriorityBlockingQueue(); m_observer = ExceptionObserver.DEFAULT; m_runThread = null; } /** * Execute a runnable on the Event/NIO thread. *

* This method is thread-safe. * * @param runnable the runnable to execute on the server thread as soon as possible, */ public void asyncExecute(Runnable runnable) { executeLater(runnable, 0); } /** * Execute a runnable on the Event/NIO thread after a delay. *

* This is the primary way to execute delayed events, typically time-outs and similar * behaviour. *

* This method is thread-safe. * * @param runnable the runnable to execute after the given delay. * @param msDelay the delay until executing this runnable. * @return the delayed event created to execute later. This can be used * to cancel the event. */ public DelayedEvent executeLater(Runnable runnable, long msDelay) { return queueAction(runnable, msDelay + System.currentTimeMillis()); } /** * Creates and queuest a delayed action for execution at a certain time. * * @param runnable the runnable to execute at the given time. * @param time the time date when this runnable should execute. * @return the delayed action created and queued. */ private DelayedAction queueAction(Runnable runnable, long time) { DelayedAction action = new DelayedAction(runnable, time); m_queue.add(action); m_service.wakeup(); return action; } /** * Execute a runnable on the Event/NIO thread after at a certain time. *

* This is the primary way to execute scheduled events. *

* This method is thread-safe. * * @param runnable the runnable to execute at the given time. * @param date the time date when this runnable should execute. * @return the delayed event created to execute later. This can be used * to cancel the event. */ public DelayedEvent executeAt(Runnable runnable, Date date) { return queueAction(runnable, date.getTime()); } /** * Sets the ExceptionObserver for this service. *

* The observer will receive all exceptions thrown by the underlying NIOService * and by queued events. *

* This method is thread-safe. * * @param observer the observer to use, may not be null. * @throws NullPointerException if the observer is null. */ public void setObserver(ExceptionObserver observer) { if (observer == null) throw new NullPointerException(); m_observer = observer; } /** * Returns the time when the next scheduled event will execute. * * @return a long representing the date of the next event, or Long.MAX_VALUE if * no event is scheduled. */ public long timeOfNextEvent() { DelayedAction action = m_queue.peek(); return action == null ? Long.MAX_VALUE : action.getTime(); } /** * Causes the event machine to start running on a separate thread together with the * NIOService. *

* Note that the NIOService should not be called (using {@link naga.NIOService#selectNonBlocking()} and related * functions) on another thread if the EventMachine is used. */ public synchronized void start() { if (m_runThread != null) throw new IllegalStateException("Service already running."); m_runThread = new Thread() { @Override public void run() { while (m_runThread == this) { try { select(); } catch (Throwable e) { m_observer.notifyExceptionThrown(e); } } } }; m_runThread.start(); } /** * Stops the event machine thread. */ public synchronized void stop() { if (m_runThread == null) throw new IllegalStateException("Service is not running."); m_runThread = null; m_service.wakeup(); } /** * Run all delayed events, then run select on the NIOService. * * @throws Throwable if any exception is thrown while executing events or handling IO. */ private void select() throws Throwable { // Run queued actions to be called while (timeOfNextEvent() <= System.currentTimeMillis()) { runNextAction(); } if (timeOfNextEvent() == Long.MAX_VALUE) { m_service.selectBlocking(); } else { long delay = timeOfNextEvent() - System.currentTimeMillis(); m_service.selectBlocking(Math.max(1, delay)); } } /** * Runs the next action in the queue. */ private void runNextAction() { m_queue.poll().run(); } /** * The current ExceptionObserver used by this service. *

* Will default to ExceptionObserver.DEFAULT if no observer was set. * * @return the current ExceptionObserver for this service. */ public ExceptionObserver getObserver() { return m_observer; } /** * Returns the NIOService used by this event service. * * @return the NIOService that this event service uses. */ public NIOService getNIOService() { return m_service; } /** * Return the current event service queue. * * @return a copy of the current queue. */ public Queue getQueue() { return new PriorityQueue(m_queue); } /** * Return the current queue size. * * @return the number events in the event queue. */ public int getQueueSize() { return m_queue.size(); } } naga-2.1/src/main/naga/eventmachine/ExceptionObserver.java0000664000175000017500000000117711653321253023143 0ustar mbanckmbanckpackage naga.eventmachine; /** * Implemented by observers of event exceptions. *

* The notifyExceptionThrown will be called synchronously * by the event machine if executing an event throws * an exception. * * @author Christoffer Lerno */ public interface ExceptionObserver { ExceptionObserver DEFAULT = new ExceptionObserver() { @SuppressWarnings({"CallToPrintStackTrace"}) public void notifyExceptionThrown(Throwable e) { e.printStackTrace(); } }; /** * Notify the observer that an exception has been thrown. * * @param e the exception that was thrown. */ void notifyExceptionThrown(Throwable e); } naga-2.1/src/main/naga/eventmachine/package-info.java0000664000175000017500000000022011653321234022004 0ustar mbanckmbanck/** * An optional simple service for driving asynchronous and delayed tasks integrated with the Naga NIOService. */ package naga.eventmachine;naga-2.1/src/main/naga/eventmachine/DelayedEvent.java0000664000175000017500000000206211653321234022037 0ustar mbanckmbanckpackage naga.eventmachine; /** * A cancellable, delayed event posted to the event service. * * @author Christoffer Lerno */ public interface DelayedEvent { /** * Cancels this delayed event. *

* Note that cancelling a delayed event is *not* guaranteed to * remove the event from the queue. But it is guaranteed to * clear the reference to the Runnable associated with the event. * The method may be called multiple times with no ill effect. *

* Cancelling an event while it is executing will not prevent it * from executing. *

* This metod is thread-safe. */ void cancel(); /** * Returns the actual Runnable to be executed when this event runs. *

* This will value will be null if this event has been cancelled. * * @return the call to execute with this event runs, or null if this * event is cancelled. */ Runnable getCall(); /** * Returns the time when this event will execute. See Date#getTime(). * * @return a long representing the time when this event will occur. */ long getTime(); } naga-2.1/src/main/naga/ServerSocketObserverAdapter.java0000664000175000017500000000057111653321234022453 0ustar mbanckmbanckpackage naga; import java.io.IOException; /** * Class with null-implementations for all callbacks. * * @author Christoffer Lerno */ public class ServerSocketObserverAdapter implements ServerSocketObserver { public void acceptFailed(IOException exception) { } public void serverSocketDied(Exception e) { } public void newConnection(NIOSocket nioSocket) { } } naga-2.1/src/main/naga/ChannelResponder.java0000664000175000017500000001543211653321253020260 0ustar mbanckmbanckpackage naga; import java.net.InetSocketAddress; import java.nio.channels.CancelledKeyException; import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; /** * The base class for socket and server socket responders. *

* Handles basic functionality such as presenting IP and port, * handling of key and channel as well as closing the socket. * * @author Christoffer Lerno */ abstract class ChannelResponder implements NIOAbstractSocket { private final NIOService m_service; private final String m_ip; private final InetSocketAddress m_address; private final int m_port; private final SelectableChannel m_channel; private volatile boolean m_open; private volatile SelectionKey m_key; private volatile int m_interestOps; private boolean m_observerSet; /** * Creates a new channel responder. * * @param service the NIOService this responder belongs to. * @param channel the channel handled by this responder. * @param address the address this channel is associated with. */ protected ChannelResponder(NIOService service, SelectableChannel channel, InetSocketAddress address) { m_channel = channel; m_service = service; m_open = true; m_key = null; m_interestOps = 0; m_observerSet = false; m_address = address; m_ip = address.getAddress().getHostAddress(); m_port = address.getPort(); } public InetSocketAddress getAddress() { return m_address; } public String getIp() { return m_ip; } public int getPort() { return m_port; } /** * @return the NIOService this responder is connected to. */ protected NIOService getNIOService() { return m_service; } /** * Mark the observer for this responder as set. *

* This does not mean that the observer is really set, but instead that * a set has been queued on the NIOService thread. *

* This call prevents the observer from being set more than once. */ protected void markObserverSet() { synchronized (this) { if (m_observerSet) throw new IllegalStateException("Listener already set."); m_observerSet = true; } } /** * Called by the NIOService when the key has read interest ready. */ void socketReadyForRead() { throw new UnsupportedOperationException(getClass() + " does not support read."); } /** * Called by the NIOService when the key has write interest ready. */ void socketReadyForWrite() { throw new UnsupportedOperationException(getClass() + " does not support write."); } /** * Called by the NIOService when the key has accept interest ready. */ void socketReadyForAccept() { throw new UnsupportedOperationException(getClass() + " does not support accept."); } /** * Called by the NIOService when the key has connect interest ready. */ void socketReadyForConnect() { throw new UnsupportedOperationException(getClass() + " does not support connect."); } /** * Get the channel used by this responder. *

* This method is thread-safe. * * @return the channel used by the responder. */ protected SelectableChannel getChannel() { return m_channel; } /** * Sets the selection key of this responder. *

* This method is called on the NIOService-thread. * * @param key the selection key for this responder. * @throws IllegalStateException if the selection key already is set. */ void setKey(SelectionKey key) { if (m_key != null) throw new IllegalStateException("Tried to set selection key twice"); m_key = key; if (!isOpen()) { // If we closed this before receiving the key, we should cancel the key. NIOUtils.cancelKeySilently(m_key); return; } keyInitialized(); synchronizeKeyInterestOps(); } /** * Returns the selection key or null if the key is not set. *

* This method is thread-safe. * * @return the selection key or nul if the responder is not yet registered. */ protected SelectionKey getKey() { return m_key; } /** * This method is called after the SelectionKey is set. *

* This method is called on the NIOService-thread. */ abstract void keyInitialized(); public boolean isOpen() { return m_open; } public void close() { close(null); } /** * Asynchronously closes the channel with a given exception as a cause. * * @param exception the exception that caused the close, or null if this was * closed by the user of this responder. */ protected void close(Exception exception) { if (isOpen()) { getNIOService().queue(new CloseEvent(this, exception)); } } /** * Synchronizes the desired interest ops with the key interests ops, * if the key is initialized. *

* This method is thread-safe. */ private void synchronizeKeyInterestOps() { if (m_key != null) { try { int oldOps = m_key.interestOps(); if ((m_interestOps & SelectionKey.OP_CONNECT) != 0) { m_key.interestOps(SelectionKey.OP_CONNECT); } else { m_key.interestOps(m_interestOps); } if (m_key.interestOps() != oldOps) { m_service.wakeup(); } } catch (CancelledKeyException e) { // Ignore these. } } } /** * Deleted an interest on a key. *

* This method is thread-safe. * * @param interest the interest to delete. */ protected void deleteInterest(int interest) { m_interestOps = m_interestOps & ~interest; synchronizeKeyInterestOps(); } /** * Add an interest to the key, or change the currently pending interest. *

* This method is thread-safe. * * @param interest the interest to add. */ protected void addInterest(int interest) { m_interestOps |= interest; synchronizeKeyInterestOps(); } /** * Returns a string on the format [ip]:[port] * This method is thread-safe. * * @return a string on the format IP:port. */ @Override public String toString() { return m_ip + ":" + m_port; } /** * Called once when the responder goes from state open to closed. *

* This method is called on the NIOService thread. * * @param e the exception that caused the shutdown, may be null. */ protected abstract void shutdown(Exception e); /** * A close event sent when close() is called. *

* When run this event sets m_open to false and silently cancels * the key and closes the channel. *

* Finally the responder's shutdown method is called. */ private static class CloseEvent implements Runnable { private final ChannelResponder m_responder; private final Exception m_exception; private CloseEvent(ChannelResponder responder, Exception e) { m_responder = responder; m_exception = e; } public void run() { if (m_responder.isOpen()) { m_responder.m_open = false; NIOUtils.closeKeyAndChannelSilently(m_responder.getKey(), m_responder.getChannel()); m_responder.shutdown(m_exception); } } } } naga-2.1/src/main/naga/SocketObserver.java0000664000175000017500000000445311653321253017767 0ustar mbanckmbanckpackage naga; /** * This interface contains the callbacks used by a NIOSocket * to inform its observer of events. *

* All callbacks will be run on the NIOService-thread, * so callbacks should try to return as quickly as possible * since the callback blocks communication on all sockets * of the service. * * @author Christoffer Lerno */ public interface SocketObserver { /** A null object used as the default observer */ SocketObserver NULL = new SocketObserverAdapter(); /** * Called by the NIOService on the NIO thread when a connection completes on a socket. *

* Note: Since this is a direct callback on the NIO thread, this method will suspend IO on * all other connections until the method returns. It is therefore strongly recommended * that the implementation of this method returns as quickly as possible to avoid blocking IO. * * @param nioSocket the socket that completed its connect. */ void connectionOpened(NIOSocket nioSocket); /** * Called by the NIOService on the NIO thread when a connection is disconnected. *

* This may be sent even if a connectionOpened(NIOSocket) * wasn't ever called, since the connect itself may * fail. *

* Note: Since this is a direct callback on the NIO thread, this method will suspend IO on * all other connections until the method returns. It is therefore strongly recommended * that the implementation of this method returns as quickly as possible to avoid blocking IO. * * @param nioSocket the socket that was disconnected. * @param exception the exception that caused the connection to break, may be null. */ void connectionBroken(NIOSocket nioSocket, Exception exception); /** * Called by the NIOService on the NIO thread when a packet is finished reading. * The byte array contains the packet as parsed by the current PacketReader. *

* Note: Since this is a direct callback on the NIO thread, this method will suspend IO on * all other connections until the method returns. It is therefore strongly recommended * that the implementation of this method returns as quickly as possible to avoid blocking IO. * * @param socket the socket we received a packet on. * @param packet the packet we received. */ void packetReceived(NIOSocket socket, byte[] packet); } naga-2.1/src/main/naga/SocketObserverAdapter.java0000664000175000017500000000061211653321253021261 0ustar mbanckmbanckpackage naga; /** * Class with null-implementation of all SocketObserver callbacks. * * @author Christoffer Lerno */ public class SocketObserverAdapter implements SocketObserver { public void connectionBroken(NIOSocket nioSocket, Exception exception) { } public void packetReceived(NIOSocket socket, byte[] packet) { } public void connectionOpened(NIOSocket nioSocket) { } } naga-2.1/src/main/naga/ServerSocketObserver.java0000664000175000017500000000443011653321234021150 0ustar mbanckmbanckpackage naga; import java.io.IOException; /** * Implemented by an observer to a server socket. *

* A server socket observer is responsible for handling * incoming connections by implementing a callback * to properly assign a SocketObserver to the new connections. *

* All callbacks will be run on the NIOService-thread, * so callbacks should try to return as quickly as possible * since the callback blocks communication on all sockets * of the service. * * @author Christoffer Lerno */ public interface ServerSocketObserver { /** * Called by the NIOService on the NIO thread when an accept fails on the socket. *

* Note: Since this is a direct callback on the NIO thread, this method will suspend IO on * all other connections until the method returns. It is therefore strongly recommended * that the implementation of this method returns as quickly as possible to avoid blocking IO. * * @param exception the reason for the failure, never null. */ void acceptFailed(IOException exception); /** * Called by the NIOService on the NIO thread when the server socket is closed. *

* Note: Since this is a direct callback on the NIO thread, this method will suspend IO on * all other connections until the method returns. It is therefore strongly recommended * that the implementation of this method returns as quickly as possible to avoid blocking IO. * * @param exception the exception that caused the close, or null if this was * caused by an explicit close() on the NIOServerSocket. */ void serverSocketDied(Exception exception); /** * Called by the NIOService on the NIO thread when a new connection has been accepted by the socket. *

* The normal behaviour would be for the observer to assign a reader and a writer to the socket, * and then finally invoke NIOSocket#listen(SocketObserver) on the socket. *

* Note: Since this is a direct callback on the NIO thread, this method will suspend IO on * all other connections until the method returns. It is therefore strongly recommended * that the implementation of this method returns as quickly as possible to avoid blocking IO. * * @param nioSocket the socket that was accepted. */ void newConnection(NIOSocket nioSocket); } naga-2.1/src/main/naga/NIOSocket.java0000664000175000017500000000767611653321253016637 0ustar mbanckmbanckpackage naga; import java.net.Socket; /** * Interface for the NIOSocket, which is * an asynchronous facade to an underlying Socket. *

* The NIOSocket executes callbacks to a Socket observer * to react to incoming packets and other events. * * @author Christoffer Lerno */ public interface NIOSocket extends NIOAbstractSocket { /** * Write a packet of bytes asynchronously on this socket. *

* The bytes will be sent to the PacketWriter belonging to this * socket for dispatch. However, if the queue is full (i.e. the new * queue size would exceed getMaxQueueSize()), * the packet is discarded and the method returns false. *

* This method is thread-safe. * * @param packet the packet to send. * @return true if the packet was queued, false if the queue limit * was reached and the packet was thrown away. */ boolean write(byte[] packet); /** * Return the total number of bytes read on this socket since * it was opened. *

* This method is thread-safe. * * @return the total number of bytes read on this socket. */ long getBytesRead(); /** * Return the total number of bytes written on this socket * since it was opened. *

* This method is thread-safe. * * @return the total number of bytes written on this socket. */ long getBytesWritten(); /** * Return the time this socket has been open. *

* This method is thread-safe. * * @return the time this socket has been open in ms. */ long getTimeOpen(); /** * This method returns the number of bytes queued for dispatch. * This size is compared against the maximum queue size to determine if additional packets * will be refused or not. *

* However, this number does not include the packet currently waiting to be written. *

* This method is thread-safe. * * @return the total size of the packets waiting to be dispatched, exluding the currently * dispatching packet. */ long getWriteQueueSize(); /** * The current maximum queue size in bytes. *

* This method is thread-safe. * * @return the current maximum queue size. */ int getMaxQueueSize(); /** * Sets the maximum number of bytes allowed in the queue for this socket. If this * number is less than 1, the queue is unbounded. *

* This method is thread-safe. * * @param maxQueueSize the new max queue size. A value less than 1 is an unbounded queue. */ void setMaxQueueSize(int maxQueueSize); /** * Sets the packet reader for this socket. * * @param packetReader the packet reader to interpret the incoming byte stream. */ void setPacketReader(PacketReader packetReader); /** * Sets the packet writer for this socket. * * @param packetWriter the packet writer to interpret the incoming byte stream. */ void setPacketWriter(PacketWriter packetWriter); /** * Opens the socket for reads. *

* The socket observer will receive connects, disconnects and packets. * If the socket was opened or disconnected before the observer was attached, * the socket observer will still receive those callbacks. *

* This method is thread-safe, but may only be called once. * * @param socketObserver the observer to receive packets and be notified of connects/disconnects. * @throws IllegalStateException if the method already has been called. */ void listen(SocketObserver socketObserver); /** * Causes the socket to close after writing the current entries in the queue * (consequent entries will be thrown away). *

* Also see close() if you want to immediately close the socket. *

* This method is thread-safe. */ void closeAfterWrite(); /** * Allows access to the underlying socket. *

* Note that accessing streams or closing the socket will * put this NIOSocket in an undefined state * * @return return the underlying socket. */ Socket socket(); } naga-2.1/src/main/naga/examples/0000775000175000017500000000000011653321253015774 5ustar mbanckmbancknaga-2.1/src/main/naga/examples/Rot13Server.java0000664000175000017500000000532411653321253020742 0ustar mbanckmbanckpackage naga.examples; import naga.*; import naga.packetreader.AsciiLinePacketReader; import java.io.IOException; /** * Creates a Rot13Server that takes a line of text and returns the Rot13 version of the * text. *

* Run using {@code java naga.examples.Rot13Server [port]}. * * @author Christoffer Lerno */ public class Rot13Server { Rot13Server() {} /** * Runs the rot13 server. * * @param args command line arguments, assumed to be a 1 length string containing a port. */ public static void main(String... args) { int port = Integer.parseInt(args[0]); try { // Open the service. NIOService service = new NIOService(); NIOServerSocket socket = service.openServerSocket(port); final byte[] welcomeMessage = ("Welcome to the ROT13 server at " + socket.toString() + "!\n").getBytes(); final byte[] newLine = "\n".getBytes(); // Start listening to the server socket. socket.listen(new ServerSocketObserverAdapter() { public void newConnection(NIOSocket nioSocket) { System.out.println("Client " + nioSocket.getIp() + " connected."); nioSocket.write(welcomeMessage); nioSocket.setPacketReader(new AsciiLinePacketReader()); nioSocket.listen(new SocketObserverAdapter() { public void packetReceived(NIOSocket socket, byte[] packet) { // Convert the packet to a string and trim non-printables. String line = new String(packet).trim(); // Disconnect on "+++" if (line.equals("+++")) { socket.write("Thank you and good bye.\n".getBytes()); socket.closeAfterWrite(); return; } // Build our ROT13 version of the incoming string. StringBuilder builder = new StringBuilder(line); for (int i = 0; i < builder.length(); i++) { char c = builder.charAt(i); if (c >= 'a' && c <= 'z') { builder.setCharAt(i, (char) (((c - 'a') + 13) % 26 + 'a')); } if (c >= 'A' && c <= 'Z') { builder.setCharAt(i, (char) (((c - 'A') + 13) % 26 + 'A')); } } // Write the result and append a new line. socket.write(builder.toString().getBytes()); socket.write(newLine); } public void connectionBroken(NIOSocket nioSocket, Exception exception) { System.out.println("Client " + nioSocket.getIp() + " disconnected."); if (exception != null) { exception.printStackTrace(); } } }); } }); // Allow all connections. socket.setConnectionAcceptor(ConnectionAcceptor.ALLOW); // Read IO until process exits. while (true) { service.selectBlocking(); } } catch (IOException e) { e.printStackTrace(); } } } naga-2.1/src/main/naga/examples/EchoServer.java0000664000175000017500000000244011653321253020704 0ustar mbanckmbanckpackage naga.examples; import naga.*; import java.io.IOException; /** * Creates a very simple echo server. *

* Run using {@code java naga.examples.EchoServer [port]} * * @author Christoffer Lerno */ public class EchoServer { EchoServer() {} /** * Runs the echo server. * * @param args command line arguments, assumed to be a 1 length string containing a port. */ public static void main(String... args) { int port = Integer.parseInt(args[0]); try { NIOService service = new NIOService(); NIOServerSocket socket = service.openServerSocket(port); socket.listen(new ServerSocketObserverAdapter() { public void newConnection(NIOSocket nioSocket) { System.out.println("Client " + nioSocket.getIp() + " connected."); nioSocket.listen(new SocketObserverAdapter() { public void packetReceived(NIOSocket socket, byte[] packet) { socket.write(packet); } public void connectionBroken(NIOSocket nioSocket, Exception exception) { System.out.println("Client " + nioSocket.getIp() + " disconnected."); } }); } }); socket.setConnectionAcceptor(ConnectionAcceptor.ALLOW); while (true) { service.selectBlocking(); } } catch (IOException e) { e.printStackTrace(); } } } naga-2.1/src/main/naga/examples/ValidationServer.java0000664000175000017500000000600311653321234022116 0ustar mbanckmbanckpackage naga.examples; import naga.*; import naga.packetreader.RegularPacketReader; import naga.packetwriter.RegularPacketWriter; import java.io.*; import java.util.HashMap; import java.util.Map; /** * An example validation server to validate logins. * * @author Christoffer Lerno */ public class ValidationServer { ValidationServer() { } public static void main(String... args) { int port = Integer.parseInt(args[0]); // Create a map with users and passwords. final Map passwords = new HashMap(); passwords.put("Admin", "password"); passwords.put("Aaron", "AAAAAAAA"); passwords.put("Bob", "QWERTY"); passwords.put("Lisa", "secret"); try { NIOService service = new NIOService(); NIOServerSocket socket = service.openServerSocket(port); socket.listen(new ServerSocketObserverAdapter() { public void newConnection(NIOSocket nioSocket) { System.out.println("Received connection: " + nioSocket); // Set a 1 byte header regular reader. nioSocket.setPacketReader(new RegularPacketReader(1, true)); // Set a 1 byte header regular writer. nioSocket.setPacketWriter(new RegularPacketWriter(1, true)); // Listen on the connection. nioSocket.listen(new SocketObserverAdapter() { public void packetReceived(NIOSocket socket, byte[] packet) { // We received a packet. Should contain two encoded // UTF strings with user and password. System.out.println("Login attempt from " + socket); try { // Let us unpack the bytes by converting the bytes to a stream. DataInputStream stream = new DataInputStream(new ByteArrayInputStream(packet)); // Read the two strings. String user = stream.readUTF(); String password = stream.readUTF(); // Prepare to encode the response. ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); DataOutputStream out = new DataOutputStream(byteArrayOutputStream); if (!passwords.containsKey(user)) { System.out.println("Unknown user: " + user); out.writeUTF("NO_SUCH_USER"); } else if (!passwords.get(user).equals(password)) { out.writeUTF("INCORRECT_PASS"); System.out.println("Failed login for: " + user); } else { out.writeUTF("LOGIN_OK"); System.out.println("Successful login for: " + user); } // Create the outgoing packet. out.flush(); socket.write(byteArrayOutputStream.toByteArray()); // Close after the packet has finished writing. socket.closeAfterWrite(); } catch (IOException e) { // No error handling to speak of. socket.close(); } } }); } }); // Allow all logins. socket.setConnectionAcceptor(ConnectionAcceptor.ALLOW); // Keep reading IO forever. while (true) { service.selectBlocking(); } } catch (IOException e) { } } } naga-2.1/src/main/naga/examples/ValidationClient.java0000664000175000017500000000503111653321253022067 0ustar mbanckmbanckpackage naga.examples; import naga.NIOService; import naga.NIOSocket; import naga.SocketObserver; import naga.packetreader.RegularPacketReader; import naga.packetwriter.RegularPacketWriter; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; /** * A client for exercising the validation server. *

* Use with {@code java naga.examples.ValidationClient [host] [port] [account] [password]}. * @author Christoffer Lerno */ public class ValidationClient { ValidationClient() {} /** * Make a login request to the server. * * @param args assumed to be 4 strings representing host, port, account and password. */ public static void main(String... args) { try { // Parse arguments. String host = args[0]; int port = Integer.parseInt(args[1]); String account = args[2]; String password = args[3]; // Prepare the login packet, packing two UTF strings together // using a data output stream. ByteArrayOutputStream stream = new ByteArrayOutputStream(); DataOutputStream dataStream = new DataOutputStream(stream); dataStream.writeUTF(account); dataStream.writeUTF(password); dataStream.flush(); final byte[] content = stream.toByteArray(); dataStream.close(); // Start up the service. NIOService service = new NIOService(); // Open our socket. NIOSocket socket = service.openSocket(host, port); // Use regular 1 byte header reader/writer socket.setPacketReader(new RegularPacketReader(1, true)); socket.setPacketWriter(new RegularPacketWriter(1, true)); // Start listening to the socket. socket.listen(new SocketObserver() { public void connectionOpened(NIOSocket nioSocket) { System.out.println("Sending login..."); nioSocket.write(content); } public void packetReceived(NIOSocket socket, byte[] packet) { try { // Read the UTF-reply and print it. String reply = new DataInputStream(new ByteArrayInputStream(packet)).readUTF(); System.out.println("Reply was: " + reply); // Exit the program. System.exit(0); } catch (Exception e) { e.printStackTrace(); } } public void connectionBroken(NIOSocket nioSocket, Exception exception) { System.out.println("Connection failed."); // Exit the program. System.exit(-1); } }); // Read IO until process exits. while (true) { service.selectBlocking(); } } catch (Exception e) { e.printStackTrace(); } } } naga-2.1/src/main/naga/examples/package-info.java0000664000175000017500000000025111653321234021160 0ustar mbanckmbanck/** * Various examples on how to use Naga. *

* These classes are only available in the debug version of the naga deliverable. */ package naga.examples;naga-2.1/src/main/naga/NIOAbstractSocket.java0000664000175000017500000000210611653321253020302 0ustar mbanckmbanckpackage naga; import java.net.InetSocketAddress; /** * An interface describing methods common to both NIOSocket and NIOServerSocket. * * @author Christoffer Lerno */ public interface NIOAbstractSocket { /** * Closes this socket (the actual disconnect will occur on the NIOService thread) *

* This method is thread-safe. */ void close(); /** * Returns the InetSocketAddress for this socket. *

* This method is thread-safe. * * @return the InetSocketAddress this socket connects to. */ InetSocketAddress getAddress(); /** * Returns the current state of this socket. *

* This method is thread-safe. * * @return true if the connection is socket is open, false if closed. */ boolean isOpen(); /** * Reports the IP used by this socket. *

* This method is thread-safe. * * @return the IP of this socket. */ String getIp(); /** * Returns the port in use by this socket. *

* This method is thread-safe. * * @return the port of this socket. */ int getPort(); } naga-2.1/src/main/naga/NIOServerSocket.java0000664000175000017500000000512111653321234020004 0ustar mbanckmbanckpackage naga; import java.net.ServerSocket; /** * Interface for the NIOServerSocket, which is * an asynchronous facade to an underlying ServerSocket. *

* The NIOServerSocket executes callbacks to a ServerSocket observer * to react to new connections and other events. * * @author Christoffer Lerno */ public interface NIOServerSocket extends NIOAbstractSocket { /** * Returns the total number of connections made on this socket since * it opened. *

* This method is thread-safe. * * @return the total number of connections made on this server socket. */ long getTotalConnections(); /** * Returns the total number of refused connections on this socket since * it opened. *

* This method is thread-safe. * * @return the total number of refused connections on this server socket. */ long getTotalRefusedConnections(); /** * Returns the total number of accepted connections on this socket since * it opened. *

* This method is thread-safe. * * @return the total number of accepted connections on this server socket. */ long getTotalAcceptedConnections(); /** * Returns the total number of failed connections on this socket since * it opened. *

* This method is thread-safe. * * @return the total number of failed connections on this server socket. */ long getTotalFailedConnections(); /** * Associates a server socket observer with this server socket and * starts accepting connections. *

* This method is thread-safe, but may only be called once. * * @param observer the observer to receive callbacks from this socket. * @throws NullPointerException if the observer given is null. * @throws IllegalStateException if an observer has already been set. */ void listen(ServerSocketObserver observer); /** * Sets the connection acceptor for this server socket. *

* A connection acceptor determines if a connection should be * disconnected or not after the initial accept is done. *

* For more information, see the documentation for naga.ConnectionAcceptor. *

* This method is thread-safe. * * @param acceptor the acceptor to use, or null to default to * ConnectorAcceptor.DENY. */ void setConnectionAcceptor(ConnectionAcceptor acceptor); /** * Allows access to the underlying server socket. *

* Note calling close and similar functions on this socket * will put the NIOServerSocket in an undefined state * * @return return the underlying server socket. */ ServerSocket socket(); } naga-2.1/src/main/naga/package-info.java0000664000175000017500000000055011653321234017344 0ustar mbanckmbanck/** * The main Naga classes. *

* See {@link naga.NIOService} on how to start a new NIOService for asynchronous * socket I/O. *

* The library uses the implementations of {@link naga.NIOSocket} and {@link naga.NIOServerSocket} * as asynchronous counterparts to {@link java.net.Socket} and {@link java.net.ServerSocket}. *

*/ package naga;naga-2.1/src/main/naga/ConnectionAcceptor.java0000664000175000017500000000316011653321234020600 0ustar mbanckmbanckpackage naga; import java.net.InetSocketAddress; /** * The ConnectionAcceptor is used by the NIOServerSocket to determine * if a connection should be accepted or refused. *

* This can be used to implement black-listing of certain IP-ranges * or to limit the number of simultaneous connection. However, * in most cases it is enough to use the ConnectorAcceptor.ALLOW which * accepts all incoming connections. *

* Note that a NIOServerSocket defaults to the ConnectorAcceptor.ALLOW * acceptor when it is created. * * * @author Christoffer Lerno */ public interface ConnectionAcceptor { /** * A connection acceptor that refuses all connections. */ ConnectionAcceptor DENY = new ConnectionAcceptor() { public boolean acceptConnection(InetSocketAddress address) { return false; } }; /** * A connection acceptor that accepts all connections. */ ConnectionAcceptor ALLOW = new ConnectionAcceptor() { public boolean acceptConnection(InetSocketAddress address) { return true; } }; /** * Return true if the connection should be accepted, false otherwise. *

* Note: This callback is run on the NIOService thread. This means it will block * all other reads, writes and accepts on the service while it executes. * For this reason it is recommended that this method should return fairly quickly * (i.e. don't make reverse ip lookups or similar - potentially very slow - calls). * * * @param inetSocketAddress the adress the connection came from. * @return true to accept, false to refuse. */ boolean acceptConnection(InetSocketAddress inetSocketAddress); } naga-2.1/src/main/naga/ServerSocketChannelResponder.java0000664000175000017500000001003411653321253022611 0ustar mbanckmbanckpackage naga; import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.nio.channels.SelectionKey; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; /** * @author Christoffer Lerno */ class ServerSocketChannelResponder extends ChannelResponder implements NIOServerSocket { private long m_totalRefusedConnections; private long m_totalAcceptedConnections; private long m_totalFailedConnections; private long m_totalConnections; private volatile ConnectionAcceptor m_connectionAcceptor; private ServerSocketObserver m_observer; @SuppressWarnings({"ObjectToString"}) public ServerSocketChannelResponder(NIOService service, ServerSocketChannel channel, InetSocketAddress address) throws IOException { super(service, channel, address); m_observer = null; setConnectionAcceptor(ConnectionAcceptor.ALLOW); m_totalRefusedConnections = 0; m_totalAcceptedConnections = 0; m_totalFailedConnections = 0; m_totalConnections = 0; } public void keyInitialized() { // Do nothing, since the accept automatically will be set. } public ServerSocketChannel getChannel() { return (ServerSocketChannel) super.getChannel(); } /** * Callback to tell the object that there is at least one accept that can be done on the server socket. */ public void socketReadyForAccept() { m_totalConnections++; SocketChannel socketChannel = null; try { socketChannel = getChannel().accept(); if (socketChannel == null) { // This means there actually wasn't any connection waiting, // so tick down the number of actual total connections. m_totalConnections--; return; } InetSocketAddress address = (InetSocketAddress) socketChannel.socket().getRemoteSocketAddress(); // Is this connection acceptable? if (!m_connectionAcceptor.acceptConnection(address)) { // Connection was refused by the socket owner, so update stats and close connection m_totalRefusedConnections++; NIOUtils.closeChannelSilently(socketChannel); return; } m_observer.newConnection(getNIOService().registerSocketChannel(socketChannel, address)); m_totalAcceptedConnections++; } catch (IOException e) { // Close channel in case it opened. NIOUtils.closeChannelSilently(socketChannel); m_totalFailedConnections++; m_observer.acceptFailed(e); } } public void notifyWasCancelled() { close(); } public long getTotalRefusedConnections() { return m_totalRefusedConnections; } public long getTotalConnections() { return m_totalConnections; } public long getTotalFailedConnections() { return m_totalFailedConnections; } public long getTotalAcceptedConnections() { return m_totalAcceptedConnections; } public void setConnectionAcceptor(ConnectionAcceptor connectionAcceptor) { m_connectionAcceptor = connectionAcceptor == null ? ConnectionAcceptor.DENY : connectionAcceptor; } @SuppressWarnings({"CallToPrintStackTrace"}) private void notifyObserverSocketDied(Exception exception) { if (m_observer == null) return; try { m_observer.serverSocketDied(exception); } catch (Exception e) { e.printStackTrace(); } } public void listen(ServerSocketObserver observer) { if (observer == null) throw new NullPointerException(); markObserverSet(); getNIOService().queue(new BeginListenEvent(observer)); } private class BeginListenEvent implements Runnable { private final ServerSocketObserver m_newObserver; private BeginListenEvent(ServerSocketObserver socketObserver) { m_newObserver = socketObserver; } public void run() { m_observer = m_newObserver; if (!isOpen()) { notifyObserverSocketDied(null); return; } addInterest(SelectionKey.OP_ACCEPT); } @Override public String toString() { return "BeginListen[" + m_newObserver + "]"; } } protected void shutdown(Exception e) { notifyObserverSocketDied(e); } public ServerSocket socket() { return getChannel().socket(); } } naga-2.1/src/main/naga/NIOService.java0000664000175000017500000002710711653321253016776 0ustar mbanckmbanckpackage naga; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.channels.*; import java.util.Iterator; import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; /** * This class forms the basis of the NIO handling in Naga. *

* Common usage is to create a single instance of this service and * then run one other the select methods in a loop. *

* Use {@link naga.NIOService#openSocket(String, int)} to open a socket to a remote server, * and {@link naga.NIOService#openServerSocket(int)} to open a server socket locally. *

* Note that the NIOServerSocket by default opens in a "refuse connections" state. To * start accepting players, the socket's acceptor must first be set to accept connections. * See documentation for openServerSocket for more details. *

* Example use: *

* Using the server socket: *

 * NIOService service = new NIOService;
 * NIOServerSocket serverSocket = service.openServerSocket(1234);
 * serverSocket.setConnectionAcceptor(myAcceptor);
 * serverSocket.listen(myObserver);
 * 
* Using regular sockets: *
 * NIOService service = new NIOService;
 * NIOSocket socket = service.openSocket("www.google.com", 1234);
 * socket.listen(myObserver);
 * // Asynchronous write by default:
 * socket.write("Some message".getBytes());
 * 
* * @author Christoffer Lerno */ public class NIOService { /** The selector used by this service */ private final Selector m_selector; private final Queue m_internalEventQueue; /** * Create a new nio service. * * @throws IOException if we failed to open the underlying selector used by the * service. */ public NIOService() throws IOException { m_selector = Selector.open(); m_internalEventQueue = new ConcurrentLinkedQueue(); } /** * Run all waiting NIO requests, blocking indefinitely * until at least one request is handled. * * @throws IOException if there is an IO error waiting for requests. * @throws ClosedSelectorException if the underlying selector is closed * (in this case, NIOService#isOpen will return false) */ public synchronized void selectBlocking() throws IOException { executeQueue(); if (m_selector.select() > 0) { handleSelectedKeys(); } executeQueue(); } /** * Run all waiting NIO requests, returning immediately if * no requests are found. * * @throws IOException if there is an IO error waiting for requests. * @throws ClosedSelectorException if the underlying selector is closed. * (in this case, NIOService#isOpen will return false) */ public synchronized void selectNonBlocking() throws IOException { executeQueue(); if (m_selector.selectNow() > 0) { handleSelectedKeys(); } executeQueue(); } /** * Run all waiting NIO requests, blocking until * at least one request is found, or the method has blocked * for the time given by the timeout value, whatever comes first. * * @param timeout the maximum time to wait for requests. * @throws IllegalArgumentException If the value of the timeout argument is negative. * @throws IOException if there is an IO error waiting for requests. * @throws ClosedSelectorException if the underlying selector is closed. * (in this case, NIOService#isOpen will return false) */ public synchronized void selectBlocking(long timeout) throws IOException { executeQueue(); if (m_selector.select(timeout) > 0) { handleSelectedKeys(); } executeQueue(); } /** * Open a normal socket to the host on the given port returning * a NIOSocket. *

* This roughly corresponds to creating a regular socket using new Socket(host, port). *

* This method is thread-safe. * * @param host the host we want to connect to. * @param port the port to use for the connection. * @return a NIOSocket object for asynchronous communication. * @throws IOException if registering the new socket failed. */ public NIOSocket openSocket(String host, int port) throws IOException { return openSocket(InetAddress.getByName(host), port); } /** * Open a normal socket to the host on the given port returning * a NIOSocket. *

* This roughly corresponds to creating a regular socket using new Socket(inetAddress, port). *

* This method is thread-safe. * * @param inetAddress the address we want to connect to. * @param port the port to use for the connection. * @return a NIOSocket object for asynchronous communication. * @throws IOException if registering the new socket failed. */ public NIOSocket openSocket(InetAddress inetAddress, int port) throws IOException { SocketChannel channel = SocketChannel.open(); channel.configureBlocking(false); InetSocketAddress address = new InetSocketAddress(inetAddress, port); channel.connect(address); return registerSocketChannel(channel, address); } /** * Open a server socket on the given port. *

* This roughly corresponds to using new ServerSocket(port, backlog); *

* This method is thread-safe. * * @param port the port to open. * @param backlog the maximum connection backlog (i.e. connections pending accept) * @return a NIOServerSocket for asynchronous connection to the server socket. * @throws IOException if registering the socket fails. */ public NIOServerSocket openServerSocket(int port, int backlog) throws IOException { return openServerSocket(new InetSocketAddress(port), backlog); } /** * Open a server socket on the given port with the default connection backlog. *

* This roughly corresponds to using new ServerSocket(port); *

* This method is thread-safe. * * @param port the port to open. * @return a NIOServerSocket for asynchronous connection to the server socket. * @throws IOException if registering the socket fails. */ public NIOServerSocket openServerSocket(int port) throws IOException { return openServerSocket(port, -1); } /** * Open a server socket on the address. *

* This method is thread-safe. * * @param address the address to open. * @param backlog the maximum connection backlog (i.e. connections pending accept) * @return a NIOServerSocket for asynchronous connection to the server socket. * @throws IOException if registering the socket fails. */ public NIOServerSocket openServerSocket(InetSocketAddress address, int backlog) throws IOException { ServerSocketChannel channel = ServerSocketChannel.open(); channel.socket().setReuseAddress(true); channel.socket().bind(address, backlog); channel.configureBlocking(false); ServerSocketChannelResponder channelResponder = new ServerSocketChannelResponder(this, channel, address); queue(new RegisterChannelEvent(channelResponder)); return channelResponder; } /** * Internal method to mark a socket channel for pending registration * and create a NIOSocket wrapper around it. *

* This method is thread-safe. * * @param socketChannel the socket channel to wrap. * @param address the address for this socket. * @return the NIOSocket wrapper. * @throws IOException if configuring the channel fails, or the underlying selector is closed. */ NIOSocket registerSocketChannel(SocketChannel socketChannel, InetSocketAddress address) throws IOException { socketChannel.configureBlocking(false); SocketChannelResponder channelResponder = new SocketChannelResponder(this, socketChannel, address); queue(new RegisterChannelEvent(channelResponder)); return channelResponder; } /** * Internal method to execute events on the internal event queue. *

* This method should only ever be called from the NIOService thread. */ private void executeQueue() { Runnable event; while ((event = m_internalEventQueue.poll()) != null) { event.run(); } } /** * Internal method to handle the key set generated by the internal Selector. *

* Will simply remove each entry and handle the key. *

* Called on the NIOService thread. */ private void handleSelectedKeys() { // Loop through all selected keys and handle each key at a time. for (Iterator it = m_selector.selectedKeys().iterator(); it.hasNext();) { // Retrieve the key. SelectionKey key = it.next(); if (key.readyOps() == 0) throw new RuntimeException("Not ready!"); // Remove it from the set so that it is not read again. it.remove(); // Handle actions on this key. handleKey(key); } } /** * Internal method to handle a SelectionKey that has changed. *

* Will delegate actual actions to the associated ChannelResponder. *

* Called on the NIOService thread. * @param key the key to handle. */ private void handleKey(SelectionKey key) { ChannelResponder responder = (ChannelResponder) key.attachment(); try { if (key.isReadable()) { responder.socketReadyForRead(); } if (key.isWritable()) { responder.socketReadyForWrite(); } if (key.isAcceptable()) { responder.socketReadyForAccept(); } if (key.isConnectable()) { responder.socketReadyForConnect(); } } catch (CancelledKeyException e) { responder.close(e); // The key was cancelled and will automatically be removed next select. } } /** * Close the entire service. *

* This will disconnect all sockets associated with this service. *

* It is not possible to restart the service once closed. *

* This method is thread-safe. */ public void close() { if (!isOpen()) return; queue(new ShutdownEvent()); } /** * Determine if this service is open. *

* This method is thread-safe. * * @return true if the service is open, false otherwise. */ public boolean isOpen() { return m_selector.isOpen(); } /** * Queues an event on the NIOService queue. *

* This method is thread-safe, but should in general not be used by * other applications. * * @param event the event to run on the NIOService-thread. */ public void queue(Runnable event) { m_internalEventQueue.add(event); wakeup(); } /** * Returns a copy of the internal event queue. * * @return a copy of the internal event queue. */ public Queue getQueue() { return new LinkedList(m_internalEventQueue); } /** * Runs wakeup on the selector, causing any blocking select to be released. */ public void wakeup() { m_selector.wakeup(); } /** * A registration class to let registrations occur on the NIOService thread. */ private class RegisterChannelEvent implements Runnable { private final ChannelResponder m_channelResponder; private RegisterChannelEvent(ChannelResponder channelResponder) { m_channelResponder = channelResponder; } public void run() { try { SelectionKey key = m_channelResponder.getChannel().register(m_selector, 0); m_channelResponder.setKey(key); key.attach(m_channelResponder); } catch (Exception e) { m_channelResponder.close(e); } } @Override public String toString() { return "Register[" + m_channelResponder + "]"; } } /** * Shutdown class to let shutdown happen on the NIOService thread. */ private class ShutdownEvent implements Runnable { public void run() { if (!isOpen()) return; for (SelectionKey key : m_selector.keys()) { try { NIOUtils.cancelKeySilently(key); ((ChannelResponder) key.attachment()).close(); } catch (Exception e) { // Swallow exceptions. } } try { m_selector.close(); } catch (IOException e) { // Swallow exceptions. } } } } naga-2.1/src/main/naga/PacketReader.java0000664000175000017500000000327411653321253017361 0ustar mbanckmbanckpackage naga; import naga.exception.ProtocolViolationException; import java.nio.ByteBuffer; /** * Interface for packet reader plugins to assist a socket in reading. *

* To implement a packet reader, the reader has to offer the currently * used byte buffer whenever the NIO service calls PacketReader#getBuffer() *

* PacketReader#getNextPacket() should return a byte-array if it is possible to create * one from the data loaded into the buffer(s). *

* Note that getNextPacket() will be called repeatedly until it returns null. * * @author Christoffer Lerno */ public interface PacketReader { /** * Return the currently used byte buffer. The NIOSocket will use this byte buffer and perform * a SocketChannel.read(ByteBuffer) on it. *

* The reader is guaranteed not to have this method called more than once before a * call to PacketReader#getNextPacket() is made. * * @return the byte buffer to use. * @throws ProtocolViolationException if a protocol violation was detected when * reading preparing the buffer. */ ByteBuffer getBuffer() throws ProtocolViolationException; /** * Return the next packet constructed from the data read in the buffers. *

* This call may or may not have been proceeded by a call to getBuffer(). *

* The calling thread will call this method repeatedly until it returns null. * * @return a byte array containing the data of a packet, or null if not packet can be created * yet from the data read. * @throws ProtocolViolationException if a protocol violation was detected when * parsing the next packet. */ byte[] getNextPacket() throws ProtocolViolationException; } naga-2.1/src/main/naga/NIOUtils.java0000664000175000017500000000661311653321253016475 0ustar mbanckmbanckpackage naga; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.Channel; import java.nio.channels.SelectionKey; /** * A collection of utilites used by various classes. * * @author Christoffer Lerno */ public class NIOUtils { NIOUtils() {} /** * Silently close both a key and a channel. * * @param key the key to cancel, may be null. * @param channel the channel to close, may be null. */ public static void closeKeyAndChannelSilently(SelectionKey key, Channel channel) { closeChannelSilently(channel); cancelKeySilently(key); } /** * Creates a byte buffer with a given length with an encoded value, * in either big or little endian encoding (i.e. biggest or smallest byte first). * * @param headerSize the header size in bytes. 1-4. * @param valueToEncode the value to encode, 0 <= value < 2^(headerSize * 8) * @param bigEndian if the encoding is big endian or not. * @return a byte buffer with the number encoded. * @throws IllegalArgumentException if the value is out of range for the given header size. */ public static ByteBuffer getByteBufferFromPacketSize(int headerSize, int valueToEncode, boolean bigEndian) { if (valueToEncode < 0) throw new IllegalArgumentException("Payload size is less than 0."); // If header size is 4, we get valueToEncode >> 32, which is defined as valueToEncode >> 0 for int. // Therefore, we handle the that case separately, as any int will fit in 4 bytes. if (headerSize != 4 && valueToEncode >> (headerSize * 8) > 0) { throw new IllegalArgumentException("Payload size cannot be encoded into " + headerSize + " byte(s)."); } ByteBuffer header = ByteBuffer.allocate(headerSize); for (int i = 0; i < headerSize; i++) { int index = bigEndian ? (headerSize - 1 - i) : i; // We do not need to extend valueToEncode here, since the maximum is valueToEncode >> 24 header.put((byte) (valueToEncode >> (8 * index) & 0xFF)); } header.rewind(); return header; } /** * Converts a value in a header buffer encoded in either big or little endian * encoding. *

* Note that trying to decode a value larger than 2^31 - 2 is not supported. * * @param header the header to encode from. * @param bigEndian if the encoding is big endian or not. * @return the decoded number. */ public static int getPacketSizeFromByteBuffer(ByteBuffer header, boolean bigEndian) { long packetSize = 0; if (bigEndian) { header.rewind(); while (header.hasRemaining()) { packetSize <<= 8; packetSize += header.get() & 0xFF; } } else { header.rewind(); int shift = 0; while (header.hasRemaining()) { // We do not need to extend valueToEncode here, since the maximum is valueToEncode >> 24 packetSize += (header.get() & 0xFF) << shift; shift += 8; } } return (int) packetSize; } /** * Silently close a channel. * * @param channel the channel to close, may be null. */ public static void closeChannelSilently(Channel channel) { try { if (channel != null) { channel.close(); } } catch (IOException e) { // Do nothing } } /** * Silently cancel a key. * * @param key the key to cancel, may be null. */ public static void cancelKeySilently(SelectionKey key) { try { if (key != null) key.cancel(); } catch (Exception e) { // Do nothing } } }naga-2.1/version.properties0000664000175000017500000000016311653321253015340 0ustar mbanckmbanck#Build Number for ANT. Do not edit! #Sun Apr 25 09:31:26 CEST 2010 build.number=43 major.version=2 minor.version=1 naga-2.1/build.properties0000664000175000017500000000017011653321234014747 0ustar mbanckmbanckbuild.dir=_BUILD build.classes.dir=${build.dir}/classes main.dir=src/main docs.dir=${build.dir}/docs/api dist.dir=_DIST