/*
* Created on 7 Jun 2007
* Created by Allan Crooks
* Copyright (C) 2007 Aelitis, All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* AELITIS, SAS au capital de 46,603.30 euros
* 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
*/
package com.aelitis.azureus.core.peermanager.utils;
/**
* @author Allan Crooks
*/
class BTPeerIDByteDecoderUtils {
public static String decodeMnemonic(char c) {
switch (c) {
case 'b':
case 'B':
return "Beta";
case 'X':
case 'x':
case 'Z':
return "(Dev)"; // Just for Transmission at the moment.
}
return null;
}
public static String decodeNumericValueOfByte(byte b) {
return String.valueOf((int)b & 0xFF);
}
public static String decodeNumericValueOfByte(byte b, int min_digits) {
String result = decodeNumericValueOfByte(b);
while (result.length() < min_digits) {result = "0" + result;}
return result;
}
public static String decodeNumericChar(char c) {
String result = decodeAlphaNumericChar(c);
if (result == null || result.length() == 1) {return result;}
return null;
}
public static String intchar(char c) {
String result = decodeNumericChar(c);
if (result == null) {throw new IllegalArgumentException("not an integer character: " + c);}
return result;
}
public static String decodeAlphaNumericChar(char c) {
if ('0' <= c && c <= '9') {
return String.valueOf(c);
}
else if ('A' <= c && c <= 'Z') {
return String.valueOf(10 + (c - 'A'));
}
else if ('a' <= c && c <= 'z') {
return String.valueOf(36 + (c - 'A'));
}
else if (c == '.') {
return "62";
}
return null;
}
public static boolean isAzStyle(String peer_id) {
if (peer_id.charAt(0) != '-') {return false;}
if (peer_id.charAt(7) == '-') {return true;}
/**
* Hack for FlashGet - it doesn't use the trailing dash.
* Also, LH-ABC has strayed into "forgetting about the delimiter" territory.
*
* In fact, the code to generate a peer ID for LH-ABC is based on BitTornado's,
* yet tries to give an Az style peer ID... oh dear.
*
* BT Next Evolution seems to be in the same boat as well.
*
* KTorrent 3 appears to use a dash rather than a final character.
*/
if (peer_id.substring(1, 3).equals("FG")) {return true;}
if (peer_id.substring(1, 3).equals("LH")) {return true;}
if (peer_id.substring(1, 3).equals("NE")) {return true;}
if (peer_id.substring(1, 3).equals("KT")) {return true;}
if (peer_id.substring(1, 3).equals("SP")) {return true;}
return false;
}
/**
* Checking whether a peer ID is Shadow style or not is a bit tricky.
*
* The BitTornado peer ID convention code is explained here:
* http://forums.degreez.net/viewtopic.php?t=7070
*
* The main thing we are interested in is the first six characters.
* Although the other characters are base64 characters, there's no
* guarantee that other clients which follow that style will follow
* that convention (though the fact that some of these clients use
* BitTornado in the core does blur the lines a bit between what is
* "style" and what is just common across clients).
*
* So if we base it on the version number information, there's another
* problem - there isn't the use of absolute delimiters (no fixed dash
* character, for example).
*
* There are various things we can do to determine how likely the peer
* ID is to be of that style, but for now, I'll keep it to a relatively
* simple check.
*
* We'll assume that no client uses the fifth version digit, so we'll
* expect a dash. We'll also assume that no client has reached version 10
* yet, so we expect the first two characters to be "letter,digit".
*
* We've seen some clients which don't appear to contain any version
* information, so we need to allow for that.
*/
public static boolean isShadowStyle(String peer_id) {
if (peer_id.charAt(5) != '-') {return false;}
if (!Character.isLetter(peer_id.charAt(0))) {return false;}
if (!(Character.isDigit(peer_id.charAt(1)) || peer_id.charAt(1) == '-')) {return false;}
// Find where the version number string ends.
int last_ver_num_index = 4;
for (; last_ver_num_index>0; last_ver_num_index--) {
if (peer_id.charAt(last_ver_num_index) != '-') {break;}
}
// For each digit in the version string, check it is a valid version identifier.
char c;
for (int i=1; i<=last_ver_num_index; i++) {
c = peer_id.charAt(i);
if (c == '-') {return false;}
if (decodeAlphaNumericChar(c) == null) {return false;}
}
return true;
}
public static boolean isMainlineStyle(String peer_id) {
/**
* One of the following styles will be used:
* Mx-y-z--
* Mx-yy-z-
*/
return peer_id.charAt(2) == '-' && peer_id.charAt(7) == '-' && (
peer_id.charAt(4) == '-' || peer_id.charAt(5) == '-');
}
public static boolean isPossibleSpoofClient(String peer_id) {
return peer_id.endsWith("UDP0") || peer_id.endsWith("HTTPBT");
}
public static String getMainlineStyleVersionNumber(String peer_id) {
boolean two_digit_in_middle = peer_id.charAt(5) == '-';
String middle_part = decodeNumericChar(peer_id.charAt(3));
if (two_digit_in_middle) {
middle_part = join(middle_part, decodeNumericChar(peer_id.charAt(4)));
}
return joinAsDotted(
decodeNumericChar(peer_id.charAt(1)), middle_part,
decodeNumericChar(peer_id.charAt(two_digit_in_middle ? 6 : 5))
);
}
public static String getShadowStyleVersionNumber(String peer_id) {
String ver_number = decodeAlphaNumericChar(peer_id.charAt(1));
if (ver_number == null) {return null;}
for (int i=2; i<6 && ver_number != null; i++) {
char c = peer_id.charAt(i);
if (c == '-') {break;}
ver_number = joinAsDotted(ver_number, decodeAlphaNumericChar(peer_id.charAt(i)));
}
// We'll strip off trailing redundant zeroes.
while (ver_number.endsWith(".0")) {ver_number = ver_number.substring(0, ver_number.length()-2);}
return ver_number;
}
public static String decodeAzStyleVersionNumber(String version_data, String version_scheme) {
char a = version_data.charAt(0);
char b = version_data.charAt(1);
char c = version_data.charAt(2);
char d = version_data.charAt(3);
/**
* Specialness for Transmission - thanks to BentMyWookie for the information,
* I'm sure he'll be reading this comment anyway... ;)
*/
if (version_scheme == BTPeerIDByteDecoderDefinitions.VER_AZ_TRANSMISSION_STYLE) {
// Very old client style: -TR0006- is 0.6.
if (version_data.startsWith("000")) {
version_scheme = "3.4";
}
// Previous client style: -TR0072- is 0.72.
else if (version_data.startsWith("00")) {
version_scheme = BTPeerIDByteDecoderDefinitions.VER_AZ_SKIP_FIRST_ONE_MAJ_TWO_MIN;
}
// Current client style: -TR072Z- is 0.72 (Dev).
else {
version_scheme = BTPeerIDByteDecoderDefinitions.VER_AZ_ONE_MAJ_TWO_MIN_PLUS_MNEMONIC;
}
}
if (version_scheme == BTPeerIDByteDecoderDefinitions.VER_AZ_FOUR_DIGITS) {
return intchar(a) + "." + intchar(b) + "." + intchar(c) + "." + intchar(d);
}
else if (version_scheme == BTPeerIDByteDecoderDefinitions.VER_AZ_THREE_DIGITS ||
version_scheme == BTPeerIDByteDecoderDefinitions.VER_AZ_THREE_DIGITS_PLUS_MNEMONIC ||
version_scheme == BTPeerIDByteDecoderDefinitions.VER_AZ_ONE_MAJ_TWO_MIN_PLUS_MNEMONIC) {
String result;
if (version_scheme == BTPeerIDByteDecoderDefinitions.VER_AZ_ONE_MAJ_TWO_MIN_PLUS_MNEMONIC) {
result = intchar(a) + "." + intchar(b) + intchar(c);
}
else {
result = intchar(a) + "." + intchar(b) + "." + intchar(c);
}
if (version_scheme == BTPeerIDByteDecoderDefinitions.VER_AZ_THREE_DIGITS_PLUS_MNEMONIC ||
version_scheme == BTPeerIDByteDecoderDefinitions.VER_AZ_ONE_MAJ_TWO_MIN_PLUS_MNEMONIC) {
String mnemonic = decodeMnemonic(d);
if (mnemonic != null) {result += " " + mnemonic;}
}
return result;
}
else if (version_scheme == BTPeerIDByteDecoderDefinitions.VER_AZ_TWO_MAJ_TWO_MIN) {
return (a == '0' ? "" : intchar(a)) + intchar(b) + "." + intchar(c) + intchar(d);
}
else if (version_scheme == BTPeerIDByteDecoderDefinitions.VER_AZ_LAST_THREE_DIGITS) {
return intchar(b) + "." + intchar(c) + "." + intchar(d);
}
else if (version_scheme == BTPeerIDByteDecoderDefinitions.VER_AZ_THREE_ALPHANUMERIC_DIGITS) {
return decodeAlphaNumericChar(a) + "." + decodeAlphaNumericChar(b) + "." + decodeAlphaNumericChar(c);
}
else if (version_scheme == BTPeerIDByteDecoderDefinitions.VER_AZ_SKIP_FIRST_ONE_MAJ_TWO_MIN) {
return intchar(b) + "." + intchar(c) + intchar(d);
}
else if (version_scheme == BTPeerIDByteDecoderDefinitions.VER_AZ_KTORRENT_STYLE) {
// Either something like this:
// 1.2 RC 4 [where 3 == 'R')
// 1.2 Dev [where 3 == 'D')
// 1.2 [where 3 doesn't equal the above]
switch (c) {
case 'R':
return intchar(a) + "." + intchar(b) + " RC" + intchar(d);
case 'D':
return intchar(a) + "." + intchar(b) + " Dev";
default:
return intchar(a) + "." + intchar(b);
}
}
else if (version_scheme.equals("1.234")) {
return intchar(a) + "." + intchar(b) + intchar(c) + intchar(d);
}
else if (version_scheme.equals("1.2(34)")) {
return intchar(a) + "." + intchar(b) + "(" + intchar(c) + intchar(d) + ")";
}
else if (version_scheme.equals("1.2.34")) {
return intchar(a) + "." + intchar(b) + "." + intchar(c) + intchar(d);
}
else if (version_scheme.equals("v1234")) {
return "v" + intchar(a) + intchar(b) + intchar(c) + intchar(d);
}
else if (version_scheme.equals("1.2")) {
return intchar(a) + "." + intchar(b);
}
else if (version_scheme.equals("3.4")) {
return intchar(c) + "." + intchar(d);
}
else if (version_scheme.equals("12.3-4")) {
return decodeAlphaNumericChar(a) + decodeAlphaNumericChar(b) + "." + decodeAlphaNumericChar(c) + "-" + decodeAlphaNumericChar(d);
}
else {
throw new RuntimeException("unknown AZ style version number scheme - " + version_scheme);
}
}
public static String getTwoByteThreePartVersion(byte b1, byte b2) {
String min_part = decodeNumericValueOfByte(b2, 2);
return joinAsDotted(decodeNumericValueOfByte(b1), min_part.substring(0, 1), min_part.substring(1, 2));
}
/**
* Look at the peer ID and just grab as many readable characters to form the version
* substring as possible.
*/
public static String extractReadableVersionSubstringFromPeerID(String peer_id) {
for (int i=0; i<peer_id.length(); i++) {
char c = peer_id.charAt(i);
// This is based on All Peers peer ID at the time of writing, e.g:
// AP0.70rc30->>...
if (Character.isLetter(c)) continue;
if (Character.isDigit(c)) continue;
if (c == '.') continue;
// Must be delimiter character.
return peer_id.substring(0, i);
}
return peer_id;
}
public static String decodeCustomVersionNumber(String version_data, String version_scheme) {
if (version_scheme == BTPeerIDByteDecoderDefinitions.VER_BLOCK) {
return version_data;
}
else if (version_scheme == BTPeerIDByteDecoderDefinitions.VER_DOTTED_BLOCK ||
version_scheme == BTPeerIDByteDecoderDefinitions.VER_BYTE_BLOCK_DOTTED_CHAR) {
int inc_size = (version_scheme == BTPeerIDByteDecoderDefinitions.VER_DOTTED_BLOCK) ? 2 : 1;
String result = version_data.substring(0, 1);
for (int i=0+inc_size; i<version_data.length(); i+=inc_size) {
result = joinAsDotted(result, String.valueOf(version_data.charAt(i)));
}
return result;
}
else if (version_scheme == BTPeerIDByteDecoderDefinitions.VER_BITS_ON_WHEELS) {
if (version_data.equals("A0C")) {return "1.0.6";}
else if (version_data.equals("A0B")) {return "1.0.5";}
throw new RuntimeException("Unknown BitsOnWheels version number - " + version_data);
}
else {
throw new RuntimeException("unknown custom version number scheme - " + version_scheme);
}
}
private static String join(String a, String b) {
if (a == null) {return null;}
if (b == null) {return null;}
return a + b;
}
private static String joinAsDotted(String a, String b) {
if (a == null) {return null;}
if (b == null) {return null;}
return a + "." + b;
}
private static String joinAsDotted(String a, String b, String c) {
if (a == null) {return null;}
if (b == null) {return null;}
if (c == null) {return null;}
return a + "." + b + "." + c;
}
}