1
votes

new to socket programming in java!. i am trying my level best to understand java socket programming, and how to decode 'C' strut like packets.

The client is python which sends packet between tags, example Payload : <tag1> data packet 1</tag1><tag2>data packet 2</tag2>

Have a c code to which received the payload and decode the payload with structure , all the tags are of 30 bytes, data packet 1, 2,.. n will be of different structure

I am fairly new to java program, after searching through the web, i could write a socket server which receives payload from client, now facing problem in decoding the data packets. I could display the bytes. so Used Arrays.copyOfRange method to get data.

The packet data are something represented as below,

typedef struct {

  unsigned char     dataType;
  unsigned char     value[4];

} GEN_VAL

typedef struct {

  unsigned char     dataType;
  unsigned char     numBytes;
  unsigned char     value[32];

} GEN_STR_32;

typedef struct {

    char           startTag[32];
    GEN_STR_32     c_code;
    GEN_STR_32     c_name;
    GEN_STR_32     c_info;

    GEN_VAL        i_type;
    GEN_VAL        i_count1;
    GEN_VAL        i_count2;
    char           endTag[32];

} DATA_PACKET_1;

I receive buffer with following code snippet

  DataInputStream inStream = new DataInputStream
                        (new BufferedInputStream(socket.getInputStream()));

 while ( ( noOfBytes = (int)inStream.read(recvBuff)) != -1)
{
 cDecode mydecode = new cDecode();
 mydecode.decodePacket(noOfBytes, recvBuff);
}

cDecode.java

public void decodePacket(int TotalBytes, byte[] BuffRecv) 
{
    byte[] GetTag = new byte[32];
    byte[] GEN_STR_32 = new byte[32];
    byte[] GEN_INT_4  = new byte[4];

     GetTag = Arrays.copyOfRange(BuffRecv,0,31);
     String TagStr = new String(GetTag).trim();  //get start tag 

     int startloc = 31;
     int offset = startloc + 32;
     GEN_STR_32 = Arrays.copyOfRange(Buff,startloc,offset);
     String cCode = new String(GEN_STR_32).trim();  // get value of cCode
     System.out.println("Code   :" + cCode );

     startloc = offset;
     offset = startloc + 32;
     GEN_STR_32 = Arrays.copyOfRange(Buff,startloc,offset);
     String cName = new String(GEN_STR_32).trim(); //get value of cName
     System.out.println("Name:" + cName );

     startloc = offset;
     offset = startloc + 32;
     GEN_STR_32 = Arrays.copyOfRange(Buff,startloc,offset);
     String cInfo = new String(GEN_STR_32).trim(); //get value of cName
     System.out.println("Info:" + cInfo );

     startloc = offset;
     offset = offset + 4;
     ByteBuffer iTypeByteBuff = ByteBuffer.wrap(GEN_DATA_INT_4);
     int iType= iTypeByteBuff .getInt();
     System.out.println("type :" + iType);

     startloc = offset;
     offset = offset + 4;
     // likewise used ByteBuffer for remaining integer data 
     // for receiving end tag, offset is added with 32!
}

first 3 data is string value and
second 3 data have integer value

String value is displayed correctly.
finding problem in converting byte to integer, not sure whether i am using correct value for startloc and offset in Arrays.copyOfRange method.

I tried this based on the information i got from net.

I also read about having a separate class without methods for all data structures. but i cant find any complete example, as 'sizeof' is not available in java.

can some one guide me the correct way to decode packets in this scenario.

2

2 Answers

1
votes

For byte arrays or input/output you can use a ByteBuffer.

byte[] bytes = ...
ByteBuffer buf = ByteBuffer.wrap(bytes);
buf.order(ByteOrder.LITTLE_ENDIAN); // Intel byte order.
short sh = buf.getShort(sh); // Java short = 2B
int unsignedSh = buf.getShort() & 0xFFFF; // Unsigned short emulation


ByteBuffer buf = ByteBuffer.allocate(4);
buf.order(ByteOrder.LITTLE_ENDIAN);
buf.putShort(sh); // Java short = 2B
buf.putShort((short) unsignedSh);

There also exists a binary format standard ASN, to work more grammar like, but in this case the above will do.

One problem on the C side is field alignment and platform portability. One could use macros to transform a struct into a fully specified binary data structure building call.


After comments, using a DataInputStream

You did not seem to use the data specific read calls for DataInputStream.

enum DataType {
    X0,
    GEN_VAL,
    X2,
    X3,
    GEN_STR_32,
    ...
}

class GenStr32 {
     final DataType dataType = DataType.GEN_STR_32;
     int numbytes; // 0..255
     String value; // 32 bytes incl. '\0' in C
}

void readAnyTyped(DataInputStream in) {
    int dataTypeIx = in.readByte() & 0xFF;
    DataType dataType = DataType.values()[dataTypeIx];
    switch (dataType) {
    case GEN_STR_32:
        GenStr32 data = new GenStr32();
        data.numbytes = in.readByte();
        byte[] bytes = new byte[data.numbytes]; // or 32?
        bytes = in.readFully();
        int length = 0;
        while (length < bytes.length && bytes[length] != 0) {
            ++length;
        }
        data.value = new String(bytes, 0, length,
            StandardCharsets.ISO_8859_1);
        process(data);
        break;
    }
}

DataInputStream might be more direct. ByteBuffer has the advantage of the specifiable byte order, as java defaults to BIG_ENDIAN.

0
votes

If you think of the data as a stream to be read as a data type, you could extend DataInputStream to create a PacketInputStream that has a readDataPacket() method. Define a DataPacket class to hold the data from your C DATA_PACKET_1 struct.

First the classes to hold your data:

public class GenValue {
    private final byte dataType;
    private final byte[] value;

    public GenVal(byte dataType, byte[] value) {
        this.dataType = dataType;
        this.value = value;
    }

    public byte getDataType() {
        return dataType;
    }

    public byte[] getValue() {
        return value;
    }
}

public class DataPacket {
    private final String startTag;
    private final String code;
    private final String name;
    private final String info;
    private final GenValue type;
    private final GenValue count1;
    private final GenValue count2;
    private final String endTag;

    public DataPacket(String startTag, other args here...) {
        this.startTag = startTag;
        // Set the other properties from constructor args...
    }

    public String getStartTag() {
        return startTag;
    }

    // Add getters for the other properties...
}

And the DataInputStream implementation to decode your packets:

public class PacketInputStream extends DataInputStream {
    public DataPacket readDataPacket() throws IOException {
        String startTag = readGenStr32();
        String code = readGenStr32();

        // Do the same for name and info...
        ...

        GenValue type = readGenValue();
        GenValue count1 = readGenValue();
        GenValue count2 = readGenValue();

        // Read the endTag the same as the Strings above...
        ...

        return new DataPacket(startTag, code, name, info, type, count1, count2, endTag);
    }

    public String readGenStr32() throws IOException {
        byte[] strBuf = new byte[32];
        readFully(strBuf, 0, 32);
        return new String(strBuf).trim();
    }

    public GenValue readGenValue() throws IOException {
        byte dataType = readByte();
        byte[] value = new byte[4];
        readFully(value);
        return new GenValue(dataType, value);
    }
}

Calling code would do something like:

PacketInputStream in = new PacketInputStream((new BufferedInputStream(socket.getInputStream()));
DataPacket p = in.readDataPacket();
System.out.println("Start tag: " + p.getStartTag());
System.out.println("Code: " + p.getCode());
// Print other values of interest...

I would just override DataPacket.toString() and then do System.out.println(p), but there's enough code here already. Also, I would store primitive types instead of using a GenValue class, but I don't know enough about your actual data to work that out.