Skip to content

Add an OrderedDictionary implementation for algorithm priorities #1611

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions src/Renci.SshNet/Common/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -358,5 +358,30 @@ internal static string Join(this IEnumerable<string> values, string separator)
// which is not available on all targets.
return string.Join(separator, values);
}

#if NETFRAMEWORK || NETSTANDARD2_0
internal static bool TryAdd<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key, TValue value)
{
if (!dictionary.ContainsKey(key))
{
dictionary.Add(key, value);
return true;
}

return false;
}

internal static bool Remove<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key, out TValue value)
{
if (dictionary.TryGetValue(key, out value))
{
_ = dictionary.Remove(key);
return true;
}

value = default;
return false;
}
#endif
}
}
42 changes: 20 additions & 22 deletions src/Renci.SshNet/ConnectionInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,42 +50,40 @@ public class ConnectionInfo : IConnectionInfoInternal
/// <summary>
/// Gets supported key exchange algorithms for this connection.
/// </summary>
public IDictionary<string, Func<IKeyExchange>> KeyExchangeAlgorithms { get; private set; }
public IOrderedDictionary<string, Func<IKeyExchange>> KeyExchangeAlgorithms { get; }

/// <summary>
/// Gets supported encryptions for this connection.
/// </summary>
#pragma warning disable CA1859 // Use concrete types when possible for improved performance
public IDictionary<string, CipherInfo> Encryptions { get; private set; }
#pragma warning restore CA1859 // Use concrete types when possible for improved performance
public IOrderedDictionary<string, CipherInfo> Encryptions { get; }

/// <summary>
/// Gets supported hash algorithms for this connection.
/// </summary>
public IDictionary<string, HashInfo> HmacAlgorithms { get; private set; }
public IOrderedDictionary<string, HashInfo> HmacAlgorithms { get; }

/// <summary>
/// Gets supported host key algorithms for this connection.
/// </summary>
public IDictionary<string, Func<byte[], KeyHostAlgorithm>> HostKeyAlgorithms { get; private set; }
public IOrderedDictionary<string, Func<byte[], KeyHostAlgorithm>> HostKeyAlgorithms { get; }

/// <summary>
/// Gets supported authentication methods for this connection.
/// </summary>
public IList<AuthenticationMethod> AuthenticationMethods { get; private set; }
public IList<AuthenticationMethod> AuthenticationMethods { get; }

/// <summary>
/// Gets supported compression algorithms for this connection.
/// </summary>
public IDictionary<string, Func<Compressor>> CompressionAlgorithms { get; private set; }
public IOrderedDictionary<string, Func<Compressor>> CompressionAlgorithms { get; }

/// <summary>
/// Gets the supported channel requests for this connection.
/// </summary>
/// <value>
/// The supported channel requests for this connection.
/// </value>
public IDictionary<string, RequestInfo> ChannelRequests { get; private set; }
public IDictionary<string, RequestInfo> ChannelRequests { get; }

/// <summary>
/// Gets a value indicating whether connection is authenticated.
Expand All @@ -101,48 +99,48 @@ public class ConnectionInfo : IConnectionInfoInternal
/// <value>
/// The connection host.
/// </value>
public string Host { get; private set; }
public string Host { get; }

/// <summary>
/// Gets connection port.
/// </summary>
/// <value>
/// The connection port. The default value is 22.
/// </value>
public int Port { get; private set; }
public int Port { get; }

/// <summary>
/// Gets connection username.
/// </summary>
public string Username { get; private set; }
public string Username { get; }

/// <summary>
/// Gets proxy type.
/// </summary>
/// <value>
/// The type of the proxy.
/// </value>
public ProxyTypes ProxyType { get; private set; }
public ProxyTypes ProxyType { get; }

/// <summary>
/// Gets proxy connection host.
/// </summary>
public string ProxyHost { get; private set; }
public string ProxyHost { get; }

/// <summary>
/// Gets proxy connection port.
/// </summary>
public int ProxyPort { get; private set; }
public int ProxyPort { get; }

/// <summary>
/// Gets proxy connection username.
/// </summary>
public string ProxyUsername { get; private set; }
public string ProxyUsername { get; }

/// <summary>
/// Gets proxy connection password.
/// </summary>
public string ProxyPassword { get; private set; }
public string ProxyPassword { get; }

/// <summary>
/// Gets or sets connection timeout.
Expand Down Expand Up @@ -347,7 +345,7 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy
MaxSessions = 10;
Encoding = Encoding.UTF8;

KeyExchangeAlgorithms = new Dictionary<string, Func<IKeyExchange>>
KeyExchangeAlgorithms = new OrderedDictionary<string, Func<IKeyExchange>>
{
{ "mlkem768x25519-sha256", () => new KeyExchangeMLKem768X25519Sha256() },
{ "sntrup761x25519-sha512", () => new KeyExchangeSNtruP761X25519Sha512() },
Expand All @@ -365,7 +363,7 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy
{ "diffie-hellman-group1-sha1", () => new KeyExchangeDiffieHellmanGroup1Sha1() },
};

Encryptions = new Dictionary<string, CipherInfo>
Encryptions = new OrderedDictionary<string, CipherInfo>
{
{ "aes128-ctr", new CipherInfo(128, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false)) },
{ "aes192-ctr", new CipherInfo(192, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false)) },
Expand All @@ -379,7 +377,7 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy
{ "3des-cbc", new CipherInfo(192, (key, iv) => new TripleDesCipher(key, iv, CipherMode.CBC, pkcs7Padding: false)) },
};

HmacAlgorithms = new Dictionary<string, HashInfo>
HmacAlgorithms = new OrderedDictionary<string, HashInfo>
{
/* Encrypt-and-MAC (encrypt-and-authenticate) variants */
{ "hmac-sha2-256", new HashInfo(32*8, key => new HMACSHA256(key)) },
Expand All @@ -392,7 +390,7 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy
};

#pragma warning disable SA1107 // Code should not contain multiple statements on one line
var hostAlgs = new Dictionary<string, Func<byte[], KeyHostAlgorithm>>();
var hostAlgs = new OrderedDictionary<string, Func<byte[], KeyHostAlgorithm>>();
hostAlgs.Add("[email protected]", data => { var cert = new Certificate(data); return new CertificateHostAlgorithm("[email protected]", cert, hostAlgs); });
hostAlgs.Add("[email protected]", data => { var cert = new Certificate(data); return new CertificateHostAlgorithm("[email protected]", cert, hostAlgs); });
hostAlgs.Add("[email protected]", data => { var cert = new Certificate(data); return new CertificateHostAlgorithm("[email protected]", cert, hostAlgs); });
Expand All @@ -411,7 +409,7 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy
#pragma warning restore SA1107 // Code should not contain multiple statements on one line
HostKeyAlgorithms = hostAlgs;

CompressionAlgorithms = new Dictionary<string, Func<Compressor>>
CompressionAlgorithms = new OrderedDictionary<string, Func<Compressor>>
{
{ "none", null },
{ "[email protected]", () => new ZlibOpenSsh() },
Expand Down
149 changes: 149 additions & 0 deletions src/Renci.SshNet/IOrderedDictionary`2.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;

namespace Renci.SshNet
{
/// <summary>
/// Represents a collection of key/value pairs that are accessible by the key or index.
/// </summary>
/// <typeparam name="TKey">The type of the keys in the dictionary.</typeparam>
/// <typeparam name="TValue">The type of the values in the dictionary.</typeparam>
public interface IOrderedDictionary<TKey, TValue> :
IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue>
where TKey : notnull
{
// Some members are redefined with 'new' to resolve ambiguities.

/// <summary>Gets or sets the value associated with the specified key.</summary>
/// <param name="key">The key of the value to get or set.</param>
/// <returns>The value associated with the specified key. If the specified key is not found, a get operation throws a <see cref="KeyNotFoundException"/>, and a set operation creates a new element with the specified key.</returns>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
/// <exception cref="KeyNotFoundException">The property is retrieved and <paramref name="key"/> does not exist in the collection.</exception>
/// <remarks>Setting the value of an existing key does not impact its order in the collection.</remarks>
new TValue this[TKey key] { get; set; }

/// <summary>Gets a collection containing the keys in the <see cref="IOrderedDictionary{TKey, TValue}"/>.</summary>
new ICollection<TKey> Keys { get; }

/// <summary>Gets a collection containing the values in the <see cref="IOrderedDictionary{TKey, TValue}"/>.</summary>
new ICollection<TValue> Values { get; }

/// <summary>Gets the number of key/value pairs contained in the <see cref="IOrderedDictionary{TKey, TValue}"/>.</summary>
new int Count { get; }

/// <summary>Determines whether the <see cref="IOrderedDictionary{TKey, TValue}"/> contains the specified key.</summary>
/// <param name="key">The key to locate in the <see cref="IOrderedDictionary{TKey, TValue}"/>.</param>
/// <returns><see langword="true"/> if the <see cref="IOrderedDictionary{TKey, TValue}"/> contains an element with the specified key; otherwise, <see langword="false"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
new bool ContainsKey(TKey key);

/// <summary>Determines whether the <see cref="IOrderedDictionary{TKey, TValue}"/> contains a specific value.</summary>
/// <param name="value">The value to locate in the <see cref="IOrderedDictionary{TKey, TValue}"/>. The value can be null for reference types.</param>
/// <returns><see langword="true"/> if the <see cref="IOrderedDictionary{TKey, TValue}"/> contains an element with the specified value; otherwise, <see langword="false"/>.</returns>
bool ContainsValue(TValue value);

/// <summary>Gets the key/value pair at the specified index.</summary>
/// <param name="index">The zero-based index of the pair to get.</param>
/// <returns>The element at the specified index.</returns>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than 0 or greater than or equal to <see cref="Count"/>.</exception>
KeyValuePair<TKey, TValue> GetAt(int index);

/// <summary>Determines the index of a specific key in the <see cref="IOrderedDictionary{TKey, TValue}"/>.</summary>
/// <param name="key">The key to locate.</param>
/// <returns>The index of <paramref name="key"/> if found; otherwise, -1.</returns>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
int IndexOf(TKey key);

/// <summary>Inserts an item into the collection at the specified index.</summary>
/// <param name="index">The zero-based index at which item should be inserted.</param>
/// <param name="key">The key to insert.</param>
/// <param name="value">The value to insert.</param>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException">An element with the same key already exists in the <see cref="IOrderedDictionary{TKey, TValue}"/>.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than 0 or greater than <see cref="Count"/>.</exception>
void Insert(int index, TKey key, TValue value);

/// <summary>Removes the value with the specified key from the <see cref="IOrderedDictionary{TKey, TValue}"/> and copies the element to the value parameter.</summary>
/// <param name="key">The key of the element to remove.</param>
/// <param name="value">The removed element.</param>
/// <returns><see langword="true"/> if the element is successfully found and removed; otherwise, <see langword="false"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
bool Remove(TKey key, [MaybeNullWhen(false)] out TValue value);

/// <summary>Removes the key/value pair at the specified index.</summary>
/// <param name="index">The zero-based index of the item to remove.</param>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than 0 or greater than or equal to <see cref="Count"/>.</exception>
void RemoveAt(int index);

/// <summary>Sets the key/value pair at the specified index.</summary>
/// <param name="index">The zero-based index at which to set the key/value pair.</param>
/// <param name="key">The key to store at the specified index.</param>
/// <param name="value">The value to store at the specified index.</param>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException">An element with the same key already exists at an index different to <paramref name="index"/>.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than 0 or greater than or equal to <see cref="Count"/>.</exception>
void SetAt(int index, TKey key, TValue value);

/// <summary>Sets the value for the key at the specified index.</summary>
/// <param name="index">The zero-based index at which to set the key/value pair.</param>
/// <param name="value">The value to store at the specified index.</param>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than 0 or greater than or equal to <see cref="Count"/>.</exception>
void SetAt(int index, TValue value);

/// <summary>
/// Moves an existing key/value pair to the specified index in the collection.
/// </summary>
/// <param name="index">The current zero-based index of the key/value pair to move.</param>
/// <param name="newIndex">The zero-based index at which to set the key/value pair.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="index"/> or <paramref name="newIndex"/> are less than 0 or greater than or equal to <see cref="Count"/>.
/// </exception>
void SetPosition(int index, int newIndex);

/// <summary>
/// Moves an existing key/value pair to the specified index in the collection.
/// </summary>
/// <param name="key">The key to move.</param>
/// <param name="newIndex">The zero-based index at which to set the key/value pair.</param>
/// <exception cref="KeyNotFoundException">The specified key does not exist in the collection.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="newIndex"/> is less than 0 or greater than or equal to <see cref="Count"/>.</exception>
void SetPosition(TKey key, int newIndex);

/// <summary>Adds the specified key and value to the dictionary if the key doesn't already exist.</summary>
/// <param name="key">The key of the element to add.</param>
/// <param name="value">The value of the element to add. The value can be <see langword="null"/> for reference types.</param>
/// <returns><see langword="true"/> if the key didn't exist and the key and value were added to the dictionary; otherwise, <see langword="false"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
bool TryAdd(TKey key, TValue value);

/// <summary>Adds the specified key and value to the dictionary if the key doesn't already exist.</summary>
/// <param name="key">The key of the element to add.</param>
/// <param name="value">The value of the element to add. The value can be <see langword="null"/> for reference types.</param>
/// <param name="index">The index of the added or existing <paramref name="key"/>. This is always a valid index into the dictionary.</param>
/// <returns><see langword="true"/> if the key didn't exist and the key and value were added to the dictionary; otherwise, <see langword="false"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
bool TryAdd(TKey key, TValue value, out int index);

/// <summary>Gets the value associated with the specified key.</summary>
/// <param name="key">The key of the value to get.</param>
/// <param name="value">
/// When this method returns, contains the value associated with the specified key, if the key is found;
/// otherwise, the default value for the type of the value parameter.
/// </param>
/// <returns><see langword="true"/> if the <see cref="IOrderedDictionary{TKey, TValue}"/> contains an element with the specified key; otherwise, <see langword="false"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
new bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value);

/// <summary>Gets the value associated with the specified key.</summary>
/// <param name="key">The key of the value to get.</param>
/// <param name="value">
/// When this method returns, contains the value associated with the specified key, if the key is found;
/// otherwise, the default value for the type of the value parameter.
/// </param>
/// <param name="index">The index of <paramref name="key"/> if found; otherwise, -1.</param>
/// <returns><see langword="true"/> if the <see cref="IOrderedDictionary{TKey, TValue}"/> contains an element with the specified key; otherwise, <see langword="false"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value, out int index);
}
}
Loading