Skip to content

Commit 66650df

Browse files
authored
Refactor base encodings (#10)
Comprehensive refactoring of all base encoding implementations (Base2, Base8, Base16, Base32, Base45, Base64, Base85, MoneroBase58, DividingCoder, Multibase). - Made Base64 TryEncode/TryDecode allocation-free with two-layer architecture - Added static Internal safe-count methods to Base64, matching other bases - Replaced shr on signed types with TBitOperations.Asr32/Asr64 for cross-platform correctness - Abstracted reverse lookup access into shared TCodingAlphabet.TryLookup - Unified decode success path to System.Copy trim pattern across all bases - Added upfront buffer validation and empty-input guards to all Try* methods - Deduplicated TBits.PartialBigEndianBytesToUInt64 (MoneroBase58 now uses it) - Removed redundant Base16Alphabet.MapCounterparts and Multibase helper - Standardized error messages, exception types, and System-qualified calls - Various minor fixes: comment typos, bounds guards, empty-result guards
1 parent 79361b1 commit 66650df

18 files changed

Lines changed: 700 additions & 539 deletions

SimpleBaseLib/src/Alphabets/SbpAliasedBase32Alphabet.pas

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ interface
99
SbpCharMap,
1010
SbpPaddingPosition,
1111
SbpCharUtilities,
12+
SbpCodingAlphabet,
1213
SbpIAliasedBase32Alphabet,
1314
SbpBase32Alphabet;
1415

@@ -53,7 +54,9 @@ procedure TAliasedBase32Alphabet.MapAlternate(ASource, ADestination: Char);
5354
var
5455
LResult: Int32;
5556
begin
56-
LResult := ReverseLookupTable[Ord(ADestination)] - 1;
57+
if not TCodingAlphabet.TryLookup(ReverseLookupTable, ADestination, LResult) then
58+
raise EArgumentSimpleBaseLibException.CreateFmt(
59+
'Character "%s" is not in the alphabet', [ADestination]);
5760
Map(ASource, LResult);
5861
Map(TCharUtilities.ToAsciiLower(ASource), LResult);
5962
end;

SimpleBaseLib/src/Alphabets/SbpBase16Alphabet.pas

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,14 @@
55
interface
66

77
uses
8-
SysUtils,
98
SbpICodingAlphabet,
109
SbpIBase16Alphabet,
11-
SbpCharUtilities,
1210
SbpCodingAlphabet;
1311

1412
type
1513
TBase16Alphabet = class(TCodingAlphabet, IBase16Alphabet)
1614
strict private
1715
FCaseSensitive: Boolean;
18-
19-
procedure MapCounterparts;
2016
public
2117
class var FUpperCaseAlphabet: ICodingAlphabet;
2218
class var FLowerCaseAlphabet: ICodingAlphabet;
@@ -58,36 +54,8 @@ constructor TBase16Alphabet.Create(const AAlphabet: String);
5854
constructor TBase16Alphabet.Create(const AAlphabet: String;
5955
const ACaseSensitive: Boolean);
6056
begin
61-
inherited Create(16, AAlphabet, False);
57+
inherited Create(16, AAlphabet, not ACaseSensitive);
6258
FCaseSensitive := ACaseSensitive;
63-
64-
if not FCaseSensitive then
65-
begin
66-
MapCounterparts;
67-
end;
68-
end;
69-
70-
procedure TBase16Alphabet.MapCounterparts;
71-
var
72-
LI, LAlphaLen: Int32;
73-
LChar: Char;
74-
begin
75-
LAlphaLen := System.Length(Value);
76-
for LI := 0 to LAlphaLen - 1 do
77-
begin
78-
LChar := Value[LI + 1];
79-
if TCharUtilities.IsLetter(LChar) then
80-
begin
81-
if TCharUtilities.IsAsciiUpper(LChar) then
82-
begin
83-
Map(TCharUtilities.ToAsciiLower(LChar), LI);
84-
end
85-
else
86-
begin
87-
Map(TCharUtilities.ToAsciiUpper(LChar), LI);
88-
end;
89-
end;
90-
end;
9159
end;
9260

9361
class function TBase16Alphabet.GetUpperCase: ICodingAlphabet;

SimpleBaseLib/src/Alphabets/SbpBase32Alphabet.pas

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ class function TBase32Alphabet.GetCrockford: IBase32Alphabet;
180180
begin
181181
if FCrockfordAlphabet = nil then
182182
begin
183-
SetLength(LMap, 3);
183+
System.SetLength(LMap, 3);
184184
LMap[0] := CMap[0];
185185
LMap[1] := CMap[1];
186186
LMap[2] := CMap[2];
@@ -203,7 +203,7 @@ class function TBase32Alphabet.GetBase32H: IBase32Alphabet;
203203
begin
204204
if FBase32HAlphabet = nil then
205205
begin
206-
SetLength(LMap, 4);
206+
System.SetLength(LMap, 4);
207207
LMap[0] := CMap[0];
208208
LMap[1] := CMap[1];
209209
LMap[2] := CMap[2];

SimpleBaseLib/src/Alphabets/SbpCodingAlphabet.pas

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ TCodingAlphabet = class(TInterfacedObject, ICodingAlphabet)
3737
function ToString: String; override;
3838
function GetHashCode: {$IFDEF DELPHI}Int32;{$ELSE}PtrInt;{$ENDIF DELPHI}override;
3939

40+
class function TryLookup(const ATable: TSimpleBaseLibByteArray;
41+
AChar: Char; out AValue: Int32): Boolean; static; inline;
42+
4043
property Value: String read GetValue;
4144
property Length: Int32 read GetLength;
4245
property ReverseLookupTable: TSimpleBaseLibByteArray read GetReverseLookupTable;
@@ -137,5 +140,18 @@ procedure TCodingAlphabet.Map(AChar: Char; AValue: Int32);
137140
FReverseLookupTable[Ord(AChar)] := Byte(AValue + 1);
138141
end;
139142

143+
class function TCodingAlphabet.TryLookup(const ATable: TSimpleBaseLibByteArray;
144+
AChar: Char; out AValue: Int32): Boolean;
145+
begin
146+
if Ord(AChar) >= System.Length(ATable) then
147+
begin
148+
AValue := -1;
149+
Result := False;
150+
Exit;
151+
end;
152+
AValue := ATable[Ord(AChar)] - 1;
153+
Result := AValue >= 0;
154+
end;
155+
140156
end.
141157

SimpleBaseLib/src/Bases/SbpBase16.pas

Lines changed: 57 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ interface
1313
SbpIBaseStreamCoder,
1414
SbpINonAllocatingBaseCoder,
1515
SbpStreamUtilities,
16+
SbpCodingAlphabet,
1617
SbpBase16Alphabet;
1718

1819
type
@@ -41,6 +42,9 @@ TBase16 = class(TInterfacedObject, IBase16, IBaseStreamCoder, INonAllocatingBa
4142
class procedure InternalEncode(const AInput: TSimpleBaseLibByteArray;
4243
const AOutput: TSimpleBaseLibCharArray; const AAlphabet: String); static;
4344

45+
function InternalDecode(const AInput: String;
46+
const AOutput: TSimpleBaseLibByteArray; out ABytesWritten: Int32): Boolean;
47+
4448
public
4549
class constructor Create;
4650

@@ -141,23 +145,24 @@ class function TBase16.GetModHex: IBase16;
141145

142146
function TBase16.Decode(const AText: String): TSimpleBaseLibByteArray;
143147
var
144-
LTextLen: Int32;
145-
LSafeCount: Int32;
146-
LBytesWritten: Int32;
148+
LSafeCount, LBytesWritten: Int32;
147149
begin
148-
LTextLen := System.Length(AText);
149-
if LTextLen = 0 then
150+
if System.Length(AText) = 0 then
150151
begin
151152
Result := nil;
152153
Exit;
153154
end;
154155

155156
LSafeCount := GetSafeByteCountForDecoding(AText);
156-
System.SetLength(Result, LSafeCount);
157+
if LSafeCount = 0 then
158+
begin
159+
raise EArgumentSimpleBaseLibException.Create('Invalid text length');
160+
end;
157161

158-
if not TryDecode(AText, Result, LBytesWritten) then
162+
System.SetLength(Result, LSafeCount);
163+
if not InternalDecode(AText, Result, LBytesWritten) then
159164
begin
160-
raise EArgumentSimpleBaseLibException.Create('Invalid text');
165+
raise EArgumentSimpleBaseLibException.Create('Invalid character in input');
161166
end;
162167
end;
163168

@@ -193,56 +198,30 @@ function TBase16.TryDecode(const AText: String;
193198
const AOutput: TSimpleBaseLibByteArray;
194199
out ABytesWritten: Int32): Boolean;
195200
var
196-
LTextLen, LI, LO, LByte1, LByte2, LOutputLen: Int32;
197-
LChar1, LChar2: Char;
201+
LTextLen, LOutputLen: Int32;
198202
begin
203+
ABytesWritten := 0;
199204
LTextLen := System.Length(AText);
200205
if LTextLen = 0 then
201206
begin
202-
ABytesWritten := 0;
203207
Result := True;
204208
Exit;
205209
end;
206210

207211
if (LTextLen and 1) <> 0 then
208212
begin
209-
ABytesWritten := 0;
210213
Result := False;
211214
Exit;
212215
end;
213216

214217
LOutputLen := LTextLen div 2;
215218
if System.Length(AOutput) < LOutputLen then
216219
begin
217-
ABytesWritten := 0;
218220
Result := False;
219221
Exit;
220222
end;
221223

222-
LO := 0;
223-
LI := 1;
224-
while LI <= LTextLen do
225-
begin
226-
LChar1 := AText[LI];
227-
LChar2 := AText[LI + 1];
228-
229-
LByte1 := FAlphabet.ReverseLookupTable[Ord(LChar1)] - 1;
230-
LByte2 := FAlphabet.ReverseLookupTable[Ord(LChar2)] - 1;
231-
232-
if (LByte1 < 0) or (LByte2 < 0) then
233-
begin
234-
ABytesWritten := LO;
235-
Result := False;
236-
Exit;
237-
end;
238-
239-
AOutput[LO] := Byte((LByte1 * 16) or LByte2);
240-
Inc(LO);
241-
Inc(LI, 2);
242-
end;
243-
244-
ABytesWritten := LO;
245-
Result := True;
224+
Result := InternalDecode(AText, AOutput, ABytesWritten);
246225
end;
247226

248227
function TBase16.Encode(const ABytes: TSimpleBaseLibByteArray): String;
@@ -260,9 +239,9 @@ function TBase16.Encode(const ABytes: TSimpleBaseLibByteArray): String;
260239

261240
LAlphabet := FAlphabet.Value;
262241
LSafeCount := GetSafeCharCountForEncoding(ABytes);
263-
SetLength(LOutput, LSafeCount);
242+
System.SetLength(LOutput, LSafeCount);
264243
InternalEncode(ABytes, LOutput, LAlphabet);
265-
SetString(Result, PChar(@LOutput[0]), Length(LOutput));
244+
SetString(Result, PChar(@LOutput[0]), System.Length(LOutput));
266245
end;
267246

268247
function TBase16.TryEncode(const ABytes: TSimpleBaseLibByteArray;
@@ -271,20 +250,18 @@ function TBase16.TryEncode(const ABytes: TSimpleBaseLibByteArray;
271250
LLen, LOutputLen: Int32;
272251
LAlphabet: String;
273252
begin
253+
ACharsWritten := 0;
274254
LLen := System.Length(ABytes);
275-
LOutputLen := LLen * 2;
276-
277-
if System.Length(AOutput) < LOutputLen then
255+
if LLen = 0 then
278256
begin
279-
ACharsWritten := 0;
280-
Result := False;
257+
Result := True;
281258
Exit;
282259
end;
283260

284-
if LOutputLen = 0 then
261+
LOutputLen := LLen * 2;
262+
if System.Length(AOutput) < LOutputLen then
285263
begin
286-
ACharsWritten := 0;
287-
Result := True;
264+
Result := False;
288265
Exit;
289266
end;
290267

@@ -295,6 +272,39 @@ function TBase16.TryEncode(const ABytes: TSimpleBaseLibByteArray;
295272
Result := True;
296273
end;
297274

275+
function TBase16.InternalDecode(const AInput: String;
276+
const AOutput: TSimpleBaseLibByteArray; out ABytesWritten: Int32): Boolean;
277+
var
278+
LTextLen, LI, LO, LByte1, LByte2: Int32;
279+
LChar1, LChar2: Char;
280+
LTable: TSimpleBaseLibByteArray;
281+
begin
282+
LTable := FAlphabet.ReverseLookupTable;
283+
LTextLen := System.Length(AInput);
284+
LO := 0;
285+
LI := 1;
286+
while LI <= LTextLen do
287+
begin
288+
LChar1 := AInput[LI];
289+
LChar2 := AInput[LI + 1];
290+
291+
if (not TCodingAlphabet.TryLookup(LTable, LChar1, LByte1)) or
292+
(not TCodingAlphabet.TryLookup(LTable, LChar2, LByte2)) then
293+
begin
294+
ABytesWritten := LO;
295+
Result := False;
296+
Exit;
297+
end;
298+
299+
AOutput[LO] := Byte((LByte1 * 16) or LByte2);
300+
Inc(LO);
301+
Inc(LI, 2);
302+
end;
303+
304+
ABytesWritten := LO;
305+
Result := True;
306+
end;
307+
298308
class procedure TBase16.InternalEncode(const AInput: TSimpleBaseLibByteArray;
299309
const AOutput: TSimpleBaseLibCharArray; const AAlphabet: String);
300310
var

SimpleBaseLib/src/Bases/SbpBase2.pas

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,15 @@ function TBase2.Decode(const AText: String): TSimpleBaseLibByteArray;
8282
var
8383
LOutputLen: Int32;
8484
begin
85+
if System.Length(AText) = 0 then
86+
begin
87+
Result := nil;
88+
Exit;
89+
end;
90+
8591
if (System.Length(AText) mod 8) <> 0 then
8692
begin
87-
raise EArgumentSimpleBaseLibException.Create('Input length must be multiply of 8');
93+
raise EArgumentSimpleBaseLibException.Create('Input length must be a multiple of 8');
8894
end;
8995

9096
LOutputLen := System.Length(AText) div 8;
@@ -170,6 +176,12 @@ function TBase2.TryEncode(const ABytes: TSimpleBaseLibByteArray;
170176
LTargetLen: Int32;
171177
begin
172178
ACharsWritten := 0;
179+
if System.Length(ABytes) = 0 then
180+
begin
181+
Result := True;
182+
Exit;
183+
end;
184+
173185
LTargetLen := System.Length(ABytes) * 8;
174186
if System.Length(AOutput) < LTargetLen then
175187
begin

0 commit comments

Comments
 (0)