views:

233

answers:

4

I have a C structure that is sent over some intermediate networks and gets received over a serial link by a java code. The Java code gives me a byte array that I now want to repackage it as the original structure. Now if the receive code was in C, this was simple. Is there any simple way to repackage a byte[] in java to a C struct. I have minimal experience in java but this doesnt appear to be a common problem or solved in any FAQ that I could find.

FYI the C struct is

struct data {
     uint8_t        moteID;
    uint8_t     status; //block or not
    uint16_t   tc_1;
    uint16_t   tc_2;
    uint16_t   panelTemp;  //board temp
    uint16_t   epoch#;
    uint16_t   count;    //pkt seq since the start of epoch
    uint16_t   TEG_v;   
    int16_t   TEG_c;    
 }data;
+1  A: 

Assuming you have control over both ends of the link, rather than sending raw data you might be better off going for an encoding that C and Java can both use. Look at either JSON or Protocol Buffers.

Andrew
Yeah, because even in C this would have been very dangerous depending on how one end chose to pack those bytes. At a minimum, you should be sending the numbers in a known byte ordering one element at a time so that you can read them back the same way.
PSpeed
It seems like JSON might introduce a lot of bandwidth overhead for this struct.
Crashworks
@PSpeed that's what network byte order is for.@Crashworks Probably would. Protocol buffers would probably do a better job from what I know about it.
Andrew
@Andrew, indeed, but if you are sending a raw struct from C you are getting a dump of whatever is in memory. You have to have specifically sent the individual elements for byte ordering to come into play. As it is, even other C programs would have trouble reading his struct unless they were built on a similar platform with the exact same optimizations. For example, it's quite possible that all of those 8 bit ints are word aligned. A memory dump does not a protocol make.
PSpeed
@PSpeed very true.
Andrew
+2  A: 

This can be difficult to do when sending from C to C.

If you have a data struct, cast it so that you end up with an array of bytes/chars and then you just blindly send it you can sometimes end up with big problems decoding it on the other end. This is because sometimes the compiler has decided to optimize the way that the data is packed in the struct, so in raw bytes it may not look exactly how you expect it would look based on how you code it. It really depends on the compiler!

There are compiler pragma's you can use to make packing unoptimized. See C/C++ Preprocessor Reference - pack

The other problem is the 32/64-bit bit problem if you just use "int", and "long" without specifying the number of bytes... but you have done that :-)

Unfortunately, Java doesnt really have structs... but it represents the same information in classes. What I recommend is that you make a class that consists of your variables, and just make a custom unpacking function that will pull the bytes out from the received packet (after you have checked its correctness after transfer) and then load them in to the class.

e.g. You have a data class like

class Data
{
    public int        moteID;
    public int        status; //block or not
    public int        tc_1;
    public int        tc_2;
}

Then when you receive a byte array, you can do something like this

Data convertBytesToData(byte[] dataToConvert)
{
    Data d = Data();
    d.moteId = (int)dataToConvert[0];
    d.status = (int)dataToConvert[1];
    d.tc_1 = ((int)dataToConvert[2] << 8) + dataTocConvert[3];  // unpacking 16-bits
    d.tc_2 = ((int)dataToConvert[4] << 8) + dataTocConvert[5];  // unpacking 16-bits
}

I might have the 16-bit unpacking the wrong way around, it depends on the endian of your C system, but you'll be able to play around and see if its right or not. I havent played with Java for sometime, but hopefully there might be byte[] to int functions built in these days. I know there are for C# anyway.

With all this in mind, if you are not doing high data rate transfers, definately look at JSON and Protocol Buffers!

Fuzz
Thanks everyone, I had actually already made sure everything is transmitted in network order. Since I dont want to dabble too much in Java, the above solution of packing into a Java class seems to be the best solution.
intiha
@intiha, ah, we were led to believe you were writing the C struct out as a memory dump of its bytes. If you are actually writing out each individual member (and thus able to correctly control byte ordering) then it's an easier (and solvable) problem.
PSpeed
A: 

What you are trying to do is problematic for a couple of reasons:

  • Different C implementations will represent uint16_t (and int16_t) values in different ways. In some cases, the most significant byte will be first when the struct is laid out in memory. In other cases, the least significant byte will.

  • Different C compilers may pack the fields of the struct differently. So it is possible (for example) that the fields have been reordered or padding may have been added.

So what this all means is that you have to figure out exactly the struct is laid out ... and just hope that this doesn't change when / if you change C compilers or C target platform.

Having said that, I could not find a Java library for decoding arbitrary binary data streams that allows you to select "endian-ness". The DataInputStream and DataOutputStream classes may be the answer, but they are explicitly defined to send/expect the high order byte first. If your data comes the other way around you will need to do some Java bit bashing to fix it.

EDIT : actually (as @Kevin Brock points out) java.nio.ByteBuffer allows you to specify the endian-ness when fetching various data types from a binary buffer.

Stephen C
You can use `java.nio.ByteBuffer` which supports different byte orders and allows you to retrieve specific types.
Kevin Brock
+2  A: 

I would recommend that you send the numbers across the wire in network byte order all the time. This eliminates the problems of:

  1. Compiler specific word boundary generation for your structure.
  2. Byte order specific to your hardware (both sending and receiving).

Also, Java's numbers are always stored in network-byte-order no matter the platform that you run Java upon (the JVM spec requires a specific byte order).

A very good class for extracting bits from a stream is java.nio.ByteBuffer, which can wrap arbitrary byte arrays; not just those coming from a I/O class in java.nio. You really should not hand code your own extraction of primitive values if at all possible (i.e. bit shifting and so forth) since it is easy to get this wrong, the code is the same for every instance of the same type, and there are plenty of standard classes that provide this for you.

For example:

public class Data {

    private byte moteId;
    private byte status;
    private short tc_1;
    private short tc_2;
    //...etc...
    private int tc_2_as_int;

    private Data() {
        // empty
    }

    public static Data createFromBytes(byte[] bytes) throws IOException {
        final Data data = new Data();
        final ByteBuffer buf = ByteBuffer.wrap(bytes);

        // If needed...
        //buf.order(ByteOrder.LITTLE_ENDIAN);

        data.moteId = buf.get();
        data.status = buf.get();
        data.tc_1 = buf.getShort();
        data.tc_2 = buf.getShort();
        // ...extract other fields here

        // Example to convert unsigned short to a positive int
        data.tc_2_as_int = buf.getShort() & 0xffff;

        return data;        
    }

}

Now, to create one, just call Data.createFromBytes(byteArray).

Note that Java does not have unsigned integer variables, but these will be retrieved with the exact same bit pattern. So anything where the high-order bit is not set will be exactly the same when used. You will need to deal with the high-order bit if you expected that in your unsigned numbers. Sometimes this means storing the value in the next larger integer type (byte -> short; short -> int; int -> long).

Edit: Updated the example to show how to convert a short (16-bit signed) to an int (32-bit signed) with the unsigned value with tc_2_as_int.

Note also that if you cannot change the byte-order and it is not in network order, then java.nio.ByteBuffer can still serve you here with buf.order(ByteOrder.LITTLE_ENDIAN); before retrieving the values.

Kevin Brock
nice, I knew a java person would be able to show how to do what I was trying to explain by using built in library functions :-)
Fuzz
I actually going for this solution now. Helps solve many problem. Thanks.
intiha