Search code examples
javaendiannesspreon

Preon unable to encode/decode little endian


Background

I am attempting to use the preon library to encode/decode binary data. My 1 byte structure works fine when I configure the byte order for big endian, but fails for little endian. It appears that the endianess effects the intra-byte data in preon. It appears this question touches on the same issue, but does not resolve the little endian aspect.


I have attached a simple case demonstrating the issue.

Is there a defect in PreonLittleEndianNumber.java or does preon simply not work when little endian byte order is specified?


PreonTest.java

package me;

import org.codehaus.preon.Codec;
import org.codehaus.preon.Codecs;
import org.codehaus.preon.DecodingException;
import org.junit.Test;

import java.io.IOException;

import org.junit.Assert;

public class PreonTest {

    @Test
    public void bigEndianDecodeEncodeTest() {

        byte[] testByte = {(byte) 0xDE};
        Codec<PreonBigEndianNumber> bigCodec = Codecs.create(PreonBigEndianNumber.class);

        try {

            // Big Endian Decode/Encode Test
            PreonBigEndianNumber pben = Codecs.decode(bigCodec, testByte);
            byte[] testByte1 = Codecs.encode(pben, bigCodec);
            Assert.assertArrayEquals(testByte, testByte1);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (DecodingException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void bigEndianEncodeDecodeTest() {

        PreonBigEndianNumber pben = new PreonBigEndianNumber((byte)0xDE);
        Codec<PreonBigEndianNumber> bigCodec = Codecs.create(PreonBigEndianNumber.class);

        try {

            byte[] testByte1 = Codecs.encode(pben, bigCodec);
            PreonBigEndianNumber pben2 = Codecs.decode(bigCodec, testByte1);
            Assert.assertEquals(pben, pben2);

        } catch (IOException e) {
            e.printStackTrace();
        } catch (DecodingException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void littleEndianDecodeEncodeTest() {

        byte[] testByte = {(byte) 0xDE};
        Codec<PreonLittleEndianNumber> littleCodec = Codecs.create(PreonLittleEndianNumber.class);

        try {

            // Big Endian Decode/Encode Test
            PreonLittleEndianNumber plen = Codecs.decode(littleCodec, testByte);
            byte[] testByte1 = Codecs.encode(plen, littleCodec);
            Assert.assertArrayEquals(testByte, testByte1);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (DecodingException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void littleEndianEncodeDecodeTest() {

        PreonLittleEndianNumber plen = new PreonLittleEndianNumber((byte)0xDE);
        Codec<PreonLittleEndianNumber> littleCodec = Codecs.create(PreonLittleEndianNumber.class);

        try {

            byte[] testByte1 = Codecs.encode(plen, littleCodec);
            PreonLittleEndianNumber plen2 = Codecs.decode(littleCodec, testByte1);
            Assert.assertEquals(plen, plen2);

        } catch (IOException e) {
            e.printStackTrace();
        } catch (DecodingException e) {
            e.printStackTrace();
        }
    }
}

PreonBigEndianNumber.java

package me;

import org.codehaus.preon.annotation.BoundNumber;
import org.codehaus.preon.buffer.ByteOrder;

public class PreonBigEndianNumber {
    @BoundNumber(size="1", byteOrder=ByteOrder.BigEndian) public byte bit7;
    @BoundNumber(size="1", byteOrder=ByteOrder.BigEndian) public byte bit6;
    @BoundNumber(size="1", byteOrder=ByteOrder.BigEndian) public byte bit5;
    @BoundNumber(size="1", byteOrder=ByteOrder.BigEndian) public byte bit4;
    @BoundNumber(size="1", byteOrder=ByteOrder.BigEndian) public byte bit3;
    @BoundNumber(size="1", byteOrder=ByteOrder.BigEndian) public byte bit2;
    @BoundNumber(size="1", byteOrder=ByteOrder.BigEndian) public byte bit1;
    @BoundNumber(size="1", byteOrder=ByteOrder.BigEndian) public byte bit0;

    /**
     * Default constructor
     */
    public PreonBigEndianNumber() {

    }

    public PreonBigEndianNumber(byte value) {
        bit7 = (byte) ((0b10000000 & value) >>> 7);
        bit6 = (byte) ((0b01000000 & value) >>> 6);
        bit5 = (byte) ((0b00100000 & value) >>> 5);
        bit4 = (byte) ((0b00010000 & value) >>> 4);
        bit3 = (byte) ((0b00001000 & value) >>> 3);
        bit2 = (byte) ((0b00000100 & value) >>> 2);
        bit1 = (byte) ((0b00000010 & value) >>> 1);
        bit0 = (byte) ((0b00000001 & value) >>> 0);
    }

    public byte getByte() {
        // Pack bits back into an int
        int b = 0;
        int shift = 0;
        for (int i=7;i>=0;i--) {
            b = (b << shift);
            if      (i==0) {b += bit0;}
            else if (i==1) {b += bit1;}
            else if (i==2) {b += bit2;}
            else if (i==3) {b += bit3;}
            else if (i==4) {b += bit4;}
            else if (i==5) {b += bit5;}
            else if (i==6) {b += bit6;}
            else if (i==7) {b += bit7;}
            shift = 1;
        }
        return (byte) b;
    }

    @Override
    public String toString() {
        return  bit7 +
                bit6 +
                bit5 +
                bit4 +
                " " +
                bit3 +
                bit2 +
                bit1 +
                bit0;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {return true;}
        if (o == null || getClass() != o.getClass()) {return false;}

        PreonBigEndianNumber preonBits = (PreonBigEndianNumber) o;
        if (bit7 != preonBits.bit7) {return false;}
        if (bit6 != preonBits.bit6) {return false;}
        if (bit5 != preonBits.bit5) {return false;}
        if (bit4 != preonBits.bit4) {return false;}
        if (bit3 != preonBits.bit3) {return false;}
        if (bit2 != preonBits.bit2) {return false;}
        if (bit1 != preonBits.bit1) {return false;}
        return bit0 == preonBits.bit0;
    }

    @Override
    public int hashCode() {
        int result = (int) bit7;
        result = 31 * result + (int) bit6;
        result = 31 * result + (int) bit5;
        result = 31 * result + (int) bit4;
        result = 31 * result + (int) bit3;
        result = 31 * result + (int) bit2;
        result = 31 * result + (int) bit1;
        result = 31 * result + (int) bit0;
        return result;
    }
}

PreonLittleEndianNumber.java

package me;

import org.codehaus.preon.annotation.BoundNumber;
import org.codehaus.preon.buffer.ByteOrder;

public class PreonLittleEndianNumber {
    @BoundNumber(size="1", byteOrder=ByteOrder.LittleEndian) public byte bit7;
    @BoundNumber(size="1", byteOrder=ByteOrder.LittleEndian) public byte bit6;
    @BoundNumber(size="1", byteOrder=ByteOrder.LittleEndian) public byte bit5;
    @BoundNumber(size="1", byteOrder=ByteOrder.LittleEndian) public byte bit4;
    @BoundNumber(size="1", byteOrder=ByteOrder.LittleEndian) public byte bit3;
    @BoundNumber(size="1", byteOrder=ByteOrder.LittleEndian) public byte bit2;
    @BoundNumber(size="1", byteOrder=ByteOrder.LittleEndian) public byte bit1;
    @BoundNumber(size="1", byteOrder=ByteOrder.LittleEndian) public byte bit0;

    /**
     * Default constructor
     */
    public PreonLittleEndianNumber() {

    }

    public PreonLittleEndianNumber(byte value) {
        bit7 = (byte) ((0b10000000 & value) >>> 7);
        bit6 = (byte) ((0b01000000 & value) >>> 6);
        bit5 = (byte) ((0b00100000 & value) >>> 5);
        bit4 = (byte) ((0b00010000 & value) >>> 4);
        bit3 = (byte) ((0b00001000 & value) >>> 3);
        bit2 = (byte) ((0b00000100 & value) >>> 2);
        bit1 = (byte) ((0b00000010 & value) >>> 1);
        bit0 = (byte) ((0b00000001 & value) >>> 0);
    }

    public byte getByte() {
        // Pack bits back into an int
        int b = 0;
        int shift = 0;
        for (int i=7;i>=0;i--) {
            b = (b << shift);
            if      (i==0) {b += bit0;}
            else if (i==1) {b += bit1;}
            else if (i==2) {b += bit2;}
            else if (i==3) {b += bit3;}
            else if (i==4) {b += bit4;}
            else if (i==5) {b += bit5;}
            else if (i==6) {b += bit6;}
            else if (i==7) {b += bit7;}
            shift = 1;
        }
        return (byte) b;
    }

    @Override
    public String toString() {
        return  bit7 +
                bit6 +
                bit5 +
                bit4 +
                " " +
                bit3 +
                bit2 +
                bit1 +
                bit0;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {return true;}
        if (o == null || getClass() != o.getClass()) {return false;}

        PreonLittleEndianNumber preonBits = (PreonLittleEndianNumber) o;
        if (bit7 != preonBits.bit7) {return false;}
        if (bit6 != preonBits.bit6) {return false;}
        if (bit5 != preonBits.bit5) {return false;}
        if (bit4 != preonBits.bit4) {return false;}
        if (bit3 != preonBits.bit3) {return false;}
        if (bit2 != preonBits.bit2) {return false;}
        if (bit1 != preonBits.bit1) {return false;}
        return bit0 == preonBits.bit0;
    }

    @Override
    public int hashCode() {
        int result = (int) bit7;
        result = 31 * result + (int) bit6;
        result = 31 * result + (int) bit5;
        result = 31 * result + (int) bit4;
        result = 31 * result + (int) bit3;
        result = 31 * result + (int) bit2;
        result = 31 * result + (int) bit1;
        result = 31 * result + (int) bit0;
        return result;
    }
}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<groupId>me</groupId>
<artifactId>parser</artifactId>
<version>1.0</version>

<repositories>
    <repository>
        <id>atlassian-repo</id>
        <name>Atlassian Repository</name>
        <url>https://maven.atlassian.com/repository/public</url>
    </repository>
    <repository>
        <id>limbo-repository</id>
        <url>http://limbo.sourceforge.net/repository</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
        <releases>
            <enabled>true</enabled>
        </releases>
    </repository>
    <repository>
        <id>pecia-repository</id>
        <url>http://pecia.sourceforge.net/repository</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
        <releases>
            <enabled>true</enabled>
        </releases>
    </repository>
</repositories>

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.codehaus.preon</groupId>
        <artifactId>preon-binding</artifactId>
        <version>1.1-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>org.codehaus.preon</groupId>
        <artifactId>preon-el</artifactId>
        <version>1.1-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>org.codehaus.preon</groupId>
        <artifactId>preon-emitter</artifactId>
        <version>1.1-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>org.codehaus.preon</groupId>
        <artifactId>preon-io</artifactId>
        <version>1.1-SNAPSHOT</version>
    </dependency>
</dependencies>


Solution

  • I experienced the same issue. It appears that the decoding for ByteOrder.BigEndian and ByteOrder.LittleEndian is the different. However, the encoding is the same. That means when you use ByteOrder.LittleEndian the round trip between encode and decode will be incorrect.

    The decoding for ByteOrder.BigEndianis using MSB 0 bit numbering and the decoding for ByteOrder.LittleEndian is using LSB 0 bit numbering decoding. With this in mind, I believe that the encoder for ByteOrder.LittleEndianwas broken.

    I forked the current repository of preon (https://github.com/jdl17/preon/) and made the necessary changes. Along with these changes, I found that I needed to incorporate the flush fix that was originally submitted by mrumpf in a pull request (https://github.com/preon/preon/pull/25). I extended it further to allow for specification on endianness on the flush.

    Beyond the aforementioned changes, the fork also includes changes that implement the encode and decode methods for the ListCodecFactory and encode method for the SelectFromCode. Finally, I updated ANTLR from 3.3 to 3.5.2 so it would work with Java 8.

    All this has been submitted as a pull request back into the preon (https://github.com/preon/preon/pull/35).

    I should also comment that I added routines to properly encode booleans in little endian and big endian. However, the current implementation of Bound does not allow for for a byteOrder annotation is currently hardcoded to ByteOrder.BigEndian. This was outside of the scope of my work and I didn't need to use booleans for my decoding and encoding. However, be advised of this if you plan to use boolean in a ByteOrder.LittleEndian encoding.

    Hopefully this will resolve your issue.

    Beyond that, the pull request implements the encode and decode methods for the ListCodecFactory and the encode method for the SelectFromCode.