Removed some ifdefs.
[crypto.git] / lib / silcutil / silcfsm.c
1 /*
2
3   silcfsm.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 2005 - 2006 Pekka Riikonen
8
9   This program is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; version 2 of the License.
12
13   This program is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   GNU General Public License for more details.
17
18 */
19
20 #include "silc.h"
21
22 SILC_TASK_CALLBACK(silc_fsm_run);
23 SILC_TASK_CALLBACK(silc_fsm_finish);
24 SILC_TASK_CALLBACK(silc_fsm_sema_timedout);
25 SILC_TASK_CALLBACK(silc_fsm_start_real_thread);
26 static void *silc_fsm_thread(void *context);
27 static void silc_fsm_thread_termination_post(SilcFSMSema sema);
28 static void silc_fsm_sema_ref(SilcFSMSema sema);
29 static void silc_fsm_sema_unref(SilcFSMSema sema);
30
31 /* Allocate FSM */
32
33 SilcFSM silc_fsm_alloc(void *fsm_context,
34                        SilcFSMDestructor destructor,
35                        void *destructor_context,
36                        SilcSchedule schedule)
37 {
38   SilcFSM fsm;
39
40   fsm = silc_calloc(1, sizeof(*fsm));
41   if (!fsm)
42     return NULL;
43
44   if (!silc_fsm_init(fsm, fsm_context, destructor,
45                      destructor_context, schedule)) {
46     silc_free(fsm);
47     return NULL;
48   }
49
50   return fsm;
51 }
52
53 /* Initialize FSM */
54
55 SilcBool silc_fsm_init(SilcFSM fsm,
56                        void *fsm_context,
57                        SilcFSMDestructor destructor,
58                        void *destructor_context,
59                        SilcSchedule schedule)
60 {
61   if (!schedule)
62     return FALSE;
63
64   fsm->fsm_context = fsm_context;
65   fsm->state_context = NULL;
66   fsm->destructor = destructor;
67   fsm->destructor_context = destructor_context;
68   fsm->schedule = schedule;
69   fsm->thread = FALSE;
70   fsm->async_call = FALSE;
71   fsm->u.m.threads = 0;
72   fsm->u.m.lock = NULL;
73
74   return TRUE;
75 }
76
77 /* Allocate FSM thread.  Internally machine and thread use same context. */
78
79 SilcFSMThread silc_fsm_thread_alloc(SilcFSM fsm,
80                                     void *thread_context,
81                                     SilcFSMThreadDestructor destructor,
82                                     void *destructor_context,
83                                     SilcBool real_thread)
84 {
85   SilcFSMThread thread;
86
87   thread = silc_calloc(1, sizeof(*thread));
88   if (!thread)
89     return NULL;
90
91   silc_fsm_thread_init(thread, fsm, thread_context, destructor,
92                        destructor_context, real_thread);
93   return thread;
94 }
95
96 /* Initialize FSM thread.  Internally machine and thread use same context. */
97
98 void silc_fsm_thread_init(SilcFSMThread thread,
99                           SilcFSM fsm,
100                           void *thread_context,
101                           SilcFSMThreadDestructor destructor,
102                           void *destructor_context,
103                           SilcBool real_thread)
104 {
105   SILC_LOG_DEBUG(("Initializing new thread %p (%s)",
106                   thread, real_thread ? "real" : "FSM"));
107
108   SILC_ASSERT(!fsm->thread);
109
110   thread->fsm_context = thread_context;
111   thread->state_context = NULL;
112   thread->destructor = (SilcFSMDestructor)destructor;
113   thread->destructor_context = destructor_context;
114   thread->schedule = fsm->schedule;
115   thread->thread = TRUE;
116   thread->async_call = FALSE;
117   thread->real_thread = real_thread;
118   thread->u.t.fsm = fsm;
119
120   /* Add to machine */
121   fsm->u.m.threads++;
122
123   /* Allocate lock for the machine if using real threads. */
124   if (real_thread && !fsm->u.m.lock)
125     if (!silc_mutex_alloc(&fsm->u.m.lock))
126       thread->real_thread = FALSE;
127 }
128
129 /* FSM is destroyed through scheduler to make sure that all dying
130    real system threads will have their finish callbacks scheduled before
131    this one (when SILC_FSM_THREAD_WAIT was used). */
132
133 SILC_TASK_CALLBACK(silc_fsm_free_final)
134 {
135   SilcFSM f = context;
136
137 #if defined(SILC_DEBUG)
138   /* We must be finished */
139   SILC_ASSERT(f->finished);
140
141   /* Machine must not have active threads */
142   if (!f->thread && f->u.m.threads)
143     SILC_ASSERT(f->u.m.threads == 0);
144 #endif /* SILC_DEBUG */
145
146   if (!f->thread && f->u.m.lock)
147     silc_mutex_free(f->u.m.lock);
148
149   if (f->thread && f->u.t.sema)
150     silc_fsm_sema_free(f->u.t.sema);
151
152   silc_free(f);
153 }
154
155 /* Free FSM */
156
157 void silc_fsm_free(void *fsm)
158 {
159   SilcFSM f = fsm;
160   silc_schedule_task_add_timeout(f->schedule, silc_fsm_free_final, f, 0, 1);
161 }
162
163 /* Task to start real thread. We start threads through scheduler, not
164    directly in silc_fsm_start. */
165
166 SILC_TASK_CALLBACK(silc_fsm_start_real_thread)
167 {
168   SilcFSM f = context;
169
170 #ifdef SILC_THREADS
171   if (silc_thread_create(silc_fsm_thread, f, FALSE))
172     return;
173 #endif /* SILC_THREADS */
174
175   SILC_LOG_DEBUG(("Could not create real thread, using normal FSM thread"));
176
177   /* Normal FSM operation */
178   f->real_thread = FALSE;
179   silc_fsm_continue_sync(f);
180 }
181
182 /* Start FSM in the specified state */
183
184 void silc_fsm_start(void *fsm, SilcFSMStateCallback start_state)
185 {
186   SilcFSM f = fsm;
187
188   SILC_LOG_DEBUG(("Starting %s %p", f->thread ? "thread" : "FSM", fsm));
189
190   f->finished = FALSE;
191   f->next_state = start_state;
192   f->synchronous = FALSE;
193
194   /* Start real thread through scheduler */
195   if (f->thread && f->real_thread) {
196     silc_schedule_task_add_timeout(f->schedule, silc_fsm_start_real_thread,
197                                    f, 0, 1);
198     return;
199   }
200
201   /* Normal FSM operation */
202   silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f, 0, 1);
203 }
204
205 /* Start FSM in the specified state synchronously */
206
207 void silc_fsm_start_sync(void *fsm, SilcFSMStateCallback start_state)
208 {
209   SilcFSM f = fsm;
210
211   SILC_LOG_DEBUG(("Starting %s %p", f->thread ? "thread" : "FSM", fsm));
212
213   f->finished = FALSE;
214   f->next_state = start_state;
215   f->synchronous = TRUE;
216
217   /* Start real thread directly */
218   if (f->thread && f->real_thread) {
219     silc_fsm_start_real_thread(f->schedule,
220                                silc_schedule_get_context(f->schedule),
221                                0, 0, f);
222     return;
223   }
224
225   /* Normal FSM operation */
226   silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
227 }
228
229 /* Set next FSM state */
230
231 void silc_fsm_next(void *fsm, SilcFSMStateCallback next_state)
232 {
233   SilcFSM f = fsm;
234   f->next_state = next_state;
235 }
236
237 /* Continue after timeout */
238
239 void silc_fsm_next_later(void *fsm, SilcFSMStateCallback next_state,
240                          SilcUInt32 seconds, SilcUInt32 useconds)
241 {
242   SilcFSM f = fsm;
243   f->next_state = next_state;
244   if (!seconds && !useconds)
245     return;
246   silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f,
247                                  seconds, useconds);
248 }
249
250 /* Continue after callback or async operation */
251
252 void silc_fsm_continue(void *fsm)
253 {
254   SilcFSM f = fsm;
255   silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f, 0, 1);
256 }
257
258 /* Continue after callback or async operation immediately */
259
260 void silc_fsm_continue_sync(void *fsm)
261 {
262   SilcFSM f = fsm;
263   silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
264 }
265
266 /* Return associated scheduler */
267
268 SilcSchedule silc_fsm_get_schedule(void *fsm)
269 {
270   SilcFSM f = fsm;
271   return f->schedule;
272 }
273
274 /* Return thread's machine */
275
276 SilcFSM silc_fsm_get_machine(SilcFSMThread thread)
277 {
278   SILC_ASSERT(thread->thread);
279   return (SilcFSM)thread->u.t.fsm;
280 }
281
282 /* Set context */
283
284 void silc_fsm_set_context(void *fsm, void *fsm_context)
285 {
286   SilcFSM f = fsm;
287   f->fsm_context = fsm_context;
288 }
289
290 /* Get context */
291
292 void *silc_fsm_get_context(void *fsm)
293 {
294   SilcFSM f = fsm;
295   return f->fsm_context;
296 }
297
298 /* Set state context */
299
300 void silc_fsm_set_state_context(void *fsm, void *state_context)
301 {
302   SilcFSM f = fsm;
303   f->state_context = state_context;
304 }
305
306 /* Get state context */
307
308 void *silc_fsm_get_state_context(void *fsm)
309 {
310   SilcFSM f = fsm;
311   return f->state_context;
312 }
313
314 /* Wait for thread to terminate */
315
316 SilcBool silc_fsm_thread_wait(void *fsm, void *thread)
317 {
318   SilcFSM t = thread;
319
320   SILC_ASSERT(t->thread);
321
322   if (t->finished)
323     return FALSE;
324   t->u.t.sema = silc_fsm_sema_alloc(t->u.t.fsm, 0);
325   if (!t->u.t.sema)
326     return FALSE;
327
328   SILC_LOG_DEBUG(("Waiting for thread %p to terminate", thread));
329   silc_fsm_sema_wait(t->u.t.sema, fsm);
330   return TRUE;
331 }
332
333 /* The machine */
334
335 SILC_TASK_CALLBACK(silc_fsm_run)
336 {
337   SilcFSM fsm = context;
338   SilcFSMStatus status;
339
340   SILC_LOG_DEBUG(("Running %s %p", fsm->thread ? "thread" : "FSM", fsm));
341
342   /* Run the states */
343   do
344     status = fsm->next_state(fsm, fsm->fsm_context, fsm->state_context);
345   while (status == SILC_FSM_CONTINUE);
346
347   switch (status) {
348   case SILC_FSM_YIELD:
349     /* Continue through scheduler */
350     silc_fsm_continue(fsm);
351     break;
352
353   case SILC_FSM_WAIT:
354     /* The machine is in hold */
355     SILC_LOG_DEBUG(("State wait %p", fsm));
356     fsm->synchronous = FALSE;
357     break;
358
359   case SILC_FSM_FINISH:
360     /* Finish the state machine */
361     SILC_LOG_DEBUG(("State finish %p", fsm));
362 #if defined(SILC_DEBUG)
363     SILC_ASSERT(!fsm->finished);
364 #endif /* SILC_DEBUG */
365     fsm->finished = TRUE;
366
367     /* If we are thread and using real threads, the FSM thread will finish
368        after the real thread has finished, in the main thread. */
369     if (fsm->thread && fsm->real_thread) {
370       silc_schedule_stop(fsm->schedule);
371       break;
372     }
373
374     /* Normal FSM operation */
375     if (fsm->synchronous)
376       silc_fsm_finish(fsm->schedule, app_context, 0, 0, fsm);
377     else
378       silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_finish,
379                                      fsm, 0, 1);
380     break;
381
382   default:
383     break;
384   }
385 }
386
387 /* Finishes the FSM.  This is always executed in the main thread, even
388    for FSM threads that were run in real threads. */
389
390 SILC_TASK_CALLBACK(silc_fsm_finish)
391 {
392   SilcFSM fsm = context;
393
394   SILC_LOG_DEBUG(("%s %p, is finished", fsm->thread ? "Thread" : "FSM", fsm));
395
396   fsm->next_state = NULL;
397
398   if (fsm->thread) {
399     /* This is thread, send signal */
400     if (fsm->u.t.sema) {
401       silc_fsm_thread_termination_post(fsm->u.t.sema);
402       silc_fsm_sema_free(fsm->u.t.sema);
403       fsm->u.t.sema = NULL;
404     }
405
406     /* Remove the thread from machine */
407     fsm->u.t.fsm->u.m.threads--;
408
409     /* Call the destructor callback only if the underlaying machine is
410        still valid. */
411     if (fsm->destructor && fsm->u.t.fsm->finished == FALSE)
412       fsm->destructor(fsm, fsm->fsm_context, fsm->destructor_context);
413
414   } else {
415     if (fsm->u.m.lock) {
416       silc_mutex_free(fsm->u.m.lock);
417       fsm->u.m.lock = NULL;
418     }
419
420     /* Call the destructor callback. */
421     if (fsm->destructor)
422       fsm->destructor(fsm, fsm->fsm_context, fsm->destructor_context);
423   }
424 }
425
426 /* Allocate FSM semaphore */
427
428 SilcFSMSema silc_fsm_sema_alloc(SilcFSM fsm, SilcUInt32 value)
429 {
430   SilcFSMSema sema;
431
432   sema = silc_calloc(1, sizeof(*sema));
433   if (!sema)
434     return NULL;
435
436   silc_fsm_sema_init(sema, fsm, value);
437   sema->allocated = TRUE;
438
439   return sema;
440 }
441
442 /* Initializes FSM semaphore */
443
444 void silc_fsm_sema_init(SilcFSMSema sema, SilcFSM fsm, SilcUInt32 value)
445 {
446   SILC_LOG_DEBUG(("Initializing semaphore %p", sema));
447 #if defined(SILC_DEBUG)
448   SILC_ASSERT(!fsm->thread);
449 #endif /* SILC_DEBUG */
450   memset(sema, 0, sizeof(*sema));
451   sema->fsm = fsm;
452   sema->refcnt = 0;
453   silc_list_init(sema->waiters, struct SilcFSMObject, next);
454   sema->value = value;
455 }
456
457 /* Free semaphore */
458
459 void silc_fsm_sema_free(SilcFSMSema sema)
460 {
461   if (sema->refcnt > 0)
462     return;
463   if (silc_list_count(sema->waiters) > 0)
464     return;
465   silc_free(sema);
466 }
467
468 /* Reference semaphore */
469
470 static void silc_fsm_sema_ref(SilcFSMSema sema)
471 {
472   sema->refcnt++;
473 }
474
475 /* Unreference semaphore */
476
477 static void silc_fsm_sema_unref(SilcFSMSema sema)
478 {
479   sema->refcnt--;
480   if (sema->refcnt == 0 && sema->allocated)
481     silc_fsm_sema_free(sema);
482 }
483
484 /* Wait until semaphore is non-zero. */
485
486 SilcUInt32 silc_fsm_sema_wait(SilcFSMSema sema, void *fsm)
487 {
488   SilcMutex lock = sema->fsm->u.m.lock;
489
490   silc_mutex_lock(lock);
491
492   if (!sema->value) {
493 #if defined(SILC_DEBUG)
494     SilcFSM entry;
495     silc_list_start(sema->waiters);
496     while ((entry = silc_list_get(sema->waiters)) != SILC_LIST_END)
497       SILC_ASSERT(entry != fsm);
498 #endif /* SILC_DEBUG */
499
500     SILC_LOG_DEBUG(("Waiting for semaphore %p", sema));
501
502     /* Add the FSM to waiter list */
503     silc_list_add(sema->waiters, fsm);
504     silc_mutex_unlock(lock);
505     return 0;
506   }
507
508   SILC_LOG_DEBUG(("Acquired semaphore %p", sema));
509
510   /* It is possible that this FSM is in the list so remove it */
511   silc_list_del(sema->waiters, fsm);
512   sema->value--;
513   silc_mutex_unlock(lock);
514   return 1;
515 }
516
517 /* Wait util semaphore is non-zero, or timeout occurs. */
518
519 SilcUInt32 silc_fsm_sema_timedwait(SilcFSMSema sema, void *fsm,
520                                    SilcUInt32 seconds, SilcUInt32 useconds,
521                                    SilcBool *ret_to)
522 {
523   SilcMutex lock = sema->fsm->u.m.lock;
524   SilcFSM f = fsm;
525   SilcUInt32 value;
526
527   silc_mutex_lock(lock);
528
529   if (f->sema_timedout) {
530     SILC_LOG_DEBUG(("Semaphore was timedout"));
531     f->sema_timedout = FALSE;
532     if (ret_to)
533       *ret_to = TRUE;
534     silc_mutex_unlock(lock);
535     return 1;
536   }
537
538   silc_mutex_unlock(lock);
539
540   value = silc_fsm_sema_wait(sema, fsm);
541   if (!value) {
542     silc_schedule_task_add_timeout(f->schedule, silc_fsm_sema_timedout,
543                                    f, seconds, useconds);
544     f->sema = sema;
545   }
546
547   if (ret_to)
548     *ret_to = FALSE;
549
550   return value;
551 }
552
553 /* Semaphore timedout */
554
555 SILC_TASK_CALLBACK(silc_fsm_sema_timedout)
556 {
557   SilcFSM fsm = context;
558   SilcMutex lock = fsm->sema->fsm->u.m.lock;
559
560   SILC_LOG_DEBUG(("Semaphore %p timedout", fsm->sema));
561
562   /* Remove the waiter from the semaphore */
563   silc_mutex_lock(lock);
564   silc_list_del(fsm->sema->waiters, fsm);
565
566   /* Continue */
567   if (fsm->sema) {
568     silc_fsm_continue(fsm);
569     fsm->sema_timedout = TRUE;
570     fsm->sema = NULL;
571   }
572
573   silc_mutex_unlock(lock);
574 }
575
576 /* Signalled, semaphore */
577
578 SILC_TASK_CALLBACK(silc_fsm_signal)
579 {
580   SilcFSMSemaPost p = context;
581   SilcMutex lock = p->sema->fsm->u.m.lock;
582
583   /* If the semaphore value has went to zero while we've been waiting this
584      callback, sempahore has been been signalled already.  It can happen
585      when using real threads because the FSM may not be waiting state when
586      the sempahore is posted. */
587   silc_mutex_lock(lock);
588   if (!p->sema->value) {
589     silc_mutex_unlock(lock);
590     silc_fsm_sema_unref(p->sema);
591     silc_free(p);
592     return;
593   }
594   silc_mutex_unlock(lock);
595
596   SILC_LOG_DEBUG(("Signalled %s %p", p->fsm->thread ? "thread" : "FSM",
597                   p->fsm));
598
599   /* Signal */
600   silc_fsm_continue_sync(p->fsm);
601
602   silc_fsm_sema_unref(p->sema);
603   silc_free(p);
604 }
605
606 /* Increase semaphore */
607
608 void silc_fsm_sema_post(SilcFSMSema sema)
609 {
610   SilcFSM fsm;
611   SilcFSMSemaPost p;
612   SilcMutex lock = sema->fsm->u.m.lock;
613
614   SILC_LOG_DEBUG(("Posting semaphore %p", sema));
615
616   silc_mutex_lock(lock);
617
618   sema->value++;
619   silc_list_start(sema->waiters);
620   while ((fsm = silc_list_get(sema->waiters)) != SILC_LIST_END) {
621     if (fsm->sema) {
622       silc_schedule_task_del_by_all(fsm->schedule, 0, silc_fsm_sema_timedout,
623                                     fsm);
624       fsm->sema = NULL;
625     }
626
627     p = silc_calloc(1, sizeof(*p));
628     if (!p)
629       continue;
630     p->sema = sema;
631     p->fsm = fsm;
632     silc_fsm_sema_ref(sema);
633
634     /* Signal through scheduler.  Wake up destination scheduler in case
635        caller is a real thread. */
636     silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_signal, p, 0, 1);
637     silc_schedule_wakeup(fsm->schedule);
638   }
639
640   silc_mutex_unlock(lock);
641 }
642
643 /* Post thread termination semaphore.  Special function used only to
644    signal thread termination when SILC_FSM_THREAD_WAIT was used. */
645
646 static void silc_fsm_thread_termination_post(SilcFSMSema sema)
647 {
648   SilcFSM fsm;
649   SilcMutex lock = sema->fsm->u.m.lock;
650
651   SILC_LOG_DEBUG(("Post thread terminate semaphore %p", sema));
652
653   silc_mutex_lock(lock);
654
655   silc_list_start(sema->waiters);
656   while ((fsm = silc_list_get(sema->waiters)) != SILC_LIST_END) {
657     /* Signal on thread termination.  Wake up destination scheduler in case
658        caller is a real thread. */
659     silc_list_del(sema->waiters, fsm);
660     silc_fsm_continue(fsm);
661     silc_schedule_wakeup(fsm->schedule);
662   }
663
664   silc_mutex_unlock(lock);
665 }
666
667 /* Real thread */
668
669 static void *silc_fsm_thread(void *context)
670 {
671   SilcFSM fsm = context;
672   SilcSchedule old = fsm->schedule;
673
674   SILC_LOG_DEBUG(("Starting FSM thread in real thread"));
675
676   /* We allocate new SilcSchedule for the FSM, as the old SilcSchedule
677      cannot be used in this thread.  Application may still use it if it
678      wants but we use our own. */
679   fsm->schedule = silc_schedule_init(0, old);
680   if (!fsm->schedule)
681     return NULL;
682
683   /* Start the FSM thread */
684   if (!silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_run, fsm, 0, 1))
685     return NULL;
686
687   /* Run the scheduler */
688   silc_schedule(fsm->schedule);
689
690   /* Free resources */
691   silc_schedule_uninit(fsm->schedule);
692
693   fsm->schedule = old;
694
695   /* Finish the FSM thread in the main thread */
696   SILC_ASSERT(fsm->finished);
697   silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_finish, fsm, 0, 1);
698   silc_schedule_wakeup(fsm->schedule);
699
700   return NULL;
701 }