Skip to content

Commit 2ca1958

Browse files
committed
F Improved JPEG validation #19
1 parent a779bec commit 2ca1958

3 files changed

Lines changed: 50 additions & 25 deletions

File tree

src/Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<Nullable>disable</Nullable>
66
<ImplicitUsings>disable</ImplicitUsings>
77
<Title>$(ProjectName)</Title>
8-
<Version>0.1.0</Version>
8+
<Version>0.1.2</Version>
99
<Authors>Lukas Volf</Authors>
1010
<Copyright>MIT</Copyright>
1111
<PackageProjectUrl>https://github.com/jimm98y/SharpRealTimeStreaming</PackageProjectUrl>

src/RTSPServerApp/RTSPServerWorker.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,14 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
154154
}
155155
}
156156

157-
rtspVideoTrack.FeedInRawSamples((uint)(videoIndex * videoSampleDuration), (List<byte[]>)videoTrack[videoIndex++ % videoTrack.Count]);
157+
try
158+
{
159+
rtspVideoTrack.FeedInRawSamples((uint)(videoIndex * videoSampleDuration), (List<byte[]>)videoTrack[videoIndex++ % videoTrack.Count]);
160+
}
161+
catch(Exception ex)
162+
{
163+
_logger.LogError(ex, $"FeedInRawSamples failed: {ex.Message}");
164+
}
158165

159166
if (videoIndex % videoTrack.Count == 0)
160167
{
@@ -179,14 +186,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
179186
};
180187
}
181188

182-
try
183-
{
184-
_server.StartListen();
185-
}
186-
catch (Exception ex)
187-
{
188-
_logger.LogError(ex, ex.ToString());
189-
}
189+
_server.StartListen();
190190

191191
_logger.LogInformation($"RTSP URL is rtsp://{userName}:{password}@{hostName}:{port}");
192192

src/SharpRTSPServer/MJpegTrack.cs

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
using System.Buffers;
33
using System.Buffers.Binary;
44
using System.Collections.Generic;
5-
using System.IO;
65
using System.Linq;
76
using System.Text;
87

@@ -213,11 +212,23 @@ public JpgComponent(byte id, byte samp, byte qt)
213212
}
214213
}
215214

216-
private static (int width, int height, int bpp, byte type) ParseJpeg(Span<byte> binaryReader, out ReadOnlySpan<byte> first, out ReadOnlySpan<byte> second, out Span<byte> retReader)
215+
/// <summary>
216+
/// Parse JPEG image and return the width, height, bit depth and type.
217+
/// </summary>
218+
/// <param name="jpegImage">Span holding the JPEG image buffer. <see cref="Span{Byte}"/>.</param>
219+
/// <param name="firstQuantizationTable">The first Quantization Table or an empty span when not present.</param>
220+
/// <param name="secondQuantizationTable">The second Quantization Table or an empty span when not present.</param>
221+
/// <param name="imageData">Image data.</param>
222+
/// <returns>JPEG image width, height, bit depth and the RTP payload type.</returns>
223+
/// <exception cref="ArgumentException">Thrown when jpegImage data do not contain a valid JPEG image.</exception>
224+
/// <exception cref="NotSupportedException">Thrown when JPEG image passed to this function does not meet the criteria for being used in RTP without re-encoding.
225+
/// The criteria are: image dimensions must be less than or equal to 2040 x 2040 pixels and chroma subsampling must be set to 4:2:0 or 4:2:2.
226+
/// </exception>
227+
public static (int width, int height, int bpp, byte type) ParseJpeg(Span<byte> jpegImage, out ReadOnlySpan<byte> firstQuantizationTable, out ReadOnlySpan<byte> secondQuantizationTable, out Span<byte> imageData)
217228
{
218-
first = ReadOnlySpan<byte>.Empty;
219-
second = ReadOnlySpan<byte>.Empty;
220-
Span<byte> br = binaryReader;
229+
firstQuantizationTable = ReadOnlySpan<byte>.Empty;
230+
secondQuantizationTable = ReadOnlySpan<byte>.Empty;
231+
Span<byte> br = jpegImage;
221232
bool isDriPresent = false;
222233

223234
// JPG magic bytes
@@ -233,7 +244,7 @@ private static (int width, int height, int bpp, byte type) ParseJpeg(Span<byte>
233244
// Start-Of-Frame (SOF) has 4 possible values
234245
if (br[1] == 0xc0 || br[1] == 0xc1 || br[1] == 0xc2 || br[1] == 0xc3)
235246
{
236-
retReader = br;
247+
imageData = br;
237248

238249
br = br.Slice(2);
239250
br = br.Slice(2);
@@ -250,6 +261,11 @@ private static (int width, int height, int bpp, byte type) ParseJpeg(Span<byte>
250261
int width = (br[0] << 8) | br[1];
251262
br = br.Slice(2);
252263

264+
if (width > 2040 || height > 2040)
265+
{
266+
throw new NotSupportedException("JPEG image is too large. Maximum image dimensions allowed in JPEG over RTP are 2040x2040 pixels.");
267+
}
268+
253269
int numComponents = br[0];
254270
br = br.Slice(1);
255271

@@ -269,6 +285,8 @@ private static (int width, int height, int bpp, byte type) ParseJpeg(Span<byte>
269285
components.Add(component);
270286
}
271287

288+
// https://datatracker.ietf.org/doc/html/rfc2435#section-4.1: supported types are only 0, 1 and 64, 65
289+
// JPEG must be re-encoded with chroma subsampling 4:2:0 (0x22,0x11,0x11) or 4:2:2 (0x21,0x11,0x11).
272290
var sortedComponents = components.OrderBy(x => x.id);
273291
byte type = 0;
274292
if (sortedComponents.First().samp == 0x21)
@@ -281,13 +299,20 @@ private static (int width, int height, int bpp, byte type) ParseJpeg(Span<byte>
281299
}
282300
else
283301
{
284-
// https://datatracker.ietf.org/doc/html/rfc2435#section-4.1: supported types are only 0, 1 and 64, 65
285-
// JPEG must be re-encoded with chroma subsampling 4:2:0 (0x22) or 4:2:2 (0x21).
286-
throw new NotSupportedException("Unsupported chroma subsampling. Please re-encode the JPEG with chroma subsampling 4:2:0 (0x22) or 4:2:2 (0x21).");
302+
throw new NotSupportedException($"Unsupported chroma subsampling 0x{sortedComponents.First().samp:X2}. Supported chroma subsampling values are 4:2:0 (0x22,0x11,0x11) or 4:2:2 (0x21,0x11,0x11).");
303+
}
304+
305+
foreach(var comp in sortedComponents.Skip(1))
306+
{
307+
if (comp.samp != 0x11)
308+
{
309+
throw new NotSupportedException($"Unsupported chroma subsampling 0x{comp.samp:X2}. Supported chroma subsampling values are 4:2:0 (0x22,0x11,0x11) or 4:2:2 (0x21,0x11,0x11).");
310+
}
287311
}
288312

289-
if(isDriPresent)
313+
if (isDriPresent)
290314
{
315+
// when restart marker is present, we shift the type by 64
291316
type += 64;
292317
}
293318

@@ -307,12 +332,12 @@ private static (int width, int height, int bpp, byte type) ParseJpeg(Span<byte>
307332
{
308333
int matrix_length = chunkLength - 2;
309334
var matrix = br.Slice(0, matrix_length);
310-
if (first.IsEmpty)
311-
first = matrix;
312-
else if (second.IsEmpty)
313-
second = matrix;
335+
if (firstQuantizationTable.IsEmpty)
336+
firstQuantizationTable = matrix;
337+
else if (secondQuantizationTable.IsEmpty)
338+
secondQuantizationTable = matrix;
314339
else
315-
throw new InvalidOperationException("Error: More than 2 quantization tables in JPEG image");
340+
throw new NotSupportedException("Invalid JPEG image. More than 2 quantization tables found.");
316341
}
317342

318343
// restart marker

0 commit comments

Comments
 (0)