Skip to content

Commit dac7b9f

Browse files
authored
Merge pull request #1 from darkplusplus/refactor
refactor takclient transport and protobuf negotiation
2 parents 974478f + bd17be4 commit dac7b9f

14 files changed

Lines changed: 681 additions & 38 deletions

.github/workflows/ci.yml

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,25 @@ on:
88

99
jobs:
1010
build:
11-
1211
runs-on: ubuntu-latest
1312

1413
steps:
1514
- name: Checkout
16-
uses: actions/checkout@v2
17-
15+
uses: actions/checkout@v4
16+
1817
- name: Setup .NET
19-
uses: actions/setup-dotnet@v1
18+
uses: actions/setup-dotnet@v4
2019
with:
21-
dotnet-version: 5.0.x
22-
20+
dotnet-version: |
21+
6.0.x
22+
8.0.x
23+
10.0.x
24+
2325
- name: Restore dependencies
2426
run: dotnet restore
25-
27+
2628
- name: Build
27-
run: dotnet build --no-restore
28-
29+
run: dotnet build --no-restore -m:1
30+
2931
- name: Test
30-
run: dotnet test --no-build --verbosity normal
31-
32-
- name: Publish
33-
run: dotnet publish
32+
run: dotnet test --no-build --verbosity normal -m:1

.github/workflows/release.yml

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,35 +7,47 @@ on:
77

88
jobs:
99
build:
10-
1110
runs-on: ubuntu-latest
1211

1312
steps:
1413
- name: Checkout
15-
uses: actions/checkout@v2
16-
14+
uses: actions/checkout@v4
15+
1716
- name: Setup .NET
18-
uses: actions/setup-dotnet@v1
17+
uses: actions/setup-dotnet@v4
1918
with:
20-
dotnet-version: 5.0.x
21-
19+
dotnet-version: |
20+
6.0.x
21+
8.0.x
22+
10.0.x
23+
2224
- name: Restore dependencies
2325
run: dotnet restore
24-
26+
27+
- name: Verify tag matches package version
28+
shell: bash
29+
run: |
30+
PACKAGE_VERSION="$(grep -oPm1 '(?<=<Version>)[^<]+' dpp.takclient/dpp.takclient.csproj)"
31+
TAG_VERSION="${GITHUB_REF_NAME#v}"
32+
if [[ "$PACKAGE_VERSION" != "$TAG_VERSION" ]]; then
33+
echo "Tag version '$TAG_VERSION' does not match package version '$PACKAGE_VERSION'."
34+
exit 1
35+
fi
36+
2537
- name: Build
26-
run: dotnet build -c Release --no-restore --version-suffix="$GITHUB_REF_NAME.$GITHUB_RUN_NUMBER"
27-
28-
- name: Publish
29-
run: dotnet publish -c Release --no-build --version-suffix="$GITHUB_REF_NAME.$GITHUB_RUN_NUMBER"
38+
run: dotnet build takclient.sln -c Release --no-restore -m:1
39+
40+
- name: Test
41+
run: dotnet test takclient.sln -c Release --no-build --verbosity normal -m:1
3042

3143
- name: Pack
32-
run: dotnet pack -c Release --no-build --version-suffix="$GITHUB_REF_NAME.$GITHUB_RUN_NUMBER"
33-
44+
run: dotnet pack dpp.takclient/dpp.takclient.csproj -c Release --no-build
45+
3446
- name: Upload Artifacts
35-
uses: actions/upload-artifact@v2
47+
uses: actions/upload-artifact@v4
3648
with:
3749
name: dpp.takclient
38-
path: dpp.takclient/bin/*/*.nupkg
50+
path: dpp.takclient/bin/Release/*.nupkg
3951

4052
- name: Publish To Nuget
4153
run: dotnet nuget push dpp.takclient/bin/Release/*.nupkg -k $NUGET_AUTH_TOKEN -s https://api.nuget.org/v3/index.json
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
using System;
2+
using System.Text;
3+
using dpp.cot;
4+
using Xunit;
5+
6+
namespace dpp.takclient.Tests
7+
{
8+
public class TakClientTests
9+
{
10+
private const string SupportAdvertisementXml =
11+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
12+
"<event version='2.0' uid='protouid' type='t-x-takp-v' time='2022-02-02T22:22:22Z' start='2022-02-02T22:22:22Z' stale='2022-02-02T22:32:22Z' how='m-g'>" +
13+
"<point lat='0.0' lon='0.0' hae='0.0' ce='999999' le='999999'/>" +
14+
"<detail><TakControl><TakProtocolSupport version='1'/></TakControl></detail>" +
15+
"</event>";
16+
17+
private const string NegotiationAcceptedXml =
18+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
19+
"<event version='2.0' uid='protouid' type='t-x-takp-r' time='2022-02-02T22:22:22Z' start='2022-02-02T22:22:22Z' stale='2022-02-02T22:32:22Z' how='m-g'>" +
20+
"<point lat='0.0' lon='0.0' hae='0.0' ce='999999' le='999999'/>" +
21+
"<detail><TakControl><TakResponse status='true'/></TakControl></detail>" +
22+
"</event>";
23+
24+
[Fact]
25+
public void StreamingXmlUsesXmlDeclarationAndRawEvent()
26+
{
27+
var message = new Message
28+
{
29+
Event = new Event
30+
{
31+
Uid = "TEST-XML",
32+
Type = "a-f-G-U-C",
33+
How = "m-g",
34+
Time = new DateTime(2022, 2, 2, 22, 22, 22, DateTimeKind.Utc),
35+
Start = new DateTime(2022, 2, 2, 22, 22, 22, DateTimeKind.Utc),
36+
Stale = new DateTime(2022, 2, 2, 22, 32, 22, DateTimeKind.Utc),
37+
}
38+
};
39+
40+
var bytes = TakClient.SerializeMessage(message, TakTransportMode.StreamingXml);
41+
var payload = Encoding.UTF8.GetString(bytes);
42+
43+
Assert.StartsWith("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<event", payload);
44+
Assert.Contains("uid=\"TEST-XML\"", payload);
45+
Assert.DoesNotContain("\u00ef", payload);
46+
Assert.DoesNotContain("magic", payload);
47+
}
48+
49+
[Fact]
50+
public void StreamingProtobufUsesTakStreamingEnvelope()
51+
{
52+
var message = new Message
53+
{
54+
Event = new Event
55+
{
56+
Uid = "TEST-PROTO",
57+
Type = "a-f-G-U-C",
58+
How = "m-g",
59+
Time = new DateTime(2022, 2, 2, 22, 22, 22, DateTimeKind.Utc),
60+
Start = new DateTime(2022, 2, 2, 22, 22, 22, DateTimeKind.Utc),
61+
Stale = new DateTime(2022, 2, 2, 22, 32, 22, DateTimeKind.Utc),
62+
}
63+
};
64+
65+
var bytes = TakClient.SerializeMessage(message, TakTransportMode.StreamingProtobuf);
66+
67+
Assert.NotEmpty(bytes);
68+
Assert.Equal(0xbf, bytes[0]);
69+
70+
Assert.True(Message.TryParseStreaming(bytes, 0, bytes.Length, 0x01, out var parsed, out var consumed));
71+
Assert.Equal(bytes.Length, consumed);
72+
Assert.NotNull(parsed.Event);
73+
Assert.Equal("TEST-PROTO", parsed.Event.Uid);
74+
}
75+
76+
[Fact]
77+
public void NullMessageThrows()
78+
{
79+
Assert.Throws<ArgumentNullException>(() => TakClient.SerializeMessage(null, TakTransportMode.StreamingXml));
80+
}
81+
82+
[Fact]
83+
public void ProtocolSessionRequestsProtobufWhenServerAdvertisesSupport()
84+
{
85+
var session = new TakProtocolSession(TakTransportMode.StreamingProtobuf);
86+
var advertisement = Message.Parse(Encoding.UTF8.GetBytes(SupportAdvertisementXml), 39, SupportAdvertisementXml.Length - 39);
87+
88+
var request = session.ProcessIncoming(advertisement);
89+
90+
Assert.NotNull(request);
91+
Assert.Equal(TakTransportMode.StreamingXml, session.ActiveTransportMode);
92+
Assert.Equal(TakNegotiationState.AwaitingResponse, session.NegotiationState);
93+
Assert.Equal("t-x-takp-q", request.Event.Type);
94+
Assert.Contains("version=\"1\"", request.ToXmlString());
95+
}
96+
97+
[Fact]
98+
public void ProtocolSessionSwitchesToProtobufAfterPositiveResponse()
99+
{
100+
var session = new TakProtocolSession(TakTransportMode.StreamingProtobuf);
101+
var advertisement = Message.Parse(Encoding.UTF8.GetBytes(SupportAdvertisementXml), 39, SupportAdvertisementXml.Length - 39);
102+
var accepted = Message.Parse(Encoding.UTF8.GetBytes(NegotiationAcceptedXml), 39, NegotiationAcceptedXml.Length - 39);
103+
104+
session.ProcessIncoming(advertisement);
105+
var followUp = session.ProcessIncoming(accepted);
106+
107+
Assert.Null(followUp);
108+
Assert.Equal(TakTransportMode.StreamingProtobuf, session.ActiveTransportMode);
109+
Assert.Equal(TakNegotiationState.Complete, session.NegotiationState);
110+
}
111+
112+
[Fact]
113+
public void ProtocolSessionBlocksApplicationMessagesWhileAwaitingResponse()
114+
{
115+
var session = new TakProtocolSession(TakTransportMode.StreamingProtobuf);
116+
var advertisement = Message.Parse(Encoding.UTF8.GetBytes(SupportAdvertisementXml), 39, SupportAdvertisementXml.Length - 39);
117+
118+
session.ProcessIncoming(advertisement);
119+
120+
Assert.Throws<InvalidOperationException>(() => session.Serialize(new Message
121+
{
122+
Event = new Event
123+
{
124+
Uid = "TEST-BLOCK",
125+
Type = "a-f-G-U-C",
126+
How = "m-g",
127+
},
128+
}));
129+
}
130+
131+
[Fact]
132+
public void XmlStreamParserConsumesDeclarationAndReturnsMessage()
133+
{
134+
var xmlBytes = Encoding.UTF8.GetBytes(SupportAdvertisementXml);
135+
136+
Assert.True(TakMessageStreamParser.TryParseXml(xmlBytes, 0, xmlBytes.Length, out var message, out var consumed));
137+
Assert.Equal(xmlBytes.Length, consumed);
138+
Assert.NotNull(message.Event);
139+
Assert.Equal("t-x-takp-v", message.Event.Type);
140+
}
141+
}
142+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net10.0</TargetFramework>
5+
<IsPackable>false</IsPackable>
6+
<ImplicitUsings>disable</ImplicitUsings>
7+
<LangVersion>latest</LangVersion>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
12+
<PackageReference Include="xunit" Version="2.4.1" />
13+
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
14+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
15+
<PrivateAssets>all</PrivateAssets>
16+
</PackageReference>
17+
<PackageReference Include="coverlet.collector" Version="1.3.0">
18+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
19+
<PrivateAssets>all</PrivateAssets>
20+
</PackageReference>
21+
</ItemGroup>
22+
23+
<ItemGroup>
24+
<ProjectReference Include="..\dpp.takclient\dpp.takclient.csproj" />
25+
</ItemGroup>
26+
27+
</Project>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
using System.Runtime.CompilerServices;
2+
3+
[assembly: InternalsVisibleTo("dpp.takclient.Tests")]

dpp.takclient/README-NUGET.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# dpp.takclient
2+
3+
`dpp.takclient` is a .NET TAK client library built on top of [`dpp.cot`](https://www.nuget.org/packages/dpp.cot).
4+
5+
It provides:
6+
7+
- TAK XML streaming connections
8+
- TAK protobuf streaming connections
9+
- automatic protobuf negotiation over the initial XML session
10+
- parsed inbound CoT message delivery
11+
12+
Targets:
13+
14+
- `net6.0`
15+
- `net8.0`
16+
- `net10.0`
17+
18+
Source:
19+
20+
- <https://github.com/darkplusplus/takclient>

0 commit comments

Comments
 (0)