pecl-libsodium to php72-sodium compat lib
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env php | |
<?php | |
/* | |
* generates the code for sodium_compat.php | |
*/ | |
define('BASE', dirname(dirname(__FILE__)) . '/'); | |
$lib = '<?php | |
/* | |
* pecl-libsodium to php72-sodium compat lib | |
* | |
* gist: https://gist.github.com/oschonrock/a3e0490e0d410de2803a0e1ce762026f | |
* | |
* The purpose of this small compat lib is to make the migration from pecl-libsodium to php72-sodium easier | |
* the RFC process makign Sodium a core php extension: https://wiki.php.net/rfc/libsodium | |
* decided to use global namespace for the functions and constants provided by the new php72-sodium | |
* | |
* This creates some small problems for those who were already using pecl-lisodium with its \Sodium\ functions and constants | |
* | |
* Usually when ugrading php on a server, the code changes are done ahead of upgrade time and then the sysadmin upgrades the machine. | |
* Because the upgraded code (using sodium_* functions and SODIUM_* constants) will not run with pecl-lisodium, this lib provides a compat layer. | |
* | |
* It wraps all the old \Sodium\ functions and constants in sodium_* and SODIUM_* new style functions and constants | |
* | |
* How to use: | |
* 1. Just require this file in your application bootstrap, before you upgrade to php72 and the new php72-sodium extension | |
* 2. make your code compat with the new signatures (ie change all your calls to \Sodium\* to (sodium_|SODIUM_)* ) | |
* 3. commit and deploy. Your new code can run against the old pecl extension now | |
* 4. When the sysadmin upgrades to php72 and ext-sodium, it will already "magically" work | |
* | |
* \Sodium\randombytes_buf (particularly usesful for people upgrading from php56) | |
* If you were using this rather than the core php function random_bytes(), then this lib helps with that as well | |
* it is not quite part of the above pattern, because the new php72-sodium extension does not include \Sodium\randombytes_buf | |
* you are supposed to use random_bytes() now. But if you still have php56 then you were probably using \Sodium\randombytes_buf | |
* so there is a wrapper for that as well. | |
* | |
* This code was generated by a script which uses get_defined_constants() and get_defined_functions() and then "writes" the wrappers | |
* That generator script is also part of this gist. | |
* | |
* LICENSE WhateverYouWantItToBe | |
* author: oliver at schonrocks dot com | |
* | |
*/ | |
if (!function_exists(\'sodium_crypto_box_open\') && function_exists(\'\Sodium\crypto_box_open\')) | |
{ | |
'; | |
foreach (get_defined_constants() as $const => $value) | |
{ | |
if ((strpos($const, 'Sodium\\') === 0)) | |
{ | |
$gconst = strtoupper(str_replace('Sodium\\', 'Sodium_', $const)); | |
$lib .= ' define(\'' . $gconst . '\', \\' . $const . ');' . "\n"; | |
} | |
} | |
$lib .= "\n\n"; | |
foreach (get_defined_functions()['internal'] as $func) | |
{ | |
if ((strpos($func, 'sodium\\') === 0)) | |
{ | |
$gfunc = str_replace('sodium\\', 'sodium_', $func); | |
$lib .= ' function ' . $gfunc . '(...$p) { return \\' . ucfirst($func) . '(...$p); }' . "\n"; | |
} | |
} | |
$lib .= ' | |
} | |
if (!function_exists(\'random_bytes\') && function_exists(\'\Sodium\randombytes_buf\')) | |
{ | |
// and migrate to the generic crypto stream random generator | |
function random_bytes(...$p) { return \Sodium\randombytes_buf(...$p); } | |
} | |
'; | |
// just build once | |
file_put_contents(BASE . 'lib/sodium_compat.php', $lib); | |
// test | |
require(BASE . 'lib/sodium_compat.php'); | |
var_dump(SODIUM_CRYPTO_AUTH_BYTES); | |
$key = random_bytes(SODIUM_CRYPTO_AUTH_KEYBYTES); | |
var_dump(bin2hex($key)); | |
$msg = 'some garbage messgae'; | |
var_dump(sodium_crypto_auth_verify(sodium_crypto_auth($msg, $key), $msg, $key)); | |
var_dump(SODIUM_CRYPTO_AUTH_KEYBYTES); | |
$bad_sig = random_bytes(SODIUM_CRYPTO_AUTH_BYTES); | |
var_dump(bin2hex($bad_sig)); | |
var_dump(sodium_crypto_auth_verify($bad_sig, $msg, $key)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* | |
* pecl-libsodium to php72-sodium compat lib | |
* | |
* The purpose of this small compat lib is to make the migration from pecl-libsodium to php72-sodium easier | |
* the RFC process makign Sodium a core php extension: https://wiki.php.net/rfc/libsodium | |
* decided to use global namespace for the functions and constants provided by the new php72-sodium | |
* | |
* This creates some small problems for those who were already using pecl-lisodium with its \Sodium\ functions and constants | |
* | |
* Usually when ugrading php on a server, the code changes are done ahead of upgrade time and then the sysadmin upgrades the machine. | |
* Because the upgraded code (using sodium_* functions and SODIUM_* constants) will not run with pecl-lisodium, this lib provides a compat layer. | |
* | |
* It wraps all the old \Sodium\ functions and constants in sodium_* and SODIUM_* new style functions and constants | |
* | |
* How to use: | |
* 1. Just require this file in your application bootstrap, before you upgrade to php72 and the new php72-sodium extension | |
* 2. make your code compat with the new signatures (ie change all your calls to \Sodium\* to (sodium_|SODIUM_)* ) | |
* 3. commit and deploy. Your new code can run against the old pecl extension now | |
* 4. When the sysadmin upgrades to php72 and ext-sodium, it will already "magically" work | |
* | |
* \Sodium\randombytes_buf (particularly usesful for people upgrading from php56) | |
* If you were using this rather than the core php function random_bytes(), then this lib helps with that as well | |
* it is not quite part of the above pattern, because the new php72-sodium extension does not include \Sodium\randombytes_buf | |
* you are supposed to use random_bytes() now. But if you still have php56 then you were probably using \Sodium\randombytes_buf | |
* so there is a wrapper for that as well. | |
* | |
* This code was generated by a script which uses get_defined_constants() and get_defined_functions() and then "writes" the wrappers | |
* That generator script is also part of this gist. | |
* | |
* LICENSE WhateverYouWantItToBe | |
* author: oliver at schonrocks dot com | |
* | |
*/ | |
if (!function_exists('sodium_crypto_box_open') && function_exists('\Sodium\crypto_box_open')) | |
{ | |
define('SODIUM_CRYPTO_AEAD_AES256GCM_KEYBYTES', \Sodium\CRYPTO_AEAD_AES256GCM_KEYBYTES); | |
define('SODIUM_CRYPTO_AEAD_AES256GCM_NSECBYTES', \Sodium\CRYPTO_AEAD_AES256GCM_NSECBYTES); | |
define('SODIUM_CRYPTO_AEAD_AES256GCM_NPUBBYTES', \Sodium\CRYPTO_AEAD_AES256GCM_NPUBBYTES); | |
define('SODIUM_CRYPTO_AEAD_AES256GCM_ABYTES', \Sodium\CRYPTO_AEAD_AES256GCM_ABYTES); | |
define('SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES', \Sodium\CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES); | |
define('SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_NSECBYTES', \Sodium\CRYPTO_AEAD_CHACHA20POLY1305_NSECBYTES); | |
define('SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_NPUBBYTES', \Sodium\CRYPTO_AEAD_CHACHA20POLY1305_NPUBBYTES); | |
define('SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_ABYTES', \Sodium\CRYPTO_AEAD_CHACHA20POLY1305_ABYTES); | |
define('SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_IETF_KEYBYTES', \Sodium\CRYPTO_AEAD_CHACHA20POLY1305_IETF_KEYBYTES); | |
define('SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_IETF_NSECBYTES', \Sodium\CRYPTO_AEAD_CHACHA20POLY1305_IETF_NSECBYTES); | |
define('SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES', \Sodium\CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES); | |
define('SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES', \Sodium\CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES); | |
define('SODIUM_CRYPTO_AUTH_BYTES', \Sodium\CRYPTO_AUTH_BYTES); | |
define('SODIUM_CRYPTO_AUTH_KEYBYTES', \Sodium\CRYPTO_AUTH_KEYBYTES); | |
define('SODIUM_CRYPTO_BOX_SEALBYTES', \Sodium\CRYPTO_BOX_SEALBYTES); | |
define('SODIUM_CRYPTO_BOX_SECRETKEYBYTES', \Sodium\CRYPTO_BOX_SECRETKEYBYTES); | |
define('SODIUM_CRYPTO_BOX_PUBLICKEYBYTES', \Sodium\CRYPTO_BOX_PUBLICKEYBYTES); | |
define('SODIUM_CRYPTO_BOX_KEYPAIRBYTES', \Sodium\CRYPTO_BOX_KEYPAIRBYTES); | |
define('SODIUM_CRYPTO_BOX_MACBYTES', \Sodium\CRYPTO_BOX_MACBYTES); | |
define('SODIUM_CRYPTO_BOX_NONCEBYTES', \Sodium\CRYPTO_BOX_NONCEBYTES); | |
define('SODIUM_CRYPTO_BOX_SEEDBYTES', \Sodium\CRYPTO_BOX_SEEDBYTES); | |
define('SODIUM_CRYPTO_KX_BYTES', \Sodium\CRYPTO_KX_BYTES); | |
define('SODIUM_CRYPTO_KX_PUBLICKEYBYTES', \Sodium\CRYPTO_KX_PUBLICKEYBYTES); | |
define('SODIUM_CRYPTO_KX_SECRETKEYBYTES', \Sodium\CRYPTO_KX_SECRETKEYBYTES); | |
define('SODIUM_CRYPTO_GENERICHASH_BYTES', \Sodium\CRYPTO_GENERICHASH_BYTES); | |
define('SODIUM_CRYPTO_GENERICHASH_BYTES_MIN', \Sodium\CRYPTO_GENERICHASH_BYTES_MIN); | |
define('SODIUM_CRYPTO_GENERICHASH_BYTES_MAX', \Sodium\CRYPTO_GENERICHASH_BYTES_MAX); | |
define('SODIUM_CRYPTO_GENERICHASH_KEYBYTES', \Sodium\CRYPTO_GENERICHASH_KEYBYTES); | |
define('SODIUM_CRYPTO_GENERICHASH_KEYBYTES_MIN', \Sodium\CRYPTO_GENERICHASH_KEYBYTES_MIN); | |
define('SODIUM_CRYPTO_GENERICHASH_KEYBYTES_MAX', \Sodium\CRYPTO_GENERICHASH_KEYBYTES_MAX); | |
define('SODIUM_CRYPTO_PWHASH_SALTBYTES', \Sodium\CRYPTO_PWHASH_SALTBYTES); | |
define('SODIUM_CRYPTO_PWHASH_STRPREFIX', \Sodium\CRYPTO_PWHASH_STRPREFIX); | |
define('SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE', \Sodium\CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE); | |
define('SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE', \Sodium\CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE); | |
define('SODIUM_CRYPTO_PWHASH_OPSLIMIT_MODERATE', \Sodium\CRYPTO_PWHASH_OPSLIMIT_MODERATE); | |
define('SODIUM_CRYPTO_PWHASH_MEMLIMIT_MODERATE', \Sodium\CRYPTO_PWHASH_MEMLIMIT_MODERATE); | |
define('SODIUM_CRYPTO_PWHASH_OPSLIMIT_SENSITIVE', \Sodium\CRYPTO_PWHASH_OPSLIMIT_SENSITIVE); | |
define('SODIUM_CRYPTO_PWHASH_MEMLIMIT_SENSITIVE', \Sodium\CRYPTO_PWHASH_MEMLIMIT_SENSITIVE); | |
define('SODIUM_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_SALTBYTES', \Sodium\CRYPTO_PWHASH_SCRYPTSALSA208SHA256_SALTBYTES); | |
define('SODIUM_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_STRPREFIX', \Sodium\CRYPTO_PWHASH_SCRYPTSALSA208SHA256_STRPREFIX); | |
define('SODIUM_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_OPSLIMIT_INTERACTIVE', \Sodium\CRYPTO_PWHASH_SCRYPTSALSA208SHA256_OPSLIMIT_INTERACTIVE); | |
define('SODIUM_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_MEMLIMIT_INTERACTIVE', \Sodium\CRYPTO_PWHASH_SCRYPTSALSA208SHA256_MEMLIMIT_INTERACTIVE); | |
define('SODIUM_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_OPSLIMIT_SENSITIVE', \Sodium\CRYPTO_PWHASH_SCRYPTSALSA208SHA256_OPSLIMIT_SENSITIVE); | |
define('SODIUM_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_MEMLIMIT_SENSITIVE', \Sodium\CRYPTO_PWHASH_SCRYPTSALSA208SHA256_MEMLIMIT_SENSITIVE); | |
define('SODIUM_CRYPTO_SCALARMULT_BYTES', \Sodium\CRYPTO_SCALARMULT_BYTES); | |
define('SODIUM_CRYPTO_SCALARMULT_SCALARBYTES', \Sodium\CRYPTO_SCALARMULT_SCALARBYTES); | |
define('SODIUM_CRYPTO_SHORTHASH_BYTES', \Sodium\CRYPTO_SHORTHASH_BYTES); | |
define('SODIUM_CRYPTO_SHORTHASH_KEYBYTES', \Sodium\CRYPTO_SHORTHASH_KEYBYTES); | |
define('SODIUM_CRYPTO_SECRETBOX_KEYBYTES', \Sodium\CRYPTO_SECRETBOX_KEYBYTES); | |
define('SODIUM_CRYPTO_SECRETBOX_MACBYTES', \Sodium\CRYPTO_SECRETBOX_MACBYTES); | |
define('SODIUM_CRYPTO_SECRETBOX_NONCEBYTES', \Sodium\CRYPTO_SECRETBOX_NONCEBYTES); | |
define('SODIUM_CRYPTO_SIGN_BYTES', \Sodium\CRYPTO_SIGN_BYTES); | |
define('SODIUM_CRYPTO_SIGN_SEEDBYTES', \Sodium\CRYPTO_SIGN_SEEDBYTES); | |
define('SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES', \Sodium\CRYPTO_SIGN_PUBLICKEYBYTES); | |
define('SODIUM_CRYPTO_SIGN_SECRETKEYBYTES', \Sodium\CRYPTO_SIGN_SECRETKEYBYTES); | |
define('SODIUM_CRYPTO_SIGN_KEYPAIRBYTES', \Sodium\CRYPTO_SIGN_KEYPAIRBYTES); | |
define('SODIUM_CRYPTO_STREAM_NONCEBYTES', \Sodium\CRYPTO_STREAM_NONCEBYTES); | |
define('SODIUM_CRYPTO_STREAM_KEYBYTES', \Sodium\CRYPTO_STREAM_KEYBYTES); | |
function sodium_crypto_aead_aes256gcm_is_available(...$p) { return \Sodium\crypto_aead_aes256gcm_is_available(...$p); } | |
function sodium_crypto_aead_aes256gcm_decrypt(...$p) { return \Sodium\crypto_aead_aes256gcm_decrypt(...$p); } | |
function sodium_crypto_aead_aes256gcm_encrypt(...$p) { return \Sodium\crypto_aead_aes256gcm_encrypt(...$p); } | |
function sodium_crypto_aead_chacha20poly1305_decrypt(...$p) { return \Sodium\crypto_aead_chacha20poly1305_decrypt(...$p); } | |
function sodium_crypto_aead_chacha20poly1305_encrypt(...$p) { return \Sodium\crypto_aead_chacha20poly1305_encrypt(...$p); } | |
function sodium_crypto_aead_chacha20poly1305_ietf_decrypt(...$p) { return \Sodium\crypto_aead_chacha20poly1305_ietf_decrypt(...$p); } | |
function sodium_crypto_aead_chacha20poly1305_ietf_encrypt(...$p) { return \Sodium\crypto_aead_chacha20poly1305_ietf_encrypt(...$p); } | |
function sodium_crypto_auth(...$p) { return \Sodium\crypto_auth(...$p); } | |
function sodium_crypto_auth_verify(...$p) { return \Sodium\crypto_auth_verify(...$p); } | |
function sodium_crypto_box(...$p) { return \Sodium\crypto_box(...$p); } | |
function sodium_crypto_box_keypair(...$p) { return \Sodium\crypto_box_keypair(...$p); } | |
function sodium_crypto_box_seed_keypair(...$p) { return \Sodium\crypto_box_seed_keypair(...$p); } | |
function sodium_crypto_box_keypair_from_secretkey_and_publickey(...$p) { return \Sodium\crypto_box_keypair_from_secretkey_and_publickey(...$p); } | |
function sodium_crypto_box_open(...$p) { return \Sodium\crypto_box_open(...$p); } | |
function sodium_crypto_box_publickey(...$p) { return \Sodium\crypto_box_publickey(...$p); } | |
function sodium_crypto_box_publickey_from_secretkey(...$p) { return \Sodium\crypto_box_publickey_from_secretkey(...$p); } | |
function sodium_crypto_box_seal(...$p) { return \Sodium\crypto_box_seal(...$p); } | |
function sodium_crypto_box_seal_open(...$p) { return \Sodium\crypto_box_seal_open(...$p); } | |
function sodium_crypto_box_secretkey(...$p) { return \Sodium\crypto_box_secretkey(...$p); } | |
function sodium_crypto_kx(...$p) { return \Sodium\crypto_kx(...$p); } | |
function sodium_crypto_generichash(...$p) { return \Sodium\crypto_generichash(...$p); } | |
function sodium_crypto_generichash_init(...$p) { return \Sodium\crypto_generichash_init(...$p); } | |
function sodium_crypto_generichash_update(...$p) { return \Sodium\crypto_generichash_update(...$p); } | |
function sodium_crypto_generichash_final(...$p) { return \Sodium\crypto_generichash_final(...$p); } | |
function sodium_crypto_pwhash(...$p) { return \Sodium\crypto_pwhash(...$p); } | |
function sodium_crypto_pwhash_str(...$p) { return \Sodium\crypto_pwhash_str(...$p); } | |
function sodium_crypto_pwhash_str_verify(...$p) { return \Sodium\crypto_pwhash_str_verify(...$p); } | |
function sodium_crypto_pwhash_scryptsalsa208sha256(...$p) { return \Sodium\crypto_pwhash_scryptsalsa208sha256(...$p); } | |
function sodium_crypto_pwhash_scryptsalsa208sha256_str(...$p) { return \Sodium\crypto_pwhash_scryptsalsa208sha256_str(...$p); } | |
function sodium_crypto_pwhash_scryptsalsa208sha256_str_verify(...$p) { return \Sodium\crypto_pwhash_scryptsalsa208sha256_str_verify(...$p); } | |
function sodium_crypto_scalarmult(...$p) { return \Sodium\crypto_scalarmult(...$p); } | |
function sodium_crypto_secretbox(...$p) { return \Sodium\crypto_secretbox(...$p); } | |
function sodium_crypto_secretbox_open(...$p) { return \Sodium\crypto_secretbox_open(...$p); } | |
function sodium_crypto_shorthash(...$p) { return \Sodium\crypto_shorthash(...$p); } | |
function sodium_crypto_sign(...$p) { return \Sodium\crypto_sign(...$p); } | |
function sodium_crypto_sign_detached(...$p) { return \Sodium\crypto_sign_detached(...$p); } | |
function sodium_crypto_sign_ed25519_pk_to_curve25519(...$p) { return \Sodium\crypto_sign_ed25519_pk_to_curve25519(...$p); } | |
function sodium_crypto_sign_ed25519_sk_to_curve25519(...$p) { return \Sodium\crypto_sign_ed25519_sk_to_curve25519(...$p); } | |
function sodium_crypto_sign_keypair(...$p) { return \Sodium\crypto_sign_keypair(...$p); } | |
function sodium_crypto_sign_keypair_from_secretkey_and_publickey(...$p) { return \Sodium\crypto_sign_keypair_from_secretkey_and_publickey(...$p); } | |
function sodium_crypto_sign_open(...$p) { return \Sodium\crypto_sign_open(...$p); } | |
function sodium_crypto_sign_publickey(...$p) { return \Sodium\crypto_sign_publickey(...$p); } | |
function sodium_crypto_sign_secretkey(...$p) { return \Sodium\crypto_sign_secretkey(...$p); } | |
function sodium_crypto_sign_publickey_from_secretkey(...$p) { return \Sodium\crypto_sign_publickey_from_secretkey(...$p); } | |
function sodium_crypto_sign_seed_keypair(...$p) { return \Sodium\crypto_sign_seed_keypair(...$p); } | |
function sodium_crypto_sign_verify_detached(...$p) { return \Sodium\crypto_sign_verify_detached(...$p); } | |
function sodium_crypto_stream(...$p) { return \Sodium\crypto_stream(...$p); } | |
function sodium_crypto_stream_xor(...$p) { return \Sodium\crypto_stream_xor(...$p); } | |
function sodium_randombytes_buf(...$p) { return \Sodium\randombytes_buf(...$p); } | |
function sodium_randombytes_random16(...$p) { return \Sodium\randombytes_random16(...$p); } | |
function sodium_randombytes_uniform(...$p) { return \Sodium\randombytes_uniform(...$p); } | |
function sodium_bin2hex(...$p) { return \Sodium\bin2hex(...$p); } | |
function sodium_compare(...$p) { return \Sodium\compare(...$p); } | |
function sodium_hex2bin(...$p) { return \Sodium\hex2bin(...$p); } | |
function sodium_increment(...$p) { return \Sodium\increment(...$p); } | |
function sodium_add(...$p) { return \Sodium\add(...$p); } | |
function sodium_library_version_major(...$p) { return \Sodium\library_version_major(...$p); } | |
function sodium_library_version_minor(...$p) { return \Sodium\library_version_minor(...$p); } | |
function sodium_memcmp(...$p) { return \Sodium\memcmp(...$p); } | |
function sodium_memzero(...$p) { return \Sodium\memzero(...$p); } | |
function sodium_version_string(...$p) { return \Sodium\version_string(...$p); } | |
function sodium_crypto_scalarmult_base(...$p) { return \Sodium\crypto_scalarmult_base(...$p); } | |
} | |
if (!function_exists('random_bytes') && function_exists('\Sodium\randombytes_buf')) | |
{ | |
// and migrate to the generic crypto stream random generator | |
function random_bytes(...$p) { return \Sodium\randombytes_buf(...$p); } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This file https://github.com/paragonie/sodium_compat/blob/master/lib/php72compat.php
in the (larger & more complete) https://github.com/paragonie/sodium_compat Paragonie Compatibility library does something very similar without using code generation, the "..." operator and calling the bigger ParagonIE_Sodium_Compat::* wrappers.
Your mileage may vary.
If you want a slim (and temporary) compat layer, this gist will probably do the job.