tgstation-server 6.12.0
The /tg/station 13 server suite
Loading...
Searching...
No Matches
Chunker.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Generic;
3using System.Threading;
4using System.Threading.Tasks;
5
6using Microsoft.Extensions.Logging;
7
8using Newtonsoft.Json;
9
11{
15 abstract class Chunker
16 {
20 protected ILogger<Chunker> Logger { get; }
21
25 protected uint NextPayloadId
26 {
27 get
28 {
29 // 0 is special, since BYOND doesn't use it we reserve it for if we have to send a chunked topic request immediately upon reattaching
30 // Otherwise, jump ahead a bunch compared to what was last used/seen, so we don't accidentally clash
31 lock (chunkSets)
32 return highestSeenPayloadId == 0 ? 0 : highestSeenPayloadId + 20;
33 }
34 }
35
40 readonly Dictionary<uint, Tuple<ChunkSetInfo, string[]>> chunkSets;
41
46
51 protected Chunker(ILogger<Chunker> logger)
52 {
53 Logger = logger ?? throw new ArgumentNullException(nameof(logger));
54 chunkSets = new Dictionary<uint, Tuple<ChunkSetInfo, string[]>>();
55 }
56
67 protected async ValueTask<TResponse?> ProcessChunk<TCommunication, TResponse>(
68 Func<TCommunication, CancellationToken, ValueTask<TResponse?>> completionCallback,
69 Func<string, TResponse?> chunkErrorCallback,
70 ChunkData? chunk,
71 CancellationToken cancellationToken)
72 where TCommunication : class
73 where TResponse : IMissingPayloadsCommunication, new()
74 {
75 if (chunk == null)
76 return chunkErrorCallback("Missing chunk!");
77
78 if (!chunk.PayloadId.HasValue)
79 return chunkErrorCallback("Missing chunk payloadId!");
80
81 if (!chunk.SequenceId.HasValue)
82 return chunkErrorCallback("Missing chunk sequenceId!");
83
84 if (chunk.Payload == null)
85 return chunkErrorCallback("Missing chunk payload!");
86
87 ChunkSetInfo requestInfo;
88 string[] payloads;
89 lock (chunkSets)
90 {
91 highestSeenPayloadId = Math.Max(chunk.PayloadId.Value, highestSeenPayloadId);
92 if (!chunkSets.TryGetValue(chunk.PayloadId.Value, out var tuple))
93 {
94 // first time seeing this payload
95 if (chunk.TotalChunks == 0)
96 return chunkErrorCallback("Receieved chunked request with 0 totalChunks!");
97
98 tuple = Tuple.Create<ChunkSetInfo, string[]>(chunk, new string[chunk.TotalChunks]);
99 chunkSets.Add(chunk.PayloadId.Value, tuple);
100 }
101
102 requestInfo = tuple.Item1;
103 payloads = tuple.Item2;
104
105 Logger.LogTrace("Received chunk P{payloadId}: {sequenceId}/{totalChunks}", requestInfo.PayloadId, chunk.SequenceId + 1, requestInfo.TotalChunks);
106
107 if (chunk.TotalChunks != requestInfo.TotalChunks)
108 {
109 chunkSets.Remove(requestInfo.PayloadId!.Value);
110 return chunkErrorCallback("Received differing total chunks for same payloadId! Invalidating payloadId!");
111 }
112
113 if (payloads[chunk.SequenceId.Value] != null && payloads[chunk.SequenceId.Value] != chunk.Payload)
114 {
115 chunkSets.Remove(requestInfo.PayloadId!.Value);
116 return chunkErrorCallback("Received differing payload for same sequenceId! Invalidating payloadId!");
117 }
118
119 payloads[chunk.SequenceId.Value] = chunk.Payload;
120 var missingPayloads = new List<uint>();
121 for (var i = 0U; i < payloads.Length; ++i)
122 if (payloads[i] == null)
123 missingPayloads.Add(i);
124
125 if (missingPayloads.Count > 0)
126 return new TResponse
127 {
128 MissingChunks = missingPayloads,
129 };
130
131 Logger.LogTrace("Received all chunks for P{payloadId}, processing request...", requestInfo.PayloadId);
132 chunkSets.Remove(requestInfo.PayloadId!.Value);
133 }
134
135 TCommunication? completedCommunication;
136 var fullCommunicationJson = String.Concat(payloads);
137 try
138 {
139 completedCommunication = JsonConvert.DeserializeObject<TCommunication>(fullCommunicationJson, DMApiConstants.SerializerSettings);
140 }
141 catch (Exception ex)
142 {
143 Logger.LogDebug(ex, "Bad chunked communication for payload {payloadId}!", requestInfo.PayloadId);
144 completedCommunication = null;
145 }
146
147 if (completedCommunication == null)
148 return chunkErrorCallback("Chunked request completed with bad JSON!");
149
150 return await completionCallback(completedCommunication, cancellationToken);
151 }
152 }
153}
A packet of a split serialized set of data.
Definition ChunkData.cs:7
Information about a chunked bridge request.
uint? PayloadId
The ID of the full request to differentiate different chunkings.Nullable to prevent default value omi...
uint TotalChunks
The total number of chunks in the request.
Class that deserializes chunked interop payloads.
Definition Chunker.cs:16
readonly Dictionary< uint, Tuple< ChunkSetInfo, string[]> > chunkSets
The cache of chunked communications.
Definition Chunker.cs:40
ILogger< Chunker > Logger
The ILogger for the Chunker.
Definition Chunker.cs:20
uint NextPayloadId
Gets a payload ID for use in a new ChunkSetInfo.
Definition Chunker.cs:26
async ValueTask< TResponse?> ProcessChunk< TCommunication, TResponse >(Func< TCommunication, CancellationToken, ValueTask< TResponse?> > completionCallback, Func< string, TResponse?> chunkErrorCallback, ChunkData? chunk, CancellationToken cancellationToken)
Process a given chunk .
Definition Chunker.cs:67
uint highestSeenPayloadId
The highest payload ID value seen.
Definition Chunker.cs:45
Chunker(ILogger< Chunker > logger)
Initializes a new instance of the Chunker class.
Definition Chunker.cs:51
Constants used for communication with the DMAPI.
static readonly JsonSerializerSettings SerializerSettings
JsonSerializerSettings for use when communicating with the DMAPI.