using System; using System.IO; using System.Collections.Generic; namespace MonoTorrent.BEncoding { /// /// Class representing a BEncoded Dictionary /// public class BEncodedDictionary : BEncodedValue, IDictionary { #region Member Variables private SortedDictionary dictionary; #endregion #region Constructors /// /// Create a new BEncodedDictionary /// public BEncodedDictionary() { this.dictionary = new SortedDictionary(); } #endregion #region Encode/Decode Methods /// /// Encodes the dictionary to a byte[] /// /// The buffer to encode the data to /// The offset to start writing the data to /// public override int Encode(byte[] buffer, int offset) { int written = 0; //Dictionaries start with 'd' buffer[offset] = (byte)'d'; written++; foreach (KeyValuePair keypair in this) { written += keypair.Key.Encode(buffer, offset + written); written += keypair.Value.Encode(buffer, offset + written); } // Dictionaries end with 'e' buffer[offset + written] = (byte)'e'; written++; return written; } /// /// /// /// internal override void DecodeInternal(RawReader reader) { DecodeInternal(reader, reader.StrictDecoding); } private void DecodeInternal(RawReader reader, bool strictDecoding) { BEncodedString key = null; BEncodedValue value = null; BEncodedString oldkey = null; if (reader.ReadByte() != 'd') throw new BEncodingException("Invalid data found. Aborting"); // Remove the leading 'd' while ((reader.PeekByte() != -1) && (reader.PeekByte() != 'e')) { key = (BEncodedString)BEncodedValue.Decode(reader); // keys have to be BEncoded strings if (oldkey != null && oldkey.CompareTo(key) > 0) if (strictDecoding) throw new BEncodingException(String.Format( "Illegal BEncodedDictionary. The attributes are not ordered correctly. Old key: {0}, New key: {1}", oldkey, key)); oldkey = key; value = BEncodedValue.Decode(reader); // the value is a BEncoded value dictionary.Add(key, value); } if (reader.ReadByte() != 'e') // remove the trailing 'e' throw new BEncodingException("Invalid data found. Aborting"); } public static BEncodedDictionary DecodeTorrent(byte[] bytes) { return DecodeTorrent(new MemoryStream(bytes)); } public static BEncodedDictionary DecodeTorrent(Stream s) { return DecodeTorrent(new RawReader(s)); } /// /// Special decoding method for torrent files - allows dictionary attributes to be out of order for the /// overall torrent file, but imposes strict rules on the info dictionary. /// /// public static BEncodedDictionary DecodeTorrent(RawReader reader) { BEncodedString key = null; BEncodedValue value = null; BEncodedDictionary torrent = new BEncodedDictionary(); if (reader.ReadByte() != 'd') throw new BEncodingException("Invalid data found. Aborting"); // Remove the leading 'd' while ((reader.PeekByte() != -1) && (reader.PeekByte() != 'e')) { key = (BEncodedString)BEncodedValue.Decode(reader); // keys have to be BEncoded strings if (reader.PeekByte() == 'd') { value = new BEncodedDictionary(); if (key.Text.ToLower().Equals("info")) ((BEncodedDictionary)value).DecodeInternal(reader, true); else ((BEncodedDictionary)value).DecodeInternal(reader, false); } else value = BEncodedValue.Decode(reader); // the value is a BEncoded value torrent.dictionary.Add(key, value); } if (reader.ReadByte() != 'e') // remove the trailing 'e' throw new BEncodingException("Invalid data found. Aborting"); return torrent; } #endregion #region Helper Methods /// /// Returns the size of the dictionary in bytes using UTF8 encoding /// /// public override int LengthInBytes() { int length = 0; length += 1; // Dictionaries start with 'd' foreach (KeyValuePair keypair in this.dictionary) { length += keypair.Key.LengthInBytes(); length += keypair.Value.LengthInBytes(); } length += 1; // Dictionaries end with 'e' return length; } #endregion #region Overridden Methods public override bool Equals(object obj) { BEncodedValue val; BEncodedDictionary other = obj as BEncodedDictionary; if (other == null) return false; if (this.dictionary.Count != other.dictionary.Count) return false; foreach (KeyValuePair keypair in this.dictionary) { if (!other.TryGetValue(keypair.Key, out val)) return false; if (!keypair.Value.Equals(val)) return false; } return true; } public override int GetHashCode() { int result = 0; foreach (KeyValuePair keypair in dictionary) { result ^= keypair.Key.GetHashCode(); result ^= keypair.Value.GetHashCode(); } return result; } public override string ToString() { return System.Text.Encoding.UTF8.GetString(Encode()); } #endregion #region IDictionary and IList methods public void Add(BEncodedString key, BEncodedValue value) { this.dictionary.Add(key, value); } public void Add(KeyValuePair item) { this.dictionary.Add(item.Key, item.Value); } public void Clear() { this.dictionary.Clear(); } public bool Contains(KeyValuePair item) { if (!this.dictionary.ContainsKey(item.Key)) return false; return this.dictionary[item.Key].Equals(item.Value); } public bool ContainsKey(BEncodedString key) { return this.dictionary.ContainsKey(key); } public void CopyTo(KeyValuePair[] array, int arrayIndex) { this.dictionary.CopyTo(array, arrayIndex); } public int Count { get { return this.dictionary.Count; } } //public int IndexOf(KeyValuePair item) //{ // return this.dictionary.IndexOf(item); //} //public void Insert(int index, KeyValuePair item) //{ // this.dictionary.Insert(index, item); //} public bool IsReadOnly { get { return false; } } public bool Remove(BEncodedString key) { return this.dictionary.Remove(key); } public bool Remove(KeyValuePair item) { return this.dictionary.Remove(item.Key); } //public void RemoveAt(int index) //{ // this.dictionary.RemoveAt(index); //} public bool TryGetValue(BEncodedString key, out BEncodedValue value) { return this.dictionary.TryGetValue(key, out value); } public BEncodedValue this[BEncodedString key] { get { return this.dictionary[key]; } set { this.dictionary[key] = value; } } //public KeyValuePair this[int index] //{ // get { return this.dictionary[index]; } // set { this.dictionary[index] = value; } //} public ICollection Keys { get { return this.dictionary.Keys; } } public ICollection Values { get { return this.dictionary.Values; } } public IEnumerator> GetEnumerator() { return this.dictionary.GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return this.dictionary.GetEnumerator(); } #endregion } }