/* * 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; } }