JavaIntermediate JavaIntermediate - 4 months ago 17
Java Question

Java Websocket Text Frame Error at length >=128?

I am trying to reinvent the wheel and create my own websocket server. It is connecting fine on both Google Chrome and Firefox and will receive and echo text frames correctly up to 127 characters in length. However, beyond that Google gives me the following error:

WebSocket connection to 'ws://localhost:9999/' failed: The minimal number of bytes MUST be used to encode the length

Firefox will receive/interpret the first few characters sometimes then fail code:1006.

The server shows to be receiving the message in full and attempting to broadcast it in full with no run-time errors or . It's also going to the 16 bit length creator as shown by my System.out.println(). My java console from server reads:

websocket server started
client connected
message received
1234567890abcdefghij1234567890abcdefghij1234567890abcdefghij1234567890abcdefghij1234567890abcdefghij1234567890abcdefghij12345678
broadcasting: 1234567890abcdefghij1234567890abcdefghij1234567890abcdefghij1234567890abcdefghij1234567890abcdefghij1234567890abcdefghij12345678
2nd data

I'm currently writing a frame tester to test the frame I'm sending with broadcast but was hoping the community might be able to save me some of that leg work. The really odd thing is 126 and 127 payload lengths work and reflect using the 2 bytes for length.

public void broadcast(String mess) throws IOException {
System.out.println("broadcasting: "+mess);
byte[] rawData = mess.getBytes();

int frameCount = 0;
byte[] frame = new byte[10];

frame[0] = (byte) 129;

if (rawData.length <= 125) {
frame[1] = (byte) rawData.length;
frameCount = 2;
} else if (rawData.length >= 126 && rawData.length <= 65535) {
System.out.println("2nd data");
frame[1] = (byte) 126;
byte len = (byte) rawData.length;
frame[2] = (byte) ((len >> 8) & (byte) 255);
frame[3] = (byte) (len & (byte) 255);
frameCount = 4;
} else {
System.out.println("3rd data");
frame[1] = (byte) 127;
byte len = (byte) rawData.length;
frame[2] = (byte) ((len >> 56) & (byte) 255);
frame[3] = (byte) ((len >> 48) & (byte) 255);
frame[4] = (byte) ((len >> 40) & (byte) 255);
frame[5] = (byte) ((len >> 32) & (byte) 255);
frame[6] = (byte) ((len >> 24) & (byte) 255);
frame[7] = (byte) ((len >> 16) & (byte) 255);
frame[8] = (byte) ((len >> 8) & (byte) 255);
frame[9] = (byte) (len & (byte) 255);
frameCount = 10;
}

int bLength = frameCount + rawData.length;

byte[] reply = new byte[bLength];

int bLim = 0;
for (int i = 0; i < frameCount; i++) {
reply[bLim] = frame[i];
bLim++;
}
for (int i = 0; i < rawData.length; i++) {
reply[bLim] = rawData[i];
bLim++;
}
for (OutputStream writer : writers) {
writer.write(reply);
writer.flush();
}

}


Thank you community for any help.

Answer Source

A few notes.

  1. You are handling length improperly
  2. You are not converting the String to bytes properly (must be done via UTF-8)

Here's a simple example.

The code below is licensed under : Eclipse Public License 1.0

package websocket;

import java.nio.ByteBuffer;
import java.nio.charset.Charset;

public class RawGenerate
{
    /**
     * The overhead (maximum) for a framing header. Assuming a maximum sized payload with masking key.
     */
    public static final int OVERHEAD = 28;

    public ByteBuffer asClientInitiatedTextFrame(String msg)
    {
        ByteBuffer buf = ByteBuffer.allocate(msg.length() + OVERHEAD);
        putOpFin(buf,(byte)0x01,true);
        byte mask[] = new byte[] { 1, 2, 3, 4 }; // Needs to be random
        byte payload[] = msg.getBytes(Charset.forName("UTF-8")); // must be UTF-8 (per spec)
        putLengthAndMask(buf,payload.length,mask);
        for (int i = 0; i < payload.length; i++)
        {
            buf.put((byte)(payload[i] ^= mask[i % 4]));
        }
        buf.flip();
        return buf;
    }

    public static void putOpFin(ByteBuffer buf, byte opcode, boolean fin)
    {
        byte b = 0x00;
        if (fin)
        {
            b |= 0x80;
        }
        b |= opcode & 0x0F;
        buf.put(b);
    }

    public static void putLengthAndMask(ByteBuffer buf, int length, byte mask[])
    {
        if (mask != null)
        {
            assert (mask.length == 4);
            putLength(buf,length,(mask != null));
            buf.put(mask);
        }
        else
        {
            putLength(buf,length,false);
        }
    }

    public static void putLength(ByteBuffer buf, int length, boolean masked)
    {
        if (length < 0)
        {
            throw new IllegalArgumentException("Length cannot be negative");
        }
        byte b = (masked?(byte)0x80:0x00);

        if (length > 0xFF_FF)
        {
            buf.put((byte)(b | 0x7F));
            buf.put((byte)0x00);
            buf.put((byte)0x00);
            buf.put((byte)0x00);
            buf.put((byte)0x00);
            buf.put((byte)((length >> 24) & 0xFF));
            buf.put((byte)((length >> 16) & 0xFF));
            buf.put((byte)((length >> 8) & 0xFF));
            buf.put((byte)(length & 0xFF));
        }
        else if (length >= 0x7E)
        {
            buf.put((byte)(b | 0x7E));
            buf.put((byte)(length >> 8));
            buf.put((byte)(length & 0xFF));
        }
        else
        {
            buf.put((byte)(b | length));
        }
    }
}