Skip to content

Commit 3f6940f

Browse files
authored
Merge branch 'develop' into SFTP_resume
2 parents 17d86bf + 7bdfc9e commit 3f6940f

31 files changed

+1895
-95
lines changed

src/Renci.SshNet.Silverlight/Renci.SshNet.Silverlight.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1358,6 +1358,9 @@
13581358
<Compile Include="..\Renci.SshNet\Sftp\SftpFile.cs">
13591359
<Link>Sftp\SftpFile.cs</Link>
13601360
</Compile>
1361+
<Compile Include="..\Renci.SshNet\Sftp\ISftpFile.cs">
1362+
<Link>Sftp\ISftpFile.cs</Link>
1363+
</Compile>
13611364
<Compile Include="..\Renci.SshNet\Sftp\SftpFileAttributes.cs">
13621365
<Link>Sftp\SftpFileAttributes.cs</Link>
13631366
</Compile>

src/Renci.SshNet.Silverlight5/Renci.SshNet.Silverlight5.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1364,6 +1364,9 @@
13641364
<Compile Include="..\Renci.SshNet\Sftp\SftpFile.cs">
13651365
<Link>Sftp\SftpFile.cs</Link>
13661366
</Compile>
1367+
<Compile Include="..\Renci.SshNet\Sftp\ISftpFile.cs">
1368+
<Link>Sftp\ISftpFile.cs</Link>
1369+
</Compile>
13671370
<Compile Include="..\Renci.SshNet\Sftp\SftpFileAttributes.cs">
13681371
<Link>Sftp\SftpFileAttributes.cs</Link>
13691372
</Compile>

src/Renci.SshNet.Tests/Classes/SftpClientTest.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -562,8 +562,8 @@ public void EndListDirectoryTest()
562562
ConnectionInfo connectionInfo = null; // TODO: Initialize to an appropriate value
563563
SftpClient target = new SftpClient(connectionInfo); // TODO: Initialize to an appropriate value
564564
IAsyncResult asyncResult = null; // TODO: Initialize to an appropriate value
565-
IEnumerable<SftpFile> expected = null; // TODO: Initialize to an appropriate value
566-
IEnumerable<SftpFile> actual;
565+
IEnumerable<ISftpFile> expected = null; // TODO: Initialize to an appropriate value
566+
IEnumerable<ISftpFile> actual;
567567
actual = target.EndListDirectory(asyncResult);
568568
Assert.AreEqual(expected, actual);
569569
Assert.Inconclusive("Verify the correctness of this test method.");
@@ -702,8 +702,8 @@ public void GetTest()
702702
ConnectionInfo connectionInfo = null; // TODO: Initialize to an appropriate value
703703
SftpClient target = new SftpClient(connectionInfo); // TODO: Initialize to an appropriate value
704704
string path = string.Empty; // TODO: Initialize to an appropriate value
705-
SftpFile expected = null; // TODO: Initialize to an appropriate value
706-
SftpFile actual;
705+
ISftpFile expected = null; // TODO: Initialize to an appropriate value
706+
ISftpFile actual;
707707
actual = target.Get(path);
708708
Assert.AreEqual(expected, actual);
709709
Assert.Inconclusive("Verify the correctness of this test method.");
@@ -802,8 +802,8 @@ public void ListDirectoryTest()
802802
SftpClient target = new SftpClient(connectionInfo); // TODO: Initialize to an appropriate value
803803
string path = string.Empty; // TODO: Initialize to an appropriate value
804804
Action<int> listCallback = null; // TODO: Initialize to an appropriate value
805-
IEnumerable<SftpFile> expected = null; // TODO: Initialize to an appropriate value
806-
IEnumerable<SftpFile> actual;
805+
IEnumerable<ISftpFile> expected = null; // TODO: Initialize to an appropriate value
806+
IEnumerable<ISftpFile> actual;
807807
actual = target.ListDirectory(path, listCallback);
808808
Assert.AreEqual(expected, actual);
809809
Assert.Inconclusive("Verify the correctness of this test method.");

src/Renci.SshNet.UAP10/Renci.SshNet.UAP10.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1440,6 +1440,9 @@
14401440
<Compile Include="..\Renci.SshNet\Sftp\SftpFile.cs">
14411441
<Link>Sftp\SftpFile.cs</Link>
14421442
</Compile>
1443+
<Compile Include="..\Renci.SshNet\Sftp\ISftpFile.cs">
1444+
<Link>Sftp\ISftpFile.cs</Link>
1445+
</Compile>
14431446
<Compile Include="..\Renci.SshNet\Sftp\SftpFileAttributes.cs">
14441447
<Link>Sftp\SftpFileAttributes.cs</Link>
14451448
</Compile>

src/Renci.SshNet.WindowsPhone/Renci.SshNet.WindowsPhone.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1343,6 +1343,9 @@
13431343
<Compile Include="..\Renci.SshNet\Sftp\SftpFile.cs">
13441344
<Link>Sftp\SftpFile.cs</Link>
13451345
</Compile>
1346+
<Compile Include="..\Renci.SshNet\Sftp\ISftpFile.cs">
1347+
<Link>Sftp\ISftpFile.cs</Link>
1348+
</Compile>
13461349
<Compile Include="..\Renci.SshNet\Sftp\SftpFileAttributes.cs">
13471350
<Link>Sftp\SftpFileAttributes.cs</Link>
13481351
</Compile>

src/Renci.SshNet.WindowsPhone8/Renci.SshNet.WindowsPhone8.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1396,6 +1396,9 @@
13961396
<Compile Include="..\Renci.SshNet\Sftp\SftpFile.cs">
13971397
<Link>Sftp\SftpFile.cs</Link>
13981398
</Compile>
1399+
<Compile Include="..\Renci.SshNet\Sftp\ISftpFile.cs">
1400+
<Link>Sftp\ISftpFile.cs</Link>
1401+
</Compile>
13991402
<Compile Include="..\Renci.SshNet\Sftp\SftpFileAttributes.cs">
14001403
<Link>Sftp\SftpFileAttributes.cs</Link>
14011404
</Compile>

src/Renci.SshNet/Abstractions/DnsAbstraction.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
using System.Net;
33
using System.Net.Sockets;
44

5+
#if FEATURE_TAP
6+
using System.Threading.Tasks;
7+
#endif
8+
59
#if FEATURE_DNS_SYNC
610
#elif FEATURE_DNS_APM
711
using Renci.SshNet.Common;
@@ -87,5 +91,23 @@ public static IPAddress[] GetHostAddresses(string hostNameOrAddress)
8791
#endif // FEATURE_DEVICEINFORMATION_APM
8892
#endif
8993
}
94+
95+
#if FEATURE_TAP
96+
/// <summary>
97+
/// Returns the Internet Protocol (IP) addresses for the specified host.
98+
/// </summary>
99+
/// <param name="hostNameOrAddress">The host name or IP address to resolve</param>
100+
/// <returns>
101+
/// A task with result of an array of type <see cref="IPAddress"/> that holds the IP addresses for the host that
102+
/// is specified by the <paramref name="hostNameOrAddress"/> parameter.
103+
/// </returns>
104+
/// <exception cref="ArgumentNullException"><paramref name="hostNameOrAddress"/> is <c>null</c>.</exception>
105+
/// <exception cref="SocketException">An error is encountered when resolving <paramref name="hostNameOrAddress"/>.</exception>
106+
public static Task<IPAddress[]> GetHostAddressesAsync(string hostNameOrAddress)
107+
{
108+
return Dns.GetHostAddressesAsync(hostNameOrAddress);
109+
}
110+
#endif
111+
90112
}
91113
}

src/Renci.SshNet/Abstractions/SocketAbstraction.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
using System.Net;
44
using System.Net.Sockets;
55
using System.Threading;
6+
#if FEATURE_TAP
7+
using System.Threading.Tasks;
8+
#endif
69
using Renci.SshNet.Common;
710
using Renci.SshNet.Messages.Transport;
811

@@ -59,6 +62,13 @@ public static void Connect(Socket socket, IPEndPoint remoteEndpoint, TimeSpan co
5962
ConnectCore(socket, remoteEndpoint, connectTimeout, false);
6063
}
6164

65+
#if FEATURE_TAP
66+
public static Task ConnectAsync(Socket socket, IPEndPoint remoteEndpoint, CancellationToken cancellationToken)
67+
{
68+
return socket.ConnectAsync(remoteEndpoint, cancellationToken);
69+
}
70+
#endif
71+
6272
private static void ConnectCore(Socket socket, IPEndPoint remoteEndpoint, TimeSpan connectTimeout, bool ownsSocket)
6373
{
6474
#if FEATURE_SOCKET_EAP
@@ -317,6 +327,13 @@ public static byte[] Read(Socket socket, int size, TimeSpan timeout)
317327
return buffer;
318328
}
319329

330+
#if FEATURE_TAP
331+
public static Task<int> ReadAsync(Socket socket, byte[] buffer, int offset, int length, CancellationToken cancellationToken)
332+
{
333+
return socket.ReceiveAsync(buffer, offset, length, cancellationToken);
334+
}
335+
#endif
336+
320337
/// <summary>
321338
/// Receives data from a bound <see cref="Socket"/> into a receive buffer.
322339
/// </summary>
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
#if FEATURE_TAP
2+
using System;
3+
using System.Net;
4+
using System.Net.Sockets;
5+
using System.Runtime.CompilerServices;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
9+
namespace Renci.SshNet.Abstractions
10+
{
11+
// Async helpers based on https://devblogs.microsoft.com/pfxteam/awaiting-socket-operations/
12+
13+
internal static class SocketExtensions
14+
{
15+
sealed class SocketAsyncEventArgsAwaitable : SocketAsyncEventArgs, INotifyCompletion
16+
{
17+
private readonly static Action SENTINEL = () => { };
18+
19+
private bool isCancelled;
20+
private Action continuationAction;
21+
22+
public SocketAsyncEventArgsAwaitable()
23+
{
24+
Completed += delegate { SetCompleted(); };
25+
}
26+
27+
public SocketAsyncEventArgsAwaitable ExecuteAsync(Func<SocketAsyncEventArgs, bool> func)
28+
{
29+
if (!func(this))
30+
{
31+
SetCompleted();
32+
}
33+
return this;
34+
}
35+
36+
public void SetCompleted()
37+
{
38+
IsCompleted = true;
39+
var continuation = continuationAction ?? Interlocked.CompareExchange(ref continuationAction, SENTINEL, null);
40+
if (continuation != null)
41+
{
42+
continuation();
43+
}
44+
}
45+
46+
public void SetCancelled()
47+
{
48+
isCancelled = true;
49+
SetCompleted();
50+
}
51+
52+
public SocketAsyncEventArgsAwaitable GetAwaiter() { return this; }
53+
54+
public bool IsCompleted { get; private set; }
55+
56+
void INotifyCompletion.OnCompleted(Action continuation)
57+
{
58+
if (continuationAction == SENTINEL || Interlocked.CompareExchange(ref continuationAction, continuation, null) == SENTINEL)
59+
{
60+
// We have already completed; run continuation asynchronously
61+
Task.Run(continuation);
62+
}
63+
}
64+
65+
public void GetResult()
66+
{
67+
if (isCancelled)
68+
{
69+
throw new TaskCanceledException();
70+
}
71+
else if (IsCompleted)
72+
{
73+
if (SocketError != SocketError.Success)
74+
{
75+
throw new SocketException((int)SocketError);
76+
}
77+
}
78+
else
79+
{
80+
// We don't support sync/async
81+
throw new InvalidOperationException("The asynchronous operation has not yet completed.");
82+
}
83+
}
84+
}
85+
86+
public static async Task ConnectAsync(this Socket socket, IPEndPoint remoteEndpoint, CancellationToken cancellationToken)
87+
{
88+
cancellationToken.ThrowIfCancellationRequested();
89+
90+
using (var args = new SocketAsyncEventArgsAwaitable())
91+
{
92+
args.RemoteEndPoint = remoteEndpoint;
93+
94+
using (cancellationToken.Register(o => ((SocketAsyncEventArgsAwaitable)o).SetCancelled(), args, false))
95+
{
96+
await args.ExecuteAsync(socket.ConnectAsync);
97+
}
98+
}
99+
}
100+
101+
public static async Task<int> ReceiveAsync(this Socket socket, byte[] buffer, int offset, int length, CancellationToken cancellationToken)
102+
{
103+
cancellationToken.ThrowIfCancellationRequested();
104+
105+
using (var args = new SocketAsyncEventArgsAwaitable())
106+
{
107+
args.SetBuffer(buffer, offset, length);
108+
109+
using (cancellationToken.Register(o => ((SocketAsyncEventArgsAwaitable)o).SetCancelled(), args, false))
110+
{
111+
await args.ExecuteAsync(socket.ReceiveAsync);
112+
}
113+
114+
return args.BytesTransferred;
115+
}
116+
}
117+
}
118+
}
119+
#endif

src/Renci.SshNet/Abstractions/ThreadAbstraction.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,17 @@ public static void Sleep(int millisecondsTimeout)
2121

2222
public static void ExecuteThreadLongRunning(Action action)
2323
{
24+
if (action == null)
25+
throw new ArgumentNullException("action");
26+
2427
#if FEATURE_THREAD_TAP
2528
var taskCreationOptions = System.Threading.Tasks.TaskCreationOptions.LongRunning;
2629
System.Threading.Tasks.Task.Factory.StartNew(action, taskCreationOptions);
2730
#else
28-
var thread = new System.Threading.Thread(() => action());
29-
thread.Start();
31+
new System.Threading.Thread(() => action())
32+
{
33+
IsBackground = true
34+
}.Start();
3035
#endif
3136
}
3237

src/Renci.SshNet/BaseClient.cs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
using System;
22
using System.Net.Sockets;
33
using System.Threading;
4+
#if FEATURE_TAP
5+
using System.Threading.Tasks;
6+
#endif
47
using Renci.SshNet.Abstractions;
58
using Renci.SshNet.Common;
69
using Renci.SshNet.Messages.Transport;
@@ -239,6 +242,63 @@ public void Connect()
239242
StartKeepAliveTimer();
240243
}
241244

245+
#if FEATURE_TAP
246+
/// <summary>
247+
/// Asynchronously connects client to the server.
248+
/// </summary>
249+
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to observe.</param>
250+
/// <returns>A <see cref="Task"/> that represents the asynchronous connect operation.
251+
/// </returns>
252+
/// <exception cref="InvalidOperationException">The client is already connected.</exception>
253+
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
254+
/// <exception cref="SocketException">Socket connection to the SSH server or proxy server could not be established, or an error occurred while resolving the hostname.</exception>
255+
/// <exception cref="SshConnectionException">SSH session could not be established.</exception>
256+
/// <exception cref="SshAuthenticationException">Authentication of SSH session failed.</exception>
257+
/// <exception cref="ProxyException">Failed to establish proxy connection.</exception>
258+
public async Task ConnectAsync(CancellationToken cancellationToken)
259+
{
260+
CheckDisposed();
261+
cancellationToken.ThrowIfCancellationRequested();
262+
263+
// TODO (see issue #1758):
264+
// we're not stopping the keep-alive timer and disposing the session here
265+
//
266+
// we could do this but there would still be side effects as concrete
267+
// implementations may still hang on to the original session
268+
//
269+
// therefore it would be better to actually invoke the Disconnect method
270+
// (and then the Dispose on the session) but even that would have side effects
271+
// eg. it would remove all forwarded ports from SshClient
272+
//
273+
// I think we should modify our concrete clients to better deal with a
274+
// disconnect. In case of SshClient this would mean not removing the
275+
// forwarded ports on disconnect (but only on dispose ?) and link a
276+
// forwarded port with a client instead of with a session
277+
//
278+
// To be discussed with Oleg (or whoever is interested)
279+
if (IsSessionConnected())
280+
throw new InvalidOperationException("The client is already connected.");
281+
282+
OnConnecting();
283+
284+
Session = await CreateAndConnectSessionAsync(cancellationToken).ConfigureAwait(false);
285+
try
286+
{
287+
// Even though the method we invoke makes you believe otherwise, at this point only
288+
// the SSH session itself is connected.
289+
OnConnected();
290+
}
291+
catch
292+
{
293+
// Only dispose the session as Disconnect() would have side-effects (such as remove forwarded
294+
// ports in SshClient).
295+
DisposeSession();
296+
throw;
297+
}
298+
StartKeepAliveTimer();
299+
}
300+
#endif
301+
242302
/// <summary>
243303
/// Disconnects client from the server.
244304
/// </summary>
@@ -473,6 +533,26 @@ private ISession CreateAndConnectSession()
473533
}
474534
}
475535

536+
#if FEATURE_TAP
537+
private async Task<ISession> CreateAndConnectSessionAsync(CancellationToken cancellationToken)
538+
{
539+
var session = _serviceFactory.CreateSession(ConnectionInfo, _serviceFactory.CreateSocketFactory());
540+
session.HostKeyReceived += Session_HostKeyReceived;
541+
session.ErrorOccured += Session_ErrorOccured;
542+
543+
try
544+
{
545+
await session.ConnectAsync(cancellationToken).ConfigureAwait(false);
546+
return session;
547+
}
548+
catch
549+
{
550+
DisposeSession(session);
551+
throw;
552+
}
553+
}
554+
#endif
555+
476556
private void DisposeSession(ISession session)
477557
{
478558
session.ErrorOccured -= Session_ErrorOccured;

0 commit comments

Comments
 (0)