/* * Copyright 2016 Cel Skeggs * * This file is part of the CCRE, the Common Chicken Runtime Engine. * * The CCRE is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * The CCRE is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with the CCRE. If not, see <http://www.gnu.org/licenses/>. */ package ccre.recording; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.function.Function; class StreamDecoder { private final InputStream in; public StreamDecoder(InputStream in) throws IOException { this.in = in; byte[] expected = StreamEncoder.MAGIC_STRING.getBytes(); byte[] got = new byte[expected.length]; int i = 0; while (i < got.length) { int o = in.read(got, i, got.length - i); if (o == -1) { throw new EOFException(); } i += o; } if (!Arrays.equals(expected, got)) { throw new IOException("Invalid header: is this really a recorded stream, and is it of the correct version?"); } } private int readUnsigned() throws IOException { int i = in.read(); if (i == -1) { throw new EOFException(); } return i; } private int read16() throws IOException { int i = readUnsigned(); return (i << 8) | readUnsigned(); } private int read32() throws IOException { int i = read16(); return (i << 16) | read16(); } private long read64() throws IOException { long i = read32(); return (i << 32) | (read32() & 0xFFFFFFFFL); } // ******* VARINT IMPLEMENTATIONS ******* // need to be synchronized with StreamEncoder! // in units of 10 microseconds private int readTimeDeltaWithPrefetchedByte(int b0) throws IOException { if ((b0 & 0x80) == 0) { // bit format: 0xxxxxxx, offset 0 return b0; } else { int b1 = readUnsigned(); if (b0 == 0xFF && b1 == 0xFF) { // bit format: 11111111 11111111 xxxxxxxx xxxxxxxx xxxxxxxx // xxxxxxxx, offset 0 return read32(); } else { // bit format: 1xxxxxxx xxxxxxxx, offset 128 int value = ((b0 & 0x7F) << 8) | b1; return value + 128; } } } private int readChannelNumber() throws IOException { int b0 = readUnsigned(); if ((b0 & 0x80) == 0) { // 0xxxxxxx, offset 0 return b0; } else if ((b0 & 0x40) == 0) { // 10xxxxxx, offset 128 return b0; } else if ((b0 & 0x20) == 0) { // 110xxxxx, offset 196 return b0; } else if ((b0 & 0x10) == 0) { // 1110xxxx, offset 224 return b0; } else { int b1 = readUnsigned(); if (b0 == 0xFF && b1 == 0xFF) { // 11111111 11111111 xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx return read32(); } int value = ((b0 & 0x0F) << 8) | b1; // 1111xxxx xxxxxxxx, offset 240 return value + 240; } } private int readArrayLength() throws IOException { int b0 = readUnsigned(); if (b0 != 255) { return b0; } else { return read32(); } } private long readGeneralVarInt() throws IOException { // needs one to ten bytes, depending on the value // first, read an unsigned integer with MSB continuation long l = 0; int b; do { b = readUnsigned(); l = (l << 7) | (b & 0x7F); } while ((b & 0x80) != 0); // now disjoin the positives and negatives if ((l & 1) != 0) { // >>> instead of >> so that we don't get any pesky sign-extension return ~(l >>> 1); } else { return l >>> 1; } } private long lastTimestamp = 0; public RecordSnapshot decode(Function<Integer, Byte> channelNumberToSnapshotType) throws IOException { RecordSnapshot rs = new RecordSnapshot(); int b0 = in.read(); if (b0 == -1) { return null; // END OF STREAM } lastTimestamp += readTimeDeltaWithPrefetchedByte(b0); rs.timestamp = lastTimestamp; rs.channel = readChannelNumber(); Byte b = channelNumberToSnapshotType.apply(rs.channel); if (b == null) { throw new IOException("Uninitialized channel: " + rs.channel); } rs.type = b; switch (b) { case RecordSnapshot.T_NULL: // nothing else break; case RecordSnapshot.T_BYTE: rs.value = readUnsigned(); break; case RecordSnapshot.T_SHORT: rs.value = read16(); break; case RecordSnapshot.T_INT: rs.value = read32(); break; case RecordSnapshot.T_LONG: rs.value = read64(); break; case RecordSnapshot.T_VARINT: rs.value = readGeneralVarInt(); break; case RecordSnapshot.T_BYTES: rs.data = new byte[readArrayLength()]; in.read(rs.data); break; default: throw new RuntimeException("Invalid type for StreamDecoder: " + rs.type); } return rs; } public void close() throws IOException { in.close(); } }