package org.limewire.io;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.limewire.io.GGEP;
import org.limewire.io.IOUtils;
import org.limewire.util.BaseTestCase;
import org.limewire.util.ByteUtils;
import org.limewire.util.NameValue;

import junit.framework.Test;


public class GGEPTest extends BaseTestCase {
    
    public GGEPTest(String name) {
        super(name);
    }

    public static Test suite() {
        return buildTestSuite(GGEPTest.class);
    }


    public void testCOBS() throws Exception {
        byte[] nulls = new byte[10];
        for (int i = 0; i < nulls.length; i++)
            nulls[i] = (byte)(i % 2);
        String someNulls = "Hello" + new String(nulls);
        try {
            GGEP one = new GGEP(true);
            one.put("Susheel", nulls);
            one.put("Daswani", someNulls);
            one.put("Number", 10);
            ByteArrayOutputStream oStream = new ByteArrayOutputStream();
            one.write(oStream);
            GGEP two = new GGEP(oStream.toByteArray(), 0, null);
            assertTrue(two.hasKey("Susheel"));
            byte[] shouldBeNulls = two.getBytes("Susheel");
            assertEquals(nulls, shouldBeNulls);
            assertEquals(someNulls, two.getString("Daswani"));
            assertEquals(10, two.getInt("Number"));
        }
        catch (IllegalArgumentException illegal) {
            fail("The .put method failed!! ", illegal);
        }
    }
    
    public void testPutAll() throws Exception {
        List<NameValue<?>> putm = new LinkedList<NameValue<?>>();
        putm.add(new NameValue<Integer>("int", new Integer(1)));
        putm.add(new NameValue("header"));
        putm.add(new NameValue<Long>("long", new Long(2)));
        putm.add(new NameValue<String>("string", "value"));
        putm.add(new NameValue<byte[]>("byte[]", new byte[] { 1, 2 } ));
        
        GGEP temp = new GGEP();
        temp.putAll(putm);
        
        assertTrue(temp.hasKey("int"));
        assertEquals(1, temp.getInt("int"));
        assertTrue(temp.hasKey("header"));
        assertTrue(temp.hasKey("long"));
        assertEquals(2, temp.getLong("long"));
        assertTrue(temp.hasKey("string"));
        assertEquals("value", temp.getString("string"));
        assertTrue(temp.hasKey("byte[]"));
        assertEquals(new byte[] { 1, 2 }, temp.getBytes("byte[]"));
    }

    public void testStringKeys() throws Exception {
        GGEP temp = new GGEP();
        temp.put("A", "B");
        try {
            temp.put("C", (String)null);
            fail("should have caught IAX");
        } catch(IllegalArgumentException expected) {}
        temp.put("STRING", "");
        assertTrue(temp.hasKey("A"));
        assertEquals("B", temp.getString("A"));
        assertFalse(temp.hasKey("C"));
        assertTrue(temp.hasKey("STRING"));
        assertEquals("", temp.getString("STRING"));
    }

    public void testByteKeys() throws Exception {
        GGEP temp = new GGEP();
        temp.put("A", new byte[] { (byte)3 });
        assertTrue(temp.hasKey("A"));
        assertEquals(temp.getBytes("A"), new byte[] { (byte)3 });
    }

    public void testIntKeys() throws Exception {
        GGEP temp = new GGEP();
        temp.put("A", 527);
        assertTrue(temp.hasKey("A"));
        assertEquals(527, temp.getInt("A"));
        assertEquals(temp.getBytes("A"),
                     new byte[] { (byte)0x0F, (byte)0x02 });
    }
    
    public void testLongKeys() throws Exception {
        GGEP temp = new GGEP();
        temp.put("A", 0xABCDL);
        assertTrue(temp.hasKey("A"));
        assertEquals(0xABCDL, temp.getLong("A"));
        assertEquals(temp.getBytes("A"), new byte[] {(byte)0xCD,(byte)0xAB} );
        
        temp.put("A", 0x00ABCDEF12L);
        assertTrue(temp.hasKey("A"));
        assertEquals(0xABCDEF12L, temp.getLong("A"));
        assertEquals(temp.getBytes("A"), new byte[] {
            (byte)0x12, (byte)0xEF, (byte)0xCD, (byte)0xAB });
    }
        

    /** Tests that map constructor doesn't accept keys that are too long, should
     *  throw an exception */
    public void testKeyTooBig() {
        try {
            GGEP temp = new GGEP();
            temp.put("THIS KEY IS WAY TO LONG!", "");
            fail("No IllegalArgumentException.");
        } catch (IllegalArgumentException pass) { 
        }
    }

    /** Tests that map constructor doesn't accept data that is too long, should
     *  throw an exception. */
    public void testValueTooBig() {
        StringBuffer bigBoy = new StringBuffer(GGEP.MAX_VALUE_SIZE_IN_BYTES+10);
        for (int i = 0; i < GGEP.MAX_VALUE_SIZE_IN_BYTES+10; i++)
            bigBoy.append("1");
        
        try {
            GGEP temp = new GGEP();
            temp.put("WHATEVER", bigBoy.toString());
            fail("No IllegalArgumentException.");
        } catch (IllegalArgumentException pass) { }
    }


    /** Test to see if the GGEP can handle datalens that are pretty big.... 
     */
    public void testBigValue() throws Exception {
        StringBuffer bigBoy = new StringBuffer(GGEP.MAX_VALUE_SIZE_IN_BYTES);
        for (int i = 0; i < GGEP.MAX_VALUE_SIZE_IN_BYTES; i++)
            bigBoy.append("1");
        
        String[] keys = {"Susheel", "is", "an", "Idiot!!"};

        GGEP temp = new GGEP();
        for (int i = 0; i < keys.length; i++)
            temp.put(keys[i], bigBoy.toString());
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        temp.write(baos);
        byte[] ggepBytes = baos.toByteArray();
        GGEP reconstruct = new GGEP(ggepBytes, 0, null);
        for (int i = 0; i < keys.length; i++) {
            String currValue = reconstruct.getString(keys[i]);
            assertEquals(currValue, bigBoy.toString());
        }
    }


    /** Null bytes allowed, e.g., in ping replies */
    public void testValueContainsLegalNull() {
        byte[] bytes = new byte[2];
        bytes[0] = (byte)'S';
        bytes[1] = (byte)0x0;
        String hasANull = new String(bytes);

        GGEP temp = new GGEP();
        temp.put("WHATEVER", hasANull);
    }

    /** Null bytes are always allowed now.
     */
    public void testValueContainsIllegalNull() {
        byte[] bytes = new byte[2];
        bytes[0] = (byte)'S';
        bytes[1] = (byte)0x0;
        String hasANull = new String(bytes);

        GGEP temp = new GGEP(true);
        temp.put("WHATEVER", hasANull);
    }

    public void testEquals() {
        GGEP a1=new GGEP();
        a1.put("K1", "V1");
        GGEP a2=new GGEP();
        a2.put("K1", "V1");
        GGEP b1=new GGEP();
        b1.put("K1");
        GGEP b2=new GGEP();
        b2.put("K1");

        assertEquals(a1, a1);
        assertEquals(a1, a2);
        assertEquals(b1, b1);
        assertEquals(b1, b2);
        assertNotEquals(a1, b1);
        assertNotEquals(b1, a1);
        
        GGEP c1=new GGEP();
        c1.put("K1", "V1");
        c1.put("K2", "V2");
        GGEP c2=new GGEP();
        c2.put("K1", "V1");
        c2.put("K2", "V2");        

        assertEquals(c1, c1);
        assertEquals(c1, c2);
        assertNotEquals(a1, c1);
        assertNotEquals(b1, c1);
    }
    
    public void testPutCompressed() throws Exception {
        byte[] middleValue = "middle".getBytes();
        GGEP g = new GGEP(true);
        g.put("1", "begin");
        g.putCompressed("2", middleValue);
        g.put("3", "end");
        
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        g.write(out);
        
        byte[] bytes = out.toByteArray();
        int i = 0;
        assertEquals(GGEP.GGEP_PREFIX_MAGIC_NUMBER, bytes[i++]);
        assertEquals( (byte)0x01, bytes[i++] );
        assertEquals( (byte)'1',  bytes[i++] );
        assertEquals( (byte)0x45, bytes[i++] );
        assertEquals( (byte)'b',  bytes[i++] );
        assertEquals( (byte)'e',  bytes[i++] );
        assertEquals( (byte)'g',  bytes[i++] );
        assertEquals( (byte)'i',  bytes[i++] );
        assertEquals( (byte)'n',  bytes[i++] );
        assertEquals( (byte)0x21, bytes[i++] );
        assertEquals( (byte)'2',  bytes[i++] );
        
        // now, we have to figure out how many bytes are used up in the
        // compressed version of 'middle'.
        byte[] compressed = IOUtils.deflate(middleValue);
        assertNotEquals(middleValue, compressed);
        int length = compressed.length;
        if((length & 0x3F000) != 0)
            assertEquals( (byte)0x80 | ((length & 0x3F000) >> 12), bytes[i++]);
        if((length & 0xFC0) != 0)
            assertEquals( (byte)0x80 | ((length & 0xFC0) >> 6), bytes[i++]);
        assertEquals((byte)0x40 | (length & 0x3F), bytes[i++]);
        
        for(int j = 0; j < compressed.length; j++)
            assertEquals(compressed[j], bytes[i++]);
        // end of checking for the compressed data.
            
        assertEquals( (byte)0x81, bytes[i++] );
        assertEquals( (byte)'3',  bytes[i++] );
        assertEquals( (byte)0x43, bytes[i++] );
        assertEquals( (byte)'e',  bytes[i++] );
        assertEquals( (byte)'n',  bytes[i++] );
        assertEquals( (byte)'d',  bytes[i++] );
    }
    
    public void testReadCompressedAndWritesCompressed() throws Exception {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        out.write(GGEP.GGEP_PREFIX_MAGIC_NUMBER);
        out.write(0x01);
        out.write('1');
        out.write(0x45);
        out.write('b');
        out.write('e');
        out.write('g');
        out.write('i');
        out.write('n');
        out.write(0x21);
        out.write('2');
        byte[] middleValue = "the middle value, compressed.".getBytes();
        byte[] compressed = IOUtils.deflate(middleValue);
        assertNotEquals(middleValue, compressed);
        int length = compressed.length;
        if((length & 0x3F000) != 0)
            out.write( (byte)0x80 | ((length & 0x3F000) >> 12));
        if((length & 0xFC0) != 0)
            out.write( (byte)0x80 | ((length & 0xFC0) >> 6));
        out.write((byte)0x40 | (length & 0x3F));
        out.write(compressed);
        out.write(0x81);
        out.write('3');
        out.write(0x43);
        out.write('e');
        out.write('n');
        out.write('d');
        
        int offsets[] = new int[1];
        byte[] bytes = out.toByteArray();
        GGEP ggep = new GGEP(bytes, 0, offsets);
        assertEquals(bytes.length, offsets[0]);
        assertEquals(3, ggep.getHeaders().size());
        assertEquals("begin", ggep.getString("1"));
        assertEquals(new String(middleValue), ggep.getString("2"));
        assertEquals("end", ggep.getString("3"));
        
        // Make sure that also write the data as compressed, if it was read that way.
        ByteArrayOutputStream toWrite = new ByteArrayOutputStream();
        ggep.write(toWrite);
        assertEquals(bytes, toWrite.toByteArray());
    }

    public void testBasicConstruction() throws Exception {
        byte[] bytes = new byte[24];
        bytes[0] = GGEP.GGEP_PREFIX_MAGIC_NUMBER;
        bytes[1] = (byte)0x05;
        bytes[2] = (byte)'B';
        bytes[3] = (byte)'H';
        bytes[4] = (byte)'O';
        bytes[5] = (byte)'S';
        bytes[6] = (byte)'T';
        bytes[7] = (byte)0x40;
        bytes[8] = (byte)0x87;
        bytes[9] =  (byte)'S';
        bytes[10] = (byte)'U';
        bytes[11] = (byte)'S';
        bytes[12] = (byte)'H';
        bytes[13] = (byte)'E';
        bytes[14] = (byte)'E';
        bytes[15] = (byte)'L';
        bytes[16] = (byte)0x47;
        bytes[17] = (byte)'D';
        bytes[18] = (byte)'A';
        bytes[19] = (byte)'S';
        bytes[20] = (byte)'W';
        bytes[21] = (byte)'A';
        bytes[22] = (byte)'N';
        bytes[23] = (byte)'I';
        GGEP ggep = new GGEP(bytes, 0);
                          
        bytes = new byte[32];
        bytes[0] = GGEP.GGEP_PREFIX_MAGIC_NUMBER;
        bytes[1] = (byte)0x85;
        bytes[2] = (byte)'B';
        bytes[3] = (byte)'H';
        bytes[4] = (byte)'O';
        bytes[5] = (byte)'S';
        bytes[6] = (byte)'T';
        bytes[7] = (byte)0x40;
        bytes[8] = GGEP.GGEP_PREFIX_MAGIC_NUMBER;
        bytes[9] = (byte)0x05;
        bytes[10] = (byte)'B';
        bytes[11] = (byte)'H';
        bytes[12] = (byte)'O';
        bytes[13] = (byte)'S';
        bytes[14] = (byte)'T';
        bytes[15] = (byte)0x40;
        bytes[16] = (byte)0x87;
        bytes[17] = (byte)'S';
        bytes[18] = (byte)'U';
        bytes[19] = (byte)'S';
        bytes[20] = (byte)'H';
        bytes[21] = (byte)'E';
        bytes[22] = (byte)'E';
        bytes[23] = (byte)'L';
        bytes[24] = (byte)0x47;
        bytes[25] = (byte)'D';
        bytes[26] = (byte)'A';
        bytes[27] = (byte)'S';
        bytes[28] = (byte)'W';
        bytes[29] = (byte)'A';
        bytes[30] = (byte)'N';
        bytes[31] = (byte)'I';
        ggep = new GGEP(bytes, 0);
        assertEquals(1, ggep.getHeaders().size());
        assertTrue(ggep.hasKey("BHOST"));
        ggep = new GGEP(bytes, 8);
        assertEquals(2, ggep.getHeaders().size());
        assertTrue(ggep.hasKey("BHOST"));
        assertTrue(ggep.hasKey("SUSHEEL"));
        assertEquals("DASWANI", ggep.getString("SUSHEEL"));

        bytes = new byte[24];
        bytes[0] = GGEP.GGEP_PREFIX_MAGIC_NUMBER;
        bytes[1] = (byte)0x15;
        bytes[2] = (byte)'B';
        bytes[3] = (byte)'H';
        bytes[4] = (byte)'O';
        bytes[5] = (byte)'S';
        bytes[6] = (byte)'T';
        bytes[7] = (byte)0x40;
        bytes[8] = (byte)0x87;
        bytes[9] = (byte)'S';
        bytes[10] = (byte)'U';
        bytes[11] = (byte)'S';
        bytes[12] = (byte)'H';
        bytes[13] = (byte)'E';
        bytes[14] = (byte)'E';
        bytes[15] = (byte)'L';
        bytes[16] = (byte)0x47;
        bytes[17] = (byte)'D';
        bytes[18] = (byte)'A';
        bytes[19] = (byte)'S';
        bytes[20] = (byte)'W';
        bytes[21] = (byte)'A';
        bytes[22] = (byte)'N';
        bytes[23] = (byte)'I';
        try {
            ggep = new GGEP(bytes, 0);
            fail("should have constructed");
        } catch (BadGGEPBlockException expected) {}
    }


    // tests normal behavior of the byte[] constructor
    public void testByteArrayConstructor1() throws Exception {
        int[] endOffset = new int[1];
        byte[] bytes = new byte[24];
        bytes[0] = GGEP.GGEP_PREFIX_MAGIC_NUMBER;
        bytes[1] = (byte)0x05;
        bytes[2] = (byte)'B';
        bytes[3] = (byte)'H';
        bytes[4] = (byte)'O';
        bytes[5] = (byte)'S';
        bytes[6] = (byte)'T';
        bytes[7] = (byte)0x40;
        bytes[8] = (byte)0x87;
        bytes[9] =  (byte)'S';
        bytes[10] = (byte)'U';
        bytes[11] = (byte)'S';
        bytes[12] = (byte)'H';
        bytes[13] = (byte)'E';
        bytes[14] = (byte)'E';
        bytes[15] = (byte)'L';
        bytes[16] = (byte)0x47;
        bytes[17] = (byte)'D';
        bytes[18] = (byte)'A';
        bytes[19] = (byte)'S';
        bytes[20] = (byte)'W';
        bytes[21] = (byte)'A';
        bytes[22] = (byte)'N';
        bytes[23] = (byte)'I';        

        GGEP temp = new GGEP(bytes,0, endOffset);
        Set headers = temp.getHeaders();
        assertTrue("Test 5 - NO BHOST!", headers.contains("BHOST"));
        try {
            temp.getString("BHOST");
            fail("No BadGGEPPropertyException.");
        } catch (BadGGEPPropertyException e) {
        }
        assertTrue("Test 5 - NO SUSH!", headers.contains("SUSHEEL"));
        Object shouldNotBeNull = temp.getString("SUSHEEL");
        assertNotNull(shouldNotBeNull);
        assertEquals(24, endOffset[0]);
    }


    // tests abnormal behavior of the byte[] constructor - tries to give
    // compressed data, a 0 header length, and a data length
    // which is stored in more than 3 bytes...
    public void testByteArrayConstructor2() throws Exception {
        byte[] bytes = new byte[24];
        bytes[0] = GGEP.GGEP_PREFIX_MAGIC_NUMBER;
        bytes[1] = (byte)0x05;
        bytes[2] = (byte)'B';
        bytes[3] = (byte)'H';
        bytes[4] = (byte)'O';
        bytes[5] = (byte)'S';
        bytes[6] = (byte)'T';
        bytes[7] = (byte)0x40;
        bytes[8] = (byte)0x87; 
        bytes[9] =  (byte)'S';
        bytes[10] = (byte)'U';
        bytes[11] = (byte)'S';
        bytes[12] = (byte)'H';
        bytes[13] = (byte)'E';
        bytes[14] = (byte)'E';
        bytes[15] = (byte)'L';
        bytes[16] = (byte)0x47;
        bytes[17] = (byte)'D';
        bytes[18] = (byte)'A';
        bytes[19] = (byte)'S';
        bytes[20] = (byte)'W';
        bytes[21] = (byte)'A';
        bytes[22] = (byte)'N';
        bytes[23] = (byte)'I';        

        bytes[8] = (byte)0xA5; // compressed, without valid data.
        try {
            new GGEP(bytes,0,null);
            fail("shoulda failed");
        } catch(BadGGEPBlockException bgbe) {}


        bytes[8] = (byte)0x80; // 0 len header
        try {
            new GGEP(bytes,0,null);
            assertTrue("Test 6 - 0 LEN HEADER!", false);
        } 
        catch (BadGGEPBlockException hopefullySo) {
        }

        bytes[8] = (byte)0x87; 
        bytes[16] = (byte)0xBF;
        bytes[17] = (byte)0xBF;
        bytes[18] = (byte)0xBF;
        bytes[19] = (byte)0xBF;
        try {
            new GGEP(bytes,0,null);
            assertTrue("Test 6 - >3 DATA LEN!", false);
        } 
        catch (BadGGEPBlockException hopefullySo) {
        }

        
    }


    public void testWriteMethod() throws Exception {
        GGEP one = null, two = null;
        byte[] bytes = new byte[24];
        bytes[0] = GGEP.GGEP_PREFIX_MAGIC_NUMBER;
        bytes[1] = (byte)0x05;
        bytes[2] = (byte)'B';
        bytes[3] = (byte)'H';
        bytes[4] = (byte)'O';
        bytes[5] = (byte)'S';
        bytes[6] = (byte)'T';
        bytes[7] = (byte)0x40;
        bytes[8] = (byte)0x87;
        bytes[9] =  (byte)'S';
        bytes[10] = (byte)'U';
        bytes[11] = (byte)'S';
        bytes[12] = (byte)'H';
        bytes[13] = (byte)'E';
        bytes[14] = (byte)'E';
        bytes[15] = (byte)'L';
        bytes[16] = (byte)0x47;
        bytes[17] = (byte)'D';
        bytes[18] = (byte)'A';
        bytes[19] = (byte)'S';
        bytes[20] = (byte)'W';
        bytes[21] = (byte)'A';
        bytes[22] = (byte)'N';
        bytes[23] = (byte)'I';        
        one = new GGEP(bytes,0,null);

        ByteArrayOutputStream oStream = new ByteArrayOutputStream();
        one.write(oStream);
        two = new GGEP(oStream.toByteArray(), 0, null);

        assertEquals(one.getHeaders(), two.getHeaders());
        assertEquals(one, two);
    }


    public void testMalformedGGEP() throws Exception {
        byte[] bytes = new byte[24];
        bytes[0] = GGEP.GGEP_PREFIX_MAGIC_NUMBER;
        bytes[1] = (byte)0x05;
        bytes[2] = (byte)'B';
        bytes[3] = (byte)'H';
        bytes[4] = (byte)'O';
        bytes[5] = (byte)'S';
        bytes[6] = (byte)'T';
        bytes[7] = (byte)0x40;
        bytes[8] = (byte)0x87;
        bytes[9] =  (byte)'S';
        bytes[10] = (byte)'U';
        bytes[11] = (byte)'S';
        bytes[12] = (byte)'H';
        bytes[13] = (byte)'E';
        bytes[14] = (byte)'E';
        bytes[15] = (byte)'L';
        bytes[16] = (byte)0x47;
        bytes[17] = (byte)'D';
        bytes[18] = (byte)'A';
        bytes[19] = (byte)'S';
        bytes[20] = (byte)'W';
        bytes[21] = (byte)'A';
        bytes[22] = (byte)'N';
        bytes[23] = (byte)'I';                

        // test first case where no magic number....
        bytes[0] = (byte) 'I';
        try {
            new GGEP(bytes,0,null);
            fail("need to fail.");
        }
        catch (BadGGEPBlockException hopefullySo) {
        }

        // now test a ID Len that is lying!
        bytes = new byte[6];
        bytes[0] = GGEP.GGEP_PREFIX_MAGIC_NUMBER;
        bytes[1] = (byte)0x05;
        bytes[2] = (byte)'B';
        bytes[3] = (byte)'H';
        bytes[4] = (byte)'O';
        bytes[5] = (byte)'S';
        try {
            new GGEP(bytes,0,null);
            fail("need to fail.");
        }
        catch (BadGGEPBlockException hopefullySo) {
        }

        // too many data length fields...
        bytes = new byte[24];
        bytes[0] = GGEP.GGEP_PREFIX_MAGIC_NUMBER;
        bytes[1] = (byte)0x05;
        bytes[2] = (byte)'B';
        bytes[3] = (byte)'H';
        bytes[4] = (byte)'O';
        bytes[5] = (byte)'S';
        bytes[6] = (byte)'T';
        bytes[7] = (byte)0xbf;
        bytes[8] = (byte)0xbf;
        bytes[9] = (byte)0xbf;
        bytes[10] = (byte)'U';
        bytes[11] = (byte)'S';
        bytes[12] = (byte)'H';
        bytes[13] = (byte)'E';
        bytes[14] = (byte)'E';
        bytes[15] = (byte)'L';
        bytes[16] = (byte)0x47;
        bytes[17] = (byte)'D';
        bytes[18] = (byte)'A';
        bytes[19] = (byte)'S';
        bytes[20] = (byte)'W';
        bytes[21] = (byte)'A';
        bytes[22] = (byte)'N';
        bytes[23] = (byte)'I';                
        try {
            new GGEP(bytes,0,null);
            fail("need to fail");
        }
        catch (BadGGEPBlockException hopefullySo) {
        }
        

        // not enough data length fields...
        bytes = new byte[9];
        bytes[0] = GGEP.GGEP_PREFIX_MAGIC_NUMBER;
        bytes[1] = (byte)0x05;
        bytes[2] = (byte)'B';
        bytes[3] = (byte)'H';
        bytes[4] = (byte)'O';
        bytes[5] = (byte)'S';
        bytes[6] = (byte)'T';
        bytes[7] = (byte)0xbf;
        bytes[8] = (byte)0xbf;
        try {
            new GGEP(bytes,0,null);
            fail("need to fail");
        }
        catch (BadGGEPBlockException hopefullySo) {
        }


        // not enough bytes!
        bytes = new byte[0];
        try {
            new GGEP(bytes,0,null);
            fail("need to fail");            
        }
        catch (BadGGEPBlockException hopefullySo) {
        }

        // not enough data fields...
        bytes = new byte[22];
        bytes[0] = GGEP.GGEP_PREFIX_MAGIC_NUMBER;
        bytes[1] = (byte)0x05;
        bytes[2] = (byte)'B';
        bytes[3] = (byte)'H';
        bytes[4] = (byte)'O';
        bytes[5] = (byte)'S';
        bytes[6] = (byte)'T';
        bytes[7] = (byte)0x40;
        bytes[8] = (byte)0x87;
        bytes[9] =  (byte)'S';
        bytes[10] = (byte)'U';
        bytes[11] = (byte)'S';
        bytes[12] = (byte)'H';
        bytes[13] = (byte)'E';
        bytes[14] = (byte)'E';
        bytes[15] = (byte)'L';
        bytes[16] = (byte)0x47;
        bytes[17] = (byte)'D';
        bytes[18] = (byte)'A';
        bytes[19] = (byte)'S';
        bytes[20] = (byte)'W';
        bytes[21] = (byte)'A';
        try {
            new GGEP(bytes,0,null);
            fail("need to fail");
        }
        catch (BadGGEPBlockException hopefullySo) {
        }


        // just a messed up GGEP block...
        bytes = new byte[22];
        bytes[0] = GGEP.GGEP_PREFIX_MAGIC_NUMBER;
        bytes[1] = (byte)0x05;
        bytes[2] = (byte)'B';
        bytes[3] = (byte)'H';
        bytes[4] = (byte)'O';
        bytes[5] = (byte)'S';
        bytes[6] = (byte)'T';
        bytes[7] = (byte)0x40;
        bytes[8] = (byte)'S';
        bytes[9] =  (byte)'S';
        bytes[10] = (byte)'U';
        bytes[11] = (byte)'S';
        bytes[12] = (byte)'H';
        bytes[13] = (byte)'E';
        bytes[14] = (byte)'E';
        bytes[15] = (byte)'L';
        bytes[16] = (byte)0x47;
        bytes[17] = (byte)'D';
        bytes[18] = (byte)'A';
        bytes[19] = (byte)'S';
        bytes[20] = (byte)'W';
        bytes[21] = (byte)'A';
        bytes[21] = (byte)'N';
        bytes[21] = (byte)'I';
        try {
            new GGEP(bytes,0,null);
            fail("need to fail");            
        }
        catch (BadGGEPBlockException hopefullySo) {
        }

    }

    public void testMissingMiddleValueSize() throws Exception {
    	
    	int length = 0x0003f03f;    // in the size 258111, the 6 bytes in the middle are missing 00000000 00000011 11110000 00111111
    	GGEP ggep = new GGEP(); // don't do COBS encoding
    	byte[] value = new byte[length];
    	ggep.put("TEST", value);
    	
    	// serialize it to data
    	ByteArrayOutputStream out = new ByteArrayOutputStream();
    	ggep.write(out);
    	byte[] serialized = out.toByteArray();
    	
    	// turn that back into a new GGEP block and make sure it's the same
    	GGEP ggep2 = new GGEP(serialized, 0);
    	byte[] value2 = ggep2.getBytes("TEST");
    	assertEquals(value.length, value2.length);
    }
    
    /**
     * Test if empty GGEP values can be written and read.
     */
    public void testEmptyGGEPValues() {
        GGEP ggep = new GGEP(true);
        ggep.put("EM", new byte[0]);
        
        byte[] read = ggep.get("EM");
        assertEquals(0, read.length);
    }
    
    public void testNullGGEPValues() {
        GGEP ggep = new GGEP(true);
        try {
            ggep.put("EM", (byte[])null);
            fail("should have caught IAX");
        } catch(IllegalArgumentException expected) {}
        
        assertNull(ggep.get("EM"));
        assertFalse(ggep.hasKey("EM"));
    }
    
    
    public void testCobsBoundCase() throws IOException {
        byte[] bytes = new byte[254];
        for (int i = 0; i < bytes.length; i++) bytes[i] = (byte) 7;
        byte[] after = GGEP.cobsEncode(bytes);
        assertTrue("after[0] is " + after[0], after[0] == ((byte)0xFF));
        assertEquals(256, after.length); // 2 bytes of overhead for 254 bytes
        byte[] afterOptimized = new byte[255];
        // some people leave off that last 0, we should react OK
        System.arraycopy(after, 0, afterOptimized, 0, afterOptimized.length);
        byte[] decoded = GGEP.cobsDecode(afterOptimized);
        assertEquals(254, decoded.length);
        for (int i = 0; i < bytes.length; i++)
            assertTrue(bytes[i] == decoded[i]);
    }

    public void testCobsEncodeAndDecode() throws IOException {
        for (int num = 1; num < 260; num++) 
            cobsEncodeAndDecode(num);
    }
    

    private void cobsEncodeAndDecode(int num) throws IOException {
        byte[] bytes = new byte[num];
        for (int i = 0; i < bytes.length; i++)
            bytes[i] = 0;
        byte[] after = GGEP.cobsEncode(bytes);
        assertEquals(bytes.length , (after.length-1));
        for (int i = 0; i < after.length; i++)
            assertEquals(0x01, after[i]);
        byte[] decoded = GGEP.cobsDecode(after);
        for (int i = 0; i < bytes.length; i++)
            assertEquals(bytes[i], decoded[i]);


        // test all 1s....
        for (int i = 0; i < bytes.length; i++)
            bytes[i] = 1;
        after = GGEP.cobsEncode(bytes);
        assertTrue("bytes.length = " + bytes.length + ", after.length + " +
                   after.length + ", num = " + num,
                   bytes.length == (after.length-((num / 254) + 1)));
        for (int i = 1; i < after.length; i++) 
            if (i % 255 != 0)
                assertTrue(after[i] == 0x01);
        assertTrue("after[0] = " + after[0], 
                   (ByteUtils.ubyte2int(after[0]) == (num+1)) || 
                   (ByteUtils.ubyte2int(after[0]) == 255)
                   );
        decoded = GGEP.cobsDecode(after);
        for (int i = 0; i < bytes.length; i++)
            assertEquals("num = " + num + ", i = " + i, 
                       bytes[i], decoded[i]);
        
        // ----------------------------------
        // build up 'induction' case for 0(1..).  we can trust 'induction' due
        // to nature of the COBS algorithm...

        // test 0 and 1s, specifically 0(1)^(j-1)s....
        for (int j = 2; (j < 255) && (num > 1); j++) { 
            for (int i = 0; i < bytes.length; i++) 
                if (i % j == 0)
                    bytes[i] = 0;
                else
                    bytes[i] = 1;
            after = GGEP.cobsEncode(bytes);
            assertEquals(bytes.length,(after.length-1));
            for (int i = 0; i < after.length; i++) {
                if (i == 0)
                    assertEquals(1,after[0]);
                else if ((i == 1) ||
                         ((((i-1) % j) == 0) && (num > i))
                         )
                    assertGreaterThan(1, ByteUtils.ubyte2int(after[i]));
                else
                    assertEquals(1, after[i]);
            }
            decoded = GGEP.cobsDecode(after);
            for (int i = 0; i < bytes.length; i++)
                assertEquals(bytes[i], decoded[i]);
        }
        // ----------------------------------

        // ----------------------------------
        // build up 'induction' case for (1..)0.  we can trust 'induction' due
        // to nature of the COBS algorithm...

        // test 1s and 0, specifically (1)^(j-1s)0....
        for (int j = 2; j < 255; j++) {
            for (int i = 0; i < bytes.length; i++) 
                if (i % j == 0)
                    bytes[i] = 1;
                else
                    bytes[i] = 0;
            after = GGEP.cobsEncode(bytes);
            assertEquals(bytes.length ,(after.length - 1));
            for (int i = 0; i < bytes.length; i++)
                if ((i == 0) ||
                    (i % j == 0)
                    )
                    assertGreaterThan(1, ByteUtils.ubyte2int(after[i]));
                else
                    assertEquals(1, after[i]);
            decoded = GGEP.cobsDecode(after);
            for (int i = 0; i < bytes.length; i++)
                assertEquals(bytes[i] , decoded[i]);
        }
        // ----------------------------------
    }

    public void testCobsSymmetry() throws IOException {
        // a quick test for symmetry - but symmetry was actually tested above,
        // so no need for much testing...
        byte[] bytes = (new String("Sush Is Cool!")).getBytes();
        byte[] after = GGEP.cobsDecode(GGEP.cobsEncode(bytes));
        assertEquals(bytes.length , after.length);
    }


    public void testCobsBadCOBSBlock() throws Exception {
        byte[] badBlock = new byte[10];
        badBlock[0] = (byte) 11;
        for (int i = 1; i < badBlock.length; i++)
            badBlock[i] = (byte)1;
        try {
            GGEP.cobsDecode(badBlock);
            assertTrue(false);
        }
        catch (IOException expected) {}

        badBlock = new byte[10];
        badBlock[0] = (byte) 10;
        for (int i = 1; i < badBlock.length; i++)
            badBlock[i] = (byte)1;
        GGEP.cobsDecode(badBlock);

        badBlock = new byte[4];
        badBlock[0] = (byte) 2;
        badBlock[1] = (byte) 1;
        badBlock[2] = (byte) 1;
        badBlock[3] = (byte) 2;
        try {
            GGEP.cobsDecode(badBlock);
            assertTrue(false);
        }
        catch (IOException expected) {
        }

    }
    

    public static void main(String argv[]) {
        junit.textui.TestRunner.run(suite());
    }
}
