First steps
It is very easy to work with Smart.
Open the IDE, create a new project, select for instance a "Console" template, then code your object pascal units, just as usual.
The only missing feature is a debugger integrated into the IDE. You'll have to debug your code from the JavaScript side (using the debugging tools featured in Chrome, for instance).
You have at hand the full power and readability of the great object pascal Open Source implementation of Delphi Web Script. See http://code.google.com/p/dwscript/
No more type-less JavaScript coding, no more pseudo classes, no more endless nested delegates... Readable code, modern object oriented language (including strong typing, interfaces and inheritance), with a lot of existing and proven code base from a happy-sharing community. Just plain Object Pascal code, with the full power of the HTML 5 platform at hand.
It does not need external JavaScript library (like jQuery or such), but you can use any such library, if needed.
Conversion rules
The Delphi Web Script implementation is very close to the
Delphi / FPC implementation. But it will run with JavaScript
as its runtime engine: you can see JavaScript as the replacement of
assembler code for a regular Delphi/FPC compiler - and you
can in fact write directly JavaScript code in the middle of a
Smart unit using the asm .. end
keywords.
As a consequence, when implementing low-level algorithms like crc32 or SHA-256 with Smart, some genuineness is to be taken in account:
- There is no low level binary types like
byte, integer, cardinal, Int64, UInt64
which are all mapped as oneinteger
type (since hashing use binary representation of the data, we must take care of this); - You can't play with memory buffers directly in the language (this is a
managed code, without any pointer nor memory allocation) - so we'll use
string
to handle memory buffers; - You have to handle all code endianess by hand (by definition, JavaScript is endian-agnostic);
- There is no
char
type, juststring
.
But most high-level code could be shared between a Delphi
application and a Smart application. For instance, when used within
our mORMot framework, you may share the ORM definitions (via
class
) or the SOA contract definitions (via
interface
), and most of your business logic.
CRC32 computation
So let's start with crc32 algorithm.
Here is the main computation code:
var crc32Tab: array[0..255] of integer;
function crc32(aCRC32: integer; const data: string): integer; var i: integer; begin result := (not aCRC32) shr 0; for i := 1 to length(data) do result := crc32Tab[(result xor ord(data[i])) and $ff] xor (result shr 8); result := (not result) shr 0; end;
This is a standard implementation pattern, except for two remarks:
- We added
... shr 0
in order to ensure that aninteger
variable will be maintained as anUInt32/cardinal
variable. Since crc32 is a 32 bit hashing algorithm, we need to ensure that we'll only have positive values; - No
byte
type is available here: so we'll explicitly call... and $ff
in order to truncate theinteger
value into its 8 bit content; - Since the
string
type is used for data manipulation, we useord(data[i])
to retrieve eachbyte
(orchar
) of the supplied text.
This implementation of the crc32 algorithm expect a pre-computed
table to be available. All JavaScript implementation of this algorithm
(at least, all that I was able to found on Internet) use a fixed constant
array. Since we are not afraid to write code any more in our AJAX application,
and since it may help saving bandwidth and application size, we'll compute our
own crc32Tab[]
array content with the following code:
procedure InitCrc32Tab; var i,n,crc: integer; begin // this code generates a 1KB table for i := 0 to 255 do begin crc := i; for n := 1 to 8 do if (crc and 1)<>0 then // $edb88320 from polynomial p=(0,1,2,4,5,7,8,10,11,12,16,22,23,26) crc := ((crc shr 1) xor $edb88320) shr 0 else crc := crc shr 1; CRC32Tab[i] := crc; end; end;
Then we are able to use this code as such, for instance in a Smart console application:
procedure TApplication.PopulateConsole; var i: integer; begin InitCrc32Tab; console.Writeln(IntToHex(crc32(0,'TestCRC32'),8)); end;
The InitCrc32Tab
shall be called only once, at application
startup. Its execution is immediate. It won't delay your application
display.
SHA-256
The well-known SHA-256 algorithm is a proven way of creating an unique identifier from any data input. You can use it for instance to sign any content, or store efficiently a password. It is mathematically proven to be impossible to find out the input data from its hashed reduction (at least for the next decade of computer power). And it has a very low potential of "collision" (i.e. two diverse data having the same resulting hash). It is "Top Secret" enabled - U.S. National Institute of Standards and Technology says, "Federal agencies must use the SHA-2 family of hash functions for applications that require collision resistance after 2010". This is the hashing pattern used within mORMot.
But it is also more complex than the crc32 algorithm. See http://en.wikipedia.org/wiki/SHA-2
You have an optimized implementation in the SynCrypto
unit,
with tuned x86 assembler code, and provided regression tests. We'll
implement a pure Object Pascal version, compatible with the Smart / Delphi
Web Script (DWS) compiler.
First of all, we'll define a record
type. We may have used a
class
, but since we have an extended record
type at
hand with DWS (including properties and methods), we will stay to
it.
type TSHA256Buffer = array[0..63] of integer; TSHAHash = record A,B,C,D,E,F,G,H: integer; end; TSHA256 = record private // Working hash Hash: TSHAHash; // 64bit msg length MLen: integer; // Block buffer Buffer: TSHA256Buffer; // Index in buffer Index: integer; // used by Update and Finalize procedure Compress; public /// initialize SHA256 context for hashing procedure Init; /// update the SHA256 context with some data procedure Update(const Data: string); /// finalize and compute the resulting SHA256 hash Digest of all data // affected to Update() method // - returns the data as Hexadecimal function Finalize: string; /// compute SHA256 hexa digest of a given text class function Compute(Data: string): string; end;
The main class function
can be used as such:
console.WriteLn(TSHA256.Compute('abc'));
And it will be implemented as:
class function TSHA256.Compute(Data: string): string; var SHA: TSHA256; begin SHA.Init; SHA.Update(Data); result := SHA.Finalize; end;
The initialization will be done with this method:
procedure TSHA256.Init; begin Hash.A := $6a09e667; Hash.B := $bb67ae85; Hash.C := $3c6ef372; Hash.D := $a54ff53a; Hash.E := $510e527f; Hash.F := $9b05688c; Hash.G := $1f83d9ab; Hash.H := $5be0cd19; end;
Note that the DWS compiler will initialize all record content to
zero by default. The corresponding JavaScript code will be emitted. So
only Hash.?
values are to be set explicitly: Index
and MLen
properties are already set to 0.
Then the following method will update the current hash with some supplied data:
procedure TSHA256.Update(const Data: string);
var Len, aLen, i: integer;
DataNdx: integer = 1;
begin
Len := length(Data);
inc(MLen,Len shl 3);
while Len>0 do begin
aLen := 64-Index;
if aLen<=Len then begin
for i := 0 to aLen-1 do
Buffer[Index+i] := ord(Data[DataNdx+i]) and $ff;
dec(Len,aLen);
inc(DataNdx,aLen);
Compress;
Index:= 0;
end else begin
for i := 0 to Len-1 do
Buffer[Index+i] := ord(Data[DataNdx+i]) and $ff;
inc(Index,Len);
break;
end;
end;
end;
The internal Buffer[]
, which is expected to contain up to 64
bytes, is filled with the provided data, then the global MLen
is
refreshed, and the Compress
method will do the proper hashing,
when the 64 bytes buffer is full. The Index
variable is used to
track the number of bytes available in the internal Buffer[]
array.
The Finalize
method will compute the latest block, including
the global length to the incoming data (with padding if needed), then will
return the data as an hexadecimal string:
function TSHA256.Finalize: string; var i: integer; begin // Message padding // 1. append bit '1' after Buffer Buffer[Index]:= $80; for i := Index+1 to 63 do Buffer[i] := 0; // 2. Compress if more than 448 bits, (no room for 64 bit length) if Index>=56 then begin Compress; for i := 0 to 59 do Buffer[i] := 0; end; // Write 64 bit Buffer length into the last bits of the last block // (in big endian format) and do a final compress Buffer[60] := (MLen and $ff000000)shr 24; Buffer[61] := (MLen and $ff0000)shr 16; Buffer[62] := (MLen and $ff00)shr 8; Buffer[63] := MLen and $ff; Compress; // Hash -> Digest to big endian format result := LowerCase(IntToHex(Hash.A,8)+IntToHex(Hash.B,8)+IntToHex(Hash.C,8)+ IntToHex(Hash.D,8)+IntToHex(Hash.E,8)+IntToHex(Hash.F,8)+IntToHex(Hash.G,8)); // Clear Data Init; end;
Some endianess tricks are used in the above code. But it remains easy to follow and maintain.
The main hash computation is performed in the Compress
method,
as such:
const K: TSHA256Buffer = ([ $428a2f98, $71374491, $b5c0fbcf, $e9b5dba5, $3956c25b, $59f111f1, $923f82a4, $ab1c5ed5, $d807aa98, $12835b01, $243185be, $550c7dc3, $72be5d74, $80deb1fe, $9bdc06a7, $c19bf174, $e49b69c1, $efbe4786, $0fc19dc6, $240ca1cc, $2de92c6f, $4a7484aa, $5cb0a9dc, $76f988da, $983e5152, $a831c66d, $b00327c8, $bf597fc7, $c6e00bf3, $d5a79147, $06ca6351, $14292967, $27b70a85, $2e1b2138, $4d2c6dfc, $53380d13, $650a7354, $766a0abb, $81c2c92e, $92722c85, $a2bfe8a1, $a81a664b, $c24b8b70, $c76c51a3, $d192e819, $d6990624, $f40e3585, $106aa070, $19a4c116, $1e376c08, $2748774c, $34b0bcb5, $391c0cb3, $4ed8aa4a, $5b9cca4f, $682e6ff3, $748f82ee, $78a5636f, $84c87814, $8cc70208, $90befffa, $a4506ceb, $bef9a3f7, $c67178f2]);
procedure TSHA256.Compress; var W: TSHA256Buffer; H: TSHAHash = Hash; i, t1, t2: integer; begin for i := 0 to 15 do W[i]:= (((Buffer[i*4] shl 24)shr 0)or(Buffer[i*4+1] shl 16)or (Buffer[i*4+2] shl 8)or Buffer[i*4+3]) shr 0; for i := 16 to 63 do W[i] := ((((W[i-2]shr 17)or(W[i-2]shl 15))xor((W[i-2]shr 19)or(W[i-2]shl 13)) xor (W[i-2]shr 10))+W[i-7]+(((W[i-15]shr 7)or(W[i-15]shl 25)) xor ((W[i-15]shr 18)or(W[i-15]shl 14))xor(W[i-15]shr 3))+W[i-16])shr 0; for i := 0 to high(W) do begin t1 := (H.H+(((H.E shr 6)or(H.E shl 26))xor((H.E shr 11)or(H.E shl 21))xor ((H.E shr 25)or(H.E shl 7)))+((H.E and H.F)xor(not H.E and H.G))+K[i]+W[i])shr 0; t2 := ((((H.A shr 2)or(H.A shl 30))xor((H.A shr 13)or(H.A shl 19))xor ((H.A shr 22)xor(H.A shl 10)))+((H.A and H.B)xor(H.A and H.C)xor(H.B and H.C))) shr 0; H.H := H.G; H.G := H.F; H.F := H.E; H.E := (H.D+t1)shr 0; H.D := H.C; H.C := H.B; H.B := H.A; H.A := (t1+t2)shr 0; end; Hash.A := (Hash.A+H.A)shr 0; Hash.B := (Hash.B+H.B)shr 0; Hash.C := (Hash.C+H.C)shr 0; Hash.D := (Hash.D+H.D)shr 0; Hash.E := (Hash.E+H.E)shr 0; Hash.F := (Hash.F+H.F)shr 0; Hash.G := (Hash.G+H.G)shr 0; Hash.H := (Hash.H+H.H)shr 0; end;
A K: TSHA256Buffer
constant table is used. Note the non
standard definition of DWS for a const array
: it will use
.. = ([...]);
instead of .. = ();
as in classical
Object Pascal.
The hash is computed from an internal W[]
array, which is
filled with the binary representation of the supplied Buffer[]
bytes. That is, Buffer: array[0..63] of byte
is first
un-serialized in W: array[0..15] of cardinal
.
Then the SHA-256 algorithm is performed in its most simple rolled version. An un-rolled version is not mandatory here, in our managed JavaScript runtime environment.
The only non obvious part of the above code is the use of ... shr
0
to enforce only positive 32 bit integers (aka cardinal
)
are used during the computation.
This small article tries to demonstrate how Smart, even if only in
beta phase, may be a good candidate for building Rich AJAX client, and
embed some low-level logic in a pure JavaScript application, using "classic"
object pascal coding.
Some points are to be take in account, but conversion of existing code is very
easy.
Feedback and comments are welcome in our forum, just as usual.