You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
170 lines
5.5 KiB
170 lines
5.5 KiB
11 years ago
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Text;
|
||
|
|
||
|
namespace MonoTorrent
|
||
|
{
|
||
|
public class InfoHash : IEquatable <InfoHash>
|
||
|
{
|
||
|
static Dictionary<char, byte> base32DecodeTable;
|
||
|
|
||
|
static InfoHash()
|
||
|
{
|
||
|
base32DecodeTable = new Dictionary<char, byte>();
|
||
|
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));
|
||
|
}
|
||
|
}
|
||
|
}
|