756 lines
24 KiB
C#
756 lines
24 KiB
C#
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Net.Http;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using Newtonsoft.Json;
|
|
using Tesses.VirtualFilesystem;
|
|
|
|
using System.Security.Cryptography;
|
|
|
|
using System.Collections;
|
|
using System.Linq;
|
|
namespace Tesses.CMS
|
|
{
|
|
public class Torrent
|
|
{
|
|
public string UrlList {get;set;}="";
|
|
private BencodedDictionary torrent=null;
|
|
public BencodedDictionary ToTorrentFile()
|
|
{
|
|
if(torrent != null) return torrent;
|
|
BencodedDictionary dict=new BencodedDictionary();
|
|
if(this.AnnounceList.Count> 0)
|
|
{
|
|
dict["announce"] = (BencodedString)AnnounceList[0];
|
|
var al = new BencodedList();
|
|
foreach(var item in AnnounceList)
|
|
{
|
|
BencodedList ls2=new BencodedList();
|
|
ls2.Add((BencodedString)item);
|
|
al.Add(ls2);
|
|
}
|
|
dict["announce-list"] = al;
|
|
}
|
|
if(!string.IsNullOrWhiteSpace(UrlList))
|
|
{
|
|
dict["url-list"] = (BencodedString)UrlList;
|
|
}
|
|
dict["created by"] = (BencodedString)CreatedBy;
|
|
dict["creation date"] = new BencodedNumber(CreationDate.ToUnixTimeSeconds());
|
|
var info = new BencodedDictionary();
|
|
|
|
|
|
byte[] pieces = new byte[Info.Pieces.Count * 20];
|
|
for(int i = 0;i< Info.Pieces.Count;i++)
|
|
{
|
|
Array.Copy(Info.Pieces[i],0,pieces,i*20,20);
|
|
}
|
|
|
|
if(Info.Files.Count > 0)
|
|
{
|
|
BencodedList ls=new BencodedList();
|
|
foreach(var item in Info.Files)
|
|
{
|
|
BencodedDictionary file=new BencodedDictionary();
|
|
file["length"] = new BencodedNumber(item.Length);
|
|
BencodedList paths = new BencodedList();
|
|
foreach(var path in item.Path)
|
|
{
|
|
paths.Add((BencodedString)path);
|
|
}
|
|
file["path"] = paths;
|
|
|
|
ls.Add(file);
|
|
}
|
|
info["files"] = ls;
|
|
}
|
|
else
|
|
{
|
|
info["length"] = new BencodedNumber(Info.Length);
|
|
}
|
|
|
|
|
|
info["name"] = (BencodedString)Info.Name;
|
|
info["piece length"] = new BencodedNumber(Info.PieceLength);
|
|
|
|
info["pieces"] = new BencodedString(pieces);
|
|
|
|
dict["info"] = info;
|
|
return dict;
|
|
}
|
|
public static Torrent FromTorrentFile(BencodedData file)
|
|
{
|
|
var torrent = file as BencodedDictionary;
|
|
if(torrent == null) return new Torrent();
|
|
Torrent torrentObj= new Torrent();
|
|
torrentObj.torrent = torrent;
|
|
if(torrent.TryGetValue("url-list",out var urlList))
|
|
{
|
|
torrentObj.UrlList = urlList.ToString();
|
|
}
|
|
if(torrent.TryGetValue("created by", out var created_by))
|
|
{
|
|
torrentObj.CreatedBy = created_by.ToString();
|
|
}
|
|
if(torrent.TryGetValue("creation date", out var creation_date))
|
|
{
|
|
var creationdate = creation_date as BencodedNumber;
|
|
if(creationdate != null)
|
|
{
|
|
torrentObj.CreationDate = DateTimeOffset.FromUnixTimeSeconds(creationdate.Value);
|
|
}
|
|
}
|
|
if(torrent.TryGetValue("announce-list", out var announce_list))
|
|
{
|
|
var announcelist = announce_list as BencodedList;
|
|
if(announcelist != null)
|
|
foreach(var item in announcelist)
|
|
{
|
|
var ls0 = item as BencodedList;
|
|
if(ls0 != null)
|
|
foreach(var item0 in ls0)
|
|
torrentObj.AnnounceList.Add(item0.ToString());
|
|
}
|
|
}
|
|
if(torrent.TryGetValue("announce", out var announce))
|
|
{
|
|
if(torrentObj.AnnounceList.Count == 0)
|
|
torrentObj.AnnounceList.Add(announce.ToString());
|
|
}
|
|
if(torrent.TryGetValue("info", out var info))
|
|
{
|
|
var info_dict = info as BencodedDictionary;
|
|
if(info_dict != null)
|
|
{
|
|
torrentObj.InfoHash = torrentObj.GetInfoHash(info_dict);
|
|
if(info_dict.TryGetValue("name",out var name))
|
|
{
|
|
torrentObj.Info.Name = name.ToString();
|
|
}
|
|
if(info_dict.TryGetValue("piece length",out var piece_length))
|
|
{
|
|
var piecelength = piece_length as BencodedNumber;
|
|
if(piecelength != null)
|
|
{
|
|
torrentObj.Info.PieceLength = (int)piecelength.Value;
|
|
}
|
|
}
|
|
|
|
if(info_dict.TryGetValue("files",out var files_list))
|
|
{
|
|
var files = files_list as BencodedList;
|
|
if(files != null)
|
|
{
|
|
long offset = 0;
|
|
foreach(var _file in files)
|
|
{
|
|
var _file_dict = _file as BencodedDictionary;
|
|
if(_file_dict != null)
|
|
{
|
|
TorrentInfoFile torrfile=new TorrentInfoFile();
|
|
torrfile.Offset = offset;
|
|
|
|
if(_file_dict.TryGetValue("length", out var length2))
|
|
{
|
|
var l = length2 as BencodedNumber;
|
|
if(l != null)
|
|
offset += torrfile.Length = l.Value;
|
|
}
|
|
if(_file_dict.TryGetValue("path", out var path))
|
|
{
|
|
var pathlist = path as BencodedList;
|
|
if(pathlist != null)
|
|
torrfile.Path = pathlist.Select<BencodedData,string>(e=>e.ToString()).ToArray();
|
|
}
|
|
|
|
|
|
torrentObj.Info.Files.Add(torrfile);
|
|
}
|
|
}
|
|
torrentObj.Info.Length = offset;
|
|
}
|
|
}
|
|
if(info_dict.TryGetValue("length", out var _length))
|
|
{
|
|
var length = _length as BencodedNumber;
|
|
if(length != null)
|
|
torrentObj.Info.Length = length.Value;
|
|
}
|
|
if(info_dict.TryGetValue("pieces",out var _pieces))
|
|
{
|
|
var pieces = _pieces as BencodedString;
|
|
if(pieces !=null)
|
|
{
|
|
byte[] array=(byte[])pieces;
|
|
int noPieces=pieces.Length / 20;
|
|
for(int i = 0;i<noPieces;i++)
|
|
{
|
|
byte[] piece = new byte[20];
|
|
Array.Copy(array, i*20, piece,0,20);
|
|
torrentObj.Info.Pieces.Add(piece);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
return torrentObj;
|
|
}
|
|
public DateTimeOffset CreationDate {get;set;}=DateTimeOffset.Now;
|
|
public List<string> AnnounceList {get;set;}=new List<string>();
|
|
public string CreatedBy {get;set;}="TessesCMS";
|
|
public byte[] InfoHash {get;set;}=null;
|
|
public async Task<byte[]> GetInfoHashAsync()
|
|
{
|
|
if(InfoHash != null) return InfoHash;
|
|
using(MemoryStream memoryStream=new MemoryStream())
|
|
{
|
|
await ToTorrentFile()["info"].WriteToStreamAsync(memoryStream);
|
|
using(var sha1=SHA1Managed.Create())
|
|
return sha1.ComputeHash(memoryStream);
|
|
}
|
|
}
|
|
public byte[] GetInfoHash()
|
|
{
|
|
if(InfoHash != null) return InfoHash;
|
|
return GetInfoHash(ToTorrentFile()["info"]);
|
|
}
|
|
internal byte[] GetInfoHash(BencodedData data)
|
|
{
|
|
|
|
using(MemoryStream memoryStream=new MemoryStream())
|
|
{
|
|
data.WriteToStream(memoryStream);
|
|
using(var sha1=SHA1Managed.Create())
|
|
return sha1.ComputeHash(memoryStream);
|
|
}
|
|
}
|
|
|
|
public TorrentInfo Info {get;set;}=new TorrentInfo();
|
|
}
|
|
|
|
public class TorrentInfo
|
|
{
|
|
public List<TorrentInfoFile> Files {get;set;}=new List<TorrentInfoFile>();
|
|
public string Name {get;set;}
|
|
|
|
public long Length {get;set;}
|
|
|
|
public int PieceLength {get;set;}
|
|
|
|
|
|
public List<byte[]> Pieces {get;set;}=new List<byte[]>();
|
|
}
|
|
|
|
public class TorrentInfoFile
|
|
{
|
|
public long Offset {get;set;}
|
|
|
|
public long Length {get;set;}
|
|
|
|
public string[] Path {get;set;}
|
|
|
|
public override string ToString()
|
|
{
|
|
return System.IO.Path.Combine(Path);
|
|
}
|
|
public UnixPath ToUnixPath()
|
|
{
|
|
return new UnixPath(Path);
|
|
}
|
|
}
|
|
public abstract class BencodedData
|
|
{
|
|
internal static async Task<int> ReadByteAsync(Stream strm)
|
|
{
|
|
byte[] buffer=new byte[1];
|
|
if(await strm.ReadAsync(buffer,0,1) == 0) return -1;
|
|
return buffer[0];
|
|
}
|
|
public static BencodedData ReadFromStream(Stream strm)
|
|
{
|
|
int b=strm.ReadByte();
|
|
switch(b)
|
|
{
|
|
case 'e':
|
|
return null;
|
|
case 'i':
|
|
return BencodedNumber.ReadIntFromStream(strm);
|
|
case 'l':
|
|
return BencodedList.ReadListFromStream(strm);
|
|
case 'd':
|
|
return BencodedDictionary.ReadDictionaryFromStream(strm);
|
|
default:
|
|
{
|
|
StringBuilder sb= new StringBuilder();
|
|
sb.Append((char)b);
|
|
b = strm.ReadByte();
|
|
while(b != ':' && b !=-1)
|
|
{
|
|
sb.Append((char)b);
|
|
b = strm.ReadByte();
|
|
}
|
|
if(int.TryParse(sb.ToString(), out var number))
|
|
{
|
|
byte[] str = new byte[number];
|
|
if(strm.Read(str,0,str.Length) != str.Length)
|
|
{
|
|
return (BencodedString)"";
|
|
}
|
|
return new BencodedString(str);
|
|
}
|
|
return (BencodedString)"";
|
|
}
|
|
}
|
|
}
|
|
public static async Task<BencodedData> ReadFromStreamAsync(Stream strm)
|
|
{
|
|
int b=await ReadByteAsync(strm);
|
|
switch(b)
|
|
{
|
|
case 'e':
|
|
return null;
|
|
case 'i':
|
|
return await BencodedNumber.ReadIntFromStreamAsync(strm);
|
|
case 'l':
|
|
return await BencodedList.ReadListFromStreamAsync(strm);
|
|
case 'd':
|
|
return await BencodedDictionary.ReadDictionaryFromStreamAsync(strm);
|
|
default:
|
|
{
|
|
StringBuilder sb= new StringBuilder();
|
|
sb.Append((char)b);
|
|
b = await ReadByteAsync(strm);
|
|
while(b != ':' && b !=-1)
|
|
{
|
|
sb.Append((char)b);
|
|
b = await ReadByteAsync(strm);
|
|
}
|
|
if(int.TryParse(sb.ToString(), out var number))
|
|
{
|
|
byte[] str = new byte[number];
|
|
if(await strm.ReadAsync(str,0,str.Length) != str.Length)
|
|
{
|
|
return (BencodedString)"";
|
|
}
|
|
return new BencodedString(str);
|
|
}
|
|
return (BencodedString)"";
|
|
}
|
|
}
|
|
|
|
}
|
|
public abstract Task WriteToStreamAsync(Stream strm);
|
|
|
|
public abstract void WriteToStream(Stream strm);
|
|
}
|
|
|
|
public class BencodedDictionary : BencodedData, IDictionary<string,BencodedData>
|
|
{
|
|
|
|
List<KeyValuePair<string,BencodedData>> _dict=new List<KeyValuePair<string, BencodedData>>();
|
|
public BencodedData this[string key]
|
|
{
|
|
get{
|
|
foreach(var item in _dict)
|
|
{
|
|
if(item.Key == key) return item.Value;
|
|
}
|
|
return null;
|
|
}
|
|
set {
|
|
var val= new KeyValuePair<string, BencodedData>(key,value);
|
|
for(int i = 0;i<_dict.Count;i++)
|
|
{
|
|
if(_dict[i].Key == key)
|
|
{
|
|
_dict[i] = val;
|
|
return;
|
|
}
|
|
}
|
|
_dict.Add(val);
|
|
}
|
|
}
|
|
|
|
public ICollection<string> Keys => _dict.Select(e=>e.Key).ToList();
|
|
|
|
public ICollection<BencodedData> Values => _dict.Select(e=>e.Value).ToList();
|
|
|
|
public int Count => _dict.Count;
|
|
|
|
public bool IsReadOnly => false;
|
|
|
|
internal static async Task<BencodedData> ReadDictionaryFromStreamAsync(Stream strm)
|
|
{
|
|
BencodedDictionary dict=new BencodedDictionary();
|
|
BencodedData data;
|
|
while((data=await ReadFromStreamAsync(strm)) != null)
|
|
{
|
|
var res=await ReadFromStreamAsync(strm);
|
|
if(res == null) break;
|
|
dict.Add(data.ToString(), res);
|
|
}
|
|
return dict;
|
|
}
|
|
|
|
internal static BencodedData ReadDictionaryFromStream(Stream strm)
|
|
{
|
|
BencodedDictionary dict=new BencodedDictionary();
|
|
BencodedData data;
|
|
while((data=ReadFromStream(strm)) != null)
|
|
{
|
|
var res=ReadFromStream(strm);
|
|
if(res == null) break;
|
|
dict.Add(data.ToString(), res);
|
|
}
|
|
return dict;
|
|
}
|
|
public void Add(string key, BencodedData value)
|
|
{
|
|
_dict.Add(new KeyValuePair<string, BencodedData>(key,value));
|
|
}
|
|
|
|
public void Add(KeyValuePair<string, BencodedData> item)
|
|
{
|
|
|
|
_dict.Add(item);
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
_dict.Clear();
|
|
}
|
|
|
|
public bool Contains(KeyValuePair<string, BencodedData> item)
|
|
{
|
|
return _dict.Contains(item);
|
|
}
|
|
|
|
public bool ContainsKey(string key)
|
|
{
|
|
foreach(var item in _dict)
|
|
{
|
|
if(item.Key == key) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public void CopyTo(KeyValuePair<string, BencodedData>[] array, int arrayIndex)
|
|
{
|
|
_dict.CopyTo(array,arrayIndex);
|
|
}
|
|
|
|
public IEnumerator<KeyValuePair<string, BencodedData>> GetEnumerator()
|
|
{
|
|
return _dict.GetEnumerator();
|
|
}
|
|
|
|
public bool Remove(string key)
|
|
{
|
|
for(int i = 0;i<_dict.Count;i++)
|
|
{
|
|
if(_dict[i].Key == key)
|
|
{
|
|
_dict.RemoveAt(i);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public bool Remove(KeyValuePair<string, BencodedData> item)
|
|
{
|
|
return _dict.Remove(item);
|
|
}
|
|
|
|
public bool TryGetValue(string key, out BencodedData value)
|
|
{
|
|
value = null;
|
|
foreach(var item in _dict)
|
|
{
|
|
if(item.Key == key) {value = item.Value; return true;}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public override void WriteToStream(Stream strm)
|
|
{
|
|
strm.WriteByte((byte)'d');
|
|
foreach(var item in this)
|
|
{
|
|
BencodedString str = item.Key;
|
|
str.WriteToStream(strm);
|
|
item.Value.WriteToStream(strm);
|
|
}
|
|
strm.WriteByte((byte)'e');
|
|
}
|
|
|
|
public override async Task WriteToStreamAsync(Stream strm)
|
|
{
|
|
await strm.WriteAsync(new byte[]{(byte)'d'},0,1);
|
|
|
|
foreach(var item in this)
|
|
{
|
|
BencodedString str = item.Key;
|
|
await str.WriteToStreamAsync(strm);
|
|
await item.Value.WriteToStreamAsync(strm);
|
|
}
|
|
await strm.WriteAsync(new byte[]{(byte)'e'},0,1);
|
|
}
|
|
|
|
IEnumerator IEnumerable.GetEnumerator()
|
|
{
|
|
return _dict.GetEnumerator();
|
|
}
|
|
}
|
|
|
|
public sealed class BencodedList : BencodedData, IList<BencodedData>
|
|
{
|
|
List<BencodedData> bencodedDatas=new List<BencodedData>();
|
|
public BencodedList()
|
|
{
|
|
|
|
}
|
|
|
|
public BencodedData this[int index] { get => bencodedDatas[index]; set => bencodedDatas[index]=value; }
|
|
|
|
public int Count => bencodedDatas.Count;
|
|
|
|
public bool IsReadOnly => false;
|
|
|
|
internal static BencodedData ReadListFromStream(Stream strm)
|
|
{
|
|
BencodedList list=new BencodedList();
|
|
BencodedData data;
|
|
while((data = ReadFromStream(strm)) != null)
|
|
{
|
|
list.Add(data);
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
internal static async Task<BencodedData> ReadListFromStreamAsync(Stream strm)
|
|
{
|
|
BencodedList list=new BencodedList();
|
|
BencodedData data;
|
|
while((data = await ReadFromStreamAsync(strm)) != null)
|
|
{
|
|
list.Add(data);
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
public void Add(BencodedData item)
|
|
{
|
|
bencodedDatas.Add(item);
|
|
}
|
|
|
|
public void AddRange(IEnumerable<BencodedData> items)
|
|
{
|
|
bencodedDatas.AddRange(items);
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
bencodedDatas.Clear();
|
|
}
|
|
|
|
public bool Contains(BencodedData item)
|
|
{
|
|
return bencodedDatas.Contains(item);
|
|
}
|
|
|
|
public void CopyTo(BencodedData[] array, int arrayIndex)
|
|
{
|
|
bencodedDatas.CopyTo(array,arrayIndex);
|
|
}
|
|
|
|
public IEnumerator<BencodedData> GetEnumerator()
|
|
{
|
|
return bencodedDatas.GetEnumerator();
|
|
}
|
|
|
|
public int IndexOf(BencodedData item)
|
|
{
|
|
return bencodedDatas.IndexOf(item);
|
|
}
|
|
|
|
public void Insert(int index, BencodedData item)
|
|
{
|
|
bencodedDatas.Insert(index,item);
|
|
|
|
}
|
|
|
|
public bool Remove(BencodedData item)
|
|
{
|
|
return bencodedDatas.Remove(item);
|
|
}
|
|
|
|
public void RemoveAt(int index)
|
|
{
|
|
bencodedDatas.RemoveAt(index);
|
|
}
|
|
public override void WriteToStream(Stream strm)
|
|
{
|
|
strm.WriteByte((byte)'l');
|
|
foreach(var item in this)
|
|
{
|
|
item.WriteToStream(strm);
|
|
}
|
|
strm.WriteByte((byte)'e');
|
|
}
|
|
public override async Task WriteToStreamAsync(Stream strm)
|
|
{
|
|
await strm.WriteAsync(new byte[]{(byte)'l'},0,1);
|
|
|
|
foreach(var item in this)
|
|
{
|
|
await item.WriteToStreamAsync(strm);
|
|
}
|
|
await strm.WriteAsync(new byte[]{(byte)'e'},0,1);
|
|
|
|
}
|
|
|
|
IEnumerator IEnumerable.GetEnumerator()
|
|
{
|
|
return bencodedDatas.GetEnumerator();
|
|
}
|
|
}
|
|
public sealed class BencodedNumber : BencodedData
|
|
{
|
|
public override string ToString()
|
|
{
|
|
return Value.ToString();
|
|
}
|
|
public BencodedNumber(long value)
|
|
{
|
|
Value = value;
|
|
}
|
|
public static implicit operator BencodedNumber(long number)
|
|
{
|
|
return new BencodedNumber(number);
|
|
}
|
|
public static explicit operator long(BencodedNumber number)
|
|
{
|
|
return number.Value;
|
|
}
|
|
public long Value {get;private set;}
|
|
public override async Task WriteToStreamAsync(Stream strm)
|
|
{
|
|
var bytes=Encoding.UTF8.GetBytes($"i{Value}e");
|
|
await strm.WriteAsync(bytes,0,bytes.Length);
|
|
}
|
|
|
|
internal static async Task<BencodedData> ReadIntFromStreamAsync(Stream strm)
|
|
{
|
|
|
|
StringBuilder b=new StringBuilder();
|
|
int read = await ReadByteAsync(strm);
|
|
while(read != 'e' && read != -1)
|
|
{
|
|
b.Append((char)read);
|
|
read = await ReadByteAsync(strm);
|
|
}
|
|
if(long.TryParse(b.ToString(),out var number))
|
|
{
|
|
return new BencodedNumber(number);
|
|
}
|
|
return new BencodedNumber(0);
|
|
|
|
}
|
|
|
|
public override void WriteToStream(Stream strm)
|
|
{
|
|
var bytes=Encoding.UTF8.GetBytes($"i{Value}e");
|
|
strm.Write(bytes,0,bytes.Length);
|
|
}
|
|
|
|
internal static BencodedData ReadIntFromStream(Stream strm)
|
|
{
|
|
StringBuilder b=new StringBuilder();
|
|
int read = strm.ReadByte();
|
|
while(read != 'e' && read != -1)
|
|
{
|
|
b.Append((char)read);
|
|
read = strm.ReadByte();
|
|
}
|
|
if(long.TryParse(b.ToString(),out var number))
|
|
{
|
|
return new BencodedNumber(number);
|
|
}
|
|
return new BencodedNumber(0);
|
|
}
|
|
}
|
|
public sealed class BencodedString : BencodedData, IEnumerable<byte>
|
|
{
|
|
public static implicit operator BencodedString(byte[] data)
|
|
{
|
|
return new BencodedString(data);
|
|
}
|
|
public static implicit operator BencodedString(string text)
|
|
{
|
|
return new BencodedString(Encoding.UTF8.GetBytes(text));
|
|
}
|
|
public static explicit operator string(BencodedString str)
|
|
{
|
|
return str.ToString();
|
|
}
|
|
public static explicit operator byte[](BencodedString str)
|
|
{
|
|
byte[] myL=new byte[str.data.Length];
|
|
Array.Copy(str.data,myL,myL.Length);
|
|
return myL;
|
|
}
|
|
public BencodedString(byte[] array)
|
|
{
|
|
data = array;
|
|
}
|
|
public byte this[int index]
|
|
{
|
|
get=>data[index];
|
|
}
|
|
byte[] data;
|
|
|
|
public int Count => data.Length;
|
|
public int Length => data.Length;
|
|
|
|
public override async Task WriteToStreamAsync(Stream strm)
|
|
{
|
|
byte[] utf8 = Encoding.UTF8.GetBytes($"{data.Length}:");
|
|
await strm.WriteAsync(utf8,0,utf8.Length);
|
|
await strm.WriteAsync(data,0,data.Length);
|
|
}
|
|
|
|
private IEnumerable<byte> _E()
|
|
{
|
|
foreach(var c in data) yield return c;
|
|
}
|
|
public IEnumerator<byte> GetEnumerator()
|
|
{
|
|
return _E().GetEnumerator();
|
|
}
|
|
|
|
IEnumerator IEnumerable.GetEnumerator()
|
|
{
|
|
return data.GetEnumerator();
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return Encoding.UTF8.GetString(data);
|
|
}
|
|
|
|
public override void WriteToStream(Stream strm)
|
|
{
|
|
byte[] utf8 = Encoding.UTF8.GetBytes($"{data.Length}:");
|
|
strm.Write(utf8,0,utf8.Length);
|
|
strm.Write(data,0,data.Length);
|
|
}
|
|
}
|
|
} |