BTPeerIDByteDecoderpublic class BTPeerIDByteDecoder extends Object Used for identifying clients by their peerID. |
Fields Summary |
---|
static final boolean | LOG_UNKNOWN | private static HashSet | logged_discrepancies | private static boolean | client_logging_allowed | private static HashSet | logged_ids |
Methods Summary |
---|
private static java.lang.String | asUTF8ByteString(java.lang.String text)
try {
byte[] utf_bytes = text.getBytes(Constants.DEFAULT_ENCODING);
return ByteFormatter.encodeString(utf_bytes);
}
catch (UnsupportedEncodingException uee) {return "";}
| private static void | assertDecode(java.lang.String client_result, java.lang.String peer_id)
if (peer_id.length() > 40) {
peer_id = peer_id.replaceAll("[ ]", "");
}
byte[] byte_peer_id = null;
if (peer_id.length() == 40) {
byte_peer_id = ByteFormatter.decodeString(peer_id);
String readable_peer_id = makePeerIDReadableAndUsable(byte_peer_id);
if (!peer_id.equals(readable_peer_id)) {
throw new RuntimeException("Use alternative format for peer ID - from " + peer_id + " to " + readable_peer_id);
}
}
else if (peer_id.length() == 20) {
byte_peer_id = peer_id.getBytes(Constants.BYTE_ENCODING);
}
else {
throw new IllegalArgumentException(peer_id);
}
assertDecode(client_result, byte_peer_id);
| private static void | assertDecode(java.lang.String client_result, byte[] peer_id)
String peer_id_as_string = getPrintablePeerID(peer_id, '*");
System.out.println(" Peer ID: " + peer_id_as_string + " Client: " + client_result);
// Do not log any clients.
String decoded_result = decode(peer_id);
if (client_result.equals(decoded_result)) {return;}
throw new RuntimeException("assertion failure - expected \"" + client_result + "\", got \"" + decoded_result + "\": " + peer_id_as_string);
| public static java.lang.String | decode(byte[] peer_id)Decodes the given peerID, returning an identification string.
String client = null;
try {client = decode0(peer_id);}
catch (Throwable e) {Debug.printStackTrace(e);}
String peer_id_as_string = null;
try {peer_id_as_string = new String(peer_id, Constants.BYTE_ENCODING);}
catch (UnsupportedEncodingException uee) {return "";}
if (client != null) {return client;}
boolean is_az_style = BTPeerIDByteDecoderUtils.isAzStyle(peer_id_as_string);
boolean is_shadow_style = BTPeerIDByteDecoderUtils.isShadowStyle(peer_id_as_string);
logUnknownClient(peer_id, !(is_az_style || is_shadow_style));
if (is_az_style) {
return BTPeerIDByteDecoderDefinitions.formatUnknownAzStyleClient(peer_id_as_string);
}
else if (is_shadow_style) {
return BTPeerIDByteDecoderDefinitions.formatUnknownShadowStyleClient(peer_id_as_string);
}
String sPeerID = getPrintablePeerID(peer_id);
return MessageText.getString("PeerSocket.unknown") + " [" + sPeerID + "]";
| public static java.lang.String | decode0(byte[] peer_id_bytes)
final String UNKNOWN = MessageText.getString("PeerSocket.unknown");
final String FAKE = MessageText.getString("PeerSocket.fake_client");
final String BAD_PEER_ID = MessageText.getString("PeerSocket.bad_peer_id");
String peer_id = null;
try {peer_id = new String(peer_id_bytes, Constants.BYTE_ENCODING);}
catch (UnsupportedEncodingException uee) {return "";}
// We store the result here.
String client = null;
/**
* If the client reuses parts of the peer ID of other peers, then try to determine this
* first (before we misidentify the client).
*/
if (BTPeerIDByteDecoderUtils.isPossibleSpoofClient(peer_id)) {
client = decodeBitSpiritClient(peer_id, peer_id_bytes);
if (client != null) {return client;}
client = decodeBitCometClient(peer_id, peer_id_bytes);
if (client != null) {return client;}
return "BitSpirit? (" + BAD_PEER_ID + ")";
}
/**
* See if the client uses Az style identification.
*/
if (BTPeerIDByteDecoderUtils.isAzStyle(peer_id)) {
client = BTPeerIDByteDecoderDefinitions.getAzStyleClientName(peer_id);
if (client != null) {
String client_with_version = BTPeerIDByteDecoderDefinitions.getAzStyleClientVersion(client, peer_id);
/**
* Hack for fake ZipTorrent clients - there seems to be some clients
* which use the same identifier, but they aren't valid ZipTorrent clients.
*/
if (client.startsWith("ZipTorrent") && peer_id.startsWith("bLAde", 8)) {
String client_name = (client_with_version == null) ? client : client_with_version;
return UNKNOWN + " [" + FAKE + ": " + client_name + "]";
}
/**
* BitTorrent 6.0 Beta currently misidentifies itself.
*/
if ("\u00B5Torrent 6.0.0 Beta".equals(client_with_version)) {
return "Mainline 6.0 Beta";
}
/**
* If it's the rakshasa libtorrent, then it's probably rTorrent.
*/
if (client.startsWith("libTorrent (Rakshasa)")) {
String client_name = (client_with_version == null) ? client : client_with_version;
return client_name + " / rTorrent*";
}
if (client_with_version != null) {return client_with_version;}
return client;
}
}
/**
* See if the client uses Shadow style identification.
*/
if (BTPeerIDByteDecoderUtils.isShadowStyle(peer_id)) {
client = BTPeerIDByteDecoderDefinitions.getShadowStyleClientName(peer_id);
if (client != null) {
String client_ver = BTPeerIDByteDecoderUtils.getShadowStyleVersionNumber(peer_id);
if (client_ver != null) {return client + " " + client_ver;}
return client;
}
}
/**
* See if the client uses Mainline style identification.
*/
client = BTPeerIDByteDecoderDefinitions.getMainlineStyleClientName(peer_id);
if (client != null) {
/**
* We haven't got a good way of detecting whether this is a Mainline style
* version of peer ID until we start decoding peer ID information. So for
* that reason, we wait until we get client version information here - if
* we don't manage to determine a version number, then we assume that it
* has been misidentified and carry on with it.
*/
String client_ver = BTPeerIDByteDecoderUtils.getMainlineStyleVersionNumber(peer_id);
if (client_ver != null) {
String result = client + " " + client_ver;
return result;
}
}
/**
* Check for BitSpirit / BitComet (non possible spoof client mode).
*/
client = decodeBitSpiritClient(peer_id, peer_id_bytes);
if (client != null) {return client;}
client = decodeBitCometClient(peer_id, peer_id_bytes);
if (client != null) {return client;}
/**
* See if the client identifies itself using a particular substring.
*/
BTPeerIDByteDecoderDefinitions.ClientData client_data = BTPeerIDByteDecoderDefinitions.getSubstringStyleClient(peer_id);
if (client_data != null) {
client = client_data.client_name;
String client_with_version = BTPeerIDByteDecoderDefinitions.getSubstringStyleClientVersion(client_data, peer_id, peer_id_bytes);
if (client_with_version != null) {return client_with_version;}
return client;
}
client = identifyAwkwardClient(peer_id_bytes);
if (client != null) {return client;}
return null;
| private static java.lang.String | decodeBitCometClient(java.lang.String peer_id, byte[] peer_id_bytes)
String mod_name = null;
if (peer_id.startsWith("exbc")) {mod_name = "";}
else if (peer_id.startsWith("FUTB")) {mod_name = "(Solidox Mod) ";}
else if (peer_id.startsWith("xUTB")) {mod_name = "(Mod 2) ";}
else {return null;}
boolean is_bitlord = (peer_id.substring(6, 10).equals("LORD"));
/**
* Older versions of BitLord are of the form x.yy, whereas new versions (1 and onwards),
* are of the form x.y. BitComet is of the form x.yy.
*/
String client_name = (is_bitlord) ? "BitLord " : "BitComet ";
String maj_version = BTPeerIDByteDecoderUtils.decodeNumericValueOfByte(peer_id_bytes[4]);
int min_version_length = (is_bitlord && !maj_version.equals("0")) ? 1 : 2;
return client_name + mod_name + maj_version + "." +
BTPeerIDByteDecoderUtils.decodeNumericValueOfByte(peer_id_bytes[5], min_version_length);
| private static java.lang.String | decodeBitSpiritClient(java.lang.String peer_id, byte[] peer_id_bytes)
if (!peer_id.substring(2, 4).equals("BS")) {return null;}
String version = BTPeerIDByteDecoderUtils.decodeNumericValueOfByte(peer_id_bytes[1]);
if ("0".equals(version)) {version = "1";}
return "BitSpirit v" + version;
| protected static java.lang.String | getPrintablePeerID(byte[] peer_id)
return getPrintablePeerID(peer_id, '-");
| protected static java.lang.String | getPrintablePeerID(byte[] peer_id, char fallback_char)
String sPeerID = "";
byte[] peerID = new byte[ peer_id.length ];
System.arraycopy( peer_id, 0, peerID, 0, peer_id.length );
try {
for (int i = 0; i < peerID.length; i++) {
int b = (0xFF & peerID[i]);
if (b < 32 || b > 127)
peerID[i] = (byte)fallback_char;
}
sPeerID = new String(peerID, Constants.BYTE_ENCODING);
}
catch (UnsupportedEncodingException ignore) {}
catch (Throwable e) {}
return( sPeerID );
| public static java.lang.String | identifyAwkwardClient(byte[] peer_id)
int iFirstNonZeroPos = 0;
iFirstNonZeroPos = 20;
for( int i=0; i < 20; i++ ) {
if( peer_id[i] != (byte)0 ) {
iFirstNonZeroPos = i;
break;
}
}
//Shareaza check
if( iFirstNonZeroPos == 0 ) {
boolean bShareaza = true;
for( int i=0; i < 16; i++ ) {
if( peer_id[i] == (byte)0 ) {
bShareaza = false;
break;
}
}
if( bShareaza ) {
for( int i=16; i < 20; i++ ) {
if( peer_id[i] != ( peer_id[i % 16] ^ peer_id[15 - (i % 16)] ) ) {
bShareaza = false;
break;
}
}
if( bShareaza ) return "Shareaza";
}
}
byte three = (byte)3;
if ((iFirstNonZeroPos == 9)
&& (peer_id[9] == three)
&& (peer_id[10] == three)
&& (peer_id[11] == three)) {
return "Snark";
}
if ((iFirstNonZeroPos == 12) && (peer_id[12] == (byte)97) && (peer_id[13] == (byte)97)) {
return "Experimental 3.2.1b2";
}
if ((iFirstNonZeroPos == 12) && (peer_id[12] == (byte)0) && (peer_id[13] == (byte)0)) {
return "Experimental 3.1";
}
if (iFirstNonZeroPos == 12) return "Mainline";
return null;
| public static void | logClientDiscrepancy(java.lang.String peer_id_name, java.lang.String handshake_name, java.lang.String discrepancy, java.lang.String protocol, byte[] peer_id)
if (!client_logging_allowed) {return;}
// Generate the string used that we will log.
String line_to_log = discrepancy + " [" + protocol + "]: ";
line_to_log += "\"" + peer_id_name + "\" / \"" + handshake_name + "\" ";
// We'll encode the name in byte form to help us decode it.
line_to_log += "[" + asUTF8ByteString(handshake_name) + "]";
// Avoid logging the same combination of things again.
boolean log_to_debug_out = Constants.isCVSVersion();
if (log_to_debug_out || LOG_UNKNOWN) {
// If this text has been recorded before, then avoid doing it again.
if (!logged_discrepancies.add(line_to_log)) {return;}
}
// Add peer ID bytes.
if (peer_id != null) {
line_to_log += ", Peer ID: " + ByteFormatter.encodeString(peer_id);
}
// Enable this block for now - just until we get more feedback about
// problematic clients.
if (log_to_debug_out) {
Debug.outNoStack("Conflicting peer identification: " + line_to_log);
}
if (!LOG_UNKNOWN) {return;}
FileWriter log = null;
File log_file = FileUtil.getUserFile("identification.log");
try {
log = new FileWriter(log_file, true);
log.write(line_to_log);
log.write("\n");
}
catch (Throwable e) {
Debug.printStackTrace(e);
}
finally {
try {if (log != null) log.close();}
catch (IOException ignore) {/*ignore*/}
}
| static void | logUnknownClient(byte[] peer_id_bytes)
logUnknownClient(peer_id_bytes, true);
| static void | logUnknownClient(byte[] peer_id_bytes, boolean to_debug_out)
if (!client_logging_allowed) {return;}
// Avoid logging the same client ID multiple times.
boolean log_to_debug_out = to_debug_out && Constants.isCVSVersion();
if (log_to_debug_out || LOG_UNKNOWN) {
// If the ID has been recorded before, then avoid doing it again.
if (!logged_ids.add(makePeerIDReadableAndUsable(peer_id_bytes))) {return;}
}
// Enable this block for now - just until we get more feedback about
// unknown clients.
if (log_to_debug_out) {
Debug.outNoStack("Unable to decode peer correctly - peer ID bytes: " + makePeerIDReadableAndUsable(peer_id_bytes));
}
if (!LOG_UNKNOWN) {return;}
FileWriter log = null;
File log_file = FileUtil.getUserFile("identification.log");
try {
log = new FileWriter(log_file, true);
logUnknownClient0(peer_id_bytes, log);
}
catch (Throwable e) {
Debug.printStackTrace(e);
}
finally {
try {if (log != null) log.close();}
catch (IOException ignore) {/*ignore*/}
}
| static void | logUnknownClient(java.lang.String peer_id)
try {logUnknownClient(peer_id.getBytes(Constants.BYTE_ENCODING));}
catch (UnsupportedEncodingException uee) {}
| private static void | logUnknownClient0(byte[] peer_id_bytes, java.io.Writer log)
String prop = System.getProperty("log.unknown.peerids");
LOG_UNKNOWN = prop != null && prop.equals("1");
String text = new String(peer_id_bytes, 0, 20, Constants.BYTE_ENCODING);
text = text.replace((char)12, (char)32);
text = text.replace((char)10, (char)32);
log.write("[" + text + "] "); // Readable
log.write(ByteFormatter.encodeString(peer_id_bytes) + " "); // Usable for assertion tests.
log.write("\n");
| public static void | main(java.lang.String[] args)
client_logging_allowed = false;
final String FAKE = MessageText.getString("PeerSocket.fake_client");
final String UNKNOWN = MessageText.getString("PeerSocket.unknown");
final String BAD_PEER_ID = MessageText.getString("PeerSocket.bad_peer_id");
System.out.println("Testing AZ style clients...");
assertDecode("Ares 2.0.5", "-AG2053-Em6o1EmvwLtD");
assertDecode("Artemis 2.5.2.0", "-AT2520-vEEt0wO6v0cr");
assertDecode("Azureus 2.2.0.0", "-AZ2200-6wfG2wk6wWLc");
assertDecode("BitRocket 0.3(32)", "-BR0332-!XVceSn(*KIl");
assertDecode("BitTorrent 6.0 Beta", "2D555436 3030422D A78DC290 C3F7BDE0 15EC3CC7");
assertDecode("FlashGet 1.80", "2D464730 31383075 F8005782 1359D64B B3DFD265");
assertDecode("GetRight 6.3", "-GR6300-13s3iFKmbArc");
assertDecode("Halite 0.2.9", "-HL0290-xUO*9ugvENUE");
assertDecode("KTorrent 1.1 RC1", "-KT11R1-693649213030");
assertDecode("libTorrent (Rakshasa) 0.11.2 / rTorrent*", "2D6C74304232302D0D739B93E6BE21FEBB557B20");
assertDecode("libtorrent (Rasterbar) 0.13.0", "-LT0D00-eZ0PwaDDr-~v"); // The latest version at time of writing is v0.12, but I'll assume this is valid.
assertDecode("LimeWire", "2D4C57303030312D31E0B3A0B46F7D4E954F4103");
assertDecode("Shareaza 2.1.3.2", "2D535A323133322D000000000000000000000000");
assertDecode("SymTorrent 1.17", "-ST0117-01234567890!");
assertDecode("Transmission 0.6", "-TR0006-01234567890!");
assertDecode("Transmission 0.72 (Dev)", "-TR072Z-zihst5yvg22f");
assertDecode("Transmission 0.72", "-TR0072-8vd6hrmp04an");
assertDecode("TuoTu 2.1.0", "-TT210w-dq!nWf~Qcext");
assertDecode("\u00B5Torrent 1.7.0 Beta", "2D555431 3730422D 92844644 1DB0A094 A01C01E5");
assertDecode("\u54c7\u560E (Vagaa) 2.6.4.4", "2D5647323634342D4FD62CDA69E235717E3BB94B");
assertDecode("Wyzo 0.3.0.0", "-WY0300-6huHF5Pr7Vde");
System.out.println();
// Shadow style clients.
System.out.println("Testing Shadow style clients...");
assertDecode("ABC", "A--------YMyoBPXYy2L"); // Seen this quite a bit - not sure that it is ABC, but I guess we should default to that...
assertDecode("ABC 2.6.9", "413236392D2D2D2D345077199FAEC4A673BECA01");
assertDecode("ABC 3.1", "A310--001v5Gysr4NxNK");
assertDecode("BitTornado 0.3.12", "T03C-----6tYolxhVUFS");
assertDecode("BitTornado 0.3.18", "T03I--008gY6iB6Aq27C");
assertDecode("BitTornado 0.3.9", "T0390----5uL5NvjBe2z");
assertDecode("Tribler 1", "R100--003hR6s07XWcov"); // Seen recently - is this really Tribler?
assertDecode("Tribler 3.7", "R37---003uApHy851-Pq");
System.out.println();
// Simple substring style clients.
System.out.println("Testing simple substring clients...");
assertDecode("Azureus 1", "417A7572 65757300 00000000 000000A0 76F0AEF7");
assertDecode("Azureus 2.0.3.2", "2D2D2D2D2D417A757265757354694E7A2A6454A7");
assertDecode("Hurricane Electric", "6172636C696768742E68652EA5860C157A5ADC35");
assertDecode("Pando", "Pando-6B511B691CAC2E"); // Seen recently, have they changed peer ID format?
assertDecode("\u00B5Torrent 1.7.0 RC", "2D55543137302D00AF8BC5ACCC4631481EB3EB60");
System.out.println();
// Version substring style clients.
System.out.println("Testing versioned substring clients...");
assertDecode("Bitlet 0.1", "4269744C657430319AEA4E02A09E318D70CCF47D");
assertDecode("BitsOnWheels", "-BOWP05-EPICNZOGQPHP"); // Seen in the wild - no idea what version that's meant to be - a pre-release?
assertDecode("Burst! 1.1.3", "Mbrst1-1-32e3c394b43");
assertDecode("Opera (Build 7685)", "OP7685f2c1495b1680bf");
assertDecode("Rufus 0.6.9", "00455253 416E6F6E 796D6F75 7382BE42 75024AE3");
assertDecode("BitTorrent DNA 1.0", "444E413031303030DD01C9B2DA689E6E02803E91");
assertDecode("BTuga Revolution 2.1", "BTM21abcdefghijklmno");
assertDecode("AllPeers 0.70rc30", "4150302E3730726333302D3E3EB87B31F241DBFE"); // AP0.70rc30->>-{1-A--]"
System.out.println();
// BitComet/Lord/Spirit
System.out.println("Testing BitComet/Lord/Spirit clients...");
assertDecode("BitComet 0.56", "6578626300387A4463102D6E9AD6723B339F35A9");
assertDecode("BitLord 0.56", "6578626300384C4F52443200048ECED57BD71028");
assertDecode("BitSpirit? (" + BAD_PEER_ID + ")", "4D342D302D322D2D6898D9D0CAF25E4555445030");
assertDecode("BitSpirit v2", "000242539B7ED3E058A8384AA748485454504254");
assertDecode("BitSpirit v3", "00034253 07248896 44C59530 8A5FF2CA 55445030");
System.out.println();
// Mainline style clients.
System.out.println("Testing new mainline style clients...");
assertDecode("Mainline 5.0.7", "M5-0-7--9aa757efd5be");
System.out.println();
// Various specialised clients.
System.out.println("Testing various specialised clients...");
assertDecode("Mainline", "0000000000000000000000004C53441933104277");
assertDecode(UNKNOWN + " [" + FAKE + ": ZipTorrent 1.6.0.0]", "-ZT1600-bLAdeY9rdjbe");
System.out.println();
// Unknown clients - may be random bytes.
System.out.println("Testing unknown (random byte?) clients...");
assertDecode(UNKNOWN + " [--------1}-/---A---<]", "0000000000000000317DA32F831FF041A515FE3C");
assertDecode(UNKNOWN + " [------- -- ------@(]", "000000DF05020020100020200008000000004028");
assertDecode(UNKNOWN + " [-----------D-y-I--aO]", "0000000000000000F106CE44F179A2498FAC614F");
assertDecode(UNKNOWN + " [--c--_-5-\\----t-#---]", "E7F163BB0E5FCD35005C09A11BC274C42385A1A0");
System.out.println();
// Unknown AZ style clients.
System.out.println("Testing unknown AZ style clients...");
String unknown_az;
unknown_az = MessageText.getString("PeerSocket.unknown_az_style", new String[]{"BD", "0.3.0.0"});
assertDecode(unknown_az, "-BD0300-1SGiRZ8uWpWH");
unknown_az = MessageText.getString("PeerSocket.unknown_az_style", new String[]{"wF", "2.2.0.0"});
assertDecode(unknown_az, "2D7746323230302D9DFF296B56AFC2DF751C609C");
unknown_az = MessageText.getString("PeerSocket.unknown_az_style", new String[]{"X1", "0.0.6.4"});
assertDecode(unknown_az, "2D5831303036342D12FB8A5B954153A114267F1F");
unknown_az = MessageText.getString("PeerSocket.unknown_az_style", new String[]{"bF", "2q00"}); // I made this one up.
assertDecode(unknown_az, "2D6246327130302D9DFF296B56AFC2DF751C609C");
System.out.println();
// Unknown Shadow style clients.
System.out.println("Testing unknown Shadow style clients...");
String unknown_shadow;
unknown_shadow = MessageText.getString("PeerSocket.unknown_shadow_style", new String[]{"B", "1.2"});
assertDecode(unknown_shadow, "B12------xgTofhetSVQ");
System.out.println();
// TODO
//assertDecode("KTorrent 2.2", "-KT22B1-695754334315"); // We could use the B1 information...
//assertDecode("KTorrent 2.1.4", "-KT2140-584815613993"); // Currently shows as 2.1.
//assertDecode("", "C8F2D9CD3A90455354426578626300362D2D2D92"); // Looks like a BitLord client - ESTBexbc?
//assertDecode("", "303030302D2D0000005433585859684B59584C72"); // Seen in the wild, appears to be a modified version of Azureus 2.5.0.0 (that's what was in the AZMP handshake)?
System.out.println("Done.");
| private static java.lang.String | makePeerIDReadableAndUsable(byte[] peer_id)
boolean as_ascii = true;
for (int i=0; i<peer_id.length; i++) {
int b = 0xFF & peer_id[i];
if (b < 32 || b > 127 || b == 10 || b == 9 || b==13) {
as_ascii = false;
break;
}
}
if (as_ascii) {
try {return new String(peer_id, Constants.BYTE_ENCODING);}
catch (UnsupportedEncodingException uee) {return "";}
}
else {return ByteFormatter.encodeString(peer_id);}
|
|