Last active
December 25, 2015 12:19
-
-
Save bladeFury/6975656 to your computer and use it in GitHub Desktop.
A Simple class that calculates the BTih of a torrent file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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