Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to send by socket C++ compatible struct from Java?

Suppose there is already written, unchangeable host program, that receives such C++ struct by socket:

#pragma pack(push, 2)
struct Data {
  double x;
  double y;
  double z;
  long frameNumber;
};
#pragma pack(pop) 

platform: C++ / 32-bit Windows Application compiled in Visual Studio 2008

How to send such data from Java Socket? I have attemted to fill ByteBuffer with putDouble(), and putLong(), also putInt() assuming long as 32-bit, but i cannot produce valid values.

I have also generated and sended data randomly, and structure-level bytes assignment looks fine(I can randomize only one value, for example X), but I cannot produce exact value (propably different double representation?), but only random stuff by sending Math.random() * Double.MAX_VALUE;

Can I use Google Protocol Buffers on only one side (client producing data) to solve this problem?

remember, I cannot change the server (receiving) side

I know that I can move sending data to C++ and use JNI, but I'm searching for simpler solution.

I see at least 2 possible problems here:

  • double representation at byte-level (like 01234567 and 76543210 on other side)
  • bits representation in one byte (like 00000001 and 1000000 on other side)

What those things look in Java in C++?

In several hours I will provide exact sample data for "byte-hacking" (exact Java value before send and value of received double in C++)

About server bad-design answering

I know that such implemented and unchangeable server is bad software, but the environment in this problem is to inject some data to small old application at "hobbistic-level"

Java representation

Maybe this would help some bits-level C++ expert:

Long.toBinaryString( Double.doubleToRawLongBits( (double) 0 ) );
// 000000000000000000000000000000000000000000000000000000000000000

Long.toBinaryString( Double.doubleToRawLongBits( 1 ) );
// 011111111110000000000000000000000000000000000000000000000000000

Long.toBinaryString( Double.doubleToRawLongBits( 1.1 ) );
// 011111111110001100110011001100110011001100110011001100110011010

Long.toBinaryString( Double.doubleToRawLongBits( 1024 ) );
// 100000010010000000000000000000000000000000000000000000000000000

Long.toBinaryString( Double.doubleToRawLongBits( 1024.1024 ) );
// 100000010010000000000000110100011011011100010111010110001110001

Long.toBinaryString( Double.doubleToRawLongBits( Double.MAX_VALUE ) );
// 111111111101111111111111111111111111111111111111111111111111111
like image 559
Piotr Müller Avatar asked May 13 '13 08:05

Piotr Müller


3 Answers

Suppose there is already written, unchangeable host program, that receives such C++ struct by socket

Then you already have a major problem which some unknown person has kindly left you as a legacy. You now have to produce Java code to conform to a specific C++ compiler's conception of the wire format of a specific struct, which depends on:

  • the hardware
  • the compiler vendor
  • the compiler version
  • the compilation options
  • the surrounding #pragmas
  • ...

I strongly suggest to you that you take the opportunity to fix the whole megillah and define a proper wire format for the transaction.

Once you've done that you can emulate it easily enough with the facilities of DataOutputStream, or if the problem is even worse and endian-ness is involved, NIO plus ByteBuffer.

like image 125
user207421 Avatar answered Oct 19 '22 01:10

user207421


I figured it out. Int value is sent fine (propably, can't check this 100% sure in host program), the problem is with doubles endian. You need to convert doubles before send:

public static double changeEndian( double x ) {
      ByteBuffer cv = ByteBuffer.allocate( 8 ).putDouble( x );
      cv.order( ByteOrder.LITTLE_ENDIAN );
      cv.rewind();
      return cv.getDouble();
}

And use ByteBufer with putDouble()/putInt().

like image 22
Piotr Müller Avatar answered Oct 19 '22 01:10

Piotr Müller


You'll need to verify the encoding used for double (usually an IEEE standard, but it doesn't have to be) as well as long (big vs little endian) on the platform with the server. You'll also need to identify any padding involved as well.

Then you'll need to manually construct the binary values appropriate to send if they aren't java native encodings. This can be non-trivial, especially for the floating point values, if you are on a pretty messed up server platform.

Also, track down the person who decided to use an implicit binary protocol for the server you can't change and use rubber-hose cryptanalysis on them:

http://en.wikipedia.org/wiki/Rubber-hose_cryptanalysis

There's no easy way to do it, without knowing all the details about the server's platform.

Java has a standard representation for floats and ints across all platforms, but c++ does not define those things. It is platform/architecture specific for c++.

like image 30
xaxxon Avatar answered Oct 19 '22 03:10

xaxxon