package com.limegroup.gnutella.util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import com.limegroup.gnutella.ByteOrder;
/** For implementation details, please see:
* http://www.acm.org/sigcomm/sigcomm97/papers/p062.pdf
*/
public class COBSUtil {
/** Encode a byte array with COBS. The non-allowable byte value is 0.
* PRE: src is not null.
* POST: the return array will be a cobs encoded version of src. namely,
* cobsDecode(cobsEncode(src)) == src.
* @return a COBS encoded version of src.
*/
public static byte[] cobsEncode(byte[] src) throws IOException {
final int srcLen = src.length;
int code = 1;
int currIndex = 0;
// COBS encoding adds no more than one byte of overhead for every 254
// bytes of packet data
final int maxEncodingLen = src.length + ((src.length+1)/254) + 1;
ByteArrayOutputStream sink = new ByteArrayOutputStream(maxEncodingLen);
int writeStartIndex = -1;
while (currIndex < srcLen) {
if (src[currIndex] == 0) {
// currIndex was incremented so take 1 less
code = finishBlock(code, sink, src, writeStartIndex,
(currIndex-1));
writeStartIndex = -1;
}
else {
if (writeStartIndex < 0) writeStartIndex = currIndex;
code++;
if (code == 0xFF) {
code = finishBlock(code, sink, src, writeStartIndex,
currIndex);
writeStartIndex = -1;
}
}
currIndex++;
}
// currIndex was incremented so take 1 less
finishBlock(code, sink, src, writeStartIndex, (currIndex-1));
return sink.toByteArray();
}
private static int finishBlock(int code, ByteArrayOutputStream sink,
byte[] src, int begin, int end)
throws IOException {
sink.write(code);
if (begin > -1)
sink.write(src, begin, (end-begin)+1);
return (byte) 0x01;
}
/** Decode a COBS-encoded byte array. The non-allowable byte value is 0.
* PRE: src is not null.
* POST: the return array will be a cobs decoded version of src. namely,
* cobsDecode(cobsEncode(src)) == src.
* @return the original COBS decoded string
*/
public static byte[] cobsDecode(byte[] src) throws IOException {
final int srcLen = src.length;
int currIndex = 0;
int code = 0;
ByteArrayOutputStream sink = new ByteArrayOutputStream();
while (currIndex < srcLen) {
code = ByteOrder.ubyte2int(src[currIndex++]);
if ((currIndex+(code-2)) >= srcLen)
throw new IOException();
for (int i = 1; i < code; i++) {
sink.write((int)src[currIndex++]);
}
if (currIndex < srcLen) // don't write this last one, it isn't used
if (code < 0xFF) sink.write(0);
}
return sink.toByteArray();
}
}