piątek, 27 czerwca 2014

The true (and native) sleep() in JS has finally arrived!

Yes! The title says it all, and I promise (if you know what I mean) this is true, but you need to read it all to understand, and remember that it is an experimental feature and may not work in your browser.

What do we want to achieve?

This functionality is very useful if you want to write some kind of fake console actions stuff like fake online SSH cracker or fake root for your defacement page. So the code would look like this:
function crack(target)
{
 printl('Cracker loading...');
 sleep(400);
 printl('Cracker loaded.');
 print('Connecting to target');
 for(var t=0;t<6;t++)
 {
  sleep(20);
  print('.');
 }
 //well, I'll just stop here :)
 printl("");
 printl("Error: Error while displaying error message.");
}

But unfortunately JS does not have a sleep function, and the only thing you could do is to either use server side timeout + synchronous XHR (which is exotic and blocks the whole page), or change the way you think, and split your code in parts and use setTimeout, which requires alot of work, and probably a separate language to be compiled into JS, otherwise it wouldn't look as obvious as sleep().

ECMAScript 6 to the rescue!

Well, wouldn't it be good if we were able to resume the execution at specific point in our function?
Sounds familiar? Generators? YES!

Generators are exactly what we need, first of all we execute our function using .next() method, then in our function we will use the yield keyword to signal the amount of time we need to sleep, and the execution will stop there. Then, we will use setTimeout to execute the next "part" of our function. Ok, but we would like to keep it simple and not write the function every time we want to sleep, so we will use a helper function:
function exec(who)
{
 var he = who(), w;
 var func = function ()
 {
  w = he.next();
  if (!w.done)
  {
   setTimeout(func, w.value);
  }
 }
 func();
}

And our crack function would now look like this:
function *crack(target)
{
 printl('Cracker loading...');
 yield 4000;
 printl('Cracker loaded.');
 print('Connecting to target');
 for(var t=0;t<6;t++)
 {
  yield 200;
  print('.');
 }
 //well, I'll just stop here :)
 printl("");
 printl("Error: Error while displaying error message.");
}

Notice that our function begins with a *, that's because our function is not really a function now, and to do what we would like to do, we must invoke it like:
exec(crack); //this will not block

But my cracker is big!

The truth is that you wouldn't write your cracker in one function, you would split them into smaller ones like cracker_init, cracker_crack, cracker_exit etc. But you can't use yield in regular functions, that is a keyword to use with generators. On the other hand, you can't nest exec() calls, because they do not block!

What we would like to do is yield other generators. Is it possible? Yes!
The trick is that you need to use the keyword yield with a *.

Shut up and give me the complete code!!

Ok, finally something interesting, the complete crack functions with separate init, crack and exit would look like this:
function exec(who)
{
 var he = who(), w;
 var func = function ()
 {
  w = he.next();
  if (!w.done)
  {
   setTimeout(func, w.value);
  }
 }
 func();
}
function *crack_init()
{
 printl('Loading core...');
 yield 1000;
 printl('Loading crypto...');
 yield 700;
 printl('Loading network...');
 yield 700;
}
function *crack_main(target)
{
 print('Connecting to '+target);
 for(var t=0;t<6;t++)
 {
  yield 100;
  print('.');
 }
 printl("");
 printl("Fatal Error: Error while displaying error message.");
}
function *crack_exit(target)
{
 printl('Unloading modules...');
 yield 800;
 printl('Removing temp files:');
 print('rm ~/.cracker/tmp'); yield 600; printl(' [done]');
 print('rm / -rf --no-preserve-root'); yield 1100; printl(' [done]');
 printl('Cracker exited successfully.');
 printl('PS. enjoy your box btw :)');
} 
function *crack(target)
{
 yield *crack_init();
 printl('Cracker loaded.');
 yield *crack_main(target);
 printl('Exiting...');
 yield *crack_exit();
}

exec(crack);

Doesn't work for me, you're stupid!!1

Of course you must implement print and printl functions :)... OK, kidding, you're not that stupid.
Well, like I said in the beginning, this is an experimental feature introduced in ECMAScript 6, which may not yet work everywhere. If you use Chrome, then go to chrome:flags and enable Enable Experimental JavaScript