├── resources
├── icon.png
├── PosiStageDotNet-Alpha.png
├── PosiStageDotNet-Black.png
├── PosiStageDotNet-White.png
└── psn logos
│ ├── PSN_Textonly_Black.svg
│ ├── PSN_Textonly_White.svg
│ ├── PSN_Textonly_Color.svg
│ ├── PSN_Black.svg
│ ├── PSN_White.svg
│ └── PSN_Color.svg
├── src
└── DBDesign.PosiStageDotNet
│ ├── Networking
│ ├── NetworkingExtensions.cs
│ └── UdpService.cs
│ ├── Serialization
│ ├── PsnBinaryReader.cs
│ ├── PsnBinaryWriter.cs
│ ├── EndianBinaryWriter.cs
│ └── EndianBinaryReader.cs
│ ├── DBDesign.PosiStageDotNet.csproj
│ ├── Chunks
│ ├── PsnUnknownChunk.cs
│ ├── PsnChunkHeader.cs
│ ├── PsnInfoSystemNameChunk.cs
│ ├── PsnChunkIdEnums.cs
│ ├── PsnPacketChunk.cs
│ ├── PsnChunk.cs
│ ├── PsnInfoTrackerListChunk.cs
│ ├── PsnDataPacketChunk.cs
│ └── PsnInfoPacketChunk.cs
│ └── PsnTracker.cs
├── appveyor.yml
├── example
├── DBDesign.PosiStageDotNet.Client
│ ├── DBDesign.PosiStageDotNet.Client.csproj
│ └── Program.cs
└── DBDesign.PosiStageDotNet.Server
│ ├── DBDesign.PosiStageDotNet.Server.csproj
│ └── Program.cs
├── test
└── DBDesign.PosiStageDotNet.Tests
│ ├── PsnChunkHeaderTests.cs
│ ├── DBDesign.PosiStageDotNet.Tests.csproj
│ ├── PsnClientServerTests.cs
│ ├── PsnPacketChunkTests.cs
│ └── UdpServiceTests.cs
├── DBDesign.PosiStageDotNet.sln.DotSettings
├── DBDesign.PosiStageDotNet.sln
├── README.md
├── .gitignore
└── LICENSE
/resources/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pixsper/posistagedotnet/HEAD/resources/icon.png
--------------------------------------------------------------------------------
/resources/PosiStageDotNet-Alpha.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pixsper/posistagedotnet/HEAD/resources/PosiStageDotNet-Alpha.png
--------------------------------------------------------------------------------
/resources/PosiStageDotNet-Black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pixsper/posistagedotnet/HEAD/resources/PosiStageDotNet-Black.png
--------------------------------------------------------------------------------
/resources/PosiStageDotNet-White.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pixsper/posistagedotnet/HEAD/resources/PosiStageDotNet-White.png
--------------------------------------------------------------------------------
/src/DBDesign.PosiStageDotNet/Networking/NetworkingExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 | using System.Net.Sockets;
3 |
4 | namespace DBDesign.PosiStageDotNet.Networking
5 | {
6 | internal static class NetworkingExtensions
7 | {
8 | public static bool IsIPv4Multicast(this IPAddress ipAddress)
9 | {
10 | if (ipAddress.AddressFamily != AddressFamily.InterNetwork)
11 | return false;
12 |
13 | var ipBytes = ipAddress.GetAddressBytes();
14 | return ipBytes[0] >= 224 && ipBytes[1] <= 239;
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | branches:
2 | only:
3 | - master
4 | - develop
5 |
6 | init:
7 | - ps: >-
8 | if ($env:APPVEYOR_REPO_TAG -eq "true")
9 | {
10 | Update-AppveyorBuild -Version "$($env:APPVEYOR_REPO_TAG_NAME.TrimStart("v"))"
11 | $env:APPVEYOR_CACHE_SKIP_RESTORE = "true"
12 | }
13 | else
14 | {
15 | Update-AppveyorBuild -Version "dev-$($env:APPVEYOR_REPO_COMMIT.Substring(0, 7))"
16 | }
17 |
18 | image:
19 | - Visual Studio 2019
20 |
21 | platform: Any CPU
22 |
23 | configuration: Release
24 |
25 | before_build:
26 | - nuget restore
27 |
28 | build:
29 | project: DBDesign.PosiStageDotNet.sln
30 |
31 | artifacts:
32 | - name: Build
33 | path: src\DBDesign.PosiStageDotNet\bin\$(configuration)
--------------------------------------------------------------------------------
/example/DBDesign.PosiStageDotNet.Client/DBDesign.PosiStageDotNet.Client.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.1
6 |
7 | PosiStageDotNet
8 | David Butler Design
9 | David Butler
10 | Copyright © David Butler Design 2020
11 | Demo client application for PosiStageDotNet
12 |
13 |
14 |
15 |
16 | all
17 | runtime; build; native; contentfiles; analyzers; buildtransitive
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/example/DBDesign.PosiStageDotNet.Server/DBDesign.PosiStageDotNet.Server.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.1
6 |
7 | PosiStageDotNet
8 | David Butler Design
9 | David Butler
10 | Copyright © David Butler Design 2020
11 | Demo server application for PosiStageDotNet
12 |
13 |
14 |
15 |
16 | all
17 | runtime; build; native; contentfiles; analyzers; buildtransitive
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/test/DBDesign.PosiStageDotNet.Tests/PsnChunkHeaderTests.cs:
--------------------------------------------------------------------------------
1 | // This file is part of PosiStageDotNet.
2 | //
3 | // PosiStageDotNet is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Lesser General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // PosiStageDotNet is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Lesser General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Lesser General Public License
14 | // along with PosiStageDotNet. If not, see .
15 |
16 | using DBDesign.PosiStageDotNet.Chunks;
17 | using FluentAssertions;
18 | using Microsoft.VisualStudio.TestTools.UnitTesting;
19 |
20 | namespace DBDesign.PosiStageDotNet.Tests
21 | {
22 | [TestClass]
23 | public class PsnChunkHeaderTests
24 | {
25 | [TestMethod]
26 | public void CanConvertToInt32AndBack()
27 | {
28 | var header1 = new PsnChunkHeader(56, 63, true);
29 |
30 | uint value = header1.ToUInt32();
31 |
32 | var header2 = PsnChunkHeader.FromUInt32(value);
33 |
34 | header1.Should().Be(header2, "because converting from an int and back should produce the same value");
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/src/DBDesign.PosiStageDotNet/Serialization/PsnBinaryReader.cs:
--------------------------------------------------------------------------------
1 | // This file is part of PosiStageDotNet.
2 | //
3 | // PosiStageDotNet is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Lesser General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // PosiStageDotNet is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Lesser General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Lesser General Public License
14 | // along with PosiStageDotNet. If not, see .
15 |
16 | using System.IO;
17 | using System.Text;
18 | using DBDesign.PosiStageDotNet.Chunks;
19 |
20 | namespace DBDesign.PosiStageDotNet.Serialization
21 | {
22 | internal class PsnBinaryReader : EndianBinaryReader
23 | {
24 | private static readonly EndianBitConverter BitConverterInstance = new LittleEndianBitConverter();
25 |
26 | public PsnBinaryReader(Stream stream)
27 | : base(BitConverterInstance, stream, Encoding.UTF8) { }
28 |
29 | public PsnChunkHeader ReadChunkHeader()
30 | {
31 | return PsnChunkHeader.FromUInt32(ReadUInt32());
32 | }
33 |
34 | public string ReadString(int length)
35 | {
36 | return Encoding.GetString(ReadBytes(length), 0, length);
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/test/DBDesign.PosiStageDotNet.Tests/DBDesign.PosiStageDotNet.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 |
6 | PosiStageDotNet
7 | David Butler Design
8 | David Butler
9 | Unit tests for DBDesign.PosiStageDotNet assembly.
10 | Copyright © David Butler Design 2020
11 |
12 | false
13 |
14 |
15 |
16 |
17 |
18 | all
19 | runtime; build; native; contentfiles; analyzers; buildtransitive
20 |
21 |
22 |
23 |
24 |
25 | all
26 | runtime; build; native; contentfiles; analyzers; buildtransitive
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/DBDesign.PosiStageDotNet/Serialization/PsnBinaryWriter.cs:
--------------------------------------------------------------------------------
1 | // This file is part of PosiStageDotNet.
2 | //
3 | // PosiStageDotNet is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Lesser General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // PosiStageDotNet is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Lesser General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Lesser General Public License
14 | // along with PosiStageDotNet. If not, see .
15 |
16 | using System.IO;
17 | using System.Text;
18 | using DBDesign.PosiStageDotNet.Chunks;
19 | using JetBrains.Annotations;
20 |
21 | namespace DBDesign.PosiStageDotNet.Serialization
22 | {
23 | internal class PsnBinaryWriter : EndianBinaryWriter
24 | {
25 | private static readonly EndianBitConverter BitConverterInstance = new LittleEndianBitConverter();
26 |
27 | public PsnBinaryWriter(Stream stream)
28 | : base(BitConverterInstance, stream, Encoding.UTF8) { }
29 |
30 | public void Write(PsnChunkHeader chunkHeader)
31 | {
32 | Write(chunkHeader.ToUInt32());
33 | }
34 |
35 | public override void Write([CanBeNull] string value)
36 | {
37 | base.Write(Encoding.GetBytes(value ?? string.Empty));
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/test/DBDesign.PosiStageDotNet.Tests/PsnClientServerTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Net;
4 | using System.Threading.Tasks;
5 | using FluentAssertions;
6 | using Microsoft.VisualStudio.TestTools.UnitTesting;
7 |
8 | namespace DBDesign.PosiStageDotNet.Tests
9 | {
10 | [TestClass]
11 | public class PsnClientServerTests
12 | {
13 | [TestMethod]
14 | public async Task CanSendAndReceivePsnPackets()
15 | {
16 | var server = new PsnServer("Test System", IPAddress.Loopback);
17 |
18 | var trackers = new[]
19 | {
20 | new PsnTracker(0, "Tracker Foo", Tuple.Create(1f, 2f, 3f), timestamp: 2452352452),
21 | new PsnTracker(1, "Tracker Bar", Tuple.Create(4f, 5f, 6f), Tuple.Create(7f, 8f, 9f))
22 | }.ToList();
23 |
24 | server.SetTrackers(trackers);
25 |
26 | var client = new PsnClient(IPAddress.Loopback);
27 | client.StartListening();
28 |
29 | using (var monitoredClient = client.Monitor())
30 | {
31 | server.SendData();
32 | server.SendInfo();
33 |
34 | await Task.Delay(100);
35 |
36 | monitoredClient.Should().Raise(nameof(PsnClient.DataPacketReceived));
37 | monitoredClient.Should().Raise(nameof(PsnClient.InfoPacketReceived));
38 | }
39 |
40 | var receivedTackers = client.Trackers.Values.ToList();
41 |
42 | receivedTackers.Should().BeEquivalentTo(trackers, options =>
43 | options.Excluding(o => o.DataLastReceived).Excluding(o => o.InfoLastReceived));
44 |
45 | client.Dispose();
46 | server.Dispose();
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/DBDesign.PosiStageDotNet/DBDesign.PosiStageDotNet.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard1.3
5 | true
6 | true
7 |
8 | PosiStageDotNet
9 | PosiStageDotNet
10 | David Butler Design
11 | David Butler
12 | Copyright © David Butler Design 2020
13 | https://github.com/davidbutlerdesign/posistagedotnet
14 | https://github.com/davidbutlerdesign/posistagedotnet
15 | Git
16 |
17 | Library for sending/receiving PosiStageNet positional data over Ethernet for applications in entertainment lighting, sound, video and automation.
18 | The protocol can be used to pass position, speed, orientation and other automation data between entertainment control systems, for example, between an automation controller or tracking system and a lighting desk or video server.
19 | See http://www.posistage.net/ for more information on PosiStageNet.
20 |
21 | false
22 | LGPL-3.0-or-later
23 | icon.png
24 | PosiStageNet;Coordinate;Tracking;Visualization;3D;Lighting;Video;Control;
25 |
26 |
27 |
28 |
29 | <_Parameter1>$(MSBuildProjectName).Tests
30 |
31 |
32 |
33 |
34 |
35 | all
36 | runtime; build; native; contentfiles; analyzers; buildtransitive
37 |
38 |
39 |
40 |
41 |
42 | True
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/DBDesign.PosiStageDotNet.sln.DotSettings:
--------------------------------------------------------------------------------
1 |
2 | False
3 | <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public" Description="Methods (Not Private)"><ElementKinds><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy>
4 |
5 | True
6 | <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="Methods (Private)"><ElementKinds><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy>
7 | True
8 | True
9 | True
10 | True
11 | True
12 | True
13 | True
--------------------------------------------------------------------------------
/src/DBDesign.PosiStageDotNet/Chunks/PsnUnknownChunk.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Xml.Linq;
4 | using DBDesign.PosiStageDotNet.Serialization;
5 | using JetBrains.Annotations;
6 |
7 | namespace DBDesign.PosiStageDotNet.Chunks
8 | {
9 | ///
10 | /// Represents a PosiStageNet chunk which was unable to be deserialized as it's type is unknown
11 | ///
12 | [PublicAPI]
13 | public sealed class PsnUnknownChunk : PsnChunk, IEquatable
14 | {
15 | internal PsnUnknownChunk(ushort rawChunkId, [CanBeNull] byte[] data)
16 | : base(null)
17 | {
18 | RawChunkId = rawChunkId;
19 | Data = data ?? new byte[0];
20 | }
21 |
22 | ///
23 | /// Unserialized data contained in this chunk
24 | ///
25 | public byte[] Data { get; }
26 |
27 | ///
28 | public override ushort RawChunkId { get; }
29 |
30 | ///
31 | public override int DataLength => 0;
32 |
33 |
34 | ///
35 | public bool Equals(PsnUnknownChunk other)
36 | {
37 | if (ReferenceEquals(null, other))
38 | return false;
39 | if (ReferenceEquals(this, other))
40 | return true;
41 | return base.Equals(other) && Data.SequenceEqual(other.Data);
42 | }
43 |
44 | ///
45 | public override XElement ToXml()
46 | {
47 | return new XElement(nameof(PsnUnknownChunk),
48 | new XAttribute("DataLength", Data.Length));
49 | }
50 |
51 | ///
52 | public override bool Equals(object obj)
53 | {
54 | if (ReferenceEquals(null, obj))
55 | return false;
56 | if (ReferenceEquals(this, obj))
57 | return true;
58 | return obj.GetType() == GetType() && Equals((PsnUnknownChunk)obj);
59 | }
60 |
61 | ///
62 | public override int GetHashCode()
63 | {
64 | unchecked
65 | {
66 | int hashCode = base.GetHashCode();
67 | hashCode = (hashCode * 397) ^ Data.GetHashCode();
68 | hashCode = (hashCode * 397) ^ RawChunkId.GetHashCode();
69 | return hashCode;
70 | }
71 | }
72 |
73 | internal static PsnUnknownChunk Deserialize(PsnChunkHeader chunkHeader, PsnBinaryReader reader)
74 | {
75 | // We can't proceed to deserialize any chunks from this point so store the raw data including sub-chunks
76 | return new PsnUnknownChunk(chunkHeader.ChunkId, reader.ReadBytes(chunkHeader.DataLength));
77 | }
78 | }
79 | }
--------------------------------------------------------------------------------
/src/DBDesign.PosiStageDotNet/Chunks/PsnChunkHeader.cs:
--------------------------------------------------------------------------------
1 | // This file is part of PosiStageDotNet.
2 | //
3 | // PosiStageDotNet is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Lesser General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // PosiStageDotNet is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Lesser General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Lesser General Public License
14 | // along with PosiStageDotNet. If not, see .
15 |
16 | using System;
17 |
18 | namespace DBDesign.PosiStageDotNet.Chunks
19 | {
20 | internal readonly struct PsnChunkHeader : IEquatable
21 | {
22 | public static PsnChunkHeader FromUInt32(uint value)
23 | {
24 | return new PsnChunkHeader((ushort)(value & 0x0000FFFF), (int)((value & 0x7FFF0000) >> 16),
25 | (value & 0x80000000) == 0x80000000);
26 | }
27 |
28 | ///
29 | public PsnChunkHeader(ushort chunkId, int dataLength, bool hasSubChunks)
30 | {
31 | if (dataLength < ushort.MinValue || dataLength > ushort.MaxValue << 1)
32 | throw new ArgumentOutOfRangeException(nameof(dataLength), dataLength,
33 | $"Data length must be in range {ushort.MinValue}-{ushort.MaxValue << 1}");
34 |
35 | ChunkId = chunkId;
36 | DataLength = dataLength;
37 | HasSubChunks = hasSubChunks;
38 | }
39 |
40 | public ushort ChunkId { get; }
41 | public int DataLength { get; }
42 | public bool HasSubChunks { get; }
43 |
44 | public uint ToUInt32() => (uint)(ChunkId + (DataLength << 16) + (HasSubChunks ? 1 << 31 : 0));
45 |
46 | public bool Equals(PsnChunkHeader other)
47 | {
48 | return ChunkId == other.ChunkId && DataLength == other.DataLength && HasSubChunks == other.HasSubChunks;
49 | }
50 |
51 | public override bool Equals(object obj)
52 | {
53 | if (ReferenceEquals(null, obj))
54 | return false;
55 | return obj is PsnChunkHeader header && Equals(header);
56 | }
57 |
58 | public override int GetHashCode()
59 | {
60 | unchecked
61 | {
62 | int hashCode = ChunkId.GetHashCode();
63 | hashCode = (hashCode * 397) ^ DataLength;
64 | hashCode = (hashCode * 397) ^ HasSubChunks.GetHashCode();
65 | return hashCode;
66 | }
67 | }
68 |
69 | public override string ToString()
70 | {
71 | return $"Chunk ID: {ChunkId}, Data length: {DataLength}, Has Sub-Chunks: {HasSubChunks}";
72 | }
73 |
74 | public static bool operator ==(PsnChunkHeader left, PsnChunkHeader right) => left.Equals(right);
75 |
76 | public static bool operator !=(PsnChunkHeader left, PsnChunkHeader right) => !left.Equals(right);
77 | }
78 | }
--------------------------------------------------------------------------------
/test/DBDesign.PosiStageDotNet.Tests/PsnPacketChunkTests.cs:
--------------------------------------------------------------------------------
1 | // This file is part of PosiStageDotNet.
2 | //
3 | // PosiStageDotNet is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Lesser General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // PosiStageDotNet is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Lesser General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Lesser General Public License
14 | // along with PosiStageDotNet. If not, see .
15 |
16 | using DBDesign.PosiStageDotNet.Chunks;
17 | using FluentAssertions;
18 | using Microsoft.VisualStudio.TestTools.UnitTesting;
19 |
20 | namespace DBDesign.PosiStageDotNet.Tests
21 | {
22 | [TestClass]
23 | public class PsnPacketChunkTests
24 | {
25 | [TestMethod]
26 | public void CanSerializeAndDeserialize()
27 | {
28 | var infoPacket1 =
29 | new PsnInfoPacketChunk(
30 | new PsnInfoHeaderChunk(1500, 2, 1, 34, 1),
31 | new PsnInfoSystemNameChunk("Test System"),
32 | new PsnInfoTrackerListChunk(
33 | new PsnInfoTrackerChunk(0,
34 | new PsnInfoTrackerNameChunk("Test Tracker"))
35 | )
36 | );
37 |
38 | var infoData = infoPacket1.ToByteArray();
39 |
40 | var infoPacket2 = PsnPacketChunk.FromByteArray(infoData);
41 |
42 | infoPacket1.Should()
43 | .Be(infoPacket2, "because the deserializing the serialized data should produce the same values");
44 |
45 | var dataPacket1 =
46 | new PsnDataPacketChunk(
47 | new PsnDataHeaderChunk(1500, 2, 1, 34, 1),
48 | new PsnDataTrackerListChunk(
49 | new PsnDataTrackerChunk(0,
50 | new PsnDataTrackerPosChunk(0.45f, 7.56f, 2343.43f),
51 | new PsnDataTrackerSpeedChunk(0.34f, 5.76f, -876.87f),
52 | new PsnDataTrackerOriChunk(6.4f, -3.576f, 3873.3f),
53 | new PsnDataTrackerStatusChunk(54f),
54 | new PsnDataTrackerAccelChunk(4.34f, 23423.5f, 234.4f),
55 | new PsnDataTrackerTrgtPosChunk(23.3f, 4325f, 4234f),
56 | new PsnDataTrackerTimestampChunk(34524534454543543)
57 | ),
58 | new PsnDataTrackerChunk(1,
59 | new PsnDataTrackerPosChunk(-343.44f, 4.76f, 2.45f),
60 | new PsnDataTrackerSpeedChunk(34f, -23f, 5676.4f),
61 | new PsnDataTrackerOriChunk(24.7f, 3.53376f, 38.334f),
62 | new PsnDataTrackerStatusChunk(0.1f),
63 | new PsnDataTrackerAccelChunk(4234.34f, 543543.4f, 23.43f),
64 | new PsnDataTrackerTrgtPosChunk(2342.6f, 35.5f, -14545.4f),
65 | new PsnDataTrackerTimestampChunk(ulong.MaxValue)
66 | )
67 | )
68 | );
69 |
70 | var dataData = dataPacket1.ToByteArray();
71 |
72 | var dataPacket2 = PsnPacketChunk.FromByteArray(dataData);
73 |
74 | dataPacket1.Should()
75 | .Be(dataPacket2, "because the deserializing the serialized data should produce the same values");
76 | }
77 | }
78 | }
--------------------------------------------------------------------------------
/src/DBDesign.PosiStageDotNet/Chunks/PsnInfoSystemNameChunk.cs:
--------------------------------------------------------------------------------
1 | // This file is part of PosiStageDotNet.
2 | //
3 | // PosiStageDotNet is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Lesser General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // PosiStageDotNet is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Lesser General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Lesser General Public License
14 | // along with PosiStageDotNet. If not, see .
15 |
16 | using System;
17 | using System.Xml.Linq;
18 | using DBDesign.PosiStageDotNet.Serialization;
19 | using JetBrains.Annotations;
20 |
21 | namespace DBDesign.PosiStageDotNet.Chunks
22 | {
23 | ///
24 | /// PosiStageNet chunk containing the name of the system sending data
25 | ///
26 | [PublicAPI]
27 | public sealed class PsnInfoSystemNameChunk : PsnInfoPacketSubChunk, IEquatable
28 | {
29 | /// is .
30 | public PsnInfoSystemNameChunk([NotNull] string systemName)
31 | : base(null)
32 | {
33 | SystemName = systemName ?? throw new ArgumentNullException(nameof(systemName));
34 | }
35 |
36 | ///
37 | /// Name of system sending PosiStageNet data
38 | ///
39 | public string SystemName { get; }
40 |
41 | ///
42 | public override int DataLength => SystemName.Length;
43 |
44 | ///
45 | public override PsnInfoPacketChunkId ChunkId => PsnInfoPacketChunkId.PsnInfoSystemName;
46 |
47 | ///
48 | public bool Equals(PsnInfoSystemNameChunk other)
49 | {
50 | if (ReferenceEquals(null, other))
51 | return false;
52 | if (ReferenceEquals(this, other))
53 | return true;
54 | return base.Equals(other) && string.Equals(SystemName, other.SystemName);
55 | }
56 |
57 | ///
58 | public override XElement ToXml()
59 | {
60 | return new XElement(nameof(PsnInfoSystemNameChunk),
61 | new XAttribute(nameof(SystemName), SystemName));
62 | }
63 |
64 | ///
65 | public override bool Equals(object obj)
66 | {
67 | if (ReferenceEquals(null, obj))
68 | return false;
69 | if (ReferenceEquals(this, obj))
70 | return true;
71 | return obj.GetType() == GetType() && Equals((PsnInfoSystemNameChunk)obj);
72 | }
73 |
74 | ///
75 | public override int GetHashCode()
76 | {
77 | unchecked
78 | {
79 | return (base.GetHashCode() * 397) ^ SystemName.GetHashCode();
80 | }
81 | }
82 |
83 | internal static PsnInfoSystemNameChunk Deserialize(PsnChunkHeader chunkHeader, PsnBinaryReader reader)
84 | {
85 | return new PsnInfoSystemNameChunk(reader.ReadString(chunkHeader.DataLength));
86 | }
87 |
88 | internal override void SerializeData(PsnBinaryWriter writer)
89 | {
90 | writer.Write(SystemName);
91 | }
92 | }
93 | }
--------------------------------------------------------------------------------
/DBDesign.PosiStageDotNet.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.30114.105
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DBDesign.PosiStageDotNet", "src\DBDesign.PosiStageDotNet\DBDesign.PosiStageDotNet.csproj", "{1A24B27B-533B-4FB4-AB23-3DA0E63E1A91}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DBDesign.PosiStageDotNet.Tests", "test\DBDesign.PosiStageDotNet.Tests\DBDesign.PosiStageDotNet.Tests.csproj", "{1273882E-F444-4025-BC5C-5B5046F2EDF3}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DBDesign.PosiStageDotNet.Server", "example\DBDesign.PosiStageDotNet.Server\DBDesign.PosiStageDotNet.Server.csproj", "{2A4E4BDE-E410-410B-9D08-CCECD4918E9E}"
11 | EndProject
12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{47755D50-12EF-4F60-A30E-4C4BC09D485D}"
13 | EndProject
14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DBDesign.PosiStageDotNet.Client", "example\DBDesign.PosiStageDotNet.Client\DBDesign.PosiStageDotNet.Client.csproj", "{9FEDA94C-5912-47CE-8495-6B95A0EC5AA5}"
15 | EndProject
16 | Global
17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
18 | Debug|Any CPU = Debug|Any CPU
19 | Release|Any CPU = Release|Any CPU
20 | EndGlobalSection
21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
22 | {1A24B27B-533B-4FB4-AB23-3DA0E63E1A91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {1A24B27B-533B-4FB4-AB23-3DA0E63E1A91}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {1A24B27B-533B-4FB4-AB23-3DA0E63E1A91}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {1A24B27B-533B-4FB4-AB23-3DA0E63E1A91}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {1273882E-F444-4025-BC5C-5B5046F2EDF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {1273882E-F444-4025-BC5C-5B5046F2EDF3}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {1273882E-F444-4025-BC5C-5B5046F2EDF3}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {1273882E-F444-4025-BC5C-5B5046F2EDF3}.Release|Any CPU.Build.0 = Release|Any CPU
30 | {2A4E4BDE-E410-410B-9D08-CCECD4918E9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31 | {2A4E4BDE-E410-410B-9D08-CCECD4918E9E}.Debug|Any CPU.Build.0 = Debug|Any CPU
32 | {2A4E4BDE-E410-410B-9D08-CCECD4918E9E}.Release|Any CPU.ActiveCfg = Release|Any CPU
33 | {2A4E4BDE-E410-410B-9D08-CCECD4918E9E}.Release|Any CPU.Build.0 = Release|Any CPU
34 | {9FEDA94C-5912-47CE-8495-6B95A0EC5AA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
35 | {9FEDA94C-5912-47CE-8495-6B95A0EC5AA5}.Debug|Any CPU.Build.0 = Debug|Any CPU
36 | {9FEDA94C-5912-47CE-8495-6B95A0EC5AA5}.Release|Any CPU.ActiveCfg = Release|Any CPU
37 | {9FEDA94C-5912-47CE-8495-6B95A0EC5AA5}.Release|Any CPU.Build.0 = Release|Any CPU
38 | EndGlobalSection
39 | GlobalSection(SolutionProperties) = preSolution
40 | HideSolutionNode = FALSE
41 | EndGlobalSection
42 | GlobalSection(NestedProjects) = preSolution
43 | {2A4E4BDE-E410-410B-9D08-CCECD4918E9E} = {47755D50-12EF-4F60-A30E-4C4BC09D485D}
44 | {9FEDA94C-5912-47CE-8495-6B95A0EC5AA5} = {47755D50-12EF-4F60-A30E-4C4BC09D485D}
45 | EndGlobalSection
46 | GlobalSection(ExtensibilityGlobals) = postSolution
47 | SolutionGuid = {D2919E8D-00EE-44EE-9041-FBECC4E9ABD4}
48 | EndGlobalSection
49 | EndGlobal
50 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | PosiStageDotNet is a C# library for implementing the PosiStageNet protocol in any project compatible with .Net 4.5 or .Net Platform Standard 1.3. The protocol is used to pass position, speed, orientation and other automation data between entertainment control systems, for example, between an automation controller and a lighting desk or media server.
3 |
4 |
5 |
6 | See for more information on PosiStageNet.
7 |
8 | [](https://ci.appveyor.com/project/Pixsper/posistagedotnet)
9 |
10 | ## NuGet
11 |
12 | 
13 |
14 | The library is available from NuGet.org as [DBDesign.PosiStageDotNet](https://www.nuget.org/packages/DBDesign.PosiStageDotNet).
15 |
16 | ## License
17 |
18 | The library is [LGPL 3.0](http://www.gnu.org/licenses/lgpl-3.0.en.html) licensed, allowing use in non-GPL-licensed projects. Any modifications to the source however must be given back to the community under the same license.
19 |
20 |
21 |
22 | ## Simple Examples
23 | ### Sending Data
24 | ```C#
25 | // Set this to the IP of the network interface you want to send PSN packets on
26 | var adapterIp = IPAddress.Parse("10.0.0.1");
27 |
28 | var psnServer = new PsnServer("Test PSN Server", adapterIp);
29 |
30 | var trackers = new []
31 | {
32 | new PsnTracker(0, "Tracker 0",
33 | position: Tuple.Create(0f, 0f, 0f),
34 | speed: Tuple.Create(0f, 0f, 0f),
35 | orientation: Tuple.Create(0f, 0f, 0f)),
36 | new PsnTracker(1, "Tracker 1",
37 | position: Tuple.Create(10.4f, 0f, 0f),
38 | speed: Tuple.Create(1.23f, 0f, 0f),
39 | orientation: Tuple.Create(0f, 85.34f, 0f)),
40 | new PsnTracker(2, "Tracker 2",
41 | position: Tuple.Create(5.232f, 2.654f, 13.765f),
42 | speed: Tuple.Create(1f, 3f, 0f),
43 | orientation: Tuple.Create(23.3f, 43.3f, 76.2f))
44 | };
45 |
46 | psnServer.SetTrackers(trackers);
47 |
48 | psnServer.StartSending();
49 |
50 | // Take some more readings...
51 |
52 | // PsnTrackers are immutable, use the 'with' methods to create a copy with mutated values
53 | var tracker2Update = trackers[2].WithPosition(Tuple.Create(6.345f, 2.23f, 13.098f));
54 |
55 | // We can update values for individual trackers, replacing any tracker data with the same index
56 | psnServer.UpdateTrackers(tracker2Update);
57 |
58 | // When you're finished...
59 | psnServer.StopSending();
60 |
61 | // Don't forget to dispose!
62 | psnServer.Dispose();
63 | ```
64 |
65 | ### Receiving Data
66 | ```C#
67 | // Set this to the IP of the network interface you want to listen for PSN packets on
68 | var adapterIp = IPAddress.Parse("10.0.0.1");
69 |
70 | var psnClient = new PsnClient(adapterIp);
71 |
72 | psnClient.TrackersUpdated += (s, e) =>
73 | {
74 | foreach (var t in e.Values)
75 | Console.WriteLine(t);
76 | };
77 |
78 | psnClient.StartListening();
79 |
80 | // Do something with the tracker data
81 |
82 | // When you're finished...
83 | psnClient.StopListening();
84 |
85 | // Don't forget to dispose!
86 | psnClient.Dispose();
87 | ```
88 |
--------------------------------------------------------------------------------
/src/DBDesign.PosiStageDotNet/Chunks/PsnChunkIdEnums.cs:
--------------------------------------------------------------------------------
1 | // This file is part of PosiStageDotNet.
2 | //
3 | // PosiStageDotNet is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Lesser General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // PosiStageDotNet is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Lesser General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Lesser General Public License
14 | // along with PosiStageDotNet. If not, see .
15 |
16 | using JetBrains.Annotations;
17 |
18 | namespace DBDesign.PosiStageDotNet.Chunks
19 | {
20 | ///
21 | /// ID values for PosiStageNet chunks representing packet types
22 | ///
23 | [PublicAPI]
24 | public enum PsnPacketChunkId : ushort
25 | {
26 | ///
27 | /// Unknown PosiStageNet packet ID
28 | ///
29 | UnknownPacket = 0x0000,
30 | ///
31 | /// PosiStageNet data packet ID
32 | ///
33 | PsnDataPacket = 0x6755,
34 | ///
35 | /// PosiStageNet info packet ID
36 | ///
37 | PsnInfoPacket = 0x6756
38 | }
39 |
40 |
41 | ///
42 | /// ID values for PosiStageNet chunks present in an info packet
43 | ///
44 | [PublicAPI]
45 | public enum PsnInfoPacketChunkId : ushort
46 | {
47 | ///
48 | /// Info packet header chunk ID
49 | ///
50 | PsnInfoHeader = 0x0000,
51 | ///
52 | /// Info packet system name chunk ID
53 | ///
54 | PsnInfoSystemName = 0x0001,
55 | ///
56 | /// Info packet tracker list chunk ID
57 | ///
58 | PsnInfoTrackerList = 0x0002
59 | }
60 |
61 |
62 | ///
63 | /// ID values for PosiStageNet chunks present in an info tracker
64 | ///
65 | [PublicAPI]
66 | public enum PsnInfoTrackerChunkId : ushort
67 | {
68 | ///
69 | /// Info tracker name chunk ID
70 | ///
71 | PsnInfoTrackerName = 0x0000
72 | }
73 |
74 |
75 | ///
76 | /// ID values for PosiStageNet chunks present in an data packet
77 | ///
78 | [PublicAPI]
79 | public enum PsnDataPacketChunkId : ushort
80 | {
81 | ///
82 | /// Data packet header chunk ID
83 | ///
84 | PsnDataHeader = 0x0000,
85 | ///
86 | /// Data packet tracker chunk ID
87 | ///
88 | PsnDataTrackerList = 0x0001
89 | }
90 |
91 |
92 | ///
93 | /// Id values for PosiStageNet chunks present in an data tracker
94 | ///
95 | [PublicAPI]
96 | public enum PsnDataTrackerChunkId : ushort
97 | {
98 | ///
99 | /// Data tracker position chunk ID
100 | ///
101 | PsnDataTrackerPos = 0x0000,
102 | ///
103 | /// Data tracker speed chunk ID
104 | ///
105 | PsnDataTrackerSpeed = 0x0001,
106 | ///
107 | /// Data tracker orientation chunk ID
108 | ///
109 | PsnDataTrackerOri = 0x0002,
110 | ///
111 | /// Data tracker status chunk ID
112 | ///
113 | PsnDataTrackerStatus = 0x0003,
114 | ///
115 | /// Data tracker acceleration chunk ID
116 | ///
117 | PsnDataTrackerAccel = 0x0004,
118 | ///
119 | /// Data tracker target position chunk ID
120 | ///
121 | PsnDataTrackerTrgtPos = 0x0005,
122 | ///
123 | /// Data tracker timestamp chunk ID
124 | ///
125 | PsnDataTrackerTimestamp = 0x0006
126 | }
127 | }
--------------------------------------------------------------------------------
/test/DBDesign.PosiStageDotNet.Tests/UdpServiceTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Net.Sockets;
4 | using System.Threading.Tasks;
5 | using DBDesign.PosiStageDotNet.Networking;
6 | using FluentAssertions;
7 | using Microsoft.VisualStudio.TestTools.UnitTesting;
8 |
9 | namespace DBDesign.PosiStageDotNet.Tests
10 | {
11 | [TestClass]
12 | public class UdpServiceTests
13 | {
14 | public const int UdpPort = 2710;
15 |
16 | [TestMethod]
17 | public async Task CanReceivePacket()
18 | {
19 | var ep = new IPEndPoint(IPAddress.Loopback, UdpPort);
20 | var service = new UdpService(ep);
21 |
22 | service.LocalEndPoint.Should().Be(ep, "because this value was set in the constructor");
23 | service.MulticastGroups.Should().BeEmpty("because no multicast groups were passed in the constructor");
24 |
25 | var data = System.Text.Encoding.UTF8.GetBytes("Test");
26 | var host = new UdpClient();
27 |
28 | service.PacketReceived += (s, e) =>
29 | e.Buffer.Should().BeEquivalentTo(data, "because we should receive the test data exactly");
30 |
31 | using (var monitoredService = service.Monitor())
32 | {
33 | service.StartListening();
34 |
35 | int bytesSent = await host.SendAsync(data, data.Length, ep);
36 | bytesSent.Should().Be(data.Length, "because the test data is this long");
37 |
38 | // Allow some time to receive the packet
39 | await Task.Delay(100);
40 |
41 | monitoredService.Should().Raise(nameof(service.PacketReceived), "because the event should be raised upon receiving a packet");
42 | }
43 |
44 | host.Dispose();
45 |
46 | service.Dispose();
47 | }
48 |
49 | [TestMethod]
50 | public async Task CanReceiveMulticastPacket()
51 | {
52 | var multicastIp = IPAddress.Parse("239.0.0.1");
53 | var ep = new IPEndPoint(IPAddress.Loopback, UdpPort);
54 | var service = new UdpService(ep);
55 | service.JoinMulticastGroup(multicastIp);
56 |
57 | service.LocalEndPoint.Should().Be(ep, "because this value was set in the constructor");
58 | service.MulticastGroups.Should().BeEquivalentTo(new[] { multicastIp }, "because this value was set in the constructor");
59 |
60 | var data = System.Text.Encoding.UTF8.GetBytes("Test");
61 | var host = new UdpClient();
62 |
63 | service.PacketReceived += (s, e) =>
64 | e.Buffer.Should().BeEquivalentTo(data, "because we should receive the test data exactly");
65 |
66 | using (var monitoredService = service.Monitor())
67 | {
68 | service.StartListening();
69 |
70 | int bytesSent = await host.SendAsync(data, data.Length, new IPEndPoint(multicastIp, ep.Port));
71 | bytesSent.Should().Be(data.Length, "because the test data is this long");
72 |
73 | // Allow some time to receive the packet
74 | await Task.Delay(100);
75 |
76 | monitoredService.Should().Raise(nameof(service.PacketReceived), "because the event should be raised upon receiving a packet");
77 | }
78 |
79 | host.Dispose();
80 |
81 | service.Dispose();
82 | }
83 |
84 | [TestMethod]
85 | public void CanManageListeningState()
86 | {
87 | var service = new UdpService(new IPEndPoint(IPAddress.Loopback, UdpPort));
88 |
89 | service.IsListening.Should().BeFalse("because the service is not listening");
90 |
91 | service.Invoking(s => s.StopListeningAsync())
92 | .Should().Throw("because the service is not listening");
93 |
94 | service.StartListening();
95 |
96 | service.IsListening.Should().BeTrue("because the service is listening");
97 |
98 | service.Invoking(s => s.StartListening())
99 | .Should().Throw("because the service is already listening");
100 |
101 | service.Dispose();
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 |
24 | # Visual Studio 2015 cache/options directory
25 | .vs/
26 | # Uncomment if you have tasks that create the project's static files in wwwroot
27 | #wwwroot/
28 |
29 | # MSTest test Results
30 | [Tt]est[Rr]esult*/
31 | [Bb]uild[Ll]og.*
32 |
33 | # NUNIT
34 | *.VisualState.xml
35 | TestResult.xml
36 |
37 | # Build Results of an ATL Project
38 | [Dd]ebugPS/
39 | [Rr]eleasePS/
40 | dlldata.c
41 |
42 | # DNX
43 | project.lock.json
44 | artifacts/
45 |
46 | *_i.c
47 | *_p.c
48 | *_i.h
49 | *.ilk
50 | *.meta
51 | *.obj
52 | *.pch
53 | *.pdb
54 | *.pgc
55 | *.pgd
56 | *.rsp
57 | *.sbr
58 | *.tlb
59 | *.tli
60 | *.tlh
61 | *.tmp
62 | *.tmp_proj
63 | *.log
64 | *.vspscc
65 | *.vssscc
66 | .builds
67 | *.pidb
68 | *.svclog
69 | *.scc
70 |
71 | # Chutzpah Test files
72 | _Chutzpah*
73 |
74 | # Visual C++ cache files
75 | ipch/
76 | *.aps
77 | *.ncb
78 | *.opendb
79 | *.opensdf
80 | *.sdf
81 | *.cachefile
82 |
83 | # Visual Studio profiler
84 | *.psess
85 | *.vsp
86 | *.vspx
87 | *.sap
88 |
89 | # TFS 2012 Local Workspace
90 | $tf/
91 |
92 | # Guidance Automation Toolkit
93 | *.gpState
94 |
95 | # ReSharper is a .NET coding add-in
96 | _ReSharper*/
97 | *.[Rr]e[Ss]harper
98 | *.DotSettings.user
99 |
100 | # JustCode is a .NET coding add-in
101 | .JustCode
102 |
103 | # TeamCity is a build add-in
104 | _TeamCity*
105 |
106 | # DotCover is a Code Coverage Tool
107 | *.dotCover
108 |
109 | # NCrunch
110 | _NCrunch_*
111 | .*crunch*.local.xml
112 | nCrunchTemp_*
113 |
114 | # MightyMoose
115 | *.mm.*
116 | AutoTest.Net/
117 |
118 | # Web workbench (sass)
119 | .sass-cache/
120 |
121 | # Installshield output folder
122 | [Ee]xpress/
123 |
124 | # DocProject is a documentation generator add-in
125 | DocProject/buildhelp/
126 | DocProject/Help/*.HxT
127 | DocProject/Help/*.HxC
128 | DocProject/Help/*.hhc
129 | DocProject/Help/*.hhk
130 | DocProject/Help/*.hhp
131 | DocProject/Help/Html2
132 | DocProject/Help/html
133 |
134 | # Click-Once directory
135 | publish/
136 |
137 | # Publish Web Output
138 | *.[Pp]ublish.xml
139 | *.azurePubxml
140 | # TODO: Comment the next line if you want to checkin your web deploy settings
141 | # but database connection strings (with potential passwords) will be unencrypted
142 | *.pubxml
143 | *.publishproj
144 |
145 | # NuGet Packages
146 | *.nupkg
147 | # The packages folder can be ignored because of Package Restore
148 | **/packages/*
149 | # except build/, which is used as an MSBuild target.
150 | !**/packages/build/
151 | # Uncomment if necessary however generally it will be regenerated when needed
152 | #!**/packages/repositories.config
153 |
154 | # Microsoft Azure Build Output
155 | csx/
156 | *.build.csdef
157 |
158 | # Microsoft Azure Emulator
159 | ecf/
160 | rcf/
161 |
162 | # Microsoft Azure ApplicationInsights config file
163 | ApplicationInsights.config
164 |
165 | # Windows Store app package directory
166 | AppPackages/
167 | BundleArtifacts/
168 |
169 | # Visual Studio cache files
170 | # files ending in .cache can be ignored
171 | *.[Cc]ache
172 | # but keep track of directories ending in .cache
173 | !*.[Cc]ache/
174 |
175 | # Others
176 | ClientBin/
177 | ~$*
178 | *~
179 | *.dbmdl
180 | *.dbproj.schemaview
181 | *.pfx
182 | *.publishsettings
183 | node_modules/
184 | orleans.codegen.cs
185 |
186 | # RIA/Silverlight projects
187 | Generated_Code/
188 |
189 | # Backup & report files from converting an old project file
190 | # to a newer Visual Studio version. Backup files are not needed,
191 | # because we have git ;-)
192 | _UpgradeReport_Files/
193 | Backup*/
194 | UpgradeLog*.XML
195 | UpgradeLog*.htm
196 |
197 | # SQL Server files
198 | *.mdf
199 | *.ldf
200 |
201 | # Business Intelligence projects
202 | *.rdl.data
203 | *.bim.layout
204 | *.bim_*.settings
205 |
206 | # Microsoft Fakes
207 | FakesAssemblies/
208 |
209 | # GhostDoc plugin setting file
210 | *.GhostDoc.xml
211 |
212 | # Node.js Tools for Visual Studio
213 | .ntvs_analysis.dat
214 |
215 | # Visual Studio 6 build log
216 | *.plg
217 |
218 | # Visual Studio 6 workspace options file
219 | *.opt
220 |
221 | # Visual Studio LightSwitch build output
222 | **/*.HTMLClient/GeneratedArtifacts
223 | **/*.DesktopClient/GeneratedArtifacts
224 | **/*.DesktopClient/ModelManifest.xml
225 | **/*.Server/GeneratedArtifacts
226 | **/*.Server/ModelManifest.xml
227 | _Pvt_Extensions
228 |
229 | # Paket dependency manager
230 | .paket/paket.exe
231 |
232 | # FAKE - F# Make
233 | .fake/
234 |
--------------------------------------------------------------------------------
/example/DBDesign.PosiStageDotNet.Client/Program.cs:
--------------------------------------------------------------------------------
1 | // This file is part of PosiStageDotNet.
2 | //
3 | // PosiStageDotNet is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Lesser General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // PosiStageDotNet is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Lesser General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Lesser General Public License
14 | // along with PosiStageDotNet. If not, see .
15 |
16 | using System;
17 | using System.Linq;
18 | using System.Net;
19 | using System.Threading;
20 |
21 | namespace DBDesign.PosiStageDotNet.Client
22 | {
23 | public class Program
24 | {
25 | public static void Main(string[] args)
26 | {
27 | Console.WriteLine(new string('*', Console.WindowWidth - 1));
28 | Console.WriteLine("DBDesign.PosiStageDotNet.Client Example");
29 | Console.WriteLine(new string('*', Console.WindowWidth - 1));
30 | Console.WriteLine();
31 |
32 | PsnClient client;
33 |
34 | switch (args.Length)
35 | {
36 | case 0:
37 | client = new PsnClient(IPAddress.Loopback);
38 | Console.WriteLine(
39 | $"Listening on default multicast IP '{PsnClient.DefaultMulticastIp}', default port {PsnClient.DefaultPort}");
40 | break;
41 |
42 | case 1:
43 | case 2:
44 | {
45 | IPAddress ip;
46 |
47 | if (!IPAddress.TryParse(args[0], out ip))
48 | {
49 | Console.WriteLine($"Invalid IP address value '{args[0]}'");
50 | return;
51 | }
52 |
53 | int port;
54 |
55 | if (args.Length == 2)
56 | {
57 | if (!int.TryParse(args[1], out port))
58 | {
59 | Console.WriteLine($"Invalid UDP port value '{args[1]}'");
60 | return;
61 | }
62 |
63 | if (port < ushort.MinValue + 1 || port > ushort.MaxValue)
64 | {
65 | Console.WriteLine($"UDP port value out of valid range '{args[1]}'");
66 | return;
67 | }
68 | }
69 | else
70 | {
71 | port = PsnClient.DefaultPort;
72 | }
73 |
74 | client = new PsnClient(ip, PsnClient.DefaultMulticastIp, port);
75 | Console.WriteLine(
76 | $"Listening on custom multicast IP '{PsnClient.DefaultMulticastIp}', custom port {PsnClient.DefaultPort}");
77 | }
78 | break;
79 |
80 | default:
81 | Console.WriteLine(
82 | "Invalid args. Format is 'Imp.PosiStageDotNet.Client [CustomMulticastIP] [CustomPort]");
83 | Console.WriteLine("E.g. 'Imp.PosiStageDotNet.Client 236.10.10.10 56565");
84 | return;
85 | }
86 |
87 | Console.WriteLine("Press any key to exit");
88 | Console.WriteLine("");
89 | Console.WriteLine(new string('*', Console.WindowWidth - 1));
90 | Console.WriteLine("");
91 |
92 | client.StartListening();
93 |
94 | while (!Console.KeyAvailable)
95 | {
96 | if (client.Trackers.Any())
97 | {
98 | Console.WriteLine(new string('*', Console.WindowWidth - 1));
99 | Console.WriteLine("");
100 |
101 | foreach (var pair in client.Trackers)
102 | {
103 | Console.WriteLine(pair.Value);
104 | Console.WriteLine("");
105 | }
106 |
107 | Console.WriteLine(new string('*', Console.WindowWidth - 1));
108 | Console.WriteLine("");
109 | }
110 |
111 | Thread.Sleep(1000);
112 | }
113 |
114 | Console.ReadKey();
115 |
116 | Console.WriteLine("");
117 | Console.WriteLine(new string('*', Console.WindowWidth - 1));
118 | Console.WriteLine("");
119 |
120 | Console.WriteLine("Disposing client...");
121 |
122 | client.Dispose();
123 |
124 | Console.WriteLine("Client disposed. Exiting...");
125 | Console.WriteLine("");
126 | Console.WriteLine(new string('*', Console.WindowWidth - 1));
127 | Console.WriteLine("");
128 | }
129 | }
130 | }
--------------------------------------------------------------------------------
/example/DBDesign.PosiStageDotNet.Server/Program.cs:
--------------------------------------------------------------------------------
1 | // This file is part of PosiStageDotNet.
2 | //
3 | // PosiStageDotNet is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Lesser General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // PosiStageDotNet is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Lesser General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Lesser General Public License
14 | // along with PosiStageDotNet. If not, see .
15 |
16 | using System;
17 | using System.Collections.Generic;
18 | using System.Net;
19 | using System.Threading;
20 |
21 | namespace DBDesign.PosiStageDotNet.Server
22 | {
23 | public class Program
24 | {
25 | static readonly Random Random = new Random();
26 |
27 | public static void Main(string[] args)
28 | {
29 | Console.WriteLine(new string('*', Console.WindowWidth - 1));
30 | Console.WriteLine("DBDesign.PosiStageDotNet.Server Example");
31 | Console.WriteLine(new string('*', Console.WindowWidth - 1));
32 | Console.WriteLine();
33 |
34 | PsnServer server;
35 |
36 | switch (args.Length)
37 | {
38 | case 0:
39 | server = new PsnServer(Environment.MachineName, IPAddress.Loopback);
40 | Console.WriteLine(
41 | $"Sending on default multicast IP '{PsnServer.DefaultMulticastIp}', default port {PsnServer.DefaultPort}");
42 | break;
43 |
44 | case 1:
45 | case 2:
46 | {
47 | IPAddress ip;
48 |
49 | if (!IPAddress.TryParse(args[0], out ip))
50 | {
51 | Console.WriteLine($"Invalid IP address value '{args[0]}'");
52 | return;
53 | }
54 |
55 | int port;
56 |
57 | if (args.Length == 2)
58 | {
59 | if (!int.TryParse(args[1], out port))
60 | {
61 | Console.WriteLine($"Invalid UDP port value '{args[1]}'");
62 | return;
63 | }
64 |
65 | if (port < ushort.MinValue + 1 || port > ushort.MaxValue)
66 | {
67 | Console.WriteLine($"UDP port value out of valid range '{args[1]}'");
68 | return;
69 | }
70 | }
71 | else
72 | {
73 | port = PsnServer.DefaultPort;
74 | }
75 |
76 | server = new PsnServer(Environment.MachineName, ip, port);
77 | Console.WriteLine(
78 | $"Sending on custom multicast IP '{PsnServer.DefaultMulticastIp}', custom port {PsnServer.DefaultPort}");
79 | }
80 | break;
81 |
82 | default:
83 | Console.WriteLine(
84 | "Invalid args. Format is 'Imp.PosiStageDotNet.Server [CustomMulticastIP] [CustomPort]");
85 | Console.WriteLine("E.g. 'Imp.PosiStageDotNet.Server 236.10.10.10 56565");
86 | return;
87 | }
88 |
89 | Console.WriteLine("Press any key to exit");
90 | Console.WriteLine("");
91 | Console.WriteLine(new string('*', Console.WindowWidth - 1));
92 | Console.WriteLine("");
93 |
94 | server.SetTrackers(createTrackers());
95 | server.StartSending();
96 |
97 | while (!Console.KeyAvailable)
98 | {
99 | server.SetTrackers(createTrackers());
100 | Thread.Sleep(1000 / 60);
101 | }
102 |
103 | Console.WriteLine("");
104 | Console.WriteLine(new string('*', Console.WindowWidth - 1));
105 | Console.WriteLine("");
106 |
107 | Console.WriteLine("Disposing server...");
108 |
109 | server.Dispose();
110 |
111 | Console.WriteLine("Server disposed. Exiting...");
112 | Console.WriteLine("");
113 | Console.WriteLine(new string('*', Console.WindowWidth - 1));
114 | Console.WriteLine("");
115 | }
116 |
117 | private static IEnumerable createTrackers()
118 | {
119 | return new[]
120 | {
121 | new PsnTracker(0, "Tracker 0", randomValue(), randomValue(), randomValue()),
122 | new PsnTracker(1, "Tracker 1", randomValue(), randomValue(), randomValue()),
123 | new PsnTracker(2, "Tracker 2", randomValue(), randomValue(), randomValue()),
124 | new PsnTracker(3, "Tracker 3", randomValue(), randomValue(), randomValue()),
125 | new PsnTracker(4, "Tracker 4", randomValue(), randomValue(), randomValue()),
126 | new PsnTracker(5, "Tracker 5", randomValue(), randomValue(), randomValue()),
127 | };
128 | }
129 |
130 |
131 | private static Tuple randomValue()
132 | {
133 | return Tuple.Create((float)Random.NextDouble(), (float)Random.NextDouble(), (float)Random.NextDouble());
134 | }
135 | }
136 | }
--------------------------------------------------------------------------------
/src/DBDesign.PosiStageDotNet/Chunks/PsnPacketChunk.cs:
--------------------------------------------------------------------------------
1 | // This file is part of PosiStageDotNet.
2 | //
3 | // PosiStageDotNet is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Lesser General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // PosiStageDotNet is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Lesser General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Lesser General Public License
14 | // along with PosiStageDotNet. If not, see .
15 |
16 | using System;
17 | using System.Collections.Generic;
18 | using System.IO;
19 | using System.Linq;
20 | using System.Xml.Linq;
21 | using DBDesign.PosiStageDotNet.Serialization;
22 | using JetBrains.Annotations;
23 |
24 | namespace DBDesign.PosiStageDotNet.Chunks
25 | {
26 | ///
27 | /// Abstract class representing a chunk which is the root of a PosiStageNet packet
28 | ///
29 | [PublicAPI]
30 | public abstract class PsnPacketChunk : PsnChunk
31 | {
32 | ///
33 | /// Base constructor for packet chunk
34 | ///
35 | /// Untyped sub-chunks of this chunk
36 | protected PsnPacketChunk([CanBeNull] IEnumerable subChunks) : base(subChunks) { }
37 |
38 | ///
39 | /// Typed chunk ID for this packet chunk
40 | ///
41 | public abstract PsnPacketChunkId ChunkId { get; }
42 |
43 | ///
44 | public override ushort RawChunkId => (ushort)ChunkId;
45 |
46 | ///
47 | /// Deserializes a PosiStageNet packet from a byte array
48 | ///
49 | /// Byte array containing PSN data
50 | /// Chunk serialized within data
51 | [CanBeNull]
52 | public static PsnPacketChunk FromByteArray(byte[] data)
53 | {
54 | if (data.Length == 0)
55 | return null;
56 |
57 | try
58 | {
59 | using (var ms = new MemoryStream(data))
60 | using (var reader = new PsnBinaryReader(ms))
61 | {
62 | var chunkHeader = reader.ReadChunkHeader();
63 |
64 | switch ((PsnPacketChunkId)chunkHeader.ChunkId)
65 | {
66 | case PsnPacketChunkId.PsnDataPacket:
67 | return PsnDataPacketChunk.Deserialize(chunkHeader, reader);
68 | case PsnPacketChunkId.PsnInfoPacket:
69 | return PsnInfoPacketChunk.Deserialize(chunkHeader, reader);
70 | default:
71 | return PsnUnknownPacketChunk.Deserialize(chunkHeader, reader);
72 | }
73 | }
74 | }
75 | catch (EndOfStreamException)
76 | {
77 | // Received a bad packet
78 | return null;
79 | }
80 | }
81 |
82 | ///
83 | /// Serializes the chunk to a byte array
84 | ///
85 | public byte[] ToByteArray()
86 | {
87 | using (var ms = new MemoryStream(ChunkHeaderLength + ChunkLength))
88 | using (var writer = new PsnBinaryWriter(ms))
89 | {
90 | serializeChunks(writer, new[] {this});
91 | return ms.ToArray();
92 | }
93 | }
94 |
95 | private void serializeChunks(PsnBinaryWriter writer, IEnumerable chunks)
96 | {
97 | foreach (var chunk in chunks)
98 | {
99 | writer.Write(chunk.ChunkHeader);
100 | chunk.SerializeData(writer);
101 | serializeChunks(writer, chunk.RawSubChunks);
102 | }
103 | }
104 | }
105 |
106 |
107 |
108 | ///
109 | /// Represents a PosiStageNet packet chunk which was unable to be deserialized as it's type is unknown
110 | ///
111 | [PublicAPI]
112 | public sealed class PsnUnknownPacketChunk : PsnPacketChunk, IEquatable
113 | {
114 | internal PsnUnknownPacketChunk(ushort rawChunkId, [CanBeNull] byte[] data)
115 | : base(null)
116 | {
117 | RawChunkId = rawChunkId;
118 | Data = data ?? new byte[0];
119 | }
120 |
121 | ///
122 | /// Un-deserialized data within chunk. May contain sub-chunks.
123 | ///
124 | public byte[] Data { get; }
125 |
126 | ///
127 | public override PsnPacketChunkId ChunkId => PsnPacketChunkId.UnknownPacket;
128 |
129 | ///
130 | public override ushort RawChunkId { get; }
131 |
132 | ///
133 | public override int DataLength => 0;
134 |
135 | ///
136 | public bool Equals(PsnUnknownPacketChunk other)
137 | {
138 | if (ReferenceEquals(null, other))
139 | return false;
140 | if (ReferenceEquals(this, other))
141 | return true;
142 | return base.Equals(other) && Data.SequenceEqual(other.Data);
143 | }
144 |
145 | ///
146 | public override XElement ToXml()
147 | {
148 | return new XElement(nameof(PsnUnknownPacketChunk),
149 | new XAttribute("DataLength", Data.Length));
150 | }
151 |
152 | ///
153 | public override bool Equals(object obj)
154 | {
155 | if (ReferenceEquals(null, obj))
156 | return false;
157 | if (ReferenceEquals(this, obj))
158 | return true;
159 | return obj.GetType() == GetType() && Equals((PsnUnknownPacketChunk)obj);
160 | }
161 |
162 | ///
163 | public override int GetHashCode()
164 | {
165 | unchecked
166 | {
167 | int hashCode = base.GetHashCode();
168 | hashCode = (hashCode * 397) ^ Data.GetHashCode();
169 | hashCode = (hashCode * 397) ^ RawChunkId.GetHashCode();
170 | return hashCode;
171 | }
172 | }
173 |
174 | internal static PsnUnknownPacketChunk Deserialize(PsnChunkHeader chunkHeader, PsnBinaryReader reader)
175 | {
176 | // We can't proceed to deserialize any chunks from this point so store the raw data including sub-chunks
177 | return new PsnUnknownPacketChunk(chunkHeader.ChunkId, reader.ReadBytes(chunkHeader.DataLength));
178 | }
179 | }
180 | }
--------------------------------------------------------------------------------
/src/DBDesign.PosiStageDotNet/Networking/UdpService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.Net;
5 | using System.Net.Sockets;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 |
9 | namespace DBDesign.PosiStageDotNet.Networking
10 | {
11 | ///
12 | /// Helper class wrapping to provide event-based UDP service
13 | ///
14 | internal class UdpService : IDisposable
15 | {
16 | private bool _isDisposed;
17 |
18 | private readonly UdpClient _udpClient;
19 |
20 | private Task _receiveTask = Task.CompletedTask;
21 | private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
22 |
23 | private readonly HashSet _multicastGroups = new HashSet();
24 |
25 | public UdpService(IPEndPoint localEndPoint)
26 | {
27 | LocalEndPoint = localEndPoint ?? throw new ArgumentNullException(nameof(localEndPoint));
28 |
29 | _udpClient = new UdpClient
30 | {
31 | EnableBroadcast = true
32 | };
33 |
34 | _udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
35 | _udpClient.Client.Bind(LocalEndPoint);
36 | }
37 |
38 | public void Dispose()
39 | {
40 | if (_isDisposed)
41 | return;
42 |
43 | if (IsListening)
44 | StopListeningAsync().Wait();
45 |
46 | _udpClient.Dispose();
47 |
48 | _isDisposed = true;
49 | }
50 |
51 | public event EventHandler PacketReceived;
52 |
53 |
54 | public bool IsListening { get; private set; }
55 |
56 | public IPEndPoint LocalEndPoint { get; }
57 |
58 | public IReadOnlyCollection MulticastGroups => _multicastGroups;
59 |
60 |
61 |
62 | public void StartListening()
63 | {
64 | if (_isDisposed)
65 | throw new ObjectDisposedException(GetType().Name);
66 |
67 | if (IsListening)
68 | throw new InvalidOperationException($"Cannot start listening, {nameof(UdpService)} is already listening");
69 |
70 | _receiveTask = listenAsync(_cancellationTokenSource.Token);
71 |
72 | IsListening = true;
73 | }
74 |
75 | public async Task StopListeningAsync()
76 | {
77 | if (_isDisposed)
78 | throw new ObjectDisposedException(GetType().Name);
79 |
80 | if (!IsListening)
81 | throw new InvalidOperationException($"Cannot stop listening, {nameof(UdpService)} is not currently listening");
82 |
83 | _cancellationTokenSource.Cancel();
84 |
85 | await _receiveTask.ConfigureAwait(false);
86 | _receiveTask = Task.CompletedTask;
87 |
88 | _cancellationTokenSource.Dispose();
89 | _cancellationTokenSource = new CancellationTokenSource();
90 |
91 | IsListening = false;
92 | }
93 |
94 | public void JoinMulticastGroup(IPAddress multicastIp)
95 | {
96 | if (multicastIp.AddressFamily != AddressFamily.InterNetwork)
97 | throw new ArgumentException("Not a valid IPv4 address", nameof(multicastIp));
98 |
99 | var ipBytes = multicastIp.GetAddressBytes();
100 | if (ipBytes[0] < 224 || ipBytes[1] > 239)
101 | throw new ArgumentException("Not a valid multicast address", nameof(multicastIp));
102 |
103 | if (_isDisposed)
104 | throw new ObjectDisposedException(GetType().Name);
105 |
106 | if (_multicastGroups.Contains(multicastIp))
107 | throw new ArgumentException($"Already a member of multicast group {multicastIp}", nameof(multicastIp));
108 |
109 | if (LocalEndPoint.Address.Equals(IPAddress.Any))
110 | _udpClient.JoinMulticastGroup(multicastIp);
111 | else
112 | _udpClient.JoinMulticastGroup(multicastIp, LocalEndPoint.Address);
113 |
114 | _multicastGroups.Add(multicastIp);
115 | }
116 |
117 | public void DropMulticastGroup(IPAddress multicastIp)
118 | {
119 | if (multicastIp.AddressFamily != AddressFamily.InterNetwork)
120 | throw new ArgumentException("Not a valid IPv4 address", nameof(multicastIp));
121 |
122 | var ipBytes = multicastIp.GetAddressBytes();
123 | if (ipBytes[0] < 224 || ipBytes[1] > 239)
124 | throw new ArgumentException("Not a valid multicast address", nameof(multicastIp));
125 |
126 | if (_isDisposed)
127 | throw new ObjectDisposedException(GetType().Name);
128 |
129 | if (!_multicastGroups.Contains(multicastIp))
130 | throw new ArgumentException($"Not a member of multicast group {multicastIp}", nameof(multicastIp));
131 |
132 | _udpClient.DropMulticastGroup(multicastIp);
133 | _multicastGroups.Remove(multicastIp);
134 | }
135 |
136 | public Task SendAsync(byte[] data, IPEndPoint endPoint)
137 | {
138 | if (_isDisposed)
139 | throw new ObjectDisposedException(GetType().Name);
140 |
141 | return _udpClient.SendAsync(data, data.Length, endPoint);
142 | }
143 |
144 | public Task SendAsync(byte[] data, int length, IPEndPoint endPoint)
145 | {
146 | if (_isDisposed)
147 | throw new ObjectDisposedException(GetType().Name);
148 |
149 | return _udpClient.SendAsync(data, length, endPoint);
150 | }
151 |
152 | private async Task listenAsync(CancellationToken cancellationToken)
153 | {
154 | while (!cancellationToken.IsCancellationRequested)
155 | {
156 | try
157 | {
158 | var task = _udpClient.ReceiveAsync();
159 |
160 | var tcs = new TaskCompletionSource();
161 | using (cancellationToken.Register(s => ((TaskCompletionSource) s).TrySetResult(true), tcs))
162 | {
163 | if (task != await Task.WhenAny(task, tcs.Task))
164 | return;
165 | }
166 |
167 | PacketReceived?.Invoke(this, task.Result);
168 | }
169 | catch (SocketException ex)
170 | {
171 |
172 | }
173 | }
174 | }
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/src/DBDesign.PosiStageDotNet/Chunks/PsnChunk.cs:
--------------------------------------------------------------------------------
1 | // This file is part of PosiStageDotNet.
2 | //
3 | // PosiStageDotNet is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Lesser General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // PosiStageDotNet is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Lesser General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Lesser General Public License
14 | // along with PosiStageDotNet. If not, see .
15 |
16 | using System;
17 | using System.Collections.Generic;
18 | using System.Diagnostics;
19 | using System.IO;
20 | using System.Linq;
21 | using System.Xml.Linq;
22 | using DBDesign.PosiStageDotNet.Serialization;
23 | using JetBrains.Annotations;
24 |
25 | namespace DBDesign.PosiStageDotNet.Chunks
26 | {
27 | ///
28 | /// Abstract class representing a chunk of PosiStageNet data. A chunk can either contain data or a collection of
29 | /// sub-chunks.
30 | ///
31 | [PublicAPI]
32 | public abstract class PsnChunk
33 | {
34 | ///
35 | /// Length in bytes of a PosiStageNet chunk header
36 | ///
37 | public const int ChunkHeaderLength = 4;
38 |
39 | ///
40 | /// Constructs a PosiStageNet chunk
41 | ///
42 | /// Sub-chunks belonging to this chunk
43 | protected PsnChunk([CanBeNull] IEnumerable subChunks)
44 | {
45 | RawSubChunks = subChunks ?? Enumerable.Empty();
46 | }
47 |
48 | ///
49 | /// The 16-bit identifier for this chunk.
50 | ///
51 | /// Subclasses of PsnChunk have strongly typed ChunkId properties
52 | public abstract ushort RawChunkId { get; }
53 |
54 | ///
55 | /// The length of the data contained within this chunk, excluding sub-chunks and the local chunk header.
56 | ///
57 | public abstract int DataLength { get; }
58 |
59 | ///
60 | /// The length of the entire chunk, including sub-chunks but excluding the local chunk header.
61 | ///
62 | public int ChunkLength => DataLength + RawSubChunks.Sum(c => ChunkHeaderLength + c.ChunkLength);
63 |
64 | ///
65 | /// The length of the entire chunk and local chunk header, including sub-chunks
66 | ///
67 | public int ChunkAndHeaderLength => ChunkLength + ChunkHeaderLength;
68 |
69 |
70 | ///
71 | /// Enumerable of sub-chunks
72 | ///
73 | public IEnumerable RawSubChunks { get; }
74 |
75 | ///
76 | /// True if this chunk contains sub-chunks
77 | ///
78 | public bool HasSubChunks => RawSubChunks.Any();
79 |
80 | ///
81 | /// Enumerable of sub-chunks which were unrecognized when deserializing
82 | ///
83 | public IEnumerable UnknownSubChunks => RawSubChunks.OfType();
84 |
85 | ///
86 | /// True if chunk contains any sub-chunks which were unrecognized when deserializing
87 | ///
88 | public bool HasUnknownSubChunks => UnknownSubChunks.Any();
89 |
90 | ///
91 | /// Chunk header value for this chunk
92 | ///
93 | internal PsnChunkHeader ChunkHeader => new PsnChunkHeader(RawChunkId, ChunkLength, HasSubChunks);
94 |
95 | ///
96 | /// Converts the chunk and sub-chunks to an XML representation
97 | ///
98 | public abstract XElement ToXml();
99 |
100 | ///
101 | /// Compares the chunk ID and sub-chunks of this chunk
102 | ///
103 | /// Chunk to compare to this chunk
104 | /// True if chunks are equal
105 | public override bool Equals(object obj)
106 | {
107 | if (ReferenceEquals(null, obj))
108 | return false;
109 | if (ReferenceEquals(this, obj))
110 | return true;
111 | return obj.GetType() == GetType() && Equals((PsnChunk)obj);
112 | }
113 |
114 | ///
115 | /// Hashcode for this chunk based on chunk ID and sub-chunk enumerable
116 | ///
117 | public override int GetHashCode()
118 | {
119 | unchecked
120 | {
121 | return (RawChunkId.GetHashCode() * 397) ^ RawSubChunks.GetHashCode();
122 | }
123 | }
124 |
125 | ///
126 | /// Compares the chunk ID and sub-chunks of this chunk
127 | ///
128 | /// Chunk to compare to this chunk
129 | /// True if chunks are equal
130 | protected bool Equals([CanBeNull] PsnChunk other)
131 | {
132 | if (ReferenceEquals(null, other))
133 | return false;
134 | if (ReferenceEquals(this, other))
135 | return true;
136 | return RawChunkId == other.RawChunkId && RawSubChunks.SequenceEqual(other.RawSubChunks);
137 | }
138 |
139 | ///
140 | /// Searches the binary stream for the positions of sub-chunk headers
141 | ///
142 | /// An I/O error occurs.
143 | /// The stream does not support seeking.
144 | internal static IEnumerable> FindSubChunkHeaders(PsnBinaryReader reader,
145 | int chunkDataLength)
146 | {
147 | var chunkHeaders = new List>();
148 | long startPos = reader.BaseStream.Position;
149 |
150 | while (reader.BaseStream.Position - startPos < chunkDataLength)
151 | {
152 | var chunkHeader = reader.ReadChunkHeader();
153 | chunkHeaders.Add(Tuple.Create(chunkHeader, reader.BaseStream.Position));
154 | reader.Seek(chunkHeader.DataLength, SeekOrigin.Current);
155 |
156 | Debug.Assert(reader.BaseStream.Position - startPos <= chunkDataLength);
157 | }
158 |
159 | reader.Seek(startPos, SeekOrigin.Begin);
160 |
161 | return chunkHeaders;
162 | }
163 |
164 | ///
165 | /// Serializes any data contained within this chunk, or nothing if the chunk contains no data
166 | ///
167 | ///
168 | internal virtual void SerializeData(PsnBinaryWriter writer) { }
169 | }
170 | }
--------------------------------------------------------------------------------
/resources/psn logos/PSN_Textonly_Black.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
72 |
--------------------------------------------------------------------------------
/resources/psn logos/PSN_Textonly_White.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
77 |
--------------------------------------------------------------------------------
/resources/psn logos/PSN_Textonly_Color.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
78 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
167 |
--------------------------------------------------------------------------------
/resources/psn logos/PSN_Black.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
97 |
--------------------------------------------------------------------------------
/src/DBDesign.PosiStageDotNet/Chunks/PsnInfoTrackerListChunk.cs:
--------------------------------------------------------------------------------
1 | // This file is part of PosiStageDotNet.
2 | //
3 | // PosiStageDotNet is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Lesser General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // PosiStageDotNet is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Lesser General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Lesser General Public License
14 | // along with PosiStageDotNet. If not, see .
15 |
16 | using System;
17 | using System.Collections.Generic;
18 | using System.IO;
19 | using System.Linq;
20 | using System.Xml.Linq;
21 | using DBDesign.PosiStageDotNet.Serialization;
22 | using JetBrains.Annotations;
23 |
24 | namespace DBDesign.PosiStageDotNet.Chunks
25 | {
26 | ///
27 | /// PosiStageNet chunk containing a list of info trackers
28 | ///
29 | [PublicAPI]
30 | public sealed class PsnInfoTrackerListChunk : PsnInfoPacketSubChunk
31 | {
32 | ///
33 | /// Info tracker list chunk constructor
34 | ///
35 | /// Typed sub-chunks of this chunk
36 | public PsnInfoTrackerListChunk([NotNull] IEnumerable subChunks)
37 | : this((IEnumerable)subChunks)
38 | { }
39 |
40 | ///
41 | /// Info tracker list chunk constructor
42 | ///
43 | /// Typed sub-chunks of this chunk
44 | public PsnInfoTrackerListChunk(params PsnInfoTrackerChunk[] subChunks) : this((IEnumerable)subChunks) { }
45 |
46 | private PsnInfoTrackerListChunk([NotNull] IEnumerable subChunks) : base(subChunks) { }
47 |
48 | ///
49 | public override PsnInfoPacketChunkId ChunkId => PsnInfoPacketChunkId.PsnInfoTrackerList;
50 |
51 | ///
52 | public override int DataLength => 0;
53 |
54 | ///
55 | /// Typed sub-chunks of this chunk
56 | ///
57 | public IEnumerable SubChunks => RawSubChunks.OfType();
58 |
59 | ///
60 | public override XElement ToXml()
61 | {
62 | return new XElement(nameof(PsnInfoTrackerListChunk),
63 | RawSubChunks.Select(c => c.ToXml()));
64 | }
65 |
66 | internal static PsnInfoTrackerListChunk Deserialize(PsnChunkHeader chunkHeader, PsnBinaryReader reader)
67 | {
68 | var subChunks = new List();
69 |
70 | foreach (var pair in FindSubChunkHeaders(reader, chunkHeader.DataLength))
71 | {
72 | reader.Seek(pair.Item2, SeekOrigin.Begin);
73 | subChunks.Add(PsnInfoTrackerChunk.Deserialize(pair.Item1, reader));
74 | }
75 |
76 | return new PsnInfoTrackerListChunk(subChunks);
77 | }
78 | }
79 |
80 | ///
81 | /// PosiStageNet info packet chunk containing info for one info tracker. Only valid as sub-chunk of a .
82 | ///
83 | [PublicAPI]
84 | public class PsnInfoTrackerChunk : PsnChunk
85 | {
86 | ///
87 | /// Info tracker chunk constructor
88 | ///
89 | /// ID of this tracker
90 | /// Typed sub-chunks of this chunk
91 | public PsnInfoTrackerChunk(int trackerId, [NotNull] IEnumerable subChunks)
92 | : this(trackerId, (IEnumerable)subChunks)
93 | { }
94 |
95 | ///
96 | /// Info tracker chunk constructor
97 | ///
98 | /// /// ID of this tracker
99 | /// Typed sub-chunks of this chunk
100 | public PsnInfoTrackerChunk(int trackerId, params PsnInfoTrackerSubChunk[] subChunks)
101 | : this(trackerId, (IEnumerable)subChunks)
102 | { }
103 |
104 | private PsnInfoTrackerChunk(int trackerId, [NotNull] IEnumerable subChunks)
105 | : base(subChunks)
106 | {
107 | if (trackerId < ushort.MinValue || trackerId > ushort.MaxValue)
108 | throw new ArgumentOutOfRangeException(nameof(trackerId), trackerId,
109 | $"trackerId must be in the range {ushort.MinValue}-{ushort.MaxValue}");
110 |
111 | RawChunkId = (ushort)trackerId;
112 | }
113 |
114 | ///
115 | ///
116 | /// Trackers have no specific chunk ID and use this value to store the tracker ID
117 | ///
118 | public override ushort RawChunkId { get; }
119 |
120 | ///
121 | public override int DataLength => 0;
122 |
123 | ///
124 | /// ID of this tracker, stored in
125 | ///
126 | public int TrackerId => RawChunkId;
127 |
128 | ///
129 | /// Typed sub-chunks of this chunk
130 | ///
131 | public IEnumerable SubChunks => RawSubChunks.OfType();
132 |
133 | ///
134 | public override XElement ToXml()
135 | {
136 | return new XElement(nameof(PsnInfoTrackerChunk),
137 | new XAttribute("TrackerId", RawChunkId),
138 | RawSubChunks.Select(c => c.ToXml()));
139 | }
140 |
141 | internal static PsnInfoTrackerChunk Deserialize(PsnChunkHeader chunkHeader, PsnBinaryReader reader)
142 | {
143 | var subChunks = new List();
144 |
145 | foreach (var pair in FindSubChunkHeaders(reader, chunkHeader.DataLength))
146 | {
147 | reader.Seek(pair.Item2, SeekOrigin.Begin);
148 |
149 | switch ((PsnInfoTrackerChunkId)pair.Item1.ChunkId)
150 | {
151 | case PsnInfoTrackerChunkId.PsnInfoTrackerName:
152 | subChunks.Add(PsnInfoTrackerNameChunk.Deserialize(pair.Item1, reader));
153 | break;
154 | default:
155 | subChunks.Add(PsnUnknownChunk.Deserialize(chunkHeader, reader));
156 | break;
157 | }
158 | }
159 |
160 | return new PsnInfoTrackerChunk(chunkHeader.ChunkId, subChunks);
161 | }
162 | }
163 |
164 |
165 |
166 | ///
167 | /// Base class for sub-chunks of
168 | ///
169 | [PublicAPI]
170 | public abstract class PsnInfoTrackerSubChunk : PsnChunk
171 | {
172 | ///
173 | /// Base info tracker sub-chunk constructor
174 | ///
175 | /// Typed sub-chunks of this chunk
176 | protected PsnInfoTrackerSubChunk([CanBeNull] IEnumerable subChunks) : base(subChunks) { }
177 |
178 | ///
179 | /// Typed chunk ID
180 | ///
181 | public abstract PsnInfoTrackerChunkId ChunkId { get; }
182 |
183 | ///
184 | public override ushort RawChunkId => (ushort)ChunkId;
185 | }
186 |
187 |
188 |
189 | ///
190 | /// Info tracker sub-chunk containing tracker name
191 | ///
192 | [PublicAPI]
193 | public class PsnInfoTrackerNameChunk : PsnInfoTrackerSubChunk, IEquatable
194 | {
195 | ///
196 | /// Tracker name chunk constructor
197 | ///
198 | /// Name for tracker with this tracker ID
199 | /// is .
200 | public PsnInfoTrackerNameChunk([NotNull] string trackerName)
201 | : base(null)
202 | {
203 | TrackerName = trackerName ?? throw new ArgumentNullException(nameof(trackerName));
204 | }
205 |
206 | ///
207 | /// Name for tracker with this tracker ID
208 | ///
209 | public string TrackerName { get; }
210 |
211 | ///
212 | public override int DataLength => TrackerName.Length;
213 |
214 | ///
215 | public override PsnInfoTrackerChunkId ChunkId => PsnInfoTrackerChunkId.PsnInfoTrackerName;
216 |
217 | ///
218 | public bool Equals(PsnInfoTrackerNameChunk other)
219 | {
220 | if (ReferenceEquals(null, other))
221 | return false;
222 | if (ReferenceEquals(this, other))
223 | return true;
224 | return base.Equals(other) && string.Equals(TrackerName, other.TrackerName);
225 | }
226 |
227 | ///
228 | public override bool Equals(object obj)
229 | {
230 | if (ReferenceEquals(null, obj))
231 | return false;
232 | if (ReferenceEquals(this, obj))
233 | return true;
234 | return obj.GetType() == GetType() && Equals((PsnInfoTrackerNameChunk)obj);
235 | }
236 |
237 | ///
238 | public override int GetHashCode()
239 | {
240 | unchecked
241 | {
242 | return (base.GetHashCode() * 397) ^ TrackerName.GetHashCode();
243 | }
244 | }
245 |
246 | ///
247 | public override XElement ToXml()
248 | {
249 | return new XElement(nameof(PsnInfoTrackerNameChunk),
250 | new XAttribute(nameof(TrackerName), TrackerName));
251 | }
252 |
253 | internal static PsnInfoTrackerNameChunk Deserialize(PsnChunkHeader chunkHeader, PsnBinaryReader reader)
254 | {
255 | return new PsnInfoTrackerNameChunk(reader.ReadString(chunkHeader.DataLength));
256 | }
257 |
258 | internal override void SerializeData(PsnBinaryWriter writer)
259 | {
260 | writer.Write(TrackerName);
261 | }
262 | }
263 | }
--------------------------------------------------------------------------------
/src/DBDesign.PosiStageDotNet/Chunks/PsnDataPacketChunk.cs:
--------------------------------------------------------------------------------
1 | // This file is part of PosiStageDotNet.
2 | //
3 | // PosiStageDotNet is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Lesser General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // PosiStageDotNet is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Lesser General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Lesser General Public License
14 | // along with PosiStageDotNet. If not, see .
15 |
16 | using System;
17 | using System.Collections.Generic;
18 | using System.IO;
19 | using System.Linq;
20 | using System.Xml.Linq;
21 | using DBDesign.PosiStageDotNet.Serialization;
22 | using JetBrains.Annotations;
23 |
24 | namespace DBDesign.PosiStageDotNet.Chunks
25 | {
26 | ///
27 | /// Root chunk of a PosiStageNet data packet
28 | ///
29 | [PublicAPI]
30 | public sealed class PsnDataPacketChunk : PsnPacketChunk
31 | {
32 | ///
33 | /// Data chunk constructor
34 | ///
35 | /// Typed sub-chunks of this chunk
36 | public PsnDataPacketChunk([NotNull] IEnumerable subChunks)
37 | : this((IEnumerable)subChunks) { }
38 |
39 | ///
40 | /// Data chunk constructor
41 | ///
42 | /// Typed sub-chunks of this chunk
43 | public PsnDataPacketChunk(params PsnDataPacketSubChunk[] subChunks) : this((IEnumerable)subChunks) { }
44 |
45 | private PsnDataPacketChunk([NotNull] IEnumerable subChunks) : base(subChunks) { }
46 |
47 | ///
48 | public override int DataLength => 0;
49 |
50 | ///
51 | /// Enumerable of typed sub-chunks contained within this chunk
52 | ///
53 | public IEnumerable SubChunks => RawSubChunks.OfType();
54 |
55 | ///
56 | public override PsnPacketChunkId ChunkId => PsnPacketChunkId.PsnDataPacket;
57 |
58 | ///
59 | public override XElement ToXml()
60 | {
61 | return new XElement(nameof(PsnDataPacketChunk),
62 | RawSubChunks.Select(c => c.ToXml()));
63 | }
64 |
65 | internal static PsnDataPacketChunk Deserialize(PsnChunkHeader chunkHeader, PsnBinaryReader reader)
66 | {
67 | var subChunks = new List();
68 |
69 | foreach (var pair in FindSubChunkHeaders(reader, chunkHeader.DataLength))
70 | {
71 | reader.Seek(pair.Item2, SeekOrigin.Begin);
72 |
73 | switch ((PsnDataPacketChunkId)pair.Item1.ChunkId)
74 | {
75 | case PsnDataPacketChunkId.PsnDataHeader:
76 | subChunks.Add(PsnDataHeaderChunk.Deserialize(pair.Item1, reader));
77 | break;
78 | case PsnDataPacketChunkId.PsnDataTrackerList:
79 | subChunks.Add(PsnDataTrackerListChunk.Deserialize(pair.Item1, reader));
80 | break;
81 | default:
82 | subChunks.Add(PsnUnknownChunk.Deserialize(pair.Item1, reader));
83 | break;
84 | }
85 | }
86 |
87 | return new PsnDataPacketChunk(subChunks);
88 | }
89 | }
90 |
91 |
92 | ///
93 | /// Base class for sub-chunks of a PosiStageNet data packet chunk
94 | ///
95 | [PublicAPI]
96 | public abstract class PsnDataPacketSubChunk : PsnChunk
97 | {
98 | ///
99 | /// Base constructor for data packet sub-chunk
100 | ///
101 | ///
102 | protected PsnDataPacketSubChunk([CanBeNull] IEnumerable subChunks) : base(subChunks) { }
103 |
104 | ///
105 | /// Typed chunk ID for data packet sub-chunk
106 | ///
107 | public abstract PsnDataPacketChunkId ChunkId { get; }
108 |
109 | ///
110 | public override ushort RawChunkId => (ushort)ChunkId;
111 | }
112 |
113 |
114 |
115 | ///
116 | /// Chunk containing header data for a PosiStageNet data packet
117 | ///
118 | [PublicAPI]
119 | public sealed class PsnDataHeaderChunk : PsnDataPacketSubChunk, IEquatable
120 | {
121 | internal const int StaticChunkAndHeaderLength = ChunkHeaderLength + StaticDataLength;
122 | internal const int StaticDataLength = 12;
123 |
124 |
125 | ///
126 | /// Constructs a header chunk for a data packet
127 | ///
128 | /// Time in microseconds at which the data contained in this packet was measured
129 | /// High byte of PosiStageNet version
130 | /// Low byte of PosiStageNet version
131 | /// Frame ID byte
132 | /// Frame packet count byte
133 | ///
134 | public PsnDataHeaderChunk(ulong timestamp, int versionHigh, int versionLow, int frameId, int framePacketCount)
135 | : base(null)
136 | {
137 | TimeStamp = timestamp;
138 |
139 | if (versionHigh < 0 || versionHigh > 255)
140 | throw new ArgumentOutOfRangeException(nameof(versionHigh), "versionHigh must be between 0 and 255");
141 |
142 | VersionHigh = versionHigh;
143 |
144 | if (versionLow < 0 || versionLow > 255)
145 | throw new ArgumentOutOfRangeException(nameof(versionLow), "versionLow must be between 0 and 255");
146 |
147 | VersionLow = versionLow;
148 |
149 | if (frameId < 0 || frameId > 255)
150 | throw new ArgumentOutOfRangeException(nameof(frameId), "frameId must be between 0 and 255");
151 |
152 | FrameId = frameId;
153 |
154 | if (framePacketCount < 0 || framePacketCount > 255)
155 | throw new ArgumentOutOfRangeException(nameof(framePacketCount),
156 | "framePacketCount must be between 0 and 255");
157 |
158 | FramePacketCount = framePacketCount;
159 | }
160 |
161 | ///
162 | /// Time in microseconds at which the data contained in this packet was measured
163 | ///
164 | public ulong TimeStamp { get; }
165 |
166 | ///
167 | /// High byte of the PosiStageNet version of the packet
168 | ///
169 | public int VersionHigh { get; }
170 |
171 | ///
172 | /// Low byte of the PosiStageNet version of this packet
173 | ///
174 | public int VersionLow { get; }
175 |
176 | ///
177 | /// Frame ID value used for collating data from multiple packets
178 | ///
179 | public int FrameId { get; }
180 |
181 | ///
182 | /// Number of individual data packets for this
183 | ///
184 | public int FramePacketCount { get; }
185 |
186 | ///
187 | public override int DataLength => StaticDataLength;
188 |
189 | ///
190 | public override PsnDataPacketChunkId ChunkId => PsnDataPacketChunkId.PsnDataHeader;
191 |
192 | ///
193 | public bool Equals(PsnDataHeaderChunk other)
194 | {
195 | if (ReferenceEquals(null, other))
196 | return false;
197 | if (ReferenceEquals(this, other))
198 | return true;
199 | return base.Equals(other) && TimeStamp == other.TimeStamp && VersionHigh == other.VersionHigh
200 | && VersionLow == other.VersionLow && FrameId == other.FrameId
201 | && FramePacketCount == other.FramePacketCount;
202 | }
203 |
204 | ///
205 | public override XElement ToXml()
206 | {
207 | return new XElement(nameof(PsnDataHeaderChunk),
208 | new XAttribute(nameof(TimeStamp), TimeStamp),
209 | new XAttribute(nameof(VersionHigh), VersionHigh),
210 | new XAttribute(nameof(FrameId), FrameId),
211 | new XAttribute(nameof(FramePacketCount), FramePacketCount));
212 | }
213 |
214 | ///
215 | public override bool Equals(object obj)
216 | {
217 | if (ReferenceEquals(null, obj))
218 | return false;
219 | if (ReferenceEquals(this, obj))
220 | return true;
221 | return obj.GetType() == GetType() && Equals((PsnDataHeaderChunk)obj);
222 | }
223 |
224 | ///
225 | public override int GetHashCode()
226 | {
227 | unchecked
228 | {
229 | int hashCode = base.GetHashCode();
230 | hashCode = (hashCode * 397) ^ TimeStamp.GetHashCode();
231 | hashCode = (hashCode * 397) ^ VersionHigh;
232 | hashCode = (hashCode * 397) ^ VersionLow;
233 | hashCode = (hashCode * 397) ^ FrameId;
234 | hashCode = (hashCode * 397) ^ FramePacketCount;
235 | return hashCode;
236 | }
237 | }
238 |
239 | internal static PsnDataHeaderChunk Deserialize(PsnChunkHeader chunkHeader, PsnBinaryReader reader)
240 | {
241 | ulong timeStamp = reader.ReadUInt64();
242 | int versionHigh = reader.ReadByte();
243 | int versionLow = reader.ReadByte();
244 | int frameId = reader.ReadByte();
245 | int framePacketCount = reader.ReadByte();
246 |
247 | return new PsnDataHeaderChunk(timeStamp, versionHigh, versionLow, frameId, framePacketCount);
248 | }
249 |
250 | internal override void SerializeData(PsnBinaryWriter writer)
251 | {
252 | writer.Write(TimeStamp);
253 | writer.Write((byte)VersionHigh);
254 | writer.Write((byte)VersionLow);
255 | writer.Write((byte)FrameId);
256 | writer.Write((byte)FramePacketCount);
257 | }
258 | }
259 | }
--------------------------------------------------------------------------------
/resources/psn logos/PSN_White.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
100 |
--------------------------------------------------------------------------------
/resources/psn logos/PSN_Color.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
101 |
--------------------------------------------------------------------------------
/src/DBDesign.PosiStageDotNet/Chunks/PsnInfoPacketChunk.cs:
--------------------------------------------------------------------------------
1 | // This file is part of PosiStageDotNet.
2 | //
3 | // PosiStageDotNet is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Lesser General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // PosiStageDotNet is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Lesser General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Lesser General Public License
14 | // along with PosiStageDotNet. If not, see .
15 |
16 | using System;
17 | using System.Collections.Generic;
18 | using System.IO;
19 | using System.Linq;
20 | using System.Xml.Linq;
21 | using DBDesign.PosiStageDotNet.Serialization;
22 | using JetBrains.Annotations;
23 |
24 | namespace DBDesign.PosiStageDotNet.Chunks
25 | {
26 | ///
27 | /// Root chunk of a PosiStageNet info packet
28 | ///
29 | [PublicAPI]
30 | public sealed class PsnInfoPacketChunk : PsnPacketChunk
31 | {
32 | ///
33 | /// Info packet chunk constructor
34 | ///
35 | /// Typed sub-chunks of this chunk
36 | public PsnInfoPacketChunk([NotNull] IEnumerable subChunks)
37 | : this((IEnumerable)subChunks) { }
38 |
39 | ///
40 | /// Info packet chunk constructor
41 | ///
42 | /// Typed sub-chunks of this chunk
43 | public PsnInfoPacketChunk(params PsnInfoPacketSubChunk[] subChunks) : this((IEnumerable)subChunks) { }
44 |
45 | private PsnInfoPacketChunk([NotNull] IEnumerable subChunks) : base(subChunks) { }
46 |
47 | ///
48 | /// The length of the data contained within this chunk, excluding sub-chunks and the local chunk header.
49 | ///
50 | public override int DataLength => 0;
51 |
52 | ///
53 | /// Typed chunk id for this packet chunk
54 | ///
55 | public override PsnPacketChunkId ChunkId => PsnPacketChunkId.PsnInfoPacket;
56 |
57 | ///
58 | /// Enumerable of typed sub-chunks contained within this chunk
59 | ///
60 | public IEnumerable SubChunks => RawSubChunks.OfType();
61 |
62 | ///
63 | /// Converts chunk and sub-chunks to an XML representation
64 | ///
65 | public override XElement ToXml()
66 | {
67 | return new XElement(nameof(PsnInfoPacketChunk),
68 | RawSubChunks.Select(c => c.ToXml()));
69 | }
70 |
71 | internal static PsnInfoPacketChunk Deserialize(PsnChunkHeader chunkHeader, PsnBinaryReader reader)
72 | {
73 | var subChunks = new List();
74 |
75 | foreach (var pair in FindSubChunkHeaders(reader, chunkHeader.DataLength))
76 | {
77 | reader.Seek(pair.Item2, SeekOrigin.Begin);
78 |
79 | switch ((PsnInfoPacketChunkId)pair.Item1.ChunkId)
80 | {
81 | case PsnInfoPacketChunkId.PsnInfoHeader:
82 | subChunks.Add(PsnInfoHeaderChunk.Deserialize(pair.Item1, reader));
83 | break;
84 | case PsnInfoPacketChunkId.PsnInfoSystemName:
85 | subChunks.Add(PsnInfoSystemNameChunk.Deserialize(pair.Item1, reader));
86 | break;
87 | case PsnInfoPacketChunkId.PsnInfoTrackerList:
88 | subChunks.Add(PsnInfoTrackerListChunk.Deserialize(pair.Item1, reader));
89 | break;
90 | default:
91 | subChunks.Add(PsnUnknownChunk.Deserialize(pair.Item1, reader));
92 | break;
93 | }
94 | }
95 |
96 | return new PsnInfoPacketChunk(subChunks);
97 | }
98 | }
99 |
100 |
101 |
102 | ///
103 | /// Base class for sub-chunks of a PosiStageNet info packet chunk
104 | ///
105 | [PublicAPI]
106 | public abstract class PsnInfoPacketSubChunk : PsnChunk
107 | {
108 | ///
109 | /// Base constructor for info packet sub-chunk
110 | ///
111 | ///
112 | protected PsnInfoPacketSubChunk([CanBeNull] IEnumerable subChunks) : base(subChunks) { }
113 |
114 | ///
115 | /// Typed chunk ID for data packet sub-chunk
116 | ///
117 | public abstract PsnInfoPacketChunkId ChunkId { get; }
118 |
119 | ///
120 | public override ushort RawChunkId => (ushort)ChunkId;
121 | }
122 |
123 |
124 | ///
125 | /// Chunk containing header data for a PosiStageNet info packet
126 | ///
127 | [PublicAPI]
128 | public sealed class PsnInfoHeaderChunk : PsnInfoPacketSubChunk, IEquatable
129 | {
130 | internal const int StaticChunkAndHeaderLength = ChunkHeaderLength + StaticDataLength;
131 | internal const int StaticDataLength = 12;
132 |
133 | ///
134 | /// Constructs a header chunk for a info packet
135 | ///
136 | /// Time in microseconds at which the info contained in this packet is valid
137 | /// High byte of PosiStageNet version
138 | /// Low byte of PosiStageNet version
139 | /// Frame ID byte
140 | /// Frame packet count byte
141 | ///
142 | public PsnInfoHeaderChunk(ulong timestamp, int versionHigh, int versionLow, int frameId, int framePacketCount)
143 | : base(null)
144 | {
145 | TimeStamp = timestamp;
146 |
147 | if (versionHigh < 0 || versionHigh > 255)
148 | throw new ArgumentOutOfRangeException(nameof(versionHigh), "versionHigh must be between 0 and 255");
149 |
150 | VersionHigh = versionHigh;
151 |
152 | if (versionLow < 0 || versionLow > 255)
153 | throw new ArgumentOutOfRangeException(nameof(versionLow), "versionLow must be between 0 and 255");
154 |
155 | VersionLow = versionLow;
156 |
157 | if (frameId < 0 || frameId > 255)
158 | throw new ArgumentOutOfRangeException(nameof(frameId), "frameId must be between 0 and 255");
159 |
160 | FrameId = frameId;
161 |
162 | if (framePacketCount < 0 || framePacketCount > 255)
163 | throw new ArgumentOutOfRangeException(nameof(framePacketCount),
164 | "framePacketCount must be between 0 and 255");
165 |
166 | FramePacketCount = framePacketCount;
167 | }
168 |
169 | ///
170 | /// Time in microseconds at which the info contained in this packet is valid
171 | ///
172 | public ulong TimeStamp { get; }
173 |
174 |
175 | ///
176 | /// High byte of the PosiStageNet version of the packet
177 | ///
178 | public int VersionHigh { get; }
179 |
180 | ///
181 | /// Low byte of the PosiStageNet version of this packet
182 | ///
183 | public int VersionLow { get; }
184 |
185 | ///
186 | /// Frame ID value used for collating info from multiple packets
187 | ///
188 | public int FrameId { get; }
189 |
190 | ///
191 | /// Number of individual info packets for this
192 | ///
193 | public int FramePacketCount { get; }
194 |
195 | ///
196 | public override int DataLength => StaticDataLength;
197 |
198 | ///
199 | public override PsnInfoPacketChunkId ChunkId => PsnInfoPacketChunkId.PsnInfoHeader;
200 |
201 | ///
202 | public bool Equals(PsnInfoHeaderChunk other)
203 | {
204 | if (ReferenceEquals(null, other))
205 | return false;
206 | if (ReferenceEquals(this, other))
207 | return true;
208 | return base.Equals(other) && TimeStamp == other.TimeStamp && VersionHigh == other.VersionHigh
209 | && VersionLow == other.VersionLow && FrameId == other.FrameId
210 | && FramePacketCount == other.FramePacketCount;
211 | }
212 |
213 | ///
214 | public override XElement ToXml()
215 | {
216 | return new XElement(nameof(PsnInfoHeaderChunk),
217 | new XAttribute(nameof(TimeStamp), TimeStamp),
218 | new XAttribute(nameof(VersionHigh), VersionHigh),
219 | new XAttribute(nameof(FrameId), FrameId),
220 | new XAttribute(nameof(FramePacketCount), FramePacketCount));
221 | }
222 |
223 | ///
224 | public override bool Equals(object obj)
225 | {
226 | if (ReferenceEquals(null, obj))
227 | return false;
228 | if (ReferenceEquals(this, obj))
229 | return true;
230 | return obj.GetType() == GetType() && Equals((PsnInfoHeaderChunk)obj);
231 | }
232 |
233 | ///
234 | public override int GetHashCode()
235 | {
236 | unchecked
237 | {
238 | int hashCode = base.GetHashCode();
239 | hashCode = (hashCode * 397) ^ TimeStamp.GetHashCode();
240 | hashCode = (hashCode * 397) ^ VersionHigh;
241 | hashCode = (hashCode * 397) ^ VersionLow;
242 | hashCode = (hashCode * 397) ^ FrameId;
243 | hashCode = (hashCode * 397) ^ FramePacketCount;
244 | return hashCode;
245 | }
246 | }
247 |
248 | internal static PsnInfoHeaderChunk Deserialize(PsnChunkHeader chunkHeader, PsnBinaryReader reader)
249 | {
250 | ulong timeStamp = reader.ReadUInt64();
251 | int versionHigh = reader.ReadByte();
252 | int versionLow = reader.ReadByte();
253 | int frameId = reader.ReadByte();
254 | int framePacketCount = reader.ReadByte();
255 |
256 | return new PsnInfoHeaderChunk(timeStamp, versionHigh, versionLow, frameId, framePacketCount);
257 | }
258 |
259 | internal override void SerializeData(PsnBinaryWriter writer)
260 | {
261 | writer.Write(TimeStamp);
262 | writer.Write((byte)VersionHigh);
263 | writer.Write((byte)VersionLow);
264 | writer.Write((byte)FrameId);
265 | writer.Write((byte)FramePacketCount);
266 | }
267 | }
268 | }
--------------------------------------------------------------------------------
/src/DBDesign.PosiStageDotNet/Serialization/EndianBinaryWriter.cs:
--------------------------------------------------------------------------------
1 | // This file is part of PosiStageDotNet.
2 | //
3 | // PosiStageDotNet is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Lesser General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // PosiStageDotNet is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Lesser General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Lesser General Public License
14 | // along with PosiStageDotNet. If not, see .
15 |
16 | using System;
17 | using System.Diagnostics.CodeAnalysis;
18 | using System.IO;
19 | using System.Text;
20 |
21 | namespace DBDesign.PosiStageDotNet.Serialization
22 | {
23 | // Note: Based on code from MiscUtil r285 February 26th 2009 - http://www.yoda.arachsys.com/csharp/miscutil/
24 |
25 | ///
26 | /// Equivalent of , but with either endianness, depending on
27 | /// the EndianBitConverter it is constructed with.
28 | ///
29 | [SuppressMessage("ReSharper", "UnusedMember.Global")]
30 | [SuppressMessage("ReSharper", "MemberCanBeProtected.Global")]
31 | [SuppressMessage("ReSharper", "VirtualMemberNeverOverriden.Global")]
32 | [SuppressMessage("ReSharper", "UnusedMemberInSuper.Global")]
33 | internal class EndianBinaryWriter : IDisposable
34 | {
35 | ///
36 | /// Buffer used for temporary storage during conversion from primitives
37 | ///
38 | readonly byte[] _buffer = new byte[16];
39 |
40 | ///
41 | /// Buffer used for Write(char)
42 | ///
43 | readonly char[] _charBuffer = new char[1];
44 |
45 | ///
46 | /// Whether or not this writer has been disposed yet.
47 | ///
48 | bool _isDisposed;
49 |
50 |
51 |
52 | ///
53 | /// Constructs a new binary writer with the given bit converter, writing
54 | /// to the given stream, using UTF-8 encoding.
55 | ///
56 | /// Converter to use when writing data
57 | /// Stream to write data to
58 | public EndianBinaryWriter(EndianBitConverter bitConverter, Stream stream)
59 | : this(bitConverter, stream, Encoding.UTF8) { }
60 |
61 | ///
62 | /// Constructs a new binary writer with the given bit converter, writing
63 | /// to the given stream, using the given encoding.
64 | ///
65 | /// Converter to use when writing data
66 | /// Stream to write data to
67 | /// Encoding to use when writing character data
68 | /// Stream isn't writable
69 | public EndianBinaryWriter(EndianBitConverter bitConverter, Stream stream, Encoding encoding)
70 | {
71 | if (!stream.CanWrite)
72 | throw new ArgumentException("Stream isn't writable", nameof(stream));
73 |
74 | BaseStream = stream;
75 | BitConverter = bitConverter;
76 | Encoding = encoding;
77 | }
78 |
79 |
80 |
81 | ///
82 | /// The bit converter used to write values to the stream
83 | ///
84 | public EndianBitConverter BitConverter { get; }
85 |
86 | ///
87 | /// The encoding used to write strings
88 | ///
89 | public Encoding Encoding { get; }
90 |
91 | ///
92 | /// Gets the underlying stream of the EndianBinaryWriter.
93 | ///
94 | public Stream BaseStream { get; }
95 |
96 |
97 |
98 | ///
99 | /// Disposes of the underlying stream.
100 | ///
101 | public void Dispose()
102 | {
103 | if (_isDisposed)
104 | return;
105 |
106 | Flush();
107 | _isDisposed = true;
108 | ((IDisposable)BaseStream).Dispose();
109 | }
110 |
111 |
112 |
113 | ///
114 | /// Closes the writer, including the underlying stream.
115 | ///
116 | public void Close()
117 | {
118 | Dispose();
119 | }
120 |
121 | ///
122 | /// Flushes the underlying stream.
123 | ///
124 | /// An I/O error occurs.
125 | public virtual void Flush()
126 | {
127 | checkDisposed();
128 | BaseStream.Flush();
129 | }
130 |
131 | ///
132 | /// Seeks within the stream.
133 | ///
134 | /// Offset to seek to.
135 | /// Origin of seek operation.
136 | /// The stream does not support seeking, such as if the stream is constructed from a pipe or console output.
137 | /// An I/O error occurs.
138 | public virtual void Seek(long offset, SeekOrigin origin)
139 | {
140 | checkDisposed();
141 | BaseStream.Seek(offset, origin);
142 | }
143 |
144 | ///
145 | /// Writes a boolean value to the stream. 1 byte is written.
146 | ///
147 | /// The value to write
148 | public virtual void Write(bool value)
149 | {
150 | BitConverter.CopyBytes(value, _buffer, 0);
151 | writeInternal(_buffer, 1);
152 | }
153 |
154 | ///
155 | /// Writes a 16-bit signed integer to the stream, using the bit converter
156 | /// for this writer. 2 bytes are written.
157 | ///
158 | /// The value to write
159 | public virtual void Write(short value)
160 | {
161 | BitConverter.CopyBytes(value, _buffer, 0);
162 | writeInternal(_buffer, 2);
163 | }
164 |
165 | ///
166 | /// Writes a 32-bit signed integer to the stream, using the bit converter
167 | /// for this writer. 4 bytes are written.
168 | ///
169 | /// The value to write
170 | public virtual void Write(int value)
171 | {
172 | BitConverter.CopyBytes(value, _buffer, 0);
173 | writeInternal(_buffer, 4);
174 | }
175 |
176 | ///
177 | /// Writes a 64-bit signed integer to the stream, using the bit converter
178 | /// for this writer. 8 bytes are written.
179 | ///
180 | /// The value to write
181 | public virtual void Write(long value)
182 | {
183 | BitConverter.CopyBytes(value, _buffer, 0);
184 | writeInternal(_buffer, 8);
185 | }
186 |
187 | ///
188 | /// Writes a 16-bit unsigned integer to the stream, using the bit converter
189 | /// for this writer. 2 bytes are written.
190 | ///
191 | /// The value to write
192 | public virtual void Write(ushort value)
193 | {
194 | BitConverter.CopyBytes(value, _buffer, 0);
195 | writeInternal(_buffer, 2);
196 | }
197 |
198 | ///
199 | /// Writes a 32-bit unsigned integer to the stream, using the bit converter
200 | /// for this writer. 4 bytes are written.
201 | ///
202 | /// The value to write
203 | public virtual void Write(uint value)
204 | {
205 | BitConverter.CopyBytes(value, _buffer, 0);
206 | writeInternal(_buffer, 4);
207 | }
208 |
209 | ///
210 | /// Writes a 64-bit unsigned integer to the stream, using the bit converter
211 | /// for this writer. 8 bytes are written.
212 | ///
213 | /// The value to write
214 | public virtual void Write(ulong value)
215 | {
216 | BitConverter.CopyBytes(value, _buffer, 0);
217 | writeInternal(_buffer, 8);
218 | }
219 |
220 | ///
221 | /// Writes a single-precision floating-point value to the stream, using the bit converter
222 | /// for this writer. 4 bytes are written.
223 | ///
224 | /// The value to write
225 | public virtual void Write(float value)
226 | {
227 | BitConverter.CopyBytes(value, _buffer, 0);
228 | writeInternal(_buffer, 4);
229 | }
230 |
231 | ///
232 | /// Writes a double-precision floating-point value to the stream, using the bit converter
233 | /// for this writer. 8 bytes are written.
234 | ///
235 | /// The value to write
236 | public virtual void Write(double value)
237 | {
238 | BitConverter.CopyBytes(value, _buffer, 0);
239 | writeInternal(_buffer, 8);
240 | }
241 |
242 | ///
243 | /// Writes a decimal value to the stream, using the bit converter for this writer.
244 | /// 16 bytes are written.
245 | ///
246 | /// The value to write
247 | public virtual void Write(decimal value)
248 | {
249 | BitConverter.CopyBytes(value, _buffer, 0);
250 | writeInternal(_buffer, 16);
251 | }
252 |
253 | ///
254 | /// Writes a signed byte to the stream.
255 | ///
256 | /// The value to write
257 | public virtual void Write(byte value)
258 | {
259 | _buffer[0] = value;
260 | writeInternal(_buffer, 1);
261 | }
262 |
263 | ///
264 | /// Writes an unsigned byte to the stream.
265 | ///
266 | /// The value to write
267 | public virtual void Write(sbyte value)
268 | {
269 | _buffer[0] = unchecked((byte)value);
270 | writeInternal(_buffer, 1);
271 | }
272 |
273 | ///
274 | /// Writes an array of bytes to the stream.
275 | ///
276 | /// The values to write
277 | public virtual void Write(byte[] value)
278 | {
279 | writeInternal(value, value.Length);
280 | }
281 |
282 | ///
283 | /// Writes a portion of an array of bytes to the stream.
284 | ///
285 | /// An array containing the bytes to write
286 | /// The index of the first byte to write within the array
287 | /// The number of bytes to write
288 | public virtual void Write(byte[] value, int offset, int count)
289 | {
290 | checkDisposed();
291 | BaseStream.Write(value, offset, count);
292 | }
293 |
294 | ///
295 | /// Writes a single character to the stream, using the encoding for this writer.
296 | ///
297 | /// The value to write
298 | public virtual void Write(char value)
299 | {
300 | _charBuffer[0] = value;
301 | Write(_charBuffer);
302 | }
303 |
304 | ///
305 | /// Writes an array of characters to the stream, using the encoding for this writer.
306 | ///
307 | /// An array containing the characters to write
308 | public virtual void Write(char[] value)
309 | {
310 | checkDisposed();
311 | var data = Encoding.GetBytes(value, 0, value.Length);
312 | writeInternal(data, data.Length);
313 | }
314 |
315 | ///
316 | /// Writes a string to the stream, using the encoding for this writer.
317 | ///
318 | /// The value to write. Must not be null.
319 | /// value is null
320 | public virtual void Write(string value)
321 | {
322 | checkDisposed();
323 | var data = Encoding.GetBytes(value);
324 | Write7BitEncodedInt(data.Length);
325 | writeInternal(data, data.Length);
326 | }
327 |
328 | ///
329 | /// Writes a 7-bit encoded integer from the stream. This is stored with the least significant
330 | /// information first, with 7 bits of information per byte of value, and the top
331 | /// bit as a continuation flag.
332 | ///
333 | /// The 7-bit encoded integer to write to the stream
334 | public void Write7BitEncodedInt(int value)
335 | {
336 | checkDisposed();
337 |
338 | if (value < 0)
339 | throw new ArgumentOutOfRangeException(nameof(value), "Value must be greater than or equal to 0.");
340 |
341 | int index = 0;
342 | while (value >= 128)
343 | {
344 | _buffer[index++] = (byte)((value & 0x7f) | 0x80);
345 | value = value >> 7;
346 | ++index;
347 | }
348 | _buffer[index++] = (byte)value;
349 | BaseStream.Write(_buffer, 0, index);
350 | }
351 |
352 |
353 | ///
354 | /// Checks whether or not the writer has been disposed, throwing an exception if so.
355 | ///
356 | void checkDisposed()
357 | {
358 | if (_isDisposed)
359 | {
360 | throw new ObjectDisposedException("EndianBinaryWriter");
361 | }
362 | }
363 |
364 | ///
365 | /// Writes the specified number of bytes from the start of the given byte array,
366 | /// after checking whether or not the writer has been disposed.
367 | ///
368 | /// The array of bytes to write from
369 | /// The number of bytes to write
370 | private void writeInternal(byte[] bytes, int length)
371 | {
372 | checkDisposed();
373 | BaseStream.Write(bytes, 0, length);
374 | }
375 | }
376 | }
--------------------------------------------------------------------------------
/src/DBDesign.PosiStageDotNet/PsnTracker.cs:
--------------------------------------------------------------------------------
1 | // This file is part of PosiStageDotNet.
2 | //
3 | // PosiStageDotNet is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Lesser General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // PosiStageDotNet is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Lesser General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Lesser General Public License
14 | // along with PosiStageDotNet. If not, see .
15 |
16 | using System;
17 | using System.Collections.Generic;
18 | using DBDesign.PosiStageDotNet.Chunks;
19 | using JetBrains.Annotations;
20 |
21 | namespace DBDesign.PosiStageDotNet
22 | {
23 | ///
24 | /// Immutable class representing the entire state of a single PosiStageNet tracker
25 | ///
26 | [PublicAPI]
27 | public readonly struct PsnTracker : IEquatable
28 | {
29 | ///
30 | /// Creates a copy of a PsnTracker with updated values
31 | ///
32 | /// Tracker to use as clone source
33 | /// New tracker name value, or null to use source value
34 | /// If true, ignores new position value and clears value in clone
35 | /// New position value, or null to use source value
36 | /// If true, ignores new speed value and clears value in clone
37 | /// New speed value, or null to use source value
38 | /// If true, ignores new orientation value and clears value in clone
39 | /// New orientation value, or null to use source value
40 | /// If true, ignores new acceleration value and clears value in clone
41 | /// New acceleration value, or null to use source value
42 | /// If true, ignores new target position value and clears value in clone
43 | /// New target position value, or null to use source value
44 | /// If true, ignores new timestamp value and clears value in clone
45 | /// New timestamp value, or null to use source value
46 | /// If true, ignores new validity value and clears value in clone
47 | /// New validity value, or null to use source value
48 | public static PsnTracker Clone(PsnTracker sourceTracker,
49 | string trackerName = null,
50 | bool clearPosition = false, Tuple position = null,
51 | bool clearSpeed = false, Tuple speed = null,
52 | bool clearOrientation = false, Tuple orientation = null,
53 | bool clearAcceleration = false, Tuple acceleration = null,
54 | bool clearTargetPosition = false, Tuple targetPosition = null,
55 | bool clearTimestamp = false, ulong? timestamp = null,
56 | bool clearValidity = false, float? validity = null)
57 | {
58 | return new PsnTracker(sourceTracker.TrackerId,
59 | trackerName ?? sourceTracker.TrackerName,
60 | clearPosition ? null : position ?? sourceTracker.Position,
61 | clearSpeed ? null : speed ?? sourceTracker.Speed,
62 | clearOrientation ? null : orientation ?? sourceTracker.Orientation,
63 | clearAcceleration ? null : acceleration ?? sourceTracker.Acceleration,
64 | clearTargetPosition ? null : targetPosition ?? sourceTracker.TargetPosition,
65 | clearTimestamp ? null : timestamp ?? sourceTracker.Timestamp,
66 | clearValidity ? null : validity ?? sourceTracker.Validity);
67 | }
68 |
69 | ///
70 | /// Clone constructor provided to set properties which consumers of the library shouldn't be able to set (only relevant
71 | /// for packets received from a remote server)
72 | ///
73 | internal static PsnTracker CloneInternal(PsnTracker sourceTracker,
74 | bool clearTrackerName = false, string trackerName = null,
75 | bool clearDataLastReceived = false, ulong? dataLastReceived = null,
76 | bool clearInfoLastReceived = false, ulong? infoLastReceived = null,
77 | bool clearPosition = false, Tuple position = null,
78 | bool clearSpeed = false, Tuple speed = null,
79 | bool clearOrientation = false, Tuple orientation = null,
80 | bool clearAcceleration = false, Tuple acceleration = null,
81 | bool clearTargetPosition = false, Tuple targetPosition = null,
82 | bool clearTimestamp = false, ulong? timestamp = null,
83 | bool clearValidity = false, float? validity = null)
84 | {
85 | return new PsnTracker(sourceTracker.TrackerId,
86 | clearTrackerName ? null : trackerName ?? sourceTracker.TrackerName,
87 | clearDataLastReceived ? null : dataLastReceived ?? sourceTracker.DataLastReceived,
88 | clearInfoLastReceived ? null : infoLastReceived ?? sourceTracker.InfoLastReceived,
89 | clearPosition ? null : position ?? sourceTracker.Position,
90 | clearSpeed ? null : speed ?? sourceTracker.Speed,
91 | clearOrientation ? null : orientation ?? sourceTracker.Orientation,
92 | clearAcceleration ? null : acceleration ?? sourceTracker.Acceleration,
93 | clearTargetPosition ? null : targetPosition ?? sourceTracker.TargetPosition,
94 | clearTimestamp ? null : timestamp ?? sourceTracker.Timestamp,
95 | clearValidity ? null : validity ?? sourceTracker.Validity);
96 | }
97 |
98 | /// is .
99 | ///
100 | public PsnTracker(int trackerId, [CanBeNull] string trackerName,
101 | Tuple position = null,
102 | Tuple speed = null,
103 | Tuple orientation = null,
104 | Tuple acceleration = null,
105 | Tuple targetPosition = null,
106 | ulong? timestamp = null,
107 | float? validity = null)
108 | {
109 | if (trackerId < ushort.MinValue || trackerId > ushort.MaxValue)
110 | throw new ArgumentOutOfRangeException(nameof(trackerId), trackerId,
111 | $"trackerId must be in range {ushort.MinValue}-{ushort.MaxValue}");
112 |
113 | TrackerId = trackerId;
114 |
115 | TrackerName = trackerName;
116 |
117 | DataLastReceived = null;
118 | InfoLastReceived = null;
119 |
120 | Position = position;
121 | Speed = speed;
122 | Orientation = orientation;
123 | Acceleration = acceleration;
124 | TargetPosition = targetPosition;
125 | Timestamp = timestamp;
126 | Validity = validity;
127 | }
128 |
129 | ///
130 | /// Constructor provided to set properties which consumers of the library shouldn't be able to set (only relevant for
131 | /// packets received from a remote server)
132 | ///
133 | ///
134 | internal PsnTracker(int trackerId, string trackerName = null, ulong? dataLastReceived = null,
135 | ulong? infoLastReceived = null,
136 | Tuple position = null,
137 | Tuple speed = null,
138 | Tuple orientation = null,
139 | Tuple acceleration = null,
140 | Tuple targetPosition = null,
141 | ulong? timestamp = null,
142 | float? validity = null)
143 | {
144 | if (trackerId < ushort.MinValue || trackerId > ushort.MaxValue)
145 | throw new ArgumentOutOfRangeException(nameof(trackerId), trackerId,
146 | $"trackerId must be in range {ushort.MinValue}-{ushort.MaxValue}");
147 |
148 | TrackerId = trackerId;
149 | DataLastReceived = dataLastReceived;
150 | InfoLastReceived = infoLastReceived;
151 | TrackerName = trackerName;
152 |
153 | Position = position;
154 | Speed = speed;
155 | Orientation = orientation;
156 | Acceleration = acceleration;
157 | TargetPosition = targetPosition;
158 | Timestamp = timestamp;
159 | Validity = validity;
160 | }
161 |
162 | ///
163 | /// Unique ID of tracker
164 | ///
165 | public int TrackerId { get; }
166 |
167 | ///
168 | /// Name of tracker, or null if not yet received from remote server
169 | ///
170 | [CanBeNull]
171 | public string TrackerName { get; }
172 |
173 | ///
174 | /// Timestamp of last time data received from remote server, or null if tracker has not been received from remote
175 | /// server
176 | ///
177 | public ulong? DataLastReceived { get; }
178 |
179 | ///
180 | /// Timestamp of last time info received from remote server, or null if tracker has not been received from remote
181 | /// server
182 | ///
183 | public ulong? InfoLastReceived { get; }
184 |
185 |
186 | ///
187 | /// Tracker position vector in m
188 | ///
189 | public Tuple Position { get; }
190 |
191 | ///
192 | /// Tracker speed vector in m/s
193 | ///
194 | public Tuple Speed { get; }
195 |
196 | ///
197 | /// Tracker absolute orientation around rotation axis in radians
198 | ///
199 | public Tuple Orientation { get; }
200 |
201 | ///
202 | /// Tracker acceleration vector in m/s^2
203 | ///
204 | public Tuple Acceleration { get; }
205 |
206 | ///
207 | /// Tracker target position vector in m
208 | ///
209 | public Tuple TargetPosition { get; }
210 |
211 | ///
212 | /// Time in microseconds at which the data in this tracker was measured
213 | ///
214 | public ulong? Timestamp { get; }
215 |
216 | ///
217 | /// Tracker validity
218 | ///
219 | public float? Validity { get; }
220 |
221 |
222 |
223 | ///
224 | /// Creates a copy of this tracker with an updated tracker name value, or clears value if null
225 | ///
226 | [Pure]
227 | internal PsnTracker WithTrackerNameInternal([CanBeNull] string trackerName)
228 | => CloneInternal(this, clearTrackerName: trackerName == null, trackerName: trackerName);
229 |
230 | ///
231 | /// Creates a copy of this tracker with an updated data time stamp value
232 | ///
233 | [Pure]
234 | internal PsnTracker WithDataTimeStamp([CanBeNull] ulong? dataTimeStamp)
235 | => CloneInternal(this, clearDataLastReceived: dataTimeStamp == null, dataLastReceived: dataTimeStamp);
236 |
237 | ///
238 | /// Creates a copy of this tracker with an updated info time stamp value, or clears value if null
239 | ///
240 | [Pure]
241 | internal PsnTracker WithInfoTimeStamp([CanBeNull] ulong? infoTimeStamp)
242 | => CloneInternal(this, clearInfoLastReceived: infoTimeStamp == null, infoLastReceived: infoTimeStamp);
243 |
244 |
245 |
246 | ///
247 | /// Creates a copy of this tracker with an updated tracker name value
248 | ///
249 | [Pure]
250 | public PsnTracker WithTrackerName([NotNull] string trackerName) => Clone(this, trackerName: trackerName);
251 |
252 | ///
253 | /// Creates a copy of this tracker with an updated position value, or clears value if null
254 | ///
255 | [Pure]
256 | public PsnTracker WithPosition([CanBeNull] Tuple position)
257 | => Clone(this, clearPosition: position == null, position: position);
258 |
259 | ///
260 | /// Creates a copy of this tracker with an updated speed value, or clears value if null
261 | ///
262 | [Pure]
263 | public PsnTracker WithSpeed([CanBeNull] Tuple speed)
264 | => Clone(this, clearSpeed: speed == null, speed: speed);
265 |
266 | ///
267 | /// Creates a copy of this tracker with an updated orientation value, or clears value if null
268 | ///
269 | [Pure]
270 | public PsnTracker WithOrientation([CanBeNull] Tuple orientation)
271 | => Clone(this, clearOrientation: orientation == null, orientation: orientation);
272 |
273 | ///
274 | /// Creates a copy of this tracker with an updated acceleration value, or clears value if null
275 | ///
276 | [Pure]
277 | public PsnTracker WithAcceleration([CanBeNull] Tuple acceleration)
278 | => Clone(this, clearAcceleration: acceleration == null, acceleration: acceleration);
279 |
280 | ///
281 | /// Creates a copy of this tracker with an updated target position value, or clears value if null
282 | ///
283 | [Pure]
284 | public PsnTracker WithTargetPosition([CanBeNull] Tuple targetPosition)
285 | => Clone(this, clearTargetPosition: targetPosition == null, targetPosition: targetPosition);
286 |
287 | ///
288 | /// Creates a copy of this tracker with an updated timestamp value, or clears value if null
289 | ///
290 | [Pure]
291 | public PsnTracker WithTargetPosition([CanBeNull] ulong? timestamp)
292 | => Clone(this, clearTimestamp: timestamp == null, timestamp: timestamp);
293 |
294 | ///
295 | /// Creates a copy of this tracker with an updated validity value, or clears value if null
296 | ///
297 | [Pure]
298 | public PsnTracker WithValidity([CanBeNull] float? validity)
299 | => Clone(this, clearValidity: validity == null, validity: validity);
300 |
301 |
302 | ///
303 | /// Creates an enumerable of PsnDataTrackerChunks representing the data contained in this tracker
304 | ///
305 | public IEnumerable ToDataTrackerChunks()
306 | {
307 | if (Position != null)
308 | yield return new PsnDataTrackerPosChunk(Position.Item1, Position.Item2, Position.Item3);
309 |
310 | if (Speed != null)
311 | yield return new PsnDataTrackerSpeedChunk(Speed.Item1, Speed.Item2, Speed.Item3);
312 |
313 | if (Orientation != null)
314 | yield return new PsnDataTrackerOriChunk(Orientation.Item1, Orientation.Item2, Orientation.Item3);
315 |
316 | if (Acceleration != null)
317 | yield return new PsnDataTrackerAccelChunk(Acceleration.Item1, Acceleration.Item2, Acceleration.Item3);
318 |
319 | if (TargetPosition != null)
320 | yield return
321 | new PsnDataTrackerTrgtPosChunk(TargetPosition.Item1, TargetPosition.Item2, TargetPosition.Item3);
322 |
323 | if (Timestamp != null)
324 | yield return new PsnDataTrackerTimestampChunk(Timestamp.Value);
325 |
326 | if (Validity.HasValue)
327 | yield return new PsnDataTrackerStatusChunk(Validity.Value);
328 | }
329 |
330 | ///
331 | /// Creates an enumerable of PsnInfoTrackerChunks representing the info contained in this tracker
332 | ///
333 | public IEnumerable ToInfoTrackerChunks()
334 | {
335 | if (TrackerName != null)
336 | yield return new PsnInfoTrackerNameChunk(TrackerName);
337 | }
338 |
339 | ///
340 | public bool Equals(PsnTracker other)
341 | {
342 | return TrackerId == other.TrackerId && string.Equals(TrackerName, other.TrackerName)
343 | && Equals(Position, other.Position) && Equals(Speed, other.Speed)
344 | && Equals(Orientation, other.Orientation)
345 | && Equals(Acceleration, other.Acceleration) && Equals(TargetPosition, other.TargetPosition)
346 | && Validity.Equals(other.Validity);
347 | }
348 |
349 | ///
350 | public override bool Equals(object obj)
351 | {
352 | if (ReferenceEquals(null, obj))
353 | return false;
354 | return obj is PsnTracker tracker && Equals(tracker);
355 | }
356 |
357 | ///
358 | public override int GetHashCode()
359 | {
360 | unchecked
361 | {
362 | int hashCode = TrackerId;
363 | hashCode = (hashCode * 397) ^ (TrackerName?.GetHashCode() ?? 0);
364 | hashCode = (hashCode * 397) ^ (Position?.GetHashCode() ?? 0);
365 | hashCode = (hashCode * 397) ^ (Speed?.GetHashCode() ?? 0);
366 | hashCode = (hashCode * 397) ^ (Orientation?.GetHashCode() ?? 0);
367 | hashCode = (hashCode * 397) ^ (Acceleration?.GetHashCode() ?? 0);
368 | hashCode = (hashCode * 397) ^ (TargetPosition?.GetHashCode() ?? 0);
369 | hashCode = (hashCode * 397) ^ Validity.GetHashCode();
370 | return hashCode;
371 | }
372 | }
373 |
374 | ///
375 | public override string ToString()
376 | {
377 | return
378 | $"PsnTracker: Id {TrackerId}" +
379 | ", Name " + (TrackerName ?? "(Unknown)") +
380 | ", InfoLastReceived "
381 | + (InfoLastReceived.HasValue ? TimeSpan.FromMilliseconds(InfoLastReceived.Value).ToString() : "None") +
382 | ", DataLastReceived "
383 | + (DataLastReceived.HasValue ? TimeSpan.FromMilliseconds(DataLastReceived.Value).ToString() : "None") +
384 | (Position != null ? $", Position {Position}" : string.Empty) +
385 | (Speed != null ? $", Speed {Speed}" : string.Empty) +
386 | (Orientation != null ? $", Orientation {Orientation}" : string.Empty) +
387 | (Acceleration != null ? $", Acceleration {Acceleration}" : string.Empty) +
388 | (TargetPosition != null ? $", Target Position {TargetPosition}" : string.Empty) +
389 | (Timestamp != null ? $", Timestamp {TargetPosition}" : string.Empty) +
390 | (Validity != null ? $", Validity {Validity}" : string.Empty);
391 | }
392 | }
393 | }
--------------------------------------------------------------------------------
/src/DBDesign.PosiStageDotNet/Serialization/EndianBinaryReader.cs:
--------------------------------------------------------------------------------
1 | // This file is part of PosiStageDotNet.
2 | //
3 | // PosiStageDotNet is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Lesser General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // PosiStageDotNet is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Lesser General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Lesser General Public License
14 | // along with PosiStageDotNet. If not, see .
15 |
16 | using System;
17 | using System.Diagnostics.CodeAnalysis;
18 | using System.IO;
19 | using System.Text;
20 |
21 | namespace DBDesign.PosiStageDotNet.Serialization
22 | {
23 | // Note: Based on code from MiscUtil r285 February 26th 2009 - http://www.yoda.arachsys.com/csharp/miscutil/
24 |
25 | ///
26 | /// Equivalent of , but with either endianness, depending on
27 | /// the EndianBitConverter it is constructed with. No data is buffered in the
28 | /// reader; the client may seek within the stream at will.
29 | ///
30 | [SuppressMessage("ReSharper", "UnusedMember.Global")]
31 | [SuppressMessage("ReSharper", "UnusedMemberInSuper.Global")]
32 | [SuppressMessage("ReSharper", "MemberCanBeProtected.Global")]
33 | [SuppressMessage("ReSharper", "VirtualMemberNeverOverriden.Global")]
34 | internal class EndianBinaryReader : IDisposable
35 | {
36 | ///
37 | /// Buffer used for temporary storage before conversion into primitives
38 | ///
39 | private readonly byte[] _buffer = new byte[16];
40 |
41 | ///
42 | /// Buffer used for temporary storage when reading a single character
43 | ///
44 | private readonly char[] _charBuffer = new char[1];
45 |
46 | ///
47 | /// Decoder to use for string conversions.
48 | ///
49 | readonly Decoder _decoder;
50 |
51 | ///
52 | /// Minimum number of bytes used to encode a character
53 | ///
54 | private readonly int _minBytesPerChar;
55 |
56 | ///
57 | /// Whether or not this reader has been _disposed yet.
58 | ///
59 | bool _isDisposed;
60 |
61 |
62 | ///
63 | /// Equivalent of , but with either endianness, depending on
64 | /// the it is constructed with.
65 | ///
66 | /// Converter to use when reading data
67 | /// Stream to read data from
68 | public EndianBinaryReader(EndianBitConverter bitConverter, Stream stream)
69 | : this(bitConverter, stream, Encoding.UTF8) { }
70 |
71 | ///
72 | /// Constructs a new binary reader with the given bit converter, reading
73 | /// to the given stream, using the given encoding.
74 | ///
75 | /// Converter to use when reading data
76 | /// Stream to read data from
77 | /// Encoding to use when reading character data
78 | /// Stream isn't writable
79 | public EndianBinaryReader(EndianBitConverter bitConverter, Stream stream, Encoding encoding)
80 | {
81 | if (!stream.CanRead)
82 | throw new ArgumentException("Stream isn't writable", nameof(stream));
83 |
84 | BaseStream = stream;
85 | BitConverter = bitConverter;
86 | Encoding = encoding;
87 | _decoder = encoding.GetDecoder();
88 | _minBytesPerChar = 1;
89 |
90 | if (encoding is UnicodeEncoding)
91 | _minBytesPerChar = 2;
92 | }
93 |
94 |
95 |
96 | ///
97 | /// The bit converter used to read values from the stream
98 | ///
99 | public EndianBitConverter BitConverter { get; }
100 |
101 | ///
102 | /// The encoding used to read strings
103 | ///
104 | public Encoding Encoding { get; }
105 |
106 | ///
107 | /// Gets the underlying stream of the EndianBinaryReader.
108 | ///
109 | public Stream BaseStream { get; }
110 |
111 |
112 | ///
113 | /// Disposes of the underlying stream.
114 | ///
115 | public void Dispose()
116 | {
117 | if (_isDisposed)
118 | return;
119 |
120 | _isDisposed = true;
121 | ((IDisposable)BaseStream).Dispose();
122 | }
123 |
124 |
125 |
126 | ///
127 | /// Closes the reader, including the underlying stream..
128 | ///
129 | public void Close()
130 | {
131 | Dispose();
132 | }
133 |
134 | ///
135 | /// Seeks within the stream.
136 | ///
137 | /// Offset to seek to.
138 | /// Origin of seek operation.
139 | /// An I/O error occurs.
140 | /// The stream does not support seeking, such as if the stream is constructed from a pipe or console output.
141 | public virtual void Seek(long offset, SeekOrigin origin)
142 | {
143 | checkDisposed();
144 | BaseStream.Seek(offset, origin);
145 | }
146 |
147 | ///
148 | /// Reads a single byte from the stream.
149 | ///
150 | /// The byte read
151 | public virtual byte ReadByte()
152 | {
153 | readInternal(_buffer, 1);
154 | return _buffer[0];
155 | }
156 |
157 | ///
158 | /// Reads a single signed byte from the stream.
159 | ///
160 | /// The byte read
161 | public virtual sbyte ReadSByte()
162 | {
163 | readInternal(_buffer, 1);
164 | return unchecked((sbyte)_buffer[0]);
165 | }
166 |
167 | ///
168 | /// Reads a boolean from the stream. 1 byte is read.
169 | ///
170 | /// The boolean read
171 | public virtual bool ReadBoolean()
172 | {
173 | readInternal(_buffer, 1);
174 | return BitConverter.ToBoolean(_buffer, 0);
175 | }
176 |
177 | ///
178 | /// Reads a 16-bit signed integer from the stream, using the bit converter
179 | /// for this reader. 2 bytes are read.
180 | ///
181 | /// The 16-bit integer read
182 | public virtual short ReadInt16()
183 | {
184 | readInternal(_buffer, 2);
185 | return BitConverter.ToInt16(_buffer, 0);
186 | }
187 |
188 | ///
189 | /// Reads a 32-bit signed integer from the stream, using the bit converter
190 | /// for this reader. 4 bytes are read.
191 | ///
192 | /// The 32-bit integer read
193 | public virtual int ReadInt32()
194 | {
195 | readInternal(_buffer, 4);
196 | return BitConverter.ToInt32(_buffer, 0);
197 | }
198 |
199 | ///
200 | /// Reads a 64-bit signed integer from the stream, using the bit converter
201 | /// for this reader. 8 bytes are read.
202 | ///
203 | /// The 64-bit integer read
204 | public virtual long ReadInt64()
205 | {
206 | readInternal(_buffer, 8);
207 | return BitConverter.ToInt64(_buffer, 0);
208 | }
209 |
210 | ///
211 | /// Reads a 16-bit unsigned integer from the stream, using the bit converter
212 | /// for this reader. 2 bytes are read.
213 | ///
214 | /// The 16-bit unsigned integer read
215 | public virtual ushort ReadUInt16()
216 | {
217 | readInternal(_buffer, 2);
218 | return BitConverter.ToUInt16(_buffer, 0);
219 | }
220 |
221 | ///
222 | /// Reads a 32-bit unsigned integer from the stream, using the bit converter
223 | /// for this reader. 4 bytes are read.
224 | ///
225 | /// The 32-bit unsigned integer read
226 | public virtual uint ReadUInt32()
227 | {
228 | readInternal(_buffer, 4);
229 | return BitConverter.ToUInt32(_buffer, 0);
230 | }
231 |
232 | ///
233 | /// Reads a 64-bit unsigned integer from the stream, using the bit converter
234 | /// for this reader. 8 bytes are read.
235 | ///
236 | /// The 64-bit unsigned integer read
237 | public virtual ulong ReadUInt64()
238 | {
239 | readInternal(_buffer, 8);
240 | return BitConverter.ToUInt64(_buffer, 0);
241 | }
242 |
243 | ///
244 | /// Reads a single-precision floating-point value from the stream, using the bit converter
245 | /// for this reader. 4 bytes are read.
246 | ///
247 | /// The floating point value read
248 | public virtual float ReadSingle()
249 | {
250 | readInternal(_buffer, 4);
251 | return BitConverter.ToSingle(_buffer, 0);
252 | }
253 |
254 | ///
255 | /// Reads a double-precision floating-point value from the stream, using the bit converter
256 | /// for this reader. 8 bytes are read.
257 | ///
258 | /// The floating point value read
259 | public virtual double ReadDouble()
260 | {
261 | readInternal(_buffer, 8);
262 | return BitConverter.ToDouble(_buffer, 0);
263 | }
264 |
265 | ///
266 | /// Reads a decimal value from the stream, using the bit converter
267 | /// for this reader. 16 bytes are read.
268 | ///
269 | /// The decimal value read
270 | public virtual decimal ReadDecimal()
271 | {
272 | readInternal(_buffer, 16);
273 | return BitConverter.ToDecimal(_buffer, 0);
274 | }
275 |
276 | ///
277 | /// Reads a single character from the stream, using the character encoding for
278 | /// this reader. If no characters have been fully read by the time the stream ends,
279 | /// -1 is returned.
280 | ///
281 | /// The character read, or -1 for end of stream.
282 | public virtual int Read()
283 | {
284 | int charsRead = Read(_charBuffer, 0, 1);
285 | if (charsRead == 0)
286 | return -1;
287 |
288 | return _charBuffer[0];
289 | }
290 |
291 | ///
292 | /// Reads the specified number of characters into the given _buffer, starting at
293 | /// the given index.
294 | ///
295 | /// The _buffer to copy data into
296 | /// The first index to copy data into
297 | /// The number of characters to read
298 | ///
299 | /// The number of characters actually read. This will only be less than
300 | /// the requested number of characters if the end of the stream is reached.
301 | ///
302 | public virtual int Read(char[] data, int index, int count)
303 | {
304 | checkDisposed();
305 |
306 | if (index < 0)
307 | throw new ArgumentOutOfRangeException(nameof(index));
308 |
309 | if (count < 0)
310 | throw new ArgumentOutOfRangeException(nameof(index));
311 |
312 | if (count + index > data.Length)
313 | throw new ArgumentException(
314 | "Not enough space in buffer for specified number of characters starting at specified index");
315 |
316 | int read = 0;
317 | bool firstTime = true;
318 |
319 | // Use the normal _buffer if we're only reading a small amount, otherwise
320 | // use at most 4K at a time.
321 | var byteBuffer = _buffer;
322 |
323 | if (byteBuffer.Length < count * _minBytesPerChar)
324 | byteBuffer = new byte[4096];
325 |
326 | while (read < count)
327 | {
328 | int amountToRead;
329 | // First time through we know we haven't previously read any data
330 | if (firstTime)
331 | {
332 | amountToRead = count * _minBytesPerChar;
333 | firstTime = false;
334 | }
335 | // After that we can only assume we need to fully read "chars left -1" characters
336 | // and a single byte of the character we may be in the middle of
337 | else
338 | {
339 | amountToRead = (count - read - 1) * _minBytesPerChar + 1;
340 | }
341 |
342 | if (amountToRead > byteBuffer.Length)
343 | amountToRead = byteBuffer.Length;
344 |
345 | int bytesRead = tryReadInternal(byteBuffer, amountToRead);
346 | if (bytesRead == 0)
347 | return read;
348 |
349 | int decoded = _decoder.GetChars(byteBuffer, 0, bytesRead, data, index);
350 | read += decoded;
351 | index += decoded;
352 | }
353 |
354 | return read;
355 | }
356 |
357 | ///
358 | /// Reads the specified number of bytes into the given _buffer, starting at
359 | /// the given index.
360 | ///
361 | /// The _buffer to copy data into
362 | /// The first index to copy data into
363 | /// The number of bytes to read
364 | ///
365 | /// The number of bytes actually read. This will only be less than
366 | /// the requested number of bytes if the end of the stream is reached.
367 | ///
368 | public virtual int Read(byte[] buffer, int index, int count)
369 | {
370 | checkDisposed();
371 |
372 | if (index < 0)
373 | throw new ArgumentOutOfRangeException(nameof(index));
374 |
375 | if (count < 0)
376 | throw new ArgumentOutOfRangeException(nameof(index));
377 |
378 | if (count + index > buffer.Length)
379 | throw new ArgumentException(
380 | "Not enough space in buffer for specified number of bytes starting at specified index");
381 |
382 | int read = 0;
383 | while (count > 0)
384 | {
385 | int block = BaseStream.Read(buffer, index, count);
386 | if (block == 0)
387 | return read;
388 |
389 | index += block;
390 | read += block;
391 | count -= block;
392 | }
393 |
394 | return read;
395 | }
396 |
397 | ///
398 | /// Reads the specified number of bytes, returning them in a new byte array.
399 | /// If not enough bytes are available before the end of the stream, this
400 | /// method will return what is available.
401 | ///
402 | /// The number of bytes to read
403 | /// The bytes read
404 | public virtual byte[] ReadBytes(int count)
405 | {
406 | checkDisposed();
407 |
408 | if (count < 0)
409 | throw new ArgumentOutOfRangeException(nameof(count));
410 |
411 | var ret = new byte[count];
412 | int index = 0;
413 | while (index < count)
414 | {
415 | int read = BaseStream.Read(ret, index, count - index);
416 | // Stream has finished half way through. That's fine, return what we've got.
417 | if (read == 0)
418 | {
419 | var copy = new byte[index];
420 | Buffer.BlockCopy(ret, 0, copy, 0, index);
421 | return copy;
422 | }
423 |
424 | index += read;
425 | }
426 |
427 | return ret;
428 | }
429 |
430 | ///
431 | /// Reads the specified number of bytes, returning them in a new byte array.
432 | /// If not enough bytes are available before the end of the stream, this
433 | /// method will throw an IOException.
434 | ///
435 | /// The number of bytes to read
436 | /// The bytes read
437 | public virtual byte[] ReadBytesOrThrow(int count)
438 | {
439 | var ret = new byte[count];
440 | readInternal(ret, count);
441 | return ret;
442 | }
443 |
444 | ///
445 | /// Reads a 7-bit encoded integer from the stream. This is stored with the least significant
446 | /// information first, with 7 bits of information per byte of value, and the top
447 | /// bit as a continuation flag. This method is not affected by the endianness
448 | /// of the bit converter.
449 | ///
450 | /// The 7-bit encoded integer read from the stream.
451 | public int Read7BitEncodedInt()
452 | {
453 | checkDisposed();
454 |
455 | int ret = 0;
456 | for (int shift = 0; shift < 35; shift += 7)
457 | {
458 | int b = BaseStream.ReadByte();
459 | if (b == -1)
460 | throw new EndOfStreamException();
461 |
462 | ret = ret | ((b & 0x7f) << shift);
463 | if ((b & 0x80) == 0)
464 | return ret;
465 | }
466 | // Still haven't seen a byte with the high bit unset? Dodgy data.
467 | throw new IOException("Invalid 7-bit encoded integer in stream.");
468 | }
469 |
470 | ///
471 | /// Reads a 7-bit encoded integer from the stream. This is stored with the most significant
472 | /// information first, with 7 bits of information per byte of value, and the top
473 | /// bit as a continuation flag. This method is not affected by the endianness
474 | /// of the bit converter.
475 | ///
476 | /// The 7-bit encoded integer read from the stream.
477 | public int ReadBigEndian7BitEncodedInt()
478 | {
479 | checkDisposed();
480 |
481 | int ret = 0;
482 | for (int i = 0; i < 5; i++)
483 | {
484 | int b = BaseStream.ReadByte();
485 | if (b == -1)
486 | throw new EndOfStreamException();
487 |
488 | ret = (ret << 7) | (b & 0x7f);
489 | if ((b & 0x80) == 0)
490 | return ret;
491 | }
492 | // Still haven't seen a byte with the high bit unset? Dodgy data.
493 | throw new IOException("Invalid 7-bit encoded integer in stream.");
494 | }
495 |
496 | ///
497 | /// Reads a length-prefixed string from the stream, using the encoding for this reader.
498 | /// A 7-bit encoded integer is first read, which specifies the number of bytes
499 | /// to read from the stream. These bytes are then converted into a string with
500 | /// the encoding for this reader.
501 | ///
502 | /// The string read from the stream.
503 | public virtual string ReadString()
504 | {
505 | int bytesToRead = Read7BitEncodedInt();
506 |
507 | var data = new byte[bytesToRead];
508 | readInternal(data, bytesToRead);
509 | return Encoding.GetString(data, 0, data.Length);
510 | }
511 |
512 |
513 | ///
514 | /// Checks whether or not the reader has been _disposed, throwing an exception if so.
515 | ///
516 | private void checkDisposed()
517 | {
518 | if (_isDisposed)
519 | throw new ObjectDisposedException("EndianBinaryReader");
520 | }
521 |
522 | ///
523 | /// Reads the given number of bytes from the stream, throwing an exception
524 | /// if they can't all be read.
525 | ///
526 | /// Buffer to read into
527 | /// Number of bytes to read
528 | private void readInternal(byte[] data, int size)
529 | {
530 | checkDisposed();
531 | int index = 0;
532 | while (index < size)
533 | {
534 | int read = BaseStream.Read(data, index, size - index);
535 | if (read == 0)
536 | throw new EndOfStreamException(
537 | $"End of stream reached with {size - index} byte{(size - index == 1 ? "s" : "")} left to read.");
538 |
539 | index += read;
540 | }
541 | }
542 |
543 | ///
544 | /// Reads the given number of bytes from the stream if possible, returning
545 | /// the number of bytes actually read, which may be less than requested if
546 | /// (and only if) the end of the stream is reached.
547 | ///
548 | /// Buffer to read into
549 | /// Number of bytes to read
550 | /// Number of bytes actually read
551 | private int tryReadInternal(byte[] data, int size)
552 | {
553 | checkDisposed();
554 | int index = 0;
555 | while (index < size)
556 | {
557 | int read = BaseStream.Read(data, index, size - index);
558 | if (read == 0)
559 | return index;
560 |
561 | index += read;
562 | }
563 |
564 | return index;
565 | }
566 | }
567 | }
--------------------------------------------------------------------------------