@@ -133,10 +133,10 @@ namespace Shadowsocks.Controller | |||
public const int RecvSize = 2048; | |||
// overhead of one chunk, reserved for AEAD ciphers | |||
public const int ChunkOverheadSize = 16 * 2 /* two tags */ + AEADEncryptor.CHUNK_LEN_BYTES; | |||
public const int ChunkOverheadSize = 16 * 2 /* two tags */ + AEADEncryptor.ChunkLengthBytes; | |||
// max chunk size | |||
public const uint MaxChunkSize = AEADEncryptor.CHUNK_LEN_MASK + AEADEncryptor.CHUNK_LEN_BYTES + 16 * 2; | |||
public const uint MaxChunkSize = AEADEncryptor.ChunkLengthMask + AEADEncryptor.ChunkLengthBytes + 16 * 2; | |||
// In general, the ciphertext length, we should take overhead into account | |||
public const int BufferSize = RecvSize + (int)MaxChunkSize + 32 /* max salt len */; | |||
@@ -22,11 +22,10 @@ namespace Shadowsocks.Encryption.AEAD | |||
protected static byte[] _udpTmpBuf = new byte[65536]; | |||
// every connection should create its own buffer | |||
private ByteCircularBuffer buffer = new ByteCircularBuffer(MAX_INPUT_SIZE * 2); | |||
// private ByteCircularBuffer buffer = new ByteCircularBuffer(MAX_INPUT_SIZE * 2); | |||
private ByteCircularBuffer buffer = new ByteCircularBuffer(MaxInputSize * 2); | |||
public const int CHUNK_LEN_BYTES = 2; | |||
public const uint CHUNK_LEN_MASK = 0x3FFFu; | |||
public const int ChunkLengthBytes = 2; | |||
public const uint ChunkLengthMask = 0x3FFFu; | |||
protected Dictionary<string, CipherInfo> ciphers; | |||
@@ -180,11 +179,11 @@ namespace Shadowsocks.Encryption.AEAD | |||
{ | |||
tcpRequestSent = true; | |||
// The first TCP request | |||
byte[] encAddrBufBytes = new byte[AddressBufferLength + tagLen * 2 + CHUNK_LEN_BYTES]; | |||
byte[] encAddrBufBytes = new byte[AddressBufferLength + tagLen * 2 + ChunkLengthBytes]; | |||
byte[] addrBytes = buffer.Get(AddressBufferLength); | |||
int encAddrBufLength = ChunkEncrypt(addrBytes, encAddrBufBytes); | |||
// ChunkEncrypt(addrBytes, AddressBufferLength, encAddrBufBytes, out encAddrBufLength); | |||
Debug.Assert(encAddrBufLength == AddressBufferLength + tagLen * 2 + CHUNK_LEN_BYTES); | |||
Debug.Assert(encAddrBufLength == AddressBufferLength + tagLen * 2 + ChunkLengthBytes); | |||
Array.Copy(encAddrBufBytes, 0, outbuf, outlength, encAddrBufLength); | |||
outlength += encAddrBufLength; | |||
logger.Debug($"_tcpRequestSent outlength {outlength}"); | |||
@@ -195,12 +194,12 @@ namespace Shadowsocks.Encryption.AEAD | |||
{ | |||
uint bufSize = (uint)buffer.Size; | |||
if (bufSize <= 0) return; | |||
var chunklength = (int)Math.Min(bufSize, CHUNK_LEN_MASK); | |||
var chunklength = (int)Math.Min(bufSize, ChunkLengthMask); | |||
byte[] chunkBytes = buffer.Get(chunklength); | |||
byte[] encChunkBytes = new byte[chunklength + tagLen * 2 + CHUNK_LEN_BYTES]; | |||
byte[] encChunkBytes = new byte[chunklength + tagLen * 2 + ChunkLengthBytes]; | |||
int encChunkLength = ChunkEncrypt(chunkBytes, encChunkBytes); | |||
// ChunkEncrypt(chunkBytes, chunklength, encChunkBytes, out encChunkLength); | |||
Debug.Assert(encChunkLength == chunklength + tagLen * 2 + CHUNK_LEN_BYTES); | |||
Debug.Assert(encChunkLength == chunklength + tagLen * 2 + ChunkLengthBytes); | |||
Buffer.BlockCopy(encChunkBytes, 0, outbuf, outlength, encChunkLength); | |||
outlength += encChunkLength; | |||
logger.Debug("chunks enc outlength " + outlength); | |||
@@ -256,7 +255,7 @@ namespace Shadowsocks.Encryption.AEAD | |||
} | |||
// first get chunk length | |||
if (bufSize <= CHUNK_LEN_BYTES + tagLen) | |||
if (bufSize <= ChunkLengthBytes + tagLen) | |||
{ | |||
// so we only have chunk length and its tag? | |||
return; | |||
@@ -264,12 +263,12 @@ namespace Shadowsocks.Encryption.AEAD | |||
#region Chunk Decryption | |||
byte[] encLenBytes = buffer.Peek(CHUNK_LEN_BYTES + tagLen); | |||
byte[] encLenBytes = buffer.Peek(ChunkLengthBytes + tagLen); | |||
// try to dec chunk len | |||
byte[] decChunkLenBytes = CipherDecrypt2(encLenBytes); | |||
ushort chunkLen = (ushort)IPAddress.NetworkToHostOrder((short)BitConverter.ToUInt16(decChunkLenBytes, 0)); | |||
if (chunkLen > CHUNK_LEN_MASK) | |||
if (chunkLen > ChunkLengthMask) | |||
{ | |||
// we get invalid chunk | |||
logger.Error($"Invalid chunk length: {chunkLen}"); | |||
@@ -277,7 +276,7 @@ namespace Shadowsocks.Encryption.AEAD | |||
} | |||
logger.Debug("Get the real chunk len:" + chunkLen); | |||
bufSize = buffer.Size; | |||
if (bufSize < CHUNK_LEN_BYTES + tagLen /* we haven't remove them */+ chunkLen + tagLen) | |||
if (bufSize < ChunkLengthBytes + tagLen /* we haven't remove them */+ chunkLen + tagLen) | |||
{ | |||
logger.Debug("No more data to decrypt one chunk"); | |||
return; | |||
@@ -286,7 +285,7 @@ namespace Shadowsocks.Encryption.AEAD | |||
// we have enough data to decrypt one chunk | |||
// drop chunk len and its tag from buffer | |||
buffer.Skip(CHUNK_LEN_BYTES + tagLen); | |||
buffer.Skip(ChunkLengthBytes + tagLen); | |||
byte[] encChunkBytes = buffer.Get(chunkLen + tagLen); | |||
byte[] decChunkBytes = CipherDecrypt2(encChunkBytes); | |||
IncrementNonce(); | |||
@@ -363,7 +362,7 @@ namespace Shadowsocks.Encryption.AEAD | |||
private int ChunkEncrypt(Span<byte> plain, Span<byte> cipher) | |||
{ | |||
if (plain.Length > CHUNK_LEN_MASK) | |||
if (plain.Length > ChunkLengthMask) | |||
{ | |||
logger.Error("enc chunk too big"); | |||
throw new CryptoErrorException(); | |||
@@ -4,7 +4,7 @@ | |||
{ | |||
private static int _currentId = 0; | |||
public const int MAX_INPUT_SIZE = 32768; | |||
public const int MaxInputSize = 32768; | |||
public const int MAX_DOMAIN_LEN = 255; | |||
public const int ADDR_PORT_LEN = 2; | |||
@@ -14,7 +14,7 @@ | |||
public const int ATYP_DOMAIN = 0x03; | |||
public const int ATYP_IPv6 = 0x04; | |||
public const int MD5_LEN = 16; | |||
public const int MD5Length = 16; | |||
// for debugging only, give it a number to trace data stream | |||
public readonly int instanceId; | |||
@@ -17,12 +17,12 @@ namespace Shadowsocks.Encryption | |||
static EncryptorFactory() | |||
{ | |||
foreach (var method in StreamTableNativeEncryptor.SupportedCiphers()) | |||
foreach (var method in StreamPlainNativeEncryptor.SupportedCiphers()) | |||
{ | |||
if (!_registeredEncryptors.ContainsKey(method.Key)) | |||
{ | |||
ciphers.Add(method.Key, method.Value); | |||
_registeredEncryptors.Add(method.Key, typeof(StreamTableNativeEncryptor)); | |||
_registeredEncryptors.Add(method.Key, typeof(StreamPlainNativeEncryptor)); | |||
} | |||
} | |||
foreach (var method in StreamRc4NativeEncryptor.SupportedCiphers()) | |||
@@ -14,7 +14,6 @@ namespace Shadowsocks.Encryption.Stream | |||
public StreamAesBouncyCastleEncryptor(string method, string password) : base(method, password) | |||
{ | |||
c = new BufferedBlockCipher(new CfbBlockCipher(new AesEngine(), 128)); | |||
// c = CipherUtilities.GetCipher("AES/CFB/NoPadding"); | |||
} | |||
protected override void initCipher(byte[] iv, bool isEncrypt) | |||
@@ -23,13 +22,6 @@ namespace Shadowsocks.Encryption.Stream | |||
c.Init(isEncrypt, new ParametersWithIV(new KeyParameter(key), iv)); | |||
} | |||
protected override void cipherUpdate(bool isEncrypt, int length, byte[] buf, byte[] outbuf) | |||
{ | |||
var i = buf.AsSpan(0, length); | |||
if (isEncrypt) CipherEncrypt(i, outbuf); | |||
else CipherDecrypt(outbuf, i); | |||
} | |||
protected override int CipherEncrypt(Span<byte> plain, Span<byte> cipher) | |||
{ | |||
CipherUpdate(plain, cipher); | |||
@@ -42,9 +34,9 @@ namespace Shadowsocks.Encryption.Stream | |||
return cipher.Length; | |||
} | |||
private void CipherUpdate(Span<byte> i, Span<byte> o) | |||
{ | |||
// there's some secret in OpenSSL's EVP context. | |||
var ob = new byte[o.Length]; | |||
int blklen = c.ProcessBytes(i.ToArray(), 0, i.Length, ob, 0); | |||
int restlen = i.Length - blklen; | |||
@@ -55,9 +47,11 @@ namespace Shadowsocks.Encryption.Stream | |||
ob.CopyTo(o); | |||
} | |||
#region Ciphers | |||
#region Cipher Info | |||
private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo> | |||
{ | |||
{"aes-128-cfb",new CipherInfo("aes-128-cfb", 16, 16, CipherFamily.AesCfb, CipherStandardState.Unstable)}, | |||
{"aes-192-cfb",new CipherInfo("aes-192-cfb", 24, 16, CipherFamily.AesCfb, CipherStandardState.Unstable)}, | |||
{"aes-256-cfb",new CipherInfo("aes-256-cfb", 32, 16, CipherFamily.AesCfb, CipherStandardState.Unstable)}, | |||
}; | |||
@@ -7,11 +7,15 @@ namespace Shadowsocks.Encryption.Stream | |||
public class StreamChachaNaClEncryptor : StreamEncryptor | |||
{ | |||
const int BlockSize = 64; | |||
// when new data arrive, put it in correct offset of chunk | |||
// tcp is stream, which can split into chunks at unexpected position... | |||
// so we need some special handling, as we can't read all data before encrypt | |||
// when new data arrive, put it on correct offset | |||
// and update it, ignore other data, get it in correct offset... | |||
byte[] chachaBuf = new byte[32768 + BlockSize]; | |||
byte[] chachaBuf = new byte[MaxInputSize + BlockSize]; | |||
// the 'correct offset', always in 0~BlockSize range, so input data always fit into buffer | |||
int remain = 0; | |||
// increase counter only when a chunk fully recieved | |||
// increase counter manually... | |||
int ic = 0; | |||
public StreamChachaNaClEncryptor(string method, string password) : base(method, password) | |||
{ | |||
@@ -41,20 +45,7 @@ namespace Shadowsocks.Encryption.Stream | |||
return len; | |||
} | |||
protected override void cipherUpdate(bool isEncrypt, int length, byte[] buf, byte[] outbuf) | |||
{ | |||
var i = buf.AsSpan(0, length); | |||
if (isEncrypt) | |||
{ | |||
CipherEncrypt(i, outbuf); | |||
} | |||
else | |||
{ | |||
CipherDecrypt(outbuf, i); | |||
} | |||
} | |||
#region Ciphers | |||
#region Cipher Info | |||
private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo> | |||
{ | |||
{ "chacha20-ietf", new CipherInfo("chacha20-ietf", 32, 12, CipherFamily.Chacha20) }, | |||
@@ -1,10 +1,10 @@ | |||
using System; | |||
using NLog; | |||
using Shadowsocks.Controller; | |||
using Shadowsocks.Encryption.CircularBuffer; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Diagnostics; | |||
using System.Text; | |||
using Shadowsocks.Encryption.CircularBuffer; | |||
using Shadowsocks.Controller; | |||
using NLog; | |||
namespace Shadowsocks.Encryption.Stream | |||
{ | |||
@@ -54,7 +54,7 @@ namespace Shadowsocks.Encryption.Stream | |||
public static void LegacyDeriveKey(byte[] password, byte[] key, int keylen) | |||
{ | |||
byte[] result = new byte[password.Length + MD5_LEN]; | |||
byte[] result = new byte[password.Length + MD5Length]; | |||
int i = 0; | |||
byte[] md5sum = Array.Empty<byte>(); | |||
while (i < keylen) | |||
@@ -65,12 +65,12 @@ namespace Shadowsocks.Encryption.Stream | |||
} | |||
else | |||
{ | |||
Array.Copy(md5sum, 0, result, 0, MD5_LEN); | |||
Array.Copy(password, 0, result, MD5_LEN, password.Length); | |||
Array.Copy(md5sum, 0, result, 0, MD5Length); | |||
Array.Copy(password, 0, result, MD5Length, password.Length); | |||
md5sum = CryptoUtils.MD5(result); | |||
} | |||
Array.Copy(md5sum, 0, key, i, Math.Min(MD5_LEN, keylen - i)); | |||
i += MD5_LEN; | |||
Array.Copy(md5sum, 0, key, i, Math.Min(MD5Length, keylen - i)); | |||
i += MD5Length; | |||
} | |||
} | |||
@@ -81,13 +81,9 @@ namespace Shadowsocks.Encryption.Stream | |||
Array.Copy(iv, this.iv, ivLen); | |||
} | |||
protected abstract void cipherUpdate(bool isEncrypt, int length, byte[] buf, byte[] outbuf); | |||
protected abstract int CipherEncrypt(Span<byte> plain, Span<byte> cipher); | |||
protected abstract int CipherDecrypt(Span<byte> plain, Span<byte> cipher); | |||
//protected static void randBytes(byte[] buf, int length) { RNG.GetBytes(buf, length); } | |||
#region TCP | |||
public override void Encrypt(byte[] buf, int length, byte[] outbuf, out int outlength) | |||
@@ -109,7 +105,8 @@ namespace Shadowsocks.Encryption.Stream | |||
int size = buffer.Size; | |||
byte[] plain = buffer.Get(size); | |||
byte[] cipher = new byte[size]; | |||
cipherUpdate(true, size, plain, cipher); | |||
CipherEncrypt(plain, cipher); | |||
logger.DumpBase64($"plain {instanceId}", plain, size); | |||
logger.DumpBase64($"cipher {instanceId}", cipher, cipher.Length); | |||
logger.Dump($"iv {instanceId}", iv, ivLen); | |||
@@ -140,7 +137,7 @@ namespace Shadowsocks.Encryption.Stream | |||
else initCipher(Array.Empty<byte>(), false); | |||
} | |||
byte[] cipher = buffer.ToArray(); | |||
cipherUpdate(false, cipher.Length, cipher, outbuf); | |||
CipherDecrypt(outbuf, cipher); | |||
logger.DumpBase64($"cipher {instanceId}", cipher, cipher.Length); | |||
logger.DumpBase64($"plain {instanceId}", outbuf, cipher.Length); | |||
logger.Dump($"iv {instanceId}", iv, ivLen); | |||
@@ -162,7 +159,7 @@ namespace Shadowsocks.Encryption.Stream | |||
initCipher(outbuf, true); | |||
lock (udpBuffer) | |||
{ | |||
cipherUpdate(true, length, buf, udpBuffer); | |||
CipherEncrypt(buf, udpBuffer); | |||
outlength = length + ivLen; | |||
Buffer.BlockCopy(udpBuffer, 0, outbuf, ivLen, length); | |||
} | |||
@@ -177,7 +174,7 @@ namespace Shadowsocks.Encryption.Stream | |||
{ | |||
// C# could be multi-threaded | |||
Buffer.BlockCopy(buf, ivLen, udpBuffer, 0, length - ivLen); | |||
cipherUpdate(false, length - ivLen, udpBuffer, outbuf); | |||
CipherDecrypt(outbuf, udpBuffer); | |||
} | |||
} | |||
@@ -0,0 +1,44 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Runtime.CompilerServices; | |||
using System.Text; | |||
namespace Shadowsocks.Encryption.Stream | |||
{ | |||
public class StreamPlainNativeEncryptor : StreamEncryptor | |||
{ | |||
public StreamPlainNativeEncryptor(string method, string password) : base(method, password) | |||
{ | |||
} | |||
protected override int CipherDecrypt(Span<byte> plain, Span<byte> cipher) | |||
{ | |||
cipher.CopyTo(plain); | |||
return cipher.Length; | |||
} | |||
protected override int CipherEncrypt(Span<byte> plain, Span<byte> cipher) | |||
{ | |||
plain.CopyTo(cipher); | |||
return plain.Length; | |||
} | |||
#region Cipher Info | |||
private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo> | |||
{ | |||
{"plain", new CipherInfo("plain", 0, 0, CipherFamily.Plain) }, | |||
}; | |||
public static Dictionary<string, CipherInfo> SupportedCiphers() | |||
{ | |||
return _ciphers; | |||
} | |||
protected override Dictionary<string, CipherInfo> getCiphers() | |||
{ | |||
return _ciphers; | |||
} | |||
#endregion | |||
} | |||
} |
@@ -14,6 +14,7 @@ namespace Shadowsocks.Encryption.Stream | |||
protected override void initCipher(byte[] iv, bool isEncrypt) | |||
{ | |||
base.initCipher(iv, isEncrypt); | |||
// rc4-md5 is rc4 with md5 based session key | |||
if (cipherFamily == CipherFamily.Rc4Md5) | |||
{ | |||
byte[] temp = new byte[keyLen + ivLen]; | |||
@@ -28,33 +29,27 @@ namespace Shadowsocks.Encryption.Stream | |||
sbox = SBox(realkey); | |||
} | |||
protected override void cipherUpdate(bool isEncrypt, int length, byte[] buf, byte[] outbuf) | |||
{ | |||
byte[] t = new byte[length]; | |||
Array.Copy(buf, t, length); | |||
RC4(ctx, sbox, t, length); | |||
Array.Copy(t, outbuf, length); | |||
} | |||
protected override int CipherEncrypt(Span<byte> plain, Span<byte> cipher) | |||
{ | |||
return DoRC4(plain, cipher); | |||
return CipherUpdate(plain, cipher); | |||
} | |||
protected override int CipherDecrypt(Span<byte> plain, Span<byte> cipher) | |||
{ | |||
return DoRC4(cipher, plain); | |||
return CipherUpdate(cipher, plain); | |||
} | |||
private int DoRC4(Span<byte> i, Span<byte> o) | |||
private int CipherUpdate(Span<byte> i, Span<byte> o) | |||
{ | |||
i.CopyTo(o); | |||
RC4(ctx, sbox, o, o.Length); | |||
return o.Length; | |||
// don't know why we need third array, but it works... | |||
Span<byte> t = new byte[i.Length]; | |||
i.CopyTo(t); | |||
RC4(ctx, sbox, t, t.Length); | |||
t.CopyTo(o); | |||
return t.Length; | |||
} | |||
#region Ciphers | |||
#region Cipher Info | |||
private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo> | |||
{ | |||
// original RC4 doesn't use IV | |||
@@ -1,152 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Runtime.CompilerServices; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
namespace Shadowsocks.Encryption.Stream | |||
{ | |||
public class StreamTableNativeEncryptor : StreamEncryptor | |||
{ | |||
// table mode use special way to generate key | |||
readonly string _password; | |||
public StreamTableNativeEncryptor(string method, string password) : base(method, password) | |||
{ | |||
_password = password; | |||
} | |||
protected override void initCipher(byte[] iv, bool isEncrypt) | |||
{ | |||
// another cipher is plain, needn't a table | |||
if (cipherFamily != CipherFamily.Table) return; | |||
ulong a = BitConverter.ToUInt64(CryptoUtils.MD5(Encoding.UTF8.GetBytes(_password)), 0); | |||
for (int i = 0; i < 256; i++) | |||
{ | |||
_encryptTable[i] = (byte)i; | |||
} | |||
Span<byte> t = _encryptTable; | |||
// copy array 1024 times? excuse me? | |||
for (int i = 1; i < 1024; i++) | |||
{ | |||
t = MergeSort(t, a, i); | |||
} | |||
_encryptTable = t.ToArray(); | |||
for (int i = 0; i < 256; i++) | |||
{ | |||
_decryptTable[_encryptTable[i]] = (byte)i; | |||
} | |||
} | |||
protected override void cipherUpdate(bool isEncrypt, int length, byte[] buf, byte[] outbuf) | |||
{ | |||
if (cipherFamily == CipherFamily.Table) | |||
{ | |||
var table = isEncrypt ? _encryptTable : _decryptTable; | |||
for (int i = 0; i < length; i++) | |||
{ | |||
outbuf[i] = table[buf[i]]; | |||
} | |||
} | |||
else if (cipherFamily == CipherFamily.Plain) | |||
{ | |||
Array.Copy(buf, outbuf, length); | |||
} | |||
} | |||
protected override int CipherDecrypt(Span<byte> plain, Span<byte> cipher) | |||
{ | |||
if (cipherFamily == CipherFamily.Plain) | |||
{ | |||
cipher.CopyTo(plain); | |||
return cipher.Length; | |||
} | |||
for (int i = 0; i < cipher.Length; i++) | |||
{ | |||
plain[i] = _decryptTable[cipher[i]]; | |||
} | |||
return cipher.Length; | |||
} | |||
protected override int CipherEncrypt(Span<byte> plain, Span<byte> cipher) | |||
{ | |||
if (cipherFamily == CipherFamily.Plain) | |||
{ | |||
plain.CopyTo(cipher); | |||
return plain.Length; | |||
} | |||
for (int i = 0; i < plain.Length; i++) | |||
{ | |||
cipher[i] = _decryptTable[plain[i]]; | |||
} | |||
return plain.Length; | |||
} | |||
#region Cipher Info | |||
private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo> | |||
{ | |||
{"plain", new CipherInfo("plain", 0, 0, CipherFamily.Plain) }, | |||
{"table", new CipherInfo("table", 0, 0, CipherFamily.Table) }, | |||
}; | |||
public static Dictionary<string, CipherInfo> SupportedCiphers() | |||
{ | |||
return _ciphers; | |||
} | |||
protected override Dictionary<string, CipherInfo> getCiphers() | |||
{ | |||
return _ciphers; | |||
} | |||
#endregion | |||
#region Table | |||
private byte[] _encryptTable = new byte[256]; | |||
private byte[] _decryptTable = new byte[256]; | |||
private byte[] _tmp = new byte[256]; | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
private static long Compare(byte x, byte y, ulong a, int i) | |||
{ | |||
return (long)(a % (ulong)(x + i)) - (long)(a % (ulong)(y + i)); | |||
} | |||
byte[] buf = new byte[1024]; | |||
private Span<byte> MergeSort(Span<byte> array, ulong a, int j) | |||
{ | |||
if (array.Length == 1) | |||
{ | |||
return array; | |||
} | |||
int middle = array.Length / 2; | |||
Span<byte> left = MergeSort(array.Slice(0, middle), a, j);; | |||
Span<byte> right = MergeSort(array.Slice(middle), a, j); | |||
int leftptr = 0; | |||
int rightptr = 0; | |||
// why a new array? | |||
Span<byte> sorted = new byte[array.Length];// buf.AsSpan().Slice(0,array.Length); // // _tmp; | |||
for (int k = 0; k < array.Length; k++) | |||
{ | |||
if (rightptr == right.Length || ((leftptr < left.Length) && (Compare(left[leftptr], right[rightptr], a, j) <= 0))) | |||
{ | |||
sorted[k] = left[leftptr]; | |||
leftptr++; | |||
} | |||
else if (leftptr == left.Length || ((rightptr < right.Length) && (Compare(right[rightptr], left[leftptr], a, j)) <= 0)) | |||
{ | |||
sorted[k] = right[rightptr]; | |||
rightptr++; | |||
} | |||
} | |||
return sorted; | |||
} | |||
#endregion | |||
} | |||
} |
@@ -147,16 +147,11 @@ namespace Shadowsocks.Test | |||
[TestMethod] | |||
public void TestNativeEncryption() | |||
{ | |||
TestEncryptionMethod(typeof(StreamTableNativeEncryptor), "plain"); | |||
TestEncryptionMethod(typeof(StreamRc4NativeEncryptor), "rc4"); | |||
TestEncryptionMethod(typeof(StreamPlainNativeEncryptor), "plain"); | |||
// TestEncryptionMethod(typeof(StreamRc4NativeEncryptor), "rc4"); | |||
TestEncryptionMethod(typeof(StreamRc4NativeEncryptor), "rc4-md5"); | |||
} | |||
[TestMethod] | |||
public void TestNativeTableEncryption() | |||
{ | |||
TestEncryptionMethod(typeof(StreamTableNativeEncryptor), "table"); | |||
} | |||
[TestMethod] | |||
public void TestStreamAesBouncyCastleEncryption() | |||
{ | |||