Skip to content

Commit c8e53ad

Browse files
josesimoesclaude
andcommitted
NetworkStream now rejects non-stream sockets
- Constructor throws IOException when socket type is not Stream, matching .NET. - Remove unreachable datagram branches from Read and Write. - Remove redundant socket type check from SslStream constructor. - Fix exception documentation (drop nonblocking clause, stray characters, wrong conditions). - Add NetworkStream constructor tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent bb4b9c6 commit c8e53ad

4 files changed

Lines changed: 144 additions & 41 deletions

File tree

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
//
2+
// Copyright (c) .NET Foundation and Contributors
3+
// See LICENSE file in the project root for full license information.
4+
//
5+
6+
using nanoFramework.TestFramework;
7+
using System;
8+
using System.IO;
9+
using System.Net;
10+
using System.Net.Sockets;
11+
12+
namespace NFUnitTestSocketTests
13+
{
14+
[TestClass]
15+
public class NetworkStreamTests
16+
{
17+
[Setup]
18+
public void SetupConnectToEthernetTests()
19+
{
20+
// Comment next line to run the tests on a real hardware
21+
Assert.SkipTest("Skipping tests using nanoCLR Win32 in a pipeline");
22+
}
23+
24+
[TestMethod]
25+
public void Ctor_NullSocket_ThrowsArgumentNullException()
26+
{
27+
Assert.ThrowsException(typeof(ArgumentNullException), () =>
28+
{
29+
_ = new NetworkStream(null);
30+
});
31+
32+
Assert.ThrowsException(typeof(ArgumentNullException), () =>
33+
{
34+
_ = new NetworkStream(null, true);
35+
});
36+
}
37+
38+
[TestMethod]
39+
public void Ctor_UnconnectedStreamSocket_ThrowsIOException()
40+
{
41+
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
42+
43+
try
44+
{
45+
Assert.ThrowsException(typeof(IOException), () =>
46+
{
47+
_ = new NetworkStream(socket);
48+
});
49+
50+
Assert.ThrowsException(typeof(IOException), () =>
51+
{
52+
_ = new NetworkStream(socket, false);
53+
});
54+
}
55+
finally
56+
{
57+
socket.Close();
58+
}
59+
}
60+
61+
[TestMethod]
62+
public void Ctor_ConnectedDgramSocket_ThrowsIOException()
63+
{
64+
SocketPair testSockets = new SocketPair(ProtocolType.Udp, SocketType.Dgram);
65+
66+
try
67+
{
68+
testSockets.Startup(0, 0);
69+
70+
// a connected datagram socket has a remote endpoint, so this exercises the socket type check
71+
testSockets.socketClient.Connect(testSockets.epServer);
72+
73+
Assert.ThrowsException(typeof(IOException), () =>
74+
{
75+
_ = new NetworkStream(testSockets.socketClient);
76+
});
77+
78+
Assert.ThrowsException(typeof(IOException), () =>
79+
{
80+
_ = new NetworkStream(testSockets.socketClient, false);
81+
});
82+
}
83+
finally
84+
{
85+
testSockets.TearDown();
86+
}
87+
}
88+
89+
[TestMethod]
90+
public void Ctor_ConnectedStreamSocket_Succeeds()
91+
{
92+
SocketPair testSockets = new SocketPair(ProtocolType.Tcp, SocketType.Stream);
93+
Socket acceptedSocket = null;
94+
95+
try
96+
{
97+
testSockets.Startup(0, 0);
98+
testSockets.socketServer.Listen(1);
99+
testSockets.socketClient.Connect(testSockets.epServer);
100+
acceptedSocket = testSockets.socketServer.Accept();
101+
102+
NetworkStream clientStream = new NetworkStream(testSockets.socketClient);
103+
NetworkStream serverStream = new NetworkStream(acceptedSocket, true);
104+
105+
clientStream.Write(testSockets.bufSend, 0, testSockets.bufSend.Length);
106+
107+
int bytesRead = serverStream.Read(testSockets.bufReceive, 0, testSockets.bufReceive.Length);
108+
109+
testSockets.AssertDataReceived(bytesRead);
110+
111+
clientStream.Dispose();
112+
113+
// server stream owns the accepted socket, so disposing it closes the socket
114+
serverStream.Dispose();
115+
acceptedSocket = null;
116+
}
117+
finally
118+
{
119+
acceptedSocket?.Close();
120+
testSockets.TearDown();
121+
}
122+
}
123+
}
124+
}

‎Tests/SocketTests/SocketTests.nfproj‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
<Import Project="$(NanoFrameworkProjectSystemPath)NFProjectSystem.props" Condition="Exists('$(NanoFrameworkProjectSystemPath)NFProjectSystem.props')" />
2828
<ItemGroup>
2929
<Compile Include="CompanionClient.cs" />
30+
<Compile Include="NetworkStreamTests.cs" />
3031
<Compile Include="SocketOptionsTests.cs" />
3132
<Compile Include="SocketPair.cs" />
3233
<Compile Include="SocketExceptionsTests.cs" />

‎nanoFramework.System.Net/Security/SslStream.cs‎

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,6 @@ public class SslStream : NetworkStream
5454
public SslStream(Socket socket)
5555
: base(socket, false)
5656
{
57-
if (SocketType.Stream != (SocketType)_socketType)
58-
{
59-
throw new NotSupportedException();
60-
}
61-
6257
_sslContext = -1;
6358
_isServer = false;
6459

‎nanoFramework.System.Net/Sockets/NetworkStream.cs‎

Lines changed: 19 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public class NetworkStream : Stream
2222
protected int _socketType;
2323

2424
/// <summary>
25-
/// Internal endpoint ref used for dgram sockets
25+
/// Internal endpoint ref of the remote peer
2626
/// </summary>
2727
protected EndPoint _remoteEndPoint;
2828

@@ -39,7 +39,7 @@ public class NetworkStream : Stream
3939
/// </summary>
4040
/// <param name="socket">The <see cref="Socket"/> that the <see cref="NetworkStream"/> will use to send and receive data.</param>
4141
/// <exception cref="ArgumentNullException"><paramref name="socket"/> is <see langword="null"/>.</exception>
42-
/// <exception cref="IOException"><paramref name="socket"/> is not connected. -or- The <see cref="Socket.SocketType"/> property of <paramref name="socket"/> is not <see cref="SocketType.Stream"/>. -or- <paramref name="socket"/> is in a nonblocking state.</exception>
42+
/// <exception cref="IOException"><paramref name="socket"/> is not connected. -or- The <see cref="Socket.SocketType"/> property of <paramref name="socket"/> is not <see cref="SocketType.Stream"/>.</exception>
4343
public NetworkStream(Socket socket)
4444
: this(socket, false)
4545
{
@@ -54,7 +54,7 @@ public NetworkStream(Socket socket)
5454
/// <param name="ownsSocket"><see langword="true"/> to indicate that the <see cref="NetworkStream"/> will take ownership of the <see cref="Socket"/>;
5555
/// otherwise, <see langword="false"/>.</param>
5656
/// <exception cref="ArgumentNullException"><paramref name="socket"/> is <see langword="null"/>.</exception>
57-
/// <exception cref="IOException"><paramref name="socket"/> is not connected. -or- The <see cref="Socket.SocketType"/> property of <paramref name="socket"/> is not <see cref="SocketType.Stream"/>. -or- <paramref name="socket"/> is in a nonblocking state.</exception>
57+
/// <exception cref="IOException"><paramref name="socket"/> is not connected. -or- The <see cref="Socket.SocketType"/> property of <paramref name="socket"/> is not <see cref="SocketType.Stream"/>.</exception>
5858
public NetworkStream(Socket socket, bool ownsSocket)
5959
{
6060
if (socket == null) throw new ArgumentNullException();
@@ -70,7 +70,15 @@ public NetworkStream(Socket socket, bool ownsSocket)
7070

7171
throw new IOException(errCode.ToString(), e);
7272
}
73-
73+
74+
// NetworkStream is only valid for stream-oriented sockets (same as .NET).
75+
// Datagram sockets don't have stream semantics: e.g. a zero-length datagram
76+
// would make Read return 0 without the peer having shut down the connection.
77+
if (socket.SocketType != SocketType.Stream)
78+
{
79+
throw new IOException();
80+
}
81+
7482
// Set the internal socket
7583
_socket = socket;
7684

@@ -245,14 +253,14 @@ public override void Flush()
245253
/// <param name="offset">The location in <paramref name="buffer"/> to begin storing the data to.</param>
246254
/// <param name="count">The number of bytes to read from the <see cref="NetworkStream"/>.</param>
247255
/// <returns>The total number of bytes read into the buffer between zero (0) and the requested count. The method returns zero (0) only if zero bytes were requested or if no more bytes are available because the peer socket performed a graceful shutdown.</returns>
248-
/// <exception cref="IOException">The underlying <see cref="Socket"/> is closed.</exception>
256+
/// <exception cref="IOException">The underlying <see cref="Socket"/> is closed. -or- There was a failure while reading from the network.</exception>
249257
/// <exception cref="ArgumentNullException"><paramref name="buffer"/> is <see langword="null"/>.</exception>
250-
/// <exception cref="ObjectDisposedException">The <see cref="NetworkStream"/> is closed or there is a failure reading from the network.</exception>"
258+
/// <exception cref="ObjectDisposedException">The <see cref="NetworkStream"/> is closed.</exception>
251259
/// <exception cref="ArgumentOutOfRangeException"><paramref name="offset"/> is less than 0 or greater than the length of <paramref name="buffer"/>. -or- <paramref name="count"/> is less than 0 or greater than the length of <paramref name="buffer"/> minus the value of the <paramref name="offset"/> parameter.</exception>
252260
/// <remarks>
253261
/// <para>This method reads data into the <paramref name="buffer"/> parameter and returns the number of bytes successfully read. The <see cref="Read"/> operation reads as much data as is available, up to the number of bytes specified by the <paramref name="count"/> parameter. If the remote host shuts down the connection, and all available data has been received, the Read method completes immediately and return zero bytes.</para>
254262
/// <note type="important">
255-
/// Check to see if the <see cref="NetworkStream"/> is readable by calling the <see cref="CanRead"/> property. If you attempt to read from a <see cref="NetworkStream"/> that is not readable, you will get an <see cref="IOException"/> .
263+
/// Check to see if the <see cref="NetworkStream"/> is readable by calling the <see cref="CanRead"/> property. If you attempt to read from a <see cref="NetworkStream"/> that is not readable, you will get an <see cref="IOException"/>.
256264
/// </note>
257265
/// </remarks>
258266
public override int Read(byte[] buffer, int offset, int count)
@@ -273,18 +281,7 @@ public override int Read(byte[] buffer, int offset, int count)
273281
count = available;
274282
}
275283

276-
if (_socketType == (int)SocketType.Stream)
277-
{
278-
return _socket.Receive(buffer, offset, count, SocketFlags.None);
279-
}
280-
else if (_socketType == (int)SocketType.Dgram)
281-
{
282-
return _socket.ReceiveFrom(buffer, offset, count, SocketFlags.None, ref _remoteEndPoint);
283-
}
284-
else
285-
{
286-
throw new NotSupportedException();
287-
}
284+
return _socket.Receive(buffer, offset, count, SocketFlags.None);
288285
}
289286

290287
/// <summary>
@@ -315,10 +312,9 @@ public override void SetLength(long value)
315312
/// <param name="offset">The location in <paramref name="buffer"/> from which to start writing data.</param>
316313
/// <param name="count">The number of bytes to write to the <see cref="NetworkStream"/>.</param>
317314
/// <exception cref="ArgumentOutOfRangeException"><paramref name="offset"/> is less than 0 or greater than the length of <paramref name="buffer"/>. -or- <paramref name="count"/> is less than 0 or greater than the length of <paramref name="buffer"/> minus the value of the <paramref name="offset"/> parameter.</exception>
318-
/// <exception cref="ObjectDisposedException">The <see cref="NetworkStream"/> is closed or there is a failure reading from the network.</exception>
319-
/// <exception cref="IOException">There was a failure while writing to the network. -or-An error occurred when accessing the socket. See the Remarks section for more information.</exception>
315+
/// <exception cref="ObjectDisposedException">The <see cref="NetworkStream"/> is closed.</exception>
316+
/// <exception cref="IOException">There was a failure while writing to the network. -or- An error occurred when accessing the socket. See the Remarks section for more information.</exception>
320317
/// <exception cref="ArgumentNullException"><paramref name="buffer"/> is <see langword="null"/>.</exception>
321-
/// <exception cref="NotSupportedException">The underlying <see cref="Socket"/> is not of type <see cref="SocketType.Stream"/> or <see cref="SocketType.Dgram"/>.</exception>
322318
/// <remarks>
323319
/// The Write method starts at the specified offset and sends count bytes from the contents of buffer to the network.
324320
/// The Write method blocks until the requested number of bytes is sent or a <see cref="SocketException"/> is thrown.
@@ -333,20 +329,7 @@ public override void Write(byte[] buffer, int offset, int count)
333329
if (offset < 0 || offset > buffer.Length) throw new ArgumentOutOfRangeException();
334330
if (count < 0 || count > buffer.Length - offset) throw new ArgumentOutOfRangeException();
335331

336-
int bytesSent = 0;
337-
338-
if (_socketType == (int)SocketType.Stream)
339-
{
340-
bytesSent = _socket.Send(buffer, offset, count, SocketFlags.None);
341-
}
342-
else if (_socketType == (int)SocketType.Dgram)
343-
{
344-
bytesSent = _socket.SendTo(buffer, offset, count, SocketFlags.None, _socket.RemoteEndPoint);
345-
}
346-
else
347-
{
348-
throw new NotSupportedException();
349-
}
332+
int bytesSent = _socket.Send(buffer, offset, count, SocketFlags.None);
350333

351334
if (bytesSent != count) throw new IOException();
352335
}

0 commit comments

Comments
 (0)