diff --git a/.github/actions/build_nuget/action.yml b/.github/actions/build_nuget/action.yml
index ed709f2..d8c2fd0 100644
--- a/.github/actions/build_nuget/action.yml
+++ b/.github/actions/build_nuget/action.yml
@@ -31,6 +31,30 @@ runs:
- name: Build Nuget Package for ${{ inputs.projectPath }}
shell: bash
working-directory: ${{ inputs.projectPath }}
- run: dotnet pack --include-source --include-symbols /p:ContinuousIntegrationBuild=true /p:PackageVersion=${{ steps.gitversion.outputs.nugetVersionV2 }} -c Release -o ${{ inputs.outputDirectory }}
+ run: |
+ echo "Building NuGet package for ${{ inputs.projectPath }}"
+ echo "Package version: ${{ steps.gitversion.outputs.nugetVersionV2 }}"
+ echo "Output directory: ${{ inputs.outputDirectory }}"
+
+ # Create output directory if it doesn't exist
+ mkdir -p ${{ inputs.outputDirectory }}
+
+ # Build the package
+ dotnet pack \
+ --include-source \
+ --include-symbols \
+ /p:ContinuousIntegrationBuild=true \
+ /p:PackageVersion=${{ steps.gitversion.outputs.nugetVersionV2 }} \
+ -c Release \
+ -o ${{ inputs.outputDirectory }}
+
+ # Verify package was created
+ if [ ! "$(ls -A ${{ inputs.outputDirectory }})" ]; then
+ echo "ERROR: No package was created in ${{ inputs.outputDirectory }}"
+ exit 1
+ fi
+
+ echo "Successfully created package(s):"
+ ls -la ${{ inputs.outputDirectory }}
\ No newline at end of file
diff --git a/.github/actions/publish_nuget/action.yml b/.github/actions/publish_nuget/action.yml
index d014d8d..e8b1339 100644
--- a/.github/actions/publish_nuget/action.yml
+++ b/.github/actions/publish_nuget/action.yml
@@ -18,10 +18,12 @@ runs:
- name: Publish Nuget Packages
shell: bash
- working-directory: ${{ inputs.projectPath }}
run: |
for file in ${{ inputs.nugetPackageDirectory }}/*.nupkg; do
- dotnet nuget push $file -k ${{ inputs.nugetApiKey }} -s ${{ inputs.nugetPackageSource }} --skip-duplicate
+ if [ -f "$file" ]; then
+ echo "Publishing $file to ${{ inputs.nugetPackageSource }}"
+ dotnet nuget push "$file" -k ${{ inputs.nugetApiKey }} -s ${{ inputs.nugetPackageSource }} --skip-duplicate
+ fi
done
\ No newline at end of file
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..e698e49
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,31 @@
+# To get started with Dependabot version updates, you'll need to specify which
+# package ecosystems to update and where the package manifests are located.
+# Please see the documentation for all configuration options:
+# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
+
+version: 2
+updates:
+ # Enable version updates for NuGet
+ - package-ecosystem: "nuget"
+ directory: "/src"
+ schedule:
+ interval: "weekly"
+ open-pull-requests-limit: 10
+ reviewers:
+ - "domdeger"
+ commit-message:
+ prefix: "deps"
+ prefix-development: "deps-dev"
+ include: "scope"
+
+ # Enable version updates for GitHub Actions
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ open-pull-requests-limit: 5
+ reviewers:
+ - "domdeger"
+ commit-message:
+ prefix: "ci"
+ include: "scope"
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000..e641b3d
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,27 @@
+## Description
+Brief description of what this PR does.
+
+## Type of change
+Please delete options that are not relevant.
+
+- [ ] Bug fix (non-breaking change which fixes an issue)
+- [ ] New feature (non-breaking change which adds functionality)
+- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
+- [ ] This change requires a documentation update
+
+## How Has This Been Tested?
+Please describe the tests that you ran to verify your changes.
+
+- [ ] Unit tests
+- [ ] Integration tests
+- [ ] Manual testing
+
+## Checklist:
+- [ ] My code follows the style guidelines of this project
+- [ ] I have performed a self-review of my own code
+- [ ] I have commented my code, particularly in hard-to-understand areas
+- [ ] I have made corresponding changes to the documentation
+- [ ] My changes generate no new warnings
+- [ ] I have added tests that prove my fix is effective or that my feature works
+- [ ] New and existing unit tests pass locally with my changes
+- [ ] Any dependent changes have been merged and published in downstream modules
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e73e86a..cc3337f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -14,15 +14,32 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
fetch-depth: 0
- name: Setup .NET
- uses: actions/setup-dotnet@v3
+ uses: actions/setup-dotnet@v4
with:
- dotnet-version: 8.0.x
+ dotnet-version: |
+ 7.0.x
+ 8.0.x
+ 9.0.x
+
+ - name: Cache NuGet packages
+ uses: actions/cache@v4
+ with:
+ path: ~/.nuget/packages
+ key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
+ restore-keys: |
+ ${{ runner.os }}-nuget-
+
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v3
+ with:
+ languages: csharp
+
- name: Restore dependencies
run: dotnet restore ./src
@@ -32,6 +49,9 @@ jobs:
- name: Test
run: dotnet test ./src --no-build --verbosity normal --collect:"XPlat Code Coverage" --results-directory ./coverage --filter Category!=IntegrationTest
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v3
+
- name: Merge Code Coverage files with Reportgenerator
uses: danielpalme/ReportGenerator-GitHub-Action@5.2.0
with:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 76f56f3..dbd5e4a 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -8,7 +8,7 @@ jobs:
name: Build Nuget Packages
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 0
@@ -23,7 +23,21 @@ jobs:
updateAssemblyInfo: true
useConfigFile: true
- - run: mkdir -p $PWD/npks
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 8.0.x
+
+ - name: Cache NuGet packages
+ uses: actions/cache@v4
+ with:
+ path: ~/.nuget/packages
+ key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
+ restore-keys: |
+ ${{ runner.os }}-nuget-
+
+ - name: Create output directory
+ run: mkdir -p $PWD/npkgs
- uses: ./.github/actions/build_nuget
name: Build Nuget Package for Conversion
@@ -75,31 +89,63 @@ jobs:
name: Build Nuget Package for Vendors.Ifm
with:
projectPath: ./src/Vendors/Ifm
- outputDirectory: ../../../npkgs
+ outputDirectory: ../../npkgs
- - run: echo $PWD
- - run: ls -la $PWD/npkgs
- - name: Publish Nuget Package for ${{ inputs.projectPath }}
+ - name: Verify packages were created
+ run: |
+ if [ ! "$(ls -A $PWD/npkgs)" ]; then
+ echo "No packages were created!"
+ exit 1
+ fi
+ echo "Created packages:"
+ ls -la $PWD/npkgs
+
+ - name: Publish Nuget Package for packages
uses: actions/upload-artifact@v4
with:
- name: packages
+ name: nuget-packages
path: ./npkgs
+ retention-days: 30
publish:
name: Publish Nuget Packages
runs-on: ubuntu-latest
needs: pack
+ environment: production
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
+
- uses: actions/download-artifact@v4
with:
- name: packages
+ name: nuget-packages
path: ./packages
- name: Setup .NET
- uses: actions/setup-dotnet@v3
+ uses: actions/setup-dotnet@v4
with:
- dotnet-version: 8.0.x
+ dotnet-version: 9.0.x
+
+ - name: Verify downloaded packages
+ run: |
+ echo "Downloaded packages:"
+ ls -la $PWD/packages
+ if [ ! "$(ls -A $PWD/packages)" ]; then
+ echo "No packages downloaded!"
+ exit 1
+ fi
+
+ - name: Validate package versions
+ run: |
+ echo "Validating package versions..."
+ for file in $PWD/packages/*.nupkg; do
+ if [ -f "$file" ]; then
+ filename=$(basename "$file")
+ echo "Checking if $filename already exists on NuGet.org..."
+ # Extract package name and version from filename
+ # This is a basic validation - you might want to enhance this
+ echo "Package: $filename"
+ fi
+ done
- run: echo "$GITHUB_CONTEXT"
- name: Upload Packages to release
@@ -107,16 +153,12 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
for file in $PWD/packages/*.nupkg; do
- gh release upload ${{ github.event.release.tag_name }} $file
+ if [ -f "$file" ]; then
+ echo "Uploading $file to release ${{ github.event.release.tag_name }}"
+ gh release upload ${{ github.event.release.tag_name }} "$file" --clobber
+ fi
done
- # - uses: ./.github/actions/publish_nuget
- # name: Publish Nuget Packages to GitHub
- # with:
- # nugetApiKey: ${{ secrets.GITHUB_TOKEN }}
- # nugetPackageSource: ${{ vars.NUGET_PACKAGE_SOURCE }}
- # nugetPackageDirectory: $PWD/packages
-
- uses: ./.github/actions/publish_nuget
name: Publish Nuget Packages to Nuget.org
with:
diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml
new file mode 100644
index 0000000..102ef0e
--- /dev/null
+++ b/.github/workflows/security.yml
@@ -0,0 +1,73 @@
+name: Security Scan
+
+on:
+ schedule:
+ # Run weekly on Mondays at 9 AM UTC
+ - cron: '0 9 * * 1'
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ security:
+ name: Security Scan
+ runs-on: ubuntu-latest
+ permissions:
+ security-events: write
+ contents: read
+ actions: read
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 9.0.x
+
+ - name: Cache NuGet packages
+ uses: actions/cache@v4
+ with:
+ path: ~/.nuget/packages
+ key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
+ restore-keys: |
+ ${{ runner.os }}-nuget-
+
+ - name: Restore dependencies
+ run: dotnet restore ./src
+
+ - name: Run .NET security scan
+ run: |
+ dotnet list ./src package --vulnerable --include-transitive --include-prerelease > vulnerability-report.txt 2>&1 || true
+ if grep -q "has the following vulnerable packages" vulnerability-report.txt; then
+ echo "Vulnerable packages found:"
+ cat vulnerability-report.txt
+ echo "::warning::Vulnerable NuGet packages detected"
+ else
+ echo "No vulnerable packages found"
+ fi
+
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v3
+ with:
+ languages: csharp
+
+ - name: Build for security analysis
+ run: dotnet build ./src --no-restore
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v3
+ with:
+ category: "/language:csharp"
+
+ - name: Upload vulnerability report
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: vulnerability-report
+ path: vulnerability-report.txt
+ retention-days: 30
diff --git a/samples/01_convert_iol_data_with_iodd/Program.cs b/samples/01_convert_iol_data_with_iodd/Program.cs
index 45f13a2..5128fdd 100644
--- a/samples/01_convert_iol_data_with_iodd/Program.cs
+++ b/samples/01_convert_iol_data_with_iodd/Program.cs
@@ -6,10 +6,11 @@
using IOLinkNET.IODD;
using IOLinkNET.IODD.Resolution;
-
// First we need to parse an IODD definition. This can be done by using the IODDParser class. Or by directly retrieving the IODD from the IODD finder with the IODDFinderPublicClient class.
IODDParser parser = new();
-var device = parser.Parse(XElement.Load("../iodds/Balluff-BISM4A308240107S4-CCM-20210928-IODD1.1.xml"));
+var device = parser.Parse(
+ XElement.Load("./iodds/Balluff-BISM4A308240107S4-CCM-20210928-IODD1.1.xml")
+);
// Now we can use the IoddConverter class to convert the data.
var converter = new IoddConverter();
@@ -19,7 +20,6 @@
void DecodeParameterData()
{
-
// Retrieve the parameter data from the device
var data = Convert.FromBase64String("SGVsbG9IZWxsb0hlbGxvSGVsbG9IZWxsb0hlbGxvSGVsbG9IZWxsbw==");
diff --git a/samples/03_work_with_iodds/03_work_with_iodds.csproj b/samples/03_work_with_iodds/03_work_with_iodds.csproj
new file mode 100644
index 0000000..5b7791d
--- /dev/null
+++ b/samples/03_work_with_iodds/03_work_with_iodds.csproj
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+ Exe
+ net8.0
+ 03_work_with_iodds
+ enable
+ enable
+
+
+
+ iodds\%(RecursiveDir)/%(FileName)%(Extension)
+ Always
+
+
+
diff --git a/samples/03_work_with_iodds/Program.cs b/samples/03_work_with_iodds/Program.cs
new file mode 100644
index 0000000..f16159d
--- /dev/null
+++ b/samples/03_work_with_iodds/Program.cs
@@ -0,0 +1,84 @@
+using System.Xml.Linq;
+using IOLinkNET.IODD;
+using IOLinkNET.IODD.Structure.Datatypes;
+using IOLinkNET.IODD.Structure.Structure.Menu;
+
+// Load the IODD file and parse it.
+IODDParser parser = new();
+var iodd = parser.Parse(
+ XElement.Load("./iodds/Balluff-BISM4A308240107S4-CCM-20210928-IODD1.1.xml")
+);
+
+var parameters = iodd.ProfileBody.DeviceFunction.VariableCollection;
+
+PrintDeviceInformation();
+AnalyzeDeviceVariables();
+AnalyzeProcessData();
+DisplayMenuStructure();
+
+void PrintDeviceInformation()
+{
+ Console.WriteLine(
+ $"Vendor: {iodd.ProfileBody.DeviceIdentity.VendorName} ({iodd.ProfileBody.DeviceIdentity.VendorId}), Device: {iodd.ProfileBody.DeviceIdentity.DeviceId}"
+ );
+}
+
+void AnalyzeDeviceVariables()
+{
+ Console.WriteLine($"Device has {parameters.Count()} parameters:");
+ foreach (var parameter in parameters)
+ {
+ Console.WriteLine($"\t{parameter.Name.TextId} ({parameter.Index})");
+ }
+
+ var parameterWithUnresolvedData = parameters.First(p => p.Index == 147);
+
+ Console.WriteLine(
+ $"\nParameter with unresolved data type: {parameterWithUnresolvedData.Name.TextId}:"
+ );
+
+ var parameterDataType = parameterWithUnresolvedData.Datatype as RecordT;
+ foreach (var field in parameterDataType!.Items)
+ {
+ Console.WriteLine(
+ $"\t{field.Name.TextId} DataType: ({field.Type}), DataTypeRef: {field.Ref}"
+ );
+ }
+}
+
+void AnalyzeProcessData()
+{
+ var processData = iodd.ProfileBody.DeviceFunction.ProcessDataCollection;
+ if (processData.Count() == 1)
+ {
+ Console.WriteLine("Device has no process data condition.");
+
+ var processDataType = processData.First().ProcessDataIn!.Datatype as RecordT;
+ foreach (var field in processDataType!.Items)
+ {
+ Console.WriteLine(
+ $"\t{field.Name.TextId} DataType: ({field.Type}), DataTypeRef: {field.Ref}"
+ );
+ }
+ }
+ else
+ {
+ var condition = processData.First(pd => pd.Condition is not null).Condition;
+ Console.WriteLine($"Device has process data condition: {condition!.VariableId}");
+ }
+}
+
+void DisplayMenuStructure()
+{
+ var menuStructure = iodd.ProfileBody.DeviceFunction.UserInterface;
+ Console.WriteLine($"Device has {menuStructure.MenuCollection.Count()} menus:");
+
+ foreach (var menu in menuStructure.MenuCollection)
+ {
+ Console.WriteLine($"\t{menu.Menu.Name}");
+ foreach (var subMenu in menu.Menu.MenuRefs ?? Enumerable.Empty())
+ {
+ Console.WriteLine($"\t\t{subMenu.MenuId}");
+ }
+ }
+}
diff --git a/samples/samples.sln b/samples/samples.sln
index dff4004..0990733 100644
--- a/samples/samples.sln
+++ b/samples/samples.sln
@@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "01_convert_iol_data_with_io
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "02_read_process_data_from_ifm_iotcore", "02_read_process_data_from_ifm_iotcore\02_read_process_data_from_ifm_iotcore.csproj", "{5C06058D-B10A-4903-8E0C-E88779DEBD02}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "02_read_process_data_from_ifm_iotcore", "03_work_with_iodds\03_work_with_iodds.csproj", "{4CE6306D-5438-4087-81F5-A27450A0368A}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -24,5 +26,9 @@ Global
{5C06058D-B10A-4903-8E0C-E88779DEBD02}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5C06058D-B10A-4903-8E0C-E88779DEBD02}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5C06058D-B10A-4903-8E0C-E88779DEBD02}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4CE6306D-5438-4087-81F5-A27450A0368A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4CE6306D-5438-4087-81F5-A27450A0368A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4CE6306D-5438-4087-81F5-A27450A0368A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4CE6306D-5438-4087-81F5-A27450A0368A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/src/Conversion/IIoddDataConverter.cs b/src/Conversion/IIoddDataConverter.cs
deleted file mode 100644
index 0234db9..0000000
--- a/src/Conversion/IIoddDataConverter.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using IOLinkNET.IODD.Resolution;
-
-namespace IOLinkNET.Conversion;
-
-public interface IIoddDataConverter
-{
- object Convert(ParsableDatatype datatypeDef, ReadOnlySpan data);
-}
\ No newline at end of file
diff --git a/src/Conversion/IoddConverter.cs b/src/Conversion/IoddConverter.cs
deleted file mode 100644
index 7b0a670..0000000
--- a/src/Conversion/IoddConverter.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using IOLinkNET.IODD.Resolution;
-
-namespace IOLinkNET.Conversion;
-
-public class IoddConverter : IIoddDataConverter
-{
- public object Convert(ParsableDatatype datatypeDef, ReadOnlySpan data) => datatypeDef switch
- {
- ParsableComplexDataTypeDef complexType => IoddComplexConverter.Convert(complexType, data),
- ParsableSimpleDatatypeDef simpleType => IoddScalarReader.Convert(simpleType, data),
- _ => throw new NotImplementedException()
- };
-}
\ No newline at end of file
diff --git a/src/Conversion/IoddScalarWriter.cs b/src/Conversion/IoddScalarWriter.cs
deleted file mode 100644
index c0eb47f..0000000
--- a/src/Conversion/IoddScalarWriter.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-using System.Buffers.Binary;
-using System.Numerics;
-using System.Text;
-
-using Conversion.Extensions;
-
-using IOLinkNET.IODD.Resolution;
-using IOLinkNET.IODD.Structure.Datatypes;
-
-namespace Conversion;
-
-public class IoddScalarWriter
-{
- public static byte[] Write(ParsableSimpleDatatypeDef typeDef, object value)
- => typeDef switch
- {
- { Datatype: KindOfSimpleType.Boolean } => BitConverter.GetBytes((bool)value),
- { Datatype: KindOfSimpleType.Float } => WriteFloat(value),
- { Datatype: KindOfSimpleType.UInteger } => WriteUInt(value, typeDef.Length),
- { Datatype: KindOfSimpleType.Integer } => WriteInt(value, typeDef.Length),
- { Datatype: KindOfSimpleType.OctetString } => Convert.FromHexString((string)value),
- ParsableStringDef s => WriteString(s, (string)value),
- _ => throw new NotImplementedException()
- };
-
- private static byte[] WriteUInt(object value, ushort bitLength)
- => bitLength switch
- {
- <= 2 => throw new ArgumentOutOfRangeException(nameof(bitLength), bitLength, "Invalid bitLength for UInt -> byte[] write"),
- <= 16 => WriteInt(value, bitLength, Convert.ToUInt16),
- <= 32 => WriteInt(value, bitLength, Convert.ToUInt32),
- <= 64 => WriteInt(value, bitLength, Convert.ToUInt64),
- _ => throw new ArgumentOutOfRangeException(nameof(bitLength), bitLength, "Invalid bitLength for UInt -> byte[] write")
- };
-
- private static byte[] WriteInt(object value, ushort bitLength)
- => bitLength switch
- {
- <= 2 => throw new ArgumentOutOfRangeException(nameof(bitLength), bitLength, "Invalid bitLength for Int -> byte[] write"),
- <= 16 => WriteInt(value, bitLength, Convert.ToInt16),
- <= 32 => WriteInt(value, bitLength, Convert.ToInt32),
- <= 64 => WriteInt(value, bitLength, Convert.ToInt64),
- _ => throw new ArgumentOutOfRangeException(nameof(bitLength), bitLength, "Invalid bitLength for Int -> byte[] write")
- };
-
- private static byte[] WriteString(ParsableStringDef stringDef, string value)
- => stringDef.Encoding switch
- {
- StringTEncoding.ASCII => Encoding.ASCII.GetBytes(value),
- StringTEncoding.UTF8 => Encoding.UTF8.GetBytes(value),
- _ => throw new NotImplementedException($"Encoding {stringDef.Encoding} is not supported.")
- };
-
- private static byte[] WriteFloat(object value)
- {
- var bytes = new byte[4];
- BinaryPrimitives.WriteSingleBigEndian(bytes, (float)value);
- return bytes;
- }
-
- private static byte[] WriteInt(object value, ushort bitLength, Func