xref: /freebsd/crypto/openssl/doc/man3/ASYNC_start_job.pod (revision e71b70530d95c4f34d8bdbd78d1242df1ba4a945)
1*e71b7053SJung-uk Kim=pod
2*e71b7053SJung-uk Kim
3*e71b7053SJung-uk Kim=head1 NAME
4*e71b7053SJung-uk Kim
5*e71b7053SJung-uk KimASYNC_get_wait_ctx,
6*e71b7053SJung-uk KimASYNC_init_thread, ASYNC_cleanup_thread, ASYNC_start_job, ASYNC_pause_job,
7*e71b7053SJung-uk KimASYNC_get_current_job, ASYNC_block_pause, ASYNC_unblock_pause, ASYNC_is_capable
8*e71b7053SJung-uk Kim- asynchronous job management functions
9*e71b7053SJung-uk Kim
10*e71b7053SJung-uk Kim=head1 SYNOPSIS
11*e71b7053SJung-uk Kim
12*e71b7053SJung-uk Kim #include <openssl/async.h>
13*e71b7053SJung-uk Kim
14*e71b7053SJung-uk Kim int ASYNC_init_thread(size_t max_size, size_t init_size);
15*e71b7053SJung-uk Kim void ASYNC_cleanup_thread(void);
16*e71b7053SJung-uk Kim
17*e71b7053SJung-uk Kim int ASYNC_start_job(ASYNC_JOB **job, ASYNC_WAIT_CTX *ctx, int *ret,
18*e71b7053SJung-uk Kim                     int (*func)(void *), void *args, size_t size);
19*e71b7053SJung-uk Kim int ASYNC_pause_job(void);
20*e71b7053SJung-uk Kim
21*e71b7053SJung-uk Kim ASYNC_JOB *ASYNC_get_current_job(void);
22*e71b7053SJung-uk Kim ASYNC_WAIT_CTX *ASYNC_get_wait_ctx(ASYNC_JOB *job);
23*e71b7053SJung-uk Kim void ASYNC_block_pause(void);
24*e71b7053SJung-uk Kim void ASYNC_unblock_pause(void);
25*e71b7053SJung-uk Kim
26*e71b7053SJung-uk Kim int ASYNC_is_capable(void);
27*e71b7053SJung-uk Kim
28*e71b7053SJung-uk Kim=head1 DESCRIPTION
29*e71b7053SJung-uk Kim
30*e71b7053SJung-uk KimOpenSSL implements asynchronous capabilities through an ASYNC_JOB. This
31*e71b7053SJung-uk Kimrepresents code that can be started and executes until some event occurs. At
32*e71b7053SJung-uk Kimthat point the code can be paused and control returns to user code until some
33*e71b7053SJung-uk Kimsubsequent event indicates that the job can be resumed.
34*e71b7053SJung-uk Kim
35*e71b7053SJung-uk KimThe creation of an ASYNC_JOB is a relatively expensive operation. Therefore, for
36*e71b7053SJung-uk Kimefficiency reasons, jobs can be created up front and reused many times. They are
37*e71b7053SJung-uk Kimheld in a pool until they are needed, at which point they are removed from the
38*e71b7053SJung-uk Kimpool, used, and then returned to the pool when the job completes. If the user
39*e71b7053SJung-uk Kimapplication is multi-threaded, then ASYNC_init_thread() may be called for each
40*e71b7053SJung-uk Kimthread that will initiate asynchronous jobs. Before
41*e71b7053SJung-uk Kimuser code exits per-thread resources need to be cleaned up. This will normally
42*e71b7053SJung-uk Kimoccur automatically (see L<OPENSSL_init_crypto(3)>) but may be explicitly
43*e71b7053SJung-uk Kiminitiated by using ASYNC_cleanup_thread(). No asynchronous jobs must be
44*e71b7053SJung-uk Kimoutstanding for the thread when ASYNC_cleanup_thread() is called. Failing to
45*e71b7053SJung-uk Kimensure this will result in memory leaks.
46*e71b7053SJung-uk Kim
47*e71b7053SJung-uk KimThe B<max_size> argument limits the number of ASYNC_JOBs that will be held in
48*e71b7053SJung-uk Kimthe pool. If B<max_size> is set to 0 then no upper limit is set. When an
49*e71b7053SJung-uk KimASYNC_JOB is needed but there are none available in the pool already then one
50*e71b7053SJung-uk Kimwill be automatically created, as long as the total of ASYNC_JOBs managed by the
51*e71b7053SJung-uk Kimpool does not exceed B<max_size>. When the pool is first initialised
52*e71b7053SJung-uk KimB<init_size> ASYNC_JOBs will be created immediately. If ASYNC_init_thread() is
53*e71b7053SJung-uk Kimnot called before the pool is first used then it will be called automatically
54*e71b7053SJung-uk Kimwith a B<max_size> of 0 (no upper limit) and an B<init_size> of 0 (no ASYNC_JOBs
55*e71b7053SJung-uk Kimcreated up front).
56*e71b7053SJung-uk Kim
57*e71b7053SJung-uk KimAn asynchronous job is started by calling the ASYNC_start_job() function.
58*e71b7053SJung-uk KimInitially B<*job> should be NULL. B<ctx> should point to an ASYNC_WAIT_CTX
59*e71b7053SJung-uk Kimobject created through the L<ASYNC_WAIT_CTX_new(3)> function. B<ret> should
60*e71b7053SJung-uk Kimpoint to a location where the return value of the asynchronous function should
61*e71b7053SJung-uk Kimbe stored on completion of the job. B<func> represents the function that should
62*e71b7053SJung-uk Kimbe started asynchronously. The data pointed to by B<args> and of size B<size>
63*e71b7053SJung-uk Kimwill be copied and then passed as an argument to B<func> when the job starts.
64*e71b7053SJung-uk KimASYNC_start_job will return one of the following values:
65*e71b7053SJung-uk Kim
66*e71b7053SJung-uk Kim=over 4
67*e71b7053SJung-uk Kim
68*e71b7053SJung-uk Kim=item B<ASYNC_ERR>
69*e71b7053SJung-uk Kim
70*e71b7053SJung-uk KimAn error occurred trying to start the job. Check the OpenSSL error queue (e.g.
71*e71b7053SJung-uk Kimsee L<ERR_print_errors(3)>) for more details.
72*e71b7053SJung-uk Kim
73*e71b7053SJung-uk Kim=item B<ASYNC_NO_JOBS>
74*e71b7053SJung-uk Kim
75*e71b7053SJung-uk KimThere are no jobs currently available in the pool. This call can be retried
76*e71b7053SJung-uk Kimagain at a later time.
77*e71b7053SJung-uk Kim
78*e71b7053SJung-uk Kim=item B<ASYNC_PAUSE>
79*e71b7053SJung-uk Kim
80*e71b7053SJung-uk KimThe job was successfully started but was "paused" before it completed (see
81*e71b7053SJung-uk KimASYNC_pause_job() below). A handle to the job is placed in B<*job>. Other work
82*e71b7053SJung-uk Kimcan be performed (if desired) and the job restarted at a later time. To restart
83*e71b7053SJung-uk Kima job call ASYNC_start_job() again passing the job handle in B<*job>. The
84*e71b7053SJung-uk KimB<func>, B<args> and B<size> parameters will be ignored when restarting a job.
85*e71b7053SJung-uk KimWhen restarting a job ASYNC_start_job() B<must> be called from the same thread
86*e71b7053SJung-uk Kimthat the job was originally started from.
87*e71b7053SJung-uk Kim
88*e71b7053SJung-uk Kim=item B<ASYNC_FINISH>
89*e71b7053SJung-uk Kim
90*e71b7053SJung-uk KimThe job completed. B<*job> will be NULL and the return value from B<func> will
91*e71b7053SJung-uk Kimbe placed in B<*ret>.
92*e71b7053SJung-uk Kim
93*e71b7053SJung-uk Kim=back
94*e71b7053SJung-uk Kim
95*e71b7053SJung-uk KimAt any one time there can be a maximum of one job actively running per thread
96*e71b7053SJung-uk Kim(you can have many that are paused). ASYNC_get_current_job() can be used to get
97*e71b7053SJung-uk Kima pointer to the currently executing ASYNC_JOB. If no job is currently executing
98*e71b7053SJung-uk Kimthen this will return NULL.
99*e71b7053SJung-uk Kim
100*e71b7053SJung-uk KimIf executing within the context of a job (i.e. having been called directly or
101*e71b7053SJung-uk Kimindirectly by the function "func" passed as an argument to ASYNC_start_job())
102*e71b7053SJung-uk Kimthen ASYNC_pause_job() will immediately return control to the calling
103*e71b7053SJung-uk Kimapplication with ASYNC_PAUSE returned from the ASYNC_start_job() call. A
104*e71b7053SJung-uk Kimsubsequent call to ASYNC_start_job passing in the relevant ASYNC_JOB in the
105*e71b7053SJung-uk KimB<*job> parameter will resume execution from the ASYNC_pause_job() call. If
106*e71b7053SJung-uk KimASYNC_pause_job() is called whilst not within the context of a job then no
107*e71b7053SJung-uk Kimaction is taken and ASYNC_pause_job() returns immediately.
108*e71b7053SJung-uk Kim
109*e71b7053SJung-uk KimASYNC_get_wait_ctx() can be used to get a pointer to the ASYNC_WAIT_CTX
110*e71b7053SJung-uk Kimfor the B<job>. ASYNC_WAIT_CTXs can have a "wait" file descriptor associated
111*e71b7053SJung-uk Kimwith them. Applications can wait for the file descriptor to be ready for "read"
112*e71b7053SJung-uk Kimusing a system function call such as select or poll (being ready for "read"
113*e71b7053SJung-uk Kimindicates that the job should be resumed). If no file descriptor is made
114*e71b7053SJung-uk Kimavailable then an application will have to periodically "poll" the job by
115*e71b7053SJung-uk Kimattempting to restart it to see if it is ready to continue.
116*e71b7053SJung-uk Kim
117*e71b7053SJung-uk KimAn example of typical usage might be an async capable engine. User code would
118*e71b7053SJung-uk Kiminitiate cryptographic operations. The engine would initiate those operations
119*e71b7053SJung-uk Kimasynchronously and then call L<ASYNC_WAIT_CTX_set_wait_fd(3)> followed by
120*e71b7053SJung-uk KimASYNC_pause_job() to return control to the user code. The user code can then
121*e71b7053SJung-uk Kimperform other tasks or wait for the job to be ready by calling "select" or other
122*e71b7053SJung-uk Kimsimilar function on the wait file descriptor. The engine can signal to the user
123*e71b7053SJung-uk Kimcode that the job should be resumed by making the wait file descriptor
124*e71b7053SJung-uk Kim"readable". Once resumed the engine should clear the wake signal on the wait
125*e71b7053SJung-uk Kimfile descriptor.
126*e71b7053SJung-uk Kim
127*e71b7053SJung-uk KimThe ASYNC_block_pause() function will prevent the currently active job from
128*e71b7053SJung-uk Kimpausing. The block will remain in place until a subsequent call to
129*e71b7053SJung-uk KimASYNC_unblock_pause(). These functions can be nested, e.g. if you call
130*e71b7053SJung-uk KimASYNC_block_pause() twice then you must call ASYNC_unblock_pause() twice in
131*e71b7053SJung-uk Kimorder to re-enable pausing. If these functions are called while there is no
132*e71b7053SJung-uk Kimcurrently active job then they have no effect. This functionality can be useful
133*e71b7053SJung-uk Kimto avoid deadlock scenarios. For example during the execution of an ASYNC_JOB an
134*e71b7053SJung-uk Kimapplication acquires a lock. It then calls some cryptographic function which
135*e71b7053SJung-uk Kiminvokes ASYNC_pause_job(). This returns control back to the code that created
136*e71b7053SJung-uk Kimthe ASYNC_JOB. If that code then attempts to acquire the same lock before
137*e71b7053SJung-uk Kimresuming the original job then a deadlock can occur. By calling
138*e71b7053SJung-uk KimASYNC_block_pause() immediately after acquiring the lock and
139*e71b7053SJung-uk KimASYNC_unblock_pause() immediately before releasing it then this situation cannot
140*e71b7053SJung-uk Kimoccur.
141*e71b7053SJung-uk Kim
142*e71b7053SJung-uk KimSome platforms cannot support async operations. The ASYNC_is_capable() function
143*e71b7053SJung-uk Kimcan be used to detect whether the current platform is async capable or not.
144*e71b7053SJung-uk Kim
145*e71b7053SJung-uk Kim=head1 RETURN VALUES
146*e71b7053SJung-uk Kim
147*e71b7053SJung-uk KimASYNC_init_thread returns 1 on success or 0 otherwise.
148*e71b7053SJung-uk Kim
149*e71b7053SJung-uk KimASYNC_start_job returns one of ASYNC_ERR, ASYNC_NO_JOBS, ASYNC_PAUSE or
150*e71b7053SJung-uk KimASYNC_FINISH as described above.
151*e71b7053SJung-uk Kim
152*e71b7053SJung-uk KimASYNC_pause_job returns 0 if an error occurred or 1 on success. If called when
153*e71b7053SJung-uk Kimnot within the context of an ASYNC_JOB then this is counted as success so 1 is
154*e71b7053SJung-uk Kimreturned.
155*e71b7053SJung-uk Kim
156*e71b7053SJung-uk KimASYNC_get_current_job returns a pointer to the currently executing ASYNC_JOB or
157*e71b7053SJung-uk KimNULL if not within the context of a job.
158*e71b7053SJung-uk Kim
159*e71b7053SJung-uk KimASYNC_get_wait_ctx() returns a pointer to the ASYNC_WAIT_CTX for the job.
160*e71b7053SJung-uk Kim
161*e71b7053SJung-uk KimASYNC_is_capable() returns 1 if the current platform is async capable or 0
162*e71b7053SJung-uk Kimotherwise.
163*e71b7053SJung-uk Kim
164*e71b7053SJung-uk Kim=head1 NOTES
165*e71b7053SJung-uk Kim
166*e71b7053SJung-uk KimOn Windows platforms the openssl/async.h header is dependent on some
167*e71b7053SJung-uk Kimof the types customarily made available by including windows.h. The
168*e71b7053SJung-uk Kimapplication developer is likely to require control over when the latter
169*e71b7053SJung-uk Kimis included, commonly as one of the first included headers. Therefore
170*e71b7053SJung-uk Kimit is defined as an application developer's responsibility to include
171*e71b7053SJung-uk Kimwindows.h prior to async.h.
172*e71b7053SJung-uk Kim
173*e71b7053SJung-uk Kim=head1 EXAMPLE
174*e71b7053SJung-uk Kim
175*e71b7053SJung-uk KimThe following example demonstrates how to use most of the core async APIs:
176*e71b7053SJung-uk Kim
177*e71b7053SJung-uk Kim #ifdef _WIN32
178*e71b7053SJung-uk Kim # include <windows.h>
179*e71b7053SJung-uk Kim #endif
180*e71b7053SJung-uk Kim #include <stdio.h>
181*e71b7053SJung-uk Kim #include <unistd.h>
182*e71b7053SJung-uk Kim #include <openssl/async.h>
183*e71b7053SJung-uk Kim #include <openssl/crypto.h>
184*e71b7053SJung-uk Kim
185*e71b7053SJung-uk Kim int unique = 0;
186*e71b7053SJung-uk Kim
187*e71b7053SJung-uk Kim void cleanup(ASYNC_WAIT_CTX *ctx, const void *key, OSSL_ASYNC_FD r, void *vw)
188*e71b7053SJung-uk Kim {
189*e71b7053SJung-uk Kim     OSSL_ASYNC_FD *w = (OSSL_ASYNC_FD *)vw;
190*e71b7053SJung-uk Kim
191*e71b7053SJung-uk Kim     close(r);
192*e71b7053SJung-uk Kim     close(*w);
193*e71b7053SJung-uk Kim     OPENSSL_free(w);
194*e71b7053SJung-uk Kim }
195*e71b7053SJung-uk Kim
196*e71b7053SJung-uk Kim int jobfunc(void *arg)
197*e71b7053SJung-uk Kim {
198*e71b7053SJung-uk Kim     ASYNC_JOB *currjob;
199*e71b7053SJung-uk Kim     unsigned char *msg;
200*e71b7053SJung-uk Kim     int pipefds[2] = {0, 0};
201*e71b7053SJung-uk Kim     OSSL_ASYNC_FD *wptr;
202*e71b7053SJung-uk Kim     char buf = 'X';
203*e71b7053SJung-uk Kim
204*e71b7053SJung-uk Kim     currjob = ASYNC_get_current_job();
205*e71b7053SJung-uk Kim     if (currjob != NULL) {
206*e71b7053SJung-uk Kim         printf("Executing within a job\n");
207*e71b7053SJung-uk Kim     } else {
208*e71b7053SJung-uk Kim         printf("Not executing within a job - should not happen\n");
209*e71b7053SJung-uk Kim         return 0;
210*e71b7053SJung-uk Kim     }
211*e71b7053SJung-uk Kim
212*e71b7053SJung-uk Kim     msg = (unsigned char *)arg;
213*e71b7053SJung-uk Kim     printf("Passed in message is: %s\n", msg);
214*e71b7053SJung-uk Kim
215*e71b7053SJung-uk Kim     if (pipe(pipefds) != 0) {
216*e71b7053SJung-uk Kim         printf("Failed to create pipe\n");
217*e71b7053SJung-uk Kim         return 0;
218*e71b7053SJung-uk Kim     }
219*e71b7053SJung-uk Kim     wptr = OPENSSL_malloc(sizeof(OSSL_ASYNC_FD));
220*e71b7053SJung-uk Kim     if (wptr == NULL) {
221*e71b7053SJung-uk Kim         printf("Failed to malloc\n");
222*e71b7053SJung-uk Kim         return 0;
223*e71b7053SJung-uk Kim     }
224*e71b7053SJung-uk Kim     *wptr = pipefds[1];
225*e71b7053SJung-uk Kim     ASYNC_WAIT_CTX_set_wait_fd(ASYNC_get_wait_ctx(currjob), &unique,
226*e71b7053SJung-uk Kim                                pipefds[0], wptr, cleanup);
227*e71b7053SJung-uk Kim
228*e71b7053SJung-uk Kim     /*
229*e71b7053SJung-uk Kim      * Normally some external event would cause this to happen at some
230*e71b7053SJung-uk Kim      * later point - but we do it here for demo purposes, i.e.
231*e71b7053SJung-uk Kim      * immediately signalling that the job is ready to be woken up after
232*e71b7053SJung-uk Kim      * we return to main via ASYNC_pause_job().
233*e71b7053SJung-uk Kim      */
234*e71b7053SJung-uk Kim     write(pipefds[1], &buf, 1);
235*e71b7053SJung-uk Kim
236*e71b7053SJung-uk Kim     /* Return control back to main */
237*e71b7053SJung-uk Kim     ASYNC_pause_job();
238*e71b7053SJung-uk Kim
239*e71b7053SJung-uk Kim     /* Clear the wake signal */
240*e71b7053SJung-uk Kim     read(pipefds[0], &buf, 1);
241*e71b7053SJung-uk Kim
242*e71b7053SJung-uk Kim     printf ("Resumed the job after a pause\n");
243*e71b7053SJung-uk Kim
244*e71b7053SJung-uk Kim     return 1;
245*e71b7053SJung-uk Kim }
246*e71b7053SJung-uk Kim
247*e71b7053SJung-uk Kim int main(void)
248*e71b7053SJung-uk Kim {
249*e71b7053SJung-uk Kim     ASYNC_JOB *job = NULL;
250*e71b7053SJung-uk Kim     ASYNC_WAIT_CTX *ctx = NULL;
251*e71b7053SJung-uk Kim     int ret;
252*e71b7053SJung-uk Kim     OSSL_ASYNC_FD waitfd;
253*e71b7053SJung-uk Kim     fd_set waitfdset;
254*e71b7053SJung-uk Kim     size_t numfds;
255*e71b7053SJung-uk Kim     unsigned char msg[13] = "Hello world!";
256*e71b7053SJung-uk Kim
257*e71b7053SJung-uk Kim     printf("Starting...\n");
258*e71b7053SJung-uk Kim
259*e71b7053SJung-uk Kim     ctx = ASYNC_WAIT_CTX_new();
260*e71b7053SJung-uk Kim     if (ctx == NULL) {
261*e71b7053SJung-uk Kim         printf("Failed to create ASYNC_WAIT_CTX\n");
262*e71b7053SJung-uk Kim         abort();
263*e71b7053SJung-uk Kim     }
264*e71b7053SJung-uk Kim
265*e71b7053SJung-uk Kim     for (;;) {
266*e71b7053SJung-uk Kim         switch (ASYNC_start_job(&job, ctx, &ret, jobfunc, msg, sizeof(msg))) {
267*e71b7053SJung-uk Kim         case ASYNC_ERR:
268*e71b7053SJung-uk Kim         case ASYNC_NO_JOBS:
269*e71b7053SJung-uk Kim             printf("An error occurred\n");
270*e71b7053SJung-uk Kim             goto end;
271*e71b7053SJung-uk Kim         case ASYNC_PAUSE:
272*e71b7053SJung-uk Kim             printf("Job was paused\n");
273*e71b7053SJung-uk Kim             break;
274*e71b7053SJung-uk Kim         case ASYNC_FINISH:
275*e71b7053SJung-uk Kim             printf("Job finished with return value %d\n", ret);
276*e71b7053SJung-uk Kim             goto end;
277*e71b7053SJung-uk Kim         }
278*e71b7053SJung-uk Kim
279*e71b7053SJung-uk Kim         /* Wait for the job to be woken */
280*e71b7053SJung-uk Kim         printf("Waiting for the job to be woken up\n");
281*e71b7053SJung-uk Kim
282*e71b7053SJung-uk Kim         if (!ASYNC_WAIT_CTX_get_all_fds(ctx, NULL, &numfds)
283*e71b7053SJung-uk Kim                 || numfds > 1) {
284*e71b7053SJung-uk Kim             printf("Unexpected number of fds\n");
285*e71b7053SJung-uk Kim             abort();
286*e71b7053SJung-uk Kim         }
287*e71b7053SJung-uk Kim         ASYNC_WAIT_CTX_get_all_fds(ctx, &waitfd, &numfds);
288*e71b7053SJung-uk Kim         FD_ZERO(&waitfdset);
289*e71b7053SJung-uk Kim         FD_SET(waitfd, &waitfdset);
290*e71b7053SJung-uk Kim         select(waitfd + 1, &waitfdset, NULL, NULL, NULL);
291*e71b7053SJung-uk Kim     }
292*e71b7053SJung-uk Kim
293*e71b7053SJung-uk Kim end:
294*e71b7053SJung-uk Kim     ASYNC_WAIT_CTX_free(ctx);
295*e71b7053SJung-uk Kim     printf("Finishing\n");
296*e71b7053SJung-uk Kim
297*e71b7053SJung-uk Kim     return 0;
298*e71b7053SJung-uk Kim }
299*e71b7053SJung-uk Kim
300*e71b7053SJung-uk KimThe expected output from executing the above example program is:
301*e71b7053SJung-uk Kim
302*e71b7053SJung-uk Kim Starting...
303*e71b7053SJung-uk Kim Executing within a job
304*e71b7053SJung-uk Kim Passed in message is: Hello world!
305*e71b7053SJung-uk Kim Job was paused
306*e71b7053SJung-uk Kim Waiting for the job to be woken up
307*e71b7053SJung-uk Kim Resumed the job after a pause
308*e71b7053SJung-uk Kim Job finished with return value 1
309*e71b7053SJung-uk Kim Finishing
310*e71b7053SJung-uk Kim
311*e71b7053SJung-uk Kim=head1 SEE ALSO
312*e71b7053SJung-uk Kim
313*e71b7053SJung-uk KimL<crypto(7)>, L<ERR_print_errors(3)>
314*e71b7053SJung-uk Kim
315*e71b7053SJung-uk Kim=head1 HISTORY
316*e71b7053SJung-uk Kim
317*e71b7053SJung-uk KimASYNC_init_thread, ASYNC_cleanup_thread,
318*e71b7053SJung-uk KimASYNC_start_job, ASYNC_pause_job, ASYNC_get_current_job, ASYNC_get_wait_ctx(),
319*e71b7053SJung-uk KimASYNC_block_pause(), ASYNC_unblock_pause() and ASYNC_is_capable() were first
320*e71b7053SJung-uk Kimadded to OpenSSL 1.1.0.
321*e71b7053SJung-uk Kim
322*e71b7053SJung-uk Kim=head1 COPYRIGHT
323*e71b7053SJung-uk Kim
324*e71b7053SJung-uk KimCopyright 2015-2016 The OpenSSL Project Authors. All Rights Reserved.
325*e71b7053SJung-uk Kim
326*e71b7053SJung-uk KimLicensed under the OpenSSL license (the "License").  You may not use
327*e71b7053SJung-uk Kimthis file except in compliance with the License.  You can obtain a copy
328*e71b7053SJung-uk Kimin the file LICENSE in the source distribution or at
329*e71b7053SJung-uk KimL<https://www.openssl.org/source/license.html>.
330*e71b7053SJung-uk Kim
331*e71b7053SJung-uk Kim=cut
332