package otto import ( "math/rand" "strconv" "strings" "testing" "github.com/stretchr/testify/require" ) func TestGoSliceQuickSort(t *testing.T) { testGoSliceSort(t, "quickSort(testSlice, 0, testSlice.length-1);", jsQuickSort) } func TestGoSliceHeapSort(t *testing.T) { testGoSliceSort(t, "heapSort(testSlice)", jsHeapSort) } func TestJsArrayQuicksort(t *testing.T) { testJsArraySort(t, "quickSort(testSlice, 0, testSlice.length-1);", jsQuickSort) } func TestJsArrayHeapSort(t *testing.T) { testJsArraySort(t, "heapSort(testSlice)", jsHeapSort) } func TestJsArrayMergeSort(t *testing.T) { testJsArraySort(t, "testSlice = mergeSort(testSlice)", jsMergeSort) } func TestCryptoAes(t *testing.T) { tt(t, func() { _, vm := test() _, err := vm.Run(jsCryptoAES) is(err, nil) }) } func BenchmarkGoSliceQuickSort100000000(b *testing.B) { benchmarkGoSliceSort(b, 100000000, "quickSort(testSlice, 0, testSlice.length-1);", jsQuickSort) } func BenchmarkGoSliceHeapSort100000000(b *testing.B) { benchmarkGoSliceSort(b, 100000000, "heapSort(testSlice);", jsHeapSort) } func BenchmarkJsArrayQuickSort500(b *testing.B) { benchmarkJsArraySort(b, 500, "quickSort(testSlice, 0, testSlice.length-1);", jsQuickSort) } func BenchmarkJsArrayMergeSort500(b *testing.B) { benchmarkJsArraySort(b, 500, "mergeSort(testSlice);", jsMergeSort) } func BenchmarkJsArrayHeapSort500(b *testing.B) { benchmarkJsArraySort(b, 500, "heapSort(testSlice);", jsHeapSort) } func BenchmarkCryptoAES(b *testing.B) { vm := New() // Make sure VM creation time is not counted in runtime test b.ResetTimer() for i := 0; i < b.N; i++ { _, err := vm.Run(jsCryptoAES) require.NoError(b, err) } } func testGoSliceSort(t *testing.T, sortFuncCall string, sortCode string) { t.Helper() tt(t, func() { test, vm := test() // inject quicksort code _, err := vm.Run(sortCode) is(err, nil) testSlice := []int{5, 3, 2, 4, 1} vm.Set("testSlice", testSlice) _, err = vm.Run(sortFuncCall) is(err, nil) is(test(`testSlice[0]`).export(), 1) is(test(`testSlice[1]`).export(), 2) is(test(`testSlice[2]`).export(), 3) is(test(`testSlice[3]`).export(), 4) is(test(`testSlice[4]`).export(), 5) is(testSlice[0], 1) is(testSlice[1], 2) is(testSlice[2], 3) is(testSlice[3], 4) is(testSlice[4], 5) }) } func testJsArraySort(t *testing.T, sortFuncCall string, sortCode string) { t.Helper() tt(t, func() { test, vm := test() // inject quicksort code _, err := vm.Run(sortCode) require.NoError(t, err) _, err = vm.Run("var testSlice = [5, 3, 2, 4, 1];") require.NoError(t, err) _, err = vm.Run(sortFuncCall) require.NoError(t, err) is(test(`testSlice[0]`).export(), 1) is(test(`testSlice[1]`).export(), 2) is(test(`testSlice[2]`).export(), 3) is(test(`testSlice[3]`).export(), 4) is(test(`testSlice[4]`).export(), 5) }) } func benchmarkGoSliceSort(b *testing.B, size int, sortFuncCall string, sortCode string) { b.Helper() // generate arbitrary slice of 'size' testSlice := make([]int, size) for i := range size { testSlice[i] = rand.Int() //nolint:gosec } vm := New() // inject the sorting code _, err := vm.Run(sortCode) require.NoError(b, err) // Reset timer - everything until this point may have taken a long time b.ResetTimer() for i := 0; i < b.N; i++ { _, err = vm.Run(sortFuncCall) require.NoError(b, err) } } func benchmarkJsArraySort(b *testing.B, size int, sortFuncCall string, sortCode string) { b.Helper() // generate arbitrary slice of 'size' testSlice := make([]string, size) for i := range testSlice { testSlice[i] = strconv.Itoa(rand.Int()) //nolint:gosec } jsArrayString := "[" + strings.Join(testSlice, ",") + "]" vm := New() // inject the test array _, err := vm.Run("testSlice = " + jsArrayString) require.NoError(b, err) // inject the sorting code _, err = vm.Run(sortCode) require.NoError(b, err) // Reset timer - everything until this point may have taken a long time b.ResetTimer() for i := 0; i < b.N; i++ { _, err = vm.Run(sortFuncCall) require.NoError(b, err) } } /**********************************************************************************************************************/ // Appendix - all the Javascript algorithm code constants const jsQuickSort = ` function quickSort(arr, left, right){ var len = arr.length, pivot, partitionIndex; if(left < right){ pivot = right; partitionIndex = partition(arr, pivot, left, right); // sort left and right quickSort(arr, left, partitionIndex - 1); quickSort(arr, partitionIndex + 1, right); } return arr; } function partition(arr, pivot, left, right){ var pivotValue = arr[pivot], partitionIndex = left; for(var i = left; i < right; i++){ if(arr[i] < pivotValue){ swap(arr, i, partitionIndex); partitionIndex++; } } swap(arr, right, partitionIndex); return partitionIndex; } function swap(arr, i, j){ var temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } ` const jsMergeSort = ` function mergeSort(arr){ var len = arr.length; if(len <2) return arr; var mid = Math.floor(len/2), left = arr.slice(0,mid), right =arr.slice(mid); // send left and right to the mergeSort to broke it down into pieces // then merge those return merge(mergeSort(left),mergeSort(right)); } function merge(left, right){ var result = [], lLen = left.length, rLen = right.length, l = 0, r = 0; while(l < lLen && r < rLen){ if(left[l] < right[r]){ result.push(left[l++]); } else{ result.push(right[r++]); } } // remaining part needs to be addred to the result return result.concat(left.slice(l)).concat(right.slice(r)); } ` const jsHeapSort = ` function heapSort(arr){ var len = arr.length, end = len-1; heapify(arr, len); while(end > 0){ swap(arr, end--, 0); siftDown(arr, 0, end); } return arr; } function heapify(arr, len){ // break the array into root + two sides, to create tree (heap) var mid = Math.floor((len-2)/2); while(mid >= 0){ siftDown(arr, mid--, len-1); } } function siftDown(arr, start, end){ var root = start, child = root*2 + 1, toSwap = root; while(child <= end){ if(arr[toSwap] < arr[child]){ swap(arr, toSwap, child); } if(child+1 <= end && arr[toSwap] < arr[child+1]){ swap(arr, toSwap, child+1) } if(toSwap != root){ swap(arr, root, toSwap); root = toSwap; } else{ return; } toSwap = root; child = root*2+1 } } function swap(arr, i, j){ var temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } ` // Copied from JetStream benchmarking suite // http://browserbench.org/JetStream/sources/crypto-aes.js const jsCryptoAES = ` /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /* * AES Cipher function: encrypt 'input' with Rijndael algorithm * * takes byte-array 'input' (16 bytes) * 2D byte-array key schedule 'w' (Nr+1 x Nb bytes) * * applies Nr rounds (10/12/14) using key schedule w for 'add round key' stage * * returns byte-array encrypted value (16 bytes) */ function Cipher(input, w) { // main Cipher function [§5.1] var Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES) var Nr = w.length/Nb - 1; // no of rounds: 10/12/14 for 128/192/256-bit keys var state = [[],[],[],[]]; // initialise 4xNb byte-array 'state' with input [§3.4] for (var i=0; i<4*Nb; i++) state[i%4][Math.floor(i/4)] = input[i]; state = AddRoundKey(state, w, 0, Nb); for (var round=1; round 6 && i%Nk == 4) { temp = SubWord(temp); } for (var t=0; t<4; t++) w[i][t] = w[i-Nk][t] ^ temp[t]; } return w; } function SubWord(w) { // apply SBox to 4-byte word w for (var i=0; i<4; i++) w[i] = Sbox[w[i]]; return w; } function RotWord(w) { // rotate 4-byte word w left by one byte w[4] = w[0]; for (var i=0; i<4; i++) w[i] = w[i+1]; return w; } // Sbox is pre-computed multiplicative inverse in GF(2^8) used in SubBytes and KeyExpansion [§5.1.1] var Sbox = [0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76, 0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0, 0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15, 0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75, 0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84, 0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf, 0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8, 0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2, 0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73, 0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb, 0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79, 0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08, 0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a, 0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e, 0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf, 0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16]; // Rcon is Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)] [§5.2] var Rcon = [ [0x00, 0x00, 0x00, 0x00], [0x01, 0x00, 0x00, 0x00], [0x02, 0x00, 0x00, 0x00], [0x04, 0x00, 0x00, 0x00], [0x08, 0x00, 0x00, 0x00], [0x10, 0x00, 0x00, 0x00], [0x20, 0x00, 0x00, 0x00], [0x40, 0x00, 0x00, 0x00], [0x80, 0x00, 0x00, 0x00], [0x1b, 0x00, 0x00, 0x00], [0x36, 0x00, 0x00, 0x00] ]; /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /* * Use AES to encrypt 'plaintext' with 'password' using 'nBits' key, in 'Counter' mode of operation * - see http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf * for each block * - outputblock = cipher(counter, key) * - cipherblock = plaintext xor outputblock */ function AESEncryptCtr(plaintext, password, nBits) { if (!(nBits==128 || nBits==192 || nBits==256)) return ''; // standard allows 128/192/256 bit keys // for this example script, generate the key by applying Cipher to 1st 16/24/32 chars of password; // for real-world applications, a more secure approach would be to hash the password e.g. with SHA-1 var nBytes = nBits/8; // no bytes in key var pwBytes = new Array(nBytes); for (var i=0; i>> i*8) & 0xff; for (var i=0; i<4; i++) counterBlock[i+4] = (nonce/0x100000000 >>> i*8) & 0xff; // generate key schedule - an expansion of the key into distinct Key Rounds for each round var keySchedule = KeyExpansion(key); var blockCount = Math.ceil(plaintext.length/blockSize); var ciphertext = new Array(blockCount); // ciphertext as array of strings for (var b=0; b>> c*8) & 0xff; for (var c=0; c<4; c++) counterBlock[15-c-4] = (b/0x100000000 >>> c*8) var cipherCntr = Cipher(counterBlock, keySchedule); // -- encrypt counter block -- // calculate length of final block: var blockLength = b>> c*8) & 0xff; for (var c=0; c<4; c++) counterBlock[15-c-4] = ((b/0x100000000-1) >>> c*8) & 0xff; var cipherCntr = Cipher(counterBlock, keySchedule); // encrypt counter block ciphertext[b] = unescCtrlChars(ciphertext[b]); var pt = ''; for (var i=0; i>18 & 0x3f; h2 = bits>>12 & 0x3f; h3 = bits>>6 & 0x3f; h4 = bits & 0x3f; // end of string? index to '=' in b64 if (isNaN(o3)) h4 = 64; if (isNaN(o2)) h3 = 64; // use hexets to index into b64, and append result to encoded string enc += b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4); } while (i < str.length); return enc; } function decodeBase64(str) { var o1, o2, o3, h1, h2, h3, h4, bits, i=0, enc=''; do { // unpack four hexets into three octets using index points in b64 h1 = b64.indexOf(str.charAt(i++)); h2 = b64.indexOf(str.charAt(i++)); h3 = b64.indexOf(str.charAt(i++)); h4 = b64.indexOf(str.charAt(i++)); bits = h1<<18 | h2<<12 | h3<<6 | h4; o1 = bits>>16 & 0xff; o2 = bits>>8 & 0xff; o3 = bits & 0xff; if (h3 == 64) enc += String.fromCharCode(o1); else if (h4 == 64) enc += String.fromCharCode(o1, o2); else enc += String.fromCharCode(o1, o2, o3); } while (i < str.length); return decodeUTF8(enc); // decode UTF-8 byte-array back to Unicode } function encodeUTF8(str) { // encode multi-byte string into utf-8 multiple single-byte characters str = str.replace( /[\u0080-\u07ff]/g, // U+0080 - U+07FF = 2-byte chars function(c) { var cc = c.charCodeAt(0); return String.fromCharCode(0xc0 | cc>>6, 0x80 | cc&0x3f); } ); str = str.replace( /[\u0800-\uffff]/g, // U+0800 - U+FFFF = 3-byte chars function(c) { var cc = c.charCodeAt(0); return String.fromCharCode(0xe0 | cc>>12, 0x80 | cc>>6&0x3F, 0x80 | cc&0x3f); } ); return str; } function decodeUTF8(str) { // decode utf-8 encoded string back into multi-byte characters str = str.replace( /[\u00c0-\u00df][\u0080-\u00bf]/g, // 2-byte chars function(c) { var cc = (c.charCodeAt(0)&0x1f)<<6 | c.charCodeAt(1)&0x3f; return String.fromCharCode(cc); } ); str = str.replace( /[\u00e0-\u00ef][\u0080-\u00bf][\u0080-\u00bf]/g, // 3-byte chars function(c) { var cc = (c.charCodeAt(0)&0x0f)<<12 | (c.charCodeAt(1)&0x3f<<6) | c.charCodeAt(2)&0x3f; return String.fromCharCode(cc); } ); return str; } function byteArrayToHexStr(b) { // convert byte array to hex string for displaying test vectors var s = ''; for (var i=0; i