Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
seanoflynn committed May 25, 2016
0 parents commit e60d2c5
Show file tree
Hide file tree
Showing 14 changed files with 2,644 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
@@ -0,0 +1,3 @@
bin/
obj/
packages/
296 changes: 296 additions & 0 deletions BitTorrent/BEncoding.cs
@@ -0,0 +1,296 @@
using System;
using System.Text;
using System.Linq;
using System.Collections.Generic;
using System.IO;

namespace BitTorrent
{
public static class BEncoding
{
private static byte DictionaryStart = System.Text.Encoding.UTF8.GetBytes("d")[0]; // 100
private static byte DictionaryEnd = System.Text.Encoding.UTF8.GetBytes("e")[0]; // 101
private static byte ListStart = System.Text.Encoding.UTF8.GetBytes("l")[0]; // 108
private static byte ListEnd = System.Text.Encoding.UTF8.GetBytes("e")[0]; // 101
private static byte NumberStart = System.Text.Encoding.UTF8.GetBytes("i")[0]; // 105
private static byte NumberEnd = System.Text.Encoding.UTF8.GetBytes("e")[0]; // 101
private static byte ByteArrayDivider = System.Text.Encoding.UTF8.GetBytes(":")[0]; // 58

#region Decode

public static object Decode(byte[] bytes)
{
IEnumerator<byte> enumerator = ((IEnumerable<byte>)bytes).GetEnumerator();
enumerator.MoveNext();

return DecodeNextObject(enumerator);
}

public static object DecodeFile(string path)
{
if (!File.Exists(path))
throw new FileNotFoundException("unable to find file: " + path);

byte[] bytes = File.ReadAllBytes(path);

return BEncoding.Decode(bytes);
}

private static object DecodeNextObject(IEnumerator<byte> enumerator)
{
if (enumerator.Current == DictionaryStart)
return DecodeDictionary(enumerator);

if (enumerator.Current == ListStart)
return DecodeList(enumerator);

if (enumerator.Current == NumberStart)
return DecodeNumber(enumerator);

return DecodeByteArray(enumerator);
}

private static Dictionary<string,object> DecodeDictionary(IEnumerator<byte> enumerator)
{
Dictionary<string,object> dict = new Dictionary<string,object>();
List<string> keys = new List<string>();

// keep decoding objects until we hit the end flag
while (enumerator.MoveNext())
{
if( enumerator.Current == DictionaryEnd )
break;

// all keys are valid UTF8 strings
string key = Encoding.UTF8.GetString(DecodeByteArray(enumerator));
enumerator.MoveNext();
object val = DecodeNextObject(enumerator);

keys.Add(key);
dict.Add(key, val);
}

// verify incoming dictionary is sorted correctly
// we will not be able to create an identical encoding otherwise
var sortedKeys = keys.OrderBy(x => BitConverter.ToString(Encoding.UTF8.GetBytes(x)));
if (!keys.SequenceEqual(sortedKeys))
throw new Exception("error loading dictionary: keys not sorted");

return dict;
}

private static List<object> DecodeList(IEnumerator<byte> enumerator)
{
List<object> list = new List<object>();

// keep decoding objects until we hit the end flag
while (enumerator.MoveNext())
{
if( enumerator.Current == ListEnd )
break;

list.Add(DecodeNextObject(enumerator));
}

return list;
}

private static byte[] DecodeByteArray(IEnumerator<byte> enumerator)
{
List<byte> lengthBytes = new List<byte>();

// scan until we get to divider
do
{
if( enumerator.Current == ByteArrayDivider )
break;

lengthBytes.Add(enumerator.Current);
}
while (enumerator.MoveNext());

string lengthString = System.Text.Encoding.UTF8.GetString(lengthBytes.ToArray());

int length;
if (!Int32.TryParse(lengthString, out length))
throw new Exception("unable to parse length of byte array");

// now read in the actual byte array
byte[] bytes = new byte[length];

for (int i = 0; i < length; i++)
{
enumerator.MoveNext();
bytes[i] = enumerator.Current;
}

return bytes;
}

private static long DecodeNumber(IEnumerator<byte> enumerator)
{
List<byte> bytes = new List<byte>();

// keep pulling bytes until we hit the end flag
while (enumerator.MoveNext())
{
if (enumerator.Current == NumberEnd)
break;

bytes.Add(enumerator.Current);
}

string numAsString = Encoding.UTF8.GetString(bytes.ToArray());

return Int64.Parse(numAsString);
}

#endregion

#region Encode

public static byte[] Encode(object obj)
{
MemoryStream buffer = new MemoryStream();

EncodeNextObject(buffer, obj);

return buffer.ToArray();
}

public static void EncodeToFile(object obj, string path)
{
File.WriteAllBytes(path, Encode(obj));
}

private static void EncodeNextObject(MemoryStream buffer, object obj)
{
if (obj is byte[])
EncodeByteArray(buffer, (byte[])obj);
else if (obj is string)
EncodeString(buffer, (string)obj);
else if (obj is long)
EncodeNumber(buffer, (long)obj);
else if (obj.GetType() == typeof(List<object>))
EncodeList(buffer, (List<object>)obj);
else if (obj.GetType() == typeof(Dictionary<string,object>))
EncodeDictionary(buffer, (Dictionary<string,object>)obj);
else
throw new Exception("unable to encode type " + obj.GetType());
}

private static void EncodeByteArray(MemoryStream buffer, byte[] body)
{
buffer.Append(Encoding.UTF8.GetBytes(Convert.ToString(body.Length)));
buffer.Append(ByteArrayDivider);
buffer.Append(body);
}

private static void EncodeString(MemoryStream buffer, string input)
{
EncodeByteArray(buffer, Encoding.UTF8.GetBytes(input));
}

private static void EncodeNumber(MemoryStream buffer, long input)
{
buffer.Append(NumberStart);
buffer.Append(Encoding.UTF8.GetBytes(Convert.ToString(input)));
buffer.Append(NumberEnd);
}

private static void EncodeList(MemoryStream buffer, List<object> input)
{
buffer.Append(ListStart);
foreach (var item in input)
EncodeNextObject(buffer, item);
buffer.Append(ListEnd);
}

private static void EncodeDictionary(MemoryStream buffer, Dictionary<string,object> input)
{
buffer.Append(DictionaryStart);

// we need to sort the keys by their raw bytes, not the string
var sortedKeys = input.Keys.ToList().OrderBy(x => BitConverter.ToString(Encoding.UTF8.GetBytes(x)));

foreach (var key in sortedKeys)
{
EncodeString(buffer, key);
EncodeNextObject(buffer, input[key]);
}
buffer.Append(DictionaryEnd);
}

#endregion

#region Helper

public static string GetFormattedString(object obj, int depth = 0)
{
string output = "";

if (obj is byte[])
output += GetFormattedString((byte[])obj);
else if (obj is long)
output += GetFormattedString((long)obj);
else if (obj.GetType() == typeof(List<object>))
output += GetFormattedString((List<object>)obj, depth);
else if (obj.GetType() == typeof(Dictionary<string,object>))
output += GetFormattedString((Dictionary<string,object>)obj, depth);
else
throw new Exception("unable to encode type " + obj.GetType());

return output;
}

private static string GetFormattedString(byte[] obj)
{
return String.Join("", obj.Select(x => x.ToString("x2"))) + " (" + Encoding.UTF8.GetString(obj) + ")";
}

private static string GetFormattedString(long obj)
{
return obj.ToString();
}

private static string GetFormattedString(List<object> obj, int depth)
{
string pad1 = new String(' ', depth * 2);
string pad2 = new String(' ', (depth+1) * 2);

if (obj.Count < 1)
return "[]";

if (obj[0].GetType() == typeof(Dictionary<string,object>))
return "\n" + pad1 + "[" + String.Join(",", obj.Select(x => pad2 + GetFormattedString(x, depth + 1))) + "\n" + pad1 + "]";

return "[ " + String.Join(", ", obj.Select(x => GetFormattedString(x))) + " ]";
}

private static string GetFormattedString(Dictionary<string,object> obj, int depth)
{
string pad1 = new String(' ', depth * 2);
string pad2 = new String(' ', (depth+1) * 2);

return (depth>0?"\n":"") + pad1 + "{" + String.Join("", obj.Select(x => "\n" + pad2 + (x.Key+":").PadRight(15,' ') + GetFormattedString(x.Value, depth+1))) + "\n" + pad1 + "}";
//return String.Join("", obj.Select(x => "\n" + pad2 + (x.Key+":").PadRight(15,' ') + GetFormattedString(x.Value, depth+1)));
}

#endregion
}

// source: Fredrik Mörk (http://stackoverflow.com/a/4015634)
public static class MemoryStreamExtensions
{
public static void Append(this MemoryStream stream, byte value)
{
stream.Append(new[] { value });
}

public static void Append(this MemoryStream stream, byte[] values)
{
stream.Write(values, 0, values.Length);
}
}
}

50 changes: 50 additions & 0 deletions BitTorrent/BitTorrent.csproj
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{05551E96-A709-4790-9256-77B375CEF51E}</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>BitTorrent</RootNamespace>
<AssemblyName>BitTorrent</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>full</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="MiscUtil">
<HintPath>..\packages\JonSkeet.MiscUtil.0.1\lib\net35-Client\MiscUtil.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="BEncoding.cs" />
<Compile Include="Torrent.cs" />
<Compile Include="Client.cs" />
<Compile Include="Peer.cs" />
<Compile Include="Tracker.cs" />
<Compile Include="Throttle.cs" />
<Compile Include="Log.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
</Project>

0 comments on commit e60d2c5

Please sign in to comment.