Skip to content

Commit 1030701

Browse files
authored
Merge pull request #526 from Shane32/ecc_mode_mismatch
ECC code mismatch detection
2 parents 276e4ec + d65a5f0 commit 1030701

File tree

8 files changed

+115
-6
lines changed

8 files changed

+115
-6
lines changed

QRCoder/PayloadGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public static class PayloadGenerator
1515
public abstract class Payload
1616
{
1717
public virtual int Version { get { return -1; } }
18-
public virtual QRCodeGenerator.ECCLevel EccLevel { get { return QRCodeGenerator.ECCLevel.M; } }
18+
public virtual QRCodeGenerator.ECCLevel EccLevel { get { return QRCodeGenerator.ECCLevel.Default; } }
1919
public virtual QRCodeGenerator.EciMode EciMode { get { return QRCodeGenerator.EciMode.Default; } }
2020
public abstract override string ToString();
2121
}

QRCoder/QRCodeGenerator.ECCLevel.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,35 @@ public partial class QRCodeGenerator
88
/// </summary>
99
public enum ECCLevel
1010
{
11+
/// <summary>
12+
/// Default error correction level, which will select Level M (Medium) unless otherwise specified by the payload.
13+
/// Level M allows approximately 15% of data to be recovered, offering a balance between data capacity and error recovery.
14+
/// </summary>
15+
Default = -1,
16+
1117
/// <summary>
1218
/// Level L: Low error correction (approximately 7% of data can be recovered).
1319
/// This level allows the highest data density.
1420
/// </summary>
15-
L,
21+
L = 0,
1622

1723
/// <summary>
1824
/// Level M: Medium error correction (approximately 15% of data can be recovered).
1925
/// Offers a balance between data capacity and error recovery.
2026
/// </summary>
21-
M,
27+
M = 1,
2228

2329
/// <summary>
2430
/// Level Q: Quartile error correction (approximately 25% of data can be recovered).
2531
/// More robust error correction at the cost of reduced data capacity.
2632
/// </summary>
27-
Q,
33+
Q = 2,
2834

2935
/// <summary>
3036
/// Level H: High error correction (approximately 30% of data can be recovered).
3137
/// Provides the highest level of error recovery, ideal for environments with high risk of data loss.
3238
/// </summary>
33-
H
39+
H = 3
3440
}
3541
}
3642
}

QRCoder/QRCodeGenerator.cs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ public static QRCodeData GenerateQrCode(PayloadGenerator.Payload payload)
105105
/// <returns>Returns the raw QR code data which can be used for rendering.</returns>
106106
public static QRCodeData GenerateQrCode(PayloadGenerator.Payload payload, ECCLevel eccLevel)
107107
{
108+
if (eccLevel == ECCLevel.Default)
109+
eccLevel = payload.EccLevel;
110+
else if (payload.EccLevel != ECCLevel.Default && eccLevel != payload.EccLevel)
111+
throw new ArgumentOutOfRangeException(nameof(eccLevel), $"The provided payload requires a ECC level of {eccLevel}.");
108112
return GenerateQrCode(payload.ToString(), eccLevel, false, false, payload.EciMode, payload.Version);
109113
}
110114

@@ -121,6 +125,7 @@ public static QRCodeData GenerateQrCode(PayloadGenerator.Payload payload, ECCLev
121125
/// <returns>Returns the raw QR code data which can be used for rendering.</returns>
122126
public static QRCodeData GenerateQrCode(string plainText, ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1)
123127
{
128+
eccLevel = ValidateECCLevel(eccLevel);
124129
EncodingMode encoding = GetEncodingFromPlaintext(plainText, forceUtf8);
125130
var codedText = PlainTextToBinary(plainText, encoding, eciMode, utf8BOM, forceUtf8);
126131
var dataInputLength = GetDataLength(encoding, plainText, codedText, forceUtf8);
@@ -165,7 +170,6 @@ public static QRCodeData GenerateQrCode(string plainText, ECCLevel eccLevel, boo
165170
return GenerateQrCode(completeBitArray, eccLevel, version);
166171
}
167172

168-
169173
/// <summary>
170174
/// Calculates the QR code data which than can be used in one of the rendering classes to generate a graphical representation.
171175
/// </summary>
@@ -175,6 +179,7 @@ public static QRCodeData GenerateQrCode(string plainText, ECCLevel eccLevel, boo
175179
/// <returns>Returns the raw QR code data which can be used for rendering.</returns>
176180
public static QRCodeData GenerateQrCode(byte[] binaryData, ECCLevel eccLevel)
177181
{
182+
eccLevel = ValidateECCLevel(eccLevel);
178183
int version = GetVersion(binaryData.Length, EncodingMode.Byte, eccLevel);
179184

180185
int countIndicatorLen = GetCountIndicatorLength(version, EncodingMode.Byte);
@@ -187,6 +192,27 @@ public static QRCodeData GenerateQrCode(byte[] binaryData, ECCLevel eccLevel)
187192
return GenerateQrCode(bitArray, eccLevel, version);
188193
}
189194

195+
/// <summary>
196+
/// Validates the specified error correction level.
197+
/// Returns the provided level if it is valid, or the level M if the provided level is Default.
198+
/// Throws an exception if an invalid level is provided.
199+
/// </summary>
200+
private static ECCLevel ValidateECCLevel(ECCLevel eccLevel)
201+
{
202+
switch (eccLevel)
203+
{
204+
case ECCLevel.L:
205+
case ECCLevel.M:
206+
case ECCLevel.Q:
207+
case ECCLevel.H:
208+
return eccLevel;
209+
case ECCLevel.Default:
210+
return ECCLevel.M;
211+
default:
212+
throw new ArgumentOutOfRangeException(nameof(eccLevel), eccLevel, "Invalid error correction level.");
213+
}
214+
}
215+
190216
private static readonly BitArray _repeatingPattern = new BitArray(
191217
new[] { true, true, true, false, true, true, false, false, false, false, false, true, false, false, false, true });
192218

QRCoderApiTests/net35+net40+net50+net50-windows+netstandard20/QRCoder.approved.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -895,6 +895,7 @@ namespace QRCoder
895895
public static QRCoder.QRCodeData GenerateQrCode(string plainText, QRCoder.QRCodeGenerator.ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, QRCoder.QRCodeGenerator.EciMode eciMode = 0, int requestedVersion = -1) { }
896896
public enum ECCLevel
897897
{
898+
Default = -1,
898899
L = 0,
899900
M = 1,
900901
Q = 2,

QRCoderApiTests/net60-windows/QRCoder.approved.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -903,6 +903,7 @@ namespace QRCoder
903903
public static QRCoder.QRCodeData GenerateQrCode(string plainText, QRCoder.QRCodeGenerator.ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, QRCoder.QRCodeGenerator.EciMode eciMode = 0, int requestedVersion = -1) { }
904904
public enum ECCLevel
905905
{
906+
Default = -1,
906907
L = 0,
907908
M = 1,
908909
Q = 2,

QRCoderApiTests/net60/QRCoder.approved.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,7 @@ namespace QRCoder
837837
public static QRCoder.QRCodeData GenerateQrCode(string plainText, QRCoder.QRCodeGenerator.ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, QRCoder.QRCodeGenerator.EciMode eciMode = 0, int requestedVersion = -1) { }
838838
public enum ECCLevel
839839
{
840+
Default = -1,
840841
L = 0,
841842
M = 1,
842843
Q = 2,

QRCoderApiTests/netstandard13/QRCoder.approved.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -802,6 +802,7 @@ namespace QRCoder
802802
public static QRCoder.QRCodeData GenerateQrCode(string plainText, QRCoder.QRCodeGenerator.ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, QRCoder.QRCodeGenerator.EciMode eciMode = 0, int requestedVersion = -1) { }
803803
public enum ECCLevel
804804
{
805+
Default = -1,
805806
L = 0,
806807
M = 1,
807808
Q = 2,

QRCoderTests/QRGeneratorTests.cs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Linq;
88
using System.Collections;
99
using System.Text;
10+
using System;
1011

1112
namespace QRCoderTests
1213
{
@@ -226,6 +227,78 @@ bool IsValidISO(string input)
226227
}
227228
}
228229
}
230+
231+
[Fact]
232+
[Category("QRGenerator/EccLevel")]
233+
public void ecc_level_from_payload_works()
234+
{
235+
var stringValue = "this is a test";
236+
237+
// set up baselines
238+
var expectedL = Encode(QRCodeGenerator.GenerateQrCode(stringValue, QRCodeGenerator.ECCLevel.L));
239+
var expectedM = Encode(QRCodeGenerator.GenerateQrCode(stringValue, QRCodeGenerator.ECCLevel.M));
240+
var expectedQ = Encode(QRCodeGenerator.GenerateQrCode(stringValue, QRCodeGenerator.ECCLevel.Q));
241+
var expectedH = Encode(QRCodeGenerator.GenerateQrCode(stringValue, QRCodeGenerator.ECCLevel.H));
242+
243+
// ensure that the baselines are different from each other
244+
expectedL.ShouldNotBe(expectedM);
245+
expectedL.ShouldNotBe(expectedQ);
246+
expectedL.ShouldNotBe(expectedH);
247+
expectedM.ShouldNotBe(expectedQ);
248+
expectedM.ShouldNotBe(expectedH);
249+
expectedQ.ShouldNotBe(expectedH);
250+
251+
// validate that any ECC level can be used when the payload specifies a default ECC level
252+
var payloadDefault = new SamplePayload(stringValue, QRCodeGenerator.ECCLevel.Default);
253+
Encode(QRCodeGenerator.GenerateQrCode(payloadDefault)).ShouldBe(expectedM);
254+
Encode(QRCodeGenerator.GenerateQrCode(payloadDefault, QRCodeGenerator.ECCLevel.Default)).ShouldBe(expectedM);
255+
Encode(QRCodeGenerator.GenerateQrCode(payloadDefault, QRCodeGenerator.ECCLevel.L)).ShouldBe(expectedL);
256+
Encode(QRCodeGenerator.GenerateQrCode(payloadDefault, QRCodeGenerator.ECCLevel.M)).ShouldBe(expectedM);
257+
Encode(QRCodeGenerator.GenerateQrCode(payloadDefault, QRCodeGenerator.ECCLevel.Q)).ShouldBe(expectedQ);
258+
Encode(QRCodeGenerator.GenerateQrCode(payloadDefault, QRCodeGenerator.ECCLevel.H)).ShouldBe(expectedH);
259+
260+
// validate that the ECC level specified in the payload is used when default is specified,
261+
// or checks that the selected ECC level matches the payload ECC level, throwing an exception otherwise
262+
Verify(QRCodeGenerator.ECCLevel.L, expectedL);
263+
Verify(QRCodeGenerator.ECCLevel.M, expectedM);
264+
Verify(QRCodeGenerator.ECCLevel.Q, expectedQ);
265+
Verify(QRCodeGenerator.ECCLevel.H, expectedH);
266+
267+
268+
void Verify(QRCodeGenerator.ECCLevel eccLevel, string expected)
269+
{
270+
var payload = new SamplePayload(stringValue, eccLevel);
271+
Encode(QRCodeGenerator.GenerateQrCode(payload)).ShouldBe(expected);
272+
foreach (var ecc in Enum.GetValues(typeof(QRCodeGenerator.ECCLevel)).Cast<QRCodeGenerator.ECCLevel>())
273+
{
274+
if (ecc == eccLevel || ecc == QRCodeGenerator.ECCLevel.Default)
275+
Encode(QRCodeGenerator.GenerateQrCode(payload, ecc)).ShouldBe(expected);
276+
else
277+
Should.Throw<ArgumentOutOfRangeException>(() => Encode(QRCodeGenerator.GenerateQrCode(payload, ecc)));
278+
}
279+
}
280+
281+
string Encode(QRCodeData qrData)
282+
{
283+
return string.Join("", qrData.ModuleMatrix.Select(x => x.ToBitString()).ToArray());
284+
}
285+
}
286+
287+
private class SamplePayload : PayloadGenerator.Payload
288+
{
289+
private string _data;
290+
private QRCodeGenerator.ECCLevel _eccLevel;
291+
292+
public SamplePayload(string data, QRCodeGenerator.ECCLevel eccLevel)
293+
{
294+
_data = data;
295+
_eccLevel = eccLevel;
296+
}
297+
298+
public override QRCodeGenerator.ECCLevel EccLevel => _eccLevel;
299+
300+
public override string ToString() => _data;
301+
}
229302
}
230303

231304
public static class ExtensionMethods

0 commit comments

Comments
 (0)