diff --git a/src/messenger/tests/webim/libs/MibewAPIExecutionContextTest.php b/src/messenger/tests/webim/libs/MibewAPIExecutionContextTest.php new file mode 100644 index 00000000..6b78c730 --- /dev/null +++ b/src/messenger/tests/webim/libs/MibewAPIExecutionContextTest.php @@ -0,0 +1,167 @@ + 'test_function', + 'arguments' => array( + 'return' => array('microtime' => 'time'), + 'references' => array() + ) + ); + + // Wrong function's results + $wrong_results = array(); + + // Try to catch MibewAPIException with + // MibewAPIException::VARIABLE_IS_UNDEFINED_IN_RESULT code + try { + $context->storeFunctionResults($function, $wrong_results); + $this->fail("Exception must be thrown"); + } catch(MibewAPIException $e) { + $this->assertEquals( + MibewAPIException::VARIABLE_IS_UNDEFINED_IN_RESULT, + $e->getCode() + ); + } + + // Correct function's results + $results = array( + 'microtime' => 'some_microtime_value' + ); + + $context->storeFunctionResults($function, $results); + return $context; + } + + /** + * @depends testStoreFunctionResults + */ + public function testGetResults(MibewAPIExecutionContext $context) { + $results = $context->getResults(); + $this->assertEquals( + array('time' => 'some_microtime_value'), + $results + ); + //return $context; + } + + /** + * @depends testStoreFunctionResults + */ + public function testGetArgumentsList(MibewAPIExecutionContext $context) { + // Function with wrong references arguments. See Mibew API for details of 'function' + // array + $wrong_function = array( + 'function' => 'test', + 'arguments' => array( + 'return' => array(), + 'references' => array( + // Wrong function number. Execution does not have to many + // functons results + 'x' => 12 + ), + 'x' => 'microtime' + ) + ); + + // Try to catch MibewAPIException with + // MibewAPIException::WRONG_FUNCTION_NUM_IN_REFERENCE code + try { + $context->getArgumentsList($wrong_function); + $this->fail("Exception must be thrown"); + } catch(MibewAPIException $e) { + $this->assertEquals( + MibewAPIException::WRONG_FUNCTION_NUM_IN_REFERENCE, + $e->getCode() + ); + } + + // Another wrong function. + $wrong_function = array( + 'function' => 'test', + 'arguments' => array( + 'return' => array(), + 'references' => array( + // Wrong argument 'x'. This function does not have this + // argument + 'x' => 1 + ) + ) + ); + + // Try to catch MibewAPIException with + // MibewAPIException::EMPTY_VARIABLE_IN_REFERENCE code + try { + $context->getArgumentsList($wrong_function); + $this->fail("Exception must be thrown"); + } catch(MibewAPIException $e) { + $this->assertEquals( + MibewAPIException::EMPTY_VARIABLE_IN_REFERENCE, + $e->getCode() + ); + } + + // Another wrong function. + $wrong_function = array( + 'function' => 'test', + 'arguments' => array( + 'return' => array(), + 'references' => array( + 'x' => 1 + ), + // Wrong reference name. + 'x' => 'wrong_result' + ) + ); + + // Try to catch MibewAPIException with + // MibewAPIException::VARIABLE_IS_UNDEFINED_IN_REFERENCE code + try { + $context->getArgumentsList($wrong_function); + $this->fail("Exception must be thrown"); + } catch(MibewAPIException $e) { + $this->assertEquals( + MibewAPIException::VARIABLE_IS_UNDEFINED_IN_REFERENCE, + $e->getCode() + ); + } + + // Correct function. + $correct_function = array( + 'function' => 'test', + 'arguments' => array( + 'return' => array(), + 'references' => array( + 'x' => 1 + ), + 'x' => 'microtime' + ) + ); + + $arguments = $context->getArgumentsList($correct_function); + + $this->assertEquals( + array( + 'x' => 'some_microtime_value', + 'return' => array(), + 'references' => array( + 'x' => 1 + ) + ), + $arguments + ); + } + +} + +?> diff --git a/src/messenger/tests/webim/libs/MibewAPITest.php b/src/messenger/tests/webim/libs/MibewAPITest.php new file mode 100644 index 00000000..dc773b92 --- /dev/null +++ b/src/messenger/tests/webim/libs/MibewAPITest.php @@ -0,0 +1,619 @@ +assertEquals($mibew_api, MibewAPI::getAPI('base')); + } + + /** + * @depends testGetAPI + */ + public function testCheckFunction() { + $api = MibewAPI::getAPI('base'); + + // Wrong function. Function name is absent + $wrong_function = array(); + + // Try to catch MibewAPIException with + // MibewAPIException::VARIABLE_IS_UNDEFINED_IN_RESULT code + try { + $api->checkFunction($wrong_function); + $this->fail("Exception must be thrown"); + } catch (MibewAPIException $e) { + $this->assertEquals( + MibewAPIException::EMPTY_FUNCTION_NAME, + $e->getCode() + ); + } + + // Another wrong function. It have reserved name and second argument in + // MibewAPI::checkFunction() will be true. + $wrong_function = array( + 'function' => 'result' + ); + + // Try to catch MibewAPIException with MibewAPIException::FUNCTION_NAME_RESERVED + // code + try { + $api->checkFunction($wrong_function, true); + $this->fail("Exception must be thrown"); + } catch (MibewAPIException $e) { + $this->assertEquals( + MibewAPIException::FUNCTION_NAME_RESERVED, + $e->getCode() + ); + } + + // Another wrong function. It have reserved name, but second argument in + // MibewAPI::checkFunction() will be false as default. Also it have no 'arguments' + // element. + $wrong_function = array( + 'function' => 'result' + ); + + // Try to catch MibewAPIException with MibewAPIException::EMPTY_ARGUMENTS code + try { + $api->checkFunction($wrong_function); + $this->fail("Exception must be thrown"); + } catch (MibewAPIException $e) { + $this->assertEquals( + MibewAPIException::EMPTY_ARGUMENTS, + $e->getCode() + ); + } + + // Another wrong function. 'arguments' element is an empty array. + $wrong_function = array( + 'function' => 'wrong_function', + 'arguments' => array() + ); + + // Try to catch MibewAPIException with MibewAPIException::EMPTY_ARGUMENTS code + try { + $api->checkFunction($wrong_function); + $this->fail("Exception must be thrown"); + } catch (MibewAPIException $e) { + $this->assertEquals( + MibewAPIException::EMPTY_ARGUMENTS, + $e->getCode() + ); + } + + // Another wrong function. 'arguments' element is not array. + $wrong_function = array( + 'function' => 'wrong_function', + 'arguments' => 'not an array' + ); + + // Try to catch MibewAPIException with MibewAPIException::WRONG_ARGUMENTS_TYPE code + try { + $api->checkFunction($wrong_function); + $this->fail("Exception must be thrown"); + } catch (MibewAPIException $e) { + $this->assertEquals( + MibewAPIException::WRONG_ARGUMENTS_TYPE, + $e->getCode() + ); + } + + // Another wrong function. The obligatary arguments missed. + $wrong_function = array( + 'function' => 'wrong_function', + 'arguments' => array( + 'x' => 11 + ) + ); + + // Try to catch MibewAPIException with + // MibewAPIException::OBLIGATORY_ARGUMENTS_MISSED code + try { + $api->checkFunction($wrong_function); + $this->fail("Exception must be thrown"); + } catch (MibewAPIException $e) { + $this->assertEquals( + MibewAPIException::OBLIGATORY_ARGUMENTS_MISSED, + $e->getCode() + ); + } + + // Another wrong function. Some of the obligatary arguments missed. + $wrong_function = array( + 'function' => 'wrong_function', + 'arguments' => array( + 'x' => 11, + 'return' => array() + ) + ); + + // Try to catch MibewAPIException with + // MibewAPIException::OBLIGATORY_ARGUMENTS_MISSED code + try { + $api->checkFunction($wrong_function); + $this->fail("Exception must be thrown"); + } catch (MibewAPIException $e) { + $this->assertEquals( + MibewAPIException::OBLIGATORY_ARGUMENTS_MISSED, + $e->getCode() + ); + } + + // Correct function + $correct_function = array( + 'function' => 'correct_function', + 'arguments' => array( + 'return' => array('name' => 'alias'), + 'references' => array('x' => 1), + 'x' => 'argument_from_first_function', + 'argument_key' => 'argument_value' + ) + ); + + $api->checkFunction($correct_function); + + return $correct_function; + } + + /** + * @depends testCheckFunction + */ + public function testCheckRequest($correct_function) { + $api = MibewAPI::getAPI('base'); + + // Wrong request. Its 'token' element is absent. + $wrong_request = array(); + + // Try to catch MibewAPIException with MibewAPIException::EMPTY_TOKEN code + try { + $api->checkRequest($wrong_request); + $this->fail("Exception must be thrown"); + } catch (MibewAPIException $e) { + $this->assertEquals( + MibewAPIException::EMPTY_TOKEN, + $e->getCode() + ); + } + + // Wrong request. Its 'token' element is empty. + $wrong_request = array('token' => ''); + + // Try to catch MibewAPIException with MibewAPIException::EMPTY_TOKEN code + try { + $api->checkRequest($wrong_request); + $this->fail("Exception must be thrown"); + } catch (MibewAPIException $e) { + $this->assertEquals( + MibewAPIException::EMPTY_TOKEN, + $e->getCode() + ); + } + + // Wrong request. Its 'function' element is absent. + $wrong_request = array( + 'token' => 'some_test_token' + ); + + // Try to catch MibewAPIException with MibewAPIException::EMPTY_FUNCTIONS code + try { + $api->checkRequest($wrong_request); + $this->fail("Exception must be thrown"); + } catch (MibewAPIException $e) { + $this->assertEquals( + MibewAPIException::EMPTY_FUNCTIONS, + $e->getCode() + ); + } + + // Wrong request. Its 'function' element is empty. + $wrong_request = array( + 'token' => 'some_test_token', + 'functions' => array() + ); + + // Try to catch MibewAPIException with MibewAPIException::EMPTY_FUNCTIONS code + try { + $api->checkRequest($wrong_request); + $this->fail("Exception must be thrown"); + } catch (MibewAPIException $e) { + $this->assertEquals( + MibewAPIException::EMPTY_FUNCTIONS, + $e->getCode() + ); + } + + // Correct request + $correct_request = array( + 'token' => 'some_test_token', + 'functions' => array($correct_function, $correct_function) + ); + + $api->checkRequest($correct_request); + + return $correct_request; + } + + /** + * @depends testCheckRequest + */ + public function testCheckPackage($correct_request) { + $api = MibewAPI::getAPI('base'); + + $trusted_signatures = array('some_trusted_signature'); + // Wrong package. Signature element is absent. + $wrong_package = array(); + + // Try to catch MibewAPIException with MibewAPIException::EMPTY_SIGNATURE code + try { + $api->checkPackage($wrong_package, $trusted_signatures); + $this->fail("Exception must be thrown"); + } catch (MibewAPIException $e) { + $this->assertEquals( + MibewAPIException::EMPTY_SIGNATURE, + $e->getCode() + ); + } + + // Wrong package. Signature element is empty. + $wrong_package = array('signature' => ''); + + // Try to catch MibewAPIException with MibewAPIException::EMPTY_SIGNATURE code + try { + $api->checkPackage($wrong_package, $trusted_signatures); + $this->fail("Exception must be thrown"); + } catch (MibewAPIException $e) { + $this->assertEquals( + MibewAPIException::EMPTY_SIGNATURE, + $e->getCode() + ); + } + + // Wrong package. Signature is wrong. + $wrong_package = array('signature' => 'wrong_signature'); + + // Try to catch MibewAPIException with MibewAPIException::UNTRUSTED_SIGNATURE code + try { + $api->checkPackage($wrong_package, $trusted_signatures); + $this->fail("Exception must be thrown"); + } catch (MibewAPIException $e) { + $this->assertEquals( + MibewAPIException::UNTRUSTED_SIGNATURE, + $e->getCode() + ); + } + + // Wrong package. Protocol is absent. + $wrong_package = array( + 'signature' => 'some_trusted_signature' + ); + + // Try to catch MibewAPIException with MibewAPIException::EMPTY_PROTOCOL code + try { + $api->checkPackage($wrong_package, $trusted_signatures); + $this->fail("Exception must be thrown"); + } catch (MibewAPIException $e) { + $this->assertEquals( + MibewAPIException::EMPTY_PROTOCOL, + $e->getCode() + ); + } + + // Wrong package. Protocol is empty. + $wrong_package = array( + 'signature' => 'some_trusted_signature', + 'proto' => '' + ); + + // Try to catch MibewAPIException with MibewAPIException::EMPTY_PROTOCOL code + try { + $api->checkPackage($wrong_package, $trusted_signatures); + $this->fail("Exception must be thrown"); + } catch (MibewAPIException $e) { + $this->assertEquals( + MibewAPIException::EMPTY_PROTOCOL, + $e->getCode() + ); + } + + // Wrong package. Protocol is wrong. + $wrong_package = array( + 'signature' => 'some_trusted_signature', + 'proto' => 'wrong_protocol' + ); + + // Try to catch MibewAPIException with MibewAPIException::WRONG_PROTOCOL_VERSION + // code + try { + $api->checkPackage($wrong_package, $trusted_signatures); + $this->fail("Exception must be thrown"); + } catch (MibewAPIException $e) { + $this->assertEquals( + MibewAPIException::WRONG_PROTOCOL_VERSION, + $e->getCode() + ); + } + + // Wrong package. 'async' flag is absent. + $wrong_package = array( + 'signature' => 'some_trusted_signature', + 'proto' => MibewAPI::PROTOCOL_VERSION + ); + + // Try to catch MibewAPIException with MibewAPIException::ASYNC_FLAG_MISSED code + try { + $api->checkPackage($wrong_package, $trusted_signatures); + $this->fail("Exception must be thrown"); + } catch (MibewAPIException $e) { + $this->assertEquals( + MibewAPIException::ASYNC_FLAG_MISSED, + $e->getCode() + ); + } + + // Wrong package. 'async' flag is wrong. + $wrong_package = array( + 'signature' => 'some_trusted_signature', + 'proto' => MibewAPI::PROTOCOL_VERSION, + 'async' => 'wrong_async_flag' + ); + + // Try to catch MibewAPIException with MibewAPIException::WRONG_ASYNC_FLAG_VALUE + // code + try { + $api->checkPackage($wrong_package, $trusted_signatures); + $this->fail("Exception must be thrown"); + } catch (MibewAPIException $e) { + $this->assertEquals( + MibewAPIException::WRONG_ASYNC_FLAG_VALUE, + $e->getCode() + ); + } + + // Wrong package. Requests is absent. + $wrong_package = array( + 'signature' => 'some_trusted_signature', + 'proto' => MibewAPI::PROTOCOL_VERSION, + 'async' => false + ); + + // Try to catch MibewAPIException with MibewAPIException::EMPTY_REQUESTS code + try { + $api->checkPackage($wrong_package, $trusted_signatures); + $this->fail("Exception must be thrown"); + } catch (MibewAPIException $e) { + $this->assertEquals( + MibewAPIException::EMPTY_REQUESTS, + $e->getCode() + ); + } + + // Wrong package. Requests is empty. + $wrong_package = array( + 'signature' => 'some_trusted_signature', + 'proto' => MibewAPI::PROTOCOL_VERSION, + 'async' => false, + 'requests' => array() + ); + + // Try to catch MibewAPIException with MibewAPIException::EMPTY_REQUESTS code + try { + $api->checkPackage($wrong_package, $trusted_signatures); + $this->fail("Exception must be thrown"); + } catch (MibewAPIException $e) { + $this->assertEquals( + MibewAPIException::EMPTY_REQUESTS, + $e->getCode() + ); + } + + // Correct package. + $correct_package = array( + 'signature' => 'some_trusted_signature', + 'proto' => MibewAPI::PROTOCOL_VERSION, + 'async' => false, + 'requests' => array($correct_request, $correct_request) + ); + + $api->checkPackage($correct_package, $trusted_signatures); + + return $correct_package; + } + + /** + * @depends testCheckPackage + */ + public function testEncodePackage($correct_package) { + $api = MibewAPI::getAPI('base'); + + // Get package values + $requests = $correct_package['requests']; + $signature = $correct_package['signature']; + $async = $correct_package['async']; + // Encode package + $encoded_package = $api->encodePackage($requests, $signature, $async); + $this->assertEquals( + urlencode(json_encode($correct_package)), + $encoded_package + ); + return array( + 'package' => $correct_package, + 'encoded_package' => $encoded_package + ); + } + + /** + * @depends testEncodePackage + */ + public function testDecodePackage($vars) { + $api = MibewAPI::getAPI('base'); + + // Try to catch MibewAPIException with MibewAPIException::NOT_VALID_JSON code + try { + $api->decodePackage( + substr( + $vars['encoded_package'], + // Break package + ceil(strlen($vars['encoded_package']) / 2) + ), + array('some_trusted_signature') + ); + $this->fail("Exception must be thrown"); + } catch (MibewAPIException $e) { + $this->assertEquals( + MibewAPIException::NOT_VALID_JSON, + $e->getCode() + ); + } + + $this->assertEquals( + $vars['package'], + $api->decodePackage( + $vars['encoded_package'], + array('some_trusted_signature') + ) + ); + } + + /** + * @depends testGetAPI + */ + public function testGetResultFunction() { + $api = MibewAPI::getAPI('base'); + + // Wrong functions list. More than one result function. + $functions_list = array( + array( + 'function' => 'result', + 'num' => 1 + ), + array( + 'function' => 'result', + 'num' => 2 + ) + ); + + // Try to catch MibewAPIException with + // MibewAPIException::RESULT_FUNCTION_ALREADY_EXISTS code + try { + $api->getResultFunction($functions_list, null); + $this->fail("Exception must be thrown"); + } catch (MibewAPIException $e) { + $this->assertEquals( + MibewAPIException::RESULT_FUNCTION_ALREADY_EXISTS, + $e->getCode() + ); + } + + // Wrong functions list. Result function not exists, but getResultFunction's second + // argument is true + $functions_list = array(); + + // Try to catch MibewAPIException with + // MibewAPIException::NO_RESULT_FUNCTION code + try { + $api->getResultFunction($functions_list, true); + $this->fail("Exception must be thrown"); + } catch (MibewAPIException $e) { + $this->assertEquals( + MibewAPIException::NO_RESULT_FUNCTION, + $e->getCode() + ); + } + + // Wrong functions list. Result function exists, but getResultFunction's second + // argument is false + $functions_list = array( + array( + 'function' => 'result', + 'num' => 1 + ) + ); + + // Try to catch MibewAPIException with + // MibewAPIException::RESULT_FUNCTION_EXISTS code + try { + $api->getResultFunction($functions_list, false); + $this->fail("Exception must be thrown"); + } catch (MibewAPIException $e) { + $this->assertEquals( + MibewAPIException::RESULT_FUNCTION_EXISTS, + $e->getCode() + ); + } + + // Correct functions list. Result function not exists and getResultFunction's second + // argument is false + $functions_list = array(); + + $this->assertEquals(null, $api->getResultFunction($functions_list, false)); + + // Correct functions list. Result function exists and getResultFunction's second + // argument is true + $functions_list = array( + array( + 'function' => 'result', + 'num' => 1 + ) + ); + + $this->assertEquals( + $functions_list[0], + $api->getResultFunction($functions_list, true) + ); + + // Correct functions list. Result function not exists and getResultFunction's second + // argument is null + $functions_list = array(); + + $this->assertEquals(null, $api->getResultFunction($functions_list, null)); + + // Correct functions list. Result function exists and getResultFunction's second + // argument is null + $functions_list = array( + array( + 'function' => 'result', + 'num' => 1 + ) + ); + + $this->assertEquals( + $functions_list[0], + $api->getResultFunction($functions_list, null) + ); + } + + /** + * @depends testGetAPI + */ + public function testBuildResult() { + $api = MibewAPI::getAPI('base'); + + $token = 'some_test_token'; + $package = array( + 'token' => $token, + 'functions' => array( + array( + 'function' => 'result', + 'arguments' => array( + 'references' => array(), + 'return' => array(), + 'test_argument' => 'test_value' + ) + ) + ) + ); + + $this->assertEquals( + $package, + $api->buildResult($token, array('test_argument' => 'test_value')) + ); + } +} + +?> diff --git a/src/messenger/webim/libs/mibew_api.php b/src/messenger/webim/libs/mibew_api.php new file mode 100644 index 00000000..a8c00573 --- /dev/null +++ b/src/messenger/webim/libs/mibew_api.php @@ -0,0 +1,567 @@ +interaction = $interaction_type; + } + + /** + * Validate package + * + * @param array $package Package array. See Mibew API for details. + * @param array $trusted_signatures Array of trusted signatures. + * @throws MibewAPIException + */ + public function checkPackage($package, $trusted_signatures) { + // Check signature + if (empty($package['signature'])) { + throw new MibewAPIException( + "Package signature is empty", + MibewAPIException::EMPTY_SIGNATURE + ); + } + if (! in_array($package['signature'], $trusted_signatures)) { + throw new MibewAPIException( + "Package signed with untrusted signature", + MibewAPIException::UNTRUSTED_SIGNATURE + ); + } + + // Check protocol + if (empty($package['proto'])) { + throw new MibewAPIException( + "Package protocol is empty", + MibewAPIException::EMPTY_PROTOCOL + ); + } + if ($package['proto'] != self::PROTOCOL_VERSION) { + throw new MibewAPIException( + "Wrong package protocol version '{$package['proto']}'", + MibewAPIException::WRONG_PROTOCOL_VERSION + ); + } + + // Check async flag + if (! isset($package['async'])) { + throw new MibewAPIException( + "'async' flag is missed", + MibewAPIException::ASYNC_FLAG_MISSED + ); + } + if (! is_bool($package['async'])) { + throw new MibewAPIException( + "Wrong 'async' flag value", + MibewAPIException::WRONG_ASYNC_FLAG_VALUE + ); + } + + // Package must have at least one request + if (empty($package['requests'])) { + throw new MibewAPIException( + "Empty requests set", + MibewAPIException::EMPTY_REQUESTS + ); + } + // Check requests in package + foreach ($package['requests'] as $request) { + $this->checkRequest($request); + } + } + + /** + * Validate request + * + * @param array $request Request array. See Mibew API for details. + * @throws MibewAPIException + */ + public function checkRequest($request) { + // Check token + if (empty($request['token'])) { + throw new MibewAPIException( + "Empty request token", + MibewAPIException::EMPTY_TOKEN + ); + } + // Request must have at least one function + if (empty($request['functions'])) { + throw new MibewAPIException( + "Empty functions set", + MibewAPIException::EMPTY_FUNCTIONS + ); + } + // Check functions in request + foreach ($request['functions'] as $function) { + $this->checkFunction($function); + } + } + + /** + * Validate function + * + * @param array $function Function array. See Mibew API for details. + * @param boolean $filter_reserved_functions Determine if function name must not be in + * reserved list + * @throws MibewAPIException + */ + public function checkFunction($function, $filter_reserved_functions = false) { + // Check function name + if (empty($function['function'])) { + throw new MibewAPIException( + 'Cannot call for function with empty name', + MibewAPIException::EMPTY_FUNCTION_NAME + ); + } + if ($filter_reserved_functions) { + if (in_array( + $function['function'], + $this->interaction->reservedFunctionNames + )) { + throw new MibewAPIException( + "'{$function['function']}' is reserved function name", + MibewAPIException::FUNCTION_NAME_RESERVED + ); + } + } + // Check function's arguments + if (empty($function['arguments'])) { + throw new MibewAPIException( + "There are no arguments in '{$function['function']}' function", + MibewAPIException::EMPTY_ARGUMENTS + ); + } + if (! is_array($function['arguments'])) { + throw new MibewAPIException( + "Arguments must be an array", + MibewAPIException::WRONG_ARGUMENTS_TYPE + ); + } + $unset_arguments = array_diff( + $this->interaction->obligatoryArguments, + array_keys($function['arguments']) + ); + if (! empty($unset_arguments)) { + throw new MibewAPIException( + "Arguments '" . implode("', '", $unset_arguments) . "' must be set", + MibewAPIException::OBLIGATORY_ARGUMENTS_MISSED + ); + } + } + + /** + * Encodes package + * + * @param array $requests Requests array. See Mibew API for details. + * @param string $signature Sender signature. + * @param boolean $async true for asynchronous request and false for synchronous request + * @return string Ready for transfer encoded package + */ + public function encodePackage($requests, $signature, $async) { + $package = array(); + $package['signature'] = $signature; + $package['proto'] = self::PROTOCOL_VERSION; + $package['async'] = $async; + $package['requests'] = $requests; + return urlencode(json_encode($package)); + } + + /** + * Decodes package and validate package structure + * + * @param string $package Encoded package + * @param array $trusted_signatures List of trusted signatures + * @return array Decoded package array. See Mibew API for details. + * @throws MibewAPIException + */ + public function decodePackage($package, $trusted_signatures) { + $decoded_package = urldecode($package); + // JSON regular expression + $pcre_regex = '/ + (?(DEFINE) + (? -? (?= [1-9]|0(?!\d) ) \d+ (\.\d+)? ([eE] [+-]? \d+)? ) + (? true | false | null ) + (? " ([^"\\\\]* | \\\\ ["\\\\bfnrt\/] | \\\\ u [0-9a-f]{4} )* " ) + (? \[ (?: (?&json) (?: , (?&json) )* )? \s* \] ) + (? \s* (?&string) \s* : (?&json) ) + (? \{ (?: (?&pair) (?: , (?&pair) )* )? \s* \} ) + (? \s* (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) ) \s* ) + ) + \A (?&json) \Z + /six'; + // Check JSON + if (!preg_match($pcre_regex, $decoded_package)) { + // Not valid JSON + throw new MibewAPIException( + "Package have not valid json structure", + MibewAPIException::NOT_VALID_JSON + ); + } + $decoded_package = json_decode($decoded_package, true); + $this->checkPackage($decoded_package, $trusted_signatures); + return $decoded_package; + } + + /** + * Builds result package + * + * @param string $token Token of the result package + * @param array $result_arguments Arguments of result function + * @return Result package + */ + public function buildResult($token, $result_arguments) { + $arguments = $result_arguments + $this->interaction->getDefaultObligatoryArguments(); + $package = array( + 'token' => $token, + 'functions' => array( + array( + 'function' => 'result', + 'arguments' => $arguments + ) + ) + ); + return $package; + } + + /** + * Search 'result' function in $function_list. If request contains more than one result + * functions throws an MibewAPIException + * + * @param array $functions_list Array of functions. See MibewAPI for function structure + * details + * @param mixed $existance Control existance of the 'result' function in request. + * Use boolean true if 'result' function must exists in request, boolean false if must not + * and null if it doesn't matter. + * @return mixed Function array if 'result' function found and NULL otherwise + * @throws MibewAPIException + */ + public function getResultFunction ($functions_list, $existence) { + $result_function = null; + // Try to find 'result' function + foreach ($functions_list as $function) { + if ($function['function'] == 'result') { + if (! is_null($result_function)) { + // Another 'result' function found + throw new MibewAPIException( + "Function 'result' already exists in request", + MibewAPIException::RESULT_FUNCTION_ALREADY_EXISTS + ); + } + // Furst 'result' function found + $result_function = $function; + } + } + if ($existence === true && is_null($result_function)) { + // 'result' function must present in request + throw new MibewAPIException( + "There is no 'result' function in request", + MibewAPIException::NO_RESULT_FUNCTION + ); + } + if ($existence === false && !is_null($result_function)) { + // 'result' function must not present in request + throw new MibewAPIException( + "There is 'result' function in request", + MibewAPIException::RESULT_FUNCTION_EXISTS + ); + } + return $result_function; + } +} + +/** + * Implements functions execution context + */ +Class MibewAPIExecutionContext { + /** + * Values which returns after execution of all functions in request + * @var array + */ + protected $return = array(); + + /** + * Results of execution of all function in request + * @var array + */ + protected $functions_results = array(); + + /** + * Returns requets results + * + * @return array Request results + * @see MibewAPIExecutionContext::$return + */ + public function getResults () { + return $this->return; + } + + /** + * Build arguments list by replace all references by values of execution context + * + * @param array $function Function array. See MibewAPI for details. + * @return array Arguments list + * @throws MibewAPIException + */ + public function getArgumentsList ($function) { + $arguments = $function['arguments']; + $references = $function['arguments']['references']; + foreach ($references as $variable => $func_num) { + // Check target function in context + if (! isset($this->functions_results[$func_num - 1])) { + // Wrong function num + throw new MibewAPIException( + "Wrong reference in '{$function['function']}' function. " . + "Function #{$func_num} does not call yet.", + MibewAPIException::WRONG_FUNCTION_NUM_IN_REFERENCE + ); + } + + // Check reference + if (empty($arguments[$variable])) { + // Empty argument that should contains reference + throw new MibewAPIException( + "Wrong reference in '{$function['function']}' function. " . + "Empty {$variable} argument.", + MibewAPIException::EMPTY_VARIABLE_IN_REFERENCE + ); + } + $reference_to = $arguments[$variable]; + + // Check target value + if (! isset($this->functions_results[$func_num - 1][$reference_to])) { + // Undefined target value + throw new MibewAPIException( + "Wrong reference in '{$function['function']}' function. " . + "There is no '{$reference_to}' argument in #{$func_num} " . + "function results", + MibewAPIException::VARIABLE_IS_UNDEFINED_IN_REFERENCE + ); + } + + // Replace reference by target value + $arguments[$variable] = $this->functions_results[$func_num - 1][$reference_to]; + } + return $arguments; + } + + /** + * Stores functions results in execution context and add values to request result + * + * @param array $function Function array. See MibewAPI for details. + * @param array $results Associative array of the function results. + * @throws MibewAPIException + */ + public function storeFunctionResults ($function, $results) { + // Add value to request results + foreach ($function['arguments']['return'] as $name => $alias) { + if (! isset($results[$name])) { + // Value that defined in 'return' argument is undefined + throw new MibewAPIException( + "Variable with name '{$name}' is undefined in the " . + "results of the '{$function['function']}' function", + MibewAPIException::VARIABLE_IS_UNDEFINED_IN_RESULT + ); + } + $this->return[$alias] = $results[$name]; + } + // Store function results in execution context + $this->functions_results[] = $results; + } + +} + +/** + * Encapsulates interaction type + */ +abstract class MibewAPIInteraction { + /** + * Abligatory arguments in called functions + * @var array + */ + public $obligatoryArguments; + /** + * Reserved function names + * @var array + */ + public $reservedFunctionNames; + + /** + * Returns default values of obligatory arguments + * + * @return array Associative array with keys are obligatory arguments and values are default + * values of them + */ + abstract public function getDefaultObligatoryArguments(); +} + +/** + * Implements Base Mibew Interaction + */ +class MibewAPIBaseInteraction extends MibewAPIInteraction { + public $obligatoryArguments = array( + 'references', + 'return' + ); + + public $reservedFunctionNames = array( + 'result' + ); + + public function getDefaultObligatoryArguments() { + return array( + 'references' => array(), + 'return' => array() + ); + } +} + +/** + * Mibew API Exception class. + */ +class MibewAPIException extends Exception { + /** + * Async flag is missed. + */ + const ASYNC_FLAG_MISSED = 1; + /** + * There are no arguments in function + */ + const EMPTY_ARGUMENTS = 2; + /** + * Cannot call for function with empty name + */ + const EMPTY_FUNCTION_NAME = 3; + /** + * Functions set is empty + */ + const EMPTY_FUNCTIONS = 4; + /** + * Package protocol is empty + */ + const EMPTY_PROTOCOL = 5; + /** + * Requests set is empty + */ + const EMPTY_REQUESTS = 6; + /** + * Package signature is empty + */ + const EMPTY_SIGNATURE = 7; + /** + * Request token is empty + */ + const EMPTY_TOKEN = 8; + /** + * Wrong reference. Reference variable is empty + */ + const EMPTY_VARIABLE_IN_REFERENCE = 9; + /** + * This function name is reserved + */ + const FUNCTION_NAME_RESERVED = 10; + /** + * There is no result function + */ + const NO_RESULT_FUNCTION = 11; + /** + * Package have not valid JSON structure + */ + const NOT_VALID_JSON = 12; + /** + * Some of the function's obligatory arguments are missed + */ + const OBLIGATORY_ARGUMENTS_MISSED = 13; + /** + * Request contains more than one result functions + */ + const RESULT_FUNCTION_ALREADY_EXISTS = 14; + /** + * There is 'result' function in request + */ + const RESULT_FUNCTION_EXISTS = 15; + /** + * Package signed with untrusted signature + */ + const UNTRUSTED_SIGNATURE = 16; + /** + * Wrong reference. Variable is undefined in functions results + */ + const VARIABLE_IS_UNDEFINED_IN_REFERENCE = 17; + /** + * Variable is undefined in function's results + */ + const VARIABLE_IS_UNDEFINED_IN_RESULT = 18; + /** + * Arguments must be an array + */ + const WRONG_ARGUMENTS_TYPE = 19; + /** + * Async flag value is wrong + */ + const WRONG_ASYNC_FLAG_VALUE = 20; + /** + * Wrong reference. Function with this number does not call yet + */ + const WRONG_FUNCTION_NUM_IN_REFERENCE = 21; + /** + * Wrong interaction type + */ + const WRONG_INTERACTION_TYPE = 22; + /** + * Wrong package protocol version + */ + const WRONG_PROTOCOL_VERSION = 23; +} + +?> \ No newline at end of file