WTelegramClient/src/Helpers.cs

257 lines
11 KiB
C#
Raw Normal View History

2021-08-04 00:40:09 +02:00
using System;
using System.Collections.Generic;
2022-01-11 04:14:23 +01:00
using System.IO;
2021-08-10 08:25:37 +02:00
using System.Numerics;
using System.Reflection;
using System.Text.Json;
2022-01-11 04:14:23 +01:00
using System.Threading;
using System.Threading.Tasks;
2021-08-04 00:40:09 +02:00
namespace WTelegram
{
public static class Helpers
{
2021-11-12 06:10:26 +01:00
/// <summary>Callback for logging a line (string) with its associated <see href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel">severity level</see> (int)</summary>
2021-08-06 01:54:29 +02:00
public static Action<int, string> Log { get; set; } = DefaultLogger;
2021-11-06 05:22:33 +01:00
/// <summary>For serializing indented Json with fields included</summary>
2022-01-13 03:06:22 +01:00
public static readonly JsonSerializerOptions JsonOptions = new() { IncludeFields = true, WriteIndented = true,
IgnoreReadOnlyProperties = true, DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull };
2021-08-04 00:40:09 +02:00
2022-10-01 13:56:43 +02:00
private static readonly ConsoleColor[] LogLevelToColor = new[] { ConsoleColor.DarkGray, ConsoleColor.DarkCyan,
ConsoleColor.Cyan, ConsoleColor.Yellow, ConsoleColor.Red, ConsoleColor.Magenta, ConsoleColor.DarkBlue };
2021-08-04 10:11:07 +02:00
private static void DefaultLogger(int level, string message)
{
Console.ForegroundColor = LogLevelToColor[level];
Console.WriteLine(message);
Console.ResetColor();
}
2022-01-07 00:24:47 +01:00
public static V GetOrCreate<K, V>(this Dictionary<K, V> dictionary, K key) where V : new()
=> dictionary.TryGetValue(key, out V value) ? value : dictionary[key] = new V();
2021-11-06 05:22:33 +01:00
/// <summary>Get a cryptographic random 64-bit value</summary>
public static long RandomLong()
{
#if NETCOREAPP2_1_OR_GREATER
2022-10-01 13:56:43 +02:00
long value = 0;
System.Security.Cryptography.RandomNumberGenerator.Fill(System.Runtime.InteropServices.MemoryMarshal.AsBytes(
System.Runtime.InteropServices.MemoryMarshal.CreateSpan(ref value, 1)));
return value;
#else
var span = new byte[8];
Encryption.RNG.GetBytes(span);
return BitConverter.ToInt64(span, 0);
#endif
}
2022-01-11 04:14:23 +01:00
public static async Task<int> FullReadAsync(this Stream stream, byte[] buffer, int length, CancellationToken ct)
{
for (int offset = 0; offset < length;)
{
#pragma warning disable CA1835
var read = await stream.ReadAsync(buffer, offset, length - offset, ct);
#pragma warning restore CA1835
if (read == 0) return offset;
offset += read;
}
return length;
}
2021-11-06 05:22:33 +01:00
internal static byte[] ToBigEndian(ulong value) // variable-size buffer
2021-08-04 00:40:09 +02:00
{
2022-01-07 00:24:47 +01:00
int i = 1;
for (ulong temp = value; (temp >>= 8) != 0; ) i++;
2021-08-04 00:40:09 +02:00
var result = new byte[i];
2022-01-07 00:24:47 +01:00
for (; --i >= 0; value >>= 8)
result[i] = (byte)value;
2021-08-04 00:40:09 +02:00
return result;
}
2021-11-06 05:22:33 +01:00
internal static ulong FromBigEndian(byte[] bytes) // variable-size buffer
2021-08-04 00:40:09 +02:00
{
2021-08-06 01:54:29 +02:00
if (bytes.Length > 8) throw new ArgumentException($"expected bytes length <= 8 but got {bytes.Length}");
2021-08-04 00:40:09 +02:00
ulong result = 0;
foreach (byte b in bytes)
result = (result << 8) + b;
return result;
}
internal static byte[] To256Bytes(this BigInteger bi)
{
var bigEndian = bi.ToByteArray(true, true);
if (bigEndian.Length == 256) return bigEndian;
var result = new byte[256];
bigEndian.CopyTo(result, 256 - bigEndian.Length);
return result;
}
2022-10-16 11:49:16 +02:00
internal static ulong PQFactorize(ulong pq) // ported from https://github.com/tdlib/td/blob/master/tdutils/td/utils/crypto.cpp#L103
2021-08-04 00:40:09 +02:00
{
if (pq < 2) return 1;
var random = new Random();
ulong g = 0;
for (int i = 0, iter = 0; i < 3 || iter < 1000; i++)
{
ulong q = (ulong)random.Next(17, 32) % (pq - 1);
ulong x = ((ulong)random.Next() + (ulong)random.Next() << 31) % (pq - 1) + 1;
ulong y = x;
int lim = 1 << (Math.Min(5, i) + 18);
for (int j = 1; j < lim; j++)
{
iter++;
2022-10-16 11:49:16 +02:00
// x = (q + x * x) % pq
ulong res = q, a = x;
while (x != 0)
2021-08-04 00:40:09 +02:00
{
2022-10-16 11:49:16 +02:00
if ((x & 1) != 0)
res = (res + a) % pq;
a = (a + a) % pq;
x >>= 1;
2021-08-04 00:40:09 +02:00
}
2022-10-16 11:49:16 +02:00
x = res;
2021-08-04 00:40:09 +02:00
ulong z = x < y ? pq + x - y : x - y;
g = gcd(z, pq);
if (g != 1)
break;
if ((j & (j - 1)) == 0)
y = x;
}
if (g > 1 && g < pq)
break;
}
if (g != 0)
{
ulong other = pq / g;
if (other < g)
g = other;
}
return g;
2022-10-16 11:49:16 +02:00
static ulong gcd(ulong left, ulong right)
2021-08-04 00:40:09 +02:00
{
2022-10-16 11:49:16 +02:00
while (right != 0)
2021-08-04 00:40:09 +02:00
{
2022-10-16 11:49:16 +02:00
ulong num = left % right;
left = right;
right = num;
2021-08-04 00:40:09 +02:00
}
2022-10-16 11:49:16 +02:00
return left;
2021-08-04 00:40:09 +02:00
}
}
2021-08-10 08:25:37 +02:00
public static int MillerRabinIterations { get; set; } = 64; // 64 is OpenSSL default for 2048-bits numbers
2021-11-06 05:22:33 +01:00
/// <summary>MillerRabin primality test</summary>
/// <param name="n">The number to check for primality</param>
public static bool IsProbablePrime(this BigInteger n)
2021-08-10 08:25:37 +02:00
{
var n_minus_one = n - BigInteger.One;
if (n_minus_one.Sign <= 0) return false;
int s;
var d = n_minus_one;
for (s = 0; d.IsEven; s++) d >>= 1;
var bitLen = n.GetBitLength();
var randomBytes = new byte[bitLen / 8 + 1];
var lastByteMask = (byte)((1 << (int)(bitLen % 8)) - 1);
BigInteger a;
if (MillerRabinIterations < 15) // 15 is the minimum recommended by Telegram
Log(3, $"MillerRabinIterations ({MillerRabinIterations}) is below the minimal level of safety (15)");
for (int i = 0; i < MillerRabinIterations; i++)
{
do
{
Encryption.RNG.GetBytes(randomBytes);
randomBytes[^1] &= lastByteMask; // we don't want more bits than necessary
a = new BigInteger(randomBytes);
}
while (a < 3 || a >= n_minus_one);
a--;
var x = BigInteger.ModPow(a, d, n);
if (x.IsOne || x == n_minus_one) continue;
int r;
for (r = s - 1; r > 0; r--)
{
x = BigInteger.ModPow(x, 2, n);
if (x.IsOne) return false;
if (x == n_minus_one) break;
}
if (r == 0) return false;
}
return true;
2021-08-10 08:25:37 +02:00
}
internal static readonly byte[] StrippedThumbJPG = // see https://core.telegram.org/api/files#stripped-thumbnails
{
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49,
0x46, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x28, 0x1c,
0x1e, 0x23, 0x1e, 0x19, 0x28, 0x23, 0x21, 0x23, 0x2d, 0x2b, 0x28, 0x30, 0x3c, 0x64, 0x41, 0x3c, 0x37, 0x37,
0x3c, 0x7b, 0x58, 0x5d, 0x49, 0x64, 0x91, 0x80, 0x99, 0x96, 0x8f, 0x80, 0x8c, 0x8a, 0xa0, 0xb4, 0xe6, 0xc3,
0xa0, 0xaa, 0xda, 0xad, 0x8a, 0x8c, 0xc8, 0xff, 0xcb, 0xda, 0xee, 0xf5, 0xff, 0xff, 0xff, 0x9b, 0xc1, 0xff,
0xff, 0xff, 0xfa, 0xff, 0xe6, 0xfd, 0xff, 0xf8, 0xff, 0xdb, 0x00, 0x43, 0x01, 0x2b, 0x2d, 0x2d, 0x3c, 0x35,
0x3c, 0x76, 0x41, 0x41, 0x76, 0xf8, 0xa5, 0x8c, 0xa5, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8,
0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8,
0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8,
0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xff, 0xc0, 0x00, 0x11, 0x08, 0x00, 0x00, 0x00, 0x00, 0x03, 0x01, 0x22, 0x00,
0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x00, 0x1f, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05,
0x04, 0x04, 0x00, 0x00, 0x01, 0x7d, 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06,
0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52,
0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28,
0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53,
0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75,
0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96,
0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6,
0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4,
0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xc4, 0x00, 0x1f, 0x01, 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05,
0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41,
0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33,
0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26,
0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74,
0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94,
0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4,
0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4,
0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4,
0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00,
0x3f, 0x00
};
internal static string GetSystemVersion()
{
var os = System.Runtime.InteropServices.RuntimeInformation.OSDescription;
int space = os.IndexOf(' ') + 1, dot = os.IndexOf('.');
return os[(os.IndexOf(' ', space) < 0 ? 0 : space)..(dot < 0 ? os.Length : dot)];
}
internal static string GetAppVersion()
=> (Assembly.GetEntryAssembly() ?? Array.Find(AppDomain.CurrentDomain.GetAssemblies(), a => a.EntryPoint != null))?.GetName().Version.ToString() ?? "0.0";
2022-01-21 01:40:10 +01:00
public class IndirectStream : Stream
{
2022-01-21 01:40:10 +01:00
public IndirectStream(Stream innerStream) => _innerStream = innerStream;
public long? ContentLength;
protected readonly Stream _innerStream;
public override bool CanRead => _innerStream.CanRead;
public override bool CanSeek => _innerStream.CanSeek;
public override bool CanWrite => _innerStream.CanWrite;
public override long Length => ContentLength ?? _innerStream.Length;
public override long Position { get => _innerStream.Position; set => _innerStream.Position = value; }
public override void Flush() => _innerStream.Flush();
public override int Read(byte[] buffer, int offset, int count) => _innerStream.Read(buffer, offset, count);
public override long Seek(long offset, SeekOrigin origin) => _innerStream.Seek(offset, origin);
public override void SetLength(long value) => _innerStream.SetLength(value);
public override void Write(byte[] buffer, int offset, int count) => _innerStream.Write(buffer, offset, count);
protected override void Dispose(bool disposing) => _innerStream.Dispose();
}
2021-08-04 00:40:09 +02:00
}
}