jadb/src/se/vidstige/jadb/ShellProtocolTransport.java

154 lines
4.6 KiB
Java

package se.vidstige.jadb;
import java.io.*;
class ShellProtocolTransport implements Closeable {
private final DataOutputStream output;
private final DataInputStream input;
ShellProtocolTransport(DataOutputStream outputStream, DataInputStream inputStream) {
output = outputStream;
input = inputStream;
}
// replace with Integer.toUnsignedLong in Java 8
private static long integerToUnsignedLong(int i) {
return ((long) (int) i) & 0xffffffffL;
}
// replace with Byte.toUnsignedInt in Java 8
private static int byteToUnsignedInt(byte b) {
return ((int) b) & 0xff;
}
private ShellMessageType readMessageType() throws IOException {
return ShellMessageType.fromId(input.readByte());
}
private long readDataLength() throws IOException {
return integerToUnsignedLong(Integer.reverseBytes(input.readInt()));
}
private void readDataTo(OutputStream out, long dataLength, byte[] buffer) throws IOException {
long remaining = dataLength;
while (remaining > 0) {
int len = (int) Math.min(remaining, buffer.length);
input.readFully(buffer, 0, len);
out.write(buffer, 0, len);
remaining -= len;
}
out.flush();
}
int demuxOutput(OutputStream outputStream, OutputStream errorStream) throws JadbException, IOException {
int exitCode = 0;
byte[] buf = new byte[256 * 1024];
try {
while (true) {
ShellMessageType messageType = readMessageType();
long length = readDataLength();
switch (messageType) {
case STDOUT:
readDataTo(outputStream, length, buf);
break;
case STDERR:
readDataTo(errorStream, length, buf);
break;
case EXIT:
if (length != 1) {
throw new JadbException("Expected only one byte for exitCode");
}
exitCode = byteToUnsignedInt(input.readByte());
break;
default:
// ignore;
break;
}
}
} catch (EOFException e) {
// ignore
}
return exitCode;
}
private void writeData(ShellMessageType type, byte[] buf, int off, int len) throws IOException {
output.writeByte(byteToUnsignedInt(type.getId()));
output.writeInt(Integer.reverseBytes(len));
output.write(buf, off, len);
}
OutputStream getOutputStream() {
return new ShellProtocolOutputStream(this);
}
@Override
public void close() throws IOException {
output.close();
input.close();
}
enum ShellMessageType {
STDIN((byte) 0), STDOUT((byte) 1), STDERR((byte) 2), EXIT((byte) 3), CLOSE_STDIN((byte) 4), WINDOW_SIZE_CHANGE((byte) 5), UNKNOWN(Byte.MIN_VALUE);
private final byte id;
ShellMessageType(byte id) {
this.id = id;
}
public static ShellMessageType fromId(byte b) {
switch (b) {
case 0:
return STDIN;
case 1:
return STDOUT;
case 2:
return STDERR;
case 3:
return EXIT;
case 4:
return CLOSE_STDIN;
case 5:
// unused
return WINDOW_SIZE_CHANGE;
default:
return UNKNOWN;
}
}
public byte getId() {
return id;
}
}
private static class ShellProtocolOutputStream extends OutputStream {
private final ShellProtocolTransport transport;
ShellProtocolOutputStream(ShellProtocolTransport transport) {
this.transport = transport;
}
@Override
public void write(int b) throws IOException {
write(new byte[]{(byte) b});
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
transport.writeData(ShellMessageType.STDIN, b, off, len);
}
@Override
public void flush() throws IOException {
transport.output.flush();
}
@Override
public void close() throws IOException {
transport.writeData(ShellMessageType.CLOSE_STDIN, new byte[0], 0, 0);
}
}
}