package org.jgroups.tests; import org.jgroups.Global; import org.jgroups.Version; import org.jgroups.nio.Buffers; import org.jgroups.nio.MockSocketChannel; import org.jgroups.stack.IpAddress; import org.jgroups.util.ByteArrayDataInputStream; import org.jgroups.util.ByteArrayDataOutputStream; import org.jgroups.util.Util; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.io.EOFException; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.util.Arrays; /** * @author Bela Ban * @since 3.6.5 */ @Test(groups=Global.FUNCTIONAL) public class BuffersTest { protected static final Method makeSpace, spaceAvailable, nullData; protected static final Field position, limit; protected ByteBuffer buf1; protected ByteBuffer buf2; protected ByteBuffer buf3; static { try { makeSpace=Buffers.class.getDeclaredMethod("makeSpace"); makeSpace.setAccessible(true); spaceAvailable=Buffers.class.getDeclaredMethod("spaceAvailable", int.class); spaceAvailable.setAccessible(true); nullData=Buffers.class.getDeclaredMethod("nullData"); nullData.setAccessible(true); position=Util.getField(Buffers.class, "position"); position.setAccessible(true); limit=Util.getField(Buffers.class, "limit"); limit.setAccessible(true); } catch(Exception ex) { throw new RuntimeException(ex); } } @BeforeMethod protected void setup() { buf1=ByteBuffer.wrap("hello world".getBytes()); buf2=ByteBuffer.wrap(" from Bela".getBytes()); buf3=ByteBuffer.wrap("foo".getBytes()); } protected static ByteBuffer buffer() {return ByteBuffer.wrap("hello world".getBytes());} public void testCreation() { ByteBuffer b=buffer(); Buffers bufs=new Buffers(ByteBuffer.allocate(Global.INT_SIZE), b); System.out.println("bufs = " + bufs); assert bufs.remaining() == Global.INT_SIZE + b.capacity(); Buffers buf=new Buffers(8); check(buf, 0, 0, 0, 0); buf=new Buffers(ByteBuffer.allocate(Global.INT_SIZE), buf1); check(buf, 0, 2, 2, buf1.limit() + Global.INT_SIZE); } public void testRead() throws Exception { byte[] data="hello world".getBytes(); MockSocketChannel ch=new MockSocketChannel() .bytesToRead((ByteBuffer)ByteBuffer.allocate(Global.INT_SIZE + data.length).putInt(data.length).put(data).flip()); Buffers bufs=new Buffers(ByteBuffer.allocate(Global.INT_SIZE), null); ByteBuffer b=bufs.readLengthAndData(ch); System.out.println("b = " + b); assert b != null; assert Arrays.equals(data, b.array()); } public void testRead2() throws Exception { byte[] cookie={'b', 'e', 'l', 'a'}; IpAddress addr=new IpAddress(7500); ByteArrayDataOutputStream out=new ByteArrayDataOutputStream(); addr.writeTo(out); MockSocketChannel ch=new MockSocketChannel() .bytesToRead((ByteBuffer)ByteBuffer.allocate(cookie.length + Global.SHORT_SIZE + out.position()) .put(cookie).putShort(Version.version).put(out.buffer(), 0, out.position()).flip()); Buffers bufs=new Buffers(ByteBuffer.allocate(cookie.length), ByteBuffer.allocate(Global.SHORT_SIZE), ByteBuffer.allocate(out.position())); boolean rc=bufs.read(ch); assert rc; readCookieVersionAndAddress(bufs, cookie, addr); } public void testRead3() throws Exception { byte[] cookie={'b', 'e', 'l', 'a'}; IpAddress addr=new IpAddress(7500); ByteArrayDataOutputStream out=new ByteArrayDataOutputStream(); addr.writeTo(out); MockSocketChannel ch=new MockSocketChannel() .bytesToRead((ByteBuffer)ByteBuffer.allocate(cookie.length + Global.SHORT_SIZE + out.position()) .put(cookie).putShort(Version.version).put(out.buffer(), 0, out.position()).flip()); int remaining=ch.bytesToRead().remaining(); ch.bytesToRead().limit(remaining -10); Buffers bufs=new Buffers(ByteBuffer.allocate(cookie.length), ByteBuffer.allocate(Global.SHORT_SIZE), ByteBuffer.allocate(out.position())); boolean rc=bufs.read(ch); assert !rc; ch.bytesToRead().limit(remaining-2); rc=bufs.read(ch); assert !rc; ch.bytesToRead().limit(remaining); rc=bufs.read(ch); assert rc; readCookieVersionAndAddress(bufs, cookie, addr); } protected void readCookieVersionAndAddress(final Buffers bufs, final byte[] cookie, final IpAddress addr) throws Exception { // cookie ByteBuffer cookie_buf=bufs.get(0); byte[] cookie2=new byte[cookie_buf.position()]; cookie_buf.flip(); cookie_buf.get(cookie2, 0, cookie2.length); assert Arrays.equals(cookie, cookie2); // version ByteBuffer version_buf=bufs.get(1); short ver=version_buf.getShort(0); assert Version.version == ver; // address ByteBuffer addr_buf=bufs.get(2); addr_buf.flip(); ByteArrayDataInputStream in=new ByteArrayDataInputStream(addr_buf); IpAddress address=new IpAddress(); address.readFrom(in); assert addr.equals(address); } public void testPartialRead() throws Exception { byte[] tmp="hello world".getBytes(); ByteBuffer data=ByteBuffer.allocate(Global.INT_SIZE + tmp.length).putInt(tmp.length).put(tmp); data.flip().limit(2); // read only the first 2 bytes of the length MockSocketChannel ch=new MockSocketChannel().bytesToRead(data); Buffers bufs=new Buffers(ByteBuffer.allocate(Global.INT_SIZE), null); ByteBuffer rc=bufs.readLengthAndData(ch); assert rc == null; data.limit(8); // we can now read the remaining 2 bytes to complete the length, plus 4 bytes into the data rc=bufs.readLengthAndData(ch); assert rc == null; data.limit(14); // this will still not allow the read to complete rc=bufs.readLengthAndData(ch); assert rc == null; data.limit(15); // this will still not allow the read to complete rc=bufs.readLengthAndData(ch); assert rc != null; System.out.println("rc = " + rc); assert Arrays.equals(tmp, rc.array()); } public void testEof() throws Exception { byte[] data={'B', 'e', 'l', 'a'}; // -1 == EOF MockSocketChannel ch=new MockSocketChannel() .bytesToRead((ByteBuffer)ByteBuffer.allocate(Global.INT_SIZE + data.length).putInt(data.length).put(data).flip()); Buffers bufs=new Buffers(ByteBuffer.allocate(Global.INT_SIZE), null); ByteBuffer buf=bufs.readLengthAndData(ch); assert buf != null; assert buf.limit() == data.length; ch.doClose(); try { buf=bufs.readLengthAndData(ch); assert false : "read() should have thrown an EOFException"; } catch(EOFException eof) { System.out.printf("received exception as expected: %s\n", eof); } } public void testReadLength() throws Exception { byte[] tmp="hello world".getBytes(); ByteBuffer data=ByteBuffer.allocate(Global.INT_SIZE + tmp.length).putInt(tmp.length).put(tmp); data.flip().limit(4); // read the entire length MockSocketChannel ch=new MockSocketChannel().bytesToRead(data); Buffers bufs=new Buffers(ByteBuffer.allocate(Global.INT_SIZE), null); ByteBuffer buf=bufs.readLengthAndData(ch); assert buf == null; data.limit(8); // allow for some more data to be read... buf=bufs.readLengthAndData(ch); assert buf == null; data.limit(data.capacity()); // read all data buf=bufs.readLengthAndData(ch); assert buf != null; } /** Reads | version (short) | number (int) | cookie [4 bytes] | */ public void testRead3Buffers() throws Exception { byte[] cookie={'b', 'e', 'l', 'a'}; int num=322649; ByteBuffer input=(ByteBuffer)ByteBuffer.allocate(Global.SHORT_SIZE + cookie.length + Global.INT_SIZE) .putShort(Version.version) .putInt(num).put(cookie, 0, cookie.length).flip(); MockSocketChannel ch=new MockSocketChannel().bytesToRead(input); Buffers bufs=new Buffers(ByteBuffer.allocate(2), ByteBuffer.allocate(4), ByteBuffer.allocate(4)); boolean rc=bufs.read(ch); System.out.println("bufs = " + bufs); assert rc; assert bufs.position() == 3; assert bufs.limit() == 3; for(ByteBuffer b: bufs) // reset position so data can be read b.clear(); short version=bufs.get(0).getShort(0); assert version == Version.version; int num2=bufs.get(1).getInt(0); assert num2 == num; byte[] tmp=new byte[4]; bufs.get(2).get(tmp, 0, tmp.length); assert Arrays.equals(tmp, cookie); } public void testAdd() throws Exception { Buffers buf=new Buffers(12); buf.add(buf1); check(buf, 0, 1, 1, remaining(buf1)); buf.add(buf2); check(buf, 0, 2, 2, remaining(buf1, buf2)); buf.add(buf3); check(buf, 0, 3, 3, remaining(buf1, buf2, buf3)); } public void testMakeSpaceSimple() throws Exception { MockSocketChannel ch=new MockSocketChannel().bytesToWrite(1000); Buffers buf=new Buffers(2); buf.add(buf1); check(buf, 0, 1, 1, remaining(buf1)); boolean rc=makeSpace(buf); assert !rc; buf.write(ch); check(buf, 1, 1, 0, 0); rc=makeSpace(buf); assert rc; check(buf, 0, 0, 0, 0); } public void testMakeSpaceSimpleMove() throws Exception { MockSocketChannel ch=new MockSocketChannel().bytesToWrite(1000); Buffers buf=new Buffers(6); buf.add(buf1); buf.write(ch); check(buf, 1, 1, 0, 0); buf.add(buf2); check(buf, 1, 2, 1, remaining(buf2)); boolean rc=makeSpace(buf); assert rc; check(buf, 0, 1, 1, remaining(buf2)); } public void testMakeSpaceMoveTwo() throws Exception { MockSocketChannel ch=new MockSocketChannel().bytesToWrite(1000); Buffers buf=new Buffers(6); buf.add(buf1); buf.add(buf2); buf.write(ch); buf.add(buf3); buf.add(ByteBuffer.wrap("bla".getBytes())); ch.bytesToWrite(2); boolean rc=buf.write(ch); assert !rc; buf.copy(); rc=makeSpace(buf); assert rc; check(buf, 0, 2, 2, remaining(buf3)+3); } public void testMakeSpaceOverlappingMove() throws Exception { MockSocketChannel ch=new MockSocketChannel().bytesToWrite(1000); Buffers buf=new Buffers(6); buf.add(buf1); check(buf, 0, 1, 1, remaining(buf1)); buf.write(ch); check(buf, 1, 1, 0, 0); buf.add(buf2, buf3); boolean rc=makeSpace(buf); assert rc; check(buf, 0, 2, 2, remaining(buf2,buf3)); } public void testNullData() throws Exception { MockSocketChannel ch=new MockSocketChannel().bytesToWrite(1000); Buffers buf=new Buffers(3); Arrays.asList(buf1, buf2, buf3).forEach(buf::add); check(buf, 0, 3, 3, remaining(buf1, buf2, buf3)); boolean rc=buf.write(ch); assert rc; // makeSpace(buf); check(buf, 0, 0, 0, 0); } public void testSimpleWrite() throws Exception { MockSocketChannel ch=new MockSocketChannel().bytesToWrite(1000); Buffers buf=new Buffers(2); boolean rc=buf.write(ch, buf1.duplicate()); assert rc; check(buf, 1, 1, 0, 0); rc=buf.write(ch, buf2.duplicate()); assert rc; check(buf, 0, 0, 0, 0); } public void testWrite() throws Exception { ByteBuffer b=buf1.duplicate(); Buffers bufs=new Buffers(2); MockSocketChannel ch=new MockSocketChannel().bytesToWrite(15); boolean rc=bufs.write(ch, b); assert rc; check(bufs, 1, 1, 0, 0); // write only a portion of the data ch.bytesToWrite(10); b.clear(); rc=bufs.write(ch, b); assert !rc; check(bufs, 1, 2, 1, b.remaining()); ch.bytesToWrite(10); rc=bufs.write(ch); assert rc; check(bufs, 0, 0, 0, 0); ch.bytesToWrite(10); b=buf1.duplicate(); // mimic a new buffer rc=bufs.write(ch, b); assert rc == false; check(bufs, 0, 1, 1, b.remaining()); ch.bytesToWrite(30); b=buf1.duplicate(); // mimic a new buffer rc=bufs.write(ch, b); assert rc; check(bufs, 0, 0, 0, 0); } public void testWrite2() throws Exception { ByteBuffer b=buffer(); Buffers bufs=new Buffers(ByteBuffer.allocate(Global.INT_SIZE), null); MockSocketChannel ch=new MockSocketChannel().bytesToWrite(15); boolean rc=bufs.write(ch, b); assert rc; // write only a portion of the data ch.bytesToWrite(10); b.clear(); rc=bufs.write(ch, b); assert rc == false; ch.bytesToWrite(10); rc=bufs.write(ch); assert rc; ch.bytesToWrite(10); b=buffer(); // mimic a new buffer rc=bufs.write(ch, b); assert rc == false; ch.bytesToWrite(30); b=buffer(); // mimic a new buffer rc=bufs.write(ch, b); assert rc; } public void testWriteWithBuffering() throws Exception { Buffers bufs=new Buffers(4); MockSocketChannel ch=new MockSocketChannel(); // all writes will fail for(int i=0; i < 4; i++) { ByteBuffer b=buf1.duplicate(); boolean rc=bufs.write(ch, b); assert !rc; } check(bufs, 0, 4, 4, remaining(buf1) *4); ch.bytesToWrite(20); boolean rc=bufs.write(ch); assert !rc; rc=bufs.write(ch, buf1.duplicate()); assert !rc; check(bufs, 0, 4, 4, remaining(buf1) * 5 - 20); ch.bytesToWrite(1000); // write all buffers now rc=bufs.write(ch); assert rc; check(bufs, 0 ,0 ,0 ,0); rc=bufs.write(ch, buf1.duplicate()); assert rc; check(bufs, 1, 1, 0, 0); } public void testWriteAtStartup() throws Exception { Buffers bufs=new Buffers(5); MockSocketChannel ch=new MockSocketChannel(); // all writes will initially fail boolean rc=bufs.write(ch, buf1); assert !rc; check(bufs, 0, 1, 1, remaining(buf1)); ch.bytesToWrite(100); rc=bufs.write(ch, buf2); assert rc; check(bufs, 2, 2, 0, 0); for(int i=0; i < 3; i++) { ByteBuffer b=buf3.duplicate(); rc=bufs.write(ch, b); assert rc; } check(bufs, 0, 0, 0, 0); } public void testCopyBuffer() { byte[] array1=Util.generateArray(1024), array2=Util.generateArray(16), array3=Util.generateArray(8); int off1=500, len1=500, off2=0, len2=16, off3=0, len3=4; testCopyBuffer(ByteBuffer.wrap(array1, off1, len1)); testCopyBuffer(ByteBuffer.wrap(array2, off2, len2)); testCopyBuffer(ByteBuffer.wrap(array3, off3, len3)); ByteBuffer direct=ByteBuffer.allocateDirect(16); for(int i=0; i < direct.remaining(); i++) direct.put(i, (byte)Util.random(26)); ByteBuffer copy=Buffers.copyBuffer(direct); assert copy.equals(direct); direct.position(10); copy=Buffers.copyBuffer(direct); assert copy.equals(direct); } public void testCopy() throws Exception { copyHelper(3, 2, 8, 10); copyHelper(3, 2, 8, 4); copyHelper(3, 2, 8, 20); copyHelper(3, 3, 8, 4); copyHelper(3, 3, 8, 10); } public void testCopyWithPartialWrite() throws Exception { final String s1="hello", s2=" world", s3=" from ", s4="Bela"; ByteBuffer a=ByteBuffer.wrap(s1.getBytes()), b=ByteBuffer.wrap(s2.getBytes()), c=ByteBuffer.wrap(s3.getBytes()), d=ByteBuffer.wrap(s4.getBytes()); Buffers bufs=new Buffers(a,b,c,d); ByteBuffer recorder=ByteBuffer.allocate(100); MockSocketChannel ch=new MockSocketChannel().bytesToWrite(13) // a, b: OK, c: partial, d: fail .recorder(recorder); // we're recording the writes so we can compare expected bytes to actually written bytes boolean success=bufs.write(ch); System.out.println("bufs = " + bufs); assert !success; assert bufs.position() == 2; assert bufs.limit() == 4; // copy the buffers which have not yet been written so that we can reuse buffers (not needed if buffers are already copies) bufs.copy(); // https://issues.jboss.org/browse/JGRP-1991 makeSpace(bufs); assert bufs.position() == 0; assert bufs.limit() == 2; assert bufs.nextToCopy() == 2; // now modify the original buffers for(ByteBuffer buf: Arrays.asList(a,b,c,d)) buf.putInt(0, 322649); ch.bytesToWrite(100); // next write will be successful success=bufs.write(ch); System.out.println("bufs = " + bufs); assert success; assert bufs.position() == 2; assert bufs.limit() == 2; // now compare the contents of the buffers recorder.flip(); for(String s: Arrays.asList(s1,s2,s3,s4)) { byte[] expected=s.getBytes(); byte[] actual=new byte[expected.length]; recorder.get(actual); assert Arrays.equals(expected, actual) : String.format("expected %s, got %s\n", s, new String(actual)); } } public void testIteration() { Buffers buf=new Buffers(6).add(ByteBuffer.wrap("hello world".getBytes()), ByteBuffer.allocate(1024), ByteBuffer.allocate(500), ByteBuffer.allocate(1024)); int i=1, count=0; for(ByteBuffer b: buf) { System.out.printf("buffer #%d: %d bytes\n", i++, b.remaining()); count++; } assert count == 4; System.out.println(""); buf.remove(0).remove(2); i=1; count=0; for(ByteBuffer b: buf) { System.out.printf("buffer #%d: %d bytes\n", i++, b.remaining()); count++; } assert count == 2; buf=new Buffers(10); for(i=1; i <= 10; i++) buf.add(ByteBuffer.allocate(i)); buf.remove(3).remove(4).remove(5).remove(9); System.out.println(); count=0; i=1; for(ByteBuffer b: buf) { System.out.printf("buffer #%d: %d bytes\n", i++, b.remaining()); count++; } assert count == 6; } protected void testCopyBuffer(ByteBuffer buf) { ByteBuffer copy=Buffers.copyBuffer(buf); assert copy.equals(buf); } protected void copyHelper(int capacity, int num_buffers, int buffer_size, int bytes_to_write) throws Exception { assert capacity > 0 && num_buffers <= capacity; byte[][] arrays=new byte[num_buffers][]; // the original data, will be used to compare after the copy() for(int i=0; i < arrays.length; i++) arrays[i]=Util.generateArray(buffer_size); ByteBuffer[] buffers=new ByteBuffer[num_buffers]; for(int i=0; i < arrays.length; i++) { // make a copy as we'll modify it later, so the original is not modified: we need it to compare later byte[] tmp=Arrays.copyOf(arrays[i], arrays[i].length); buffers[i]=ByteBuffer.wrap(tmp); } ByteBuffer recorder=ByteBuffer.allocate(Math.max(bytes_to_write, num_buffers * buffer_size)); MockSocketChannel ch=new MockSocketChannel().bytesToWrite(bytes_to_write).recorder(recorder); Buffers bufs=new Buffers(capacity).add(buffers); System.out.println("\nbufs = " + bufs); assert bufs.size() == buffers.length; boolean successful_write=bytes_to_write >= num_buffers * buffer_size; boolean rc=bufs.write(ch); assert rc == successful_write; if(!successful_write) { bufs.copy(); int num_bufs_not_written=(int)Math.ceil((num_buffers * buffer_size - bytes_to_write) / (double)buffer_size); assertNotEqual(bufs, buffers, num_bufs_not_written); // modify the buffers and compare the output to the original buffers (should match) for(ByteBuffer buf: buffers) modifyBuffer(buf); ch.bytesToWrite(num_buffers * buffer_size); // the next write() will succeed rc=bufs.write(ch); assert rc; // now compare the original buffers with the recorded bytes recorder.flip(); for(byte[] original: arrays) { byte[] actual=new byte[original.length]; recorder.get(actual); assert Arrays.equals(original, actual); } } } protected void assertNotEqual(Buffers bufs, ByteBuffer[] buffers, int num_buffers) { int not_equal=0; for(int i=0; i < num_buffers; i++) { ByteBuffer original=buffers[buffers.length - num_buffers + i], actual=bufs.get(i+bufs.position()); boolean same=original == actual; System.out.printf("original %s %s actual %s\n", original.hashCode(), same? "==" : "!=", actual.hashCode()); if(!same) not_equal++; } assert not_equal > 0; } protected void check(Buffers buf, int pos, int limit, int size, int remaining) { System.out.println("buf = " + buf); assert buf.position() == pos : String.format("expected %d but got %d", pos, buf.position()); assert buf.limit() == limit : String.format("expected %d but got %d", limit, buf.limit()); assert buf.size() == size : String.format("expected %d but got %d", size, buf.size()); assert buf.remaining() == remaining : String.format("expected %d but got %d", remaining, buf.remaining()); } protected boolean makeSpace(Buffers buf) throws Exception { return (boolean)makeSpace.invoke(buf); } protected boolean spaceAvailable(Buffers buf, int num_bufs) throws Exception { return (boolean)spaceAvailable.invoke(buf, num_bufs); } protected boolean nullData(Buffers buf) throws Exception { return (boolean)nullData.invoke(buf); } protected int remaining(ByteBuffer ... buffers) { int remaining=0; for(ByteBuffer buf: buffers) { remaining+=buf.remaining(); } return remaining; } protected void position(Buffers buf, int pos) { Util.setField(position, buf, pos); } protected void limit(Buffers buf, int lim) { Util.setField(limit, buf, lim); } protected static void modifyBuffer(final ByteBuffer buf) { buf.clear(); for(int i=0; i < buf.capacity(); i++) buf.put(i, (byte)Util.random(26)); } }