using System; using System.Collections.Generic; using System.Text; namespace MonoTorrent { public class InfoHash : IEquatable { static Dictionary base32DecodeTable; static InfoHash() { base32DecodeTable = new Dictionary(); string table = "abcdefghijklmnopqrstuvwxyz234567"; for (int i = 0; i < table.Length; i++) base32DecodeTable[table[i]] = (byte)i; } byte[] hash; internal byte[] Hash { get { return hash; } } public InfoHash(byte[] infoHash) { Check.InfoHash(infoHash); if (infoHash.Length != 20) throw new ArgumentException("Infohash must be exactly 20 bytes long"); hash = (byte[])infoHash.Clone(); } public override bool Equals(object obj) { return Equals(obj as InfoHash); } public bool Equals(byte[] other) { return other == null || other.Length != 20 ? false : Toolbox.ByteMatch(Hash, other); } public bool Equals(InfoHash other) { return this == other; } public override int GetHashCode() { // Equality is based generally on checking 20 positions, checking 4 should be enough // for the hashcode as infohashes are randomly distributed. return Hash[0] | (Hash[1] << 8) | (Hash[2] << 16) | (Hash[3] << 24); } public byte[] ToArray() { return (byte[])hash.Clone(); } public string ToHex() { StringBuilder sb = new StringBuilder(40); for (int i = 0; i < hash.Length; i++) { string hex = hash[i].ToString("X"); if (hex.Length != 2) sb.Append("0"); sb.Append(hex); } return sb.ToString(); } public override string ToString() { return BitConverter.ToString(hash); } public string UrlEncode() { return UriHelper.UrlEncode(Hash); } public static bool operator ==(InfoHash left, InfoHash right) { if ((object)left == null) return (object)right == null; if ((object)right == null) return false; return Toolbox.ByteMatch(left.Hash, right.Hash); } public static bool operator !=(InfoHash left, InfoHash right) { return !(left == right); } public static InfoHash FromBase32(string infoHash) { Check.InfoHash(infoHash); if (infoHash.Length != 32) throw new ArgumentException("Infohash must be a base32 encoded 32 character string"); infoHash = infoHash.ToLower(); int infohashOffset = 0; byte[] hash = new byte[20]; var temp = new byte[8]; for (int i = 0; i < hash.Length;) { for (int j = 0; j < 8; j++) if (!base32DecodeTable.TryGetValue(infoHash[infohashOffset++], out temp[j])) throw new ArgumentException("infoHash", "Value is not a valid base32 encoded string"); //8 * 5bits = 40 bits = 5 bytes hash[i++] = (byte)((temp[0] << 3) | (temp[1] >> 2)); hash[i++] = (byte)((temp[1] << 6) | (temp[2] << 1) | (temp[3] >> 4)); hash[i++] = (byte)((temp[3] << 4) | (temp[4] >> 1)); hash[i++] = (byte)((temp[4] << 7) | (temp[5] << 2) | (temp[6] >> 3)); hash[i++] = (byte)((temp[6] << 5) | temp[7]); } return new InfoHash(hash); } public static InfoHash FromHex(string infoHash) { Check.InfoHash(infoHash); if (infoHash.Length != 40) throw new ArgumentException("Infohash must be 40 characters long"); byte[] hash = new byte[20]; for (int i = 0; i < hash.Length; i++) hash[i] = byte.Parse(infoHash.Substring(i * 2, 2), System.Globalization.NumberStyles.HexNumber); return new InfoHash(hash); } public static InfoHash FromMagnetLink(string magnetLink) { Check.MagnetLink(magnetLink); if (!magnetLink.StartsWith("magnet:?")) throw new ArgumentException("Invalid magnet link format"); magnetLink = magnetLink.Substring("magnet:?".Length); int hashStart = magnetLink.IndexOf("xt=urn:btih:"); if (hashStart == -1) throw new ArgumentException("Magnet link does not contain an infohash"); hashStart += "xt=urn:btih:".Length; int hashEnd = magnetLink.IndexOf('&', hashStart); if (hashEnd == -1) hashEnd = magnetLink.Length; switch (hashEnd - hashStart) { case 32: return FromBase32(magnetLink.Substring(hashStart, 32)); case 40: return FromHex(magnetLink.Substring(hashStart, 40)); default: throw new ArgumentException("Infohash must be base32 or hex encoded."); } } public static InfoHash UrlDecode(string infoHash) { Check.InfoHash(infoHash); return new InfoHash(UriHelper.UrlDecode(infoHash)); } } }