Separate creating, enabling/disabling, activating/deactivating of messages into SshMessageFactory.

Huge performance improvement in micro-benchmark.
Work in progress.
This commit is contained in:
drieseng
2016-06-14 22:28:49 +02:00
parent a66b32d488
commit ee3f679aa3
13 changed files with 854 additions and 153 deletions
@@ -918,6 +918,9 @@
<Compile Include="..\Renci.SshNet\SshCommand.cs">
<Link>SshCommand.cs</Link>
</Compile>
<Compile Include="..\Renci.SshNet\SshMessageFactory.cs">
<Link>SshMessageFactory.cs</Link>
</Compile>
<Compile Include="..\Renci.SshNet\SubsystemSession.cs">
<Link>SubsystemSession.cs</Link>
</Compile>
@@ -937,7 +940,7 @@
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<ProjectExtensions>
<VisualStudio>
<UserProperties ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" />
<UserProperties ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" />
</VisualStudio>
</ProjectExtensions>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
-24
View File
@@ -58,29 +58,5 @@ namespace Renci.SshNet
HandleMessage(message);
}
}
partial void InternalRegisterMessage(string messageName)
{
lock (_messagesMetadata)
{
foreach (var m in from m in _messagesMetadata where m.Name == messageName select m)
{
m.Enabled = true;
m.Activated = true;
}
}
}
partial void InternalUnRegisterMessage(string messageName)
{
lock (_messagesMetadata)
{
foreach (var m in from m in _messagesMetadata where m.Name == messageName select m)
{
m.Enabled = false;
m.Activated = false;
}
}
}
}
}
@@ -12,29 +12,5 @@ namespace Renci.SshNet
{
isConnected = (_socket != null && _socket.Connected);
}
partial void InternalRegisterMessage(string messageName)
{
lock (_messagesMetadata)
{
foreach (var item in from m in _messagesMetadata where m.Name == messageName select m)
{
item.Enabled = true;
item.Activated = true;
}
}
}
partial void InternalUnRegisterMessage(string messageName)
{
lock (_messagesMetadata)
{
foreach (var item in from m in _messagesMetadata where m.Name == messageName select m)
{
item.Enabled = false;
item.Activated = false;
}
}
}
}
}
@@ -892,6 +892,9 @@
<Compile Include="..\Renci.SshNet\SshCommand.cs">
<Link>SshCommand.cs</Link>
</Compile>
<Compile Include="..\Renci.SshNet\SshMessageFactory.cs">
<Link>SshMessageFactory.cs</Link>
</Compile>
<Compile Include="..\Renci.SshNet\SubsystemSession.cs">
<Link>SubsystemSession.cs</Link>
</Compile>
@@ -912,7 +915,7 @@
<FlavorProperties GUID="{A1591282-1198-4647-A2B1-27E5FF5F6F3B}">
<SilverlightProjectProperties />
</FlavorProperties>
<UserProperties ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" />
<UserProperties ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" />
</VisualStudio>
</ProjectExtensions>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
@@ -70,6 +70,9 @@
<Compile Include="..\..\test\Renci.SshNet.Shared.Tests\Abstractions\ThreadAbstraction_ExecuteThread.cs">
<Link>Classes\Abstractions\ThreadAbstraction_ExecuteThread.cs</Link>
</Compile>
<Compile Include="..\..\test\Renci.SshNet.Shared.Tests\SshMessageFactoryTest.cs">
<Link>Classes\SshMessageFactoryTest.cs</Link>
</Compile>
<Compile Include="Classes\BaseClientTest_Connected_KeepAlivesNotSentConcurrently.cs" />
<Compile Include="Classes\Channels\ChannelDirectTcpipTest.cs" />
<Compile Include="Classes\Channels\ChannelDirectTcpipTest_Close_SessionIsConnectedAndChannelIsOpen.cs" />
@@ -960,6 +960,9 @@
<Compile Include="..\Renci.SshNet\SshCommand.cs">
<Link>SshCommand.cs</Link>
</Compile>
<Compile Include="..\Renci.SshNet\SshMessageFactory.cs">
<Link>SshMessageFactory.cs</Link>
</Compile>
<Compile Include="..\Renci.SshNet\SubsystemSession.cs">
<Link>SubsystemSession.cs</Link>
</Compile>
@@ -910,6 +910,9 @@
<Compile Include="..\Renci.SshNet\SshCommand.cs">
<Link>SshCommand.cs</Link>
</Compile>
<Compile Include="..\Renci.SshNet\SshMessageFactory.cs">
<Link>SshMessageFactory.cs</Link>
</Compile>
<Compile Include="..\Renci.SshNet\SubsystemSession.cs">
<Link>SubsystemSession.cs</Link>
</Compile>
@@ -932,7 +935,7 @@
<Import Project="$(MSBuildExtensionsPath)\Microsoft\$(TargetFrameworkIdentifier)\$(TargetFrameworkVersion)\Microsoft.$(TargetFrameworkIdentifier).CSharp.targets" />
<ProjectExtensions>
<VisualStudio>
<UserProperties ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" />
<UserProperties ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" />
</VisualStudio>
</ProjectExtensions>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+1
View File
@@ -452,6 +452,7 @@
</Compile>
<Compile Include="SshClient.cs" />
<Compile Include="SshCommand.cs" />
<Compile Include="SshMessageFactory.cs" />
<Compile Include="SubsystemSession.cs" />
</ItemGroup>
<ItemGroup>
-20
View File
@@ -13,25 +13,5 @@ namespace Renci.SshNet
{
HandleMessage((dynamic)message);
}
partial void InternalRegisterMessage(string messageName)
{
lock (_messagesMetadata)
{
Parallel.ForEach(
from m in _messagesMetadata where m.Name == messageName select m,
item => { item.Enabled = true; item.Activated = true; });
}
}
partial void InternalUnRegisterMessage(string messageName)
{
lock (_messagesMetadata)
{
Parallel.ForEach(
from m in _messagesMetadata where m.Name == messageName select m,
item => { item.Enabled = false; item.Activated = false; });
}
}
}
}
+11 -82
View File
@@ -84,7 +84,7 @@ namespace Renci.SshNet
/// <summary>
/// Holds metada about session messages
/// </summary>
private IEnumerable<MessageMetadata> _messagesMetadata;
private SshMessageFactory _sshMessageFactory;
/// <summary>
/// Holds connection socket.
@@ -523,7 +523,7 @@ namespace Renci.SshNet
Reset();
// Build list of available messages while connecting
_messagesMetadata = GetMessagesMetadata();
_sshMessageFactory = new SshMessageFactory();
switch (ConnectionInfo.ProxyType)
{
@@ -621,6 +621,8 @@ namespace Renci.SshNet
ConnectionInfo.Authenticate(this, _serviceFactory);
_isAuthenticated = true;
Thread.Sleep(2000);
// Register Connection messages
RegisterMessage("SSH_MSG_GLOBAL_REQUEST");
RegisterMessage("SSH_MSG_REQUEST_SUCCESS");
@@ -864,49 +866,6 @@ namespace Renci.SshNet
}
}
private static IEnumerable<MessageMetadata> GetMessagesMetadata()
{
return new []
{
new MessageMetadata { Name = "SSH_MSG_NEWKEYS", Number = 21, Type = typeof(NewKeysMessage) },
new MessageMetadata { Name = "SSH_MSG_REQUEST_FAILURE", Number = 82, Type = typeof(RequestFailureMessage) },
new MessageMetadata { Name = "SSH_MSG_KEXINIT", Number = 20, Type = typeof(KeyExchangeInitMessage) },
new MessageMetadata { Name = "SSH_MSG_CHANNEL_OPEN_FAILURE", Number = 92, Type = typeof(ChannelOpenFailureMessage) },
new MessageMetadata { Name = "SSH_MSG_CHANNEL_FAILURE", Number = 100, Type = typeof(ChannelFailureMessage) },
new MessageMetadata { Name = "SSH_MSG_CHANNEL_EXTENDED_DATA", Number = 95, Type = typeof(ChannelExtendedDataMessage) },
new MessageMetadata { Name = "SSH_MSG_CHANNEL_DATA", Number = 94, Type = typeof(ChannelDataMessage) },
new MessageMetadata { Name = "SSH_MSG_USERAUTH_REQUEST", Number = 50, Type = typeof(RequestMessage) },
new MessageMetadata { Name = "SSH_MSG_CHANNEL_REQUEST", Number = 98, Type = typeof(ChannelRequestMessage) },
new MessageMetadata { Name = "SSH_MSG_USERAUTH_BANNER", Number = 53, Type = typeof(BannerMessage) },
new MessageMetadata { Name = "SSH_MSG_USERAUTH_INFO_RESPONSE", Number = 61, Type = typeof(InformationResponseMessage) },
new MessageMetadata { Name = "SSH_MSG_USERAUTH_FAILURE", Number = 51, Type = typeof(FailureMessage) },
new MessageMetadata { Name = "SSH_MSG_DEBUG", Number = 4, Type = typeof(DebugMessage), },
new MessageMetadata { Name = "SSH_MSG_KEXDH_INIT", Number = 30, Type = typeof(KeyExchangeDhInitMessage) },
new MessageMetadata { Name = "SSH_MSG_GLOBAL_REQUEST", Number = 80, Type = typeof(GlobalRequestMessage) },
new MessageMetadata { Name = "SSH_MSG_CHANNEL_OPEN", Number = 90, Type = typeof(ChannelOpenMessage) },
new MessageMetadata { Name = "SSH_MSG_CHANNEL_OPEN_CONFIRMATION", Number = 91, Type = typeof(ChannelOpenConfirmationMessage) },
new MessageMetadata { Name = "SSH_MSG_USERAUTH_INFO_REQUEST", Number = 60, Type = typeof(InformationRequestMessage) },
new MessageMetadata { Name = "SSH_MSG_UNIMPLEMENTED", Number = 3, Type = typeof(UnimplementedMessage) },
new MessageMetadata { Name = "SSH_MSG_REQUEST_SUCCESS", Number = 81, Type = typeof(RequestSuccessMessage) },
new MessageMetadata { Name = "SSH_MSG_CHANNEL_SUCCESS", Number = 99, Type = typeof(ChannelSuccessMessage) },
new MessageMetadata { Name = "SSH_MSG_USERAUTH_PASSWD_CHANGEREQ", Number = 60, Type = typeof(PasswordChangeRequiredMessage) },
new MessageMetadata { Name = "SSH_MSG_DISCONNECT", Number = 1, Type = typeof(DisconnectMessage) },
new MessageMetadata { Name = "SSH_MSG_SERVICE_REQUEST", Number = 5, Type = typeof(ServiceRequestMessage) },
new MessageMetadata { Name = "SSH_MSG_KEX_DH_GEX_REQUEST", Number = 34, Type = typeof(KeyExchangeDhGroupExchangeRequest) },
new MessageMetadata { Name = "SSH_MSG_KEX_DH_GEX_GROUP", Number = 31, Type = typeof(KeyExchangeDhGroupExchangeGroup) },
new MessageMetadata { Name = "SSH_MSG_USERAUTH_SUCCESS", Number = 52, Type = typeof(SuccessMessage) },
new MessageMetadata { Name = "SSH_MSG_USERAUTH_PK_OK", Number = 60, Type = typeof(PublicKeyMessage) },
new MessageMetadata { Name = "SSH_MSG_IGNORE", Number = 2, Type = typeof(IgnoreMessage) },
new MessageMetadata { Name = "SSH_MSG_CHANNEL_WINDOW_ADJUST", Number = 93, Type = typeof(ChannelWindowAdjustMessage) },
new MessageMetadata { Name = "SSH_MSG_CHANNEL_EOF", Number = 96, Type = typeof(ChannelEofMessage) },
new MessageMetadata { Name = "SSH_MSG_CHANNEL_CLOSE", Number = 97, Type = typeof(ChannelCloseMessage) },
new MessageMetadata { Name = "SSH_MSG_SERVICE_ACCEPT", Number = 6, Type = typeof(ServiceAcceptMessage) },
new MessageMetadata { Name = "SSH_MSG_KEXDH_REPLY", Number = 31, Type = typeof(KeyExchangeDhReplyMessage) },
new MessageMetadata { Name = "SSH_MSG_KEX_DH_GEX_INIT", Number = 32, Type = typeof(KeyExchangeDhGroupExchangeInit) },
new MessageMetadata { Name = "SSH_MSG_KEX_DH_GEX_REPLY", Number = 33, Type = typeof(KeyExchangeDhGroupExchangeReply) }
};
}
/// <summary>
/// Receives the message from the server.
/// </summary>
@@ -1332,12 +1291,8 @@ namespace Renci.SshNet
_keyExchangeCompletedWaitHandle.Reset();
// Disable all registered messages except key exchange related
foreach (var messageMetadata in _messagesMetadata)
{
if (messageMetadata.Activated && messageMetadata.Number > 2 && (messageMetadata.Number < 20 || messageMetadata.Number > 30))
messageMetadata.Enabled = false;
}
// Disable messages that are not key exchange related
_sshMessageFactory.DisableNonKeyExchangeMessages();
_keyExchange = _serviceFactory.CreateKeyExchange(ConnectionInfo.KeyExchangeAlgorithms,
message.KeyExchangeAlgorithms);
@@ -1395,12 +1350,8 @@ namespace Renci.SshNet
_keyExchange = null;
}
// Enable all active registered messages
foreach (var messageMetadata in _messagesMetadata)
{
if (messageMetadata.Activated)
messageMetadata.Enabled = true;
}
// Enable activated messages that are not key exchange related
_sshMessageFactory.EnableActivatedMessages();
var newKeysReceived = NewKeysReceived;
if (newKeysReceived != null)
@@ -1662,7 +1613,7 @@ namespace Renci.SshNet
/// <param name="messageName">The name of the message to register with the session.</param>
public void RegisterMessage(string messageName)
{
InternalRegisterMessage(messageName);
_sshMessageFactory.EnableAndActivateMessage(messageName);
}
/// <summary>
@@ -1671,7 +1622,7 @@ namespace Renci.SshNet
/// <param name="messageName">The name of the message to unregister with the session.</param>
public void UnRegisterMessage(string messageName)
{
InternalUnRegisterMessage(messageName);
_sshMessageFactory.DisableAndDeactivateMessage(messageName);
}
/// <summary>
@@ -1686,12 +1637,7 @@ namespace Renci.SshNet
private Message LoadMessage(byte[] data, int offset)
{
var messageType = data[offset];
var messageMetadata = (from m in _messagesMetadata where m.Number == messageType && m.Enabled && m.Activated select m).FirstOrDefault();
if (messageMetadata == null)
throw new SshException(string.Format(CultureInfo.CurrentCulture, "Message type {0} is not valid.", messageType));
var message = messageMetadata.Type.CreateInstance<Message>();
var message = _sshMessageFactory.Create(messageType);
message.Load(data, offset);
DiagnosticAbstraction.Log(string.Format("ReceiveMessage from server: '{0}': '{1}'.", message.GetType().Name, message));
@@ -1699,10 +1645,6 @@ namespace Renci.SshNet
return message;
}
partial void InternalRegisterMessage(string messageName);
partial void InternalUnRegisterMessage(string messageName);
#endregion
/// <summary>
@@ -2402,18 +2344,5 @@ namespace Renci.SshNet
}
#endregion ISession implementation
private class MessageMetadata
{
public string Name { get; set; }
public byte Number { get; set; }
public bool Enabled { get; set; }
public bool Activated { get; set; }
public Type Type { get; set; }
}
}
}
+191
View File
@@ -0,0 +1,191 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using Renci.SshNet.Common;
using Renci.SshNet.Messages;
using Renci.SshNet.Messages.Authentication;
using Renci.SshNet.Messages.Connection;
using Renci.SshNet.Messages.Transport;
namespace Renci.SshNet
{
internal class SshMessageFactory
{
private readonly MessageMetadata[] _enabledMessagesByNumber;
private readonly bool[] _activatedMessagesById;
private static readonly MessageMetadata[] AllMessages;
private static readonly IDictionary<string, MessageMetadata> MessagesByName;
static SshMessageFactory()
{
AllMessages = new MessageMetadata[]
{
new MessageMetadata<KeyExchangeInitMessage>(0, "SSH_MSG_KEXINIT", 20),
new MessageMetadata<NewKeysMessage> (1, "SSH_MSG_NEWKEYS", 21),
new MessageMetadata<RequestFailureMessage> (2, "SSH_MSG_REQUEST_FAILURE", 82),
new MessageMetadata<ChannelOpenFailureMessage> (3, "SSH_MSG_CHANNEL_OPEN_FAILURE", 92),
new MessageMetadata<ChannelFailureMessage> (4, "SSH_MSG_CHANNEL_FAILURE", 100),
new MessageMetadata<ChannelExtendedDataMessage> (5, "SSH_MSG_CHANNEL_EXTENDED_DATA", 95),
new MessageMetadata<ChannelDataMessage> (6, "SSH_MSG_CHANNEL_DATA", 94),
new MessageMetadata<ChannelRequestMessage> (7, "SSH_MSG_CHANNEL_REQUEST", 98),
new MessageMetadata<BannerMessage> (8, "SSH_MSG_USERAUTH_BANNER", 53),
new MessageMetadata<InformationResponseMessage> (9, "SSH_MSG_USERAUTH_INFO_RESPONSE", 61),
new MessageMetadata<FailureMessage> (10, "SSH_MSG_USERAUTH_FAILURE", 51),
new MessageMetadata<DebugMessage> (11, "SSH_MSG_DEBUG", 4),
new MessageMetadata<GlobalRequestMessage> (12, "SSH_MSG_GLOBAL_REQUEST", 80),
new MessageMetadata<ChannelOpenMessage> (13, "SSH_MSG_CHANNEL_OPEN", 90),
new MessageMetadata<ChannelOpenConfirmationMessage> (14, "SSH_MSG_CHANNEL_OPEN_CONFIRMATION", 91),
new MessageMetadata<InformationRequestMessage> (15, "SSH_MSG_USERAUTH_INFO_REQUEST", 60),
new MessageMetadata<UnimplementedMessage> (16, "SSH_MSG_UNIMPLEMENTED", 3),
new MessageMetadata<RequestSuccessMessage> (17, "SSH_MSG_REQUEST_SUCCESS", 81),
new MessageMetadata<ChannelSuccessMessage> (18, "SSH_MSG_CHANNEL_SUCCESS", 99),
new MessageMetadata<PasswordChangeRequiredMessage> (19, "SSH_MSG_USERAUTH_PASSWD_CHANGEREQ", 60),
new MessageMetadata<DisconnectMessage> (20, "SSH_MSG_DISCONNECT", 1),
new MessageMetadata<SuccessMessage> (21, "SSH_MSG_USERAUTH_SUCCESS", 52),
new MessageMetadata<PublicKeyMessage> (22, "SSH_MSG_USERAUTH_PK_OK", 60),
new MessageMetadata<IgnoreMessage> (23, "SSH_MSG_IGNORE", 2),
new MessageMetadata<ChannelWindowAdjustMessage> (24, "SSH_MSG_CHANNEL_WINDOW_ADJUST", 93),
new MessageMetadata<ChannelEofMessage> (25, "SSH_MSG_CHANNEL_EOF", 96),
new MessageMetadata<ChannelCloseMessage> (26, "SSH_MSG_CHANNEL_CLOSE", 97),
new MessageMetadata<ServiceAcceptMessage> (27, "SSH_MSG_SERVICE_ACCEPT", 6),
new MessageMetadata<KeyExchangeDhGroupExchangeGroup> (28, "SSH_MSG_KEX_DH_GEX_GROUP", 31),
new MessageMetadata<KeyExchangeDhReplyMessage> (29, "SSH_MSG_KEXDH_REPLY", 31),
new MessageMetadata<KeyExchangeDhGroupExchangeReply> (30, "SSH_MSG_KEX_DH_GEX_REPLY", 33)
};
MessagesByName = new Dictionary<string, MessageMetadata>(31);
foreach (var messageMetadata in AllMessages)
MessagesByName.Add(messageMetadata.Name, messageMetadata);
}
public SshMessageFactory()
{
_activatedMessagesById = new bool[31];
_enabledMessagesByNumber = new MessageMetadata[101];
}
/// <summary>
/// Disables and deactivate all messages.
/// </summary>
public void Reset()
{
Array.Clear(_activatedMessagesById, 0, _activatedMessagesById.Length);
Array.Clear(_enabledMessagesByNumber, 0, _enabledMessagesByNumber.Length);
}
public Message Create(byte messageNumber)
{
var messageMetadata = _enabledMessagesByNumber[messageNumber];
if (messageMetadata == null)
{
throw new SshException(string.Format(CultureInfo.CurrentCulture, "Message type {0} is not valid.", messageNumber));
}
return messageMetadata.Create();
}
public void DisableNonKeyExchangeMessages()
{
foreach (var messageMetadata in AllMessages)
{
var messageNumber = messageMetadata.Number;
if ((messageNumber > 2 && messageNumber < 20) || messageNumber > 30)
{
//Console.WriteLine("Disabling " + messageMetadata.Name + "...");
_enabledMessagesByNumber[messageNumber] = null;
}
}
}
public void EnableActivatedMessages()
{
foreach (var messageMetadata in AllMessages)
{
if (!_activatedMessagesById[messageMetadata.Id])
continue;
var enabledMessage = _enabledMessagesByNumber[messageMetadata.Number];
if (enabledMessage != null && enabledMessage != messageMetadata)
{
throw new Exception("Message X is already enabled for Y");
}
_enabledMessagesByNumber[messageMetadata.Number] = messageMetadata;
}
}
public void EnableAndActivateMessage(string messageName)
{
lock (this)
{
MessageMetadata messageMetadata;
if (!MessagesByName.TryGetValue(messageName, out messageMetadata))
{
throw new Exception("TODO");
}
var enabledMessage = _enabledMessagesByNumber[messageMetadata.Number];
if (enabledMessage != null && enabledMessage != messageMetadata)
{
throw new Exception("Message X is already enabled for Y");
}
_enabledMessagesByNumber[messageMetadata.Number] = messageMetadata;
_activatedMessagesById[messageMetadata.Id] = true;
}
}
public void DisableAndDeactivateMessage(string messageName)
{
lock (this)
{
MessageMetadata messageMetadata;
if (!MessagesByName.TryGetValue(messageName, out messageMetadata))
{
throw new Exception("TODO");
}
_activatedMessagesById[messageMetadata.Id] = false;
var enabledMetadata = _enabledMessagesByNumber[messageMetadata.Number];
if (enabledMetadata != null && enabledMetadata != messageMetadata)
throw new Exception();
_enabledMessagesByNumber[messageMetadata.Number] = null;
}
}
private abstract class MessageMetadata
{
protected MessageMetadata(byte id, string name, byte number)
{
Id = id;
Name = name;
Number = number;
}
public readonly byte Id;
public readonly string Name;
public readonly byte Number;
public abstract Message Create();
}
private class MessageMetadata<T> : MessageMetadata where T : Message, new()
{
public MessageMetadata(byte id, string name, byte number)
: base(id, name, number)
{
}
public override Message Create()
{
return new T();
}
}
}
}
@@ -12,5 +12,6 @@
<Compile Include="$(MSBuildThisFileDirectory)Abstractions\CryptoAbstraction_GenerateRandom.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Abstractions\DnsAbstraction_GetHostAddresses.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Abstractions\ThreadAbstraction_ExecuteThread.cs" />
<Compile Include="$(MSBuildThisFileDirectory)SshMessageFactoryTest.cs" />
</ItemGroup>
</Project>
@@ -0,0 +1,632 @@
#if SILVERLIGHT
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
#else
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Renci.SshNet.Common;
using Renci.SshNet.Messages;
using Renci.SshNet.Messages.Authentication;
using Renci.SshNet.Messages.Connection;
using Renci.SshNet.Messages.Transport;
#endif
namespace Renci.SshNet.Tests
{
[TestClass]
public class SshMessageFactoryTest
{
private SshMessageFactory _sshMessageFactory;
private SshMessageFactoryOriginal _sshMessageFactoryOriginal;
[TestInitialize]
public void SetUp()
{
_sshMessageFactory = new SshMessageFactory();
_sshMessageFactoryOriginal = new SshMessageFactoryOriginal();
}
[TestMethod]
public void EnableInformationRequestMessage()
{
_sshMessageFactory.EnableAndActivateMessage("SSH_MSG_USERAUTH_INFO_REQUEST");
var message = _sshMessageFactory.Create(60);
Assert.AreEqual(typeof(InformationRequestMessage), message.GetType());
}
[TestMethod]
public void EnablePasswordChangeRequiredMessage()
{
_sshMessageFactory.EnableAndActivateMessage("SSH_MSG_USERAUTH_PASSWD_CHANGEREQ");
var message = _sshMessageFactory.Create(60);
Assert.AreEqual(typeof(PasswordChangeRequiredMessage), message.GetType());
}
[TestMethod]
public void Di()
{
_sshMessageFactory.EnableAndActivateMessage("SSH_MSG_USERAUTH_PASSWD_CHANGEREQ");
_sshMessageFactory.EnableAndActivateMessage("SSH_MSG_SERVICE_ACCEPT");
_sshMessageFactory.DisableNonKeyExchangeMessages();
try
{
_sshMessageFactory.Create(6); // SSH_MSG_SERVICE_ACCEPT
Assert.Fail();
}
catch (SshException)
{
}
try
{
_sshMessageFactory.Create(60);
// SSH_MSG_USERAUTH_PASSWD_CHANGEREQ or SSH_MSG_USERAUTH_INFO_REQUEST or SSH_MSG_USERAUTH_PK_OK
Assert.Fail();
}
catch (SshException)
{
}
_sshMessageFactory.EnableAndActivateMessage("SSH_MSG_USERAUTH_INFO_REQUEST");
var message = _sshMessageFactory.Create(60);
Assert.AreEqual(typeof(InformationRequestMessage), message.GetType());
_sshMessageFactory.EnableActivatedMessages();
var message2 = _sshMessageFactory.Create(60);
Assert.AreEqual(typeof(InformationRequestMessage), message2.GetType());
}
[TestMethod]
public void Performance_Ctor()
{
const int runCount = 100000;
// warm-up
for (var i = 0; i < 3; i++)
{
var sshMessageFactory = new SshMessageFactory();
var sshMessageFactoryOriginal = new SshMessageFactoryOriginal();
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
var stopwatch = new Stopwatch();
stopwatch.Start();
for (var i = 0; i < runCount; i++)
{
var sshMessageFactory = new SshMessageFactory();
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds);
stopwatch.Reset();
stopwatch.Start();
for (var i = 0; i < runCount; i++)
{
var sshMessageFactory = new SshMessageFactoryOriginal();
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds);
}
[TestMethod]
public void Performance_Create()
{
const int runCount = 10000000;
const string messageName = "SSH_MSG_CHANNEL_CLOSE";
_sshMessageFactory.EnableAndActivateMessage(messageName);
_sshMessageFactoryOriginal.EnableAndActivateMessage(messageName);
// warm-up
for (var i = 0; i < 3; i++)
{
_sshMessageFactory.Create(97);
_sshMessageFactoryOriginal.Create(97);
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
var stopwatch = new Stopwatch();
stopwatch.Start();
for (var i = 0; i < runCount; i++)
{
var msg = _sshMessageFactory.Create(97);
if (msg == null)
Console.WriteLine();
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds);
stopwatch.Reset();
stopwatch.Start();
for (var i = 0; i < runCount; i++)
{
var msg = _sshMessageFactoryOriginal.Create(97);
if (msg == null)
Console.WriteLine();
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds);
}
[TestMethod]
public void Performance_EnableAndActivateMessage()
{
const int runCount = 1000000;
const string messageName = "SSH_MSG_CHANNEL_CLOSE";
// warm-up
for (var i = 0; i < 3; i++)
{
_sshMessageFactory.EnableAndActivateMessage(messageName);
_sshMessageFactoryOriginal.EnableAndActivateMessage(messageName);
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
var stopwatch = new Stopwatch();
stopwatch.Start();
for (var i = 0; i < runCount; i++)
_sshMessageFactory.EnableAndActivateMessage(messageName);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds);
stopwatch.Reset();
stopwatch.Start();
for (var i = 0; i < runCount; i++)
_sshMessageFactoryOriginal.EnableAndActivateMessage(messageName);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds);
}
[TestMethod]
public void Performance_DisableAndDeactivateMessage()
{
const int runCount = 1000000;
const string messageName = "SSH_MSG_CHANNEL_CLOSE";
// warm-up
for (var i = 0; i < 3; i++)
{
_sshMessageFactory.DisableAndDeactivateMessage(messageName);
_sshMessageFactoryOriginal.DisableAndDeactivateMessage(messageName);
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
var stopwatch = new Stopwatch();
stopwatch.Start();
for (var i = 0; i < runCount; i++)
_sshMessageFactory.DisableAndDeactivateMessage(messageName);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds);
stopwatch.Reset();
stopwatch.Start();
for (var i = 0; i < runCount; i++)
_sshMessageFactoryOriginal.DisableAndDeactivateMessage(messageName);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds);
}
[TestMethod]
public void Performance_DisableNonKeyExchangeMessages()
{
const int runCount = 1000000;
_sshMessageFactory.EnableAndActivateMessage("SSH_MSG_USERAUTH_BANNER");
_sshMessageFactory.EnableAndActivateMessage("SSH_MSG_DEBUG");
_sshMessageFactory.EnableAndActivateMessage("SSH_MSG_UNIMPLEMENTED");
_sshMessageFactory.EnableAndActivateMessage("SSH_MSG_SERVICE_ACCEPT");
_sshMessageFactoryOriginal.EnableAndActivateMessage("SSH_MSG_USERAUTH_BANNER");
_sshMessageFactoryOriginal.EnableAndActivateMessage("SSH_MSG_DEBUG");
_sshMessageFactoryOriginal.EnableAndActivateMessage("SSH_MSG_UNIMPLEMENTED");
_sshMessageFactoryOriginal.EnableAndActivateMessage("SSH_MSG_SERVICE_ACCEPT");
// warm-up
for (var i = 0; i < 3; i++)
{
_sshMessageFactory.DisableNonKeyExchangeMessages();
_sshMessageFactory.EnableActivatedMessages();
_sshMessageFactoryOriginal.DisableNonKeyExchangeMessages();
_sshMessageFactoryOriginal.EnableActivatedMessages();
}
//Console.WriteLine("Starting test");
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
var stopwatch = new Stopwatch();
stopwatch.Start();
for (var i = 0; i < runCount; i++)
{
_sshMessageFactory.DisableNonKeyExchangeMessages();
_sshMessageFactory.EnableActivatedMessages();
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds);
stopwatch.Reset();
stopwatch.Start();
for (var i = 0; i < runCount; i++)
{
_sshMessageFactoryOriginal.DisableNonKeyExchangeMessages();
_sshMessageFactoryOriginal.EnableActivatedMessages();
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds);
}
internal class SshMessageFactoryOriginal
{
private readonly IEnumerable<MessageMetadata> _messagesMetadata;
public SshMessageFactoryOriginal()
{
_messagesMetadata = new[]
{
new MessageMetadata {Name = "SSH_MSG_NEWKEYS", Number = 21, Type = typeof(NewKeysMessage)},
new MessageMetadata
{
Name = "SSH_MSG_REQUEST_FAILURE",
Number = 82,
Type = typeof(RequestFailureMessage)
},
new MessageMetadata {Name = "SSH_MSG_KEXINIT", Number = 20, Type = typeof(KeyExchangeInitMessage)},
new MessageMetadata
{
Name = "SSH_MSG_CHANNEL_OPEN_FAILURE",
Number = 92,
Type = typeof(ChannelOpenFailureMessage)
},
new MessageMetadata
{
Name = "SSH_MSG_CHANNEL_FAILURE",
Number = 100,
Type = typeof(ChannelFailureMessage)
},
new MessageMetadata
{
Name = "SSH_MSG_CHANNEL_EXTENDED_DATA",
Number = 95,
Type = typeof(ChannelExtendedDataMessage)
},
new MessageMetadata
{
Name = "SSH_MSG_CHANNEL_DATA",
Number = 94,
Type = typeof(ChannelDataMessage)
},
new MessageMetadata
{
Name = "SSH_MSG_USERAUTH_REQUEST",
Number = 50,
Type = typeof(RequestMessage)
},
new MessageMetadata
{
Name = "SSH_MSG_CHANNEL_REQUEST",
Number = 98,
Type = typeof(ChannelRequestMessage)
},
new MessageMetadata
{
Name = "SSH_MSG_USERAUTH_BANNER",
Number = 53,
Type = typeof(BannerMessage)
},
new MessageMetadata
{
Name = "SSH_MSG_USERAUTH_INFO_RESPONSE",
Number = 61,
Type = typeof(InformationResponseMessage)
},
new MessageMetadata
{
Name = "SSH_MSG_USERAUTH_FAILURE",
Number = 51,
Type = typeof(FailureMessage)
},
new MessageMetadata {Name = "SSH_MSG_DEBUG", Number = 4, Type = typeof(DebugMessage),},
new MessageMetadata
{
Name = "SSH_MSG_KEXDH_INIT",
Number = 30,
Type = typeof(KeyExchangeDhInitMessage)
},
new MessageMetadata
{
Name = "SSH_MSG_GLOBAL_REQUEST",
Number = 80,
Type = typeof(GlobalRequestMessage)
},
new MessageMetadata
{
Name = "SSH_MSG_CHANNEL_OPEN",
Number = 90,
Type = typeof(ChannelOpenMessage)
},
new MessageMetadata
{
Name = "SSH_MSG_CHANNEL_OPEN_CONFIRMATION",
Number = 91,
Type = typeof(ChannelOpenConfirmationMessage)
},
new MessageMetadata
{
Name = "SSH_MSG_USERAUTH_INFO_REQUEST",
Number = 60,
Type = typeof(InformationRequestMessage)
},
new MessageMetadata
{
Name = "SSH_MSG_UNIMPLEMENTED",
Number = 3,
Type = typeof(UnimplementedMessage)
},
new MessageMetadata
{
Name = "SSH_MSG_REQUEST_SUCCESS",
Number = 81,
Type = typeof(RequestSuccessMessage)
},
new MessageMetadata
{
Name = "SSH_MSG_CHANNEL_SUCCESS",
Number = 99,
Type = typeof(ChannelSuccessMessage)
},
new MessageMetadata
{
Name = "SSH_MSG_USERAUTH_PASSWD_CHANGEREQ",
Number = 60,
Type = typeof(PasswordChangeRequiredMessage)
},
new MessageMetadata {Name = "SSH_MSG_DISCONNECT", Number = 1, Type = typeof(DisconnectMessage)},
new MessageMetadata
{
Name = "SSH_MSG_SERVICE_REQUEST",
Number = 5,
Type = typeof(ServiceRequestMessage)
},
new MessageMetadata
{
Name = "SSH_MSG_KEX_DH_GEX_REQUEST",
Number = 34,
Type = typeof(KeyExchangeDhGroupExchangeRequest)
},
new MessageMetadata
{
Name = "SSH_MSG_KEX_DH_GEX_GROUP",
Number = 31,
Type = typeof(KeyExchangeDhGroupExchangeGroup)
},
new MessageMetadata
{
Name = "SSH_MSG_USERAUTH_SUCCESS",
Number = 52,
Type = typeof(SuccessMessage)
},
new MessageMetadata
{
Name = "SSH_MSG_USERAUTH_PK_OK",
Number = 60,
Type = typeof(PublicKeyMessage)
},
new MessageMetadata {Name = "SSH_MSG_IGNORE", Number = 2, Type = typeof(IgnoreMessage)},
new MessageMetadata
{
Name = "SSH_MSG_CHANNEL_WINDOW_ADJUST",
Number = 93,
Type = typeof(ChannelWindowAdjustMessage)
},
new MessageMetadata
{
Name = "SSH_MSG_CHANNEL_EOF",
Number = 96,
Type = typeof(ChannelEofMessage)
},
new MessageMetadata
{
Name = "SSH_MSG_CHANNEL_CLOSE",
Number = 97,
Type = typeof(ChannelCloseMessage)
},
new MessageMetadata
{
Name = "SSH_MSG_SERVICE_ACCEPT",
Number = 6,
Type = typeof(ServiceAcceptMessage)
},
new MessageMetadata
{
Name = "SSH_MSG_KEXDH_REPLY",
Number = 31,
Type = typeof(KeyExchangeDhReplyMessage)
},
new MessageMetadata
{
Name = "SSH_MSG_KEX_DH_GEX_INIT",
Number = 32,
Type = typeof(KeyExchangeDhGroupExchangeInit)
},
new MessageMetadata
{
Name = "SSH_MSG_KEX_DH_GEX_REPLY",
Number = 33,
Type = typeof(KeyExchangeDhGroupExchangeReply)
}
};
}
/// <summary>
/// Disables and deactivate all messages.
/// </summary>
public void Reset()
{
foreach (var messageMetadata in _messagesMetadata)
{
messageMetadata.Activated = messageMetadata.Enabled = false;
}
}
public void EnableActivatedMessages()
{
foreach (var messageMetadata in _messagesMetadata)
{
if (messageMetadata.Activated)
messageMetadata.Enabled = true;
}
}
public Message Create(byte messageNumber)
{
var messageMetadata =
(from m in _messagesMetadata where m.Number == messageNumber && m.Enabled && m.Activated select m)
.FirstOrDefault();
if (messageMetadata == null)
throw new SshException(string.Format(CultureInfo.CurrentCulture, "Message type {0} is not valid.",
messageNumber));
return messageMetadata.Type.CreateInstance<Message>();
}
public void DisableNonKeyExchangeMessages()
{
foreach (var messageMetadata in _messagesMetadata)
{
if (messageMetadata.Activated && messageMetadata.Number > 2 &&
(messageMetadata.Number < 20 || messageMetadata.Number > 30))
{
//Console.WriteLine("Disabling " + messageMetadata.Name + "...");
messageMetadata.Enabled = false;
}
}
}
public void EnableAndActivateMessage(string messageName)
{
lock (_messagesMetadata)
{
var messagesMetadata = _messagesMetadata.Where(m => m.Name == messageName);
foreach (var messageMetadata in messagesMetadata)
messageMetadata.Enabled = messageMetadata.Activated = true;
}
}
public void DisableAndDeactivateMessage(string messageName)
{
lock (_messagesMetadata)
{
var messagesMetadata = _messagesMetadata.Where(m => m.Name == messageName);
foreach (var messageMetadata in messagesMetadata)
messageMetadata.Enabled = messageMetadata.Activated = false;
}
}
private class MessageMetadata
{
public string Name { get; set; }
public byte Number { get; set; }
public bool Enabled { get; set; }
public bool Activated { get; set; }
public Type Type { get; set; }
}
}
}
}