This is an automated email from the ASF dual-hosted git repository.
hgruszecki pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iggy.git
The following commit(s) were added to refs/heads/master by this push:
new fc5d9005c feat(csharp): add TCP/TLS producer and consumer examples
(#2821)
fc5d9005c is described below
commit fc5d9005c808f22f43391a8850df73a3bb45c91a
Author: Atharva Lade <[email protected]>
AuthorDate: Tue Mar 10 10:42:28 2026 -0500
feat(csharp): add TCP/TLS producer and consumer examples (#2821)
Co-authored-by: Ćukasz Zborek <[email protected]>
---
examples/csharp/Iggy_SDK.Examples.sln | 17 +++
examples/csharp/README.md | 13 ++
.../Iggy_SDK.Examples.TcpTls.Consumer.csproj | 17 +++
.../Iggy_SDK.Examples.TcpTls.Consumer/Program.cs | 113 +++++++++++++++++
.../Iggy_SDK.Examples.TcpTls.Producer.csproj | 17 +++
.../Iggy_SDK.Examples.TcpTls.Producer/Program.cs | 141 +++++++++++++++++++++
scripts/run-csharp-examples-from-readme.sh | 87 ++++++++++++-
7 files changed, 403 insertions(+), 2 deletions(-)
diff --git a/examples/csharp/Iggy_SDK.Examples.sln
b/examples/csharp/Iggy_SDK.Examples.sln
index 79e94a648..643b23bfc 100644
--- a/examples/csharp/Iggy_SDK.Examples.sln
+++ b/examples/csharp/Iggy_SDK.Examples.sln
@@ -38,6 +38,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") =
"Iggy_SDK.Examples.NewSdk.Co
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") =
"Iggy_SDK.Examples.NewSdk.Producer",
"src\NewSdk\Iggy_SDK.Examples.NewSdk.Producer\Iggy_SDK.Examples.NewSdk.Producer.csproj",
"{DB9EAC21-61C8-449A-95A9-6DC369B15C02}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TcpTls", "TcpTls",
"{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") =
"Iggy_SDK.Examples.TcpTls.Producer",
"src\TcpTls\Iggy_SDK.Examples.TcpTls.Producer\Iggy_SDK.Examples.TcpTls.Producer.csproj",
"{B2C3D4E5-F6A7-8901-BCDE-F12345678901}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") =
"Iggy_SDK.Examples.TcpTls.Consumer",
"src\TcpTls\Iggy_SDK.Examples.TcpTls.Consumer\Iggy_SDK.Examples.TcpTls.Consumer.csproj",
"{C3D4E5F6-A7B8-9012-CDEF-123456789012}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -95,6 +101,14 @@ Global
{DB9EAC21-61C8-449A-95A9-6DC369B15C02}.Debug|Any CPU.Build.0 =
Debug|Any CPU
{DB9EAC21-61C8-449A-95A9-6DC369B15C02}.Release|Any
CPU.ActiveCfg = Release|Any CPU
{DB9EAC21-61C8-449A-95A9-6DC369B15C02}.Release|Any CPU.Build.0
= Release|Any CPU
+ {B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|Any CPU.ActiveCfg
= Debug|Any CPU
+ {B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|Any CPU.Build.0 =
Debug|Any CPU
+ {B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|Any
CPU.ActiveCfg = Release|Any CPU
+ {B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|Any CPU.Build.0
= Release|Any CPU
+ {C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|Any CPU.ActiveCfg
= Debug|Any CPU
+ {C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|Any CPU.Build.0 =
Debug|Any CPU
+ {C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|Any
CPU.ActiveCfg = Release|Any CPU
+ {C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|Any CPU.Build.0
= Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{6CA0C169-109F-4A43-A21F-0792C1F865C8} =
{3576C181-8BD5-4845-9CDA-835B33A29A29}
@@ -113,6 +127,9 @@ Global
{85F31923-45DF-4AEA-A28E-D37CF8E61A78} =
{3576C181-8BD5-4845-9CDA-835B33A29A29}
{762203E3-A41B-414A-93BF-B963EAE447D1} =
{85F31923-45DF-4AEA-A28E-D37CF8E61A78}
{DB9EAC21-61C8-449A-95A9-6DC369B15C02} =
{85F31923-45DF-4AEA-A28E-D37CF8E61A78}
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890} =
{3576C181-8BD5-4845-9CDA-835B33A29A29}
+ {B2C3D4E5-F6A7-8901-BCDE-F12345678901} =
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}
+ {C3D4E5F6-A7B8-9012-CDEF-123456789012} =
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}
EndGlobalSection
EndGlobal
diff --git a/examples/csharp/README.md b/examples/csharp/README.md
index ea7d6c0b1..19e074650 100644
--- a/examples/csharp/README.md
+++ b/examples/csharp/README.md
@@ -67,6 +67,19 @@ dotnet run --project
examples/csharp/src/MessageEnvelope/Iggy_SDK.Examples.Mess
Uses MessagesGenerator to create OrderCreated, OrderConfirmed, and
OrderRejected messages wrapped in JSON envelopes for type identification.
+## Security Examples
+
+### TCP/TLS
+
+Demonstrates secure TLS-encrypted TCP connections:
+
+```bash
+dotnet run --project
examples/csharp/src/TcpTls/Iggy_SDK.Examples.TcpTls.Producer
+dotnet run --project
examples/csharp/src/TcpTls/Iggy_SDK.Examples.TcpTls.Consumer
+```
+
+Uses `IggyClientConfigurator` with `TlsSettings` (Enabled, Hostname,
CertificatePath) to establish TLS-encrypted TCP connections with CA certificate
verification. The server must be started with TLS enabled
(`IGGY_TCP_TLS_ENABLED=true`).
+
## Example Structure
All examples can be executed directly from the repository. Follow these steps:
diff --git
a/examples/csharp/src/TcpTls/Iggy_SDK.Examples.TcpTls.Consumer/Iggy_SDK.Examples.TcpTls.Consumer.csproj
b/examples/csharp/src/TcpTls/Iggy_SDK.Examples.TcpTls.Consumer/Iggy_SDK.Examples.TcpTls.Consumer.csproj
new file mode 100644
index 000000000..9be8a4307
--- /dev/null
+++
b/examples/csharp/src/TcpTls/Iggy_SDK.Examples.TcpTls.Consumer/Iggy_SDK.Examples.TcpTls.Consumer.csproj
@@ -0,0 +1,17 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net10.0</TargetFramework>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <Nullable>enable</Nullable>
+ <WarningsAsErrors>true</WarningsAsErrors>
+ </PropertyGroup>
+ <ItemGroup>
+ <ProjectReference
Include="..\..\..\..\..\foreign\csharp\Iggy_SDK\Iggy_SDK.csproj"/>
+ <ProjectReference
Include="..\..\Iggy_SDK.Examples.Shared\Iggy_SDK.Examples.Shared.csproj"/>
+ </ItemGroup>
+ <ItemGroup>
+ <PackageReference Include="Microsoft.Extensions.Logging"
Version="10.0.2" />
+ <PackageReference Include="Microsoft.Extensions.Logging.Console"
Version="10.0.2" />
+ </ItemGroup>
+</Project>
diff --git
a/examples/csharp/src/TcpTls/Iggy_SDK.Examples.TcpTls.Consumer/Program.cs
b/examples/csharp/src/TcpTls/Iggy_SDK.Examples.TcpTls.Consumer/Program.cs
new file mode 100644
index 000000000..fbc8aaaa7
--- /dev/null
+++ b/examples/csharp/src/TcpTls/Iggy_SDK.Examples.TcpTls.Consumer/Program.cs
@@ -0,0 +1,113 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// TCP/TLS Consumer Example
+//
+// Demonstrates consuming messages over a TLS-encrypted TCP connection.
+//
+// Prerequisites:
+// Start the Iggy server with TLS enabled:
+// IGGY_TCP_TLS_ENABLED=true
+// IGGY_TCP_TLS_CERT_FILE=core/certs/iggy_cert.pem
+// IGGY_TCP_TLS_KEY_FILE=core/certs/iggy_key.pem
+
+using System.Text;
+using Apache.Iggy;
+using Apache.Iggy.Configuration;
+using Apache.Iggy.Contracts;
+using Apache.Iggy.Enums;
+using Apache.Iggy.Factory;
+using Apache.Iggy.Kinds;
+using Microsoft.Extensions.Logging;
+
+const string streamName = "tls-stream";
+const string topicName = "tls-topic";
+const uint partitionId = 0;
+const uint batchesLimit = 5;
+
+var loggerFactory = LoggerFactory.Create(b => { b.AddConsole(); });
+var logger = loggerFactory.CreateLogger<Program>();
+
+// Configure the client with TLS.
+// TlsSettings.Enabled = true activates TLS on the TCP transport
+// TlsSettings.Hostname = the expected server hostname for certificate
verification
+// TlsSettings.CertificatePath = path to CA or server certificate PEM file
+var client = IggyClientFactory.CreateClient(new IggyClientConfigurator
+{
+ BaseAddress = "127.0.0.1:8090",
+ Protocol = Protocol.Tcp,
+ LoggerFactory = loggerFactory,
+ TlsSettings = new TlsSettings
+ {
+ Enabled = true,
+ Hostname = "localhost",
+ CertificatePath = "core/certs/iggy_ca_cert.pem"
+ }
+});
+
+await client.ConnectAsync();
+await client.LoginUser("iggy", "iggy");
+logger.LogInformation("Connected and logged in over TLS.");
+
+await ConsumeMessages();
+
+async Task ConsumeMessages()
+{
+ var interval = TimeSpan.FromMilliseconds(500);
+ logger.LogInformation(
+ "Messages will be consumed from stream: {StreamName}, topic:
{TopicName}, partition: {PartitionId} with interval {Interval}.",
+ streamName, topicName, partitionId, interval);
+
+ var offset = 0ul;
+ uint messagesPerBatch = 10;
+ var consumedBatches = 0;
+ var consumer = Consumer.New(1);
+ while (true)
+ {
+ if (consumedBatches == batchesLimit)
+ {
+ logger.LogInformation("Consumed {ConsumedBatches} batches of
messages, exiting.", consumedBatches);
+ return;
+ }
+
+ var polledMessages = await client.PollMessagesAsync(
+ Identifier.String(streamName),
+ Identifier.String(topicName),
+ partitionId,
+ consumer,
+ PollingStrategy.Offset(offset),
+ messagesPerBatch,
+ false);
+
+ if (!polledMessages.Messages.Any())
+ {
+ logger.LogInformation("No messages found.");
+ await Task.Delay(interval);
+ continue;
+ }
+
+ offset += (ulong)polledMessages.Messages.Count;
+ foreach (var message in polledMessages.Messages)
+ {
+ var payload = Encoding.UTF8.GetString(message.Payload);
+ logger.LogInformation("Handling message at offset: {Offset},
payload: {Payload}...",
+ message.Header.Offset, payload);
+ }
+ consumedBatches++;
+ await Task.Delay(interval);
+ }
+}
diff --git
a/examples/csharp/src/TcpTls/Iggy_SDK.Examples.TcpTls.Producer/Iggy_SDK.Examples.TcpTls.Producer.csproj
b/examples/csharp/src/TcpTls/Iggy_SDK.Examples.TcpTls.Producer/Iggy_SDK.Examples.TcpTls.Producer.csproj
new file mode 100644
index 000000000..acb57c814
--- /dev/null
+++
b/examples/csharp/src/TcpTls/Iggy_SDK.Examples.TcpTls.Producer/Iggy_SDK.Examples.TcpTls.Producer.csproj
@@ -0,0 +1,17 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net10.0</TargetFramework>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <Nullable>enable</Nullable>
+ <WarningsAsErrors>true</WarningsAsErrors>
+ </PropertyGroup>
+ <ItemGroup>
+ <ProjectReference
Include="..\..\..\..\..\foreign\csharp\Iggy_SDK\Iggy_SDK.csproj"/>
+ <ProjectReference
Include="..\..\Iggy_SDK.Examples.Shared\Iggy_SDK.Examples.Shared.csproj"/>
+ </ItemGroup>
+ <ItemGroup>
+ <PackageReference Include="Microsoft.Extensions.Logging.Abstractions"
Version="10.0.2" />
+ <PackageReference Include="Microsoft.Extensions.Logging.Console"
Version="10.0.2" />
+ </ItemGroup>
+</Project>
diff --git
a/examples/csharp/src/TcpTls/Iggy_SDK.Examples.TcpTls.Producer/Program.cs
b/examples/csharp/src/TcpTls/Iggy_SDK.Examples.TcpTls.Producer/Program.cs
new file mode 100644
index 000000000..49cf29a94
--- /dev/null
+++ b/examples/csharp/src/TcpTls/Iggy_SDK.Examples.TcpTls.Producer/Program.cs
@@ -0,0 +1,141 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// TCP/TLS Producer Example
+//
+// Demonstrates producing messages over a TLS-encrypted TCP connection.
+//
+// Prerequisites:
+// Start the Iggy server with TLS enabled:
+// IGGY_TCP_TLS_ENABLED=true
+// IGGY_TCP_TLS_CERT_FILE=core/certs/iggy_cert.pem
+// IGGY_TCP_TLS_KEY_FILE=core/certs/iggy_key.pem
+
+using System.Text;
+using Apache.Iggy;
+using Apache.Iggy.Configuration;
+using Apache.Iggy.Enums;
+using Apache.Iggy.Exceptions;
+using Apache.Iggy.Factory;
+using Apache.Iggy.Messages;
+using Microsoft.Extensions.Logging;
+using Partitioning = Apache.Iggy.Kinds.Partitioning;
+
+const string streamName = "tls-stream";
+const string topicName = "tls-topic";
+const uint partitionId = 0;
+const uint batchesLimit = 5;
+
+var loggerFactory = LoggerFactory.Create(b => { b.AddConsole(); });
+var logger = loggerFactory.CreateLogger<Program>();
+
+// Configure the client with TLS.
+// TlsSettings.Enabled = true activates TLS on the TCP transport
+// TlsSettings.Hostname = the expected server hostname for certificate
verification
+// TlsSettings.CertificatePath = path to CA or server certificate PEM file
+var client = IggyClientFactory.CreateClient(new IggyClientConfigurator
+{
+ BaseAddress = "127.0.0.1:8090",
+ Protocol = Protocol.Tcp,
+ LoggerFactory = loggerFactory,
+ TlsSettings = new TlsSettings
+ {
+ Enabled = true,
+ Hostname = "localhost",
+ CertificatePath = "core/certs/iggy_ca_cert.pem"
+ }
+});
+
+await client.ConnectAsync();
+await client.LoginUser("iggy", "iggy");
+logger.LogInformation("Connected and logged in over TLS.");
+
+await InitSystem();
+await ProduceMessages();
+
+async Task InitSystem()
+{
+ try
+ {
+ await client.CreateStreamAsync(streamName);
+ logger.LogInformation("Stream was created.");
+ }
+ catch (Exception ex) when (ex is InvalidResponseException or
IggyInvalidStatusCodeException)
+ {
+ logger.LogWarning("Stream already exists and will not be created
again.");
+ }
+
+ try
+ {
+ await client.CreateTopicAsync(
+ Identifier.String(streamName),
+ topicName,
+ 1,
+ CompressionAlgorithm.None
+ );
+ logger.LogInformation("Topic was created.");
+ }
+ catch (Exception ex) when (ex is InvalidResponseException or
IggyInvalidStatusCodeException)
+ {
+ logger.LogWarning("Topic already exists and will not be created
again.");
+ }
+}
+
+async Task ProduceMessages()
+{
+ var interval = TimeSpan.FromMilliseconds(500);
+ logger.LogInformation(
+ "Messages will be sent to stream: {StreamName}, topic: {TopicName},
partition: {PartitionId} with interval {Interval}.",
+ streamName, topicName, partitionId, interval);
+
+ var currentId = 0;
+ var messagesPerBatch = 10;
+ var sentBatches = 0;
+ var partitioning = Partitioning.PartitionId((int)partitionId);
+ while (true)
+ {
+ if (sentBatches == batchesLimit)
+ {
+ logger.LogInformation("Sent {SentBatches} batches of messages,
exiting.", sentBatches);
+ return;
+ }
+
+ var payloads = Enumerable
+ .Range(currentId, messagesPerBatch)
+ .Aggregate(new List<string>(), (list, next) =>
+ {
+ list.Add($"message-{next}");
+ return list;
+ });
+
+ var messages = payloads
+ .Select(payload => new Message(Guid.NewGuid(),
Encoding.UTF8.GetBytes(payload)))
+ .ToList();
+
+ await client.SendMessagesAsync(
+ Identifier.String(streamName),
+ Identifier.String(topicName),
+ partitioning,
+ messages);
+
+ currentId += messagesPerBatch;
+ sentBatches++;
+ logger.LogInformation("Sent messages: {Messages}.", payloads);
+
+ await Task.Delay(interval);
+ }
+}
diff --git a/scripts/run-csharp-examples-from-readme.sh
b/scripts/run-csharp-examples-from-readme.sh
index ff05b5321..c05e48b5e 100755
--- a/scripts/run-csharp-examples-from-readme.sh
+++ b/scripts/run-csharp-examples-from-readme.sh
@@ -189,13 +189,96 @@ for readme_file in README.md examples/csharp/README.md; do
# Add a small delay between examples to avoid potential race conditions
sleep 2
- done < <(grep -E "^dotnet run --project" "${readme_file}")
+ done < <(grep -E "^dotnet run --project" "${readme_file}" | grep -v
"TcpTls")
done
-# Terminate server
+# Terminate non-TLS server
kill -TERM "$(cat ${PID_FILE})"
test -e ${PID_FILE} && rm ${PID_FILE}
+# Run TLS examples if non-TLS examples passed
+if [ "${exit_code}" -eq 0 ]; then
+ TLS_README="examples/csharp/README.md"
+ if [ -f "${TLS_README}" ] && grep -qE "^dotnet run --project.*TcpTls"
"${TLS_README}"; then
+ echo ""
+ echo "=== Running TLS examples ==="
+ echo ""
+
+ # Clean up for fresh TLS start
+ test -d local_data && rm -fr local_data
+ test -e ${LOG_FILE} && rm ${LOG_FILE}
+
+ # Start TLS server
+ echo "Starting TLS server from ${SERVER_BIN}..."
+ IGGY_ROOT_USERNAME=iggy IGGY_ROOT_PASSWORD=iggy \
+ IGGY_TCP_TLS_ENABLED=true \
+ IGGY_TCP_TLS_CERT_FILE=core/certs/iggy_cert.pem \
+ IGGY_TCP_TLS_KEY_FILE=core/certs/iggy_key.pem \
+ ${SERVER_BIN} &>${LOG_FILE} &
+ echo $! >${PID_FILE}
+
+ # Wait for TLS server to start
+ SERVER_START_TIME=0
+ while ! grep -q "has started" ${LOG_FILE}; do
+ if [ ${SERVER_START_TIME} -gt ${TIMEOUT} ]; then
+ echo "TLS server did not start within ${TIMEOUT} seconds."
+ ps fx
+ cat ${LOG_FILE}
+ exit_code=1
+ break
+ fi
+ echo "Waiting for TLS Iggy server to start... ${SERVER_START_TIME}"
+ sleep 1
+ ((SERVER_START_TIME += 1))
+ done
+
+ if [ "${exit_code}" -eq 0 ]; then
+ while IFS= read -r command; do
+ # Remove backticks and comments from command
+ command=$(echo "${command}" | tr -d '`' | sed 's/^#.*//')
+ # Skip empty lines
+ if [ -z "${command}" ]; then
+ continue
+ fi
+
+ # Add target flag if specified
+ if [ -n "${CSOS}" ]; then
+ command="${command//dotnet run /dotnet run --os ${CSOS} }"
+ fi
+
+ if [ -n "${CSARCH}" ]; then
+ command="${command//dotnet run /dotnet run --arch
${CSARCH} }"
+ fi
+
+ echo -e "\e[33mChecking TLS example command:\e[0m ${command}"
+ echo ""
+
+ set +e
+ eval "${command}"
+ exit_code=$?
+ set -e
+
+ # Stop at first failure
+ if [ ${exit_code} -ne 0 ]; then
+ echo ""
+ echo -e "\e[31mTLS example command failed:\e[0m ${command}"
+ echo ""
+ break
+ fi
+ # Add a small delay between examples to avoid potential race
conditions
+ sleep 2
+
+ done < <(grep -E "^dotnet run --project.*TcpTls" "${TLS_README}")
+ fi
+
+ # Terminate TLS server
+ if [ -e ${PID_FILE} ]; then
+ kill -TERM "$(cat ${PID_FILE})" 2>/dev/null || true
+ rm -f ${PID_FILE}
+ fi
+ fi
+fi
+
# If everything is ok remove log and pid files otherwise cat server log
if [ "${exit_code}" -eq 0 ]; then
echo "Test passed"