// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.sdk.internal.websocket;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.util.Random;
import org.chromium.sdk.ConnectionLogger;
import org.chromium.sdk.internal.transport.SocketWrapper;
import org.chromium.sdk.internal.transport.SocketWrapper.LoggableInputStream;
import org.chromium.sdk.internal.transport.SocketWrapper.LoggableOutputStream;
/**
* WebSocket connection. Sends and receives messages. Implements HyBi-00 protocol specification.
*/
public class Hybi00WsConnection
extends AbstractWsConnection<LoggableInputStream, LoggableOutputStream> {
public static Hybi00WsConnection connect(InetSocketAddress endpoint, int timeout,
String resourceId, String origin, ConnectionLogger connectionLogger) throws IOException {
SocketWrapper socketWrapper =
new SocketWrapper(endpoint, timeout, connectionLogger, LOGGER_CHARSET);
boolean handshakeDone = false;
Exception handshakeException = null;
try {
Hybi00Handshake.performHandshake(socketWrapper, endpoint, resourceId, origin,
HANDSHAKE_RANDOM);
handshakeDone = true;
} catch (RuntimeException e) {
handshakeException = e;
throw e;
} catch (IOException e) {
handshakeException = e;
throw e;
} finally {
if (!handshakeDone) {
socketWrapper.getShutdownRelay().sendSignal(null, handshakeException);
}
}
return new Hybi00WsConnection(socketWrapper, connectionLogger);
}
private Hybi00WsConnection(SocketWrapper socketWrapper,
ConnectionLogger connectionLogger) {
super(socketWrapper, connectionLogger);
}
@Override
public void sendTextualMessage(String message) throws IOException {
byte[] bytes = message.getBytes(UTF_8_CHARSET);
LoggableOutputStream loggableWriter = getSocketWrapper().getLoggableOutput();
OutputStream output = loggableWriter.getOutputStream();
synchronized (this) {
output.write((byte) 0);
output.write(bytes);
output.write((byte) 255);
output.flush();
}
loggableWriter.markSeparatorForLog();
}
@Override
protected CloseReason runListenLoop(LoggableInputStream loggableReader)
throws IOException, InterruptedException {
BufferedInputStream input = new BufferedInputStream(loggableReader.getInputStream());
while (true) {
loggableReader.markSeparatorForLog();
int firstByte;
try {
firstByte = input.read();
} catch (IOException e) {
if (isClosingGracefully()) {
return CloseReason.USER_REQUEST;
} else {
throw e;
}
}
if (firstByte == -1) {
if (isClosingGracefully()) {
return CloseReason.USER_REQUEST;
} else {
throw new IOException("Unexpected end of stream");
}
}
if ((firstByte & 0x80) == 0) {
ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
while (true) {
int i = input.read();
if (i == -1) {
throw new IOException("End of stream");
}
byte b = (byte) i;
if (b == (byte) 0xFF) {
break;
}
byteBuffer.write(b);
}
byte[] messageBytes = byteBuffer.toByteArray();
final String text = new String(messageBytes, UTF_8_CHARSET);
getDispatchQueue().put(new MessageDispatcher() {
@Override
public boolean dispatch(Listener userListener) {
userListener.textMessageRecieved(text);
return false;
}
});
} else {
long len = 0;
while (true) {
int lengthByte = input.read();
if (lengthByte == -1) {
throw new IOException("End of stream");
}
len = len * 10 + (lengthByte & 0x7F);
if (len > Integer.MAX_VALUE) {
throw new IOException("Message too long");
}
if ((lengthByte & 0x80) == 0) {
break;
}
}
long needSkip = len;
while (needSkip > 0) {
long skipped = input.skip(needSkip);
needSkip -= skipped;
}
if (firstByte == (byte) 0xFF && len == 0) {
return CloseReason.REMOTE_CLOSE_REQUEST;
} else {
final long finalLen = len;
getDispatchQueue().put(new MessageDispatcher() {
@Override
public boolean dispatch(Listener userListener) {
userListener.errorMessage(
new Exception("Unexpected binary message of length " + finalLen));
return false;
}
});
}
}
}
}
private static final Random HANDSHAKE_RANDOM = new Random();
}