| 
									
										
										
										
											2016-01-25 23:28:57 +08:00
										 |  |  | =pod | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | =head1 NAME | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ASYNC_WAIT_CTX_new, ASYNC_WAIT_CTX_free, ASYNC_WAIT_CTX_set_wait_fd, | 
					
						
							|  |  |  | ASYNC_WAIT_CTX_get_fd, ASYNC_WAIT_CTX_get_all_fds, | 
					
						
							| 
									
										
										
										
											2018-11-06 04:41:01 +08:00
										 |  |  | ASYNC_WAIT_CTX_get_changed_fds, ASYNC_WAIT_CTX_clear_fd, | 
					
						
							|  |  |  | ASYNC_WAIT_CTX_set_callback, ASYNC_WAIT_CTX_get_callback, | 
					
						
							|  |  |  | ASYNC_WAIT_CTX_set_status, ASYNC_WAIT_CTX_get_status, ASYNC_callback_fn, | 
					
						
							|  |  |  | ASYNC_STATUS_UNSUPPORTED, ASYNC_STATUS_ERR, ASYNC_STATUS_OK, | 
					
						
							|  |  |  | ASYNC_STATUS_EAGAIN | 
					
						
							|  |  |  | - functions to manage waiting for asynchronous jobs to complete | 
					
						
							| 
									
										
										
										
											2016-01-25 23:28:57 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | =head1 SYNOPSIS | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |  #include <openssl/async.h> | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-06 04:41:01 +08:00
										 |  |  |  #define ASYNC_STATUS_UNSUPPORTED    0 | 
					
						
							|  |  |  |  #define ASYNC_STATUS_ERR            1 | 
					
						
							|  |  |  |  #define ASYNC_STATUS_OK             2 | 
					
						
							|  |  |  |  #define ASYNC_STATUS_EAGAIN         3 | 
					
						
							|  |  |  |  typedef int (*ASYNC_callback_fn)(void *arg); | 
					
						
							| 
									
										
										
										
											2016-01-25 23:28:57 +08:00
										 |  |  |  ASYNC_WAIT_CTX *ASYNC_WAIT_CTX_new(void); | 
					
						
							|  |  |  |  void ASYNC_WAIT_CTX_free(ASYNC_WAIT_CTX *ctx); | 
					
						
							|  |  |  |  int ASYNC_WAIT_CTX_set_wait_fd(ASYNC_WAIT_CTX *ctx, const void *key, | 
					
						
							|  |  |  |                                 OSSL_ASYNC_FD fd, | 
					
						
							|  |  |  |                                 void *custom_data, | 
					
						
							|  |  |  |                                 void (*cleanup)(ASYNC_WAIT_CTX *, const void *, | 
					
						
							| 
									
										
										
										
											2017-01-21 02:58:49 +08:00
										 |  |  |                                                 OSSL_ASYNC_FD, void *)); | 
					
						
							| 
									
										
										
										
											2016-01-25 23:28:57 +08:00
										 |  |  |  int ASYNC_WAIT_CTX_get_fd(ASYNC_WAIT_CTX *ctx, const void *key, | 
					
						
							|  |  |  |                            OSSL_ASYNC_FD *fd, void **custom_data); | 
					
						
							|  |  |  |  int ASYNC_WAIT_CTX_get_all_fds(ASYNC_WAIT_CTX *ctx, OSSL_ASYNC_FD *fd, | 
					
						
							|  |  |  |                                 size_t *numfds); | 
					
						
							|  |  |  |  int ASYNC_WAIT_CTX_get_changed_fds(ASYNC_WAIT_CTX *ctx, OSSL_ASYNC_FD *addfd, | 
					
						
							|  |  |  |                                     size_t *numaddfds, OSSL_ASYNC_FD *delfd, | 
					
						
							|  |  |  |                                     size_t *numdelfds); | 
					
						
							|  |  |  |  int ASYNC_WAIT_CTX_clear_fd(ASYNC_WAIT_CTX *ctx, const void *key); | 
					
						
							| 
									
										
										
										
											2018-11-06 04:41:01 +08:00
										 |  |  |  int ASYNC_WAIT_CTX_set_callback(ASYNC_WAIT_CTX *ctx, | 
					
						
							|  |  |  |                                  ASYNC_callback_fn callback, | 
					
						
							|  |  |  |                                  void *callback_arg); | 
					
						
							|  |  |  |  int ASYNC_WAIT_CTX_get_callback(ASYNC_WAIT_CTX *ctx, | 
					
						
							|  |  |  |                                  ASYNC_callback_fn *callback, | 
					
						
							|  |  |  |                                  void **callback_arg); | 
					
						
							|  |  |  |  int ASYNC_WAIT_CTX_set_status(ASYNC_WAIT_CTX *ctx, int status); | 
					
						
							|  |  |  |  int ASYNC_WAIT_CTX_get_status(ASYNC_WAIT_CTX *ctx); | 
					
						
							| 
									
										
										
										
											2016-01-25 23:28:57 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | =head1 DESCRIPTION | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | For an overview of how asynchronous operations are implemented in OpenSSL see | 
					
						
							| 
									
										
										
										
											2019-09-28 14:45:05 +08:00
										 |  |  | L<ASYNC_start_job(3)>. An B<ASYNC_WAIT_CTX> object represents an asynchronous | 
					
						
							| 
									
										
										
										
											2016-01-25 23:28:57 +08:00
										 |  |  | "session", i.e. a related set of crypto operations. For example in SSL terms | 
					
						
							|  |  |  | this would have a one-to-one correspondence with an SSL connection. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-28 14:45:05 +08:00
										 |  |  | Application code must create an B<ASYNC_WAIT_CTX> using the ASYNC_WAIT_CTX_new() | 
					
						
							| 
									
										
										
										
											2016-01-25 23:28:57 +08:00
										 |  |  | function prior to calling ASYNC_start_job() (see L<ASYNC_start_job(3)>). When | 
					
						
							| 
									
										
										
										
											2019-09-28 14:45:05 +08:00
										 |  |  | the job is started it is associated with the B<ASYNC_WAIT_CTX> for the duration | 
					
						
							|  |  |  | of that job. An B<ASYNC_WAIT_CTX> should only be used for one B<ASYNC_JOB> at | 
					
						
							|  |  |  | any one time, but can be reused after an B<ASYNC_JOB> has finished for a | 
					
						
							|  |  |  | subsequent B<ASYNC_JOB>. When the session is complete (e.g. the SSL connection | 
					
						
							|  |  |  | is closed), application code cleans up with ASYNC_WAIT_CTX_free(). | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | B<ASYNC_WAIT_CTX>s can have "wait" file descriptors associated with them. | 
					
						
							|  |  |  | Calling ASYNC_WAIT_CTX_get_all_fds() and passing in a pointer to an | 
					
						
							|  |  |  | B<ASYNC_WAIT_CTX> in the I<ctx> parameter will return the wait file descriptors | 
					
						
							|  |  |  | associated with that job in I<*fd>. The number of file descriptors returned will | 
					
						
							|  |  |  | be stored in I<*numfds>. It is the caller's responsibility to ensure that | 
					
						
							|  |  |  | sufficient memory has been allocated in I<*fd> to receive all the file | 
					
						
							|  |  |  | descriptors. Calling ASYNC_WAIT_CTX_get_all_fds() with a NULL I<fd> value will | 
					
						
							| 
									
										
										
										
											2020-06-30 03:13:07 +08:00
										 |  |  | return no file descriptors but will still populate I<*numfds>. Therefore, | 
					
						
							| 
									
										
										
										
											2019-09-28 14:45:05 +08:00
										 |  |  | application code is typically expected to call this function twice: once to get | 
					
						
							|  |  |  | the number of fds, and then again when sufficient memory has been allocated. If | 
					
						
							|  |  |  | only one asynchronous engine is being used then normally this call will only | 
					
						
							|  |  |  | ever return one fd. If multiple asynchronous engines are being used then more | 
					
						
							|  |  |  | could be returned. | 
					
						
							| 
									
										
										
										
											2016-01-25 23:28:57 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-16 16:58:30 +08:00
										 |  |  | The function ASYNC_WAIT_CTX_get_changed_fds() can be used to detect if any fds | 
					
						
							| 
									
										
										
										
											2019-09-28 14:45:05 +08:00
										 |  |  | have changed since the last call time ASYNC_start_job() returned B<ASYNC_PAUSE> | 
					
						
							|  |  |  | (or since the B<ASYNC_WAIT_CTX> was created if no B<ASYNC_PAUSE> result has | 
					
						
							|  |  |  | been received). The I<numaddfds> and I<numdelfds> parameters will be populated | 
					
						
							|  |  |  | with the number of fds added or deleted respectively. I<*addfd> and I<*delfd> | 
					
						
							| 
									
										
										
										
											2016-01-25 23:28:57 +08:00
										 |  |  | will be populated with the list of added and deleted fds respectively. Similarly | 
					
						
							|  |  |  | to ASYNC_WAIT_CTX_get_all_fds() either of these can be NULL, but if they are not | 
					
						
							|  |  |  | NULL then the caller is responsible for ensuring sufficient memory is allocated. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Implementors of async aware code (e.g. engines) are encouraged to return a | 
					
						
							| 
									
										
										
										
											2019-09-28 14:45:05 +08:00
										 |  |  | stable fd for the lifetime of the B<ASYNC_WAIT_CTX> in order to reduce the | 
					
						
							|  |  |  | "churn" of regularly changing fds - although no guarantees of this are provided | 
					
						
							|  |  |  | to applications. | 
					
						
							| 
									
										
										
										
											2016-01-25 23:28:57 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | Applications can wait for the file descriptor to be ready for "read" using a | 
					
						
							|  |  |  | system function call such as select or poll (being ready for "read" indicates | 
					
						
							|  |  |  | that the job should be resumed). If no file descriptor is made available then an | 
					
						
							|  |  |  | application will have to periodically "poll" the job by attempting to restart it | 
					
						
							|  |  |  | to see if it is ready to continue. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-28 14:45:05 +08:00
										 |  |  | Async aware code (e.g. engines) can get the current B<ASYNC_WAIT_CTX> from the | 
					
						
							|  |  |  | job via L<ASYNC_get_wait_ctx(3)> and provide a file descriptor to use for | 
					
						
							|  |  |  | waiting on by calling ASYNC_WAIT_CTX_set_wait_fd(). Typically this would be done | 
					
						
							|  |  |  | by an engine immediately prior to calling ASYNC_pause_job() and not by end user | 
					
						
							|  |  |  | code. An existing association with a file descriptor can be obtained using | 
					
						
							| 
									
										
										
										
											2016-01-25 23:28:57 +08:00
										 |  |  | ASYNC_WAIT_CTX_get_fd() and cleared using ASYNC_WAIT_CTX_clear_fd(). Both of | 
					
						
							| 
									
										
										
										
											2019-09-28 14:45:05 +08:00
										 |  |  | these functions requires a I<key> value which is unique to the async aware | 
					
						
							| 
									
										
										
										
											2016-10-18 17:53:14 +08:00
										 |  |  | code.  This could be any unique value but a good candidate might be the | 
					
						
							| 
									
										
										
										
											2019-09-28 14:45:05 +08:00
										 |  |  | B<ENGINE *> for the engine. The I<custom_data> parameter can be any value, and | 
					
						
							| 
									
										
										
										
											2016-10-18 17:53:14 +08:00
										 |  |  | will be returned in a subsequent call to ASYNC_WAIT_CTX_get_fd(). The | 
					
						
							| 
									
										
										
										
											2016-01-25 23:28:57 +08:00
										 |  |  | ASYNC_WAIT_CTX_set_wait_fd() function also expects a pointer to a "cleanup" | 
					
						
							| 
									
										
										
										
											2016-10-18 17:53:14 +08:00
										 |  |  | routine. This can be NULL but if provided will automatically get called when | 
					
						
							| 
									
										
										
										
											2019-09-28 14:45:05 +08:00
										 |  |  | the B<ASYNC_WAIT_CTX> is freed, and gives the engine the opportunity to close | 
					
						
							|  |  |  | the fd or any other resources. Note: The "cleanup" routine does not get called | 
					
						
							|  |  |  | if the fd is cleared directly via a call to ASYNC_WAIT_CTX_clear_fd(). | 
					
						
							| 
									
										
										
										
											2016-01-25 23:28:57 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | An example of typical usage might be an async capable engine. User code would | 
					
						
							|  |  |  | initiate cryptographic operations. The engine would initiate those operations | 
					
						
							|  |  |  | asynchronously and then call ASYNC_WAIT_CTX_set_wait_fd() followed by | 
					
						
							|  |  |  | ASYNC_pause_job() to return control to the user code. The user code can then | 
					
						
							|  |  |  | perform other tasks or wait for the job to be ready by calling "select" or other | 
					
						
							|  |  |  | similar function on the wait file descriptor. The engine can signal to the user | 
					
						
							|  |  |  | code that the job should be resumed by making the wait file descriptor | 
					
						
							|  |  |  | "readable". Once resumed the engine should clear the wake signal on the wait | 
					
						
							|  |  |  | file descriptor. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-06 04:41:01 +08:00
										 |  |  | As well as a file descriptor, user code may also be notified via a callback. The | 
					
						
							| 
									
										
										
										
											2019-09-28 14:45:05 +08:00
										 |  |  | callback and data pointers are stored within the B<ASYNC_WAIT_CTX> along with an | 
					
						
							| 
									
										
										
										
											2018-11-06 04:41:01 +08:00
										 |  |  | additional status field that can be used for the notification of retries from an | 
					
						
							|  |  |  | engine. This additional method can be used when the user thinks that a file | 
					
						
							|  |  |  | descriptor is too costly in terms of CPU cycles or in some context where a file | 
					
						
							|  |  |  | descriptor is not appropriate. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ASYNC_WAIT_CTX_set_callback() sets the callback and the callback argument. The | 
					
						
							|  |  |  | callback will be called to notify user code when an engine completes a | 
					
						
							|  |  |  | cryptography operation. It is a requirement that the callback function is small | 
					
						
							| 
									
										
										
										
											2020-07-17 18:31:26 +08:00
										 |  |  | and nonblocking as it will be run in the context of a polling mechanism or an | 
					
						
							| 
									
										
										
										
											2018-11-06 04:41:01 +08:00
										 |  |  | interrupt. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-28 14:45:05 +08:00
										 |  |  | ASYNC_WAIT_CTX_get_callback() returns the callback set in the B<ASYNC_WAIT_CTX> | 
					
						
							| 
									
										
										
										
											2018-11-06 04:41:01 +08:00
										 |  |  | structure. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ASYNC_WAIT_CTX_set_status() allows an engine to set the current engine status. | 
					
						
							|  |  |  | The possible status values are the following: | 
					
						
							| 
									
										
										
										
											2019-09-28 14:45:05 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | =over 4 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | =item B<ASYNC_STATUS_UNSUPPORTED> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The engine does not support the callback mechanism. This is the default value. | 
					
						
							|  |  |  | The engine must call ASYNC_WAIT_CTX_set_status() to set the status to some value | 
					
						
							|  |  |  | other than B<ASYNC_STATUS_UNSUPPORTED> if it intends to enable the callback | 
					
						
							|  |  |  | mechanism. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | =item B<ASYNC_STATUS_ERR> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The engine has a fatal problem with this request. The user code should clean up | 
					
						
							|  |  |  | this session. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | =item B<ASYNC_STATUS_OK> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The request has been successfully submitted. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | =item B<ASYNC_STATUS_EAGAIN> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The engine has some problem which will be recovered soon, such as a buffer is | 
					
						
							|  |  |  | full, so user code should resume the job. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | =back | 
					
						
							| 
									
										
										
										
											2018-11-06 04:41:01 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | ASYNC_WAIT_CTX_get_status() allows user code to obtain the current status value. | 
					
						
							| 
									
										
										
										
											2019-09-28 14:45:05 +08:00
										 |  |  | If the status is any value other than B<ASYNC_STATUS_OK> then the user code | 
					
						
							|  |  |  | should not expect to receive a callback from the engine even if one has been | 
					
						
							|  |  |  | set. | 
					
						
							| 
									
										
										
										
											2018-11-06 04:41:01 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | An example of the usage of the callback method might be the following. User | 
					
						
							|  |  |  | code would initiate cryptographic operations, and the engine code would dispatch | 
					
						
							|  |  |  | this operation to hardware, and if the dispatch is successful, then the engine | 
					
						
							|  |  |  | code would call ASYNC_pause_job() to return control to the user code. After | 
					
						
							|  |  |  | that, user code can perform other tasks. When the hardware completes the | 
					
						
							|  |  |  | operation, normally it is detected by a polling function or an interrupt, as the | 
					
						
							|  |  |  | user code set a callback by calling ASYNC_WAIT_CTX_set_callback() previously, | 
					
						
							|  |  |  | then the registered callback will be called. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-25 23:28:57 +08:00
										 |  |  | =head1 RETURN VALUES | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-28 14:45:05 +08:00
										 |  |  | ASYNC_WAIT_CTX_new() returns a pointer to the newly allocated B<ASYNC_WAIT_CTX> | 
					
						
							|  |  |  | or NULL on error. | 
					
						
							| 
									
										
										
										
											2016-01-25 23:28:57 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | ASYNC_WAIT_CTX_set_wait_fd, ASYNC_WAIT_CTX_get_fd, ASYNC_WAIT_CTX_get_all_fds, | 
					
						
							| 
									
										
										
										
											2018-11-06 04:41:01 +08:00
										 |  |  | ASYNC_WAIT_CTX_get_changed_fds, ASYNC_WAIT_CTX_clear_fd, | 
					
						
							|  |  |  | ASYNC_WAIT_CTX_set_callback, ASYNC_WAIT_CTX_get_callback and | 
					
						
							|  |  |  | ASYNC_WAIT_CTX_set_status all return 1 on success or 0 on error. | 
					
						
							| 
									
										
										
										
											2019-12-01 07:18:47 +08:00
										 |  |  | ASYNC_WAIT_CTX_get_status() returns the engine status. | 
					
						
							| 
									
										
										
										
											2018-11-06 04:41:01 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-25 23:28:57 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-26 19:40:15 +08:00
										 |  |  | =head1 NOTES | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-12 22:44:43 +08:00
										 |  |  | On Windows platforms the F<< <openssl/async.h> >> header is dependent on some | 
					
						
							|  |  |  | of the types customarily made available by including F<< <windows.h> >>. The | 
					
						
							| 
									
										
										
										
											2016-06-26 19:40:15 +08:00
										 |  |  | application developer is likely to require control over when the latter | 
					
						
							| 
									
										
										
										
											2020-06-30 03:13:07 +08:00
										 |  |  | is included, commonly as one of the first included headers. Therefore, | 
					
						
							| 
									
										
										
										
											2016-06-26 19:40:15 +08:00
										 |  |  | it is defined as an application developer's responsibility to include | 
					
						
							| 
									
										
										
										
											2021-01-12 22:44:43 +08:00
										 |  |  | F<< <windows.h> >> prior to F<< <openssl/async.h> >>. | 
					
						
							| 
									
										
										
										
											2016-06-26 19:40:15 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-25 23:28:57 +08:00
										 |  |  | =head1 SEE ALSO | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-11 16:33:09 +08:00
										 |  |  | L<crypto(7)>, L<ASYNC_start_job(3)> | 
					
						
							| 
									
										
										
										
											2016-01-25 23:28:57 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | =head1 HISTORY | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-09 08:02:36 +08:00
										 |  |  | ASYNC_WAIT_CTX_new(), ASYNC_WAIT_CTX_free(), ASYNC_WAIT_CTX_set_wait_fd(), | 
					
						
							|  |  |  | ASYNC_WAIT_CTX_get_fd(), ASYNC_WAIT_CTX_get_all_fds(), | 
					
						
							|  |  |  | ASYNC_WAIT_CTX_get_changed_fds() and ASYNC_WAIT_CTX_clear_fd() | 
					
						
							|  |  |  | were added in OpenSSL 1.1.0. | 
					
						
							| 
									
										
										
										
											2016-01-25 23:28:57 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-06 04:41:01 +08:00
										 |  |  | ASYNC_WAIT_CTX_set_callback(), ASYNC_WAIT_CTX_get_callback(), | 
					
						
							|  |  |  | ASYNC_WAIT_CTX_set_status(), and ASYNC_WAIT_CTX_get_status() | 
					
						
							| 
									
										
										
										
											2019-07-15 21:03:44 +08:00
										 |  |  | were added in OpenSSL 3.0. | 
					
						
							| 
									
										
										
										
											2018-11-06 04:41:01 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-18 23:44:05 +08:00
										 |  |  | =head1 COPYRIGHT | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-28 20:54:57 +08:00
										 |  |  | Copyright 2016-2021 The OpenSSL Project Authors. All Rights Reserved. | 
					
						
							| 
									
										
										
										
											2016-05-18 23:44:05 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-06 21:04:44 +08:00
										 |  |  | Licensed under the Apache License 2.0 (the "License").  You may not use | 
					
						
							| 
									
										
										
										
											2016-05-18 23:44:05 +08:00
										 |  |  | this file except in compliance with the License.  You can obtain a copy | 
					
						
							|  |  |  | in the file LICENSE in the source distribution or at | 
					
						
							|  |  |  | L<https://www.openssl.org/source/license.html>. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | =cut |