I have a very strange situation. I connect my Java software with a device, let´s call it "Black Box" (because I cannot look into it or make traces within it). I am adressing a specific port (5550) and send commands as byte sequences on a socket. As a result, I get an answer from the Black Box on the same socket.
Both my commands and the replies are prefixed in a pre-defined way (according to the API) and have an XOR checksum.
When I run the code from Windows, all is fine: Command 1 gets its Reply 1 and Command 2 gets its Reply 2.
When I run the code from Android (which is actually my target - Windows came into play to track down the error) it gets STRANGE: Command 1 gets its Reply 1 but Command 2 does not. When I play with Command 2 (change the prefix illegally, violate the checksum) the Black Box reacts as expected (with an error reply). But with the correct Command 2 being issued from Android, the Reply is totally mis-formed: Wrong prefix and missing checksum.
In the try to analyse the error I tried WireShark and this shows that on the network interface, the Black Box is sending the RIGHT Reply 2, but evaluating this reply in Java from the socket, it is wrong. How can this be when all is fine for Command/Reply 1???
Strange is, that parts of the expected data are present:
Expected: ff fe e4 04 00 11 00 f1
Received: fd fd fd 04 00 11 00 // byte 8 missing
I am attaching the minimalistic code to force the problem. What could falsify the bytes which I receive? Is there a "raw" access in Java to the socket which could reveal the problem?
I am totally confused so any help would be appreciated:
String address = "192.168.1.10";
int port = 5550;
Socket socket;
OutputStream out;
BufferedReader in;
try {
socket = new Socket(address, port);
out = socket.getOutputStream();
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// This is "Command 1" which is receiving the right reply
// byte[] allesAn = new byte[] {(byte)0xff, (byte)0xfe, (byte)0x21, (byte)0x81, (byte)0xa0};
// out.write(allesAn);
// This is "Command 2" which will not receive a right reply
byte[] getLokInfo3 = new byte[] {(byte)0xff, (byte)0xfe, (byte)0xe3, (byte)0, (byte)0, (byte)3, (byte)0xe0};
out.write(getLokInfo3);
out.flush();
while (true) {
String received = "";
final int BufSize = 1000;
char[] buffer = new char[BufSize];
int charsRead = 0;
charsRead = in.read(buffer, 0, BufSize);
// Convert to hex presentation
for (int i=0; i < charsRead; i++) {
byte b = (byte)buffer[i];
received += hexByte((b + 256) % 256) + " ";
}
String result = charsRead + ">" + received + "<";
Log.e("X", "Read: " + result);
}
} catch (Exception e) {
Log.e("X", e.getMessage() + "");
}
with
private static String hexByte(int value) {
String s = Integer.toHexString(value);
return s.length() % 2 == 0 ? s : "0" + s;
}
Here is what wireshark says, showing the expected 8 bytes:
Related
My app sends small lines of text (commands to the server) or 32 bytes chunks of voice data. Right now I'm just using the socket's OutputStream's write. However, the problem is that Android Java seems to like to send the first byte by itself. Example:
Send: "Call Iron Man"
Received: "C", "all Iron Man"
To work around this splitting I prefix each line with a "throw away" character #. So the previous example is sent as:
Send: "#Call Iron Man"
Received: "#", "Call Iron Man" --> "Call Iron Man" will be used and "#" is ignored.
The problem becomes when I want to send 32 bytes of voice, it is sent as one packet of 1 byte, and then one packet of 31 byte. These 1 byte packets waste a lot of data because of the TCP/IP overhead. According to Sizing Source that means I will use (64+1) + (64+31) = 160 bytes for my 32 byte voice chunk when I could be using 64+32 = 96. That means I will be using 1.67X more LTE data than I should which (in Canada) will cost me a very pretty penny.
Is there a way to force all 32 bytes to be sent as one packet?
Here is the Android code for sending:
int totalRead = 0, dataRead;
while (totalRead < WAVBUFFERSIZE)
{//although unlikely to be necessary, buffer the mic input
dataRead = wavRecorder.read(wavbuffer, totalRead, WAVBUFFERSIZE - totalRead);
totalRead = totalRead + dataRead;
}
int encodeLength = AmrEncoder.encode(AmrEncoder.Mode.MR122.ordinal(), wavbuffer, amrbuffer);
try
{
Vars.mediaSocket.getOutputStream().write(amrbuffer, 0, encodeLength);
}
catch (Exception e)
{
Utils.logcat(Const.LOGE, encTag, "Cannot send amr out the media socket");
}
Here is the C/C++ code for receiving:
mediaRead = 0; //to know how much media is ACTUALLY received. don't always assume MAXMEDIA amount was received
bzero(bufferMedia, MAXMEDIA+1);
alarm(ALARMTIMEOUT);
do
{//wait for the media chunk to come in first before doing something
returnValue = SSL_read(sdssl, bufferMedia, MAXMEDIA-mediaRead);
if(returnValue > 0)
{
mediaRead = mediaRead + returnValue;
}
int sslerr = SSL_get_error(sdssl, returnValue);
switch (sslerr)
{
case SSL_ERROR_NONE:
waiting = false;
eventful = true; //an ssl operation completed this round, something did happen
break;
//other cases when necessary. right now only no error signals a successful read
}
} while(waiting && SSL_pending(sdssl));
alarm(0);
if(alarmKilled)
{
alarmKilled = false;
cout << "Alarm killed SSL read of media socket\n";
}
where MAXMEDIA = 1024 so there is definetly enough place for the 32 bytes of voice data
I have the Java server that receives the RTMP packets that are sent from client app. The server reads the packet header using InputStream, recognizes how big the packet body is, then creates byte array with that size, and then reads that body from InputStream in that array.
The problem is: the received set of bytes are modified - there are neccessary bytes (that exist in source) standing with extra bytes that don't exist in the source packet (I watch the content of the source packet via WireShark and compare them with those bytes that I received on the server).
These extra bytes are 0xc6 bytes that meet periodically by the way...
It looks like this:
Source: ... 75 f1 f5 55 73 .... fc a9 47 14 ... 40 ca d5 75 ... fe 30 a7
Received: ... 75 f1 f5 55 73 c6 .... fc a9 47 14 c6 ... 40 ca d5 75 c6 ... fe 30 a7
... - means "some quantity of bytes here"
As a result, I can't receive neccessary data because it's stretched, it's bigger than it have to be, than the body size that I received from rtmp header. And most importantly, that modified data is not what I had to receive!
My questions are: how can it be fixed? What's wrong with InputStream? Why does it insert those 0xc6 bytes to the receiving array?
I understand that I can simply parse received array and exclude those extra bytes, but this is bad solution, since speed and performance are neccessary (and, in this case, it's not clear that it's an extra byte or byte from source, without the comparison of whole arrays) ...
enter code here
public static void getRtmpPacket(InputStream in) throws Exception {
byte[] rtmpHeader = new byte[8];
byte[] rtmpBody;
int bodySize = 0;
//reading rtmp header:
in.read(rtmpHeader);
//reading the body size. This method works fine
bodySize = Server.bigEndianBytesToInt(rtmpHeader, 4, 3);
rtmpBody = new byte[bodySize];
in.read(rtmpBody);
//printing received data:
System.out.println("Packet:");
System.out.println("Body size: " + bodySize);
System.out.print(bytesToString(rtmpHeader) + " ");
System.out.print(bytesToString(rtmpBody));
System.out.println();
}
According to the RTMP spec, it behaves normally. You need to "unchunk" the incoming data, so reading it all at once in a single read() will not work.
Something along these lines (pseudocode):
int remaining = payloadSize;
int totalRead = 0;
int totalReadForChunk = 0;
while (true) {
int num = read(buf, 0, min(remaining, chunkSize - totalReadForChunk))
if (num < 0) break; // i/o error
appendData(<buf>, 0, num)
totalReadForChunk += num
remaining -= num
if (remaining == 0) break; // end of payload
if (totalReadForChunk == chunkSize) {
totalReadForChunk = 0;
// read the chunk header (it's not neccessarily 0xc6)
int header = read()
if (header != currentStreamEmptyHeader) { // 0xc6
// ... parse the new rtmp message according to header value
// (usually invoke the upper-level message reading method "recursively")
}
}
}
Probably, you should see (and use) code of Red5 Media Server and other open-source solutions that implement RTMP protocol.
InputStream.read(byte[]) is only guarenteed to read one byte, and it return the length as an int of the actual length read.
in.read(rtmpHeader); // might read 1, 2, 3, .. 8 bytes.
//reading the body size. This method works fine
bodySize = Server.bigEndianBytesToInt(rtmpHeader, 4, 3);
rtmpBody = new byte[bodySize];
in.read(rtmpBody); // might read 1, 2, 3, ... bodySize bytes.
If you don't check the actual length, and assume the byte[] is full, you get whatever bytes where there before you called read().
What you intended is available using DataInputStream
DataInputStream dis = new DataInputStream(in);
int len = dis.readInt(); // read an int in big endian.
byte[]] bytes = new byte[len];
dis.readFully(bytes); // read the whole byte[] or throw an IOException.
The problem is resolved.
Those extra 0xc6 bytes were the chunking bytes of RTMP packet, which were not visible from the WireShark.
More than this, received header says the actual body size and WireShark "confirms" it, but in fact the body size will be bigger, and should be calculated.
https://www.wireshark.org/lists/wireshark-bugs/200801/msg00011.html
http://red5.osflash.narkive.com/LYumrzr4/rtmp-video-packets-and-streaming-thereof#post12
I'm using AES/CTR/NoPadding algorithm to encrypt data sent using socket between PC and Android.
I wrote unit-test, it sends [1;512] bytes to Android device and receive back the same data - echo service. Received data must be equal to data that was sent.
Test client:
for (int n = 1; n <= 512; n++) {
... skip ...
try {
Object connection = socketFilter.openConnection(socket);
in = new CipherInputStream(socket.getInputStream(), encryptor);
out = new CipherOutputStream(socket.getOutputStream(), decryptor);
byte buf[] = new byte[n];
byte received[] = new byte[n];
TestUtils.numbers(buf);
out.write(buf, 0, buf.length);
socket.shutdownOutput();
int len = in.read(received, 0, received.length);
if (buf.length != len) {
System.err.println("Expected: " + buf.length + " but was: " + len);
}
}
finally {
... skip close streams ...
}
}
Echo server:
Socket clientSocket = socket.accept();
CipherInputStream in = new CipherInputStream(clientSocket.getInputStream(), decryptor);
CipherOutputStream out = new CipherOutputStream(clientSocket.getOutputStream(), encryptor);
try {
byte buf[] = new byte[512];
int len;
if ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
out.close();
}
}
finally {
in.close();
out.close();
}
I tested this code with localhost - all works fine.
When i testing it with Android device, the last block is lost if it's not full.
So, if it was 30 bytes, then only 16 bytes received.
Messages from the test:
... skip ...
Expected: 30 but was: 16
Expected: 31 but was: 16
Expected: 33 but was: 32
... skip ...
Expected: 207 but was: 192
Expected: 209 but was: 208
Expected: 210 but was: 208
... skip ...
What can be wrong?
It seems that the problem is caused by the fact that Android and Hotspot JVM uses different Cipher Provider.
Android uses one called Bouncy Castle which has a known 'bug' in AES/CTR mode. It will miss the last block when doing encryption/decryption. (see many other stackoverflow questions)
If you want only CTR mode. Known work around is that you implement it yourself on Android by generating blocks of keystream repeatedly "on the fly"(by encrypt byte array of 0's), and XOR them with your buffer.
Hope this helps
Have you completely flushed the encryption streams before you close them? AES processes data in block sized chunks, in CTR mode it is the keystream. If you don't fully flush the stream before closing it, on either encryption or decryption, you will probably lose the last block.
Similarly, you need to be sure that you have written/read everything from the transfer file streams between Android and PC. Your last pieces of data may have been sitting in a file transfer buffer somewhere waiting to be written when the buffer was closed.
Android does differ from Java, so I suspect that your error is probably on the Android side. Perhaps try Android -> Android as well as PC -> PC, just to make sure that everything on the Android side is fine.
I have an android java app sending bytes over a socket which is connected to a host machine running a server in Python. I need to receive these bytes as they were sent from the python socket. I see that in Python 'socket.recv' only returns a string. When I send an ASCII string from the java app, I am able to receive the data correctly in the python server, but when I send binary data using java byte, I see the data received is not same. I need to receive raw bytes in Python for my protocol to work correctly. Please point me in right direction.
Code snippet for Sending data on socket:
private void sendFrameMessage(byte[] data) {
byte[] lengthInfo = new byte[4];
Log.v(TAG, "sendFrameMessage");
for(int i=0; i<data.length; i++) {
Log.v(TAG, String.format("data[%d] = %d", i, data[i]));
}
try {
lengthInfo[0] = (byte) data.length;
lengthInfo[1] = (byte) (data.length >> 8);
lengthInfo[2] = (byte) (data.length >> 16);
lengthInfo[3] = (byte) (data.length >> 24);
DataOutputStream dos;
dos = new DataOutputStream(mSocket.getOutputStream());
dos.write(lengthInfo, 0, 4);
dos.write(data, 0, data.length);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
Python Code on receiver side
def recvFrameMessage(self, s):
recv_count = 4;
data = s.recv(recv_count)
if data == 0:
return None
total_rx = len(data)
lenInfo = data
while total_rx < recv_count:
data = s.recv(recv_count - total_rx)
if data == 0:
return None
total_rx += len(data)
lenInfo = lenInfo + data
recv_count = self.decodeFrameLen(lenInfo)
logger.info("length = %d" % recv_count)
data = s.recv(recv_count)
total_rx = len(data)
msg = data
while total_rx < recv_count:
data = s.recv(recv_count - total_rx)
if data == 0:
return None
total_rx += len(data)
msg = msg + data
logger.info("msg = " + msg)
for i in range(0, len(msg)-1):
logger.info("msg[%d] = %s" % (i, msg[i]))
return msg
#SteveP makes good points for binary data "with some structure", but if this is a plain stream of bytes, in Python 2 simply apply the ord() function to each "character" you get from the socket. For example, if the Java end sends a NUL byte, that will show up on the Python end as the character "\x00", and then:
>>> ord("\x00")
0
To convert a whole string s,
map(ord, s)
returns a list of the corresponding 8-bit unsigned integers.
I'm assuming Python 2 here.
Reading binary data is perfectly doable, but what if the binary representation from your android app is different than the byte representation on the Python server? From the Python documentation:
It is perfectly possible to send binary data over a socket. The major
problem is that not all machines use the same formats for binary data.
For example, a Motorola chip will represent a 16 bit integer with the
value 1 as the two hex bytes 00 01. Intel and DEC, however, are
byte-reversed - that same 1 is 01 00. Socket libraries have calls for
converting 16 and 32 bit integers - ntohl, htonl, ntohs, htons where
“n” means network and “h” means host, “s” means short and “l” means
long. Where network order is host order, these do nothing, but where
the machine is byte-reversed, these swap the bytes around
appropriately.
Without code and example input/output, this question is going to be really difficult to answer. I assume the issue is that the representation is different. The most likely issue is that Java uses big endian, whereas Python adheres to whatever machine you are running it off of. If your server uses little endian, then you need to account for that. See here for a more thorough explanation on endianness.
I have made a class in java that allows me to wrap an existing socket with the WebSocket protocols. I have everything working for the RFC6445 protocol working and everything works in chrome and FF. However Safari and iOS is using the hixie76 / HyBi00 protocol (according to Wikipedia).
I have everything working and Safari and iOS correctly handshake and start sending/receiving messages... well, at least most of the time.
About 20-30% of the time, the handshake fails and Safari closes the connection. (Java reads a -1 byte upon trying to read first frame). Safari does not report any errors in the console, but just calls the onclose event handler.
Why would the handshakes only work part of the time?
Here is my handshake code:
Note: No exceptions are thrown and the "Handshake Complete" is written to the console. But then upon trying to read the first frame the connection is closed. (Java returns -1 on inst.read())
// Headers are read in a previous method which wraps the socket using RFC6445
// protocol. If it detects 2 keys it will call this and pass in the headers.
public static MessagingWebSocket wrapOldProtocol(HashMap<String, String> headers, PushbackInputStream pin, Socket sock) throws IOException, NoSuchAlgorithmException {
// SPEC
// https://datatracker.ietf.org/doc/html/draft-hixie-thewebsocketprotocol-76#page-32
// Read the "key3" value. This is 8 random bytes after the headers.
byte[] key3 = new byte[8];
for ( int i=0; i<key3.length; i++ ) {
key3[i] = (byte)pin.read();
}
// Grab the two keys we need to use for the handshake
String key1 = headers.get("Sec-WebSocket-Key1");
String key2 = headers.get("Sec-WebSocket-Key2");
// Count the spaces in both keys
// Abort the connection is either key has 0 spaces
int spaces1 = StringUtils.countMatches(key1, " ");
int spaces2 = StringUtils.countMatches(key2, " ");
if ( spaces1 == 0 || spaces2 == 0 ) {
throw new IOException("Bad Handshake Request, Possible Cross-protocol attack");
}
// Strip all non-digit characters from each key
// Use the remaining value as a base-10 integer.
// Abort if either number is not a multiple of it's #spaces counterpart
// Need to use long because the values are unsigned
long num1 = Long.parseLong( key1.replaceAll("\\D", "") );
long num2 = Long.parseLong( key2.replaceAll("\\D", "") );
if ( !(num1 % spaces1 == 0) || !(num2 % spaces2 == 0) ) {
throw new IOException("Bad Handshake Request. Possible non-conforming client");
}
// Part1/2 is key num divided by the # of spaces
int part1 = (int)(num1 / spaces1);
int part2 = (int)(num2 / spaces2);
// Now calculate the challenge response
// MD5( num1 + num2 + key3 ) ... concat, not add
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(ByteBuffer.allocate(4).putInt(part1));
md.update(ByteBuffer.allocate(4).putInt(part2));
md.update(key3);
byte[] response = md.digest();
// Now build the server handshake response
// Ignore Sec-WebSocket-Protocol (we don't use this)
String origin = headers.get("Origin");
String location = "ws://" + headers.get("Host") + "/";
StringBuilder sb = new StringBuilder();
sb.append("HTTP/1.1 101 WebSocket Protocol Handshake").append("\r\n");
sb.append("Upgrade: websocket").append("\r\n");
sb.append("Connection: Upgrade").append("\r\n");
sb.append("Sec-WebSocket-Origin: ").append(origin).append("\r\n");
sb.append("Sec-WebSocket-Location: ").append(location).append("\r\n");
sb.append("\r\n");
// Anything left in the buffer?
if ( pin.available() > 0 ) {
throw new IOException("Unexpected bytes after handshake!");
}
// Send the handshake & challenge response
OutputStream out = sock.getOutputStream();
out.write(sb.toString().getBytes());
out.write(response);
out.flush();
System.out.println("[MessagingWebSocket] Handshake Complete.");
// Return the wrapper socket class.
MessagingWebSocket ws = new MessagingWebSocket(sock);
ws.oldProtocol = true;
return ws;
}
Thanks!
Note: I am not looking for third-party alternatives for WebSockets such at jWebSocket, Jetty and Socket.IO. I already know about many of these.
Your MD5 digest method has a bug:
protocol described as below: https://datatracker.ietf.org/doc/html/draft-hixie-thewebsocketprotocol-76#section-5.2
byte[] bytes = new byte[16];
BytesUtil.fillBytesWithArray(bytes, 0, 3, BytesUtil.intTobyteArray(part1));
BytesUtil.fillBytesWithArray(bytes, 4, 7, BytesUtil.intTobyteArray(part2));
BytesUtil.fillBytesWithArray(bytes, 8, 15, key3);
I think your problem is caused by Little Endian and Big Endian.