Skip to content

Instantly share code, notes, and snippets.

@bladeFury
Last active December 25, 2015 12:19
Show Gist options
  • Save bladeFury/6975656 to your computer and use it in GitHub Desktop.
Save bladeFury/6975656 to your computer and use it in GitHub Desktop.
A Simple class that calculates the BTih of a torrent file.
/*
* created by zhangge(uniqguang@gmail.com)
*/
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.security.NoSuchAlgorithmException;
/**
* A Simple class that calculates the BTih of a torrent file.
* About BTih, see http://en.wikipedia.org/wiki/BTIH
*
* @author zhangge
*/
public class BTihCalculator {
private final InputStream mTorrentInputStream;
private static final String sInfoSectionName = "info";
// The last indicator read.
// Zero if unknown.
// '0'..'9' indicates a byte[].
// 'i' indicates an Number.
// 'l' indicates a List.
// 'd' indicates a Map.
// 'e' indicates end of Number, List or Map (only used internally).
// -1 indicates end of stream.
// Call getNextIndicator to get the current value (will never return zero).
private int mIndicator = 0;
/*
* indicates whether current position is in Info section of a torrent file
*/
private boolean mIsInInfoSection;
/*
* torrent's info section
*/
private ByteArrayOutputStream mInfoBytes;
/*
* recursion level when decode
* use to determine when to stop copy stream
*/
private int mRecursionLevel;
/*
* indicates that info section is stored, no need to decode any more
*/
private boolean mFinished;
public BTihCalculator(InputStream in) {
mTorrentInputStream = in;
mIsInInfoSection = false;
mInfoBytes = new ByteArrayOutputStream(512);
mRecursionLevel = 0;
mFinished = false;
}
public String doCalculate() throws IOException, NoSuchAlgorithmException {
decode();
return AeSimpleSHA1.SHA1(mInfoBytes.toByteArray());
}
private void decode() throws IOException {
if (mFinished) {
return;
}
int c = getNextIndicator();
if (c >= '0' && c <='9') {
decodeBytes();
}
else if (c == 'i') {
decodeNumber();
}
else if (c == 'l') {
decodeList();
}
else if (c == 'd') {
decodeMap();
}
}
private String decodeBytes() throws IOException{
if (mFinished) {
return "";
}
int c = getNextIndicator();
int num = c - '0';
if (num < 0 || num > 9)
throw new InvalidBEncodingException("Number expected, not '"
+ (char)c + "'");
mIndicator = 0;
c = read();
int i = c - '0';
while (i >= 0 && i <= 9) {
// This can overflow!
num = num*10 + i;
c = this.read();
i = c - '0';
}
if (c != ':') {
throw new InvalidBEncodingException("Colon expected, not '" +
(char)c + "'");
}
byte[] str = read(num);
return new String(str, "UTF-8");
}
private void decodeNumber() throws IOException{
if (mFinished) {
return;
}
int c = getNextIndicator();
mIndicator = 0;
c = this.read();
if (c == '0') {
c = this.read();
if (c == 'e')
return ;
else
throw new InvalidBEncodingException("'e' expected after zero," +
" not '" + (char)c + "'");
}
// We don't support more the 255 char big integers
char[] chars = new char[256];
int off = 0;
if (c == '-') {
c = this.read();
if (c == '0')
throw new InvalidBEncodingException("Negative zero not allowed");
chars[off] = '-';
off++;
}
if (c < '1' || c > '9')
throw new InvalidBEncodingException("Invalid Integer start '"
+ (char)c + "'");
chars[off] = (char)c;
off++;
c = this.read();
int i = c - '0';
while (i >= 0 && i <= 9) {
chars[off] = (char)c;
off++;
c = read();
i = c - '0';
}
if (c != 'e')
throw new InvalidBEncodingException("Integer should end with 'e'");
return ;
}
private void decodeList() throws IOException{
if (mFinished) {
return;
}
int c = getNextIndicator();
if (c != 'l') {
throw new InvalidBEncodingException("Expected 'l', not '" +
(char)c + "'");
}
mIndicator = 0;
c = this.getNextIndicator();
while (c != 'e') {
if (mFinished) {
break;
}
decode();
c = this.getNextIndicator();
}
mIndicator = 0;
return;
}
/**
* Decode Map object from stream
* If key is 'info', means current position is in info section
* then store bytes
*/
private void decodeMap() throws IOException{
if (mFinished) {
return;
}
int c = getNextIndicator();
if (c != 'd') {
throw new InvalidBEncodingException("Expected 'd', not '" +
(char)c + "'");
}
if (mIsInInfoSection) {
mRecursionLevel ++;
}
mIndicator = 0;
c = getNextIndicator();
while (c != 'e') {
if (mFinished) {
break;
}
// Dictionary keys are always strings.
String key = decodeBytes();
if (key.equals(sInfoSectionName)) {
mIsInInfoSection = true;
}
decode();
c = getNextIndicator();
}
mIndicator = 0;
mRecursionLevel --;
if (mIsInInfoSection) {
if (mRecursionLevel <= 0) {
mIsInInfoSection = false;
mFinished = true;
}
}
}
/**
* Returns what the next b-encoded object will be on the stream or -1
* when the end of stream has been reached.
*
* <p>
* Can return something unexpected (not '0' .. '9', 'i', 'l' or 'd') when
* the stream isn't b-encoded.
* </p>
*
* This might or might not read one extra byte from the stream.
*/
private int getNextIndicator() throws IOException {
if (this.mIndicator == 0) {
this.mIndicator = mTorrentInputStream.read();
write(this.mIndicator);
}
return this.mIndicator;
}
/**
* Store a byte if current position is in info section
*/
private void write(int i) {
if (mIsInInfoSection) {
mInfoBytes.write(i);
}
}
/**
* Store bytes from stream if current position is in info section
*/
private void write(byte[] bs) {
if (mIsInInfoSection) {
try {
mInfoBytes.write(bs);
}
catch (IOException e ) {
e.printStackTrace();
}
}
}
/**
* Returns the next byte read from the InputStream (as int).
*
* @throws EOFException If InputStream.read() returned -1.
*/
private int read() throws IOException {
int c = mTorrentInputStream.read();
if (c == -1)
throw new EOFException();
write(c);
return c;
}
/**
* Returns a byte[] containing length valid bytes starting at offset zero.
*
* @throws EOFException If InputStream.read() returned -1 before all
* requested bytes could be read. Note that the byte[] returned might be
* bigger then requested but will only contain length valid bytes. The
* returned byte[] will be reused when this method is called again.
*/
private byte[] read(int length) throws IOException {
byte[] result = new byte[length];
int read = 0;
while (read < length)
{
int i = mTorrentInputStream.read(result, read, length - read);
if (i == -1)
throw new EOFException();
write(result);
read += i;
}
return result;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment