diff --git a/.github/workflows/update-dependencies.yml b/.github/workflows/update-dependencies.yml new file mode 100644 index 0000000..3d6c0d7 --- /dev/null +++ b/.github/workflows/update-dependencies.yml @@ -0,0 +1,32 @@ +# Copyright (c) .NET Foundation and Contributors +# See LICENSE file in the project root for full license information. + +# This workflow will periodically check .NET nanoFramework dependencies and updates them in the repository it's running. + +name: Daily update dependencies + +on: + schedule: + # At 00:00 UTC every day. + - cron: '00 00 * * *' + repository_dispatch: + types: update-dependencies + +defaults: + run: + shell: pwsh + +jobs: + update-dotnet-preview: + name: Update .NET nanoFramework dependencies + timeout-minutes: 15 + runs-on: windows-latest + env: + GITHUB_TOKEN: ${{ github.token }} + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Update dependencies + uses: nanoframework/nanodu@v1 + with: + solutionsToCheck: 'nanoframework.System.Net.Sockets.TcpClient.sln' diff --git a/.github_changelog_generator b/.github_changelog_generator new file mode 100644 index 0000000..0813106 --- /dev/null +++ b/.github_changelog_generator @@ -0,0 +1,13 @@ +user=nanoframework +project=System.Net.Sockets.TcpClient +issues=false +add_issues_wo_labels=false +add_pr_wo_labels=false +add_issues_wo_labels=false +filter_issues_by_milestone=false +exclude_labels=Area: Config-and-Build,Area: Infrastructure-and-Organization,reverted +enhancement_labels=Type: enhancement +bug_labels=Type: bug +merge_prefix=**Documentation and other chores:** +unreleased_label=**Changes available only in 'Preview' NuGet packages:** +author=false diff --git a/README.md b/README.md index f6c70c0..723dc55 100644 --- a/README.md +++ b/README.md @@ -1 +1,203 @@ -# System.Net.Sockets.TcpClient \ No newline at end of file + +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=nanoframework_System.Net.Sockets.TcpClient&metric=alert_status)](https://sonarcloud.io/dashboard?id=nanoframework_System.Net.Sockets.TcpClient) [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=nanoframework_System.Net.Sockets.TcpClient&metric=reliability_rating)](https://sonarcloud.io/dashboard?id=nanoframework_System.Net.Sockets.TcpClient) [![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![NuGet](https://img.shields.io/nuget/dt/nanoFramework.System.Net.Sockets.TcpClient.svg?label=NuGet&style=flat&logo=nuget)](https://www.nuget.org/packages/nanoFramework.System.Net.Sockets.TcpClient/) [![#yourfirstpr](https://img.shields.io/badge/first--timers--only-friendly-blue.svg)](https://github.com/nanoframework/Home/blob/main/CONTRIBUTING.md) [![Discord](https://img.shields.io/discord/478725473862549535.svg?logo=discord&logoColor=white&label=Discord&color=7289DA)](https://discord.gg/gCyBu8T) + +![nanoFramework logo](https://raw.githubusercontent.com/nanoframework/Home/main/resources/logo/nanoFramework-repo-logo.png) + +----- + +# System.Net.Sockets.TcpClient + +This API implements the TcpListener and TcpClient classes with a pattern similar to the official .NET equivalent. [System.NET.Sockets.TcpClient](https://docs.microsoft.com/en-us/dotnet/api/system.net.sockets.TcpClient). + +These are wrapper classes for the Socket when using TCP connections. +The nanoframework implementation of TcpClient doesn't include the asynchronous methods and the Connected property. + + +## Build status + +| Component | Build Status | NuGet Package | +|:-|---|---| +| nanoFramework.System.Net.Sockets.TcpClient | [![Build Status](https://dev.azure.com/nanoframework/System.Net.Sockets.TcpClient/_apis/build/status/System.Net.Sockets.TcpClient?branchName=main)](https://dev.azure.com/nanoframework/System.Net.Sockets.TcpClient/_build/latest?definitionId=91&branchName=main) | [![NuGet](https://img.shields.io/nuget/v/nanoFramework.System.Net.Sockets.TcpClient.svg?label=NuGet&style=flat&logo=nuget)](https://www.nuget.org/packages/nanoFramework.System.Net.Sockets.TcpClient/) | +| nanoFramework.System.Net.Sockets.TcpClient (preview) | [![Build Status](https://dev.azure.com/nanoframework/System.Net.Sockets.TcpClient/_apis/build/status/System.Net.Sockets.TcpClient?branchName=develop)](https://dev.azure.com/nanoframework/System.Net.Sockets.TcpClient/_build/latest?definitionId=91&branchName=develop) | [![NuGet](https://img.shields.io/nuget/vpre/nanoFramework.System.Net.Sockets.TcpClient.svg?label=NuGet&style=flat&logo=nuget)](https://www.nuget.org/packages/nanoFramework.System.Net.Sockets.TcpClient/) | + +## Usage + +**Important:** Obviously this requires a working network connection. Please check the examples with the Network Helpers on how to connect to a network. For example see the [Networking sample pack](https://github.com/nanoframework/Samples/tree/main/samples/Networking) + +The `TcpListener` class provides simple methods for creating a listening socket to accept incoming TCP connections and the `TcpClient` provides methods for connecting and communicating on a TCP connection. + +### Samples + +Samples for `TcpListener` and `TcpClient` are present in the [nanoFramework Sample repository](https://github.com/nanoframework/Samples). + +### Listening for incoming connections + +The following codes shows how to set up a Listening socket and to accept connections as a TcpClient on the 1234 port. + +```csharp +TcpListener listener = new TcpListener(IPAddress.Any, 1234); + +// Start listening for incoming connections +listener.Start(); +while (true) +{ + try + { + // Wait for incoming connections + TcpClient client = listener.AcceptTcpClient(); + + NetworkStream stream = client.GetStream(); + + Byte[] bytes = new Byte[256]; + int i; + + // Wait for incoming data and echo back + while((i = stream.Read(bytes, 0, bytes.Length))!=0) + { + // Do something with data ? + + stream.Write(bytes, 0, i); + } + + // Shutdown connection + client.Close(); + } + catch(Exception ex) + { + Debug.WriteLine($"Exception:-{ex.Message}"); + } +} +``` + +If you want to handle more then one simultaneous connection then a separate worker thread can be started. + +```csharp +TcpListener listener = new TcpListener(IPAddress.Any, 1234); + +// Start listening for incoming connections with backlog +listener.Start(2); + +while (true) +{ + try + { + // Wait for incoming connections + TcpClient client = listener.AcceptTcpClient(); + + // Start thread to handle connection + Thread worker = new Thread(() => WorkerThread(client)); + worker.Start(); + } + catch(Exception ex) + { + Debug.WriteLine($"Exception:-{ex.Message}"); + } +} +``` + +Worker Thread for handling the TcpClient connection for TcpListener example. + +```csharp +private static void WorkerThread(TcpClient client) +{ + try + { + NetworkStream stream = client.GetStream(); + + Byte[] bytes = new Byte[256]; + int i; + + // Loop reading data until connection closed + while((i = stream.Read(bytes, 0, bytes.Length))!=0) + { + // Do something with data ? + + // Write back received data bytes to stream + stream.Write(bytes, 0, i); + } + } + catch(Exception ex) + { + Debug.WriteLine($"Exception:-{ex.Message}"); + } + finally + { + // Shutdown connection + client.Close(); + } +} +``` + +### TcpClient + +The TcpClient can also be used to initiate a connection passing in the hostname/port or IPEndPoint. +Maybe connecting to another nanoFramework device which is listening for connections. + +```csharp +TcpClient client = new TcpClient() + +try +{ + client.Connect(hostname, port) + + NetworkStream stream = client.GetStream(); + + // Write / Read data on stream + + // for example Write 'ABC' and wait for response + byte[] writeData = new byte[] { 0x41, 0x42, 0x43 }; + stream.Write(writeData, 0, writeData.Length); + + // Read reply and close + byte[] buffer = new byte[1024]; + int bytesRead = stream.Read(buffer, 0, buffer.Length); + + // Process read data ? +} +catch(SocketException sx) +{ + Console.WriteLine($"Socket error:{sx.ErrorCode} exception:{sx.Message}"); +} +finally +{ + client.Close(); +} +``` + +For secure connections a `SslStream` can be used. + +```csharp +client.Connect(HostName, 443); + +// Create SSlStream from underlying SOcket +SslStream stream = new SslStream(client.Client); + +// Don't verify Server certificate for this sample code +stream.SslVerification = SslVerification.NoVerification; +stream.AuthenticateAsClient(HostName, SslProtocols.Tls12); + +// stream.Write() or stream.Read() +``` + +## Feedback and documentation + +For documentation, providing feedback, issues and finding out how to contribute please refer to the [Home repo](https://github.com/nanoframework/Home). + +Join our Discord community [here](https://discord.gg/gCyBu8T). + +## Credits + +The list of contributors to this project can be found at [CONTRIBUTORS](https://github.com/nanoframework/Home/blob/main/CONTRIBUTORS.md). + +## License + +The **nanoFramework** Class Libraries are licensed under the [MIT license](LICENSE.md). + +## Code of Conduct + +This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behaviour in our community. +For more information see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct). + +### .NET Foundation + +This project is supported by the [.NET Foundation](https://dotnetfoundation.org). \ No newline at end of file diff --git a/Tests/ConnectionTests.cs b/Tests/ConnectionTests.cs new file mode 100644 index 0000000..6e615da --- /dev/null +++ b/Tests/ConnectionTests.cs @@ -0,0 +1,257 @@ +// +// Copyright (c) .NET Foundation and Contributors +// Portions Copyright (c) Microsoft Corporation. All rights reserved. +// See LICENSE file in the project root for full license information. +// +// + +using nanoFramework.TestFramework; +using System; +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; +using System.Threading; + +namespace NFUnitTests +{ + [TestClass] + public class ConnectionTests + { + const int TESTPORT = 1234; + static byte[] _testData = new byte[256]; + + [Setup] + public void Initialize() + { + // Remove this line to run hardware tests + Assert.SkipTest("No Network"); + + Debug.WriteLine("TcpListener.TcpClient connection Tests initialized."); + + // Init test data + for (int i = 0; i < _testData.Length; i++) + { + _testData[i] = (byte)i; + } + } + + [TestMethod] + public void ListenAndConnect() + { + const int MaxConnections = 4; + + bool testRunning = true; + int workerID = 1; + int connectionAccepted = 0; + + Thread[] workers = new Thread[MaxConnections]; + + // Create listener on Loop back address + TcpListener listener = CreateListener(); + + listener.Start(4); + + // Start some Threads to connect to Listener and Send/Receive data + for (int sndCount = 0; sndCount < MaxConnections; sndCount++) + { + Thread Sender1 = new Thread(() => SenderThread(100 + workerID++)); + Sender1.Start(); + } + + while (testRunning) + { + // All connections accepted, break accept loop + if (connectionAccepted >= MaxConnections) + { + break; + } + + try + { + TcpClient client = listener.AcceptTcpClient(); + + workers[connectionAccepted] = new Thread(() => WorkerThread(client, workerID++)); + workers[connectionAccepted].Start(); + + connectionAccepted++; + } + catch (Exception ex) + { + Debug.WriteLine($"Exception:-{ex.Message}"); + Thread.Sleep(500); + } + } + + listener.Stop(); + Debug.WriteLine($"listener stopped"); + + Debug.WriteLine($"Waiting for Workers to end"); + + bool WorkersRunning = true; + while (WorkersRunning) + { + // Wait for all Worker threads to close + WorkersRunning = false; + foreach (Thread thd in workers) + { + if (thd.IsAlive) + { + WorkersRunning = true; + break; + } + } + + Thread.Sleep(10); + } + + Debug.WriteLine($"All workers ended"); + } + + public static void SenderThread(int workerID) + { + // IPV4 address + IPEndPoint endPoint = new IPEndPoint(IPAddress.Loopback, TESTPORT); + + TcpClient sender = new TcpClient(); + + sender.Connect(endPoint); + + NetworkStream stream = sender.GetStream(); + + for (int count = 1; count < 20; count++) + { + int length = count * 2; + + // Write header + stream.WriteByte(0xfc); + stream.WriteByte((byte)(length & 0xff)); // Low length + stream.WriteByte((byte)((length >> 8) & 0xff)); // High length + + byte[] buffer = new byte[length]; + + // fill buffer with test data + Array.Copy(_testData, count, buffer, 0, length); + + stream.Write(buffer, 0, length); + + Array.Clear(buffer, 0, length); + + int sync = stream.ReadByte(); + Assert.True(sync == 0xfc, $"{workerID} count={count} read sync != 0xfc => {sync}"); + int lenL = stream.ReadByte(); + int lenH = stream.ReadByte(); + int dataLength = (lenH << 8) + lenL; + Assert.True(dataLength == length, $"{workerID} Sender rx invalid length {dataLength} should be {length}"); + + int readBytes = stream.Read(buffer, 0, length); + Assert.True(readBytes == length, $"{workerID} Read bytes:{readBytes} <> requested length:{length}"); + + // Validate buffer + for (int i = 0; i < length; i++) + { + Assert.False(buffer[i] != _testData[i + count], $"Received data not same as send, position:{i} {buffer[i]}!={_testData[i + count]}"); + } + + Debug.WriteLine($"{workerID}: Send/Receive count:{count} complete"); + } + + stream.Close(); + sender.Dispose(); + } + + /// + /// Thread to echo back data received + /// Data in format 0xFC, length:uint16, data bytes ....... + /// + /// TcpClient + /// ID for logging + public static void WorkerThread(TcpClient client, int workerID) + { + Debug.WriteLine($" {workerID}:Client connected to {client.Client.RemoteEndPoint.ToString()}"); + + NetworkStream stream = client.GetStream(); + + // Set RX time outs on stream + stream.ReadTimeout = 10000; + + // Netstream.Read will return straight away if there is no data to read + while (true) + { + try + { + // Wait for first byte + // This will sit on Read until 1 byte of data available giving time to other threads + int syncbyte = stream.ReadByte(); + if (syncbyte == -1) + { + Debug.WriteLine($"{workerID}:Connection closed"); + break; + } + + Assert.True(syncbyte == 0xfc, $"{workerID}:Sync byte != FC => {syncbyte}"); + + int lenL = stream.ReadByte(); + int lenH = stream.ReadByte(); + int dataLength = (lenH << 8) + lenL; + Assert.True(dataLength <= 512, $"{workerID}:Invalid length {dataLength}"); + + byte[] buffer = new byte[dataLength]; + + // Then read the rest of data in loop + int bufferPos = 0; + int bytesToRead = 0; + + while (bytesToRead < dataLength) + { + int dataAv = client.Available; + if (dataAv > 0) + { + int bytesRead = stream.Read(buffer, bufferPos, dataAv); + bufferPos += bytesRead; + bytesToRead += bytesRead; + } + else + { + Thread.Sleep(0); + } + } + + Assert.True(bytesToRead == dataLength, $"{workerID}:Data read != availableInvalid length {dataLength}"); + + stream.WriteByte(0xfc); + stream.WriteByte((byte)(dataLength & 0xff)); // Low length + stream.WriteByte((byte)((dataLength >> 8) & 0xff)); // High length + + // Echo data back + stream.Write(buffer, 0, dataLength); + } + catch (Exception ex) + { + Debug.WriteLine($"{workerID}:Worker exception {ex.Message}"); + break; + } + } // while + + client.Close(); + Debug.WriteLine($"{workerID}:Worker closed"); + } + + /// + /// Create a TcpListener and check + /// + /// TcpListener + public TcpListener CreateListener() + { + TcpListener listener = new TcpListener(IPAddress.Loopback, TESTPORT); + Assert.NotNull(listener); + + Debug.WriteLine("TcpListener created."); + + Assert.Equal(((IPEndPoint)listener.LocalEndpoint).Port, TESTPORT, "Wrong port on Listener"); + Assert.Equal((int)((IPEndPoint)listener.LocalEndpoint).AddressFamily, (int)AddressFamily.InterNetwork, "Wrong address family"); + Assert.True(((IPEndPoint)listener.LocalEndpoint).Address == IPAddress.Loopback, "Wrong IP address"); + + return listener; + } + } +} diff --git a/Tests/Properties/AssemblyInfo.cs b/Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..a3735af --- /dev/null +++ b/Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,31 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyCopyright("Copyright (c) 2021 nanoFramework contributors")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Tests/Tests.nfproj b/Tests/Tests.nfproj new file mode 100644 index 0000000..9754daf --- /dev/null +++ b/Tests/Tests.nfproj @@ -0,0 +1,97 @@ + + + + $(MSBuildExtensionsPath)\nanoFramework\v1.0\ + + + + + + + Debug + AnyCPU + {11A8DD76-328B-46DF-9F39-F559912D0360};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + a0df7a0b-8dcd-4cfa-a29f-49f0c2400a07 + Library + Properties + 512 + NFUnitTests + NFUnitTest + False + true + UnitTest + v1.0 + + + + $(MSBuildProjectDirectory)\nano.runsettings + + + + + + + + + ..\packages\nanoFramework.CoreLibrary.1.12.0-preview.9\lib\mscorlib.dll + True + True + + + ..\packages\nanoFramework.Runtime.Events.1.10.0-preview.8\lib\nanoFramework.Runtime.Events.dll + True + True + + + ..\packages\nanoFramework.System.Text.1.1.3-preview.15\lib\nanoFramework.System.Text.dll + True + True + + + ..\packages\nanoFramework.TestFramework.1.0.178\lib\nanoFramework.TestFramework.dll + True + True + + + ..\packages\nanoFramework.TestFramework.1.0.178\lib\nanoFramework.UnitTestLauncher.exe + True + True + + + ..\packages\nanoFramework.System.IO.Streams.1.0.0-preview.12\lib\System.IO.Streams.dll + True + True + + + ..\packages\nanoFramework.System.Net.1.8.0-preview.29\lib\System.Net.dll + True + True + + + ..\packages\nanoFramework.System.Threading.1.0.4-preview.16\lib\System.Threading.dll + True + True + + + + + + + + + + + + + + + + + + + + Update the Import path in nfproj to the correct nanoFramework.TestFramework NuGet package folder. + + + + \ No newline at end of file diff --git a/Tests/UnitTestListener.cs b/Tests/UnitTestListener.cs new file mode 100644 index 0000000..b73f94b --- /dev/null +++ b/Tests/UnitTestListener.cs @@ -0,0 +1,75 @@ +// +// Copyright (c) .NET Foundation and Contributors +// Portions Copyright (c) Microsoft Corporation. All rights reserved. +// See LICENSE file in the project root for full license information. +// + +using nanoFramework.TestFramework; +using System; +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; + +namespace NFUnitTests +{ + [TestClass] + public class TestTcpListener + { + private const int TESTPORT = 1234; + + [Setup] + public void Initialize() + { + // Remove this line to run hardware tests + Assert.SkipTest("No Network"); + + Debug.WriteLine("TcpListener Tests initialized."); + } + + [TestMethod] + public void CreateAndStart() + { + Debug.WriteLine("CreateAndStart."); + + TcpListener listener = CreateListener(); + + listener.Start(1); + + // Get underlying socket + Socket listenSocket = listener.Server; + Assert.NotNull(listenSocket, "Listen socket null"); + + listener.Stop(); + + listener = null; + } + + [TestMethod] + public void StateExceptionChecks() + { + TcpListener listener = CreateListener(); + + Assert.Throws(typeof(InvalidOperationException), () => { listener.Stop(); }, "No exception when stopping and not started"); + + listener.Start(1); + + Assert.Throws(typeof(InvalidOperationException), () => { listener.Start(1); }, "No exception when starting and already started"); + + listener.Stop(); + } + + public TcpListener CreateListener() + { + TcpListener listener = new TcpListener(IPAddress.Loopback, TESTPORT); + Assert.NotNull(listener); + + Debug.WriteLine("TcpListener created."); + + Assert.Equal(((IPEndPoint)listener.LocalEndpoint).Port, TESTPORT, "Wrong port on Listener"); + Assert.Equal((int)((IPEndPoint)listener.LocalEndpoint).AddressFamily, (int)AddressFamily.InterNetwork, "Wrong address family"); + Assert.True(((IPEndPoint)listener.LocalEndpoint).Address == IPAddress.Loopback, "Wrong IP address"); + + return listener; + } + } +} diff --git a/Tests/nano.runsettings b/Tests/nano.runsettings new file mode 100644 index 0000000..fa881e3 --- /dev/null +++ b/Tests/nano.runsettings @@ -0,0 +1,14 @@ + + + + + 1 + .\TestResults + 120000 + Framework40 + + + None + False + + \ No newline at end of file diff --git a/Tests/packages.config b/Tests/packages.config new file mode 100644 index 0000000..ed5cafb --- /dev/null +++ b/Tests/packages.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/azure-pipelines.yml b/azure-pipelines.yml index fbb463f..744da23 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,10 +1,27 @@ +# Copyright (c) .NET Foundation and Contributors +# See LICENSE file in the project root for full license information. + trigger: branches: - include: [main, develop, "release-*" ] + include: + - main + - develop + - release-* paths: - exclude: [README.md, LICENSE.md, NuGet.Config, .github_changelog_generator, .gitignore] + exclude: + - .github_changelog_generator + - .gitignore + - CHANGELOG.md + - CODE_OF_CONDUCT.md + - LICENSE.md + - README.md + - NuGet.Config + - assets/* + - config/* + - .github/* tags: - include: ["v*"] + include: + - v* # PR always trigger build pr: @@ -17,80 +34,29 @@ resources: type: github name: nanoframework/nf-tools endpoint: nanoframework - -jobs: -############################## -- job: Build_Library - condition: or( eq(variables['UPDATE_DEPENDENTS'], 'false'), eq(variables['StartReleaseCandidate'], 'true') ) - - pool: - vmImage: 'windows-2019' +pool: + vmImage: 'windows-latest' - variables: - DOTNET_NOLOGO: true - solution: 'nanoFramework.System.Net.Sockets.TcpClient.sln' - buildPlatform: 'Any CPU' - buildConfiguration: 'Release' - nugetPackageName: 'nanoFramework.System.Net.Sockets.TcpClient' +variables: + DOTNET_NOLOGO: true + solution: 'nanoFramework.System.Net.Sockets.TcpClient.sln' + buildPlatform: 'Any CPU' + buildConfiguration: 'Release' + nugetPackageName: 'nanoFramework.System.Net.Sockets.TcpClient' - steps: +steps: - # step from template @ nf-tools repo - # all build, update and publish steps - - template: azure-pipelines-templates/class-lib-build.yml@templates - parameters: - sonarCloudProject: 'nanoframework_lib-nanoFramework.System.Net.Sockets.TcpClient' - runUnitTests: false - unitTestRunsettings: '$(System.DefaultWorkingDirectory)\Tests\SocketTests\nano.runsettings' +# step from template @ nf-tools repo +# build steps only +- template: azure-pipelines-templates/class-lib-build.yml@templates + parameters: + sonarCloudProject: 'nanoframework_System.Net.Sockets.TcpClient' -############################## -- job: Update_Dependents - condition: or( and( succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), eq(variables['StartReleaseCandidate'], 'false') ), and( succeeded(), contains(variables['getCommitMessage.COMMIT_MESSAGE'], '***UPDATE_DEPENDENTS***'), eq(variables['StartReleaseCandidate'], 'false') ), eq(variables['UPDATE_DEPENDENTS'], 'true') ) - - dependsOn: - - Build_Library - - pool: - vmImage: 'windows-2019' - - variables: - DOTNET_NOLOGO: true - - steps: - - - checkout: none - - # update dependents - - template: azure-pipelines-templates/update-dependents.yml@templates - parameters: - repositoriesToUpdate: | - System.Net.Http - System.Net.WebSockets - System.Device.WiFi - nanoFramework.m2mqtt - nanoFramework.Azure.Devices - -################################## -# report build failure to Discord -- job: Report_Build_Failure - condition: or( failed('Build_Library'), failed('Update_Dependents')) - - dependsOn: - - Build_Library - - Update_Dependents - - pool: - vmImage: 'windows-2019' - - steps: - - - checkout: self - - # step from template @ nf-tools repo - # report error - - template: azure-pipelines-templates/discord-webhook-task.yml@templates - parameters: - status: 'failure' - webhookUrl: '$(DiscordWebhook)' - message: '' +# step from template @ nf-tools repo +# report error +- template: azure-pipelines-templates/discord-webhook-task.yml@templates + parameters: + status: 'failure' + webhookUrl: '$(DiscordWebhook)' + message: '' \ No newline at end of file diff --git a/config/SignClient.json b/config/SignClient.json new file mode 100644 index 0000000..482177d --- /dev/null +++ b/config/SignClient.json @@ -0,0 +1,14 @@ +{ + "SignClient": { + "AzureAd": { + "AADInstance": "https://login.microsoftonline.com/", + "ClientId": "c248d68a-ba6f-4aa9-8a68-71fe872063f8", + "TenantId": "16076fdc-fcc1-4a15-b1ca-32c9a255900e" + }, + "Service": { + "Url": "https://codesign.dotnetfoundation.org/", + "ResourceId": "https://SignService/3c30251f-36f3-490b-a955-520addb85001" + } + } + } + \ No newline at end of file diff --git a/config/filelist.txt b/config/filelist.txt new file mode 100644 index 0000000..ca7fde1 --- /dev/null +++ b/config/filelist.txt @@ -0,0 +1 @@ +**/nanoFramework.System.Net.Sockets.TcpClient.* \ No newline at end of file diff --git a/nanoframework.System.Net.Sockets.TcpClient.nuspec b/nanoframework.System.Net.Sockets.TcpClient.nuspec index bf706cd..7d78d23 100644 --- a/nanoframework.System.Net.Sockets.TcpClient.nuspec +++ b/nanoframework.System.Net.Sockets.TcpClient.nuspec @@ -1,40 +1,44 @@ - - nanoframework.System.Net.Sockets.TcpClient - $version$ - nanoframework.System.Net.Sockets.TcpClient - nanoFramework project contributors - nanoFramework,dotnetfoundation - false - LICENSE.md - - - docs\README.md - false - https://github.com/nanoframework/System.Net.Sockets.TcpClient - images\nf-logo.png - - Copyright (c) .NET Foundation and Contributors - This package includes the .NET nanoFramework System.Net assembly for .NET nanoFramework C# projects. -This package requires a target with System.Net v$nativeVersion$ (checksum $checksum$). - nanoFramework C# csharp netmf netnf nanoFramework.System.Net - - - - - - - - - - - - - - - - - - + + nanoframework.System.Net.Sockets.TcpClient + $version$ + nanoframework.System.Net.Sockets.TcpClient + nanoFramework project contributors + nanoFramework,dotnetfoundation + false + LICENSE.md + + + docs\README.md + false + https://github.com/nanoframework/System.Net.Sockets.TcpClient + images\nf-logo.png + + Copyright (c) .NET Foundation and Contributors + + This package includes the .NET nanoFramework System.Net assembly for .NET nanoFramework C# projects. + This package requires a target with System.Net v$nativeVersion$ (checksum $checksum$). + + nanoFramework C# csharp netmf netnf nanoFramework.System.Net + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/nanoframework.System.Net.Sockets.TcpClient.sln b/nanoframework.System.Net.Sockets.TcpClient.sln index 7309b19..7ed83e1 100644 --- a/nanoframework.System.Net.Sockets.TcpClient.sln +++ b/nanoframework.System.Net.Sockets.TcpClient.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 16.0.32002.261 MinimumVisualStudioVersion = 10.0.40219.1 Project("{11A8DD76-328B-46DF-9F39-F559912D0360}") = "nanoframework.System.Net.Sockets.TcpClient", "nanoframework.System.Net.Sockets.TcpClient\nanoframework.System.Net.Sockets.TcpClient.nfproj", "{1D0F04E3-6AB6-4DE0-88D0-5E30F6F5719B}" EndProject +Project("{11A8DD76-328B-46DF-9F39-F559912D0360}") = "Tests", "Tests\Tests.nfproj", "{A0DF7A0B-8DCD-4CFA-A29F-49F0C2400A07}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -17,6 +19,12 @@ Global {1D0F04E3-6AB6-4DE0-88D0-5E30F6F5719B}.Release|Any CPU.ActiveCfg = Release|Any CPU {1D0F04E3-6AB6-4DE0-88D0-5E30F6F5719B}.Release|Any CPU.Build.0 = Release|Any CPU {1D0F04E3-6AB6-4DE0-88D0-5E30F6F5719B}.Release|Any CPU.Deploy.0 = Release|Any CPU + {A0DF7A0B-8DCD-4CFA-A29F-49F0C2400A07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A0DF7A0B-8DCD-4CFA-A29F-49F0C2400A07}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0DF7A0B-8DCD-4CFA-A29F-49F0C2400A07}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {A0DF7A0B-8DCD-4CFA-A29F-49F0C2400A07}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A0DF7A0B-8DCD-4CFA-A29F-49F0C2400A07}.Release|Any CPU.Build.0 = Release|Any CPU + {A0DF7A0B-8DCD-4CFA-A29F-49F0C2400A07}.Release|Any CPU.Deploy.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/nanoframework.System.Net.Sockets.TcpClient/Sockets/LingerOptions.cs b/nanoframework.System.Net.Sockets.TcpClient/Sockets/LingerOptions.cs index e42d490..f0e2753 100644 --- a/nanoframework.System.Net.Sockets.TcpClient/Sockets/LingerOptions.cs +++ b/nanoframework.System.Net.Sockets.TcpClient/Sockets/LingerOptions.cs @@ -12,13 +12,10 @@ namespace System.Net.Sockets /// public class LingerOption { - bool _enabled; - int _lingerTime; - /// - /// Initializes a new instance of the /// - /// Enable or Disable option. + /// Enable or disable option. /// Number of seconds to linger after close. public LingerOption(bool enable, int seconds) { @@ -29,11 +26,11 @@ namespace System.Net.Sockets /// /// Enables or disables lingering after close. /// - public bool Enabled { get => _enabled; set => _enabled = value; } + public bool Enabled { get; set; } /// /// The amount of time, in seconds, to remain connected after a close. /// - public int LingerTime { get => _lingerTime; set => _lingerTime = value; } - } -} \ No newline at end of file + public int LingerTime { get; set; } + } +} \ No newline at end of file diff --git a/nanoframework.System.Net.Sockets.TcpClient/Sockets/TcpClient.cs b/nanoframework.System.Net.Sockets.TcpClient/Sockets/TcpClient.cs index c6663ab..77d333c 100644 --- a/nanoframework.System.Net.Sockets.TcpClient/Sockets/TcpClient.cs +++ b/nanoframework.System.Net.Sockets.TcpClient/Sockets/TcpClient.cs @@ -6,13 +6,15 @@ namespace System.Net.Sockets { + /// + /// Provides client connections for TCP network services. + /// public class TcpClient : IDisposable { - private Socket _client; private NetworkStream _stream; - private bool disposedValue; - private AddressFamily _family = AddressFamily.InterNetwork; - bool _active; + private bool _disposed; + private readonly AddressFamily _family = AddressFamily.InterNetwork; + private bool _active; /// /// Initializes a new instance of the TcpClient class. @@ -29,7 +31,7 @@ namespace System.Net.Sockets { _family = localEP.AddressFamily; initialize(); - _client.Bind(localEP); + Client.Bind(localEP); } /// @@ -54,10 +56,10 @@ namespace System.Net.Sockets { Connect(hostname, port); } - catch (Exception ex) + catch (Exception) { - _client?.Close(); - throw ex; + Client?.Close(); + throw; } } @@ -67,18 +69,14 @@ namespace System.Net.Sockets /// internal TcpClient(Socket acceptedSocket) { - _client = acceptedSocket; + Client = acceptedSocket; _active = true; } /// /// Gets or sets the underlying Socket. /// - public Socket Client - { - get => _client; - set => _client = value; - } + public Socket Client { get; set; } /// /// Returns the NetworkStream used to send and receive data to remote host. @@ -86,15 +84,11 @@ namespace System.Net.Sockets /// The underlying NetworkStream. public NetworkStream GetStream() { - //if (!_client.Connected) - // { - // throw new InvalidOperationException("Not connected"); - // } - if (_stream == null) { _stream = new NetworkStream(Client, true); } + return _stream; } @@ -102,20 +96,30 @@ namespace System.Net.Sockets /// Gets the amount of data that has been received from the network /// and is available to be read. /// - public int Available { get => _client.Available; } + public int Available { get => Client.Available; } + private byte[] MilliSecsToTimeval(int millSecs) + { + byte[] timeval = new byte[8]; + int secs = millSecs / 1000; + int usecs = (millSecs % 1000) * 1000; - /// - /// Return connection status of underlying socket. - /// - public bool Connected - { - get - { - // We should be returning the _client.Connected state but that's not available - // so for the moment just return active state - return _active; - } + byte[] bsecs = BitConverter.GetBytes(secs); + byte[] busecs = BitConverter.GetBytes(usecs); + Array.Copy(bsecs, timeval, 4); + Array.Copy(busecs, 0, timeval, 4, 4); + + return timeval; + } + + private int TimevalToMilliSecs(byte[] timeval) + { + // Bytes 0 - 3 secs + // Bytes 4 - 7 usecs + int secs = BitConverter.ToInt32(timeval, 0); + int usecs = BitConverter.ToInt32(timeval, 4); + + return (secs * 1000) + (usecs / 1000); } /// @@ -125,13 +129,20 @@ namespace System.Net.Sockets { get { - return (int)Client.GetSocketOption(SocketOptionLevel.Socket, - SocketOptionName.ReceiveTimeout); + byte[] timeval = new byte[8]; + + Client.GetSocketOption(SocketOptionLevel.Socket, + SocketOptionName.ReceiveTimeout, timeval); + + return TimevalToMilliSecs(timeval); } + set { + byte[] timeval = MilliSecsToTimeval(value); + Client.SetSocketOption(SocketOptionLevel.Socket, - SocketOptionName.ReceiveTimeout, value); + SocketOptionName.ReceiveTimeout, timeval); } } @@ -142,14 +153,20 @@ namespace System.Net.Sockets { get { - return (int)Client.GetSocketOption(SocketOptionLevel.Socket, - SocketOptionName.SendTimeout); + byte[] timeval = new byte[8]; + + Client.GetSocketOption(SocketOptionLevel.Socket, + SocketOptionName.SendTimeout, timeval); + + return TimevalToMilliSecs(timeval); } set { + byte[] timeval = MilliSecsToTimeval(value); + Client.SetSocketOption(SocketOptionLevel.Socket, - SocketOptionName.SendTimeout, value); + SocketOptionName.SendTimeout, timeval); } } @@ -161,8 +178,9 @@ namespace System.Net.Sockets get { int optionValue = (int)Client.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Linger); - return (optionValue < 0)? new LingerOption(false, 0) : new LingerOption(true, optionValue); + return (optionValue < 0) ? new LingerOption(false, 0) : new LingerOption(true, optionValue); } + set { int optionValue = value.Enabled ? value.LingerTime : -1; @@ -171,15 +189,17 @@ namespace System.Net.Sockets } /// - /// Enables or disables delay when send or receive buffers are full. + /// Enables or disables delay when send or receive buffers are not full. (Nagle) + /// True if delay is disabled. /// public bool NoDelay { get { return (int)Client.GetSocketOption(SocketOptionLevel.Tcp, - SocketOptionName.NoDelay) != 0 ? true : false; + SocketOptionName.NoDelay) != 0; } + set { Client.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, value ? 1 : 0); @@ -193,7 +213,7 @@ namespace System.Net.Sockets /// The IPEndPoint to which you intend to connect. public void Connect(IPEndPoint remoteEP) { - _client.Connect(remoteEP); + Client.Connect(remoteEP); _active = true; } @@ -215,7 +235,24 @@ namespace System.Net.Sockets /// The port number to which you intend to connect. public void Connect(IPAddress[] address, int port) { - Connect(new IPEndPoint(address[0], port)); + foreach (IPAddress ipadr in address) + { + try + { + Connect(new IPEndPoint(ipadr, port)); + break; + } + catch (Exception) + { + // Ignore exception as we will throw NotConnected exception + // if not connected, _active not set. + } + } + + if (!_active) + { + throw new SocketException(SocketError.NotConnected); + } } /// @@ -238,7 +275,7 @@ namespace System.Net.Sockets try { // Via host name, port constructor ? - if (_client == null) + if (Client == null) { ipv4Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); ipv6Socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp); @@ -248,12 +285,12 @@ namespace System.Net.Sockets { try { - if (_client == null) + if (Client == null) { if (address.AddressFamily == AddressFamily.InterNetwork && ipv4Socket != null) { ipv4Socket.Connect(new IPEndPoint(address, port)); - _client = ipv4Socket; + Client = ipv4Socket; if (ipv6Socket != null) { ipv6Socket.Close(); @@ -262,7 +299,7 @@ namespace System.Net.Sockets else if (ipv6Socket != null) { ipv6Socket.Connect(new IPEndPoint(address, port)); - _client = ipv4Socket; + Client = ipv4Socket; if (ipv4Socket != null) { ipv4Socket.Close(); @@ -285,6 +322,7 @@ namespace System.Net.Sockets { throw; } + lastex = ex; } } // for each address @@ -295,6 +333,7 @@ namespace System.Net.Sockets { throw; } + lastex = ex; } finally @@ -311,8 +350,12 @@ namespace System.Net.Sockets { ipv4Socket.Close(); } - } + } + } + + if (!_active) + { // Throw exception if connect failed if (lastex != null) { @@ -336,22 +379,22 @@ namespace System.Net.Sockets private void initialize() { - _client = new Socket(_family, SocketType.Stream, ProtocolType.Tcp); + Client = new Socket(_family, SocketType.Stream, ProtocolType.Tcp); _active = false; } protected virtual void Dispose(bool disposing) { - if (!disposedValue) + if (!_disposed) { if (disposing) { _stream?.Dispose(); - _client?.Close(); - _client = null; + Client?.Close(); + Client = null; } - disposedValue = true; + _disposed = true; } } diff --git a/nanoframework.System.Net.Sockets.TcpClient/Sockets/TcpListener.cs b/nanoframework.System.Net.Sockets.TcpClient/Sockets/TcpListener.cs index 714654c..569cbbe 100644 --- a/nanoframework.System.Net.Sockets.TcpClient/Sockets/TcpListener.cs +++ b/nanoframework.System.Net.Sockets.TcpClient/Sockets/TcpListener.cs @@ -6,94 +6,103 @@ namespace System.Net.Sockets { - public class TcpListener - { - EndPoint _localEndPoint; - Socket _listenSocket; - bool _active = false; - - /// - /// Initializes a new instance of the TcpListener class that listens for incoming connection attempts on the specified local IP address and port number. - /// - /// An IPAddress that represents the local IP address. - /// The port on which to listen for incoming connection attempts. - public TcpListener(IPAddress localaddr, int port) : this(new IPEndPoint(localaddr, port)) - { - } - - /// - /// Initializes a new instance of the TcpListener class that listens for incoming connection - /// attempts with the specified endpoint. - /// - /// An IPEndPoint that represents the local endpoint to which to bind the listener Socket. - public TcpListener(IPEndPoint localEP) + /// + /// Listens for connections from TCP network clients. + /// + public class TcpListener + { + /// + /// Initializes a new instance of the TcpListener class that listens for incoming connection attempts on the specified local IP address and port number. + /// + /// An IPAddress that represents the local IP address. + /// The port on which to listen for incoming connection attempts. + public TcpListener(IPAddress localaddr, int port) : this(new IPEndPoint(localaddr, port)) { - _localEndPoint = localEP; - _listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IPv4); - } + } - /// - /// Gets the underlying EndPoint of the current TcpListener. - /// - public EndPoint LocalEndpoint { get => _localEndPoint; } + /// + /// Initializes a new instance of the TcpListener class that listens for incoming connection + /// attempts with the specified endpoint. + /// + /// An IPEndPoint that represents the local endpoint to which to bind the listener Socket. + public TcpListener(IPEndPoint localEP) + { + LocalEndpoint = localEP; + Server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IPv4); + } - /// - /// Gets the underlying network Socket. - /// - public Socket Server { get => _listenSocket; } + /// + /// Gets the underlying EndPoint of the current TcpListener. + /// + public EndPoint LocalEndpoint { get; } - /// - /// Gets a value that indicates whether TcpListener is actively listening - /// for client connections. - /// - protected bool Active { get => _active; } + /// + /// Gets the underlying network Socket. + /// + public Socket Server { get; private set; } - /// - /// Starts listening for incoming connection requests with a maximum number of pending connection. - /// - /// The maximum length of the pending connections queue. - public void Start(int backlog) - { - if (_listenSocket == null) + /// + /// Gets a value that indicates whether TcpListener is actively listening + /// for client connections. + /// + protected bool Active { get; private set; } + + /// + /// Starts listening for incoming connection requests with a maximum number of pending connection. + /// + /// The maximum length of the pending connections queue. + public void Start(int backlog) + { + if (Active) { - _listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IPv4); - } + throw new InvalidOperationException(); + } - _listenSocket.Bind(_localEndPoint); + if (Server == null) + { + Server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IPv4); + } - _listenSocket.Listen(backlog); + Server.Bind(LocalEndpoint); - _active = true; - } + Server.Listen(backlog); - /// - /// Closes the listener. - /// - public void Stop() + Active = true; + } + + /// + /// Closes the listener. + /// + public void Stop() { - _listenSocket.Close(); - _listenSocket = null; - _active = false; - } + if (Active) + { + throw new InvalidOperationException(); + } - /// - /// Accepts a pending connection request. - /// - /// A TcpClient used to send and receive data. - public TcpClient AcceptTcpClient() - { - TcpClient client = new TcpClient(_listenSocket.Accept()); + Server.Close(); + Server = null; + Active = false; + } - return client; - } - - /// - /// Accepts a pending connection request. - /// - /// A Socket used to send and receive data. - public Socket AcceptSocket() + /// + /// Accepts a pending connection request. + /// + /// A TcpClient used to send and receive data. + public TcpClient AcceptTcpClient() { - return _listenSocket.Accept(); - } - } + TcpClient client = new TcpClient(Server.Accept()); + + return client; + } + + /// + /// Accepts a pending connection request. + /// + /// A Socket used to send and receive data. + public Socket AcceptSocket() + { + return Server.Accept(); + } + } } diff --git a/nanoframework.System.Net.Sockets.TcpClient/key.snk b/nanoframework.System.Net.Sockets.TcpClient/key.snk new file mode 100644 index 0000000..67c9bb0 Binary files /dev/null and b/nanoframework.System.Net.Sockets.TcpClient/key.snk differ diff --git a/nanoframework.System.Net.Sockets.TcpClient/nanoframework.System.Net.Sockets.TcpClient.nfproj b/nanoframework.System.Net.Sockets.TcpClient/nanoframework.System.Net.Sockets.TcpClient.nfproj index 90d59c3..227eeb7 100644 --- a/nanoframework.System.Net.Sockets.TcpClient/nanoframework.System.Net.Sockets.TcpClient.nfproj +++ b/nanoframework.System.Net.Sockets.TcpClient/nanoframework.System.Net.Sockets.TcpClient.nfproj @@ -16,6 +16,15 @@ nanoframework.System.Net.Sockets.TcpClient v1.0 + + true + + + key.snk + + + false + @@ -25,27 +34,32 @@ - ..\packages\nanoFramework.CoreLibrary.1.12.0-preview.5\lib\mscorlib.dll + ..\packages\nanoFramework.CoreLibrary.1.12.0-preview.9\lib\mscorlib.dll True True - ..\packages\nanoFramework.Runtime.Events.1.10.0-preview.6\lib\nanoFramework.Runtime.Events.dll + ..\packages\nanoFramework.Runtime.Events.1.10.0-preview.8\lib\nanoFramework.Runtime.Events.dll True True - ..\packages\nanoFramework.System.Text.1.1.3-preview.13\lib\nanoFramework.System.Text.dll + ..\packages\nanoFramework.System.Text.1.1.3-preview.15\lib\nanoFramework.System.Text.dll + True + True + + + ..\packages\nanoFramework.System.IO.Streams.1.0.0-preview.12\lib\System.IO.Streams.dll True True - ..\packages\nanoFramework.System.Net.1.8.0-preview.26\lib\System.Net.dll + ..\packages\nanoFramework.System.Net.1.8.0-preview.29\lib\System.Net.dll True True - ..\packages\nanoFramework.System.Threading.1.0.4-preview.14\lib\System.Threading.dll + ..\packages\nanoFramework.System.Threading.1.0.4-preview.16\lib\System.Threading.dll True True @@ -59,4 +73,11 @@ + + + + This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}. + + + \ No newline at end of file diff --git a/nanoframework.System.Net.Sockets.TcpClient/packages.config b/nanoframework.System.Net.Sockets.TcpClient/packages.config index 365ce2e..eedc150 100644 --- a/nanoframework.System.Net.Sockets.TcpClient/packages.config +++ b/nanoframework.System.Net.Sockets.TcpClient/packages.config @@ -1,8 +1,10 @@  - - - - - + + + + + + + \ No newline at end of file diff --git a/version.json b/version.json new file mode 100644 index 0000000..5711497 --- /dev/null +++ b/version.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", + "version": "1.0.0-preview.{height}", + "assemblyVersion": { + "precision": "revision" + }, + "semVer1NumericIdentifierPadding": 3, + "nuGetPackageVersion": { + "semVer": 2.0 + }, + "publicReleaseRefSpec": [ + "^refs/heads/main$", + "^refs/heads/v\\d+(?:\\.\\d+)?$" + ], + "cloudBuild": { + "setAllVariables": true + }, + "release": { + "branchName": "release-v{version}", + "versionIncrement": "build", + "firstUnstableTag": "preview" + } +}