diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..d91a01d3 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,39 @@ +{ + "image": "mcr.microsoft.com/devcontainers/cpp:1.0.0-buster", + "features": { + "ghcr.io/devcontainers/features/python:1": { + "version": "3.11" + }, + "ghcr.io/devcontainers/features/dotnet:1": { + "version": "6" + }, + "ghcr.io/devcontainers/features/java:1": { + "version": "17", + "installAnt": true, + "antVersion": "1.10.12" + }, + "ghcr.io/devcontainers/features/docker-in-docker:2": { + "version": "20.10" + }, + "ghcr.io/devcontainers/features/powershell:1": { + "version": "7.3" + } +}, +"customizations": { + "vscode": { + "extensions": [ + "DavidAnson.vscode-markdownlint", + "eamodio.gitlens", + "github.vscode-github-actions", + "GitHub.codespaces", + "jebbs.plantuml", + "ms-python.isort", + "ms-vscode-remote.remote-containers", + "ms-vscode.cpptools-themes", + "ms-vscode.PowerShell", + "tomoki1207.pdf", + "yzhang.markdown-all-in-one" + ] + } +} +} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..526c8a38 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.sh text eol=lf \ No newline at end of file diff --git a/.github/workflows/dotnet-publish.yml b/.github/workflows/dotnet-publish.yml new file mode 100644 index 00000000..15ffb13f --- /dev/null +++ b/.github/workflows/dotnet-publish.yml @@ -0,0 +1,142 @@ +name: ".NET Publish" + +on: + workflow_run: + workflows: ['.NET'] + types: + - completed + branches: + - master + - main + workflow_dispatch: + +jobs: + publish: + + runs-on: ubuntu-latest + permissions: + packages: write + defaults: + run: + working-directory: ./src/dotnet + + if: ${{ github.event.workflow_run.conclusion == 'success' }} + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 6.0.x + source-url: https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json + env: + NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} + + - name: Package .NET + run: dotnet pack . -c Release + - name: Publish .NET + run: | + package=$(find . -type f -name "*.nupkg") + dotnet nuget push "$package" --skip-duplicate + + publish-linux-x64: + + runs-on: ubuntu-latest + permissions: + packages: write + + if: ${{ github.event.workflow_run.conclusion == 'success' }} + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Package .NET + uses: devcontainers/ci@v0.3 + with: + push: never + runCmd: | + cd ./src/dotnet + chmod 755 *.sh + ./pack-linux-x64.sh + - name: Set up .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '6.0.x' + source-url: https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json + env: + NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} + - name: Publish .NET + run: | + package=$(find . -type f -name "*linux-x64*.nupkg") + dotnet nuget push "$package" --skip-duplicate + + publish-win-x64: + + runs-on: windows-latest + permissions: + packages: write + defaults: + run: + working-directory: ./src/dotnet + + if: ${{ github.event.workflow_run.conclusion == 'success' }} + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup Python + uses: actions/setup-python@v5.1.0 + with: + python-version: 3.x + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 6.0.x + source-url: https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json + env: + NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} + + - name: Package .NET + run: .\pack-win-x64.bat + - name: Publish .NET + run: | + $package=Get-ChildItem -Path .\ -Filter *win-x64*.nupkg -Recurse -File | ForEach-Object { $_.FullName } + dotnet nuget push "$package" --skip-duplicate + + publish-win-x86: + + runs-on: windows-latest + permissions: + packages: write + defaults: + run: + working-directory: ./src/dotnet + + if: ${{ github.event.workflow_run.conclusion == 'success' }} + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup Python + uses: actions/setup-python@v5.1.0 + with: + python-version: 3.x + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 6.0.x + source-url: https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json + env: + NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} + + - name: Package .NET + run: .\pack-win-x86.bat + - name: Publish .NET + run: | + $package=Get-ChildItem -Path .\ -Filter *win-x86*.nupkg -Recurse -File | ForEach-Object { $_.FullName } + dotnet nuget push "$package" --skip-duplicate diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml new file mode 100644 index 00000000..4747842d --- /dev/null +++ b/.github/workflows/dotnet.yml @@ -0,0 +1,49 @@ +name: .NET + +on: [push, pull_request, workflow_dispatch] + +jobs: + build: + + runs-on: ubuntu-latest + permissions: + checks: write + pull-requests: write + defaults: + run: + working-directory: ./src/dotnet + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup Python + uses: actions/setup-python@v5.1.0 + with: + python-version: 3.x + - name: Build NORM + run: ./waf + working-directory: . + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 6.0.x + - name: Restore dependencies + run: dotnet restore /property:Configuration=Release + - name: Build + run: dotnet build --no-restore --configuration Release + - name: Test + run: dotnet test --no-build --verbosity normal --configuration Release --logger trx --results-directory TestResults + - name: Upload test results + uses: actions/upload-artifact@v4 + with: + name: dotnet-results + path: ./src/dotnet/TestResults + if: ${{ always() }} + - name: Publish test results + uses: EnricoMi/publish-unit-test-result-action@v2 + with: + files: ./src/dotnet/TestResults/*.trx + if: ${{ always() }} diff --git a/.gitignore b/.gitignore index 7faead0e..73738267 100755 --- a/.gitignore +++ b/.gitignore @@ -18,9 +18,16 @@ norp/makefiles/norp *.o *.a +# build java files +*.class + +#build android files +android/.gradle + # waf cruft .waf* .lock-waf* +waf*/* # OS generated files # .DS_Store @@ -30,3 +37,275 @@ norp/makefiles/norp .Trashes ehthumbs.db Thumbs.db + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml +*.sqlite +*.sqlite-shm +*.sqlite-wal + +# Nuget files +DeploymentSettings.props +Directory.Build.props +Directory.Build.targets +Packages.props + +#Local Log Files +log*.txt + +.NET Extension Library Files +/src/dotnet/lib \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..50bbda49 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "github.vscode-pull-request-github" + ] +} \ No newline at end of file diff --git a/README-DotNet.md b/README-DotNet.md new file mode 100644 index 00000000..401bf05f --- /dev/null +++ b/README-DotNet.md @@ -0,0 +1,72 @@ +.NET extension for NORM +========================== + +By: + Jeff Muller + Sylvester Freeman + Sergiy Yermak + +The .NET extension for NORM provides a .NET wrapper for the NORM C API. + +For documentation about the main NORM API calls, refer to the NORM Developers +guide in the regular NORM distribution. + +The .NET extension can be built using the .NET CLI. + +------------ +Requirements +------------ + +The .NET extension for NORM requires at least .NET SDK 6.0. + +The NORM library should be built prior to building the .NET extension since it is invoked by the .NET extension. + +------------ +Building +------------ + +To build the .NET extension for NORM, execute the .NET CLI command in the src/dotnet directory: + + ``` + dotnet build . + ``` + +------------ +Testing +------------ + +To test the .NET extension for NORM, execute the .NET CLI command in the src/dotnet directory: + + ``` + dotnet test . + ``` + +The test command results should show that all tests have passed. +Some tests might be skipped due to IO exception. + +------------ +Packaging +------------ +To package the .NET extension for NORM, execute the .NET CLI command in the src/dotnet directory: + +``` +dotnet pack . -c Release +``` + +To package the .NET extension for NORM that targets 64-bit Windows, execute the command in the src/dotnet directory: + +``` +pack-win-x64.bat +``` + +To package the .NET extension for NORM that targets 32-bit Windows, execute the command in the src/dotnet directory: + +``` +pack-win-x86.bat +``` + +To package the .NET extension for NORM that targets 64-bit Linux, execute the command in the src/dotnet directory: + +``` +./pack-linux-x64.sh +``` \ No newline at end of file diff --git a/src/dotnet/Mil.Navy.Nrl.Norm.sln b/src/dotnet/Mil.Navy.Nrl.Norm.sln new file mode 100644 index 00000000..97669107 --- /dev/null +++ b/src/dotnet/Mil.Navy.Nrl.Norm.sln @@ -0,0 +1,85 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.4.33213.308 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mil.Navy.Nrl.Norm", "src\Mil.Navy.Nrl.Norm\Mil.Navy.Nrl.Norm.csproj", "{0B38FE25-685E-4ADC-B63D-CA43EC023E1F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A6F15F54-6953-49E7-9394-8F6BF6EF64D0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mil.Navy.Nrl.Norm.IntegrationTests", "tests\Mil.Navy.Nrl.Norm.IntegrationTests\Mil.Navy.Nrl.Norm.IntegrationTests.csproj", "{E5CDA4C5-C1A1-4AD7-B10D-80B8A995FC49}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{8B6C912C-2A15-41F3-B3A4-E9C01E6672DE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mil.Navy.Nrl.Norm.win-x64", "src\Mil.Navy.Nrl.Norm.win-x64\Mil.Navy.Nrl.Norm.win-x64.csproj", "{EC81BD1A-ECF0-4F4D-86E3-B0B53531EBD4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mil.Navy.Nrl.Norm.win-x86", "src\Mil.Navy.Nrl.Norm.win-x86\Mil.Navy.Nrl.Norm.win-x86.csproj", "{E5EABC55-159A-4AA2-80EC-F817C20BDA40}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CD4475A5-559A-4852-A2FE-E85A8AB9E0D8}" + ProjectSection(SolutionItems) = preProject + pack-linux.sh = pack-linux.sh + pack-win32.bat = pack-win32.bat + pack-win64.bat = pack-win64.bat + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mil.Navy.Nrl.Norm.linux-x64", "src\Mil.Navy.Nrl.Norm.linux-x64\Mil.Navy.Nrl.Norm.linux-x64.csproj", "{A4967E0C-FF3E-4B61-BD6B-D63D040585CA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + Release-linux-x64|Any CPU = Release-linux-x64|Any CPU + Release-win-x64|Any CPU = Release-win-x64|Any CPU + Release-win-x86|Any CPU = Release-win-x86|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0B38FE25-685E-4ADC-B63D-CA43EC023E1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0B38FE25-685E-4ADC-B63D-CA43EC023E1F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0B38FE25-685E-4ADC-B63D-CA43EC023E1F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0B38FE25-685E-4ADC-B63D-CA43EC023E1F}.Release|Any CPU.Build.0 = Release|Any CPU + {0B38FE25-685E-4ADC-B63D-CA43EC023E1F}.Release-linux-x64|Any CPU.ActiveCfg = Release|Any CPU + {0B38FE25-685E-4ADC-B63D-CA43EC023E1F}.Release-linux-x64|Any CPU.Build.0 = Release|Any CPU + {0B38FE25-685E-4ADC-B63D-CA43EC023E1F}.Release-win-x64|Any CPU.ActiveCfg = Release|Any CPU + {0B38FE25-685E-4ADC-B63D-CA43EC023E1F}.Release-win-x64|Any CPU.Build.0 = Release|Any CPU + {0B38FE25-685E-4ADC-B63D-CA43EC023E1F}.Release-win-x86|Any CPU.ActiveCfg = Release|Any CPU + {0B38FE25-685E-4ADC-B63D-CA43EC023E1F}.Release-win-x86|Any CPU.Build.0 = Release|Any CPU + {E5CDA4C5-C1A1-4AD7-B10D-80B8A995FC49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E5CDA4C5-C1A1-4AD7-B10D-80B8A995FC49}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E5CDA4C5-C1A1-4AD7-B10D-80B8A995FC49}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E5CDA4C5-C1A1-4AD7-B10D-80B8A995FC49}.Release|Any CPU.Build.0 = Release|Any CPU + {E5CDA4C5-C1A1-4AD7-B10D-80B8A995FC49}.Release-linux-x64|Any CPU.ActiveCfg = Release|Any CPU + {E5CDA4C5-C1A1-4AD7-B10D-80B8A995FC49}.Release-win-x64|Any CPU.ActiveCfg = Release|Any CPU + {E5CDA4C5-C1A1-4AD7-B10D-80B8A995FC49}.Release-win-x86|Any CPU.ActiveCfg = Release|Any CPU + {EC81BD1A-ECF0-4F4D-86E3-B0B53531EBD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EC81BD1A-ECF0-4F4D-86E3-B0B53531EBD4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EC81BD1A-ECF0-4F4D-86E3-B0B53531EBD4}.Release-linux-x64|Any CPU.ActiveCfg = Release|Any CPU + {EC81BD1A-ECF0-4F4D-86E3-B0B53531EBD4}.Release-win-x64|Any CPU.ActiveCfg = Release|Any CPU + {EC81BD1A-ECF0-4F4D-86E3-B0B53531EBD4}.Release-win-x64|Any CPU.Build.0 = Release|Any CPU + {EC81BD1A-ECF0-4F4D-86E3-B0B53531EBD4}.Release-win-x86|Any CPU.ActiveCfg = Release|Any CPU + {E5EABC55-159A-4AA2-80EC-F817C20BDA40}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E5EABC55-159A-4AA2-80EC-F817C20BDA40}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E5EABC55-159A-4AA2-80EC-F817C20BDA40}.Release-linux-x64|Any CPU.ActiveCfg = Release|Any CPU + {E5EABC55-159A-4AA2-80EC-F817C20BDA40}.Release-win-x64|Any CPU.ActiveCfg = Release|Any CPU + {E5EABC55-159A-4AA2-80EC-F817C20BDA40}.Release-win-x86|Any CPU.ActiveCfg = Release|Any CPU + {E5EABC55-159A-4AA2-80EC-F817C20BDA40}.Release-win-x86|Any CPU.Build.0 = Release|Any CPU + {A4967E0C-FF3E-4B61-BD6B-D63D040585CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A4967E0C-FF3E-4B61-BD6B-D63D040585CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A4967E0C-FF3E-4B61-BD6B-D63D040585CA}.Release-linux-x64|Any CPU.ActiveCfg = Release|Any CPU + {A4967E0C-FF3E-4B61-BD6B-D63D040585CA}.Release-linux-x64|Any CPU.Build.0 = Release|Any CPU + {A4967E0C-FF3E-4B61-BD6B-D63D040585CA}.Release-win-x64|Any CPU.ActiveCfg = Release|Any CPU + {A4967E0C-FF3E-4B61-BD6B-D63D040585CA}.Release-win-x86|Any CPU.ActiveCfg = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {0B38FE25-685E-4ADC-B63D-CA43EC023E1F} = {A6F15F54-6953-49E7-9394-8F6BF6EF64D0} + {E5CDA4C5-C1A1-4AD7-B10D-80B8A995FC49} = {8B6C912C-2A15-41F3-B3A4-E9C01E6672DE} + {EC81BD1A-ECF0-4F4D-86E3-B0B53531EBD4} = {A6F15F54-6953-49E7-9394-8F6BF6EF64D0} + {E5EABC55-159A-4AA2-80EC-F817C20BDA40} = {A6F15F54-6953-49E7-9394-8F6BF6EF64D0} + {A4967E0C-FF3E-4B61-BD6B-D63D040585CA} = {A6F15F54-6953-49E7-9394-8F6BF6EF64D0} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {8BC1F27F-D633-4B45-A16D-D112CB08840E} + EndGlobalSection +EndGlobal diff --git a/src/dotnet/design/Mil/Navy/Nrl/Norm/Buffers/ByteBuffer.puml b/src/dotnet/design/Mil/Navy/Nrl/Norm/Buffers/ByteBuffer.puml new file mode 100644 index 00000000..22de7e71 --- /dev/null +++ b/src/dotnet/design/Mil/Navy/Nrl/Norm/Buffers/ByteBuffer.puml @@ -0,0 +1,7 @@ +@startuml +abstract class ByteBuffer { + # ByteBuffer() + + {static} AllocateDirect(capacity:int) : ByteBuffer +} +SafeBuffer <|-- ByteBuffer +@enduml diff --git a/src/dotnet/design/Mil/Navy/Nrl/Norm/Buffers/DirectByteBuffer.puml b/src/dotnet/design/Mil/Navy/Nrl/Norm/Buffers/DirectByteBuffer.puml new file mode 100644 index 00000000..58106fca --- /dev/null +++ b/src/dotnet/design/Mil/Navy/Nrl/Norm/Buffers/DirectByteBuffer.puml @@ -0,0 +1,7 @@ +@startuml +class DirectByteBuffer <> { + <> DirectByteBuffer(capacity:int) + # <> ReleaseHandle() : bool +} +ByteBuffer <|-- DirectByteBuffer +@enduml diff --git a/src/dotnet/design/Mil/Navy/Nrl/Norm/Enums/NormAckingStatus.puml b/src/dotnet/design/Mil/Navy/Nrl/Norm/Enums/NormAckingStatus.puml new file mode 100644 index 00000000..aa34be5f --- /dev/null +++ b/src/dotnet/design/Mil/Navy/Nrl/Norm/Enums/NormAckingStatus.puml @@ -0,0 +1,8 @@ +@startuml +enum NormAckingStatus { + NORM_ACK_INVALID, + NORM_ACK_FAILURE, + NORM_ACK_PENDING, + NORM_ACK_SUCCESS, +} +@enduml diff --git a/src/dotnet/design/Mil/Navy/Nrl/Norm/Enums/NormEventType.puml b/src/dotnet/design/Mil/Navy/Nrl/Norm/Enums/NormEventType.puml new file mode 100644 index 00000000..bf72c92d --- /dev/null +++ b/src/dotnet/design/Mil/Navy/Nrl/Norm/Enums/NormEventType.puml @@ -0,0 +1,33 @@ +@startuml +enum NormEventType { + NORM_EVENT_INVALID, + NORM_TX_QUEUE_VACANCY, + NORM_TX_QUEUE_EMPTY, + NORM_TX_FLUSH_COMPLETED, + NORM_TX_WATERMARK_COMPLETED, + NORM_TX_CMD_SENT, + NORM_TX_OBJECT_SENT, + NORM_TX_OBJECT_PURGED, + NORM_TX_RATE_CHANGED, + NORM_LOCAL_SENDER_CLOSED, + NORM_REMOTE_SENDER_NEW, + NORM_REMOTE_SENDER_RESET, + NORM_REMOTE_SENDER_ADDRESS, + NORM_REMOTE_SENDER_ACTIVE, + NORM_REMOTE_SENDER_INACTIVE, + NORM_REMOTE_SENDER_PURGED, + NORM_RX_CMD_NEW, + NORM_RX_OBJECT_NEW, + NORM_RX_OBJECT_INFO, + NORM_RX_OBJECT_UPDATED, + NORM_RX_OBJECT_COMPLETED, + NORM_RX_OBJECT_ABORTED, + NORM_RX_ACK_REQUEST, + NORM_GRTT_UPDATED, + NORM_CC_ACTIVE, + NORM_CC_INACTIVE, + NORM_ACKING_NODE_NEW, + NORM_SEND_ERROR, + NORM_USER_TIMEOUT, +} +@enduml diff --git a/src/dotnet/design/Mil/Navy/Nrl/Norm/Enums/NormFecType.puml b/src/dotnet/design/Mil/Navy/Nrl/Norm/Enums/NormFecType.puml new file mode 100644 index 00000000..c492e798 --- /dev/null +++ b/src/dotnet/design/Mil/Navy/Nrl/Norm/Enums/NormFecType.puml @@ -0,0 +1,7 @@ +@startuml +enum NormFecType { + RS, + RS8, + SB, +} +@enduml diff --git a/src/dotnet/design/Mil/Navy/Nrl/Norm/Enums/NormFlushMode.puml b/src/dotnet/design/Mil/Navy/Nrl/Norm/Enums/NormFlushMode.puml new file mode 100644 index 00000000..1de4e631 --- /dev/null +++ b/src/dotnet/design/Mil/Navy/Nrl/Norm/Enums/NormFlushMode.puml @@ -0,0 +1,7 @@ +@startuml +enum NormFlushMode { + NORM_FLUSH_NONE, + NORM_FLUSH_PASSIVE, + NORM_FLUSH_ACTIVE, +} +@enduml diff --git a/src/dotnet/design/Mil/Navy/Nrl/Norm/Enums/NormNackingMode.puml b/src/dotnet/design/Mil/Navy/Nrl/Norm/Enums/NormNackingMode.puml new file mode 100644 index 00000000..81285969 --- /dev/null +++ b/src/dotnet/design/Mil/Navy/Nrl/Norm/Enums/NormNackingMode.puml @@ -0,0 +1,7 @@ +@startuml +enum NormNackingMode { + NORM_NACK_NONE, + NORM_NACK_INFO_ONLY, + NORM_NACK_NORMAL, +} +@enduml diff --git a/src/dotnet/design/Mil/Navy/Nrl/Norm/Enums/NormObjectType.puml b/src/dotnet/design/Mil/Navy/Nrl/Norm/Enums/NormObjectType.puml new file mode 100644 index 00000000..42689868 --- /dev/null +++ b/src/dotnet/design/Mil/Navy/Nrl/Norm/Enums/NormObjectType.puml @@ -0,0 +1,8 @@ +@startuml +enum NormObjectType { + NORM_OBJECT_NONE, + NORM_OBJECT_DATA, + NORM_OBJECT_FILE, + NORM_OBJECT_STREAM, +} +@enduml diff --git a/src/dotnet/design/Mil/Navy/Nrl/Norm/Enums/NormProbingMode.puml b/src/dotnet/design/Mil/Navy/Nrl/Norm/Enums/NormProbingMode.puml new file mode 100644 index 00000000..93c95f00 --- /dev/null +++ b/src/dotnet/design/Mil/Navy/Nrl/Norm/Enums/NormProbingMode.puml @@ -0,0 +1,7 @@ +@startuml +enum NormProbingMode { + NORM_PROBE_NONE, + NORM_PROBE_PASSIVE, + NORM_PROBE_ACTIVE, +} +@enduml diff --git a/src/dotnet/design/Mil/Navy/Nrl/Norm/Enums/NormRepairBoundary.puml b/src/dotnet/design/Mil/Navy/Nrl/Norm/Enums/NormRepairBoundary.puml new file mode 100644 index 00000000..bba6130f --- /dev/null +++ b/src/dotnet/design/Mil/Navy/Nrl/Norm/Enums/NormRepairBoundary.puml @@ -0,0 +1,6 @@ +@startuml +enum NormRepairBoundary { + NORM_BOUNDARY_BLOCK, + NORM_BOUNDARY_OBJECT, +} +@enduml diff --git a/src/dotnet/design/Mil/Navy/Nrl/Norm/Enums/NormSyncPolicy.puml b/src/dotnet/design/Mil/Navy/Nrl/Norm/Enums/NormSyncPolicy.puml new file mode 100644 index 00000000..870db564 --- /dev/null +++ b/src/dotnet/design/Mil/Navy/Nrl/Norm/Enums/NormSyncPolicy.puml @@ -0,0 +1,7 @@ +@startuml +enum NormSyncPolicy { + NORM_SYNC_CURRENT, + NORM_SYNC_STREAM, + NORM_SYNC_ALL, +} +@enduml diff --git a/src/dotnet/design/Mil/Navy/Nrl/Norm/Enums/NormTrackingStatus.puml b/src/dotnet/design/Mil/Navy/Nrl/Norm/Enums/NormTrackingStatus.puml new file mode 100644 index 00000000..048d1e48 --- /dev/null +++ b/src/dotnet/design/Mil/Navy/Nrl/Norm/Enums/NormTrackingStatus.puml @@ -0,0 +1,9 @@ +@startuml +enum NormTrackingStatus +{ + NORM_TRACK_NONE + NORM_TRACK_RECEIVERS + NORM_TRACK_SENDERS + NORM_TRACK_ALL +} +@enduml \ No newline at end of file diff --git a/src/dotnet/design/Mil/Navy/Nrl/Norm/IO/INormEventListener.puml b/src/dotnet/design/Mil/Navy/Nrl/Norm/IO/INormEventListener.puml new file mode 100644 index 00000000..0243b8e8 --- /dev/null +++ b/src/dotnet/design/Mil/Navy/Nrl/Norm/IO/INormEventListener.puml @@ -0,0 +1,5 @@ +@startuml +interface INormEventListener { + NormEventOccurred(normEvent:NormEvent) : void +} +@enduml diff --git a/src/dotnet/design/Mil/Navy/Nrl/Norm/IO/NormInputStream.puml b/src/dotnet/design/Mil/Navy/Nrl/Norm/IO/NormInputStream.puml new file mode 100644 index 00000000..9136cba3 --- /dev/null +++ b/src/dotnet/design/Mil/Navy/Nrl/Norm/IO/NormInputStream.puml @@ -0,0 +1,45 @@ +@startuml +class NormInputStream { + - _normStream : NormStream? + - _closed : bool + - _closeLock : object + - _bufferIsEmpty : bool + - _receivedEof : bool + + NormInputStream(address:string, port:int) + + OpenDebugLog(fileName:string) : void + + CloseDebugLog() : void + + NormSetDebugLevel(level:int) : void + + SetMessageTrace(messageTrace:bool) : void + + SetMulticastInterface(multicastInterface:string) : void + + SetEcnSupport(ecnEnable:bool, ignoreLoss:bool) : void + + SetTtl(ttl:byte) : void + + SetTos(tos:byte) : void + + setSilentReceiver(silent:bool, maxDelay:int) : void + + SetDefaultUnicastNack(defaultUnicastNack:bool) : void + + SeekMsgStart() : void + + AddNormEventListener(normEventListener:INormEventListener) : void + + RemoveNormEventListener(normEventListener:INormEventListener) : void + - FireNormEventOccured(normEvent:NormEvent) : void + + Open(bufferSpace:long) : void + + <> Close() : void + + IsClosed : bool <> + + Read() : int + + <> Read(buffer:byte[], offset:int, count:int) : int + - ProcessEvent() : void + + <> Flush() : void + + <> Write(buffer:byte[], offset:int, count:int) : void + + <> Seek(offset:long, origin:SeekOrigin) : long + + <> SetLength(value:long) : void + + <> CanRead : bool <> + + <> CanSeek : bool <> + + <> CanWrite : bool <> + + <> Length : long <> + + <> Position : long <> <> +} +class "List`1" { +} +Stream <|-- NormInputStream +NormInputStream --> "_normInstance" NormInstance +NormInputStream --> "_normSession" NormSession +NormInputStream --> "_normEventListeners" "List`1" +@enduml diff --git a/src/dotnet/design/Mil/Navy/Nrl/Norm/IO/NormOutputStream.puml b/src/dotnet/design/Mil/Navy/Nrl/Norm/IO/NormOutputStream.puml new file mode 100644 index 00000000..a15b0e63 --- /dev/null +++ b/src/dotnet/design/Mil/Navy/Nrl/Norm/IO/NormOutputStream.puml @@ -0,0 +1,51 @@ +@startuml +class NormOutputStream { + - _normStream : NormStream? + - _closed : bool + - _closeLock : object + - _bufferIsFull : bool + + NormOutputStream(address:string, port:int) + + OpenDebugLog(filename:string) : void + + CloseDebugLog() : void + + SetDebugLevel(level:int) : void + + SetMessageTrace(messageTrace:bool) : void + + SetMulticastInterface(multicastInterface:string) : void + + SetEcnSupport(ecnEnable:bool, ignoreLoss:bool) : void + + SetTtl(ttl:byte) : void + + SetTos(tos:byte) : void + + SetCongestionControl(ccEnabled:bool, ccAdjustRate:bool) : void + + SetTxRateBounds(minTxRate:double, maxTxRate:double) : void + + TxRate : double <> <> + + GrttEstimate : double <> <> + + SetGroupSize(groupSize:long) : void + + SetAutoParity(autoParity:short) : void + + SetBackoffFactor(backoffFactor:double) : void + + SetAutoFlush(flushMode:NormFlushMode) : void + + SetPushEnable(pushEnable:bool) : void + + MarkEom() : void + + AddNormEventListener(normEventListener:INormEventListener) : void + + RemoveNormEventListener(normEventListener:INormEventListener) : void + - FireNormEventOccured(normEvent:NormEvent) : void + + Open(sessionId:int, bufferSpace:long, segmentSize:int, blockSize:short, numParity:short, repairWindow:long) : void + + <> Close() : void + + IsClosed : bool <> + + Write(b:int) : void + + <> Write(buffer:byte[], offset:int, count:int) : void + - ProcessEvent() : void + + <> Flush() : void + + <> Read(buffer:byte[], offset:int, count:int) : int + + <> Seek(offset:long, origin:SeekOrigin) : long + + <> SetLength(value:long) : void + + <> CanRead : bool <> + + <> CanSeek : bool <> + + <> CanWrite : bool <> + + <> Length : long <> + + <> Position : long <> <> +} +class "List`1" { +} +Stream <|-- NormOutputStream +NormOutputStream --> "_normInstance" NormInstance +NormOutputStream --> "_normSession" NormSession +NormOutputStream --> "_normEventListeners" "List`1" +@enduml diff --git a/src/dotnet/design/Mil/Navy/Nrl/Norm/IO/StreamBreakException.puml b/src/dotnet/design/Mil/Navy/Nrl/Norm/IO/StreamBreakException.puml new file mode 100644 index 00000000..10cdc78f --- /dev/null +++ b/src/dotnet/design/Mil/Navy/Nrl/Norm/IO/StreamBreakException.puml @@ -0,0 +1,6 @@ +@startuml +class StreamBreakException { + + StreamBreakException(message:string?) +} +IOException <|-- StreamBreakException +@enduml diff --git a/src/dotnet/design/Mil/Navy/Nrl/Norm/NormApi.puml b/src/dotnet/design/Mil/Navy/Nrl/Norm/NormApi.puml new file mode 100644 index 00000000..508dbe8a --- /dev/null +++ b/src/dotnet/design/Mil/Navy/Nrl/Norm/NormApi.puml @@ -0,0 +1,119 @@ +@startuml +class NormApi <> { + + <> NORM_LIBRARY : string = "norm" + + {static} <> NormCreateInstance(priorityBoost:bool) : long + + {static} <> NormDestroyInstance(instanceHandle:long) : void + + {static} <> NormStopInstance(instanceHandle:long) : void + + {static} <> NormRestartInstance(instanceHandle:long) : bool + + {static} <> NormSuspendInstance(instanceHandle:long) : bool + + {static} <> NormResumeInstance(instanceHandle:long) : bool + + {static} <> NormSetCacheDirectory(instanceHandle:long, cachePath:string) : bool + + {static} <> NormGetNextEvent(instanceHandle:long, theEvent:NormEvent, waitForEvent:bool) : bool + + {static} <> NormGetDescriptor(instanceHandle:long) : int + + {static} <> NormCreateSession(instanceHandle:long, sessionAddress:string, sessionPort:int, localNodeId:long) : long + + {static} <> NormDestroySession(sessionHandle:long) : void + + {static} <> NormGetLocalNodeId(sessionHandle:long) : long + + {static} <> NormSetTxPort(sessionHandle:long, txPortNumber:int, enableReuse:bool, txBindAddress:string?) : bool + + {static} <> NormSetTxOnly(sessionHandle:long, txOnly:bool, connectToSessionAddress:bool) : void + + {static} <> NormSetRxPortReuse(sessionHandle:long, enableReuse:bool, rxBindAddress:string?, senderAddress:string?, senderPort:int) : void + + {static} <> NormSetEcnSupport(sessionHandle:long, ecnEnable:bool, ignoreLoss:bool, tolerateLoss:bool) : void + + {static} <> NormSetMulticastInterface(sessionHandle:long, interfaceName:string) : bool + + {static} <> NormSetSSM(sessionHandle:long, sourceAddress:string) : bool + + {static} <> NormSetTTL(sessionHandle:long, ttl:byte) : bool + + {static} <> NormSetTOS(sessionHandle:long, tos:byte) : bool + + {static} <> NormSetLoopback(sessionHandle:long, loopback:bool) : bool + + {static} <> NormSetMessageTrace(sessionHandle:long, flag:bool) : void + + {static} <> NormSetTxLoss(sessionHandle:long, precent:double) : void + + {static} <> NormSetRxLoss(sessionHandle:long, precent:double) : void + + {static} <> NormOpenDebugLog(instanceHandle:long, path:string) : bool + + {static} <> NormCloseDebugLog(instanceHandle:long) : bool + + {static} <> NormOpenDebugPipe(instanceHandle:long, pipeName:string) : bool + + {static} <> NormSetDebugLevel(level:int) : void + + {static} <> NormGetDebugLevel() : int + + {static} <> NormSetReportInterval(sessionHandle:long, interval:double) : void + + {static} <> NormGetReportInterval(sessionHandle:long) : double + + {static} <> NormGetRandomSessionId() : int + + {static} <> NormStartSender(instanceHandle:long, instanceId:int, bufferSpace:long, segmentSize:int, numData:short, numParity:short, fecId:NormFecType) : bool + + {static} <> NormStopSender(sessionHandle:long) : void + + {static} <> NormSetTxRate(sessionHandle:long, rate:double) : void + + {static} <> NormGetTxRate(sessionHandle:long) : double + + {static} <> NormSetTxSocketBuffer(sessionHandle:long, bufferSize:long) : bool + + {static} <> NormSetFlowControl(sessionHandle:long, flowControlFactor:double) : void + + {static} <> NormSetCongestionControl(sessionHandle:long, enable:bool, adjustRate:bool) : void + + {static} <> NormSetTxRateBounds(sessionHandle:long, rateMin:double, rateMax:double) : void + + {static} <> NormSetTxCacheBounds(sessionHandle:long, sizeMax:long, countMin:long, countMax:long) : void + + {static} <> NormSetAutoParity(sesssionHandle:long, autoParity:short) : void + + {static} <> NormSetGrttEstimate(sessionHandle:long, grtt:double) : void + + {static} <> NormGetGrttEstimate(sessionHandle:long) : double + + {static} <> NormSetGrttMax(sessionHandle:long, grttMax:double) : void + + {static} <> NormSetGrttProbingMode(sesssionHandle:long, probingMode:NormProbingMode) : void + + {static} <> NormSetGrttProbingInterval(sessionHandle:long, intervalMin:double, intervalMax:double) : void + + {static} <> NormSetBackoffFactor(sessionHandle:long, backoffFactor:double) : void + + {static} <> NormSetGroupSize(sessionHandle:long, groupSize:long) : void + + {static} <> NormSetTxRobustFactor(sessionHandle:long, txRobustFactor:int) : void + + {static} <> NormFileEnqueue(sessionHandle:long, fileName:string, infoPtr:nint, infoLen:int) : long + + {static} <> NormDataEnqueue(sessionHandle:long, dataPtr:nint, dataLen:int, infoPtr:nint, infoLen:int) : long + + {static} <> NormRequeueObject(sessionHandle:long, objectHandle:long) : bool + + {static} <> NormStreamOpen(sessionHandle:long, bufferSize:long, infoPtr:nint, infoLen:int) : long + + {static} <> NormStreamClose(streamHandle:long, graceful:bool) : void + + <> {static} <> NormStreamWrite(streamHandle:long, buffer:byte*, numBytes:int) : int + + {static} <> NormStreamFlush(streamHandle:long, eom:bool, flushMode:NormFlushMode) : void + + {static} <> NormStreamSetAutoFlush(streamHandle:long, flushMode:NormFlushMode) : void + + {static} <> NormStreamSetPushEnable(streamHandle:long, pushEnable:bool) : void + + {static} <> NormStreamHasVacancy(streamHandle:long) : bool + + {static} <> NormStreamMarkEom(streamHandle:long) : void + + {static} <> NormSetWatermark(sessionHandle:long, objectHandle:long, overrideFlush:bool) : bool + + {static} <> NormResetWatermark(sessionHandle:long) : bool + + {static} <> NormCancelWatermark(sessionHandle:long) : void + + {static} <> NormAddAckingNode(sessionHandle:long, nodeId:long) : bool + + {static} <> NormRemoveAckingNode(sessionHandle:long, nodeId:long) : void + + {static} <> NormGetAckingStatus(sessionHandle:long, nodeId:long) : NormAckingStatus + + {static} <> NormSendCommand(sessionHandle:long, cmdBuffer:nint, cmdLength:int, robust:bool) : bool + + {static} <> NormCancelCommand(sessionHandle:long) : void + + {static} <> NormStartReceiver(sessionHandle:long, bufferSpace:long) : bool + + {static} <> NormStopReceiver(sessionHandle:long) : void + + {static} <> NormSetRxCacheLimit(sessionHandle:long, countMax:int) : void + + {static} <> NormSetRxSocketBuffer(sessionHandle:long, bufferSize:long) : bool + + {static} <> NormSetSilentReceiver(sessionHandle:long, silent:bool, maxDelay:int) : void + + {static} <> NormSetDefaultUnicastNack(sessionHandle:long, unicastNacks:bool) : void + + {static} <> NormNodeSetUnicastNack(remoteSender:long, unicastNacks:bool) : void + + {static} <> NormSetDefaultSyncPolicy(sessionHandle:long, syncPolicy:NormSyncPolicy) : void + + {static} <> NormSetDefaultNackingMode(sessionHandle:long, nackingMode:NormNackingMode) : void + + {static} <> NormNodeSetNackingMode(remoteSender:long, nackingMode:NormNackingMode) : void + + {static} <> NormObjectSetNackingMode(objectHandle:long, nackingMode:NormNackingMode) : void + + {static} <> NormSetDefaultRepairBoundary(sessionHandle:long, repairBoundary:NormRepairBoundary) : void + + {static} <> NormNodeSetRepairBoundary(remoteSender:long, repairBoundary:NormRepairBoundary) : void + + {static} <> NormSetDefaultRxRobustFactor(sessionHandle:long, robustFactor:int) : void + + {static} <> NormNodeSetRxRobustFactor(remoteSender:long, robustFactor:int) : void + + <> {static} <> NormStreamRead(streamHandle:long, buffer:byte*, numBytes:int) : bool + + {static} <> NormStreamSeekMsgStart(streamHandle:long) : bool + + {static} <> NormStreamGetReadOffset(streamHandle:long) : long + + {static} <> NormObjectGetType(objectHandle:long) : NormObjectType + + {static} <> NormObjectHasInfo(objectHandle:long) : bool + + {static} <> NormObjectGetInfoLength(objectHandle:long) : int + + {static} <> NormObjectGetInfo(objectHandle:long, buffer:byte[], bufferLen:int) : int + + {static} <> NormObjectGetSize(objectHandle:long) : int + + {static} <> NormObjectGetBytesPending(objectHandle:long) : long + + {static} <> NormObjectCancel(objectHandle:long) : void + + {static} <> NormObjectRetain(objectHandle:long) : void + + {static} <> NormObjectRelease(objectHandle:long) : void + + <> {static} <> NormFileGetName(fileHandle:long, nameBuffer:sbyte*, bufferLen:int) : bool + + {static} <> NormFileRename(fileHandle:long, fileName:string) : bool + + {static} <> NormDataAccessData(objectHandle:long) : nint + + {static} <> NormObjectGetSender(objectHandle:long) : long + + {static} <> NormNodeGetId(nodeHandle:long) : long + + <> {static} <> NormNodeGetAddress(nodeHandle:long, addrBuffer:byte*, bufferLen:int, port:int) : bool + + {static} <> NormNodeGetGrtt(nodeHandle:long) : double + + <> {static} <> NormNodeGetCommand(remoteSender:long, cmdBuffer:byte*, buflen:int) : bool + + {static} <> NormNodeFreeBuffers(remoteSender:long) : void + + {static} <> NormNodeRetain(nodeHandle:long) : void + + {static} <> NormNodeRelease(nodeHandle:long) : void +} +struct NormEvent { + + Session : long + + Sender : long + + Object : long +} +NormApi +-- NormEvent +NormEvent --> "Type" NormEventType +@enduml diff --git a/src/dotnet/design/Mil/Navy/Nrl/Norm/NormData.puml b/src/dotnet/design/Mil/Navy/Nrl/Norm/NormData.puml new file mode 100644 index 00000000..24152066 --- /dev/null +++ b/src/dotnet/design/Mil/Navy/Nrl/Norm/NormData.puml @@ -0,0 +1,7 @@ +@startuml +class NormData { + + GetData() : byte[] + <> NormData(handle:long) +} +NormObject <|-- NormData +@enduml diff --git a/src/dotnet/design/Mil/Navy/Nrl/Norm/NormEvent.puml b/src/dotnet/design/Mil/Navy/Nrl/Norm/NormEvent.puml new file mode 100644 index 00000000..6d94d58f --- /dev/null +++ b/src/dotnet/design/Mil/Navy/Nrl/Norm/NormEvent.puml @@ -0,0 +1,14 @@ +@startuml +class NormEvent { + - _sessionHandle : long <> + - _nodeHandle : long <> + - _objectHandle : long <> + + NormEvent(type:NormEventType, sessionHandle:long, nodeHandle:long, objectHandle:long) + + <> ToString() : string +} +NormEvent --> "_type" NormEventType +NormEvent --> "Type" NormEventType +NormEvent --> "Session" NormSession +NormEvent --> "Node" NormNode +NormEvent --> "Object" NormObject +@enduml diff --git a/src/dotnet/design/Mil/Navy/Nrl/Norm/NormFile.puml b/src/dotnet/design/Mil/Navy/Nrl/Norm/NormFile.puml new file mode 100644 index 00000000..b2e2a9b1 --- /dev/null +++ b/src/dotnet/design/Mil/Navy/Nrl/Norm/NormFile.puml @@ -0,0 +1,9 @@ +@startuml +class NormFile { + + <> FILENAME_MAX : int = 260 + <> NormFile(handle:long) + + <> Name : string <> + + Rename(filePath:string) : void +} +NormObject <|-- NormFile +@enduml diff --git a/src/dotnet/design/Mil/Navy/Nrl/Norm/NormInstance.puml b/src/dotnet/design/Mil/Navy/Nrl/Norm/NormInstance.puml new file mode 100644 index 00000000..f9a66e74 --- /dev/null +++ b/src/dotnet/design/Mil/Navy/Nrl/Norm/NormInstance.puml @@ -0,0 +1,24 @@ +@startuml +class NormInstance { + + <> NORM_DESCRIPTOR_INVALID : int = 0 + - _handle : long + + NormInstance(priorityBoost:bool) + + NormInstance() + - CreateInstance(priorityBoost:bool) : void + + DestroyInstance() : void + + CreateSession(address:string, port:int, localNodeId:long) : NormSession + + HasNextEvent(sec:int, usec:int) : bool + + HasNextEvent(waitTime:TimeSpan) : bool + + GetNextEvent(waitForEvent:bool) : NormEvent? + + GetNextEvent() : NormEvent? + + SetCacheDirectory(cachePath:string) : void + + StopInstance() : void + + RestartInstance() : bool + + SuspendInstance() : bool + + ResumeInstance() : void + + OpenDebugLog(fileName:string) : void + + CloseDebugLog() : void + + OpenDebugPipe(pipename:string) : void + + DebugLevel : int <> <> +} +@enduml diff --git a/src/dotnet/design/Mil/Navy/Nrl/Norm/NormNode.puml b/src/dotnet/design/Mil/Navy/Nrl/Norm/NormNode.puml new file mode 100644 index 00000000..e583819f --- /dev/null +++ b/src/dotnet/design/Mil/Navy/Nrl/Norm/NormNode.puml @@ -0,0 +1,20 @@ +@startuml +class NormNode { + + <> NORM_NODE_ANY : long = 0xffffffff + + <> NORM_NODE_NONE : int = 0 + + <> NORM_NODE_INVALID : int = 0 + - _handle : long + <> NormNode(handle:long) + + Id : long <> + + Grtt : double <> + + GetCommand(buffer:byte[], offset:int, length:int) : int + + SetUnicastNack(state:bool) : void + + SetNackingMode(nackingMode:NormNackingMode) : void + + SetRepairBoundary(repairBoundary:NormRepairBoundary) : void + + SetRxRobustFactor(robustFactor:int) : void + + FreeBuffers() : void + + Retain() : void + + Release() : void +} +NormNode --> "Address" IPEndPoint +@enduml diff --git a/src/dotnet/design/Mil/Navy/Nrl/Norm/NormObject.puml b/src/dotnet/design/Mil/Navy/Nrl/Norm/NormObject.puml new file mode 100644 index 00000000..76960267 --- /dev/null +++ b/src/dotnet/design/Mil/Navy/Nrl/Norm/NormObject.puml @@ -0,0 +1,19 @@ +@startuml +class NormObject { + + <> NORM_OBJECT_INVALID : int = 0 + # _handle : long + <> NormObject(handle:long) + + Handle : long <> + + Info : byte[] <> + + Size : long <> + + Sender : long <> + + SetNackingMode(nackingMode:NormNackingMode) : void + + GetBytesPending() : long + + Cancel() : void + + Retain() : void + + Release() : void + + <> GetHashCode() : int + + <> Equals(obj:object?) : bool +} +NormObject --> "Type" NormObjectType +@enduml diff --git a/src/dotnet/design/Mil/Navy/Nrl/Norm/NormSession.puml b/src/dotnet/design/Mil/Navy/Nrl/Norm/NormSession.puml new file mode 100644 index 00000000..6a6c9ee9 --- /dev/null +++ b/src/dotnet/design/Mil/Navy/Nrl/Norm/NormSession.puml @@ -0,0 +1,79 @@ +@startuml +class NormSession { + + <> NORM_SESSION_INVALID : int = 0 + - _handle : long + + LocalNodeId : long <> + + ReportInterval : double <> <> + + TxRate : double <> <> + + GrttEstimate : double <> <> + <> NormSession(handle:long) + <> {static} GetSession(handle:long) : NormSession? + + DestroySession() : void + - DestroySessionNative() : void + + SetTxPort(port:int) : void + + SetTxPort(port:int, enableReuse:bool, txBindAddress:string?) : void + + SetTxOnly(txOnly:bool) : void + + SetTxOnly(txOnly:bool, connectToSessionAddress:bool) : void + + SetRxPortReuse(enable:bool) : void + + SetRxPortReuse(enable:bool, rxBindAddress:string?, senderAddress:string?, senderPort:int) : void + + SetEcnSupport(ecnEnable:bool, ignoreLoss:bool) : void + + SetEcnSupport(ecnEnable:bool, ignoreLoss:bool, tolerateLoss:bool) : void + + SetMulticastInterface(interfaceName:string) : void + + SetSSM(sourceAddress:string) : void + + SetTTL(ttl:byte) : void + + SetTOS(tos:byte) : void + + SetLoopback(loopbackEnable:bool) : void + + SetMessageTrace(flag:bool) : void + + SetTxLoss(precent:double) : void + + SetRxLoss(precent:double) : void + + SetFlowControl(precent:double) : void + + SetTxSocketBuffer(bufferSize:long) : void + + SetCongestionControl(enable:bool) : void + + SetCongestionControl(enable:bool, adjustRate:bool) : void + + SetTxRateBounds(rateMin:double, rateMax:double) : void + + SetTxCacheBounds(sizeMax:long, countMin:long, countMax:long) : void + + StartSender(sessionId:int, bufferSpace:long, segmentSize:int, blockSize:short, numParity:short, fecId:NormFecType) : void + + StartSender(sessionId:int, bufferSpace:long, segmentSize:int, blockSize:short, numParity:short) : void + + StartSender(bufferSpace:long, segmentSize:int, blockSize:short, numParity:short, fecId:NormFecType) : void + + StartSender(bufferSpace:long, segmentSize:int, blockSize:short, numParity:short) : void + + StopSender() : void + + FileEnqueue(filename:string) : NormFile + + FileEnqueue(filename:string, info:byte[]?, infoOffset:int, infoLength:int) : NormFile + + DataEnqueue(dataBuffer:SafeBuffer, dataOffset:int, dataLength:int) : NormData + + DataEnqueue(dataBuffer:SafeBuffer, dataOffset:int, dataLength:int, info:byte[]?, infoOffset:int, infoLength:int) : NormData + + DataEnqueue(dataPtr:nint, dataOffset:int, dataLength:int) : NormData + + DataEnqueue(dataPtr:nint, dataOffset:int, dataLength:int, info:byte[]?, infoOffset:int, infoLength:int) : NormData + + StreamOpen(bufferSize:long) : NormStream + + StreamOpen(bufferSize:long, info:byte[]?, infoOffset:int, infoLength:int) : NormStream + + StartReceiver(bufferSpace:long) : void + + StopReceiver() : void + + SetAutoParity(autoParity:short) : void + + SetGrttMax(grttMax:double) : void + + SetGrttProbingMode(probingMode:NormProbingMode) : void + + SetGrttProbingInterval(intervalMin:double, intervalMax:double) : void + + SetBackoffFactor(backoffFactor:double) : void + + SetGroupSize(groupSize:long) : void + + SetTxRobustFactor(txRobustFactor:int) : void + + RequeueObject(normObject:NormObject) : void + + SetWatermark(normObject:NormObject) : void + + SetWatermark(normObject:NormObject, overrideFlush:bool) : void + + CancelWatermark() : void + + ResetWatermark() : void + + AddAckingNode(nodeId:long) : void + + RemoveAckingNode(nodeId:long) : void + + GetAckingStatus(nodeId:long) : NormAckingStatus + + SendCommand(cmdBuffer:byte[], cmdOffset:int, cmdLength:int, robust:bool) : void + + CancelCommand() : void + + SetRxCacheLimit(countMax:int) : void + + SetRxSocketBuffer(bufferSize:long) : void + + SetSilentReceiver(silent:bool, maxDelay:int) : void + + SetDefaultUnicastNack(enable:bool) : void + + SetDefaultSyncPolicy(syncPolicy:NormSyncPolicy) : void + + SetDefaultNackingMode(nackingMode:NormNackingMode) : void + + SetDefaultRepairBoundary(repairBoundary:NormRepairBoundary) : void + + SetDefaultRxRobustFactor(rxRobustFactor:int) : void +} +class "Dictionary`2" { +} +NormSession o-> "_normSessions" "Dictionary`2" +@enduml diff --git a/src/dotnet/design/Mil/Navy/Nrl/Norm/NormStream.puml b/src/dotnet/design/Mil/Navy/Nrl/Norm/NormStream.puml new file mode 100644 index 00000000..95b215d2 --- /dev/null +++ b/src/dotnet/design/Mil/Navy/Nrl/Norm/NormStream.puml @@ -0,0 +1,18 @@ +@startuml +class NormStream { + + HasVacancy : bool <> + + ReadOffset : long <> + <> NormStream(handle:long) + + Write(buffer:byte[], offset:int, length:int) : int + + MarkEom() : void + + Flush(eom:bool, flushMode:NormFlushMode) : void + + Flush() : void + + Close(graceful:bool) : void + + Close() : void + + Read(buffer:byte[], offset:int, length:int) : int + + SeekMsgStart() : bool + + SetPushEnable(pushEnable:bool) : void + + SetAutoFlush(flushMode:NormFlushMode) : void +} +NormObject <|-- NormStream +@enduml diff --git a/src/dotnet/design/SequenceDiagrams/NormFileSend.puml b/src/dotnet/design/SequenceDiagrams/NormFileSend.puml new file mode 100644 index 00000000..4e24294a --- /dev/null +++ b/src/dotnet/design/SequenceDiagrams/NormFileSend.puml @@ -0,0 +1,49 @@ +@startuml NormFileSend +Client -> NormInstance : new() +NormInstance -> NormInstance : CreateInstance(false) +NormInstance -> NormApi : NormCreateInstance(false) +NormInstance <- NormApi : instanceHandle +Client <- NormInstance : NormInstance +Client -> NormInstance : CreateSession(address, port, localNodeId) +NormInstance -> NormApi : NormCreateSession(instanceHandle, address, port, localNodeId) +NormInstance <- NormApi : sessionHandle +alt sessionHandle == NormApi.NORM_SESSION_INVALID + NormInstance -> IOException : throw new("Failed to create session") +end +NormInstance -> NormSession : new(sessionHandle) +NormInstance <- NormSession : NormSession +Client <- NormInstance : NormSession +Client -> NormSession : StartSender(sessionId, bufferSpace, segmentSize, blockSize, numParity, fecId) +NormSession -> NormApi : NormStartSender(sessionHandle, sessionId, bufferSpace, segmentSize, blockSize, numParity, fecId) +NormSession <- NormApi : success +alt success == false + NormSession -> IOException : throw new("Failed to start sender") +end +Client -> NormSession : FileEnqueue(filename) +NormSession -> ASCII : GetBytes(filename) +NormSession <- ASCII : info +NormSession -> NormApi : NormFileEnqueue(sessionHandle, filename, info, info.Length) +NormSession <- NormApi : objectHandle +alt objectHandle == NormApi.NORM_OBJECT_INVALID + NormSession -> IOException : throw new("Failed to enqueue file") +end +NormSession -> NormFile: new(objectHandle) +NormSession <- NormFile : NormFile +Client <- NormSession : NormFile +loop NormInstance.HasNextEvent(waitTime) + Client -> NormInstance : GetNextEvent(false) + NormInstance -> NormApi : NormGetNextEvent(instanceHandle, NormEvent, false) + NormInstance <- NormApi : success + alt success == false + Client <- NormInstance : null + end + Client <- NormInstance : NormEvent +end +Client -> NormSession : StopSender(); +NormSession -> NormApi : NormStopSender(sessionHandle) +Client -> NormSession : DestroySession() +NormSession -> NormSession : DestroySessionNative() +NormSession -> NormApi : NormDestroySession(sessionHandle) +Client -> NormInstance : DestroyInstance() +NormInstance -> NormApi : NormDestroyInstance(instanceHandle) +@enduml \ No newline at end of file diff --git a/src/dotnet/pack-linux-x64.sh b/src/dotnet/pack-linux-x64.sh new file mode 100644 index 00000000..3a81a2c3 --- /dev/null +++ b/src/dotnet/pack-linux-x64.sh @@ -0,0 +1,12 @@ +#!/bin/bash +currenPath="$PWD" +cd ../../ +./waf +cd "$currenPath" +libPath="$currenPath/lib" +if [ ! -d "$libPath" ] +then + mkdir "$libPath" +fi +cp -f ../../build/libnorm.so "$libPath/norm.so" +dotnet pack . -c Release-linux-x64 \ No newline at end of file diff --git a/src/dotnet/pack-win-x64.bat b/src/dotnet/pack-win-x64.bat new file mode 100644 index 00000000..fc23a99a --- /dev/null +++ b/src/dotnet/pack-win-x64.bat @@ -0,0 +1,14 @@ +set currenPath=%cd% +cd ..\..\ +python .\waf configure --msvc_target=x64 +python .\waf +cd %currenPath% +if not exist lib\ ( + mkdir lib +) +copy ..\..\build\norm*.dll lib +cd lib +del /f norm.dll +ren norm*.dll norm.dll +cd %currenPath% +dotnet pack . -c Release-win-x64 \ No newline at end of file diff --git a/src/dotnet/pack-win-x86.bat b/src/dotnet/pack-win-x86.bat new file mode 100644 index 00000000..14895dba --- /dev/null +++ b/src/dotnet/pack-win-x86.bat @@ -0,0 +1,14 @@ +set currenPath=%cd% +cd ..\..\ +python .\waf configure --msvc_target=x86 +python .\waf +cd %currenPath% +if not exist lib\ ( + mkdir lib +) +copy ..\..\build\norm*.dll lib +cd lib +del /f norm.dll +ren norm*.dll norm.dll +cd %currenPath% +dotnet pack . -c Release-win-x86 \ No newline at end of file diff --git a/src/dotnet/src/Mil.Navy.Nrl.Norm.linux-x64/Mil.Navy.Nrl.Norm.linux-x64.csproj b/src/dotnet/src/Mil.Navy.Nrl.Norm.linux-x64/Mil.Navy.Nrl.Norm.linux-x64.csproj new file mode 100644 index 00000000..f3c039fc --- /dev/null +++ b/src/dotnet/src/Mil.Navy.Nrl.Norm.linux-x64/Mil.Navy.Nrl.Norm.linux-x64.csproj @@ -0,0 +1,22 @@ + + + + net6.0 + enable + enable + 1.0.0 + + + + + Always + runtimes/linux-x64/native + true + + + + + + + + diff --git a/src/dotnet/src/Mil.Navy.Nrl.Norm.win-x64/Mil.Navy.Nrl.Norm.win-x64.csproj b/src/dotnet/src/Mil.Navy.Nrl.Norm.win-x64/Mil.Navy.Nrl.Norm.win-x64.csproj new file mode 100644 index 00000000..38a65849 --- /dev/null +++ b/src/dotnet/src/Mil.Navy.Nrl.Norm.win-x64/Mil.Navy.Nrl.Norm.win-x64.csproj @@ -0,0 +1,22 @@ + + + + net6.0 + enable + enable + 1.0.0 + + + + + Always + runtimes/win-x64/native + true + + + + + + + + diff --git a/src/dotnet/src/Mil.Navy.Nrl.Norm.win-x86/Mil.Navy.Nrl.Norm.win-x86.csproj b/src/dotnet/src/Mil.Navy.Nrl.Norm.win-x86/Mil.Navy.Nrl.Norm.win-x86.csproj new file mode 100644 index 00000000..e44f7a95 --- /dev/null +++ b/src/dotnet/src/Mil.Navy.Nrl.Norm.win-x86/Mil.Navy.Nrl.Norm.win-x86.csproj @@ -0,0 +1,22 @@ + + + + net6.0 + enable + enable + 1.0.0 + + + + + Always + runtimes/win-x86/native + true + + + + + + + + diff --git a/src/dotnet/src/Mil.Navy.Nrl.Norm/Buffers/ByteBuffer.cs b/src/dotnet/src/Mil.Navy.Nrl.Norm/Buffers/ByteBuffer.cs new file mode 100644 index 00000000..d85303bd --- /dev/null +++ b/src/dotnet/src/Mil.Navy.Nrl.Norm/Buffers/ByteBuffer.cs @@ -0,0 +1,26 @@ +using System.Runtime.InteropServices; + +namespace Mil.Navy.Nrl.Norm.Buffers +{ + /// + /// A byte buffer + /// + public abstract class ByteBuffer : SafeBuffer + { + /// + /// Creates a new buffer + /// + protected ByteBuffer() : base(true) + { + } + + /// + /// Allocates a new direct byte buffer + /// + /// The new buffer's capacity, in bytes + /// The new byte buffer + public static ByteBuffer AllocateDirect(int capacity) { + return new DirectByteBuffer(capacity); + } + } +} \ No newline at end of file diff --git a/src/dotnet/src/Mil.Navy.Nrl.Norm/Buffers/DirectByteBuffer.cs b/src/dotnet/src/Mil.Navy.Nrl.Norm/Buffers/DirectByteBuffer.cs new file mode 100644 index 00000000..077a9a04 --- /dev/null +++ b/src/dotnet/src/Mil.Navy.Nrl.Norm/Buffers/DirectByteBuffer.cs @@ -0,0 +1,30 @@ +using System.Runtime.InteropServices; + +namespace Mil.Navy.Nrl.Norm.Buffers +{ + /// + /// A direct byte buffer + /// + internal sealed class DirectByteBuffer : ByteBuffer + { + /// + /// Creates a new direct byte buffer with given capacity + /// + /// The new buffer's capacity, in bytes + internal DirectByteBuffer(int capacity) + { + SetHandle(Marshal.AllocHGlobal(capacity)); + Initialize(Convert.ToUInt64(capacity)); + } + + /// + /// Executes the code required to free the handle + /// + /// true if the handle is released successfully; otherwise, in the event of a catastrophic failure, false + protected override bool ReleaseHandle() + { + Marshal.FreeHGlobal(handle); + return true; + } + } +} \ No newline at end of file diff --git a/src/dotnet/src/Mil.Navy.Nrl.Norm/Enums/NormAckingStatus.cs b/src/dotnet/src/Mil.Navy.Nrl.Norm/Enums/NormAckingStatus.cs new file mode 100644 index 00000000..2e98cc1f --- /dev/null +++ b/src/dotnet/src/Mil.Navy.Nrl.Norm/Enums/NormAckingStatus.cs @@ -0,0 +1,28 @@ +namespace Mil.Navy.Nrl.Norm.Enums +{ + /// + /// The status of the watermark flushing process and/or positive acknowledgment collection. + /// + public enum NormAckingStatus + { + /// + /// The given sessionHandle is invalid or the given nodeId is not in the sender's acking list. + /// + NORM_ACK_INVALID, + /// + /// The positive acknowledgement collection process did not receive acknowledgment from every + /// listed receiver (nodeId = NORM_NODE_ANY) or the identified nodeId did not respond. + /// + NORM_ACK_FAILURE, + /// + /// The flushing process at large has not yet completed (nodeId = NORM_NODE_ANY) or the given + /// individual nodeId is still being queried for response. + /// + NORM_ACK_PENDING, + /// + /// All receivers (nodeId = NORM_NODE_ANY) responded with positive acknowledgement or the given + /// specific nodeId did acknowledge. + /// + NORM_ACK_SUCCESS + } +} diff --git a/src/dotnet/src/Mil.Navy.Nrl.Norm/Enums/NormEventType.cs b/src/dotnet/src/Mil.Navy.Nrl.Norm/Enums/NormEventType.cs new file mode 100644 index 00000000..f968e06b --- /dev/null +++ b/src/dotnet/src/Mil.Navy.Nrl.Norm/Enums/NormEventType.cs @@ -0,0 +1,194 @@ +namespace Mil.Navy.Nrl.Norm.Enums +{ + /// + /// The type identifies the event with one of NORM protocol events. + /// + public enum NormEventType + { + /// + /// This NormEventType indicates an invalid or "null" notification which should be ignored. + /// + NORM_EVENT_INVALID, + /// + /// This event indicates that there is room for additional transmit objects to be enqueued, or, + /// if the handle of NORM_OBJECT_STREAM is given in the corresponding + /// event "object" field, the application may successfully write to the indicated stream + /// object. Note this event is not dispatched until a call to NormFileEnqueue(), + /// NormDataEnqueue(), or NormStreamWrite() fails because of a filled transmit + /// cache or stream buffer. + /// + NORM_TX_QUEUE_VACANCY, + /// + /// This event indicates the NORM protocol engine has no new data pending transmission + /// and the application may enqueue additional objects for transmission. + /// If the handle of a sender NORM_OBJECT_STREAM is given in the corresponding event + /// "object" field, this indicates the stream transmit buffer has been emptied and the + /// sender application may write to the stream (Use of NORM_TX_QUEUE_VACANCY may + /// be preferred for this purpose since it allows the application to keep the NORM + /// protocol engine busier sending data, resulting in higher throughput when attempting + /// very high transfer rates). + /// + NORM_TX_QUEUE_EMPTY, + /// + /// This event indicates that the flushing process the NORM sender observes when + /// it no longer has data ready for transmission has completed. The completion of the + /// flushing process is a reasonable indicator (with a sufficient NORM "robust factor" + /// value) that the receiver set no longer has any pending repair requests. Note the + /// use of NORM's optional positive acknowledgement feature is more deterministic + /// in this regards, but this notification is useful when there are non-acking (NACK-only) + /// receivers. The default NORM robust factor of 20 (20 flush messages are + /// sent at end-of-transmission) provides a high assurance of reliable transmission, + /// even with packet loss rates of 50%. + /// + NORM_TX_FLUSH_COMPLETED, + /// + /// This event indicates that the flushing process initiated by a prior application call + /// to NormSetWatermark() has completed. The posting of this event indicates the + /// appropriate time for the application to make a call NormGetAckingStatus() to + /// determine the results of the watermark flushing process. + /// + NORM_TX_WATERMARK_COMPLETED, + /// + /// This event indicates that an application-defined command previously enqueued + /// with a call to NormSendCommand() has been transmitted, including any repetition. + /// + NORM_TX_CMD_SENT, + /// + /// This event indicates that the transport object referenced by the event's "object" + /// field has completed at least one pass of total transmission. Note that this does not + /// guarantee that reliable transmission has yet completed; only that the entire object + /// content has been transmitted. Depending upon network behavior, several rounds + /// of NACKing and repair transmissions may be required to complete reliable transfer. + /// + NORM_TX_OBJECT_SENT, + /// + /// This event indicates that the NORM protocol engine will no longer refer to the + /// transport object identified by the event's "object" field. Typically, this will occur + /// when the application has enqueued more objects than space available within the + /// set sender transmit cache bounds. Posting of this notification means the application is + /// free to free any resources (memory, files, etc) associated with the indicated "object". + /// After this event, the given "object" handle (NormObjectHandle) is no longer valid unless + /// it is specifically retained by the application. + /// + NORM_TX_OBJECT_PURGED, + /// + /// This event indicates that NORM Congestion Control operation has adjusted the + /// transmission rate. The NormGetTxRate() call may be used to retrieve the new + /// corresponding transmission rate. Note that if NormSetCongestionControl() was + /// called with its adjustRate parameter set to false, then no actual rate change has + /// occurred and the rate value returned by NormGetTxRate() reflects a "suggested" + /// rate and not the actual transmission rate. + /// + NORM_TX_RATE_CHANGED, + /// + /// This event is posted when the NORM protocol engine completes the "graceful + /// shutdown" of its participation as a sender in the indicated "session". + /// + NORM_LOCAL_SENDER_CLOSED, + /// + /// This event is posted when a receiver first receives messages from a specific remote + /// NORM sender. This marks the beginning of the interval during which the application + /// may reference the provided "node" handle (NormNodeHandle). + /// + NORM_REMOTE_SENDER_NEW, + /// + /// Remote sender instanceId or FEC params changed. + /// + NORM_REMOTE_SENDER_RESET, + /// + /// Remote sender src addr and/or port changed. + /// + NORM_REMOTE_SENDER_ADDRESS, + /// + /// This event is posted when a previously inactive (or new) remote sender is detected + /// operating as an active sender within the session. + /// + NORM_REMOTE_SENDER_ACTIVE, + /// + /// This event is posted after a significant period of inactivity (no sender messages + /// received) of a specific NORM sender within the session. The NORM protocol + /// engine frees buffering resources allocated for this sender when it becomes inactive. + /// + NORM_REMOTE_SENDER_INACTIVE, + /// + /// This event is posted when the NORM protocol engine frees resources for, and + /// thus invalidates the indicated "node" handle. + /// + NORM_REMOTE_SENDER_PURGED, + /// + /// This event indicates that an application-defined command has been received from + /// a remote sender. The NormEvent node element indicates the NormNodeHandle + /// value associated with the given sender. The NormNodeGetCommand() call can be + /// used to retrieve the received command content. + /// + NORM_RX_CMD_NEW, + /// + /// This event is posted when reception of a new transport object begins and marks + /// the beginning of the interval during which the specified "object" (NormObjectHandle) + /// is valid. + /// + NORM_RX_OBJECT_NEW, + /// + /// This notification is posted when the NORM_INFO content for the indicated "object" is received. + /// + NORM_RX_OBJECT_INFO, + /// + /// This event indicates that the identified receive "object" has newly received data content. + /// + NORM_RX_OBJECT_UPDATED, + /// + /// This event is posted when a receive object is completely received, including + /// available NORM_INFO content. Unless the application specifically retains the "object" + /// handle, the indicated NormObjectHandle becomes invalid and must no longer be + /// referenced. + /// + NORM_RX_OBJECT_COMPLETED, + /// + /// This notification is posted when a pending receive object's transmission is aborted + /// by the remote sender. Unless the application specifically retains the "object" + /// handle, the indicated NormObjectHandle becomes invalid and must no longer be + /// referenced. + /// + NORM_RX_OBJECT_ABORTED, + /// + /// Upon receipt of app-extended watermark ack request. + /// + NORM_RX_ACK_REQUEST, + /// + /// This notification indicates that either the local sender estimate of GRTT has + /// changed, or that a remote sender's estimate of GRTT has changed. The "sender" + /// member of the NormEvent is set to NORM_NODE_INVALID if the local sender's + /// GRTT estimate has changed or to the NormNodeHandle of the remote sender that + /// has updated its estimate of GRTT + /// + NORM_GRTT_UPDATED, + /// + /// This event indicates that congestion control feedback from receivers has begun + /// to be received (This also implies that receivers in the group are actually present + /// and can be used as a cue to begin data transmission.). Note that congestion control + /// must be enabled for this event to be posted. + /// Congestion control feedback can be assumed to be received until a NORM_CC_INACTIVE event is posted. + /// + NORM_CC_ACTIVE, + /// + /// This event indicates there has been no recent congestion control feedback received + /// from the receiver set and that the local NORM sender has reached its minimum + /// transmit rate. Applications may wish to refrain from new data transmission until + /// a NORM_CC_ACTIVE event is posted. This notification is only posted when congestion + /// control operation is enabled and a previous NORM_CC_ACTIVE event has occurred. + /// + NORM_CC_INACTIVE, + /// + /// When NormSetAutoAcking. + /// + NORM_ACKING_NODE_NEW, + /// + /// ICMP error (e.g. destination unreachable). + /// + NORM_SEND_ERROR, + /// + /// Issues when timeout set by NormSetUserTimer() expires. + /// + NORM_USER_TIMEOUT + } +} diff --git a/src/dotnet/src/Mil.Navy.Nrl.Norm/Enums/NormFecType.cs b/src/dotnet/src/Mil.Navy.Nrl.Norm/Enums/NormFecType.cs new file mode 100644 index 00000000..6bb6133d --- /dev/null +++ b/src/dotnet/src/Mil.Navy.Nrl.Norm/Enums/NormFecType.cs @@ -0,0 +1,21 @@ +namespace Mil.Navy.Nrl.Norm.Enums +{ + /// + /// The valid FEC Types. + /// + public enum NormFecType + { + /// + /// Fully-specified, general purpose Reed-Solomon. + /// + RS, + /// + /// Fully-specified 8-bit Reed-Solmon per RFC 5510. + /// + RS8, + /// + /// Partially-specified "small block" codes. + /// + SB + } +} \ No newline at end of file diff --git a/src/dotnet/src/Mil.Navy.Nrl.Norm/Enums/NormFlushMode.cs b/src/dotnet/src/Mil.Navy.Nrl.Norm/Enums/NormFlushMode.cs new file mode 100644 index 00000000..2212a5cb --- /dev/null +++ b/src/dotnet/src/Mil.Navy.Nrl.Norm/Enums/NormFlushMode.cs @@ -0,0 +1,22 @@ +namespace Mil.Navy.Nrl.Norm.Enums +{ + /// + /// The possible flush modes. + /// + public enum NormFlushMode + { + /// + /// No flushing occurs unless explicitly requested via NormStreamFlush(). + /// + NORM_FLUSH_NONE, + /// + /// Causes NORM to immediately transmit all enqueued data for the stream (subject to session transmit rate limits), + /// even if this results in NORM_DATA messages with "small" payloads. + /// + NORM_FLUSH_PASSIVE, + /// + /// The sender actively transmits NORM_CMD(FLUSH) messages after any enqueued stream content has been sent. + /// + NORM_FLUSH_ACTIVE + } +} diff --git a/src/dotnet/src/Mil.Navy.Nrl.Norm/Enums/NormNackingMode.cs b/src/dotnet/src/Mil.Navy.Nrl.Norm/Enums/NormNackingMode.cs new file mode 100644 index 00000000..dadd8518 --- /dev/null +++ b/src/dotnet/src/Mil.Navy.Nrl.Norm/Enums/NormNackingMode.cs @@ -0,0 +1,21 @@ +namespace Mil.Navy.Nrl.Norm.Enums +{ + /// + /// The available nacking modes. + /// + public enum NormNackingMode + { + /// + /// Do not transmit any repair requests for the newly received object. + /// + NORM_NACK_NONE, + /// + /// Transmit repair requests for NORM_INFO content only as needed. + /// + NORM_NACK_INFO_ONLY, + /// + /// Transmit repair requests for entire object as needed. + /// + NORM_NACK_NORMAL + } +} diff --git a/src/dotnet/src/Mil.Navy.Nrl.Norm/Enums/NormObjectType.cs b/src/dotnet/src/Mil.Navy.Nrl.Norm/Enums/NormObjectType.cs new file mode 100644 index 00000000..5c571407 --- /dev/null +++ b/src/dotnet/src/Mil.Navy.Nrl.Norm/Enums/NormObjectType.cs @@ -0,0 +1,25 @@ +namespace Mil.Navy.Nrl.Norm.Enums +{ + /// + /// The possible NORM data transport object types. + /// + public enum NormObjectType + { + /// + /// A special NormObjectType value, NORM_OBJECT_NONE, indicates an invalid object type. + /// + NORM_OBJECT_NONE, + /// + /// A transport object of type NORM_OBJECT_DATA. + /// + NORM_OBJECT_DATA, + /// + /// A transport object of type NORM_OBJECT_FILE. + /// + NORM_OBJECT_FILE, + /// + /// A transport object of type NORM_OBJECT_STREAM. + /// + NORM_OBJECT_STREAM + } +} diff --git a/src/dotnet/src/Mil.Navy.Nrl.Norm/Enums/NormProbingMode.cs b/src/dotnet/src/Mil.Navy.Nrl.Norm/Enums/NormProbingMode.cs new file mode 100644 index 00000000..f48ebb36 --- /dev/null +++ b/src/dotnet/src/Mil.Navy.Nrl.Norm/Enums/NormProbingMode.cs @@ -0,0 +1,24 @@ +namespace Mil.Navy.Nrl.Norm.Enums +{ + /// + /// The possible probing modes. + /// + public enum NormProbingMode + { + /// + /// The sender application must explicitly set its estimate of GRTT using the NormSetGrttEstimate() function. + /// + NORM_PROBE_NONE, + /// + /// The NORM sender still transmits NORM_CMD(CC) probe messages multiplexed with its data transmission, + /// but the receiver set does not explicitly acknowledge these probes. Instead the receiver set is limited + /// to opportunistically piggy-backing responses when NORM_NACK messages are generated. + /// + NORM_PROBE_PASSIVE, + /// + /// In this mode, the receiver set explicitly acknowledges NORM sender GRTT probes ((NORM_CMD(CC) messages) + /// with NORM_ACK responses that are group-wise suppressed. + /// + NORM_PROBE_ACTIVE + } +} diff --git a/src/dotnet/src/Mil.Navy.Nrl.Norm/Enums/NormRepairBoundary.cs b/src/dotnet/src/Mil.Navy.Nrl.Norm/Enums/NormRepairBoundary.cs new file mode 100644 index 00000000..430fc883 --- /dev/null +++ b/src/dotnet/src/Mil.Navy.Nrl.Norm/Enums/NormRepairBoundary.cs @@ -0,0 +1,22 @@ +namespace Mil.Navy.Nrl.Norm.Enums +{ + /// + /// The possible for values for NORM repair boundary. + /// + /// + /// Customizes at what points the receiver initiates the NORM NACK repair process during protocol operation. + /// + public enum NormRepairBoundary + { + /// + /// For smaller block sizes, the NACK repair process is often/quickly initiated and the + /// repair of an object will occur, as needed, during the transmission of the object. + /// + NORM_BOUNDARY_BLOCK, + /// + /// Causes the protocol to defer NACK process initiation until the current transport object + /// has been completely transmitted. + /// + NORM_BOUNDARY_OBJECT + } +} diff --git a/src/dotnet/src/Mil.Navy.Nrl.Norm/Enums/NormSyncPolicy.cs b/src/dotnet/src/Mil.Navy.Nrl.Norm/Enums/NormSyncPolicy.cs new file mode 100644 index 00000000..deb54b54 --- /dev/null +++ b/src/dotnet/src/Mil.Navy.Nrl.Norm/Enums/NormSyncPolicy.cs @@ -0,0 +1,22 @@ +namespace Mil.Navy.Nrl.Norm.Enums +{ + /// + /// The possible synchronization policies. + /// + public enum NormSyncPolicy + { + /// + /// Attempt reception of "current" and new objects only (default). + /// + NORM_SYNC_CURRENT, + /// + /// Sync to current stream, but to beginning of stream. + /// + NORM_SYNC_STREAM, + /// + /// Attempt recovery and reliable reception of all objects + /// held in sender transmit object cache and newer objects. + /// + NORM_SYNC_ALL + } +} diff --git a/src/dotnet/src/Mil.Navy.Nrl.Norm/IO/INormEventListener.cs b/src/dotnet/src/Mil.Navy.Nrl.Norm/IO/INormEventListener.cs new file mode 100644 index 00000000..d5fea394 --- /dev/null +++ b/src/dotnet/src/Mil.Navy.Nrl.Norm/IO/INormEventListener.cs @@ -0,0 +1,7 @@ +namespace Mil.Navy.Nrl.Norm.IO +{ + public interface INormEventListener + { + void NormEventOccurred(NormEvent normEvent); + } +} \ No newline at end of file diff --git a/src/dotnet/src/Mil.Navy.Nrl.Norm/IO/NormInputStream.cs b/src/dotnet/src/Mil.Navy.Nrl.Norm/IO/NormInputStream.cs new file mode 100644 index 00000000..0272df91 --- /dev/null +++ b/src/dotnet/src/Mil.Navy.Nrl.Norm/IO/NormInputStream.cs @@ -0,0 +1,332 @@ +using System.Runtime.CompilerServices; + +namespace Mil.Navy.Nrl.Norm.IO +{ + public class NormInputStream : Stream + { + private NormInstance _normInstance; + private NormSession _normSession; + private NormStream? _normStream; + + private List _normEventListeners; + + private bool _closed; + private object _closeLock; + + private bool _bufferIsEmpty; + private bool _receivedEof; + + /// + public NormInputStream(string address, int port) + { + // Create the NORM instance + _normInstance = new NormInstance(); + + // Create the NORM session + _normSession = _normInstance.CreateSession(address, port, NormNode.NORM_NODE_ANY); + _normStream = null; + + _normEventListeners = new List(); + + _closed = true; + _closeLock = new object(); + + _bufferIsEmpty = true; + _receivedEof = false; + } + + /// + [MethodImpl(MethodImplOptions.Synchronized)] + public void OpenDebugLog(string fileName) + { + if (fileName == null) + { + throw new IOException("File was name was not found."); + } + _normInstance.OpenDebugLog(fileName); + } + + [MethodImpl(MethodImplOptions.Synchronized)] + public void CloseDebugLog() => _normInstance.CloseDebugLog(); + + [MethodImpl(MethodImplOptions.Synchronized)] + public void NormSetDebugLevel(int level) { _normInstance.DebugLevel = level; } + + [MethodImpl(MethodImplOptions.Synchronized)] + public void SetMessageTrace(bool messageTrace) => _normSession.SetMessageTrace(messageTrace); + + /// + [MethodImpl(MethodImplOptions.Synchronized)] + public void SetMulticastInterface(string multicastInterface) + { + _normSession.SetMulticastInterface(multicastInterface); + } + + /// + [MethodImpl(MethodImplOptions.Synchronized)] + public void SetEcnSupport(bool ecnEnable, bool ignoreLoss) + { + _normSession.SetEcnSupport(ecnEnable, ignoreLoss); + } + + /// + [MethodImpl(MethodImplOptions.Synchronized)] + public void SetTtl(byte ttl) + { + _normSession.SetTTL(ttl); + } + + /// + [MethodImpl(MethodImplOptions.Synchronized)] + public void SetTos(byte tos) + { + _normSession.SetTOS(tos); + } + + [MethodImpl(MethodImplOptions.Synchronized)] + public void setSilentReceiver(bool silent, int maxDelay) + { + _normSession.SetSilentReceiver(silent, maxDelay); + } + + [MethodImpl(MethodImplOptions.Synchronized)] + public void SetDefaultUnicastNack(bool defaultUnicastNack) + { + _normSession.SetDefaultUnicastNack(defaultUnicastNack); + } + + /// + [MethodImpl(MethodImplOptions.Synchronized)] + public void SeekMsgStart() + { + if (_normStream == null) + { + throw new InvalidOperationException("Can only seek msg start after the stream is connected"); + } + + _normStream.SeekMsgStart(); + } + + /// The INormEventListener to add. + [MethodImpl(MethodImplOptions.Synchronized)] + public void AddNormEventListener(INormEventListener normEventListener) + { + lock (_normEventListeners) + { + _normEventListeners.Add(normEventListener); + } + } + + /// The INormEventListener to remove. + [MethodImpl(MethodImplOptions.Synchronized)] + public void RemoveNormEventListener(INormEventListener normEventListener) + { + lock (_normEventListeners) + { + _normEventListeners.Remove(normEventListener); + } + } + + [MethodImpl(MethodImplOptions.Synchronized)] + private void FireNormEventOccured(NormEvent normEvent) + { + lock (_normEventListeners) + { + foreach (var normEventListener in _normEventListeners) + { + normEventListener.NormEventOccurred(normEvent); + } + + } + } + + /// + [MethodImpl(MethodImplOptions.Synchronized)] + public void Open(long bufferSpace) + { + lock (_closeLock) + { + if (!IsClosed) + { + throw new IOException("Stream is already open"); + } + + _normSession.StartReceiver(bufferSpace); + + _closed = false; + } + } + + /// + public override void Close() + { + lock (_closeLock) + { + if (IsClosed) + { + return; + } + + _normStream?.Close(false); + _normSession.StopSender(); + _normInstance.StopInstance(); + + _closed = true; + } + } + + public bool IsClosed + { + get + { + lock (_closeLock) + { + return _closed; + } + } + } + + /// + [MethodImpl(MethodImplOptions.Synchronized)] + public int Read() + { + var buffer = new byte[1]; + + if (IsClosed) + { + throw new IOException("Stream is closed"); + } + + if (Read(buffer) < 0) + { + return -1; + } + + return buffer[0]; + } + + /// + [MethodImpl(MethodImplOptions.Synchronized)] + public override int Read(byte[] buffer, int offset, int count) + { + int n; + + if (IsClosed) + { + throw new IOException("Stream is closed"); + } + + do + { + while (_bufferIsEmpty || _normInstance.HasNextEvent(0, 0)) + { + ProcessEvent(); + + if (_receivedEof) + { + return -1; + } + } + + if (_normStream == null) + { + return -1; + } + + // Read from the stream + if ((n = _normStream.Read(buffer, offset, count)) < 0) + { + throw new IOException("Break in stream integrity"); + } + + _bufferIsEmpty = n == 0; + } + while (_bufferIsEmpty); + + return n; + } + + private void ProcessEvent() + { + // Retrieve the next event + var normEvent = _normInstance.GetNextEvent(); + + // Check if the stream was closed + if (IsClosed) + { + throw new IOException("Stream closed"); + } + + if (normEvent != null) + { + // Process the event + var eventType = normEvent.Type; + switch (eventType) + { + case NormEventType.NORM_RX_OBJECT_NEW: + var normObject = normEvent.Object; + if (normObject != null && normObject.Type == NormObjectType.NORM_OBJECT_STREAM) + { + _normStream = (NormStream)normObject; + } + break; + + case NormEventType.NORM_RX_OBJECT_UPDATED: + var theNormObject = normEvent.Object; + if (theNormObject == null || !theNormObject.Equals(_normStream)) + { + break; + } + + // Signal that the buffer is not empty + _bufferIsEmpty = false; + break; + + case NormEventType.NORM_RX_OBJECT_ABORTED: + case NormEventType.NORM_RX_OBJECT_COMPLETED: + _normStream = null; + + // Signal that the stream has ended + _receivedEof = true; + break; + + default: + break; + } + + // Notify listeners of the norm event + FireNormEventOccured(normEvent); + } + } + + public override void Flush() + { + throw new NotSupportedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override bool CanRead => true; + + public override bool CanSeek => false; + + public override bool CanWrite => false; + + public override long Length => throw new NotSupportedException(); + + public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } + } +} \ No newline at end of file diff --git a/src/dotnet/src/Mil.Navy.Nrl.Norm/IO/NormOutputStream.cs b/src/dotnet/src/Mil.Navy.Nrl.Norm/IO/NormOutputStream.cs new file mode 100644 index 00000000..5c4481d2 --- /dev/null +++ b/src/dotnet/src/Mil.Navy.Nrl.Norm/IO/NormOutputStream.cs @@ -0,0 +1,392 @@ +using System.Runtime.CompilerServices; + +namespace Mil.Navy.Nrl.Norm.IO +{ + public class NormOutputStream : Stream + { + private NormInstance _normInstance; + private NormSession _normSession; + private NormStream? _normStream; + + private List _normEventListeners; + + private bool _closed; + private object _closeLock; + + private bool _bufferIsFull; + + /// + public NormOutputStream(string address, int port) + { + // Create the NORM instance + _normInstance = new NormInstance(); + + // Create the NORM session + _normSession = _normInstance.CreateSession(address, port, NormNode.NORM_NODE_ANY); + + _normStream = null; + + _normEventListeners = new List(); + + _closed = true; + _closeLock = new object(); + + _bufferIsFull = false; + } + + /// + [MethodImpl(MethodImplOptions.Synchronized)] + public void OpenDebugLog(string filename) + { + _normInstance.OpenDebugLog(filename); + } + + [MethodImpl(MethodImplOptions.Synchronized)] + public void CloseDebugLog() + { + _normInstance.CloseDebugLog(); + } + + [MethodImpl(MethodImplOptions.Synchronized)] + public void SetDebugLevel(int level) + { + _normInstance.DebugLevel = level; + } + + [MethodImpl(MethodImplOptions.Synchronized)] + public void SetMessageTrace(bool messageTrace) + { + _normSession.SetMessageTrace(messageTrace); + } + + /// + [MethodImpl(MethodImplOptions.Synchronized)] + public void SetMulticastInterface(string multicastInterface) + { + _normSession.SetMulticastInterface(multicastInterface); + } + + /// + [MethodImpl(MethodImplOptions.Synchronized)] + public void SetEcnSupport(bool ecnEnable, bool ignoreLoss) + { + _normSession.SetEcnSupport(ecnEnable, ignoreLoss); + } + + /// + [MethodImpl(MethodImplOptions.Synchronized)] + public void SetTtl(byte ttl) + { + _normSession.SetTTL(ttl); + } + + /// + [MethodImpl(MethodImplOptions.Synchronized)] + public void SetTos(byte tos) + { + _normSession.SetTOS(tos); + } + + [MethodImpl(MethodImplOptions.Synchronized)] + public void SetCongestionControl(bool ccEnabled, bool ccAdjustRate) + { + _normSession.SetCongestionControl(ccEnabled, ccAdjustRate); + } + + [MethodImpl(MethodImplOptions.Synchronized)] + public void SetTxRateBounds(double minTxRate, double maxTxRate) + { + _normSession.SetTxRateBounds(maxTxRate, minTxRate); + } + + public double TxRate + { + get + { + lock (this) + { + return _normSession.TxRate; + } + } + set + { + lock (this) + { + _normSession.TxRate = value; + } + } + } + + public double GrttEstimate + { + get + { + lock (this) + { + return _normSession.GrttEstimate; + } + } + set + { + lock (this) + { + _normSession.GrttEstimate = value; + } + } + } + + [MethodImpl(MethodImplOptions.Synchronized)] + public void SetGroupSize(long groupSize) + { + _normSession.SetGroupSize(groupSize); + } + + [MethodImpl(MethodImplOptions.Synchronized)] + public void SetAutoParity(short autoParity) + { + _normSession.SetAutoParity(autoParity); + } + + [MethodImpl(MethodImplOptions.Synchronized)] + public void SetBackoffFactor(double backoffFactor) + { + _normSession.SetBackoffFactor(backoffFactor); + } + + /// + [MethodImpl(MethodImplOptions.Synchronized)] + public void SetAutoFlush(NormFlushMode flushMode) + { + if (_normStream == null) + { + throw new InvalidOperationException("Can only set auto flush after the stream is open"); + } + _normStream.SetAutoFlush(flushMode); + } + + /// + [MethodImpl(MethodImplOptions.Synchronized)] + public void SetPushEnable(bool pushEnable) + { + if (_normStream == null) + { + throw new InvalidOperationException("Can only set push enabled after the stream is open"); + } + _normStream.SetPushEnable(pushEnable); + } + + /// + [MethodImpl(MethodImplOptions.Synchronized)] + public void MarkEom() + { + if (_normStream == null) + { + throw new InvalidOperationException("Can only mark EOM after the stream is open"); + } + _normStream.MarkEom(); + } + + /// The INormEventListener to add. + public void AddNormEventListener(INormEventListener normEventListener) + { + lock (_normEventListeners) + { + _normEventListeners.Add(normEventListener); + } + } + + /// The INormEventListener to remove. + public void RemoveNormEventListener(INormEventListener normEventListener) + { + lock (_normEventListeners) + { + _normEventListeners.Remove(normEventListener); + } + } + + private void FireNormEventOccured(NormEvent normEvent) + { + lock (_normEventListeners) + { + foreach (var normEventListener in _normEventListeners) + { + normEventListener.NormEventOccurred(normEvent); + } + } + } + + /// + public void Open(int sessionId, long bufferSpace, int segmentSize, short blockSize, short numParity, long repairWindow) + { + lock (_closeLock) + { + if (IsClosed) + { + throw new IOException("Stream is already open"); + } + + _normSession.StartSender(sessionId, bufferSpace, segmentSize, blockSize, numParity); + + // Open the stream + _normStream = _normSession.StreamOpen(repairWindow); + + _closed = false; + } + } + + /// + public override void Close() + { + lock (_closeLock) + { + if (IsClosed) + { + return; + } + + _normStream?.Close(false); + _normSession.StopSender(); + _normInstance.StopInstance(); + + _closed = true; + } + } + + public bool IsClosed + { + get + { + lock (_closeLock) + { + return _closed; + } + } + } + + /// + [MethodImpl(MethodImplOptions.Synchronized)] + public void Write(int b) + { + if (IsClosed) + { + throw new IOException("Stream is closed"); + } + + var buffer = new byte[1]; + buffer[0] = (byte)b; + + Write(buffer); + } + + /// + [MethodImpl(MethodImplOptions.Synchronized)] + public override void Write(byte[] buffer, int offset, int count) + { + int n; + + if (IsClosed) + { + throw new IOException("Stream is closed"); + } + + while (count > 0) + { + while (_normInstance.HasNextEvent(0, 0)) + { + ProcessEvent(); + } + + // Wait while the buffer is full + while (_bufferIsFull) + { + ProcessEvent(); + } + + // Write some data + if (_normStream == null || (n = _normStream.Write(buffer, offset, count)) < 0) + { + throw new IOException("Failed to write to stream"); + } + + _bufferIsFull = n == 0; + + count -= n; + offset += n; + } + } + + /// + private void ProcessEvent() + { + // Retrieve the next event + var normEvent = _normInstance.GetNextEvent(); + + // Check if the stream was closed + if (IsClosed) + { + throw new IOException("Stream closed"); + } + + if (normEvent != null) + { + // Process the event + var eventType = normEvent.Type; + switch (eventType) + { + case NormEventType.NORM_TX_QUEUE_VACANCY: + case NormEventType.NORM_TX_QUEUE_EMPTY: + var normObject = normEvent.Object; + if (normObject == null || !normObject.Equals(_normStream)) + { + break; + } + + // Signal that the buffer is not full + _bufferIsFull = false; + break; + + case NormEventType.NORM_TX_OBJECT_SENT: + case NormEventType.NORM_TX_OBJECT_PURGED: + _normStream = null; + break; + + default: + break; + } + + // Notify listeners of the norm event + FireNormEventOccured(normEvent); + } + } + + public override void Flush() + { + throw new NotSupportedException(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override bool CanRead => false; + + public override bool CanSeek => false; + + public override bool CanWrite => IsClosed; + + public override long Length => throw new NotSupportedException(); + + public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } + } +} \ No newline at end of file diff --git a/src/dotnet/src/Mil.Navy.Nrl.Norm/IO/StreamBreakException.cs b/src/dotnet/src/Mil.Navy.Nrl.Norm/IO/StreamBreakException.cs new file mode 100644 index 00000000..b4b6998e --- /dev/null +++ b/src/dotnet/src/Mil.Navy.Nrl.Norm/IO/StreamBreakException.cs @@ -0,0 +1,9 @@ +namespace Mil.Navy.Nrl.Norm.IO +{ + public class StreamBreakException : IOException + { + public StreamBreakException(string? message) : base(message) + { + } + } +} diff --git a/src/dotnet/src/Mil.Navy.Nrl.Norm/Mil.Navy.Nrl.Norm.csproj b/src/dotnet/src/Mil.Navy.Nrl.Norm/Mil.Navy.Nrl.Norm.csproj new file mode 100644 index 00000000..ef5a5fed --- /dev/null +++ b/src/dotnet/src/Mil.Navy.Nrl.Norm/Mil.Navy.Nrl.Norm.csproj @@ -0,0 +1,11 @@ + + + + net6.0 + enable + enable + true + 1.0.0 + + + diff --git a/src/dotnet/src/Mil.Navy.Nrl.Norm/NormApi.cs b/src/dotnet/src/Mil.Navy.Nrl.Norm/NormApi.cs new file mode 100644 index 00000000..341ccdaf --- /dev/null +++ b/src/dotnet/src/Mil.Navy.Nrl.Norm/NormApi.cs @@ -0,0 +1,1134 @@ +using System.Runtime.InteropServices; + +namespace Mil.Navy.Nrl.Norm +{ + /// + /// The native NORM API functions + /// + public static class NormApi + { + /// + /// The name of the NORM library used when calling native NORM API functions + /// + public const string NORM_LIBRARY = "norm"; + + /// + /// The NormEvent type is a structure used to describe significant NORM protocol events. + /// + [StructLayout(LayoutKind.Sequential)] + public struct NormEvent + { + /// + /// The Type field indicates the NormEventType and determines how the other fields should be interpreted. + /// + public NormEventType Type; + /// + /// The Session field indicates the applicable NormSessionHandle to which the event applies. + /// + public long Session; + /// + /// The Sender field indicates the applicable NormNodeHandle to which the event applies. + /// + public long Sender; + /// + /// The Object field indicates the applicable NormObjectHandle to which the event applies. + /// + public long Object; + } + + /// + /// This function creates an instance of a NORM protocol engine and is the necessary first step before any other API functions may be used. + /// + /// The priorityBoost parameter, when set to a value of true, specifies that the NORM protocol engine thread be run with higher priority scheduling. + /// A value of NORM_INSTANCE_INVALID is returned upon failure. + [DllImport(NORM_LIBRARY)] + public static extern long NormCreateInstance(bool priorityBoost); + + /// + /// The NormDestroyInstance() function immediately shuts down and destroys the NORM protocol engine instance referred to by the instanceHandle parameter. + /// + /// The NORM protocol engine instance referred to by the instanceHandle parameter. + [DllImport(NORM_LIBRARY)] + public static extern void NormDestroyInstance(long instanceHandle); + + /// + /// The NormStopInstance() this function immediately stops the NORM protocol engine thread corresponding to the given instanceHandle parameter. + /// + /// The NORM protocol engine instance referred to by the instanceHandle parameter. + [DllImport (NORM_LIBRARY)] + public static extern void NormStopInstance(long instanceHandle); + + /// + /// The NormRestartInstance() this function creates and starts an operating system thread to resume NORM protocol engine operation for the given + /// instanceHandle that was previously stopped by a call to NormStopInstance(). + /// + /// The NORM protocol engine instance referred to by the instanceHandle parameter. + /// Boolean as to the success of the instance restart. + [DllImport (NORM_LIBRARY)] + public static extern bool NormRestartInstance(long instanceHandle); + + /// + /// The NormSuspendInstance() immediately suspends the NORM protocol engine thread corresponding to the given instanceHandle parameter + /// + /// The NORM protocol engine instance referred to by the instanceHandle parameter. + /// Boolean as to the success of the instance suspension. + [DllImport (NORM_LIBRARY)] + public static extern bool NormSuspendInstance(long instanceHandle); + + /// + /// Resumes NORM protocol engine thread corresponding to the given instanceHandler parameter. + /// + /// The NORM protocol engine instance referred to by the instanceHandle parameter. + /// Boolean as to the success of the instance resumption. + [DllImport (NORM_LIBRARY)] + public static extern bool NormResumeInstance(long instanceHandle); + + /// + /// This function sets the directory path used by receivers to cache newly-received NORM_OBJECT_FILE content. + /// + /// The instanceHandle parameter specifies the NORM protocol engine instance + /// (all NormSessions associated with that instanceHandle share the same cache path). + /// the cachePath is a string specifying a valid (and writable) directory path. + /// The function returns true on success and false on failure. The failure conditions are + /// that the indicated directory does not exist or the process does not have permissions to write. + [DllImport(NORM_LIBRARY)] + public static extern bool NormSetCacheDirectory(long instanceHandle, string cachePath); + + /// + /// This function retrieves the next available NORM protocol event from the protocol engine. + /// + /// The instanceHandle parameter specifies the applicable NORM protocol engine. + /// The parameter must be a valid pointer to a NormEvent structure capable of receiving the NORM event information. + /// waitForEvent specifies whether the call to this function is blocking or not, + /// if "waitForEvent" is false, this is a non-blocking call. + /// The function returns true when a NormEvent is successfully retrieved, and false otherwise. + /// Note that a return value of false does not indicate an error or signify end of NORM operation. + [DllImport(NORM_LIBRARY)] + public static extern bool NormGetNextEvent(long instanceHandle, out NormEvent theEvent, bool waitForEvent); + + /// + /// This function is used to retrieve a NormDescriptor (Unix int file descriptor or Win32 HANDLE) suitable for + /// asynchronous I/O notification to avoid blocking calls to NormGetNextEvent(). + /// + /// The NORM protocol engine instance referred to by the instanceHandle parameter. + /// A NormDescriptor value is returned which is valid until a call to NormDestroyInstance() is made. + /// Upon error, a value of NORM_DESCRIPTOR_INVALID is returned. + [DllImport(NORM_LIBRARY)] + public static extern int NormGetDescriptor(long instanceHandle); + + /// + /// This function creates a NORM protocol session (NormSession) using the address (multicast or unicast) and port + /// parameters provided.While session state is allocated and initialized, active session participation does not begin + /// until a call is made to NormStartSender() and/or NormStartReceiver() to join the specified multicast group + /// (if applicable) and start protocol operation. + /// + /// Valid NormInstanceHandle previously obtained with a call to NormCreateInstance(). + /// Specified address determines the destination of NORM messages sent. + /// Valid, unused port number corresponding to the desired NORM session address. + /// Identifies the application's presence in the NormSession. + /// Returns a session handle. + [DllImport(NORM_LIBRARY)] + public static extern long NormCreateSession(long instanceHandle, string sessionAddress, int sessionPort, long localNodeId); + + /// + /// This function immediately terminates the application's participation in the NormSession and frees any resources used by that session. + /// + /// Used to identify application in the NormSession. + [DllImport(NORM_LIBRARY)] + public static extern void NormDestroySession(long sessionHandle); + + /// + /// This function retrieves the NormNodeId value used for the application's participation in the NormSession. + /// + /// Used to identify application in the NormSession. + /// The returned value indicates the NormNode identifier used by the NORM protocol engine for the local application's participation in the specified NormSession. + [DllImport(NORM_LIBRARY)] + public static extern long NormGetLocalNodeId(long sessionHandle); + + /// + /// This function is used to force NORM to use a specific port number for UDP packets sent for the specified sessionHandle. + /// + /// Used to identify application in the NormSession. + /// The txPortNumber parameter, specifies which port number to use. + /// The enableReuse parameter, when set to true, allows that the specified port may be reused for multiple sessions. + /// The txBindAddress parameter allows specification of a specific source address binding for packet transmission. + /// This function returns true upon success and false upon failure. Failure will occur if a txBindAddress is providedthat does not + /// correspond to a valid, configured IP address for the local host system. + [DllImport(NORM_LIBRARY)] + public static extern bool NormSetTxPort(long sessionHandle, int txPortNumber, bool enableReuse, string? txBindAddress); + + /// + /// This function limits the NormSession to perform NORM sender functions only. + /// + /// Used to identify application in the NormSession. + /// Boolean specifing whether to turn on or off the txOnly operation. + /// The optional connectToSessionAddress parameter, when set to true, + /// causes the underlying NORM code to "connect()" the UDP socket to the session (remote receiver) address and port number. + [DllImport(NORM_LIBRARY)] + public static extern void NormSetTxOnly(long sessionHandle, bool txOnly, bool connectToSessionAddress); + + /// + /// This function allows the user to control the port reuse and binding behavior for the receive socket used for the given NORM sessionHandle. + /// + /// Used to identify application in the NormSession. + /// When the enablReuse parameter is set to true, reuse of the NormSession port number by multiple NORM instances or sessions is enabled. + /// If the optional rxBindAddress is supplied (an IP address or host name in string form), the socket will bind() to the given address + /// when it is opened in a call to NormStartReceiver() or NormStartSender(). + /// The optional senderAddress parameter can be used to connect() the underlying NORM receive socket to specific address. + /// The optional senderPort parameter can be used to connect() the underlying NORM receive socket to specific port. + [DllImport(NORM_LIBRARY)] + public static extern void NormSetRxPortReuse(long sessionHandle, bool enableReuse, string? rxBindAddress, string? senderAddress, int senderPort); + + /// Used to identify application in the NormSession. + /// Enables NORM ECN (congestion control) support. + /// With "ecnEnable", use ECN-only, ignoring packet loss. + /// Loss-tolerant congestion control, ecnEnable or not. + [DllImport(NORM_LIBRARY)] + public static extern void NormSetEcnSupport(long sessionHandle, bool ecnEnable, bool ignoreLoss, bool tolerateLoss); + + /// + /// This function specifies which host network interface is used for IP Multicast transmissions and group membership. + /// This should be called before any call to NormStartSender() or NormStartReceiver() is made so that the IP multicast group is joined on the proper host interface. + /// + /// Used to identify application in the NormSession. + /// Name of the interface + /// A return value of true indicates success while a return value of false indicates that the specified interface was + /// invalid. This function will always return true if made before calls to NormStartSender() or NormStartReceiver(). + /// However, those calls may fail if an invalid interface was specified with the call described here. + [DllImport(NORM_LIBRARY)] + public static extern bool NormSetMulticastInterface(long sessionHandle, string interfaceName); + + /// + /// This function sets the source address for Source-Specific Multicast (SSM) operation. + /// + /// Used to identify application in the NormSession. + /// Address to be set as source for source-specific multicast operation. + /// A return value of true indicates success while a return value of false indicates that the specified source address + /// was invalid. Note that if a valid IP address is specified but is improper for SSM (e.g., an IP multicast address) the + /// later calls to NormStartSender() or NormStartReceiver() may fail. + [DllImport(NORM_LIBRARY)] + public static extern bool NormSetSSM(long sessionHandle, string sourceAddress); + + /// + /// This function specifies the time-to-live (ttl) for IP Multicast datagrams generated by NORM for the specified + /// sessionHandle. The IP TTL field limits the number of router "hops" that a generated multicast packet may traverse + /// before being dropped. + /// + /// Used to identify application in the NormSession. + /// If TTL is equal to one, the transmissions will be limited to the local area network + /// (LAN) of the host computers network interface. Larger TTL values should be specified to span large networks. + /// A return value of true indicates success while a return value of false indicates that the specified ttl could not + /// be set. This function will always return true if made before calls to NormStartSender() or NormStartReceiver(). + /// However, those calls may fail if the desired ttl value cannot be set. + [DllImport(NORM_LIBRARY)] + public static extern bool NormSetTTL(long sessionHandle, byte ttl); + + /// + /// This function specifies the type-of-service (tos) field value used in IP Multicast datagrams generated by NORM for the specified sessionHandle. + /// + /// Used to identify application in the NormSession. + /// The IP TOS field value can be used as an indicator that a "flow" of packets may merit special + /// Quality-of-Service (QoS) treatment by network devices. + /// Users should refer to applicable QoS information for their network to determine the expected interpretation and + /// treatment (if any) of packets with explicit TOS marking. + /// A return value of true indicates success while a return value of false indicates that the specified tos could not + /// be set. This function will always return true if made before calls to NormStartSender() or NormStartReceiver(). + /// However, those calls may fail if the desired tos value cannot be set. + [DllImport(NORM_LIBRARY)] + public static extern bool NormSetTOS(long sessionHandle, byte tos); + + /// + /// This function enables or disables loopback operation for the indicated NORM sessionHandle. + /// + /// Used to identify application in the NormSession. + /// If loopback is set to true, loopback operation is enabled which allows the application to receive its own message traffic. + /// Thus, an application which is both actively receiving and sending may receive its own transmissions. + /// A return value of true indicates success while a return value of false indicates that the loopback operation could not be set. + [DllImport(NORM_LIBRARY)] + public static extern bool NormSetLoopback(long sessionHandle, bool loopback); + + [DllImport(NORM_LIBRARY)] + public static extern void NormSetMessageTrace(long sessionHandle, bool flag); + + [DllImport(NORM_LIBRARY)] + public static extern void NormSetTxLoss(long sessionHandle, double precent); + + [DllImport(NORM_LIBRARY)] + public static extern void NormSetRxLoss(long sessionHandle, double precent); + + /// + /// This function allows NORM debug output to be directed to a file instead of the default STDERR. + /// + /// Used to identify application in the NormSession. + /// Full path and name of the debug log. + /// The function returns true on success. If the specified file cannot be opened a value of false is returned. + [DllImport(NORM_LIBRARY)] + public static extern bool NormOpenDebugLog(long instanceHandle, string path); + + /// + /// This function disables NORM debug output to be directed to a file instead of the default STDERR. + /// + /// Used to identify application in the NormSession. + /// The function returns true on success. + [DllImport(NORM_LIBRARY)] + public static extern bool NormCloseDebugLog(long instanceHandle); + + /// + /// This function allows NORM debug output to be directed to a named pipe. + /// + /// Used to identify application in the NormSession. + /// The debug pipe name. + /// The function returns true on success. + [DllImport(NORM_LIBRARY)] + public static extern bool NormOpenDebugPipe(long instanceHandle, string pipeName); + + /// + /// This function controls the verbosity of NORM debugging output. Higher values of level result in more detailed + /// output. The highest level of debugging is 12. The debug output consists of text written to STDOUT by default but + /// may be directed to a log file using the NormOpenDebugLog() function. + /// + /// + /// PROTOLIB DEBUG LEVELS: + /// PL_FATAL=0 - The FATAL level designates very severe error events that will presumably lead the application to abort. + /// PL_ERROR=1 - The ERROR level designates error events that might still allow the application to continue running. + /// PL_WARN=2 - The WARN level designates potentially harmful situations. + /// PL_INFO=3 - The INFO level designates informational messages that highlight the progress of the application at coarse-grained level. + /// PL_DEBUG=4 - The DEBUG level designates fine-grained informational events that are most useful to debug an application. + /// PL_TRACE=5 - The TRACE level designates finer-grained informational events than the DEBUG. + /// PL_DETAIL=6 - The TRACE level designates even finer-grained informational events than the DEBUG. + /// PL_MAX=7 - Turn all comments on. + /// PL_ALWAYS - Messages at this level are always printed regardless of debug level. + /// + [DllImport(NORM_LIBRARY)] + public static extern void NormSetDebugLevel(int level); + + /// + /// Returns the currently set debug level. + /// + /// Returns the currently set debug level. + [DllImport(NORM_LIBRARY)] + public static extern int NormGetDebugLevel(); + + [DllImport(NORM_LIBRARY)] + public static extern void NormSetReportInterval(long sessionHandle, double interval); + + [DllImport(NORM_LIBRARY)] + public static extern double NormGetReportInterval(long sessionHandle); + + [DllImport(NORM_LIBRARY)] + public static extern int NormGetRandomSessionId(); + + /// + /// The application's participation as a sender within a specified NormSession begins when this function is called. + /// + /// Valid NormSessionHandle previously obtained with a call to NormCreateSession() + /// Application-defined value used as the instance_id field of NORM sender messages for the application's participation within a session. + /// This specifies the maximum memory space (in bytes) the NORM protocol engine is allowed to use to buffer any sender calculated FEC segments and repair state for the session. + /// This parameter sets the maximum payload size (in bytes) of NORM sender messages (not including any NORM message header fields). + /// This parameter sets the number of source symbol segments (packets) per coding block, for the + /// systematic Reed-Solomon FEC code used in the current NORM implementation. + /// This parameter sets the maximum number of parity symbol segments (packets) the sender is willing to calculate per FEC coding block. + /// Sets the NormFecType. + /// A value of true is returned upon success and false upon failure. + [DllImport(NORM_LIBRARY)] + public static extern bool NormStartSender(long instanceHandle, int instanceId, long bufferSpace, int segmentSize, short numData, short numParity, NormFecType fecId); + + /// + /// This function terminates the application's participation in a NormSession as a sender. By default, the sender will + /// immediately exit the session identified by the sessionHandle parameter without notifying the receiver set of its intention. + /// + /// Used to identify application in the NormSession. + [DllImport(NORM_LIBRARY)] + public static extern void NormStopSender(long sessionHandle); + + /// + /// This function sets the transmission rate (in bits per second (bps)) limit used for NormSender transmissions for the given sessionHandle. + /// + /// Used to identify application in the NormSession. + /// Transmission rate. + [DllImport(NORM_LIBRARY)] + public static extern void NormSetTxRate(long sessionHandle, double rate); + + /// + /// This function retrieves the current sender transmission rate in units of bits per second (bps) for the given sessionHandle. + /// + /// Used to identify application in the NormSession. + /// This function returns the sender transmission rate in units of bits per second (bps). + [DllImport(NORM_LIBRARY)] + public static extern double NormGetTxRate(long sessionHandle); + + /// + /// This function can be used to set a non-default socket buffer size for the UDP socket used by the specified NORM sessionHandle for data transmission. + /// + /// Used to identify application in the NormSession. + /// The bufferSize parameter specifies the desired socket buffer size in bytes. + /// This function returns true upon success and false upon failure. Possible failure modes include an invalid sessionHandle parameter, + /// a call to NormStartReceiver() or NormStartSender() has not yet been made for the session, or an invalid bufferSize was given. + /// Note some operating systems may require additional system configuration to use non-standard socket buffer sizes. + [DllImport(NORM_LIBRARY)] + public static extern bool NormSetTxSocketBuffer(long sessionHandle, long bufferSize); + + /// + /// This function controls a scaling factor that is used for sender timer-based flow control for the the specified NORM + /// sessionHandle. Timer-based flow control works by preventing the NORM sender application from enqueueing + /// new transmit objects or stream data that would purge "old" objects or stream data when there has been recent + /// NACK activity for those old objects or data. + /// + /// Used to identify application in the NormSession. + /// The flowControlFactor is used to compute a delay time for when a sender buffered object (or block of stream + /// data) may be released (i.e. purged) after transmission or applicable NACKs reception. + [DllImport(NORM_LIBRARY)] + public static extern void NormSetFlowControl(long sessionHandle, double flowControlFactor); + + /// + /// This function enables (or disables) the NORM sender congestion control operation for the session designated by + /// the sessionHandle parameter. For best operation, this function should be called before the call to NormStartSender() is made, but congestion control operation can be dynamically enabled/disabled during the course + /// of sender operation. + /// + /// Used to identify application in the NormSession. + /// Specifies whether to enable or disable the NORM sender congestion control operation. + /// The rate set by NormSetTxRate() has no effect when congestion control operation is enabled, unless the adjustRate + /// parameter here is set to false. When the adjustRate parameter is set to false, the NORM Congestion Control + /// operates as usual, with feedback collected from the receiver set and the "current limiting receiver" identified, except + /// that no actual adjustment is made to the sender's transmission rate. + [DllImport(NORM_LIBRARY)] + public static extern void NormSetCongestionControl(long sessionHandle, bool enable, bool adjustRate); + + /// + /// This function sets the range of sender transmission rates within which the NORM congestion control algorithm is + /// allowed to operate for the given sessionHandle. + /// + /// Used to identify application in the NormSession. + /// rateMin corresponds to the minimum transmission rate (bps). + /// rateMax corresponds to the maximum transmission rate (bps). + [DllImport(NORM_LIBRARY)] + public static extern void NormSetTxRateBounds(long sessionHandle, double rateMin, double rateMax); + + /// + /// This function sets limits that define the number and total size of pending transmit objects a NORM sender will allow to be enqueued by the application. + /// + /// Used to identify application in the NormSession. + /// The sizeMax parameter sets the maximum total size, in bytes, of enqueued objects allowed. + /// The countMin parameter sets the minimum number of objects the application may enqueue, + /// regardless of the objects' sizes and the sizeMax value. + /// The countMax parameter sets a ceiling on how many objects may be enqueued, + /// regardless of their total sizes with respect to the sizeMax setting. + [DllImport(NORM_LIBRARY)] + public static extern void NormSetTxCacheBounds(long sessionHandle, long sizeMax, long countMin, long countMax); + + /// + /// This function sets the quantity of proactive "auto parity" NORM_DATA messages sent at the end of each FEC coding + /// block. By default (i.e., autoParity = 0), FEC content is sent only in response to repair requests (NACKs) from receivers. + /// + /// Used to identify application in the NormSession. + /// Setting a non-zero value for autoParity, the sender can automatically accompany each coding + /// block of transport object source data segments ((NORM_DATA messages) with the set number of FEC segments. + [DllImport(NORM_LIBRARY)] + public static extern void NormSetAutoParity(long sesssionHandle, short autoParity); + + /// + /// This function sets the sender's estimate of group round-trip time (GRTT) (in units of seconds) for the given NORM sessionHandle. + /// + /// Used to identify application in the NormSession. + /// group round-trip time + [DllImport(NORM_LIBRARY)] + public static extern void NormSetGrttEstimate(long sessionHandle, double grtt); + + /// + /// This function returns the sender's current estimate(in seconds) of group round-trip timing (GRTT) for the given NORM session. + /// + /// Used to identify application in the NormSession. + /// This function returns the current sender group round-trip timing (GRTT) estimate (in units of seconds). + /// A value of -1.0 is returned if an invalid session value is provided. + [DllImport(NORM_LIBRARY)] + public static extern double NormGetGrttEstimate(long sessionHandle); + + /// + /// This function sets the sender's maximum advertised GRTT value for the given NORM sessionHandle. + /// + /// Used to identify application in the NormSession. + /// The grttMax parameter, in units of seconds, limits the GRTT used by the group for scaling protocol timers, regardless + /// of larger measured round trip times. The default maximum for the NRL NORM library is 10 seconds. + [DllImport(NORM_LIBRARY)] + public static extern void NormSetGrttMax(long sessionHandle, double grttMax); + + /// + /// This function sets the sender's mode of probing for round trip timing measurement responses from the receiver set for the given NORM sessionHandle. + /// + /// Used to identify application in the NormSession. + /// Possible values for the probingMode parameter include NORM_PROBE_NONE, NORM_PROBE_PASSIVE, and NORM_PROBE_ACTIVE. + [DllImport(NORM_LIBRARY)] + public static extern void NormSetGrttProbingMode(long sesssionHandle, NormProbingMode probingMode); + + /// + /// This function controls the sender GRTT measurement and estimation process for the given NORM sessionHandle. + /// The NORM sender multiplexes periodic transmission of NORM_CMD(CC) messages with its ongoing data transmission + /// or when data transmission is idle.When NORM congestion control operation is enabled, these probes are sent + /// once per RTT of the current limiting receiver(with respect to congestion control rate). In this case the intervalMin + /// and intervalMax parameters (in units of seconds) control the rate at which the sender's estimate of GRTT is updated. + /// + /// Used to identify application in the NormSession. + /// At session start, the estimate is updated at intervalMin and the update interval time is doubled until intervalMax is reached. + /// At session start, the estimate is updated at intervalMin and the update interval time is doubled until intervalMax is reached. + [DllImport(NORM_LIBRARY)] + public static extern void NormSetGrttProbingInterval(long sessionHandle, double intervalMin, double intervalMax); + + /// + /// This function sets the sender's "backoff factor" for the given sessionHandle. + /// + /// Used to identify application in the NormSession. + /// The backoffFactor (in units of seconds) is used to scale various timeouts related to the NACK repair process. + [DllImport(NORM_LIBRARY)] + public static extern void NormSetBackoffFactor(long sessionHandle, double backoffFactor); + + /// + /// This function sets the sender's estimate of receiver group size for the given sessionHandle. + /// + /// Used to identify application in the NormSession. + /// The sender advertises its groupSize setting to the receiver group in NORM protocol message + /// headers that, in turn, use this information to shape the distribution curve of their random timeouts for the timer-based, + /// probabilistic feedback suppression technique used in the NORM protocol. + [DllImport(NORM_LIBRARY)] + public static extern void NormSetGroupSize(long sessionHandle, long groupSize); + + /// + /// This routine sets the "robustness factor" used for various NORM sender functions. These functions include the + /// number of repetitions of "robustly-transmitted" NORM sender commands such as NORM_CMD(FLUSH) or similar + /// application-defined commands, and the number of attempts that are made to collect positive acknowledgement + /// from receivers.These commands are distinct from the NORM reliable data transmission process, but play a role + /// in overall NORM protocol operation. + /// + /// Used to identify application in the NormSession. + /// The default txRobustFactor value is 20. This relatively large value makes + /// the NORM sender end-of-transmission flushing and positive acknowledgement collection functions somewhat immune from packet loss. + /// Setting txRobustFactor to a value of -1 makes the redundant transmission of these commands continue indefinitely until completion. + /// + [DllImport(NORM_LIBRARY)] + public static extern void NormSetTxRobustFactor(long sessionHandle, int txRobustFactor); + + /// + /// This function enqueues a file for transmission within the specified NORM sessionHandle. + /// + /// Used to identify application in the NormSession. + /// The fileName parameter specifies the path to the file to be transmitted. The NORM protocol engine + /// read and writes directly from/to file system storage for file transport, potentially providing for a very large virtual + /// "repair window" as needed for some applications. While relative paths with respect to the "current working directory" + /// may be used, it is recommended that full paths be used when possible. + /// The optional infoPtr and infoLen parameters + /// are used to associate NORM_INFO content with the sent transport object. The maximum allowed infoLen + /// corresponds to the segmentSize used in the prior call to NormStartSender(). The use and interpretation of the + /// NORM_INFO content is left to the application's discretion. + /// The optional infoPtr and infoLen parameters + /// are used to associate NORM_INFO content with the sent transport object. The maximum allowed infoLen + /// corresponds to the segmentSize used in the prior call to NormStartSender(). The use and interpretation of the + /// NORM_INFO content is left to the application's discretion + /// A NormObjectHandle is returned which the application may use in other NORM API calls as needed. + [DllImport(NORM_LIBRARY)] + public static extern long NormFileEnqueue(long sessionHandle, string fileName, nint infoPtr, int infoLen); + + /// + /// This function enqueues a segment of application memory space for transmission within the specified NORM sessionHandle. + /// + /// Used to identify application in the NormSession. + /// The dataPtr parameter must be a valid pointer to the area of application memory to be transmitted. + /// The dataLen parameter indicates the quantity of data to transmit. + /// The optional infoPtr and infoLen parameters + /// are used to associate NORM_INFO content with the sent transport object. The maximum allowed infoLen + /// corresponds to the segmentSize used in the prior call to NormStartSender(). The use and interpretation of the + /// NORM_INFO content is left to the application's discretion. + /// The optional infoPtr and infoLen parameters + /// are used to associate NORM_INFO content with the sent transport object. The maximum allowed infoLen + /// corresponds to the segmentSize used in the prior call to NormStartSender(). The use and interpretation of the + /// NORM_INFO content is left to the application's discretion + /// A NormObjectHandle is returned which the application may use in other NORM API calls as needed. + [DllImport(NORM_LIBRARY)] + public static extern long NormDataEnqueue(long sessionHandle, nint dataPtr, int dataLen, nint infoPtr, int infoLen); + + /// + /// This function allows the application to resend (or reset transmission of) a NORM_OBJECT_FILE or NORM_OBJECT_DATA + /// transmit object that was previously enqueued for the indicated sessionHandle. + /// + /// Used to identify application in the NormSession. + /// The objectHandle parameter must be a valid transmit NormObjectHandle that has not yet been "purged" + /// from the sender's transmit queue. + /// A value of true is returned upon success and a value of false is returned upon failure. + [DllImport(NORM_LIBRARY)] + public static extern bool NormRequeueObject(long sessionHandle, long objectHandle); + + /// + /// This function opens a NORM_OBJECT_STREAM sender object and enqueues it for transmission within the indicated sessionHandle. + /// + /// Used to identify application in the NormSession. + /// The bufferSize parameter controls the size of the stream's "repair window" + /// which limits how far back the sender will "rewind" to satisfy receiver repair requests. + /// Note that no data is sent until subsequent calls to NormStreamWrite() are made unless + /// NORM_INFO content is specified for the stream with the infoPtr and infoLen parameters. Example usage of + /// NORM_INFO content for NORM_OBJECT_STREAM might include application-defined data typing or other information + /// which will enable NORM receiver applications to properly interpret the received stream as it is being received. + /// Note that no data is sent until subsequent calls to NormStreamWrite() are made unless + /// NORM_INFO content is specified for the stream with the infoPtr and infoLen parameters. Example usage of + /// NORM_INFO content for NORM_OBJECT_STREAM might include application-defined data typing or other information + /// which will enable NORM receiver applications to properly interpret the received stream as it is being received. + /// A NormObjectHandle is returned which the application may use in other NORM API calls as needed. + [DllImport(NORM_LIBRARY)] + public static extern long NormStreamOpen(long sessionHandle, long bufferSize, nint infoPtr, int infoLen); + + /// + /// This function halts transfer of the stream specified by the streamHandle parameter and releases any resources + /// used unless the associated object has been explicitly retained by a call to NormObjectRetain(). + /// + /// The streamHandle parameter must be a valid transmit NormObjectHandle. + /// The optional graceful parameter, when + /// set to a value of true, may be used by NORM senders to initiate "graceful" shutdown of a transmit stream. + [DllImport(NORM_LIBRARY)] + public static extern void NormStreamClose(long streamHandle, bool graceful); + + /// + /// This function enqueues data for transmission within the NORM stream specified by the streamHandle parameter. + /// + /// The streamHandle parameter must be a valid transmit NormObjectHandle. + /// The buffer parameter must be a pointer to the data to be enqueued. + /// The numBytes parameter indicates the length of the data content. + /// This function returns the number of bytes of data successfully enqueued for NORM stream transmission. + [DllImport(NORM_LIBRARY)] + public unsafe static extern int NormStreamWrite(long streamHandle, byte* buffer, int numBytes); + + /// + /// This function causes an immediate "flush" of the transmit stream specified by the streamHandle parameter. + /// + /// The streamHandle parameter must be a valid transmit NormObjectHandle. + /// The optional eom parameter, when set to true, allows the sender application to mark an end-of-message indication + /// (see NormStreamMarkEom()) for the stream and initiate flushing in a single function call. + /// The default stream "flush" operation invoked via + /// NormStreamFlush() for flushMode equal to NORM_FLUSH_PASSIVE causes NORM to immediately transmit all + /// enqueued data for the stream(subject to session transmit rate limits), even if this results in NORM_DATA messages + /// with "small" payloads.If the optional flushMode parameter is set to NORM_FLUSH_ACTIVE, the application can + /// achieve reliable delivery of stream content up to the current write position in an even more proactive fashion.In + /// this case, the sender additionally, actively transmits NORM_CMD(FLUSH) messages after any enqueued stream + /// content has been sent. This immediately prompt receivers for repair requests which reduces latency of reliable + /// delivery, but at a cost of some additional messaging. Note any such "active" flush activity will be terminated upon + /// the next subsequent write to the stream.If flushMode is set to NORM_FLUSH_NONE, this call has no effect other than + /// the optional end-of-message marking described here + [DllImport(NORM_LIBRARY)] + public static extern void NormStreamFlush(long streamHandle, bool eom, NormFlushMode flushMode); + + /// + /// This function sets "automated flushing" for the NORM transmit stream indicated by the streamHandle parameter. + /// + /// The streamHandle parameter must be a valid transmit NormObjectHandle. + /// Possible values for the flushMode parameter include NORM_FLUSH_NONE, NORM_FLUSH_PASSIVE, and NORM_FLUSH_ACTIVE. + [DllImport(NORM_LIBRARY)] + public static extern void NormStreamSetAutoFlush(long streamHandle, NormFlushMode flushMode); + + /// + /// This function controls how the NORM API behaves when the application attempts to enqueue new stream data + /// for transmission when the associated stream's transmit buffer is fully occupied with data pending original or repair + /// transmission. + /// + /// The streamHandle parameter must be a valid transmit NormObjectHandle. + /// By default (pushEnable = false), a call to NormStreamWrite() will return a zero value under this + /// condition, indicating it was unable to enqueue the new data. However, if pushEnable is set to true for a given + /// streamHandle, the NORM protocol engine will discard the oldest buffered stream data(even if it is pending repair + /// transmission or has never been transmitted) as needed to enqueue the new data. + [DllImport(NORM_LIBRARY)] + public static extern void NormStreamSetPushEnable(long streamHandle, bool pushEnable); + + /// + /// This function can be used to query whether the transmit stream, specified by the streamHandle parameter, has + /// buffer space available so that the application may successfully make a call to NormStreamWrite(). + /// + /// The streamHandle parameter must be a valid transmit NormObjectHandle. + /// This function returns a value of true when there is transmit buffer space to which the application may write and false otherwise. + [DllImport(NORM_LIBRARY)] + public static extern bool NormStreamHasVacancy(long streamHandle); + + /// + /// This function allows the application to indicate to the NORM protocol engine that the last data successfully written + /// to the stream indicated by streamHandle corresponded to the end of an application-defined message boundary. + /// + /// The streamHandle parameter must be a valid transmit NormObjectHandle. + [DllImport(NORM_LIBRARY)] + public static extern void NormStreamMarkEom(long streamHandle); + + /// + /// This function specifies a "watermark" transmission point at which NORM sender protocol operation should perform + /// a flushing process and/or positive acknowledgment collection for a given sessionHandle. + /// + /// Used to identify application in the NormSession. + /// The objectHandle parameter must be a valid transmit NormObjectHandle that has not yet been "purged" from the sender's transmit queue. + /// The optional overrideFlush parameter, when set to true, causes the watermark acknowledgment process that is + /// established with this function call to potentially fully supersede the usual NORM end-of-transmission flushing + /// process that occurs.If overrideFlush is set and the "watermark" transmission point corresponds to the last + /// transmission that will result from data enqueued by the sending application, then the watermark flush completion + /// will terminate the usual flushing process + /// The function returns true upon successful establishment of the watermark point. The function may return false upon failure. + [DllImport(NORM_LIBRARY)] + public static extern bool NormSetWatermark(long sessionHandle, long objectHandle, bool overrideFlush); + + [DllImport(NORM_LIBRARY)] + public static extern bool NormResetWatermark(long sessionHandle); + + /// + /// This function cancels any "watermark" acknowledgement request that was previously set via the NormSetWatermark() function for the given sessionHandle. + /// + /// Used to identify application in the NormSession. + [DllImport(NORM_LIBRARY)] + public static extern void NormCancelWatermark(long sessionHandle); + + /// + /// When this function is called, the specified nodeId is added to the list of NormNodeId values (i.e., the "acking node" + /// list) used when NORM sender operation performs positive acknowledgement (ACK) collection for the specified sessionHandle. + /// + /// Used to identify application in the NormSession. + /// Identifies the application's presence in the NormSession. + /// The function returns true upon success and false upon failure. + [DllImport(NORM_LIBRARY)] + public static extern bool NormAddAckingNode(long sessionHandle, long nodeId); + + /// + /// This function deletes the specified nodeId from the list of NormNodeId values used when NORM sender operation + /// performs positive acknowledgement (ACK) collection for the specified sessionHandle. + /// + /// Used to identify application in the NormSession. + /// Identifies the application's presence in the NormSession. + [DllImport(NORM_LIBRARY)] + public static extern void NormRemoveAckingNode(long sessionHandle, long nodeId); + + /// + /// This function queries the status of the watermark flushing process and/or positive acknowledgment collection + /// initiated by a prior call to NormSetWatermark() for the given sessionHandle. + /// + /// Used to identify application in the NormSession. + /// Identifies the application's presence in the NormSession. + /// + /// Possible return values include: + /// NORM_ACK_INVALID - The given sessionHandle is invalid or the given nodeId is not in the sender's acking list. + /// NORM_ACK_FAILURE - The positive acknowledgement collection process did not receive acknowledgment from every listed receiver (nodeId = NORM_NODE_ANY) or the identified nodeId did not respond. + /// NORM_ACK_PENDING - The flushing process at large has not yet completed (nodeId = NORM_NODE_ANY) or the given individual nodeId is still being queried for response. + /// NORM_ACK_SUCCESS - All receivers (nodeId = NORM_NODE_ANY) responded with positive acknowledgement or the given specific nodeId did acknowledge. + /// + [DllImport(NORM_LIBRARY)] + public static extern NormAckingStatus NormGetAckingStatus(long sessionHandle, long nodeId); + + /// + /// This function enqueues a NORM application-defined command for transmission. + /// + /// Used to identify application in the NormSession. + /// The cmdBuffer parameter points to a buffer containing the application-defined command content + /// that will be contained in the NORM_CMD(APPLICATION) message payload. + /// The cmdLength indicates the length of this content (in bytes) and MUST be less than or equal + /// to the segmentLength value for the given session (see NormStartSender()). + /// The command is NOT delivered reliably, but can be optionally transmitted with repetition + /// (once per GRTT) according to the NORM transmit robust factor value (see NormSetTxRobustFactor()) for the given + /// session if the robust parameter is set to true. + /// The function returns true upon success. The function may fail, returning false, if the session is not set for sender + /// operation (see NormStartSender()), the cmdLength exceeds the configured session segmentLength, or a previously- + /// enqueued command has not yet been sent. + [DllImport(NORM_LIBRARY)] + public static extern bool NormSendCommand(long sessionHandle, nint cmdBuffer, int cmdLength, bool robust); + + /// + /// This function terminates any pending NORM_CMD(APPLICATION) transmission that was previously initiated with the NormSendCommand() call. + /// + /// Used to identify application in the NormSession. + [DllImport(NORM_LIBRARY)] + public static extern void NormCancelCommand(long sessionHandle); + + /// + /// This function initiates the application's participation as a receiver within the NormSession identified by the sessionHandle parameter. + /// + /// Used to identify application in the NormSession. + /// The bufferSpace parameter is used to set a limit on the amount of bufferSpace allocated + /// by the receiver per active NormSender within the session. + /// A value of true is returned upon success and false upon failure. + [DllImport(NORM_LIBRARY)] + public static extern bool NormStartReceiver(long sessionHandle, long bufferSpace); + + /// + /// This function ends the application's participation as a receiver in the NormSession specified by the session parameter. + /// + /// Used to identify application in the NormSession. + [DllImport(NORM_LIBRARY)] + public static extern void NormStopReceiver(long sessionHandle); + + /// + /// This function sets a limit on the number of outstanding (pending) NormObjects for which a receiver will keep state on a per-sender basis. + /// + /// Used to identify application in the NormSession. + /// Note that the value countMax sets a limit on the maximum consecutive range of objects that can be pending + [DllImport(NORM_LIBRARY)] + public static extern void NormSetRxCacheLimit(long sessionHandle, int countMax); + + /// + /// This function allows the application to set an alternative, non-default buffer size for the UDP socket used by the specified NORM sessionHandle for packet reception. + /// + /// Used to identify application in the NormSession. + /// The bufferSize parameter specifies the socket buffer size in bytes. + /// This function returns true upon success and false upon failure. + [DllImport(NORM_LIBRARY)] + public static extern bool NormSetRxSocketBuffer(long sessionHandle, long bufferSize); + + /// + /// This function provides the option to configure a NORM receiver application as a "silent receiver". This mode of + /// receiver operation dictates that the host does not generate any protocol messages while operating as a receiver + /// within the specified sessionHandle. + /// + /// Used to identify application in the NormSession. + /// SSetting the silent parameter to true enables silent receiver operation while + /// setting it to false results in normal protocol operation where feedback is provided as needed for reliability and + /// protocol operation. + /// When the maxDelay parameter is set to a non-negative value, the value determines the maximum number + /// of FEC coding blocks (according to a NORM sender's current transmit position) the receiver will cache an incompletely-received + /// FEC block before giving the application the (incomplete) set of received source segments. + [DllImport(NORM_LIBRARY)] + public static extern void NormSetSilentReceiver(long sessionHandle, bool silent, int maxDelay); + + /// + /// This function controls the default behavior determining the destination of receiver feedback messages generated + /// while participating in the session. + /// + /// Used to identify application in the NormSession. + /// If the unicastNacks parameter is true, "unicast NACKing" is enabled for new remote + /// senders while it is disabled for state equal to false. + [DllImport(NORM_LIBRARY)] + public static extern void NormSetDefaultUnicastNack(long sessionHandle, bool unicastNacks); + + /// + /// This function controls the destination address of receiver feedback messages generated in response to a specific + /// remote NORM sender corresponding to the remoteSender parameter. + /// + /// Used to specify the remote NORM sender. + /// If unicastNacks is true, "unicast NACKing" is enabled + /// while it is disabled for enable equal to false. + [DllImport(NORM_LIBRARY)] + public static extern void NormNodeSetUnicastNack(long remoteSender, bool unicastNacks); + + /// + /// This function sets the default "synchronization policy" used when beginning (or restarting) reception of objects + /// from a remote sender (i.e., "syncing" to the sender) for the given sessionHandle + /// + /// Used to identify application in the NormSession. + /// The "synchronization policy" + /// is the behavior observed by the receiver with regards to what objects it attempts to reliably receive (via transmissions + /// of Negative Acknowledgements to the sender(s) or group as needed). There are currently two synchronization policy types defined. + [DllImport(NORM_LIBRARY)] + public static extern void NormSetDefaultSyncPolicy(long sessionHandle, NormSyncPolicy syncPolicy); + + /// + /// This function sets the default "nacking mode" used when receiving objects for the given sessionHandle. + /// This allows the receiver application some control of its degree of participation in the repair process. By limiting receivers + /// to only request repair of objects in which they are really interested in receiving, some overall savings in unnecessary + /// network loading might be realized for some applications and users. + /// + /// Used to identify application in the NormSession. + /// Specifies the nacking mode. + [DllImport(NORM_LIBRARY)] + public static extern void NormSetDefaultNackingMode(long sessionHandle, NormNackingMode nackingMode); + + /// + /// This function sets the default "nacking mode" used for receiving new objects from a specific sender as identified + /// by the remoteSender parameter. + /// + /// Used to specify the remote NORM sender. + /// Specifies the nacking mode. + [DllImport(NORM_LIBRARY)] + public static extern void NormNodeSetNackingMode(long remoteSender, NormNackingMode nackingMode); + + /// + /// This function sets the "nacking mode" used for receiving a specific transport object as identified by the objectHandle parameter. + /// + /// Specifies the transport object. + /// Specifies the nacking mode. + [DllImport(NORM_LIBRARY)] + public static extern void NormObjectSetNackingMode(long objectHandle, NormNackingMode nackingMode); + + /// + /// This function allows the receiver application to customize, for a given sessionHandle, at what points the receiver + /// initiates the NORM NACK repair process during protocol operation. + /// + /// Used to identify application in the NormSession. + /// Specifies the repair boundary. + [DllImport(NORM_LIBRARY)] + public static extern void NormSetDefaultRepairBoundary(long sessionHandle, NormRepairBoundary repairBoundary); + + /// + /// This function allows the receiver application to customize, for the specific remote sender referenced by the remoteSender + /// parameter, at what points the receiver initiates the NORM NACK repair process during protocol operation. + /// + /// Used to specify the remote NORM sender. + /// Specifies the repair boundary. + [DllImport(NORM_LIBRARY)] + public static extern void NormNodeSetRepairBoundary(long remoteSender, NormRepairBoundary repairBoundary); + + /// + /// This routine controls how persistently NORM receivers will maintain state for sender(s) and continue to request + /// repairs from the sender(s) even when packet reception has ceased. + /// + /// Used to identify application in the NormSession. + /// The robustFactor value determines how + /// many times a NORM receiver will self-initiate NACKing (repair requests) upon cessation of packet reception from + /// a sender. The default value is 20. Setting rxRobustFactor to -1 will make the NORM receiver infinitely persistent + /// (i.e., it will continue to NACK indefinitely as long as it is missing data content). + [DllImport(NORM_LIBRARY)] + public static extern void NormSetDefaultRxRobustFactor(long sessionHandle, int robustFactor); + + /// + /// This routine sets the robustFactor as described in NormSetDefaultRxRobustFactor() for an individual remote + /// sender identified by the remoteSender parameter. + /// + /// Used to specify the remote NORM sender. + /// The robustFactor value determines how + /// many times a NORM receiver will self-initiate NACKing (repair requests) upon cessation of packet reception from + /// a sender. The default value is 20. Setting rxRobustFactor to -1 will make the NORM receiver infinitely persistent + /// (i.e., it will continue to NACK indefinitely as long as it is missing data content). + [DllImport(NORM_LIBRARY)] + public static extern void NormNodeSetRxRobustFactor(long remoteSender, int robustFactor); + + /// + /// This function can be used by the receiver application to read any available data from an incoming NORM stream. + /// + /// The streamHandle parameter here must correspond to a valid NormObjectHandle value provided during such a + /// prior NORM_RX_OBJECT_NEW notification. + /// The buffer parameter must be a pointer to an array where the received + /// data can be stored of a length as referenced by the numBytes pointer + /// Specifies the length of data. + /// This function normally returns a value of true. However, if a break in the integrity of the reliable received stream + /// occurs(or the stream has been ended by the sender), a value of false is returned to indicate the break. + [DllImport(NORM_LIBRARY)] + public unsafe static extern bool NormStreamRead(long streamHandle, byte* buffer, ref int numBytes); + + /// + /// This function advances the read offset of the receive stream referenced by the streamHandle parameter to align + /// with the next available message boundary + /// + /// The streamHandle parameter here must correspond to a valid NormObjectHandle value provided during such a + /// prior NORM_RX_OBJECT_NEW notification. + /// This function returns a value of true when start-of-message is found. The next call to NormStreamRead() will + /// retrieve data aligned with the message start. If no new message boundary is found in the buffered receive data for + /// the stream, the function returns a value of false. In this case, the application should defer repeating a call to this + /// function until a subsequent NORM_RX_OBJECT_UPDATE notification is posted. + [DllImport(NORM_LIBRARY)] + public static extern bool NormStreamSeekMsgStart(long streamHandle); + + /// + /// This function retrieves the current read offset value for the receive stream indicated by the streamHandle parameter. + /// + /// The streamHandle parameter here must correspond to a valid NormObjectHandle value provided during such a + /// prior NORM_RX_OBJECT_NEW notification. + /// This function returns the current read offset in bytes. The return value is undefined for sender streams. There is + /// no error result. + [DllImport(NORM_LIBRARY)] + public static extern long NormStreamGetReadOffset(long streamHandle); + + /// + /// This function can be used to determine the object type (NORM_OBJECT_DATA, NORM_OBJECT_FILE, or NORM_OBJECT_STREAM) for the + /// NORM transport object identified by the objectHandle parameter. + /// + /// The objectHandle must refer to a current, valid transport object. + /// This function returns the NORM object type. Valid NORM object types include NORM_OBJECT_DATA, NORM_OBJECT_FILE, + /// or NORM_OBJECT_STREAM. A type value of NORM_OBJECT_NONE will be returned for an objectHandle value of NORM_OBJECT_INVALID. + [DllImport(NORM_LIBRARY)] + public static extern NormObjectType NormObjectGetType(long objectHandle); + + /// + /// This function can be used to determine if the sender has associated any NORM_INFO content with the transport object + /// specified by the objectHandle parameter. + /// + /// The objectHandle must refer to a current, valid transport object. + /// A value of true is returned if NORM_INFO is (or will be) available for the specified transport object. A value of + /// false is returned otherwise. + [DllImport(NORM_LIBRARY)] + public static extern bool NormObjectHasInfo(long objectHandle); + + /// + /// This function can be used to determine the length of currently available NORM_INFO content (if any) associated + /// with the transport object referenced by the objectHandle parameter. + /// + /// The objectHandle must refer to a current, valid transport object. + /// The length of the NORM_INFO content, in bytes, of currently available for the specified transport object is returned. + /// A value of 0 is returned if no NORM_INFO content is currently available or associated with the object. + [DllImport(NORM_LIBRARY)] + public static extern int NormObjectGetInfoLength(long objectHandle); + + /// + /// This function copies any NORM_INFO content associated (by the sender application) with the transport object specified + /// by objectHandle into the provided memory space referenced by the buffer parameter. + /// + /// The objectHandle must refer to a current, valid transport object. + /// Provided memory space for the object info. + /// The bufferLen parameter indicates the length of the buffer space in bytes. + /// The actual length of currently available NORM_INFO content for the specified transport object is returned. This + /// function can be used to determine the length of NORM_INFO content for the object even if a NULL buffer value and + /// zero bufferLen is provided. A zero value is returned if NORM_INFO content has not yet been received (or is nonexistent) for the specified object. + [DllImport(NORM_LIBRARY)] + public static extern int NormObjectGetInfo(long objectHandle, [Out] byte[] buffer, int bufferLen); + + /// + /// This function can be used to determine the size (in bytes) of the transport object specified by the objectHandle parameter. + /// + /// The objectHandle must refer to a current, valid transport object. + /// A size of the data content of the specified object, in bytes, is returned. Note that it may be possible that some objects + /// have zero data content, but do have NORM_INFO content available. + [DllImport(NORM_LIBRARY)] + public static extern int NormObjectGetSize(long objectHandle); + + /// + /// This function can be used to determine the progress of reception of the NORM transport object identified by the objectHandle parameter + /// + /// The objectHandle must refer to a current, valid transport object. + /// A number of object source data bytes pending reception (or transmission) is returned. + [DllImport(NORM_LIBRARY)] + public static extern long NormObjectGetBytesPending(long objectHandle); + + /// + /// This function immediately cancels the transmission of a local sender transport object or the reception of a specified + /// object from a remote sender as specified by the objectHandle parameter + /// + /// The objectHandle must refer to a current, valid transport object. + [DllImport(NORM_LIBRARY)] + public static extern void NormObjectCancel(long objectHandle); + + /// + /// This function "retains" the objectHandle and any state associated with it for further use by the application even + /// when the NORM protocol engine may no longer require access to the associated transport object. + /// + /// The objectHandle must refer to a current, valid transport object. + [DllImport(NORM_LIBRARY)] + public static extern void NormObjectRetain(long objectHandle); + + /// + /// This function complements the NormObjectRetain() call by immediately freeing any resources associated with + /// the given objectHandle, assuming the underlying NORM protocol engine no longer requires access to the corresponding + /// transport object. Note the NORM protocol engine retains/releases state for associated objects for its own + /// needs and thus it is very unsafe for an application to call NormObjectRelease() for an objectHandle for which + /// it has not previously explicitly retained via NormObjectRetain(). + /// + /// The objectHandle must refer to a current, valid transport object. + [DllImport(NORM_LIBRARY)] + public static extern void NormObjectRelease(long objectHandle); + + /// + /// This function copies the name, as a NULL-terminated string, of the file object specified by the objectHandle + /// parameter into the nameBuffer of length bufferLen bytes provided by the application. + /// + /// This type is used to reference state kept for data transport objects being actively transmitted or received. The objectHandle parameter + /// must refer to a valid NormObjectHandle for an object of type NORM_OBJECT_FILE. + /// provided memory space for the name of the file + /// idicates the length of the nameBuffer + /// + /// This function returns true upon success and false upon failure. Possible failure conditions include the objectHandle + /// does not refer to an object of type NORM_OBJECT_FILE. + /// + [DllImport(NORM_LIBRARY)] + public unsafe static extern bool NormFileGetName(long fileHandle, sbyte* nameBuffer, int bufferLen); + + /// + /// This function renames the file used to store content for the NORM_OBJECT_FILE transport object specified by + /// the objectHandle parameter. This allows receiver applications to rename (or move) received files as needed.NORM + /// uses temporary file names for received files until the application explicitly renames the file.For example, sender + /// applications may choose to use the NORM_INFO content associated with a file object to provide name and/or typing + /// information to receivers. + /// + /// This type is used to reference state kept for data transport objects being actively transmitted or received. + /// parameter must be a NULL-terminated string which should specify the full desired path name to be used + /// + /// This function returns true upon success and false upon failure. Possible failure conditions include the case where + /// the objectHandle does not refer to an object of type NORM_OBJECT_FILE and where NORM was unable to successfully + /// create any needed directories and/or the file itself. + /// + [DllImport(NORM_LIBRARY)] + public static extern bool NormFileRename(long fileHandle, string fileName); + + /// + /// This function allows the application to access the data storage area associated with a transport object of type + /// NORM_OBJECT_DATA.For example, the application may use this function to copy the received data content for its + /// own use.Alternatively, the application may establish "ownership" for the allocated memory space using the + /// NormDataDetachData() function if it is desired to avoid the copy. + /// + /// This type is used to reference state kept for data transport objects being actively transmitted or received. + /// + /// This function returns a pointer to the data storage area for the specified transport object. A NULL value may be + /// returned if the object has no associated data content or is not of type NORM_OBJECT_DATA. + /// + [DllImport(NORM_LIBRARY)] + public static extern nint NormDataAccessData(long objectHandle); + + /// + /// This function retrieves the NormNodeHandle corresponding to the remote sender of the transport object associated with the given objectHandle parameter. + /// + /// This type is used to reference state kept for data transport objects being actively transmitted or received. + /// This function returns the NormNodeHandle corresponding to the remote sender of the transport object associated with the given objectHandle parameter. + /// A value of NORM_NODE_INVALID is returned if the specified objectHandle + /// references a locally originated, sender object. + [DllImport(NORM_LIBRARY)] + public static extern long NormObjectGetSender(long objectHandle); + + /// + /// This function retrieves the NormNodeId identifier for the remote participant referenced by the given nodeHandle value. + /// + /// This type is used to reference state kept by the NORM implementation with respect to other participants within a NormSession. + /// This function returns the NormNodeId value associated with the specified nodeHandle. + /// In the case nodeHandle is equal to NORM_NODE_INVALID, the return value will be NORM_NODE_NONE. + [DllImport(NORM_LIBRARY)] + public static extern long NormNodeGetId(long nodeHandle); + + /// + /// This function retrieves the current network source address detected for packets received from remote NORM sender referenced by the nodeHandle parameter. + /// + /// This type is used to reference state kept by the NORM implementation with respect to other participants within a NormSession. + /// The addrBuffer must be a pointer to storage of bufferLen bytes in length in which the referenced sender node's address will be returned + /// A return value of false indicates that either no command was available or the provided buffer size + /// port number and/or specify a specific source address binding that is used for packet transmission. + /// A value of true is returned upon success and false upon failure. An invalid nodeHandle parameter value would lead to such failure. + [DllImport(NORM_LIBRARY)] + public unsafe static extern bool NormNodeGetAddress(long nodeHandle, byte* addrBuffer, ref int bufferLen, out int port); + + /// + /// This function retrieves the advertised estimate of group round-trip timing (GRTT) for the remote sender referenced by the given nodeHandle value. + /// Newly-starting senders that have been participating as a receiver within a group + /// may wish to use this function to provide a more accurate startup estimate of GRTT prior to a call to NormStartSender() + /// + /// This type is used to reference state kept by the NORM implementation with respect to other participants within a NormSession. + /// This function returns the remote sender's advertised GRTT estimate in units of seconds. + /// A value of -1.0 is returned upon failure.An invalid nodeHandle parameter value will lead to such failure. + [DllImport(NORM_LIBRARY)] + public static extern double NormNodeGetGrtt(long nodeHandle); + + /// + /// This function retrieves the content of an application-defined command that was received from a remote sender associated with the given nodeHandle. + /// + /// notification for a given remote sender when multiple senders may be providing content + /// Allocated system resources for each active sender + /// A return value of false indicates that either no command was available or the provided buffer size + /// This function returns true upon successful retrieval of command content. A return value of false indicates that + /// either no command was available or the provided buffer size (buflen parameter) was inadequate. + /// The value referenced by the buflen parameter is adjusted to indicate the actual command length (in bytes) upon return. + [DllImport(NORM_LIBRARY)] + public unsafe static extern bool NormNodeGetCommand(long remoteSender, byte* cmdBuffer, ref int buflen); + + /// + /// This function releases memory resources that were allocated for a remote sender. + /// + /// notification for a given remote sender when multiple senders may be providing content + [DllImport(NORM_LIBRARY)] + public static extern void NormNodeFreeBuffers(long remoteSender); + + /// + /// this function allows the application to retain state associated with a given nodeHandle + /// value even when the underlying NORM protocol engine might normally + /// free the associated state and thus invalidate the NormNodeHandle. + /// + /// This type is used to reference state kept by the NORM implementation with respect to other participants within a NormSession. + [DllImport(NORM_LIBRARY)] + public static extern void NormNodeRetain(long nodeHandle); + + /// + /// In complement to the NormNodeRetain() function, this API call releases the specified nodeHandle so that the + /// NORM protocol engine may free associated resources as needed.Once this call is made, the application should + /// no longer reference the specified NormNodeHandle, unless it is still valid. + /// + /// This type is used to reference state kept by the NORM implementation with respect to other participants within a NormSession. + [DllImport(NORM_LIBRARY)] + public static extern void NormNodeRelease(long nodeHandle); + } +} diff --git a/src/dotnet/src/Mil.Navy.Nrl.Norm/NormData.cs b/src/dotnet/src/Mil.Navy.Nrl.Norm/NormData.cs new file mode 100644 index 00000000..a0a13e18 --- /dev/null +++ b/src/dotnet/src/Mil.Navy.Nrl.Norm/NormData.cs @@ -0,0 +1,33 @@ +using System.Runtime.InteropServices; + +namespace Mil.Navy.Nrl.Norm +{ + /// + /// A transport object of type NORM_OBJECT_DATA. + /// + /// + /// The data storage area for the specified transport object. + /// + public class NormData : NormObject + { + /// + /// Get the data storage area associated with a transport object of type NORM_OBJECT_DATA. + /// + public byte[] GetData() + { + var dataPointer = NormDataAccessData(_handle); + var length = NormObjectGetSize(_handle); + var data = new byte[length]; + Marshal.Copy(dataPointer, data, 0, length); + return data; + } + + /// + /// Constructor of NormData + /// + /// Type is used to reference state kept for data transport objects being actively transmitted or received. + internal NormData(long handle) : base(handle) + { + } + } +} diff --git a/src/dotnet/src/Mil.Navy.Nrl.Norm/NormEvent.cs b/src/dotnet/src/Mil.Navy.Nrl.Norm/NormEvent.cs new file mode 100644 index 00000000..866fdf87 --- /dev/null +++ b/src/dotnet/src/Mil.Navy.Nrl.Norm/NormEvent.cs @@ -0,0 +1,112 @@ +namespace Mil.Navy.Nrl.Norm +{ + /// + /// The NormEvent type is used to describe significant NORM protocol events. + /// + public class NormEvent + { + /// + /// The type identifies the event with one of NORM protocol events. + /// + private NormEventType _type { get; } + /// + /// Used to identify application in the NormSession. + /// + private long _sessionHandle { get; } + /// + /// This type is used to reference state kept by the NORM implementation with respect to other participants within a NormSession. + /// + private long _nodeHandle { get; } + /// + /// This type is used to reference state kept for data transport objects being actively transmitted or received. + /// + private long _objectHandle { get; } + + /// + /// The Parameterized constructor of NormEvent + /// + /// indicates the NormEventType and determines how the other fields should be interpreted. + /// indicates the applicable NormSessionHandle to which the event applies. + /// indicates the applicable NormNodeHandle to which the event applies + /// indicates the applicable NormObjectHandle to which the event applies. + public NormEvent(NormEventType type, long sessionHandle, long nodeHandle, long objectHandle) + { + _type = type; + _sessionHandle = sessionHandle; + _nodeHandle = nodeHandle; + _objectHandle = objectHandle; + } + + /// + /// The type identifies the event with one of NORM protocol events. + /// + public NormEventType Type => _type; + + /// + /// The NormSession associated with the event. + /// + public NormSession? Session + { + get + { + if (_sessionHandle == NormSession.NORM_SESSION_INVALID) + { + return null; + } + return NormSession.GetSession(_sessionHandle); + } + } + + /// + /// A remote participant associated with the event. + /// + public NormNode? Node + { + get + { + if (_nodeHandle == 0) + { + return null; + } + return new NormNode(_nodeHandle); + } + } + + /// + /// NORM transport object. + /// + public NormObject? Object + { + get + { + NormObject? normObject = null; + var normObjectType = NormObjectGetType(_objectHandle); + switch (normObjectType) + { + case NormObjectType.NORM_OBJECT_DATA: + normObject = new NormData(_objectHandle); + break; + case NormObjectType.NORM_OBJECT_FILE: + normObject = new NormFile(_objectHandle); + break; + case NormObjectType.NORM_OBJECT_STREAM: + normObject= new NormStream(_objectHandle); + break; + case NormObjectType.NORM_OBJECT_NONE: + default: + break; + } + return normObject; + } + } + + /// + /// This function returns a description of the NORM protocol event + /// + /// A string that includes the event type. + public override string ToString() + { + return $"NormEvent [type={_type}]"; + } + } +} diff --git a/src/dotnet/src/Mil.Navy.Nrl.Norm/NormFile.cs b/src/dotnet/src/Mil.Navy.Nrl.Norm/NormFile.cs new file mode 100644 index 00000000..bb95a47e --- /dev/null +++ b/src/dotnet/src/Mil.Navy.Nrl.Norm/NormFile.cs @@ -0,0 +1,55 @@ +namespace Mil.Navy.Nrl.Norm +{ + /// + /// A transport object of type NORM_OBJECT_FILE. + /// + public class NormFile : NormObject + { + /// + /// Maximum length of file names. + /// + public const int FILENAME_MAX = 260; + + /// + /// Constructor of NormFile + /// + /// This type is used to reference state kept for data transport objects being actively transmitted or received. + internal NormFile(long handle) : base(handle) + { + } + + /// + /// The name of the file. + /// + /// Thrown when failed to get file name. + public unsafe string Name + { + get + { + var buffer = stackalloc sbyte[FILENAME_MAX]; + + if (!NormFileGetName(_handle, buffer, FILENAME_MAX)) + { + throw new IOException("Failed to get file name"); + } + + return new string(buffer); + } + } + + /// + /// This function renames the file used to store content for the NORM_OBJECT_FILE transport object. + /// This allows receiver applications to rename (or move) received files as needed. + /// NORM uses temporary file names for received files until the application explicitly renames the file. + /// + /// The full path of received file. + /// Thrown when failed to rename file. + public void Rename(string filePath) + { + if(!NormFileRename(_handle, filePath)) + { + throw new IOException("Failed to rename file"); + } + } + } +} diff --git a/src/dotnet/src/Mil.Navy.Nrl.Norm/NormInstance.cs b/src/dotnet/src/Mil.Navy.Nrl.Norm/NormInstance.cs new file mode 100644 index 00000000..f011d546 --- /dev/null +++ b/src/dotnet/src/Mil.Navy.Nrl.Norm/NormInstance.cs @@ -0,0 +1,235 @@ +using Microsoft.Win32.SafeHandles; +using System.Runtime.InteropServices; +using System.Net.Sockets; + +namespace Mil.Navy.Nrl.Norm +{ + /// + /// An instance of a NORM protocol engine + /// + public class NormInstance + { + /// + /// Returned on error when getting the file descriptor for a NormInstance. + /// + public const int NORM_DESCRIPTOR_INVALID = 0; + /// + /// The _handle refers to the NORM protocol engine instance + /// + private long _handle; + + /// + /// Constructor for NormInstance with priority boost + /// + /// The priorityBoost parameter, when set to a value of true, specifies that the NORM protocol engine thread be run with higher priority scheduling. + public NormInstance(bool priorityBoost) + { + CreateInstance(priorityBoost); + } + + /// + /// Default constructor for NormInstance + /// + public NormInstance() : this(false) + { + } + + /// + /// This function creates an instance of a NORM protocol engine and is the necessary first step before any other API functions may be used. + /// + /// The priorityBoost parameter, when set to a value of true, specifies that the NORM protocol engine thread be run with higher priority scheduling. + private void CreateInstance(bool priorityBoost) + { + var handle = NormCreateInstance(priorityBoost); + _handle = handle; + } + + /// + /// The function immediately shuts down and destroys the NORM protocol engine instance referred to by the instanceHandle parameter. + /// + public void DestroyInstance() + { + NormDestroyInstance(_handle); + } + + /// + /// This function creates a NORM protocol session (NormSession) using the address (multicast or unicast) and port + /// parameters provided. While session state is allocated and initialized, active session participation does not begin + /// until the session starts the sender and/or receiver to join the specified multicast group + /// (if applicable) and start protocol operation. + /// + /// Specified address determines the destination of NORM messages sent + /// Valid, unused port number corresponding to the desired NORM session address. + /// Identifies the application's presence in the NormSession + /// The NormSession that was createdThrows when fails to create session + public NormSession CreateSession(string address, int port, long localNodeId) + { + var session = NormCreateSession(_handle, address, port, localNodeId); + if (session == NormSession.NORM_SESSION_INVALID) + { + throw new IOException("Failed to create session"); + } + return new NormSession(session); + } + + /// + /// Determines if the NORM protocol engine instance has a next event + /// + /// The seconds to wait + /// The microseconds to wait + /// True if the NORM protocol engine instance has a next event + public bool HasNextEvent(int sec, int usec) + { + var totalMilliseconds = sec * 1000 + usec / 1000; + var waitTime = TimeSpan.FromMilliseconds(totalMilliseconds); + return HasNextEvent(waitTime); + } + + /// + /// Determines if the NORM protocol engine instance has a next event + /// + /// The time to wait + /// True if the NORM protocol engine instance has a next event + public bool HasNextEvent(TimeSpan waitTime) + { + var normDescriptor = NormGetDescriptor(_handle); + if (normDescriptor == NORM_DESCRIPTOR_INVALID) + { + return false; + } + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + using var eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset); + eventWaitHandle.SafeWaitHandle = new SafeWaitHandle(new IntPtr(normDescriptor), false); + return eventWaitHandle.WaitOne(waitTime); + } + var hasNextEvent = false; + var timeout = DateTime.Now.Add(waitTime); + while (!hasNextEvent && DateTime.Now <= timeout) + { + using var socketHandle = new SafeSocketHandle(new IntPtr(normDescriptor), false); + using var socket = new Socket(socketHandle); + hasNextEvent = socket.Available > 0; + } + return hasNextEvent; + } + + /// + /// This function retrieves the next available NORM protocol event from the protocol engine. + /// + /// waitForEvent specifies whether the call to this function is blocking or not, if "waitForEvent" is false, this is a non-blocking call. + /// Returns an instance of NormEvent if NormGetNextEvent() returns true, returns null otherwise. + public NormEvent? GetNextEvent(bool waitForEvent) + { + bool success = NormGetNextEvent(_handle, out NormApi.NormEvent normEvent, waitForEvent); + if (!success) + { + return null; + } + return new NormEvent(normEvent.Type, normEvent.Session, normEvent.Sender, normEvent.Object); + } + + /// + /// This function retrieves the next available NORM protocol event from the protocol engine. + /// + /// + /// This is an overload which calls GetNextEvent() with waitForEvent set as true. + /// + /// Returns an instance of NormEvent if NormGetNextEvent() returns true, returns null otherwise. + public NormEvent? GetNextEvent() + { + return GetNextEvent(true); + } + + /// + /// This function sets the directory path used by receivers to cache newly-received NORM_OBJECT_FILE content. + /// + /// the cachePath is a string specifying a valid (and writable) directory path. + /// Throws when fails to set the cache directory + public void SetCacheDirectory(string cachePath) + { + if(!NormSetCacheDirectory(_handle, cachePath)) + { + throw new IOException("Failed to set the cache directory"); + } + } + + /// + /// This function immediately stops the NORM protocol engine thread. + /// + public void StopInstance() + { + NormStopInstance(_handle); + } + + /// + /// This function creates and starts an operating system thread to resume NORM protocol engine operation that was previously stopped by a call to StopInstance(). + /// + /// Boolean as to the success of the instance restart. + public bool RestartInstance() + { + return NormRestartInstance(_handle); + } + + /// + /// Immediately suspends the NORM protocol engine thread. + /// + /// Boolean as to the success of the instance suspension. + public bool SuspendInstance() + { + return NormSuspendInstance(_handle); + } + + /// + /// This function allows NORM debug output to be directed to a file instead of the default STDERR. + /// + public void ResumeInstance() + { + NormResumeInstance(_handle); + } + + /// + /// This function allows NORM debug output to be directed to a file instead of the default STDERR. + /// + /// Full path and name of the debug log. + /// Throws when fails to open debug log" + public void OpenDebugLog(string fileName) + { + if (!NormOpenDebugLog(_handle, fileName)) + { + throw new IOException("Failed to open debug log"); + } + } + + /// + /// This function disables NORM debug output to be directed to a file instead of the default STDERR. + /// + public void CloseDebugLog() + { + NormCloseDebugLog(_handle); + } + + /// + /// This function allows NORM debug output to be directed to a named pipe. + /// + /// The debug pipe name. + /// Throws when fails to open debug pipe. + public void OpenDebugPipe(string pipename) + { + if (!NormOpenDebugPipe(_handle, pipename)) + { + throw new IOException("Failed to open debug pipe"); + } + } + + /// + /// The currently set debug level. + /// + public int DebugLevel + { + get => NormGetDebugLevel(); + set => NormSetDebugLevel(value); + } + } +} diff --git a/src/dotnet/src/Mil.Navy.Nrl.Norm/NormNode.cs b/src/dotnet/src/Mil.Navy.Nrl.Norm/NormNode.cs new file mode 100644 index 00000000..dc8dc95d --- /dev/null +++ b/src/dotnet/src/Mil.Navy.Nrl.Norm/NormNode.cs @@ -0,0 +1,159 @@ +using System.Net; + +namespace Mil.Navy.Nrl.Norm +{ + /// + /// A participant in a NORM protocol session + /// + public class NormNode + { + /// + /// When creating a session, allows the NORM implementation to attempt to pick an identifier based on the host computer's "default" IP address. + /// + public const long NORM_NODE_ANY = 0xffffffff; + /// + /// The special value NORM_NODE_NONE corresponds to an invalid (or null) node. + /// + public const int NORM_NODE_NONE = 0; + /// + /// The special value NORM_NODE_INVALID corresponds to an invalid reference. + /// + public const int NORM_NODE_INVALID = 0; + /// + /// The handle is associated to the NORM protocol engine instance. + /// + private long _handle; + + /// + /// Parameterized contructor. + /// + /// The handle is associated to the NORM protocol engine instance. + internal NormNode(long handle) + { + _handle = handle; + } + + /// + /// The NormNodeId identifier for the remote participant. + /// + public long Id => NormNodeGetId(_handle); + + /// + /// The current network source address detected for packets received from remote NORM sender. + /// + public unsafe IPEndPoint Address + { + get + { + var bufferLength = 256; + var buffer = stackalloc byte[bufferLength]; + + if (!NormNodeGetAddress(_handle, buffer, ref bufferLength, out int port)) + { + throw new IOException("Failed to get node address"); + } + + var addressBytes = new ReadOnlySpan(buffer, bufferLength); + var ipAddress = new IPAddress(addressBytes); + return new IPEndPoint(ipAddress, port); + } + } + + /// + /// The advertised estimate of group round-trip timing (GRTT) for the remote sender. + /// + public double Grtt => NormNodeGetGrtt(_handle); + + /// + /// This function retrieves the content of an application-defined command that was received from a remote sender. + /// + /// Thrown when the offset or length are outside of the buffer. + public int GetCommand(byte[] buffer, int offset, int length) + { + if (offset < 0 || offset >= buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(offset), "The offset is out of range"); + } + if (length < 1 || offset + length > buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(length), "The length is out of range"); + } + + unsafe + { + fixed (byte* bufferPtr = buffer) + { + if (!NormNodeGetCommand(_handle, bufferPtr + offset, ref length)) + { + throw new IOException("Failed to get command"); + } + } + } + + return length; + } + + /// + /// This function controls the destination address of receiver feedback messages generated in response to a specific remote NORM sender. + /// + /// If state is true, "unicast NACKing" is enabled. + public void SetUnicastNack(bool state) + { + NormNodeSetUnicastNack(_handle, state); + } + + /// + /// This function sets the default "nacking mode" used for receiving new objects from a specific sender. + /// + /// Specifies the nacking mode. + public void SetNackingMode(NormNackingMode nackingMode) + { + NormNodeSetNackingMode(_handle, nackingMode); + } + + /// + /// This function allows the receiver application to customize at what points the receiver initiates the NORM NACK repair process during protocol operation. + /// + /// Specifies the repair boundary. + public void SetRepairBoundary(NormRepairBoundary repairBoundary) + { + NormNodeSetRepairBoundary(_handle, repairBoundary); + } + + /// + /// This routine sets the robustFactor as described in NormSetDefaultRxRobustFactor() for an individual remote sender. + /// + /// The robustFactor value determines how + /// many times a NORM receiver will self-initiate NACKing (repair requests) upon cessation of packet reception from + /// a sender. The default value is 20. Setting rxRobustFactor to -1 will make the NORM receiver infinitely persistent + /// (i.e., it will continue to NACK indefinitely as long as it is missing data content). + public void SetRxRobustFactor(int robustFactor) + { + NormNodeSetRxRobustFactor(_handle, robustFactor); + } + + /// + /// This function releases memory resources that were allocated for a remote sender. + /// + public void FreeBuffers() + { + NormNodeFreeBuffers(_handle); + } + + /// + /// This function allows the application to retain state associated even when the underlying NORM protocol engine might normally free the associated state. + /// + public void Retain() + { + NormNodeRetain(_handle); + } + + /// + /// This function releases the Node so that the NORM protocol engine may free associated resources as needed. + /// + public void Release() + { + NormNodeRelease(_handle); + } + } +} diff --git a/src/dotnet/src/Mil.Navy.Nrl.Norm/NormObject.cs b/src/dotnet/src/Mil.Navy.Nrl.Norm/NormObject.cs new file mode 100644 index 00000000..1362e00d --- /dev/null +++ b/src/dotnet/src/Mil.Navy.Nrl.Norm/NormObject.cs @@ -0,0 +1,165 @@ +namespace Mil.Navy.Nrl.Norm +{ + /// + /// The base transport object. + /// + public class NormObject + { + /// + /// The special value NORM_OBJECT_INVALID corresponds to an invalid reference. + /// + public const int NORM_OBJECT_INVALID = 0; + /// + /// Used to reference state kept for data transport objects being actively transmitted or received. + /// + protected long _handle; + + /// + /// Internal constructor for NormObject + /// + /// Identifies an instance of NormObject + internal NormObject(long handle) + { + _handle = handle; + } + + /// + /// Used to reference state kept for data transport objects being actively transmitted or received. + /// + public long Handle => _handle; + + /// + /// Data associated with NormObject. + /// + public byte[]? Info + { + get + { + if (!NormObjectHasInfo(_handle)) + { + return null; + } + + var length = NormObjectGetInfoLength(_handle); + var buffer = new byte[length]; + NormObjectGetInfo(_handle, buffer, length); + + return buffer; + } + } + + /// + /// The type of Norm object. + /// Valid types include: + /// NORM_OBJECT_FILE + /// NORM_OBJECT_DATA + /// NORM_OBJECT_STREAM + /// + public NormObjectType Type + { + get + { + return NormObjectGetType(_handle); + } + } + + /// + /// The size (in bytes) of the transport object. + /// + public long Size + { + get + { + return NormObjectGetSize(_handle); + } + } + + /// + /// The NormNodeHandle corresponding to the remote sender of the transport object. + /// + /// Thrown when NormObjectGetSender() returns NORM_NODE_INVALID, indicating locally originated sender object. + public long Sender + { + get + { + var sender = NormObjectGetSender(_handle); + if(sender == NormNode.NORM_NODE_INVALID) + { + throw new IOException("Locally originated sender object"); + } + return sender; + } + } + + /// + /// This function sets the "nacking mode" used for receiving a specific transport object. + /// + /// Specifies the nacking mode. + public void SetNackingMode(NormNackingMode nackingMode) + { + NormObjectSetNackingMode(_handle, nackingMode); + } + + /// + /// This function can be used to determine the progress of reception of the NORM transport object + /// + /// A number of object source data bytes pending reception (or transmission) is returned. + public long GetBytesPending() + { + return NormObjectGetBytesPending(_handle); + } + + /// + /// This function immediately cancels the transmission of a local sender transport object or the reception of a specified + /// object from a remote sender. + /// + public void Cancel() + { + NormObjectCancel(_handle); + } + + /// + /// This function "retains" the objectHandle and any state associated with it for further use by the application even + /// when the NORM protocol engine may no longer require access to the associated transport object. + /// + public void Retain() + { + NormObjectRetain(_handle); + } + + /// + /// This function complements the Retain() call by immediately freeing any resources associated with + /// the given objectHandle, assuming the underlying NORM protocol engine no longer requires access to the corresponding + /// transport object. Note the NORM protocol engine retains/releases state for associated objects for its own + /// needs and thus it is very unsafe for an application to call Release() for an objectHandle for which + /// it has not previously explicitly retained via Retain(). + /// + public void Release() + { + NormObjectRelease(_handle); + } + + /// + /// Gets the hash code of the object. + /// + /// Returns the handle of the object. + public override int GetHashCode() + { + return (int)_handle; + } + + /// + /// Decides wether specified object is equal another. + /// + /// NormObject to compare to. + /// Returns true if two objects equal, false otherwise. + public override bool Equals(object? obj) + { + if(obj is NormObject) + { + return _handle == ((NormObject)obj).Handle; + } + return false; + } + } +} diff --git a/src/dotnet/src/Mil.Navy.Nrl.Norm/NormSession.cs b/src/dotnet/src/Mil.Navy.Nrl.Norm/NormSession.cs new file mode 100644 index 00000000..2aa968e5 --- /dev/null +++ b/src/dotnet/src/Mil.Navy.Nrl.Norm/NormSession.cs @@ -0,0 +1,1028 @@ +using System.Runtime.InteropServices; +using System.Text; + +namespace Mil.Navy.Nrl.Norm +{ + /// + /// NORM transport session + /// + public class NormSession + { + /// + /// The special value NORM_SESSION_INVALID is used to refer to invalid session references. + /// + public const int NORM_SESSION_INVALID = 0; + /// + /// A dictionary of NORM sessions with their respective handles. + /// + private static Dictionary _normSessions = new Dictionary(); + /// + /// The NormSessionHandle type is used to reference the NORM transport session. + /// + private long _handle; + + /// + /// Used for the application's participation in the NormSession. + /// + public long LocalNodeId + { + get => NormGetLocalNodeId(_handle); + } + + /// + /// The report interval. + /// + public double ReportInterval + { + get => NormGetReportInterval(_handle); + set => NormSetReportInterval(_handle, value); + } + + /// + /// Transmission rate (in bits per second (bps)) limit used for NormSender transmissions. + /// + public double TxRate + { + get => NormGetTxRate(_handle); + set => NormSetTxRate(_handle, value); + } + + /// + /// Group round-trip timing. + /// + public double GrttEstimate + { + get => NormGetGrttEstimate(_handle); + set => NormSetGrttEstimate(_handle, value); + } + + /// + /// Internal constructor of NormSession. + /// + /// Used to identify application in the NormSession. + /// The handle and NormSession are added to the dictionary of NORM sessions. + internal NormSession(long handle) + { + _handle = handle; + lock (_normSessions) + { + _normSessions.Add(handle, this); + } + } + + /// + /// Get a specified NormSession from the dictionary of NORM sessions. + /// + /// Specifies the session to return. + /// Returns a NormSession. + internal static NormSession? GetSession(long handle) + { + lock (_normSessions) + { + return _normSessions.TryGetValue(handle, out NormSession? session) ? session : null; + } + } + + /// + /// This function immediately terminates the application's participation in the NormSession and frees any resources used by that session. + /// + public void DestroySession() + { + lock (_normSessions) + { + _normSessions.Remove(_handle); + } + DestroySessionNative(); + } + + /// + /// This function immediately terminates the application's participation in the NormSession and frees any resources used by that session. + /// + private void DestroySessionNative() + { + NormDestroySession(_handle); + } + + /// + /// This function is used to force NORM to use a specific port number for UDP packets sent. + /// + /// + /// This is an overload which calls SetTxPort() with enableReuse set as false and txBindAddress set to null. + /// + /// The port parameter, specifies which port number to use. + /// Thrown when NormSetTxPort() returns false, indicating the failure to set tx port. + public void SetTxPort(int port) + { + SetTxPort(port, false, null); + } + + /// + /// This function is used to force NORM to use a specific port number for UDP packets sent. + /// + /// The port parameter, specifies which port number to use. + /// When set to true, allows that the specified port may be reused for multiple sessions. + /// The txBindAddress parameter allows specification of a specific source address binding for packet transmission. + /// Thrown when NormSetTxPort() returns false, indicating the failure to set tx port. + public void SetTxPort(int port, bool enableReuse, string? txBindAddress) + { + if (!NormSetTxPort(_handle, port, enableReuse, txBindAddress)) + { + throw new IOException("Failed to set Tx Port"); + } + } + + /// + /// This function limits the NormSession to perform NORM sender functions only. + /// + /// + /// This is an overload which calls SetTxOnly() with connectToSessionAddress set as false. + /// + /// Boolean specifing whether to turn on or off the txOnly operation. + public void SetTxOnly(bool txOnly) + { + SetTxOnly(txOnly, false); + } + + /// + /// This function limits the NormSession to perform NORM sender functions only. + /// + /// Boolean specifing whether to turn on or off the txOnly operation. + /// The optional connectToSessionAddress parameter, when set to true, causes the underlying NORM code to + /// "connect()" the UDP socket to the session (remote receiver) address and port number. + public void SetTxOnly(bool txOnly, bool connectToSessionAddress) + { + NormSetTxOnly(_handle, txOnly, connectToSessionAddress); + } + + /// + /// This function allows the user to control the port reuse and binding behavior for the receive socket. + /// + /// + /// This is an overload that calls SetRxPortReuse() with rxBindAddress set to null, senderAddress set to null, and senderPort set to 0. + /// + /// When the enable parameter is set to true, reuse of the NormSession port number by multiple NORM instances or sessions is enabled. + public void SetRxPortReuse(bool enable) + { + SetRxPortReuse(enable, null, null, 0); + } + + /// + /// This function allows the user to control the port reuse and binding behavior for the receive socket. + /// + /// When the enable parameter is set to true, reuse of the NormSession port number by multiple NORM instances or sessions is enabled. + /// If the optional rxBindAddress is supplied (an IP address or host name in string form), + /// the socket will bind() to the given address when it is opened in a call to StartReceiver() or StartSender(). + /// The optional senderAddress parameter can be used to connect() the underlying NORM receive socket to specific address. + /// The optional senderPort parameter can be used to connect() the underlying NORM receive socket to specific port. + public void SetRxPortReuse(bool enable, string? rxBindAddress, string? senderAddress, int senderPort) + { + NormSetRxPortReuse(_handle, enable, rxBindAddress, senderAddress, senderPort); + } + + /// + /// This is an overload which calls SetEcnSupport() with tolerateLoss set as false. + /// + /// Enables NORM ECN (congestion control) support. + /// With "ecnEnable", use ECN-only, ignoring packet loss. + public void SetEcnSupport(bool ecnEnable, bool ignoreLoss) + { + SetEcnSupport(ecnEnable, ignoreLoss, false); + } + + /// Enables NORM ECN (congestion control) support. + /// With "ecnEnable", use ECN-only, ignoring packet loss. + /// Loss-tolerant congestion control, ecnEnable or not. + public void SetEcnSupport(bool ecnEnable, bool ignoreLoss, bool tolerateLoss) + { + NormSetEcnSupport(_handle, ecnEnable, ignoreLoss, tolerateLoss); + } + + /// + /// This function specifies which host network interface is used for IP Multicast transmissions and group membership. + /// This should be called before any call to StartSender() or StartReceiver() is made so that the IP multicast + /// group is joined on the proper host interface. + /// + /// Name of the interface + /// Thrown when NormSetMulticastInterface() returns false, indicating the failure to set multicast interface. + public void SetMulticastInterface(string interfaceName) + { + if(!NormSetMulticastInterface(_handle, interfaceName)) + { + throw new IOException("Failed to set multicast interface"); + } + } + + /// + /// This function sets the source address for Source-Specific Multicast (SSM) operation. + /// + /// Address to be set as source for source-specific multicast operation. + /// Thrown when NormSetSSM() returns false, indicating the failure to set ssm. + public void SetSSM(string sourceAddress) + { + if(!NormSetSSM(_handle, sourceAddress)) + { + throw new IOException("Failed to set SSM"); + } + } + + /// + /// This function specifies the time-to-live (ttl) for IP Multicast datagrams generated by NORM for the specified + /// sessionHandle. The IP TTL field limits the number of router "hops" that a generated multicast packet may traverse + /// before being dropped. + /// + /// If TTL is equal to one, the transmissions will be limited to the local area network + /// (LAN) of the host computers network interface. Larger TTL values should be specified to span large networks. + /// Thrown when NormSetTTL() returns false, indicating the failure to set ttl. + public void SetTTL(byte ttl) + { + if (!NormSetTTL(_handle, ttl)) + { + throw new IOException("Failed to set TTL"); + } + } + + /// + /// This function specifies the type-of-service (tos) field value used in IP Multicast datagrams generated by NORM. + /// + /// The IP TOS field value can be used as an indicator that a "flow" of packets may merit special Quality-of-Service (QoS) treatment by network devices. + /// Users should refer to applicable QoS information for their network to determine the expected interpretation and treatment (if any) of packets with explicit TOS marking. + /// Thrown when NormSetTOS() returns false, indicating the failure to set TOS. + public void SetTOS(byte tos) + { + if(!NormSetTOS(_handle, tos)) + { + throw new IOException("Failed to set TOS"); + } + } + + /// + /// This function enables or disables loopback operation. + /// + /// If loopbackEnable is set to true, loopback operation is enabled which allows the application to receive its own message traffic. + /// Thus, an application which is both actively receiving and sending may receive its own transmissions. + /// Thrown when NormSetLoopback() returns false, indicating the failure to set loopback. + public void SetLoopback(bool loopbackEnable) + { + if (!NormSetLoopback(_handle, loopbackEnable)) + { + throw new IOException("Failed to set loopback"); + } + } + + public void SetMessageTrace(bool flag) + { + NormSetMessageTrace(_handle, flag); + } + + public void SetTxLoss(double precent) + { + NormSetTxLoss(_handle, precent); + } + + public void SetRxLoss(double precent) + { + NormSetRxLoss(_handle, precent); + } + + /// + /// This function controls a scaling factor that is used for sender timer-based flow control. + /// Timer-based flow control works by preventing the NORM sender application from enqueueing + /// new transmit objects or stream data that would purge "old" objects or stream data when there has been recent + /// NACK activity for those old objects or data. + /// + /// The precent is used to compute a delay time for when a sender buffered object (or block of stream + /// data) may be released (i.e. purged) after transmission or applicable NACKs reception. + public void SetFlowControl(double precent) + { + NormSetFlowControl(_handle, precent); + } + + /// + /// This function can be used to set a non-default socket buffer size for the UDP socket used for data transmission. + /// + /// The bufferSize parameter specifies the desired socket buffer size in bytes. + /// Thrown when NormSetTxSocketBuffer() returns false, indicating the failure to set tx socket buffer. + /// Possible failure modes include an invalid sessionHandle parameter, a call to StartReceiver() or StartSender() has not yet been made for the + /// session, or an invalid bufferSize was given. + /// Note some operating systems may require additional system configuration to use non-standard socket buffer sizes. + public void SetTxSocketBuffer(long bufferSize) + { + if(!NormSetTxSocketBuffer(_handle, bufferSize)) + { + throw new IOException("Failed to set tx socket buffer"); + } + } + + /// + /// This function enables (or disables) the NORM sender congestion control operation. + /// For best operation, this function should be called before the call to StartSender() is made, + /// but congestion control operation can be dynamically enabled/disabled during the course of sender operation. + /// + /// + /// This is an overload which calls SetCongestionControl() with adjustRate set to true. + /// + /// Specifies whether to enable or disable the NORM sender congestion control operation. + public void SetCongestionControl(bool enable) + { + SetCongestionControl(enable, true); + } + + /// + /// This function enables (or disables) the NORM sender congestion control operation. + /// For best operation, this function should be called before the call to StartSender() is made, + /// but congestion control operation can be dynamically enabled/disabled during the course of sender operation. + /// + /// Specifies whether to enable or disable the NORM sender congestion control operation. + /// The rate set by SetTxRate() has no effect when congestion control operation is enabled, unless the adjustRate + /// parameter here is set to false. When the adjustRate parameter is set to false, the NORM Congestion Control + /// operates as usual, with feedback collected from the receiver set and the "current limiting receiver" identified, except + /// that no actual adjustment is made to the sender's transmission rate. + public void SetCongestionControl(bool enable, bool adjustRate) + { + NormSetCongestionControl(_handle, enable, adjustRate); + } + + /// + /// This function sets the range of sender transmission rates within which the NORM congestion control algorithm is allowed to operate. + /// + /// rateMin corresponds to the minimum transmission rate (bps). + /// rateMax corresponds to the maximum transmission rate (bps). + public void SetTxRateBounds(double rateMin, double rateMax) + { + NormSetTxRateBounds(_handle, rateMin, rateMax); + } + + /// + /// This function sets limits that define the number and total size of pending transmit objects a NORM sender will allow to be enqueued by the application. + /// + /// The sizeMax parameter sets the maximum total size, in bytes, of enqueued objects allowed. + /// The countMin parameter sets the minimum number of objects the application may enqueue, regardless of the objects' sizes and the sizeMax value. + /// The countMax parameter sets a ceiling on how many objects may be enqueued, regardless of their total sizes with respect to the sizeMax setting. + public void SetTxCacheBounds(long sizeMax, long countMin, long countMax) + { + NormSetTxCacheBounds(_handle,sizeMax, countMin, countMax); + } + + /// + /// The application's participation as a sender begins when this function is called. + /// + /// Application-defined value used as the instance_id field of NORM sender messages for the application's participation within a session. + /// This specifies the maximum memory space (in bytes) the NORM protocol engine is allowed to use to buffer any sender calculated FEC segments and repair state for the session. + /// This parameter sets the maximum payload size (in bytes) of NORM sender messages (not including any NORM message header fields). + /// This parameter sets the number of source symbol segments (packets) per coding block, for the systematic Reed-Solomon FEC code used in the current NORM implementation. + /// This parameter sets the maximum number of parity symbol segments (packets) the sender is willing to calculate per FEC coding block. + /// Sets the NormFecType. + /// Thrown when NormStartSender() returns false, indicating the failure to start sender. + public void StartSender(int sessionId, long bufferSpace, int segmentSize, short blockSize, short numParity, NormFecType fecId) + { + if (!NormStartSender(_handle, sessionId, bufferSpace, segmentSize, blockSize, numParity, fecId)) + { + throw new IOException("Failed to start sender"); + } + } + + /// + /// The application's participation as a sender begins when this function is called. + /// + /// Application-defined value used as the instance_id field of NORM sender messages for the application's participation within a session. + /// This specifies the maximum memory space (in bytes) the NORM protocol engine is allowed to use to buffer any sender calculated FEC segments and repair state for the session. + /// This parameter sets the maximum payload size (in bytes) of NORM sender messages (not including any NORM message header fields). + /// This parameter sets the number of source symbol segments (packets) per coding block, for the systematic Reed-Solomon FEC code used in the current NORM implementation. + /// This parameter sets the maximum number of parity symbol segments (packets) the sender is willing to calculate per FEC coding block. + /// Uses NormFecType.RS for fecId. + /// Thrown when NormStartSender() returns false, indicating the failure to start sender. + public void StartSender(int sessionId, long bufferSpace, int segmentSize, short blockSize, short numParity) + { + StartSender(sessionId, bufferSpace, segmentSize, blockSize, numParity, NormFecType.RS); + } + + /// + /// The application's participation as a sender begins when this function is called. + /// + /// This specifies the maximum memory space (in bytes) the NORM protocol engine is allowed to use to buffer any sender calculated FEC segments and repair state for the session. + /// This parameter sets the maximum payload size (in bytes) of NORM sender messages (not including any NORM message header fields). + /// This parameter sets the number of source symbol segments (packets) per coding block, for the systematic Reed-Solomon FEC code used in the current NORM implementation. + /// This parameter sets the maximum number of parity symbol segments (packets) the sender is willing to calculate per FEC coding block. + /// Sets the NormFecType. + /// Generates a random sessionId. + /// Thrown when NormStartSender() returns false, indicating the failure to start sender. + public void StartSender(long bufferSpace, int segmentSize, short blockSize, short numParity, NormFecType fecId) + { + var sessionId = NormGetRandomSessionId(); + StartSender(sessionId, bufferSpace, segmentSize, blockSize, numParity, fecId); + } + + /// + /// The application's participation as a sender begins when this function is called. + /// + /// This specifies the maximum memory space (in bytes) the NORM protocol engine is allowed to use to buffer any sender calculated FEC segments and repair state for the session. + /// This parameter sets the maximum payload size (in bytes) of NORM sender messages (not including any NORM message header fields). + /// This parameter sets the number of source symbol segments (packets) per coding block, for the systematic Reed-Solomon FEC code used in the current NORM implementation. + /// This parameter sets the maximum number of parity symbol segments (packets) the sender is willing to calculate per FEC coding block. + /// Generates a random sessionId and uses NormFecType.RS for fecId. + /// Thrown when NormStartSender() returns false, indicating the failure to start sender. + public void StartSender(long bufferSpace, int segmentSize, short blockSize, short numParity) + { + StartSender(bufferSpace, segmentSize, blockSize, numParity, NormFecType.RS); + } + + /// + /// This function terminates the application's participation in a NormSession as a sender. By default, the sender will + /// immediately exit the session without notifying the receiver set of its intention. + /// + public void StopSender() + { + NormStopSender(_handle); + } + + /// + /// This function enqueues a file for transmission. + /// + /// + /// This is an overload which will call FileEnqueue() with info set to encoding of the filename, infoOffset set to 0, and infoLength set to info.Length. + /// + /// The fileName parameter specifies the path to the file to be transmitted. The NORM protocol engine + /// read and writes directly from/to file system storage for file transport, potentially providing for a very large virtual + /// "repair window" as needed for some applications. While relative paths with respect to the "current working directory" + /// may be used, it is recommended that full paths be used when possible. + /// A NormFile is returned which the application may use in other NORM API calls as needed. + /// Thrown when NormFileEnqueue() returns NORM_OBJECT_INVALID, indicating the failure to enqueue file. + public NormFile FileEnqueue(string filename) + { + var info = Encoding.ASCII.GetBytes(filename); + return FileEnqueue(filename, info, 0, info.Length); + } + + /// + /// This function enqueues a file for transmission. + /// + /// The fileName parameter specifies the path to the file to be transmitted. The NORM protocol engine + /// read and writes directly from/to file system storage for file transport, potentially providing for a very large virtual + /// "repair window" as needed for some applications. While relative paths with respect to the "current working directory" + /// may be used, it is recommended that full paths be used when possible. + /// The optional info and infoLength parameters are used to associate NORM_INFO content with the sent transport object. + /// Indicates the start of the message. Anything before it will not be sent. + /// Note: to send full message infoOffset should be set to 0. + /// The optional info and infoLength parameters are used to associate NORM_INFO content with the sent transport object. + /// A NormFile is returned which the application may use in other NORM API calls as needed. + /// Thrown when NormFileEnqueue() returns NORM_OBJECT_INVALID, indicating the failure to enqueue file. + /// Thrown when the info offset or info length are outside of the info buffer. + public NormFile FileEnqueue(string filename, byte[]? info, int infoOffset, int infoLength) + { + if (infoOffset < 0 || infoOffset >= info?.Length) + { + throw new ArgumentOutOfRangeException(nameof(infoOffset), "The info offset is out of range"); + } + if (info != null && infoLength < 1 || infoOffset + infoLength > info?.Length) + { + throw new ArgumentOutOfRangeException(nameof(infoLength), "The info length is out of range"); + } + + long objectHandle; + var infoHandle = GCHandle.Alloc(info, GCHandleType.Pinned); + + try + { + var infoPtr = infoHandle.AddrOfPinnedObject() + infoOffset; + objectHandle = NormFileEnqueue(_handle, filename, infoPtr, infoLength); + if (objectHandle == NormObject.NORM_OBJECT_INVALID) + { + throw new IOException("Failed to enqueue file"); + } + } + finally + { + infoHandle.Free(); + } + + return new NormFile(objectHandle); + } + + /// + /// This function enqueues a segment of application memory space for transmission. + /// + /// + /// This is an overload which will call DataEnqueue() with info set to null, infoOffset set to 0, and infoLength set to 0. + /// + /// The dataBuffer is a byte array containing the message to be transmitted. + /// Indicates the start of the message. Anything before it will not be sent. + /// Note: to send full message dataOffset should be set to 0. + /// Size of the message. + /// A NormData is returned which the application may use in other NORM API calls as needed. + /// Thrown when NormDataEnqueue() returns NORM_OBJECT_INVALID, indicating the failure to enqueue data. + /// Thrown when the data offset or data length are outside of the data buffer. + public NormData DataEnqueue(SafeBuffer dataBuffer, int dataOffset, int dataLength) + { + return DataEnqueue(dataBuffer, dataOffset, dataLength, null, 0, 0); + } + + /// + /// This function enqueues a segment of application memory space for transmission. + /// + /// The dataBuffer is a byte array containing the message to be transmitted. + /// Indicates the start of the message. Anything before it will not be sent. + /// Note: to send full message dataOffset should be set to 0. + /// Size of the message. + /// The optional info and infoLength parameters are used to associate NORM_INFO content with the sent transport object. + /// Indicates the start of the message. + /// The optional info and infoLength parameters are used to associate NORM_INFO content with the sent transport object. + /// A NormData is returned which the application may use in other NORM API calls as needed. + /// Thrown when NormDataEnqueue() returns NORM_OBJECT_INVALID, indicating the failure to enqueue data. + /// Thrown when the data offset, data length, info offset or info length are outside of the associated buffer. + public NormData DataEnqueue(SafeBuffer dataBuffer, int dataOffset, int dataLength, byte[]? info, int infoOffset, int infoLength) + { + if (dataOffset < 0 || Convert.ToUInt64(dataOffset) >= dataBuffer.ByteLength) + { + throw new ArgumentOutOfRangeException(nameof(dataOffset), "The data offset is out of range"); + } + if (dataLength < 1 || Convert.ToUInt64(dataOffset + dataLength) > dataBuffer.ByteLength) + { + throw new ArgumentOutOfRangeException(nameof(dataLength), "The data length is out of range"); + } + + unsafe + { + byte* dataPtr = null; + dataBuffer.AcquirePointer(ref dataPtr); + return DataEnqueue((nint)dataPtr, dataOffset, dataLength, info, infoOffset, infoLength); + } + } + + /// + /// This function enqueues a segment of application memory space for transmission. + /// + /// + /// This is an overload which will call DataEnqueue() with info set to null, infoOffset set to 0, and infoLength set to 0. + /// + /// The dataPtr is a pointer to the message to be transmitted. + /// Indicates the start of the message. Anything before it will not be sent. + /// Note: to send full message dataOffset should be set to 0. + /// Size of the message. + /// A NormData is returned which the application may use in other NORM API calls as needed. + /// Thrown when NormDataEnqueue() returns NORM_OBJECT_INVALID, indicating the failure to enqueue data. + /// Thrown when the data offset or data length are outside of the data buffer. + public NormData DataEnqueue(nint dataPtr, int dataOffset, int dataLength) + { + return DataEnqueue(dataPtr, dataOffset, dataLength, null, 0, 0); + } + + /// + /// This function enqueues a segment of application memory space for transmission. + /// + /// The dataPtr is a pointer to the message to be transmitted. + /// Indicates the start of the message. Anything before it will not be sent. + /// Note: to send full message dataOffset should be set to 0. + /// Size of the message. + /// The optional info and infoLength parameters are used to associate NORM_INFO content with the sent transport object. + /// Indicates the start of the message. + /// The optional info and infoLength parameters are used to associate NORM_INFO content with the sent transport object. + /// A NormData is returned which the application may use in other NORM API calls as needed. + /// Thrown when NormDataEnqueue() returns NORM_OBJECT_INVALID, indicating the failure to enqueue data. + /// Thrown when the data offset, data length, info offset or info length are outside of the associated buffer. + public NormData DataEnqueue(nint dataPtr, int dataOffset, int dataLength, byte[]? info, int infoOffset, int infoLength) + { + if (infoOffset < 0 || infoOffset >= info?.Length) + { + throw new ArgumentOutOfRangeException(nameof(infoOffset), "The info offset is out of range"); + } + if (info != null && infoLength < 1 || infoOffset + infoLength > info?.Length) + { + throw new ArgumentOutOfRangeException(nameof(infoLength), "The info length is out of range"); + } + + long objectHandle; + var infoHandle = GCHandle.Alloc(info, GCHandleType.Pinned); + + try + { + dataPtr += dataOffset; + var infoPtr = infoHandle.AddrOfPinnedObject() + infoOffset; + objectHandle = NormDataEnqueue(_handle, dataPtr, dataLength, infoPtr, infoLength); + if (objectHandle == NormObject.NORM_OBJECT_INVALID) + { + throw new IOException("Failed to enqueue data"); + } + } + finally + { + infoHandle.Free(); + } + + return new NormData(objectHandle); + } + + /// + /// This function opens a NORM_OBJECT_STREAM sender object and enqueues it for transmission. + /// + /// + /// No data is sent until subsequent calls to StreamWrite() are made unless + /// NORM_INFO content is specified for the stream with the info and infoLength parameters. Example usage of + /// NORM_INFO content for NORM_OBJECT_STREAM might include application-defined data typing or other information + /// which will enable NORM receiver applications to properly interpret the received stream as it is being received. + /// This is an overload which will call StreamOpen() with info set to null, infoOffset set to 0, and infoLength set to 0. + /// + /// + /// The bufferSize parameter controls the size of the stream's "repair window" + /// which limits how far back the sender will "rewind" to satisfy receiver repair requests. + /// + /// A NormStream is returned which the application may use in other NORM API calls as needed. + /// Thrown when NormStreamOpen() returns NORM_OBJECT_INVALID, indicating the failure to open stream. + public NormStream StreamOpen(long bufferSize) + { + return StreamOpen(bufferSize, null, 0, 0); + } + + /// + /// This function opens a NORM_OBJECT_STREAM sender object and enqueues it for transmission. + /// + /// + /// No data is sent until subsequent calls to NormStreamWrite() are made unless + /// NORM_INFO content is specified for the stream with the info and infoLength parameters. Example usage of + /// NORM_INFO content for NORM_OBJECT_STREAM might include application-defined data typing or other information + /// which will enable NORM receiver applications to properly interpret the received stream as it is being received. + /// + /// The bufferSize parameter controls the size of the stream's "repair window" + /// which limits how far back the sender will "rewind" to satisfy receiver repair requests. + /// Alloted memory space for transmitted information. + /// Indicates the start of the message. Anything before it will not be sent. + /// Note: to send full message infoOffset should be set to 0. + /// Size of the message. + /// A NormStream is returned which the application may use in other NORM API calls as needed. + /// Thrown when NormStreamOpen() returns NORM_OBJECT_INVALID, indicating the failure to open stream. + /// Thrown when the info offset or info length are outside of the info buffer. + public NormStream StreamOpen(long bufferSize, byte[]? info, int infoOffset, int infoLength) + { + if (infoOffset < 0 || infoOffset >= info?.Length) + { + throw new ArgumentOutOfRangeException(nameof(infoOffset), "The info offset is out of range"); + } + if (info != null && infoLength < 1 || infoOffset + infoLength > info?.Length) + { + throw new ArgumentOutOfRangeException(nameof(infoLength), "The info length is out of range"); + } + + long objectHandle; + var infoHandle = GCHandle.Alloc(info, GCHandleType.Pinned); + + try + { + var infoPtr = infoHandle.AddrOfPinnedObject() + infoOffset; + objectHandle = NormStreamOpen(_handle, bufferSize, infoPtr, infoLength); + if (objectHandle == NormObject.NORM_OBJECT_INVALID) + { + throw new IOException("Failed to open stream"); + } + } + finally + { + infoHandle.Free(); + } + + return new NormStream(objectHandle); + } + + /// + /// This function initiates the application's participation as a receiver within the NormSession. + /// + /// The bufferSpace parameter is used to set a limit on the amount of bufferSpace allocated + /// by the receiver per active NormSender within the session. + /// Thrown when NormStartReceiver() returns false, indicating the failure to start receiver. + public void StartReceiver(long bufferSpace) + { + if(!NormStartReceiver(_handle, bufferSpace)) + { + throw new IOException("Failed to start receiver"); + } + } + + /// + /// This function ends the application's participation as a receiver in the NormSession. + /// + public void StopReceiver() + { + NormStopReceiver(_handle); + } + + /// + /// This function sets the quantity of proactive "auto parity" NORM_DATA messages sent at the end of each FEC coding + /// block. By default (i.e., autoParity = 0), FEC content is sent only in response to repair requests (NACKs) from receivers. + /// + /// Setting a non-zero value for autoParity, the sender can automatically accompany each coding + /// block of transport object source data segments ((NORM_DATA messages) with the set number of FEC segments. + public void SetAutoParity(short autoParity) + { + NormSetAutoParity(_handle, autoParity); + } + + /// + /// This function sets the sender's maximum advertised GRTT value. + /// + /// The grttMax parameter, in units of seconds, limits the GRTT used by the group for scaling protocol timers, regardless + /// of larger measured round trip times. The default maximum for the NRL NORM library is 10 seconds. + public void SetGrttMax(double grttMax) + { + NormSetGrttMax(_handle, grttMax); + } + + /// + /// This function sets the sender's mode of probing for round trip timing measurement responses from the receiver. + /// + /// Possible values for the probingMode parameter include NORM_PROBE_NONE, NORM_PROBE_PASSIVE, and NORM_PROBE_ACTIVE. + public void SetGrttProbingMode(NormProbingMode probingMode) + { + NormSetGrttProbingMode(_handle, probingMode); + } + + /// + /// This function controls the sender GRTT measurement and estimation process. + /// The NORM sender multiplexes periodic transmission of NORM_CMD(CC) messages with its ongoing data transmission + /// or when data transmission is idle.When NORM congestion control operation is enabled, these probes are sent + /// once per RTT of the current limiting receiver(with respect to congestion control rate). In this case the intervalMin + /// and intervalMax parameters (in units of seconds) control the rate at which the sender's estimate of GRTT is updated. + /// + /// At session start, the estimate is updated at intervalMin and the update interval time is doubled until intervalMax is reached. + /// At session start, the estimate is updated at intervalMin and the update interval time is doubled until intervalMax is reached. + public void SetGrttProbingInterval(double intervalMin, double intervalMax) + { + NormSetGrttProbingInterval(_handle, intervalMin, intervalMax); + } + + /// + /// This function sets the sender's "backoff factor". + /// + /// The backoffFactor (in units of seconds) is used to scale various timeouts related to the NACK repair process. + public void SetBackoffFactor(double backoffFactor) + { + NormSetBackoffFactor(_handle, backoffFactor); + } + + /// + /// This function sets the sender's estimate of receiver group size. + /// + /// The sender advertises its groupSize setting to the receiver group in NORM protocol message + /// headers that, in turn, use this information to shape the distribution curve of their random timeouts for the timer-based, + /// probabilistic feedback suppression technique used in the NORM protocol. + public void SetGroupSize(long groupSize) + { + NormSetGroupSize(_handle, groupSize); + } + + /// + /// This routine sets the "robustness factor" used for various NORM sender functions. These functions include the + /// number of repetitions of "robustly-transmitted" NORM sender commands such as NORM_CMD(FLUSH) or similar + /// application-defined commands, and the number of attempts that are made to collect positive acknowledgement + /// from receivers.These commands are distinct from the NORM reliable data transmission process, but play a role + /// in overall NORM protocol operation. + /// + /// The default txRobustFactor value is 20. This relatively large value makes + /// the NORM sender end-of-transmission flushing and positive acknowledgement collection functions somewhat immune from packet loss. + /// Setting txRobustFactor to a value of -1 makes the redundant transmission of these commands continue indefinitely until completion. + public void SetTxRobustFactor(int txRobustFactor) + { + NormSetTxRobustFactor(_handle, txRobustFactor); + } + + /// + /// This function allows the application to resend (or reset transmission of) a NORM_OBJECT_FILE or NORM_OBJECT_DATA + /// transmit object that was previously enqueued for the session. + /// + /// The normObject parameter must be a valid transmit NormObject that has not yet been "purged" from the sender's transmit queue. + /// Thrown when NormRequeueObject() returns false, indicating the failure to requeue object. + public void RequeueObject(NormObject normObject) + { + if(!NormRequeueObject(_handle, normObject.Handle)) + { + throw new IOException("Failed to requeue object"); + } + } + + /// + /// This function specifies a "watermark" transmission point at which NORM sender protocol operation should perform + /// a flushing process and/or positive acknowledgment collection for a given sessionHandle. + /// + /// This is an overload which will call the SetWatermark() override with overrideFlush set as false. + /// The normObject parameter must be a valid transmit NormObject that has not yet been "purged" from the sender's transmit queue. + /// Thrown when NormSetWatermark() returns false, indicating the failure to set watermark. + public void SetWatermark(NormObject normObject) + { + SetWatermark(normObject, false); + } + + /// + /// This function specifies a "watermark" transmission point at which NORM sender protocol operation should perform + /// a flushing process and/or positive acknowledgment collection for a given sessionHandle. + /// + /// The normObject parameter must be a valid transmit NormObject that has not yet been "purged" from the sender's transmit queue. + /// The optional overrideFlush parameter, when set to true, causes the watermark acknowledgment process that is + /// established with this function call to potentially fully supersede the usual NORM end-of-transmission flushing + /// process that occurs. If overrideFlush is set and the "watermark" transmission point corresponds to the last + /// transmission that will result from data enqueued by the sending application, then the watermark flush completion + /// will terminate the usual flushing process + /// Thrown when NormSetWatermark() returns false, indicating the failure to set watermark. + public void SetWatermark(NormObject normObject, bool overrideFlush) + { + if(!NormSetWatermark(_handle, normObject.Handle, overrideFlush)) + { + throw new IOException("Failed to set watermark"); + } + } + + /// + /// This function cancels any "watermark" acknowledgement request that was previously set via the SetWatermark() function. + /// + public void CancelWatermark() + { + NormCancelWatermark(_handle); + } + + /// Thrown when NormResetWatermark() returns false, indicating the failure to reset watermark. + public void ResetWatermark() + { + if(!NormResetWatermark(_handle)) + { + throw new IOException("Failed to reset watermark"); + } + } + + /// + /// When this function is called, the specified nodeId is added to the list of NormNodeId values (i.e., the "acking node" + /// list) used when NORM sender operation performs positive acknowledgement (ACK) collection. + /// + /// Identifies the application's presence in the NormSession. + /// Thrown when NormAddAckingNode() returns false, indicating the failure to add acking node. + public void AddAckingNode(long nodeId) + { + if(!NormAddAckingNode(_handle, nodeId)) + { + throw new IOException("Failed to add acking node"); + } + } + + /// + /// This function deletes the specified nodeId from the list of NormNodeId values used when NORM sender operation + /// performs positive acknowledgement (ACK) collection. + /// + /// Identifies the application's presence in the NormSession. + public void RemoveAckingNode(long nodeId) + { + NormRemoveAckingNode(_handle, nodeId); + } + + /// + /// This function queries the status of the watermark flushing process and/or positive acknowledgment collection + /// initiated by a prior call to SetWatermark(). + /// + /// Identifies the application's presence in the NormSession. + /// + /// Possible return values include: + /// NORM_ACK_INVALID - The given sessionHandle is invalid or the given nodeId is not in the sender's acking list. + /// NORM_ACK_FAILURE - The positive acknowledgement collection process did not receive acknowledgment from every listed receiver (nodeId = NORM_NODE_ANY) or the identified nodeId did not respond. + /// NORM_ACK_PENDING - The flushing process at large has not yet completed (nodeId = NORM_NODE_ANY) or the given individual nodeId is still being queried for response. + /// NORM_ACK_SUCCESS - All receivers (nodeId = NORM_NODE_ANY) responded with positive acknowledgement or the given specific nodeId did acknowledge. + /// + public NormAckingStatus GetAckingStatus(long nodeId) + { + return NormGetAckingStatus(_handle, nodeId); + } + + /// + /// This function enqueues a NORM application-defined command for transmission. + /// + /// The cmdBuffer parameter points to a buffer containing the application-defined command content that will be contained in the NORM_CMD(APPLICA-TION) message payload. + /// + /// The cmdLength indicates the length of this content (in bytes) and MUST be less than or equal to the segmentLength value for the given session. + /// The command is NOT delivered reliably, + /// but can be optionally transmitted with repetition (once per GRTT) according to the NORM transmit robust factor + /// value for the given session if the robust parameter is set to true. + /// Thrown when NormSendCommand() returns false, indicating the failure to send command. + /// Thrown when the offset or length are outside of the buffer. + public void SendCommand(byte[] cmdBuffer, int cmdOffset, int cmdLength, bool robust) + { + if (cmdOffset < 0 || cmdOffset >= cmdBuffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(cmdOffset), "The offset is out of range"); + } + if (cmdLength < 1 || cmdOffset + cmdLength > cmdBuffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(cmdLength), "The command length is out of range"); + } + + var commandHandle = GCHandle.Alloc(cmdBuffer, GCHandleType.Pinned); + + try + { + var cmdPtr = commandHandle.AddrOfPinnedObject() + cmdOffset; + if (!NormSendCommand(_handle, cmdPtr, cmdLength, robust)) + { + throw new IOException("Failed to send command"); + } + } + finally + { + commandHandle.Free(); + } + } + + /// + /// This function terminates any pending NORM_CMD(APPLICATION) transmission that was previously initiated with the SendCommand() call. + /// + public void CancelCommand() + { + NormCancelCommand(_handle); + } + + /// + /// This function sets a limit on the number of outstanding (pending) NormObjects for which a receiver will keep state on a per-sender basis. + /// + /// The value countMax sets a limit on the maximum consecutive range of objects that can be pending. + public void SetRxCacheLimit(int countMax) + { + NormSetRxCacheLimit(_handle, countMax); + } + + /// + /// This function allows the application to set an alternative, non-default buffer size for the UDP socket for packet reception. + /// + /// The bufferSize parameter specifies the socket buffer size in bytes. + /// Thrown when NormSetRxSocketBuffer() returns false, indicating the failure to set rx socket buffer. + public void SetRxSocketBuffer(long bufferSize) + { + if(!NormSetRxSocketBuffer(_handle, bufferSize)) + { + throw new IOException("Failed to set rx socket buffer"); + } + } + + /// + /// This function provides the option to configure a NORM receiver application as a "silent receiver". This mode of + /// receiver operation dictates that the host does not generate any protocol messages while operating as a receiver. + /// + /// Setting the silent parameter to true enables silent receiver operation while + /// setting it to false results in normal protocol operation where feedback is provided as needed for reliability and + /// protocol operation. + /// When the maxDelay parameter is set to a non-negative value, the value determines the maximum number + /// of FEC coding blocks (according to a NORM sender's current transmit position) the receiver will cache an incompletely-received + /// FEC block before giving the application the (incomplete) set of received source segments. + public void SetSilentReceiver(bool silent, int maxDelay) + { + NormSetSilentReceiver(_handle, silent, maxDelay); + } + + /// + /// This function controls the default behavior determining the destination of receiver feedback messages generated + /// while participating in the session. + /// + /// If the enable parameter is true, "unicast NACKing" is enabled for new remote + /// senders while it is disabled for state equal to false. + public void SetDefaultUnicastNack(bool enable) + { + NormSetDefaultUnicastNack(_handle, enable); + } + + /// + /// This function sets the default "synchronization policy" used when beginning (or restarting) reception of objects + /// from a remote sender (i.e., "syncing" to the sender). + /// + /// The "synchronization policy" is the behavior observed by the receiver with regards to what objects it attempts to reliably receive + /// (via transmissions of Negative Acknowledgements to the sender(s) or group as needed). + public void SetDefaultSyncPolicy(NormSyncPolicy syncPolicy) + { + NormSetDefaultSyncPolicy(_handle, syncPolicy); + } + + /// + /// This function sets the default "nacking mode" used when receiving objects. + /// This allows the receiver application some control of its degree of participation in the repair process. By limiting receivers + /// to only request repair of objects in which they are really interested in receiving, some overall savings in unnecessary + /// network loading might be realized for some applications and users. + /// + /// Specifies the nacking mode. + public void SetDefaultNackingMode(NormNackingMode nackingMode) + { + NormSetDefaultNackingMode(_handle, nackingMode); + } + + /// + /// This function allows the receiver application to customize at what points the receiver + /// initiates the NORM NACK repair process during protocol operation. + /// + /// Specifies the repair boundary. + public void SetDefaultRepairBoundary(NormRepairBoundary repairBoundary) + { + NormSetDefaultRepairBoundary(_handle, repairBoundary); + } + + /// + /// This routine controls how persistently NORM receivers will maintain state for sender(s) and continue to request + /// repairs from the sender(s) even when packet reception has ceased. + /// + /// The rxRobustFactor value determines how + /// many times a NORM receiver will self-initiate NACKing (repair requests) upon cessation of packet reception from + /// a sender. The default value is 20. Setting rxRobustFactor to -1 will make the NORM receiver infinitely persistent + /// (i.e., it will continue to NACK indefinitely as long as it is missing data content). + public void SetDefaultRxRobustFactor(int rxRobustFactor) + { + NormSetDefaultRxRobustFactor(_handle, rxRobustFactor); + } + } +} \ No newline at end of file diff --git a/src/dotnet/src/Mil.Navy.Nrl.Norm/NormStream.cs b/src/dotnet/src/Mil.Navy.Nrl.Norm/NormStream.cs new file mode 100644 index 00000000..1b66637d --- /dev/null +++ b/src/dotnet/src/Mil.Navy.Nrl.Norm/NormStream.cs @@ -0,0 +1,205 @@ +namespace Mil.Navy.Nrl.Norm +{ + /// + /// A transport object of type NORM_OBJECT_STREAM. + /// + public class NormStream : NormObject + { + /// + /// This boolean tells whether the transmit stream, specified by the streamHandle parameter, + /// has buffer space available so that the application may successfully make a call to NormStreamWrite(). + /// + public bool HasVacancy + { + get + { + return NormStreamHasVacancy(_handle); + } + } + + /// + /// The current read offset value for the receive stream. + /// + public long ReadOffset + { + get + { + return NormStreamGetReadOffset(_handle); + } + } + + /// + /// Internal constructor for NormStream + /// + /// The handle is associated to the NORM protocol engine instance + internal NormStream(long handle) : base(handle) + { + } + + /// + /// This function enqueues data for transmission within the NORM stream. + /// + /// The buffer parameter must be a pointer to the data to be enqueued. + /// The offset indicated where in the buffer to start writing the data. + /// Note: If the data is written in its entirety, offset should be set to 0. + /// The length parameter indicates the length of the data content. + /// This function returns the number of bytes of data successfully enqueued for NORM stream transmission. + /// Thrown when the offset or length are outside of the buffer. + public int Write(byte[] buffer, int offset, int length) + { + if (offset < 0 || offset >= buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(offset), "The offset is out of range"); + } + if (length < 1 || offset + length > buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(length), "The length is out of range"); + } + + int numBytes; + unsafe + { + fixed (byte* bufferPtr = buffer) + { + numBytes = NormStreamWrite(_handle, bufferPtr + offset, length); + } + } + + return numBytes; + } + + /// + /// This function allows the application to indicate to the NORM protocol engine that the last data successfully written + /// to the stream indicated by streamHandle corresponded to the end of an application-defined message boundary. + /// + public void MarkEom() + { + NormStreamMarkEom(_handle); + } + + /// + /// This function causes an immediate "flush" of the transmit stream. + /// + /// The optional eom parameter, when set to true, allows the sender application to mark an end-of-message indication + /// (see NormStreamMarkEom()) for the stream and initiate flushing in a single function call. + /// The default stream "flush" operation invoked via + /// NormStreamFlush() for flushMode equal to NORM_FLUSH_PASSIVE causes NORM to immediately transmit all + /// enqueued data for the stream(subject to session transmit rate limits), even if this results in NORM_DATA messages + /// with "small" payloads.If the optional flushMode parameter is set to NORM_FLUSH_ACTIVE, the application can + /// achieve reliable delivery of stream content up to the current write position in an even more proactive fashion.In + /// this case, the sender additionally, actively transmits NORM_CMD(FLUSH) messages after any enqueued stream + /// content has been sent. This immediately prompt receivers for repair requests which reduces latency of reliable + /// delivery, but at a cost of some additional messaging. Note any such "active" flush activity will be terminated upon + /// the next subsequent write to the stream.If flushMode is set to NORM_FLUSH_NONE, this call has no effect other than + /// the optional end-of-message marking described here. + public void Flush(bool eom, NormFlushMode flushMode) + { + NormStreamFlush(_handle, eom, flushMode); + } + + /// + /// This function causes an immediate "flush" of the transmit stream. + /// + /// + /// This is an overload which calls Flush() with eom set as false and flushMode set to NORM_FLUSH_PASSIVE + /// + public void Flush() + { + Flush(false, NormFlushMode.NORM_FLUSH_PASSIVE); + } + + /// + /// This function halts transfer of the stream and releases any resources used unless the associated object + /// has been explicitly retained by a call to NormObjectRetain(). + /// + /// The optional graceful parameter, when + /// set to a value of true, may be used by NORM senders to initiate "graceful" shutdown of a transmit stream. + public void Close(bool graceful) + { + NormStreamClose(_handle, graceful); + } + + /// + /// This function halts transfer of the stream and releases any resources used unless the associated object + /// has been explicitly retained by a call to NormObjectRetain(). + /// + /// + /// This is an overload which calls Close() with graceful set as false. + /// + public void Close() + { + Close(false); + } + + /// + /// This function can be used by the receiver application to read any available data from an incoming NORM stream. + /// + /// The buffer parameter must be a pointer to an array where the received + /// data can be stored of a length as referenced by the length parameter. + /// Indicates where in the buffer to start reading the data. + /// Note: To read the data in its entirety, begin at offset 0. + /// Expected length of data received + /// The length of data received + /// Thrown when the offset or length are outside of the buffer. + public int Read(byte[] buffer, int offset, int length) + { + if (offset < 0 || offset >= buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(offset), "The offset is out of range"); + } + if (length < 1 || offset + length > buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(length), "The length is out of range"); + } + + unsafe + { + fixed (byte* bufferPtr = buffer) + { + if (!NormStreamRead(_handle, bufferPtr + offset, ref length)) + { + length = -1; + } + } + } + + return length; + } + + /// + /// This function advances the read offset of the receive stream referenced by the streamHandle parameter to align + /// with the next available message boundary + /// + /// + /// This function returns a value of true when start-of-message is found. The next call to NormStreamRead() will + /// retrieve data aligned with the message start. If no new message boundary is found in the buffered receive data for + /// the stream, the function returns a value of false. In this case, the application should defer repeating a call to this + /// function until a subsequent NORM_RX_OBJECT_UPDATE notification is posted. + public bool SeekMsgStart() + { + return NormStreamSeekMsgStart(_handle); + } + + /// + /// This function controls how the NORM API behaves when the application attempts to enqueue new stream data for transmission + /// when the associated stream's transmit buffer is fully occupied with data pending original or repair transmission. + /// + /// By default (pushEnable = false), a call to NormStreamWrite() will return a zero value under this + /// condition, indicating it was unable to enqueue the new data. However, if pushEnable is set to true for a given + /// streamHandle, the NORM protocol engine will discard the oldest buffered stream data(even if it is pending repair + /// transmission or has never been transmitted) as needed to enqueue the new data. + public void SetPushEnable(bool pushEnable) + { + NormStreamSetPushEnable(_handle, pushEnable); + } + + /// + /// This function sets "automated flushing" for the NORM transmit stream indicated by the streamHandle parameter. + /// + /// Possible values for the flushMode parameter include NORM_FLUSH_NONE, NORM_FLUSH_PASSIVE, and NORM_FLUSH_ACTIVE. + public void SetAutoFlush(NormFlushMode flushMode) + { + NormStreamSetAutoFlush(_handle, flushMode); + } + } +} diff --git a/src/dotnet/src/Mil.Navy.Nrl.Norm/Usings.cs b/src/dotnet/src/Mil.Navy.Nrl.Norm/Usings.cs new file mode 100644 index 00000000..830537cc --- /dev/null +++ b/src/dotnet/src/Mil.Navy.Nrl.Norm/Usings.cs @@ -0,0 +1,3 @@ +global using static Mil.Navy.Nrl.Norm.NormApi; +global using NormEvent = Mil.Navy.Nrl.Norm.NormEvent; +global using Mil.Navy.Nrl.Norm.Enums; \ No newline at end of file diff --git a/src/dotnet/tests/Mil.Navy.Nrl.Norm.IntegrationTests/Mil.Navy.Nrl.Norm.IntegrationTests.csproj b/src/dotnet/tests/Mil.Navy.Nrl.Norm.IntegrationTests/Mil.Navy.Nrl.Norm.IntegrationTests.csproj new file mode 100644 index 00000000..09bff02a --- /dev/null +++ b/src/dotnet/tests/Mil.Navy.Nrl.Norm.IntegrationTests/Mil.Navy.Nrl.Norm.IntegrationTests.csproj @@ -0,0 +1,34 @@ + + + + net6.0 + enable + enable + false + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + diff --git a/src/dotnet/tests/Mil.Navy.Nrl.Norm.IntegrationTests/NormInstanceTests.cs b/src/dotnet/tests/Mil.Navy.Nrl.Norm.IntegrationTests/NormInstanceTests.cs new file mode 100644 index 00000000..60bd071d --- /dev/null +++ b/src/dotnet/tests/Mil.Navy.Nrl.Norm.IntegrationTests/NormInstanceTests.cs @@ -0,0 +1,266 @@ +using Bogus; +using Mil.Navy.Nrl.Norm.Buffers; +using System.Text; + +namespace Mil.Navy.Nrl.Norm.IntegrationTests +{ + /// + /// Tests for NORM instance + /// + public class NormInstanceTests : IDisposable + { + /// + /// The NORM instance + /// + private NormInstance _normInstance; + private NormSession? _normSession; + /// + /// Determines if the NORM instance has been destroyed + /// + private bool _isDestroyed; + + private string _testPath; + + private void CreateTestDirectory() + { + if (!Directory.Exists(_testPath)) + { + Directory.CreateDirectory(_testPath); + } + } + + private void DeleteTestDirectory() + { + if (Directory.Exists(_testPath)) + { + Directory.Delete(_testPath, true); + } + } + + /// + /// Default constructor for NORM instance tests + /// + /// + /// Creates the NORM instance. + /// Initializes _isDestroyed to false. + /// + public NormInstanceTests() + { + _normInstance = new NormInstance(); + _isDestroyed = false; + var currentDirectory = Directory.GetCurrentDirectory(); + _testPath = Path.Combine(currentDirectory, Guid.NewGuid().ToString()); + CreateTestDirectory(); + } + + /// + /// Destroy the NORM instance + /// + private void DestroyInstance() + { + if (!_isDestroyed) + { + _normSession?.DestroySession(); + _normInstance.CloseDebugLog(); + _normInstance.DestroyInstance(); + _isDestroyed = true; + } + } + + /// + /// Dispose destroys the NORM instance + /// + public void Dispose() + { + DestroyInstance(); + DeleteTestDirectory(); + } + + /// + /// Test for creating a NORM instance + /// + [Fact] + public void CreatesNormInstance() + { + Assert.NotNull(_normInstance); + } + + /// + /// Test for creating a NORM instance + /// + [Fact] + public void CreatesNormInstanceWithPriorityBoost() + { + _normInstance.DestroyInstance(); + _normInstance = new NormInstance(true); + Assert.NotNull(_normInstance); + } + + /// + /// Test for destroying a NORM instance + /// + [Fact] + public void DestroysNormInstance() + { + DestroyInstance(); + } + + /// + /// Test for creating a NORM session + /// + [Fact] + public void CreatesSession() + { + var faker = new Faker(); + var sessionAddress = "224.1.2.3"; + var sessionPort = faker.Internet.Port(); + var localNodeId = NormNode.NORM_NODE_ANY; + + _normSession = _normInstance.CreateSession(sessionAddress, sessionPort, localNodeId); + Assert.NotNull(_normSession); + } + + /// + /// Test for throwing an exception when attempting to create a NORM session with an invalid session address + /// + [Fact] + public void CreateSessionThrowsExceptionForInvalidSessionAddress() + { + var sessionAddress = "999.999.999.999"; + var sessionPort = 6003; + var localNodeId = NormNode.NORM_NODE_ANY; + + Assert.Throws(() => _normInstance.CreateSession(sessionAddress, sessionPort, localNodeId)); + } + + [Fact] + public void StopInstance() + { + var sessionAddress = "224.1.2.3"; + var sessionPort = 6003; + var localNodeId = NormNode.NORM_NODE_ANY; + + _normSession = _normInstance.CreateSession(sessionAddress, sessionPort, localNodeId); + + _normInstance.StopInstance(); + } + + [Fact] + public void RestartInstance() + { + var sessionAddress = "224.1.2.3"; + var sessionPort = 6003; + var localNodeId = NormNode.NORM_NODE_ANY; + var expected = true; + _normSession = _normInstance.CreateSession(sessionAddress, sessionPort, localNodeId); + + _normInstance.StopInstance(); + + var actual = _normInstance.RestartInstance(); + Assert.Equal(expected,actual); + } + + [Fact] + public void SuspendInstance() + { + var sessionAddress = "224.1.2.3"; + var sessionPort = 6003; + var localNodeId = NormNode.NORM_NODE_ANY; + var expected = true; + _normSession = _normInstance.CreateSession(sessionAddress, sessionPort, localNodeId); + + _normInstance.StopInstance(); + + var actual = _normInstance.SuspendInstance(); + Assert.Equal(expected,actual); + } + [Fact] + public void ResumeInstance() + { + var sessionAddress = "224.1.2.3"; + var sessionPort = 6003; + var localNodeId = NormNode.NORM_NODE_ANY; + _normSession = _normInstance.CreateSession(sessionAddress, sessionPort, localNodeId); + + _normInstance.StopInstance(); + + _normInstance.ResumeInstance(); + Assert.NotNull(_normInstance); + } + [Fact] + public void OpenDebugLog(){ + var fileName = Guid.NewGuid().ToString(); + var filePath = Path.Combine(_testPath, fileName); + + _normInstance.OpenDebugLog(filePath); + } + + [Fact] + public void CloseDebugLog() + { + _normInstance.CloseDebugLog(); + } + + [Fact] + public void OpensDebugPipe() + { + var pipename = "test.pipe"; + Assert.Throws(() => _normInstance.OpenDebugPipe(pipename)); + } + + [Fact] + public void SetsDebugLevel() + { + var expectedDebugLevel = 12; + _normInstance.DebugLevel = expectedDebugLevel; + var actualDebugLevel = _normInstance.DebugLevel; + Assert.Equal(expectedDebugLevel, actualDebugLevel); + } + + [SkippableFact(typeof(IOException))] + public void HasEventsFromTimeSpan() + { + var faker = new Faker(); + var sessionAddress = "224.1.2.3"; + var sessionPort = faker.Internet.Port(); + var localNodeId = NormNode.NORM_NODE_ANY; + + _normSession = _normInstance.CreateSession(sessionAddress, sessionPort, localNodeId); + + _normSession.StartSender(1024 * 1024, 1400, 64, 16); + + var dataContent = faker.Lorem.Paragraph(); + var data = Encoding.ASCII.GetBytes(dataContent); + using var dataBuffer = ByteBuffer.AllocateDirect(data.Length); + dataBuffer.WriteArray(0, data, 0, data.Length); + _normSession.DataEnqueue(dataBuffer, 0, data.Length); + + Assert.True(_normInstance.HasNextEvent(TimeSpan.FromSeconds(1.5))); + + _normSession.StopSender(); + } + + [SkippableFact(typeof(IOException))] + public void HasEventsFromSecondsAndMicroseconds() + { + var faker = new Faker(); + var sessionAddress = "224.1.2.3"; + var sessionPort = faker.Internet.Port(); + var localNodeId = NormNode.NORM_NODE_ANY; + + _normSession = _normInstance.CreateSession(sessionAddress, sessionPort, localNodeId); + + _normSession.StartSender(1024 * 1024, 1400, 64, 16); + + var dataContent = faker.Lorem.Paragraph(); + var data = Encoding.ASCII.GetBytes(dataContent); + using var dataBuffer = ByteBuffer.AllocateDirect(data.Length); + dataBuffer.WriteArray(0, data, 0, data.Length); + _normSession.DataEnqueue(dataBuffer, 0, data.Length); + + Assert.True(_normInstance.HasNextEvent(1, 500000)); + + _normSession.StopSender(); + } + } +} diff --git a/src/dotnet/tests/Mil.Navy.Nrl.Norm.IntegrationTests/NormSessionTests.cs b/src/dotnet/tests/Mil.Navy.Nrl.Norm.IntegrationTests/NormSessionTests.cs new file mode 100644 index 00000000..39d24d5c --- /dev/null +++ b/src/dotnet/tests/Mil.Navy.Nrl.Norm.IntegrationTests/NormSessionTests.cs @@ -0,0 +1,2810 @@ +using Bogus; +using Mil.Navy.Nrl.Norm.Buffers; +using Mil.Navy.Nrl.Norm.Enums; +using System.Net; +using System.Text; + +namespace Mil.Navy.Nrl.Norm.IntegrationTests +{ + public class NormSessionTests : IDisposable + { + private readonly NormInstance _normInstance; + private NormSession _normSession; + private bool _isInstanceDestroyed; + private bool _isSessionDestroyed; + private bool _isSenderStarted; + private bool _isSenderStopped; + + private bool _isReceiverStarted; + private bool _isReceiverStopped; + + private string _testPath; + + /// + /// Create a NORM session + /// + private NormSession CreateSession() + { + var faker = new Faker(); + var sessionAddress = "224.1.2.3"; + var sessionPort = faker.Internet.Port(); + var localNodeId = NormNode.NORM_NODE_ANY; + + return _normInstance.CreateSession(sessionAddress, sessionPort, localNodeId); + } + + private void CreateTestDirectory() + { + if (!Directory.Exists(_testPath)) + { + Directory.CreateDirectory(_testPath); + } + } + + private void DeleteTestDirectory() + { + if (Directory.Exists(_testPath)) + { + Directory.Delete(_testPath, true); + } + } + + public NormSessionTests() + { + _normInstance = new NormInstance(); + _normSession = CreateSession(); + _isInstanceDestroyed = false; + _isSessionDestroyed = false; + _isSenderStarted = false; + _isSenderStopped = false; + var currentDirectory = Directory.GetCurrentDirectory(); + _testPath = Path.Combine(currentDirectory, Guid.NewGuid().ToString()); + CreateTestDirectory(); + } + + private void StartSender() + { + if (!_isSenderStarted) + { + _normSession.StartSender(1024 * 1024, 1400, 64, 16); + _isSenderStarted = true; + } + } + + private void StopSender() + { + if (_isSenderStarted && !_isSenderStopped) + { + _normSession.StopSender(); + _isSenderStopped = true; + } + } + + private void StartReceiver() + { + if (!_isReceiverStarted) + { + //The appropriate bufferSpace to use is a function of expected network delay * bandwidth product and packet loss characteristics + _normSession.StartReceiver(10 * 10); + _isReceiverStarted = true; + } + } + + private void StopReceiver() + { + if (_isReceiverStarted && !_isReceiverStopped) + { + _normSession.StopReceiver(); + _isReceiverStopped = true; + } + } + + /// + /// Destroys the NORM session + /// + private void DestroySession() + { + if (!_isSessionDestroyed) + { + StopSender(); + _normSession.DestroySession(); + _isSessionDestroyed = true; + } + } + + /// + /// Destroy the NORM instance + /// + private void DestroyInstance() + { + if (!_isInstanceDestroyed) + { + DestroySession(); + _normInstance.DestroyInstance(); + _isInstanceDestroyed = true; + } + } + + /// + /// Dispose destroys the NORM instance + /// + public void Dispose() + { + DestroyInstance(); + DeleteTestDirectory(); + } + + /// + /// Test for creating a NORM session + /// + [Fact] + public void CreatesSession() + { + Assert.NotNull(_normSession); + } + + /// + /// Test for destroying a NORM session + /// + [Fact] + public void DestroysSession() + { + DestroySession(); + } + + /// + /// Test for starting a NORM sender + /// + [SkippableFact(typeof(IOException))] + public void StartsSender() + { + StartSender(); + } + + /// + /// Test for stopping a NORM sender + /// + [SkippableFact(typeof(IOException))] + public void StopsSender() + { + StartSender(); + StopSender(); + } + + [SkippableFact(typeof(IOException))] + public void StartsReceiver() + { + StartReceiver(); + } + + [SkippableFact(typeof(IOException))] + public void StopsReceiver() + { + StartReceiver(); + StopReceiver(); + } + + /// + /// Generates text content + /// + /// The generated text content + private static string GenerateTextContent() + { + var faker = new Faker(); + return faker.Lorem.Paragraph(); + } + + /// + /// Generates info content + /// + /// The generated info content + private static string GenerateInfoContent() + { + var faker = new Faker(); + return faker.Lorem.Sentence(); + } + + private IEnumerable GetEvents(TimeSpan delayTime) + { + var normEvents = new List(); + while (_normInstance.HasNextEvent(delayTime)) + { + var normEvent = _normInstance.GetNextEvent(false); + if (normEvent != null) + { + normEvents.Add(normEvent); + } + } + return normEvents; + } + + private IEnumerable GetEvents() + { + return GetEvents(TimeSpan.FromMilliseconds(30)); + } + + private void WaitForEvents(TimeSpan delayTime) + { + GetEvents(delayTime); + } + + private void WaitForEvents() + { + GetEvents(); + } + + private void AssertNormEvents(IEnumerable normEvents) + { + foreach (var normEvent in normEvents) + { + var normNode = normEvent.Node; + if (normNode != null) + { + var actualId = normNode.Id; + Assert.NotEqual(NormNode.NORM_NODE_NONE, actualId); + var expectedIpAddresses = Dns.GetHostAddresses(Dns.GetHostName()) + .Where(i => i.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork && !IPAddress.IsLoopback(i)); + var actualAddress = normNode.Address; + Assert.NotNull(actualAddress); + Assert.Contains(actualAddress.Address, expectedIpAddresses); + Assert.NotEqual(default, actualAddress.Port); + var actualGrtt = normNode.Grtt; + Assert.NotEqual(-1, actualGrtt); + var expectedEventString = $"NormEvent [type={normEvent.Type}]"; + var actualEventString = normEvent.ToString(); + Assert.Equal(expectedEventString, actualEventString); + } + } + } + + public static IEnumerable GenerateInfo() + { + var info = new List(); + + var infoContent = GenerateInfoContent(); + var expectedInfoContent = infoContent; + var infoOffset = 0; + var infoLength = infoContent.Length; + info.Add(new object?[] { infoContent, expectedInfoContent, infoOffset, infoLength }); + + var faker = new Faker(); + infoLength = faker.Random.Int(infoContent.Length / 2, infoContent.Length - 1); + expectedInfoContent = infoContent.Substring(infoOffset, infoLength); + info.Add(new object?[] { infoContent, expectedInfoContent, infoOffset, infoLength }); + + infoOffset = faker.Random.Int(1, (infoContent.Length - 1) / 2); + infoLength = infoContent.Length - infoOffset; + expectedInfoContent = infoContent.Substring(infoOffset, infoLength); + info.Add(new object?[] { infoContent, expectedInfoContent, infoOffset, infoLength }); + + infoOffset = faker.Random.Int(1, (infoContent.Length - 1) / 2); + infoLength = faker.Random.Int(1, infoContent.Length - infoOffset); + expectedInfoContent = infoContent.Substring(infoOffset, infoLength); + info.Add(new object?[] { infoContent, expectedInfoContent, infoOffset, infoLength }); + + info.Add(new object?[] { null, null, null, null }); + + info.Add(new object?[] { null, "", 0, 0 }); + + return info; + } + + [SkippableTheory(typeof(IOException))] + [MemberData(nameof(GenerateInfo))] + public void EnqueuesFile(string? infoContent = null, string? expectedInfoContent = null, int? infoOffset = null, int? infoLength = null) + { + StartSender(); + + var fileContent = GenerateTextContent(); + var fileName = Guid.NewGuid().ToString(); + var filePath = Path.Combine(_testPath, fileName); + File.WriteAllText(filePath, fileContent); + //Create info to enqueue + byte[]? info = null; + if (infoContent != null) { + info = Encoding.ASCII.GetBytes(infoContent); + } + else if (expectedInfoContent == null) + { + expectedInfoContent = filePath; + } + + var expectedInfo = Array.Empty(); + if (expectedInfoContent != null) + { + expectedInfo = Encoding.ASCII.GetBytes(expectedInfoContent); + } + + try + { + var normFile = infoOffset != null && infoLength != null ? + _normSession.FileEnqueue(filePath, info, infoOffset.Value, infoLength.Value) : + _normSession.FileEnqueue(filePath); + Assert.NotNull(normFile); + var expectedEventTypes = new List { NormEventType.NORM_TX_OBJECT_SENT, NormEventType.NORM_TX_QUEUE_EMPTY }; + var actualEvents = GetEvents(); + var actualEventTypes = actualEvents.Select(e => e.Type).ToList(); + Assert.Equal(expectedEventTypes, actualEventTypes); + var expectedFileName = filePath; + var actualFileName = normFile.Name; + Assert.Equal(expectedFileName, actualFileName); + var actualInfo = normFile.Info; + Assert.Equal(expectedInfo, actualInfo); + if (actualInfo != null) + { + var actualInfoContent = Encoding.ASCII.GetString(actualInfo); + Assert.Equal(expectedInfoContent, actualInfoContent); + } + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + } + } + + public static IEnumerable GenerateOutOfRangeInfo() + { + var info = new List(); + var faker = new Faker(); + + var infoContent = GenerateInfoContent(); + var infoOffset = faker.Random.Int(-infoContent.Length, -1); + var infoLength = infoContent.Length; + info.Add(new object[] { infoContent, infoOffset, infoLength }); + + infoOffset = faker.Random.Int(infoContent.Length, infoContent.Length * 2); + infoLength = infoContent.Length; + info.Add(new object[] { infoContent, infoOffset, infoLength }); + + infoOffset = 0; + infoLength = faker.Random.Int(infoContent.Length + 1, infoContent.Length * 2); + info.Add(new object[] { infoContent, infoOffset, infoLength }); + + infoLength = -1; + info.Add(new object[] { infoContent, infoOffset, infoLength }); + + infoLength = 0; + info.Add(new object[] { infoContent, infoOffset, infoLength }); + + infoOffset = infoContent.Length - 1; + infoLength = infoContent.Length; + info.Add(new object[] { infoContent, infoOffset, infoLength }); + + return info; + } + + [SkippableTheory(typeof(IOException))] + [MemberData(nameof(GenerateOutOfRangeInfo))] + public void EnqueuesFileThrowsExceptionWhenOutOfRange(string? infoContent = null, int? infoOffset = null, int? infoLength = null) + { + StartSender(); + + var fileContent = GenerateTextContent(); + var fileName = Guid.NewGuid().ToString(); + var filePath = Path.Combine(_testPath, fileName); + File.WriteAllText(filePath, fileContent); + //Create info to enqueue + var info = infoContent != null ? Encoding.ASCII.GetBytes(infoContent) : null; + + try + { + Assert.Throws(() => + infoOffset != null && infoLength != null ? + _normSession.FileEnqueue(filePath, info, infoOffset.Value, infoLength.Value) : + _normSession.FileEnqueue(filePath)); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + } + } + + [SkippableTheory(typeof(IOException))] + [MemberData(nameof(GenerateInfo))] + public void ReceivesFile(string? infoContent = null, string? expectedInfoContent = null, int? infoOffset = null, int? infoLength = null) + { + _normSession.SetLoopback(true); + StartSender(); + StartReceiver(); + + //Set up cache directory + var folderName = Guid.NewGuid().ToString(); + var cachePath = Path.Combine(_testPath, folderName); + Directory.CreateDirectory(cachePath); + _normInstance.SetCacheDirectory(cachePath); + + //Set up file to send + var fileName = Guid.NewGuid().ToString(); + var fileContent = GenerateTextContent(); + var filePath = Path.Combine(_testPath, fileName); + File.WriteAllText(filePath, fileContent); + //Create info to enqueue + byte[]? info = null; + if (infoContent != null) { + info = Encoding.ASCII.GetBytes(infoContent); + } + else if (expectedInfoContent == null) + { + expectedInfoContent = filePath; + } + var expectedInfo = Array.Empty(); + if (expectedInfoContent != null) + { + expectedInfo = Encoding.ASCII.GetBytes(expectedInfoContent); + } + + try + { + //Enqueue file + var normFile = infoOffset != null && infoLength != null ? + _normSession.FileEnqueue(filePath, info, infoOffset.Value, infoLength.Value) : + _normSession.FileEnqueue(filePath); + //Wait for events + var normEvents = GetEvents(); + AssertNormEvents(normEvents); + + var expectedNormEventType = NormEventType.NORM_RX_OBJECT_COMPLETED; + Assert.Contains(expectedNormEventType, normEvents.Select(e => e.Type)); + var normObjectEvent = normEvents.First(e => e.Type == expectedNormEventType); + + var receivedNormFile = Assert.IsType(normObjectEvent.Object); + var expectedFileName = receivedNormFile.Name; + + //Check that file exists + var expectedFileCount = 1; + var actualFiles = Directory.GetFiles(cachePath); + var actualFileCount = actualFiles.Length; + Assert.Equal(expectedFileCount, actualFileCount); + + //Check file content + var actualFileName = actualFiles.First(); + Assert.Equal(expectedFileName, actualFileName); + var actualContent = File.ReadAllText(actualFileName); + Assert.Equal(fileContent, actualContent); + + var actualInfo = receivedNormFile.Info; + Assert.Equal(expectedInfo, actualInfo); + if (actualInfo != null) + { + var actualInfoContent = Encoding.ASCII.GetString(actualInfo); + Assert.Equal(expectedInfoContent, actualInfoContent); + } + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + StopReceiver(); + } + } + + [SkippableFact(typeof(IOException))] + public void ReceivesFileWithRename() + { + _normSession.SetLoopback(true); + StartSender(); + StartReceiver(); + + //Set up cache directory + var folderName = Guid.NewGuid().ToString(); + var cachePath = Path.Combine(_testPath, folderName); + Directory.CreateDirectory(cachePath); + _normInstance.SetCacheDirectory(cachePath); + + //Set up file to send + var fileName = Guid.NewGuid().ToString(); + var fileContent = GenerateTextContent(); + var filePath = Path.Combine(_testPath, fileName); + File.WriteAllText(filePath, fileContent); + + try + { + //Enqueue file + var fileNameBytes = Encoding.ASCII.GetBytes(fileName); + var normFile = _normSession.FileEnqueue(filePath, fileNameBytes, 0, fileNameBytes.Length); + //Wait for events + var normEvents = GetEvents(); + AssertNormEvents(normEvents); + + var expectedNormEventType = NormEventType.NORM_RX_OBJECT_COMPLETED; + Assert.Contains(expectedNormEventType, normEvents.Select(e => e.Type)); + var normObjectEvent = normEvents.First(e => e.Type == expectedNormEventType); + + var receivedNormFile = Assert.IsType(normObjectEvent.Object); + var actualInfo = receivedNormFile.Info; + Assert.NotNull(actualInfo); + + var expectedFileName = fileName; + var actualFileName = Encoding.ASCII.GetString(actualInfo); + Assert.Equal(expectedFileName, actualFileName); + + var expectedFilePath = Path.Combine(cachePath, actualFileName); + receivedNormFile.Rename(expectedFilePath); + + //Check that file exists + var expectedFileCount = 1; + var actualFiles = Directory.GetFiles(cachePath); + var actualFileCount = actualFiles.Length; + Assert.Equal(expectedFileCount, actualFileCount); + + //Check file content + var actualFilePath = actualFiles.First(); + Assert.Equal(expectedFilePath, actualFilePath); + var actualContent = File.ReadAllText(actualFilePath); + Assert.Equal(fileContent, actualContent); + + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + StopReceiver(); + } + } + + public static IEnumerable GenerateData() + { + var data = new List(); + + var dataContent = GenerateTextContent(); + var expectedDataContent = dataContent; + var dataOffset = 0; + var dataLength = dataContent.Length; + data.Add(new object[] { dataContent, expectedDataContent, dataOffset, dataLength }); + + var infoContent = GenerateInfoContent(); + var expectedInfoContent = infoContent; + var infoOffset = 0; + var infoLength = infoContent.Length; + data.Add(new object[] { dataContent, expectedDataContent, dataOffset, dataLength, infoContent, expectedInfoContent, infoOffset, infoLength }); + + var faker = new Faker(); + infoLength = faker.Random.Int(infoContent.Length / 2, infoContent.Length - 1); + expectedInfoContent = infoContent.Substring(infoOffset, infoLength); + data.Add(new object[] { dataContent, expectedDataContent, dataOffset, dataLength, infoContent, expectedInfoContent, infoOffset, infoLength }); + + infoOffset = faker.Random.Int(1, (infoContent.Length - 1) / 2); + infoLength = infoContent.Length - infoOffset; + expectedInfoContent = infoContent.Substring(infoOffset, infoLength); + data.Add(new object[] { dataContent, expectedDataContent, dataOffset, dataLength, infoContent, expectedInfoContent, infoOffset, infoLength }); + + infoOffset = faker.Random.Int(1, (infoContent.Length - 1) / 2); + infoLength = faker.Random.Int(1, infoContent.Length - infoOffset); + expectedInfoContent = infoContent.Substring(infoOffset, infoLength); + data.Add(new object[] { dataContent, expectedDataContent, dataOffset, dataLength, infoContent, expectedInfoContent, infoOffset, infoLength }); + + dataLength = faker.Random.Int(dataContent.Length / 2, dataContent.Length - 1); + expectedDataContent = dataContent.Substring(dataOffset, dataLength); + data.Add(new object[] { dataContent, expectedDataContent, dataOffset, dataLength }); + + dataOffset = faker.Random.Int(1, (dataContent.Length - 1) / 2); + dataLength = dataContent.Length - dataOffset; + expectedDataContent = dataContent.Substring(dataOffset, dataLength); + data.Add(new object[] { dataContent, expectedDataContent, dataOffset, dataLength }); + + dataOffset = faker.Random.Int(1, (dataContent.Length - 1) / 2); + dataLength = faker.Random.Int(1, dataContent.Length - dataOffset); + expectedDataContent = dataContent.Substring(dataOffset, dataLength); + data.Add(new object[] { dataContent, expectedDataContent, dataOffset, dataLength }); + + return data; + } + + [SkippableTheory(typeof(IOException))] + [MemberData(nameof(GenerateData))] + public void EnqueuesData(string dataContent, string expectedDataContent, int dataOffset, int dataLength, string? infoContent = null, string expectedInfoContent = "", int? infoOffset = null, int? infoLength = null) + { + StartSender(); + //Create data to write to enqueue + var data = Encoding.ASCII.GetBytes(dataContent); + using var dataBuffer = ByteBuffer.AllocateDirect(data.Length); + dataBuffer.WriteArray(0, data, 0, data.Length); + var expectedData = Encoding.ASCII.GetBytes(expectedDataContent); + //Create info to enqueue + var info = infoContent != null ? Encoding.ASCII.GetBytes(infoContent) : null; + var expectedInfo = Encoding.ASCII.GetBytes(expectedInfoContent); + + try + { + var normData = infoOffset != null && infoLength != null ? + _normSession.DataEnqueue(dataBuffer, dataOffset, dataLength, info, infoOffset.Value, infoLength.Value) : + _normSession.DataEnqueue(dataBuffer, dataOffset, dataLength); + var expectedEventTypes = new List { NormEventType.NORM_TX_OBJECT_SENT, NormEventType.NORM_TX_QUEUE_EMPTY }; + var actualEventTypes = GetEvents().Select(e => e.Type).ToList(); + Assert.Equal(expectedEventTypes, actualEventTypes); + var actualData = normData.GetData(); + Assert.Equal(expectedData, actualData); + var actualDataContent = Encoding.ASCII.GetString(actualData); + Assert.Equal(expectedDataContent, actualDataContent); + var actualInfo = normData.Info; + Assert.Equal(expectedInfo, actualInfo); + if (actualInfo != null) + { + var actualInfoContent = Encoding.ASCII.GetString(actualInfo); + Assert.Equal(expectedInfoContent, actualInfoContent); + } + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + } + } + + public static IEnumerable GenerateOutOfRangeData() + { + var data = new List(); + + var dataContent = GenerateTextContent(); + var faker = new Faker(); + var dataOffset = faker.Random.Int(-dataContent.Length, -1); + var dataLength = dataContent.Length; + data.Add(new object[] { dataContent, dataOffset, dataLength }); + + dataOffset = faker.Random.Int(dataContent.Length, dataContent.Length * 2); + dataLength = dataContent.Length; + data.Add(new object[] { dataContent, dataOffset, dataLength }); + + dataOffset = 0; + dataLength = faker.Random.Int(dataContent.Length + 1, dataContent.Length * 2); + data.Add(new object[] { dataContent, dataOffset, dataLength }); + + dataLength = -1; + data.Add(new object[] { dataContent, dataOffset, dataLength }); + + dataLength = 0; + data.Add(new object[] { dataContent, dataOffset, dataLength }); + + dataOffset = dataContent.Length - 1; + dataLength = dataContent.Length; + data.Add(new object[] { dataContent, dataOffset, dataLength }); + + dataOffset = 0; + dataLength = dataContent.Length; + + var infoContent = GenerateInfoContent(); + var infoOffset = faker.Random.Int(-infoContent.Length, -1); + var infoLength = infoContent.Length; + data.Add(new object[] { dataContent, dataOffset, dataLength, infoContent, infoOffset, infoLength }); + + infoOffset = faker.Random.Int(infoContent.Length, infoContent.Length * 2); + infoLength = infoContent.Length; + data.Add(new object[] { dataContent, dataOffset, dataLength, infoContent, infoOffset, infoLength }); + + infoOffset = 0; + infoLength = faker.Random.Int(infoContent.Length + 1, infoContent.Length * 2); + data.Add(new object[] { dataContent, dataOffset, dataLength, infoContent, infoOffset, infoLength }); + + infoLength = -1; + data.Add(new object[] { dataContent, dataOffset, dataLength, infoContent, infoOffset, infoLength }); + + infoLength = 0; + data.Add(new object[] { dataContent, dataOffset, dataLength, infoContent, infoOffset, infoLength }); + + infoOffset = infoContent.Length - 1; + infoLength = infoContent.Length; + data.Add(new object[] { dataContent, dataOffset, dataLength, infoContent, infoOffset, infoLength }); + + return data; + } + + [SkippableTheory(typeof(IOException))] + [MemberData(nameof(GenerateOutOfRangeData))] + public void EnqueuesDataThrowsExceptionWhenOutOfRange(string dataContent, int dataOffset, int dataLength, string? infoContent = null, int? infoOffset = null, int? infoLength = null) + { + StartSender(); + //Create data to enqueue + var data = Encoding.ASCII.GetBytes(dataContent); + using var dataBuffer = ByteBuffer.AllocateDirect(data.Length); + dataBuffer.WriteArray(0, data, 0, data.Length); + //Create info to enqueue + var info = infoContent != null ? Encoding.ASCII.GetBytes(infoContent) : null; + + try + { + Assert.Throws(() => + infoOffset != null && infoLength != null ? + _normSession.DataEnqueue(dataBuffer, dataOffset, dataLength, info, infoOffset.Value, infoLength.Value) : + _normSession.DataEnqueue(dataBuffer, dataOffset, dataLength)); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + } + } + + [SkippableTheory(typeof(IOException))] + [MemberData(nameof(GenerateData))] + public void ReceivesData(string content, string expectedDataContent, int dataOffset, int dataLength, string? infoContent = null, string expectedInfoContent = "", int? infoOffset = null, int? infoLength = null) + { + _normSession.SetLoopback(true); + StartSender(); + StartReceiver(); + + //Set up cache directory + var folderName = Guid.NewGuid().ToString(); + var cachePath = Path.Combine(_testPath, folderName); + Directory.CreateDirectory(cachePath); + _normInstance.SetCacheDirectory(cachePath); + + //Create data to be sent + var data = Encoding.ASCII.GetBytes(content); + using var dataBuffer = ByteBuffer.AllocateDirect(data.Length); + dataBuffer.WriteArray(0, data, 0, data.Length); + var expectedData = Encoding.ASCII.GetBytes(expectedDataContent); + //Create info to be sent + var info = infoContent != null ? Encoding.ASCII.GetBytes(infoContent) : null; + var expectedInfo = Encoding.ASCII.GetBytes(expectedInfoContent); + + try + { + var normData = infoOffset != null && infoLength != null ? + _normSession.DataEnqueue(dataBuffer, dataOffset, dataLength, info, infoOffset.Value, infoLength.Value) : + _normSession.DataEnqueue(dataBuffer, dataOffset, dataLength); + var expectedEventTypes = new List + { + NormEventType.NORM_REMOTE_SENDER_NEW, + NormEventType.NORM_REMOTE_SENDER_ACTIVE, + NormEventType.NORM_TX_OBJECT_SENT, + NormEventType.NORM_TX_QUEUE_EMPTY, + NormEventType.NORM_RX_OBJECT_NEW, + NormEventType.NORM_RX_OBJECT_UPDATED, + NormEventType.NORM_RX_OBJECT_COMPLETED + }; + var actualEvents = GetEvents(); + AssertNormEvents(actualEvents); + + var actualEventTypes = actualEvents.Select(e => e.Type).ToList(); + Assert.Equivalent(expectedEventTypes, actualEventTypes); + + var actualEvent = actualEvents.FirstOrDefault(e => e.Type == NormEventType.NORM_RX_OBJECT_COMPLETED); + var actualNormData = Assert.IsType(actualEvent?.Object); + + var actualData = actualNormData.GetData(); + Assert.Equal(expectedData, actualData); + var actualDataContent = Encoding.ASCII.GetString(actualData); + Assert.Equal(expectedDataContent, actualDataContent); + var actualInfo = actualNormData.Info; + Assert.Equal(expectedInfo, actualInfo); + if (actualInfo != null) + { + var actualInfoContent = Encoding.ASCII.GetString(actualInfo); + Assert.Equal(expectedInfoContent, actualInfoContent); + } + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + StopReceiver(); + } + } + + [SkippableTheory(typeof(IOException))] + [MemberData(nameof(GenerateData))] + public void SendsStream(string content, string expectedContent, int offset, int length, string? infoContent = null, string expectedInfoContent = "", int? infoOffset = null, int? infoLength = null) + { + StartSender(); + + var buffer = Encoding.ASCII.GetBytes(content); + var expectedBuffer = Encoding.ASCII.GetBytes(expectedContent); + //Create info to enqueue + var info = infoContent != null ? Encoding.ASCII.GetBytes(infoContent) : null; + var expectedInfo = Encoding.ASCII.GetBytes(expectedInfoContent); + NormStream? normStream = null; + + try + { + var repairWindowSize = 1024 * 1024; + normStream = infoOffset != null && infoLength != null ? + _normSession.StreamOpen(repairWindowSize, info, infoOffset.Value, infoLength.Value) : + _normSession.StreamOpen(repairWindowSize); + + var expectedBytesWritten = expectedBuffer.Length; + var actualBytesWritten = normStream.Write(buffer, offset, length); + + WaitForEvents(); + normStream.MarkEom(); + normStream.Flush(); + + Assert.Equal(expectedBytesWritten, actualBytesWritten); + var actualInfo = normStream.Info; + Assert.Equal(expectedInfo, actualInfo); + if (actualInfo != null) + { + var actualInfoContent = Encoding.ASCII.GetString(actualInfo); + Assert.Equal(expectedInfoContent, actualInfoContent); + } + } + catch (Exception) + { + throw; + } + finally + { + normStream?.Close(true); + StopSender(); + } + } + + [SkippableTheory(typeof(IOException))] + [MemberData(nameof(GenerateOutOfRangeData))] + public void SendsStreamThrowsExceptionWhenOutOfRange(string content, int offset, int length, string? infoContent = null, int? infoOffset = null, int? infoLength = null) + { + StartSender(); + var buffer = Encoding.ASCII.GetBytes(content); + //Create info to enqueue + var info = infoContent != null ? Encoding.ASCII.GetBytes(infoContent) : null; + NormStream? normStream = null; + + try + { + var repairWindowSize = 1024 * 1024; + + if (infoOffset != null && infoLength != null) { + Assert.Throws(() => + _normSession.StreamOpen(repairWindowSize, info, infoOffset.Value, infoLength.Value)); + } + else + { + normStream = _normSession.StreamOpen(repairWindowSize); + Assert.Throws(() => + normStream.Write(buffer, offset, length)); + } + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + } + } + + [SkippableTheory(typeof(IOException))] + [MemberData(nameof(GenerateData))] + public void ReceivesStream(string content, string expectedContent, int offset, int length, string? infoContent = null, string expectedInfoContent = "", int? infoOffset = null, int? infoLength = null) + { + _normSession.SetLoopback(true); + StartSender(); + StartReceiver(); + + var buffer = Encoding.ASCII.GetBytes(content); + var expectedBuffer = Encoding.ASCII.GetBytes(expectedContent); + //Create info to enqueue + var info = infoContent != null ? Encoding.ASCII.GetBytes(infoContent) : null; + var expectedInfo = Encoding.ASCII.GetBytes(expectedInfoContent); + NormStream? normStream = null; + + try + { + var repairWindowSize = 1024 * 1024; + normStream = infoOffset != null && infoLength != null ? + _normSession.StreamOpen(repairWindowSize, info, infoOffset.Value, infoLength.Value) : + _normSession.StreamOpen(repairWindowSize); + + var expectedBytesWritten = expectedBuffer.Length; + normStream.Write(buffer, offset, length); + + normStream.MarkEom(); + normStream.Flush(); + var normEvents = GetEvents(); + AssertNormEvents(normEvents); + + var expectedNormEventType = NormEventType.NORM_RX_OBJECT_UPDATED; + Assert.Contains(expectedNormEventType, normEvents.Select(e => e.Type)); + var normObjectEvent = normEvents.First(e => e.Type == expectedNormEventType); + + var receivedNormStream = Assert.IsType(normObjectEvent.Object); + var numRead = 0; + var receiveBuffer = new byte[65536]; + while ((numRead = receivedNormStream.Read(receiveBuffer, 0, length)) > 0) + { + if (numRead != -1) + { + var receivedData = receiveBuffer.Take(numRead).ToArray(); + var receivedContent = Encoding.ASCII.GetString(receivedData); + Assert.Equal(expectedContent, receivedContent); + } + } + var actualInfo = receivedNormStream.Info; + Assert.Equal(expectedInfo, actualInfo); + if (actualInfo != null) + { + var actualInfoContent = Encoding.ASCII.GetString(actualInfo); + Assert.Equal(expectedInfoContent, actualInfoContent); + } + } + catch (Exception) + { + throw; + } + finally + { + normStream?.Close(true); + StopSender(); + StopReceiver(); + } + } + + [SkippableFact(typeof(IOException))] + public void ReceivesStreamWithOffset() + { + _normSession.SetLoopback(true); + StartSender(); + StartReceiver(); + + //Set up cache directory + var folderName = Guid.NewGuid().ToString(); + var cachePath = Path.Combine(_testPath, folderName); + Directory.CreateDirectory(cachePath); + _normInstance.SetCacheDirectory(cachePath); + + var fileContent = GenerateTextContent(); + var data = Encoding.ASCII.GetBytes(fileContent); + NormStream? normStream = null; + + try + { + var repairWindowSize = 1024 * 1024; + + normStream = _normSession.StreamOpen(repairWindowSize); + var offset = 0; + var length = data.Length; + normStream.Write(data, offset, length); + normStream.MarkEom(); + normStream.Flush(); + + var normEvents = GetEvents(); + AssertNormEvents(normEvents); + + var expectedNormEventType = NormEventType.NORM_RX_OBJECT_UPDATED; + var normObjectEvent = normEvents.First(e => e.Type == expectedNormEventType); + var receivedNormStream = Assert.IsType(normObjectEvent.Object); + + var receiveBuffer = new byte[65536]; + var receiveOffset = 0; + var receiveBufferLength = 10; + var totalNumRead = 0; + var numRead = 0; + + while ((numRead = receivedNormStream.Read(receiveBuffer, receiveOffset, receiveBufferLength)) > 0) + { + if (numRead != -1) + { + totalNumRead += numRead; + receiveOffset += numRead; + } + } + var receivedData = receiveBuffer.Take(totalNumRead).ToArray(); + var receivedContent = Encoding.ASCII.GetString(receivedData); + Assert.Equal(fileContent, receivedContent); + + + } + catch (Exception) + { + throw; + } + finally + { + normStream?.Close(true); + StopSender(); + StopReceiver(); + } + } + + public static IEnumerable GenerateOutOfRangeReceiveStream() + { + var data = new List(); + + var faker = new Faker(); + var initialLength = faker.Random.Int(256, 1024); + var dataOffset = faker.Random.Int(-initialLength, -1); + var dataLength = initialLength; + data.Add(new object[] { initialLength, dataOffset, dataLength }); + + dataOffset = faker.Random.Int(initialLength, initialLength * 2); + dataLength = initialLength; + data.Add(new object[] { initialLength, dataOffset, dataLength }); + + dataOffset = 0; + dataLength = faker.Random.Int(initialLength + 1, initialLength * 2); + data.Add(new object[] { initialLength, dataOffset, dataLength }); + + dataLength = -1; + data.Add(new object[] { initialLength, dataOffset, dataLength }); + + dataLength = 0; + data.Add(new object[] { initialLength, dataOffset, dataLength }); + + dataOffset = initialLength - 1; + dataLength = initialLength; + data.Add(new object[] { initialLength, dataOffset, dataLength }); + + return data; + } + + [SkippableTheory(typeof(IOException))] + [MemberData(nameof(GenerateOutOfRangeReceiveStream))] + public void ReceivesStreamThrowsExceptionWhenOutOfRange(int initialReceiveBufferLength, int receiveBufferOffset, int receiveBufferLength) + { + _normSession.SetLoopback(true); + StartSender(); + StartReceiver(); + + //Set up cache directory + var folderName = Guid.NewGuid().ToString(); + var cachePath = Path.Combine(_testPath, folderName); + Directory.CreateDirectory(cachePath); + _normInstance.SetCacheDirectory(cachePath); + + var fileContent = GenerateTextContent(); + var data = Encoding.ASCII.GetBytes(fileContent); + NormStream? normStream = null; + + try + { + var repairWindowSize = 1024 * 1024; + + normStream = _normSession.StreamOpen(repairWindowSize); + var offset = 0; + var length = data.Length; + normStream.Write(data, offset, length); + normStream.MarkEom(); + normStream.Flush(); + + var normEvents = GetEvents(); + AssertNormEvents(normEvents); + + var expectedNormEventType = NormEventType.NORM_RX_OBJECT_UPDATED; + var normObjectEvent = normEvents.First(e => e.Type == expectedNormEventType); + var receivedNormStream = Assert.IsType(normObjectEvent.Object); + + var receiveBuffer = new byte[initialReceiveBufferLength]; + + Assert.Throws(() => + receivedNormStream.Read(receiveBuffer, receiveBufferOffset, receiveBufferLength)); + } + catch (Exception) + { + throw; + } + finally + { + normStream?.Close(true); + StopSender(); + StopReceiver(); + } + } + + [Fact] + public void SetsTxPort() + { + _normSession.SetTxPort(6003); + } + + [Fact] + public void SetsRxPortReuseTrue() + { + _normSession.SetRxPortReuse(true); + } + + [Fact] + public void SetsRxPortReuseFalse() + { + _normSession.SetRxPortReuse(false); + } + + [Fact] + public void SetsEcnSupport() + { + _normSession.SetEcnSupport(true, true); + } + + [Fact] + public void SetsMulticastInterface() + { + _normSession.SetMulticastInterface("interface_name"); + } + + [Fact] + public void SetsSSM() + { + var sourceAddress = "224.1.2.3"; + _normSession.SetSSM(sourceAddress); + } + + [Fact] + public void SetsSSMThrowsIOException() + { + var sourceAddress = "999.999.999.999"; + Assert.Throws(() => _normSession.SetSSM(sourceAddress)); + } + + [Fact] + public void SetsTTL() + { + var ttl = (byte)200; + _normSession.SetTTL(ttl); + } + + [Fact] + public void SetsTOS() + { + var tos = (byte)200; + _normSession.SetTOS(tos); + } + + [Fact] + public void SetsMessageTrace() + { + var flag = true; + _normSession.SetMessageTrace(flag); + } + + [Fact] + public void SetsTxLoss() + { + var txLoss = .50; + _normSession.SetTxLoss(txLoss); + } + + [Fact] + public void SetsRxLoss() + { + var rxLoss = .50; + _normSession.SetRxLoss(rxLoss); + } + + [Fact] + public void SetsReportInterval() + { + var expectedReportInterval = .50; + _normSession.ReportInterval = expectedReportInterval; + var actualReportInterval = _normSession.ReportInterval; + Assert.Equal(expectedReportInterval, actualReportInterval); + } + + [Fact] + public void SetsTxOnly() + { + var txOnly = true; + _normSession.SetTxOnly(txOnly); + } + + [Fact] + public void SetsFlowControl() + { + var flowControlFactor = .50; + _normSession.SetFlowControl(flowControlFactor); + } + + [Fact] + public void SetsTxSocketBuffer() + { + StartSender(); + var bufferSize = 100; + _normSession.SetTxSocketBuffer(bufferSize); + } + + [Fact] + public void SetsCongestionControl() + { + var enable = true; + _normSession.SetCongestionControl(enable); + } + + [Fact] + public void SetsTxRateBounds() + { + var rateMin = 1.0; + var rateMax = 100.0; + _normSession.SetTxRateBounds(rateMin, rateMax); + } + + [Fact] + public void SetsTxCacheBounds() + { + var sizeMax = 100; + var countMin = 1; + var countMax = 99; + + _normSession.SetTxCacheBounds(sizeMax, countMin, countMax); + } + + [Fact] + public void SetsAutoParity() + { + short autoParity = 123; + _normSession.SetAutoParity(autoParity); + } + + [Fact] + public void SetsTxRate() + { + double txRate = 20.0; + _normSession.TxRate = txRate; + } + + [Fact] + public void GetsTxRate() + { + double expectedTxRate = 10.0; + _normSession.TxRate = expectedTxRate; + + var actualTxRate = _normSession.TxRate; + Assert.Equal(expectedTxRate, actualTxRate); + } + + [Fact] + public void SetsGrttEstimate() + { + double grttEstimate = 10.0; + _normSession.GrttEstimate = grttEstimate; + } + + [Fact] + public void GetsGrttEstimate() + { + double expetedGrttEstimate = 10.0; + _normSession.GrttEstimate = expetedGrttEstimate; + + var actualGrttEstimate = _normSession.GrttEstimate; + Assert.InRange(actualGrttEstimate, expetedGrttEstimate - 1.0, expetedGrttEstimate + 1.0); + } + + [Fact] + public void SetsGrttMax() + { + double grttMax = 20.0; + _normSession.SetGrttMax(grttMax); + } + + [Fact] + public void SetsGrttProbingMode_NORM_PROBE_NONE() + { + var probingMode = NormProbingMode.NORM_PROBE_NONE; + _normSession.SetGrttProbingMode(probingMode); + } + + [Fact] + public void SetsGrttProbingMode_NORM_PROBE_PASSIVE() + { + var probingMode = NormProbingMode.NORM_PROBE_PASSIVE; + _normSession.SetGrttProbingMode(probingMode); + } + + [Fact] + public void SetsGrttProbingMode_NORM_PROBE_ACTIVE() + { + var probingMode = NormProbingMode.NORM_PROBE_ACTIVE; + _normSession.SetGrttProbingMode(probingMode); + } + + [Fact] + public void SetsGrttProbingInterval() + { + var intervalMin = 1.0; + var intervalMax = 10.0; + _normSession.SetGrttProbingInterval(intervalMin, intervalMax); + } + + [Fact] + public void SetsBackoffFactor() + { + var backoffFactor = 10.0; + _normSession.SetBackoffFactor(backoffFactor); + } + + [Fact] + public void SetsGroupSize() + { + var groupSize = 100; + _normSession.SetGroupSize(groupSize); + } + + [Fact] + public void SetsTxRobustFactor() + { + var txRobustFactor = 2; + _normSession.SetTxRobustFactor(txRobustFactor); + } + + [SkippableFact(typeof(IOException))] + public void RequeuesObject() + { + StartSender(); + //Create data to write to the stream + var expectedContent = GenerateTextContent(); + byte[] expectedData = Encoding.ASCII.GetBytes(expectedContent); + using var dataBuffer = ByteBuffer.AllocateDirect(expectedData.Length); + dataBuffer.WriteArray(0, expectedData, 0, expectedData.Length); + + try + { + var normData = _normSession.DataEnqueue(dataBuffer, 0, expectedData.Length); + var expectedEventTypes = new List { NormEventType.NORM_TX_OBJECT_SENT, NormEventType.NORM_TX_QUEUE_EMPTY }; + var actualEventTypes = GetEvents().Select(e => e.Type).ToList(); + Assert.Equal(expectedEventTypes, actualEventTypes); + _normSession.RequeueObject(normData); + actualEventTypes = GetEvents().Select(e => e.Type).ToList(); + Assert.Equal(expectedEventTypes, actualEventTypes); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + } + } + + [SkippableFact(typeof(IOException))] + public void SetsWatermark() + { + _normSession.AddAckingNode(_normSession.LocalNodeId); + _normSession.SetLoopback(true); + StartSender(); + StartReceiver(); + + //Set up cache directory + var folderName = Guid.NewGuid().ToString(); + var cachePath = Path.Combine(_testPath, folderName); + Directory.CreateDirectory(cachePath); + _normInstance.SetCacheDirectory(cachePath); + + //Create data to be sent + var expectedContent = GenerateTextContent(); + var expectedData = Encoding.ASCII.GetBytes(expectedContent); + using var dataBuffer = ByteBuffer.AllocateDirect(expectedData.Length); + dataBuffer.WriteArray(0, expectedData, 0, expectedData.Length); + + try + { + var normData = _normSession.DataEnqueue(dataBuffer, 0, expectedData.Length); + _normSession.SetWatermark(normData); + var expectedEventTypes = new List + { + NormEventType.NORM_REMOTE_SENDER_NEW, + NormEventType.NORM_REMOTE_SENDER_ACTIVE, + NormEventType.NORM_TX_OBJECT_SENT, + NormEventType.NORM_TX_QUEUE_EMPTY, + NormEventType.NORM_RX_OBJECT_NEW, + NormEventType.NORM_RX_OBJECT_UPDATED, + NormEventType.NORM_RX_OBJECT_COMPLETED, + NormEventType.NORM_TX_WATERMARK_COMPLETED + }; + var actualEvents = GetEvents(TimeSpan.FromSeconds(1)); + var actualEventTypes = actualEvents.Select(e => e.Type).ToList(); + Assert.Equivalent(expectedEventTypes, actualEventTypes); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + StopReceiver(); + } + } + + [SkippableFact(typeof(IOException))] + public void CancelsWatermark() + { + _normSession.AddAckingNode(_normSession.LocalNodeId); + _normSession.SetLoopback(true); + StartSender(); + StartReceiver(); + + //Set up cache directory + var folderName = Guid.NewGuid().ToString(); + var cachePath = Path.Combine(_testPath, folderName); + Directory.CreateDirectory(cachePath); + _normInstance.SetCacheDirectory(cachePath); + + //Create data to be sent + var expectedContent = GenerateTextContent(); + var expectedData = Encoding.ASCII.GetBytes(expectedContent); + using var dataBuffer = ByteBuffer.AllocateDirect(expectedData.Length); + dataBuffer.WriteArray(0, expectedData, 0, expectedData.Length); + + try + { + var normData = _normSession.DataEnqueue(dataBuffer, 0, expectedData.Length); + _normSession.SetWatermark(normData); + _normSession.CancelWatermark(); + var expectedEventTypes = new List + { + NormEventType.NORM_REMOTE_SENDER_NEW, + NormEventType.NORM_REMOTE_SENDER_ACTIVE, + NormEventType.NORM_TX_OBJECT_SENT, + NormEventType.NORM_TX_QUEUE_EMPTY, + NormEventType.NORM_RX_OBJECT_NEW, + NormEventType.NORM_RX_OBJECT_UPDATED, + NormEventType.NORM_RX_OBJECT_COMPLETED + }; + var actualEvents = GetEvents(TimeSpan.FromSeconds(1)); + var actualEventTypes = actualEvents.Select(e => e.Type).ToList(); + Assert.Equivalent(expectedEventTypes, actualEventTypes); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + StopReceiver(); + } + } + + [SkippableFact(typeof(IOException))] + public void ResetsWatermark() + { + _normSession.SetLoopback(true); + StartSender(); + StartReceiver(); + + //Set up cache directory + var folderName = Guid.NewGuid().ToString(); + var cachePath = Path.Combine(_testPath, folderName); + Directory.CreateDirectory(cachePath); + _normInstance.SetCacheDirectory(cachePath); + + //Create data to be sent + var expectedContent = GenerateTextContent(); + var expectedData = Encoding.ASCII.GetBytes(expectedContent); + using var dataBuffer = ByteBuffer.AllocateDirect(expectedData.Length); + dataBuffer.WriteArray(0, expectedData, 0, expectedData.Length); + + try + { + var normData = _normSession.DataEnqueue(dataBuffer, 0, expectedData.Length); + _normSession.SetWatermark(normData); + var expectedEventTypes = new List + { + NormEventType.NORM_REMOTE_SENDER_NEW, + NormEventType.NORM_REMOTE_SENDER_ACTIVE, + NormEventType.NORM_TX_OBJECT_SENT, + NormEventType.NORM_TX_QUEUE_EMPTY, + NormEventType.NORM_RX_OBJECT_NEW, + NormEventType.NORM_RX_OBJECT_UPDATED, + NormEventType.NORM_RX_OBJECT_COMPLETED + }; + var actualEvents = GetEvents(TimeSpan.FromSeconds(1)); + var actualEventTypes = actualEvents.Select(e => e.Type).ToList(); + Assert.Equivalent(expectedEventTypes, actualEventTypes); + + _normSession.AddAckingNode(_normSession.LocalNodeId); + _normSession.ResetWatermark(); + expectedEventTypes = new List + { + NormEventType.NORM_TX_WATERMARK_COMPLETED + }; + actualEvents = GetEvents(TimeSpan.FromSeconds(1)); + actualEventTypes = actualEvents.Select(e => e.Type).ToList(); + Assert.Equivalent(expectedEventTypes, actualEventTypes); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + StopReceiver(); + } + } + + [Fact] + public void AddsAckingNode() + { + _normSession.AddAckingNode(_normSession.LocalNodeId); + } + + [Fact] + public void RemovesAckingNode() + { + _normSession.AddAckingNode(_normSession.LocalNodeId); + _normSession.RemoveAckingNode(_normSession.LocalNodeId); + } + + [SkippableFact(typeof(IOException))] + public void GetsAckingStatus() + { + _normSession.AddAckingNode(_normSession.LocalNodeId); + _normSession.SetLoopback(true); + StartSender(); + StartReceiver(); + + //Set up cache directory + var folderName = Guid.NewGuid().ToString(); + var cachePath = Path.Combine(_testPath, folderName); + Directory.CreateDirectory(cachePath); + _normInstance.SetCacheDirectory(cachePath); + + //Create data to be sent + var expectedContent = GenerateTextContent(); + var expectedData = Encoding.ASCII.GetBytes(expectedContent); + using var dataBuffer = ByteBuffer.AllocateDirect(expectedData.Length); + dataBuffer.WriteArray(0, expectedData, 0, expectedData.Length); + + try + { + var normData = _normSession.DataEnqueue(dataBuffer, 0, expectedData.Length); + _normSession.SetWatermark(normData); + WaitForEvents(TimeSpan.FromSeconds(1)); + var expectedAckingStatus = NormAckingStatus.NORM_ACK_SUCCESS; + var actualAckingStatus = _normSession.GetAckingStatus(_normSession.LocalNodeId); + Assert.Equal(expectedAckingStatus, actualAckingStatus); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + StopReceiver(); + } + } + + [SkippableFact(typeof(IOException))] + public void SendsCommand() + { + StartSender(); + //Create command to send + var expectedContent = GenerateTextContent(); + byte[] expectedCommand = Encoding.ASCII.GetBytes(expectedContent); + + try + { + _normSession.SendCommand(expectedCommand, 0, expectedCommand.Length, false); + var expectedEventTypes = new List { NormEventType.NORM_TX_CMD_SENT }; + var actualEventTypes = GetEvents().Select(e => e.Type).ToList(); + Assert.Equal(expectedEventTypes, actualEventTypes); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + } + } + + public static IEnumerable GenerateOutOfRangeCommand() + { + var command = new List(); + var faker = new Faker(); + + var content = GenerateInfoContent(); + var offset = faker.Random.Int(-content.Length, -1); + var length = content.Length; + command.Add(new object[] { content, offset, length }); + + offset = faker.Random.Int(content.Length, content.Length * 2); + length = content.Length; + command.Add(new object[] { content, offset, length }); + + offset = 0; + length = faker.Random.Int(content.Length + 1, content.Length * 2); + command.Add(new object[] { content, offset, length }); + + length = -1; + command.Add(new object[] { content, offset, length }); + + length = 0; + command.Add(new object[] { content, offset, length }); + + offset = content.Length - 1; + length = content.Length; + command.Add(new object[] { content, offset, length }); + + return command; + } + + [SkippableTheory(typeof(IOException))] + [MemberData(nameof(GenerateOutOfRangeCommand))] + public void SendsCommandThrowsExceptionWhenOutOfRange(string content, int offset, int length) + { + StartSender(); + //Create data to enqueue + var command = Encoding.ASCII.GetBytes(content); + + try + { + Assert.Throws(() => + _normSession.SendCommand(command, offset, length, false)); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + } + } + + [SkippableFact(typeof(IOException))] + public void ReceivesCommand() + { + _normSession.SetLoopback(true); + StartSender(); + StartReceiver(); + //Create command to send + var content = GenerateInfoContent(); + var expectedContent = content; + var offset = 0; + var length = content.Length; + var command = Encoding.ASCII.GetBytes(content); + var expectedCommand = Encoding.ASCII.GetBytes(expectedContent); + + try + { + _normSession.SendCommand(command, 0, command.Length, false); + var expectedEventTypes = new List + { + NormEventType.NORM_TX_CMD_SENT, + NormEventType.NORM_REMOTE_SENDER_NEW, + NormEventType.NORM_REMOTE_SENDER_ACTIVE, + NormEventType.NORM_RX_CMD_NEW + }; + var actualEvents = GetEvents(); + var actualEventTypes = actualEvents.Select(e => e.Type).ToList(); + Assert.Equivalent(expectedEventTypes, actualEventTypes); + + var actualEvent = actualEvents.First(e => e.Type == NormEventType.NORM_RX_CMD_NEW); + var actualCommand = new byte[command.Length]; + var actualLength = actualEvent?.Node?.GetCommand(actualCommand, offset, length); + Assert.Equal(length, actualLength); + Assert.Equal(expectedCommand, actualCommand); + var actualContent = Encoding.ASCII.GetString(actualCommand); + Assert.Equal(expectedContent, actualContent); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + StopReceiver(); + } + } + + private void ReceivesCommandThrowsException(string content, int offset, int length) where TExceptionType : Exception + { + _normSession.SetLoopback(true); + StartSender(); + StartReceiver(); + //Create command to send + var command = Encoding.ASCII.GetBytes(content); + + try + { + _normSession.SendCommand(command, 0, command.Length, false); + var expectedEventTypes = new List + { + NormEventType.NORM_TX_CMD_SENT, + NormEventType.NORM_REMOTE_SENDER_NEW, + NormEventType.NORM_REMOTE_SENDER_ACTIVE, + NormEventType.NORM_RX_CMD_NEW + }; + var actualEvents = GetEvents(); + var actualEventTypes = actualEvents.Select(e => e.Type).ToList(); + Assert.Equivalent(expectedEventTypes, actualEventTypes); + + var actualEvent = actualEvents.First(e => e.Type == NormEventType.NORM_RX_CMD_NEW); + var actualCommand = new byte[command.Length]; + Assert.Throws(() => + actualEvent?.Node?.GetCommand(actualCommand, offset, length)); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + StopReceiver(); + } + } + + public static IEnumerable GenerateShortLengthCommand() + { + var command = new List(); + + var content = GenerateInfoContent(); + var faker = new Faker(); + var offset = 0; + var length = faker.Random.Int(content.Length / 2, content.Length - 1); + command.Add(new object[] { content, offset, length }); + + offset = faker.Random.Int(1, (content.Length - 1) / 2); + length = content.Length - offset; + command.Add(new object[] { content, offset, length }); + + offset = faker.Random.Int(1, (content.Length - 1) / 2); + length = faker.Random.Int(1, content.Length - offset); + command.Add(new object[] { content, offset, length }); + + return command; + } + + [SkippableTheory(typeof(IOException))] + [MemberData(nameof(GenerateShortLengthCommand))] + public void ReceivesCommandThrowsExceptionWhenLengthLessThanCommand(string content, int offset, int length) + { + ReceivesCommandThrowsException(content, offset, length); + } + + public static IEnumerable GenerateOutOfRangeReceiveCommand() + { + var command = new List(); + + var content = GenerateTextContent(); + var faker = new Faker(); + var offset = faker.Random.Int(-content.Length, -1); + var length = content.Length; + command.Add(new object[] { content, offset, length }); + + offset = faker.Random.Int(content.Length, content.Length * 2); + length = content.Length; + command.Add(new object[] { content, offset, length }); + + offset = 0; + length = faker.Random.Int(content.Length + 1, content.Length * 2); + command.Add(new object[] { content, offset, length }); + + length = -1; + command.Add(new object[] { content, offset, length }); + + length = 0; + command.Add(new object[] { content, offset, length }); + + offset = content.Length - 1; + length = content.Length; + command.Add(new object[] { content, offset, length }); + + return command; + } + + [SkippableTheory(typeof(IOException))] + [MemberData(nameof(GenerateOutOfRangeReceiveCommand))] + public void ReceivesCommandThrowsExceptionWhenOutOfRange(string content, int offset, int length) + { + ReceivesCommandThrowsException(content, offset, length); + } + + [SkippableFact(typeof(IOException))] + public void CancelsCommand() + { + StartSender(); + //Create data to write to the stream + var expectedContent = GenerateTextContent(); + byte[] expectedCommand = Encoding.ASCII.GetBytes(expectedContent); + + try + { + _normSession.SendCommand(expectedCommand, 0, expectedCommand.Length, false); + _normSession.CancelCommand(); + var expectedEventTypes = new List(); + var actualEventTypes = GetEvents().Select(e => e.Type).ToList(); + Assert.Equal(expectedEventTypes, actualEventTypes); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + } + } + + [Fact] + public void SetsRxCacheLimit() + { + var countMax = 5; + _normSession.SetRxCacheLimit(countMax); + } + + [SkippableFact(typeof(IOException))] + public void SetsRxSocketBuffer() + { + StartSender(); + var bufferSize = 8; + _normSession.SetRxSocketBuffer(bufferSize); + } + + [Fact] + public void SetsSilentReceiver() + { + var silent = true; + _normSession.SetSilentReceiver(silent, -1); + } + + [Fact] + public void SetsDefaultUnicastNack() + { + var enable = true; + _normSession.SetDefaultUnicastNack(enable); + } + + [SkippableFact(typeof(IOException))] + public void SetsUnicastNack() + { + _normSession.SetLoopback(true); + StartSender(); + StartReceiver(); + //Create data to write to the stream + var expectedContent = GenerateTextContent(); + byte[] expectedCommand = Encoding.ASCII.GetBytes(expectedContent); + + try + { + _normSession.SendCommand(expectedCommand, 0, expectedCommand.Length, false); + var normEventType = NormEventType.NORM_RX_CMD_NEW; + var actualEvents = GetEvents(); + Assert.Contains(normEventType, actualEvents.Select(e => e.Type)); + var actualEvent = actualEvents.First(e => e.Type == normEventType); + var actualNode = actualEvent.Node; + Assert.NotNull(actualNode); + actualNode.SetUnicastNack(true); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + StopReceiver(); + } + } + + [Fact] + public void SetsDefaultSyncPolicy_NORM_SYNC_CURRENT() + { + var syncPolicy = NormSyncPolicy.NORM_SYNC_CURRENT; + _normSession.SetDefaultSyncPolicy(syncPolicy); + } + + [Fact] + public void SetsDefaultSyncPolicy_NORM_SYNC_STREAM() + { + var syncPolicy = NormSyncPolicy.NORM_SYNC_STREAM; + _normSession.SetDefaultSyncPolicy(syncPolicy); + } + + [Fact] + public void SetsDefaultSyncPolicy_NORM_SYNC_ALL() + { + var syncPolicy = NormSyncPolicy.NORM_SYNC_ALL; + _normSession.SetDefaultSyncPolicy(syncPolicy); + } + + [Fact] + public void SetsDefaultNackingMode_NORM_NACK_NONE() + { + var nackingMode = NormNackingMode.NORM_NACK_NONE; + _normSession.SetDefaultNackingMode(nackingMode); + } + + [Fact] + public void SetsDefaultNackingMode_NORM_NACK_INFO_ONLY() + { + var nackingMode = NormNackingMode.NORM_NACK_INFO_ONLY; + _normSession.SetDefaultNackingMode(nackingMode); + } + + [Fact] + public void SetsDefaultNackingMode_NORM_NACK_NORMAL() + { + var nackingMode = NormNackingMode.NORM_NACK_NORMAL; + _normSession.SetDefaultNackingMode(nackingMode); + } + + [SkippableFact(typeof(IOException))] + public void SetsNackingMode() + { + _normSession.SetLoopback(true); + StartSender(); + StartReceiver(); + //Create data to write to the stream + var expectedContent = GenerateTextContent(); + byte[] expectedCommand = Encoding.ASCII.GetBytes(expectedContent); + + try + { + _normSession.SendCommand(expectedCommand, 0, expectedCommand.Length, false); + var normEventType = NormEventType.NORM_RX_CMD_NEW; + var actualEvents = GetEvents(); + Assert.Contains(normEventType, actualEvents.Select(e => e.Type)); + var actualEvent = actualEvents.First(e => e.Type == normEventType); + var actualNode = actualEvent.Node; + Assert.NotNull(actualNode); + actualNode.SetNackingMode(NormNackingMode.NORM_NACK_NONE); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + StopReceiver(); + } + } + + [Fact] + public void SetsDefaultRepairBoundary_NORM_BOUNDARY_BLOCK() + { + var repairBoundary = NormRepairBoundary.NORM_BOUNDARY_BLOCK; + _normSession.SetDefaultRepairBoundary(repairBoundary); + } + + [Fact] + public void SetsDefaultRepairBoundary_NORM_BOUNDARY_OBJECT() + { + var repairBoundary = NormRepairBoundary.NORM_BOUNDARY_OBJECT; + _normSession.SetDefaultRepairBoundary(repairBoundary); + } + + [SkippableFact(typeof(IOException))] + public void SetsRepairBoundary() + { + _normSession.SetLoopback(true); + StartSender(); + StartReceiver(); + //Create data to write to the stream + var expectedContent = GenerateTextContent(); + byte[] expectedCommand = Encoding.ASCII.GetBytes(expectedContent); + + try + { + _normSession.SendCommand(expectedCommand, 0, expectedCommand.Length, false); + var normEventType = NormEventType.NORM_RX_CMD_NEW; + var actualEvents = GetEvents(); + Assert.Contains(normEventType, actualEvents.Select(e => e.Type)); + var actualEvent = actualEvents.First(e => e.Type == normEventType); + var actualNode = actualEvent.Node; + Assert.NotNull(actualNode); + actualNode.SetRepairBoundary(NormRepairBoundary.NORM_BOUNDARY_OBJECT); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + StopReceiver(); + } + } + + [Fact] + public void SetsDefaultRxRobustFactor() + { + var rxRobustFactor = 2; + _normSession.SetDefaultRxRobustFactor(rxRobustFactor); + } + + [SkippableFact(typeof(IOException))] + public void SetsRxRobustFactor() + { + _normSession.SetLoopback(true); + StartSender(); + StartReceiver(); + //Create data to write to the stream + var expectedContent = GenerateTextContent(); + byte[] expectedCommand = Encoding.ASCII.GetBytes(expectedContent); + + try + { + _normSession.SendCommand(expectedCommand, 0, expectedCommand.Length, false); + var normEventType = NormEventType.NORM_RX_CMD_NEW; + var actualEvents = GetEvents(); + Assert.Contains(normEventType, actualEvents.Select(e => e.Type)); + var actualEvent = actualEvents.First(e => e.Type == normEventType); + var actualNode = actualEvent.Node; + Assert.NotNull(actualNode); + actualNode.SetRxRobustFactor(2); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + StopReceiver(); + } + } + + [SkippableFact(typeof(IOException))] + public void GetsCommand() + { + _normSession.SetLoopback(true); + StartSender(); + StartReceiver(); + //Create data to write to the stream + var expectedContent = GenerateTextContent(); + byte[] expectedCommand = Encoding.ASCII.GetBytes(expectedContent); + + try + { + _normSession.SendCommand(expectedCommand, 0, expectedCommand.Length, false); + var normEventType = NormEventType.NORM_RX_CMD_NEW; + var actualEvents = GetEvents(); + Assert.Contains(normEventType, actualEvents.Select(e => e.Type)); + var actualEvent = actualEvents.First(e => e.Type == normEventType); + var actualNode = actualEvent.Node; + Assert.NotNull(actualNode); + + var actualCommand = new byte[expectedCommand.Length]; + var actualLength = actualEvent?.Node?.GetCommand(actualCommand, 0, expectedCommand.Length); + Assert.Equal(expectedCommand.Length, actualLength); + Assert.Equal(expectedCommand, actualCommand); + var actualContent = Encoding.ASCII.GetString(actualCommand); + Assert.Equal(expectedContent, actualContent); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + StopReceiver(); + } + } + + [SkippableFact(typeof(IOException))] + public void FreesBuffers() + { + _normSession.SetLoopback(true); + StartSender(); + StartReceiver(); + //Create data to write to the stream + var expectedContent = GenerateTextContent(); + byte[] expectedCommand = Encoding.ASCII.GetBytes(expectedContent); + + try + { + _normSession.SendCommand(expectedCommand, 0, expectedCommand.Length, false); + var normEventType = NormEventType.NORM_RX_CMD_NEW; + var actualEvents = GetEvents(); + Assert.Contains(normEventType, actualEvents.Select(e => e.Type)); + var actualEvent = actualEvents.First(e => e.Type == normEventType); + var actualNode = actualEvent.Node; + Assert.NotNull(actualNode); + actualNode.FreeBuffers(); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + StopReceiver(); + } + } + + [SkippableFact(typeof(IOException))] + public void RetainsAndReleases() + { + _normSession.SetLoopback(true); + StartSender(); + StartReceiver(); + //Create data to write to the stream + var expectedContent = GenerateTextContent(); + byte[] expectedCommand = Encoding.ASCII.GetBytes(expectedContent); + + try + { + _normSession.SendCommand(expectedCommand, 0, expectedCommand.Length, false); + var normEventType = NormEventType.NORM_RX_CMD_NEW; + var actualEvents = GetEvents(); + Assert.Contains(normEventType, actualEvents.Select(e => e.Type)); + var actualEvent = actualEvents.First(e => e.Type == normEventType); + var actualNode = actualEvent.Node; + Assert.NotNull(actualNode); + actualNode.Retain(); + actualNode.Release(); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + StopReceiver(); + } + } + + [SkippableFact(typeof(IOException))] + public void GetsObjectType_DATA() + { + StartSender(); + //Create data to write to the stream + var expectedContent = GenerateTextContent(); + byte[] expectedData = Encoding.ASCII.GetBytes(expectedContent); + using var dataBuffer = ByteBuffer.AllocateDirect(expectedData.Length); + dataBuffer.WriteArray(0, expectedData, 0, expectedData.Length); + + try + { + var normData = _normSession.DataEnqueue(dataBuffer, 0, expectedData.Length); + Assert.Equal(NormObjectType.NORM_OBJECT_DATA, normData.Type); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + } + } + + [SkippableFact(typeof(IOException))] + public void GetsObjectType_FILE() + { + StartSender(); + + var fileContent = GenerateTextContent(); + var fileName = Guid.NewGuid().ToString(); + var filePath = Path.Combine(_testPath, fileName); + File.WriteAllText(filePath, fileContent); + + try + { + var normFile = _normSession.FileEnqueue(filePath); + Assert.Equal(NormObjectType.NORM_OBJECT_FILE, normFile.Type); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + } + } + + [SkippableFact(typeof(IOException))] + public void GetsObjectType_STREAM() + { + StartSender(); + + var fileContent = GenerateTextContent(); + var data = Encoding.ASCII.GetBytes(fileContent); + NormStream? normStream = null; + + try + { + var repairWindowSize = 1024 * 1024; + normStream = _normSession.StreamOpen(repairWindowSize); + Assert.Equal(NormObjectType.NORM_OBJECT_STREAM, normStream.Type); + } + catch (Exception) + { + throw; + } + finally + { + normStream?.Close(true); + StopSender(); + } + } + + [SkippableFact(typeof(IOException))] + public void GetsObjectSize() + { + StartSender(); + //Create data to write to the stream + var expectedContent = GenerateTextContent(); + byte[] expectedData = Encoding.ASCII.GetBytes(expectedContent); + var expectedSize = Encoding.ASCII.GetByteCount(expectedContent); + using var dataBuffer = ByteBuffer.AllocateDirect(expectedData.Length); + dataBuffer.WriteArray(0, expectedData, 0, expectedData.Length); + + try + { + var normData = _normSession.DataEnqueue(dataBuffer, 0, expectedData.Length); + Assert.Equal(expectedSize, normData.Size); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + } + } + + [SkippableFact(typeof(IOException))] + public void SetsObjectNackingMode_NORM_NACK_NONE() + { + StartSender(); + //Create data to write to the stream + var expectedContent = GenerateTextContent(); + byte[] expectedData = Encoding.ASCII.GetBytes(expectedContent); + using var dataBuffer = ByteBuffer.AllocateDirect(expectedData.Length); + dataBuffer.WriteArray(0, expectedData, 0, expectedData.Length); + + try + { + var normData = _normSession.DataEnqueue(dataBuffer, 0, expectedData.Length); + normData.SetNackingMode(NormNackingMode.NORM_NACK_NONE); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + } + } + + [SkippableFact(typeof(IOException))] + public void SetsObjectNackingMode_NORM_NACK_INFO_ONLY() + { + StartSender(); + //Create data to write to the stream + var expectedContent = GenerateTextContent(); + byte[] expectedData = Encoding.ASCII.GetBytes(expectedContent); + using var dataBuffer = ByteBuffer.AllocateDirect(expectedData.Length); + dataBuffer.WriteArray(0, expectedData, 0, expectedData.Length); + + try + { + var normData = _normSession.DataEnqueue(dataBuffer, 0, expectedData.Length); + normData.SetNackingMode(NormNackingMode.NORM_NACK_INFO_ONLY); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + } + } + + [SkippableFact(typeof(IOException))] + public void SetsObjectNackingMode_NORM_NACK_NORMAL() + { + StartSender(); + //Create data to write to the stream + var expectedContent = GenerateTextContent(); + byte[] expectedData = Encoding.ASCII.GetBytes(expectedContent); + using var dataBuffer = ByteBuffer.AllocateDirect(expectedData.Length); + dataBuffer.WriteArray(0, expectedData, 0, expectedData.Length); + + try + { + var normData = _normSession.DataEnqueue(dataBuffer, 0, expectedData.Length); + normData.SetNackingMode(NormNackingMode.NORM_NACK_NORMAL); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + } + } + + [SkippableFact(typeof(IOException))] + public void GetsBytesPending() + { + StartSender(); + //Create data to write to the stream + var expectedContent = GenerateTextContent(); + byte[] expectedData = Encoding.ASCII.GetBytes(expectedContent); + var expectedBytesPending = (long)0; + using var dataBuffer = ByteBuffer.AllocateDirect(expectedData.Length); + dataBuffer.WriteArray(0, expectedData, 0, expectedData.Length); + + try + { + var normData = _normSession.DataEnqueue(dataBuffer, 0, expectedData.Length); + WaitForEvents(); + Assert.Equal(expectedBytesPending, normData.GetBytesPending()); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + } + } + + [SkippableFact(typeof(IOException))] + public void CancelsObject() + { + StartSender(); + //Create data to write to the stream + var expectedContent = GenerateTextContent(); + byte[] expectedData = Encoding.ASCII.GetBytes(expectedContent); + using var dataBuffer = ByteBuffer.AllocateDirect(expectedData.Length); + dataBuffer.WriteArray(0, expectedData, 0, expectedData.Length); + + try + { + var normData = _normSession.DataEnqueue(dataBuffer, 0, expectedData.Length); + normData.Cancel(); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + } + } + + [SkippableFact(typeof(IOException))] + public void RetainsObject() + { + StartSender(); + //Create data to write to the stream + var expectedContent = GenerateTextContent(); + byte[] expectedData = Encoding.ASCII.GetBytes(expectedContent); + using var dataBuffer = ByteBuffer.AllocateDirect(expectedData.Length); + dataBuffer.WriteArray(0, expectedData, 0, expectedData.Length); + + try + { + var normData = _normSession.DataEnqueue(dataBuffer, 0, expectedData.Length); + normData.Retain(); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + } + } + + [SkippableFact(typeof(IOException))] + public void ReleasesObject() + { + StartSender(); + //Create data to write to the stream + var expectedContent = GenerateTextContent(); + byte[] expectedData = Encoding.ASCII.GetBytes(expectedContent); + using var dataBuffer = ByteBuffer.AllocateDirect(expectedData.Length); + dataBuffer.WriteArray(0, expectedData, 0, expectedData.Length); + + try + { + var normData = _normSession.DataEnqueue(dataBuffer, 0, expectedData.Length); + normData.Retain(); + normData.Release(); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + } + } + + [SkippableFact(typeof(IOException))] + public void GetsSenderThrowsException() + { + StartSender(); + //Create data to write to the stream + var expectedContent = GenerateTextContent(); + byte[] expectedData = Encoding.ASCII.GetBytes(expectedContent); + using var dataBuffer = ByteBuffer.AllocateDirect(expectedData.Length); + dataBuffer.WriteArray(0, expectedData, 0, expectedData.Length); + + try + { + var normData = _normSession.DataEnqueue(dataBuffer, 0, expectedData.Length); + Assert.Throws(() => normData.Sender); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + } + } + + [SkippableFact(typeof(IOException))] + public void GetsSender() + { + _normSession.SetLoopback(true); + StartSender(); + StartReceiver(); + + //Set up cache directory + var folderName = Guid.NewGuid().ToString(); + var cachePath = Path.Combine(_testPath, folderName); + Directory.CreateDirectory(cachePath); + _normInstance.SetCacheDirectory(cachePath); + + //Create data to be sent + var expectedContent = GenerateTextContent(); + var expectedData = Encoding.ASCII.GetBytes(expectedContent); + using var dataBuffer = ByteBuffer.AllocateDirect(expectedData.Length); + dataBuffer.WriteArray(0, expectedData, 0, expectedData.Length); + + try + { + var normData = _normSession.DataEnqueue(dataBuffer, 0, expectedData.Length); + var normEventType = NormEventType.NORM_RX_OBJECT_COMPLETED; + var actualEvents = GetEvents(); + Assert.Contains(normEventType, actualEvents.Select(e => e.Type)); + var actualEvent = actualEvents.First(e => e.Type == normEventType); + var actualObject = actualEvent.Object; + Assert.NotEqual(NormNode.NORM_NODE_INVALID, actualObject!.Sender); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + StopReceiver(); + } + } + + [SkippableFact(typeof(IOException))] + public void GetsObjectHashCode() + { + _normSession.SetLoopback(true); + StartSender(); + StartReceiver(); + + //Set up cache directory + var folderName = Guid.NewGuid().ToString(); + var cachePath = Path.Combine(_testPath, folderName); + Directory.CreateDirectory(cachePath); + _normInstance.SetCacheDirectory(cachePath); + + //Create data to be sent + var expectedContent = GenerateTextContent(); + var expectedData = Encoding.ASCII.GetBytes(expectedContent); + using var dataBuffer = ByteBuffer.AllocateDirect(expectedData.Length); + dataBuffer.WriteArray(0, expectedData, 0, expectedData.Length); + + try + { + var normData = _normSession.DataEnqueue(dataBuffer, 0, expectedData.Length); + var normEventType = NormEventType.NORM_RX_OBJECT_COMPLETED; + var actualEvents = GetEvents(); + Assert.Contains(normEventType, actualEvents.Select(e => e.Type)); + var actualEvent = actualEvents.First(e => e.Type == normEventType); + var actualObject = actualEvent.Object; + var expectedHashCode = (int)actualObject!.Handle; + var actualHashCode = actualObject.GetHashCode(); + Assert.Equal(expectedHashCode, actualHashCode); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + StopReceiver(); + } + } + + [SkippableFact(typeof(IOException))] + public void ObjectsEqual() + { + _normSession.SetLoopback(true); + StartSender(); + StartReceiver(); + + //Set up cache directory + var folderName = Guid.NewGuid().ToString(); + var cachePath = Path.Combine(_testPath, folderName); + Directory.CreateDirectory(cachePath); + _normInstance.SetCacheDirectory(cachePath); + + //Create data to be sent + var expectedContent = GenerateTextContent(); + var expectedData = Encoding.ASCII.GetBytes(expectedContent); + using var dataBuffer = ByteBuffer.AllocateDirect(expectedData.Length); + dataBuffer.WriteArray(0, expectedData, 0, expectedData.Length); + + try + { + _normSession.DataEnqueue(dataBuffer, 0, expectedData.Length); + + var actualEvents = GetEvents(); + + var firstNormEventType = NormEventType.NORM_RX_OBJECT_NEW; + Assert.Contains(firstNormEventType, actualEvents.Select(e => e.Type)); + var firstEvent = actualEvents.First(e => e.Type == firstNormEventType); + var firstObject = firstEvent.Object; + Assert.NotNull(firstObject); + + var secondNormEventType = NormEventType.NORM_RX_OBJECT_COMPLETED; + Assert.Contains(secondNormEventType, actualEvents.Select(e => e.Type)); + var secondEvent = actualEvents.First(e => e.Type == secondNormEventType); + var secondObject = secondEvent.Object; + Assert.NotNull(secondObject); + + Assert.True(firstObject.Equals(secondObject)); + } + catch (Exception) + { + throw; + } + finally + { + StopSender(); + StopReceiver(); + } + } + + [SkippableFact(typeof(IOException))] + public void NormStreamHasVacancy() + { + StartSender(); + + var fileContent = GenerateTextContent(); + var data = Encoding.ASCII.GetBytes(fileContent); + NormStream? normStream = null; + + try + { + var repairWindowSize = 1024 * 1024; + normStream = _normSession.StreamOpen(repairWindowSize); + + Assert.True(normStream.HasVacancy); + } + catch (Exception) + { + throw; + } + finally + { + normStream?.Close(true); + StopSender(); + } + } + + [SkippableFact(typeof(IOException))] + public void NormStreamGetsReadOffset() + { + StartSender(); + + var fileContent = GenerateTextContent(); + var data = Encoding.ASCII.GetBytes(fileContent); + NormStream? normStream = null; + var expectedReadOffset = 0; + + try + { + var repairWindowSize = 1024 * 1024; + normStream = _normSession.StreamOpen(repairWindowSize); + + Assert.Equal(expectedReadOffset, normStream.ReadOffset); + } + catch (Exception) + { + throw; + } + finally + { + normStream?.Close(true); + StopSender(); + } + } + + [SkippableFact(typeof(IOException))] + public void NormStreamSeeksMsgStart() + { + _normSession.SetLoopback(true); + StartSender(); + StartReceiver(); + + //Set up cache directory + var folderName = Guid.NewGuid().ToString(); + var cachePath = Path.Combine(_testPath, folderName); + Directory.CreateDirectory(cachePath); + _normInstance.SetCacheDirectory(cachePath); + + var fileContent = GenerateTextContent(); + var data = Encoding.ASCII.GetBytes(fileContent); + var dataOffset = 0; + NormStream? normStream = null; + + try + { + var repairWindowSize = 1024 * 1024; + normStream = _normSession.StreamOpen(repairWindowSize); + + var expectedBytesWritten = data.Length; + normStream.Write(data, dataOffset, data.Length); + + normStream.MarkEom(); + normStream.Flush(); + var normEvents = GetEvents(); + AssertNormEvents(normEvents); + + var expectedNormEventType = NormEventType.NORM_RX_OBJECT_UPDATED; + Assert.Contains(expectedNormEventType, normEvents.Select(e => e.Type)); + var normObjectEvent = normEvents.First(e => e.Type == expectedNormEventType); + + var receivedNormStream = Assert.IsType(normObjectEvent.Object); + + Assert.True(receivedNormStream.SeekMsgStart()); + } + catch (Exception) + { + throw; + } + finally + { + normStream?.Close(true); + StopSender(); + StopReceiver(); + } + } + + [SkippableFact(typeof(IOException))] + public void NormStreamSetsPushEnable() + { + StartSender(); + + var fileContent = GenerateTextContent(); + var data = Encoding.ASCII.GetBytes(fileContent); + NormStream? normStream = null; + + try + { + var repairWindowSize = 1024 * 1024; + normStream = _normSession.StreamOpen(repairWindowSize); + + normStream.SetPushEnable(true); + } + catch (Exception) + { + throw; + } + finally + { + normStream?.Close(true); + StopSender(); + } + } + + [SkippableFact(typeof(IOException))] + public void NormStreamSetsAutoFlush_FLUSH_NONE() + { + StartSender(); + + var fileContent = GenerateTextContent(); + var data = Encoding.ASCII.GetBytes(fileContent); + NormStream? normStream = null; + + try + { + var repairWindowSize = 1024 * 1024; + normStream = _normSession.StreamOpen(repairWindowSize); + + normStream.SetAutoFlush(NormFlushMode.NORM_FLUSH_NONE); + } + catch (Exception) + { + throw; + } + finally + { + normStream?.Close(true); + StopSender(); + } + } + + [SkippableFact(typeof(IOException))] + public void NormStreamSetsAutoFlush_FLUSH_PASSIVE() + { + StartSender(); + + var fileContent = GenerateTextContent(); + var data = Encoding.ASCII.GetBytes(fileContent); + NormStream? normStream = null; + + try + { + var repairWindowSize = 1024 * 1024; + normStream = _normSession.StreamOpen(repairWindowSize); + + normStream.SetAutoFlush(NormFlushMode.NORM_FLUSH_PASSIVE); + } + catch (Exception) + { + throw; + } + finally + { + normStream?.Close(true); + StopSender(); + } + } + + [SkippableFact(typeof(IOException))] + public void NormStreamSetsAutoFlush_FLUSH_ACTIVE() + { + StartSender(); + + var fileContent = GenerateTextContent(); + var data = Encoding.ASCII.GetBytes(fileContent); + NormStream? normStream = null; + + try + { + var repairWindowSize = 1024 * 1024; + normStream = _normSession.StreamOpen(repairWindowSize); + + normStream.SetAutoFlush(NormFlushMode.NORM_FLUSH_ACTIVE); + } + catch (Exception) + { + throw; + } + finally + { + normStream?.Close(true); + StopSender(); + } + } + } +} \ No newline at end of file diff --git a/src/dotnet/tests/Mil.Navy.Nrl.Norm.IntegrationTests/Usings.cs b/src/dotnet/tests/Mil.Navy.Nrl.Norm.IntegrationTests/Usings.cs new file mode 100644 index 00000000..8c927eb7 --- /dev/null +++ b/src/dotnet/tests/Mil.Navy.Nrl.Norm.IntegrationTests/Usings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file