Generate scheduled updates. Untested, but builds.
[trust_router.git] / trp / trp_rtable.c
1 #include <stdlib.h>
2
3 #include <glib.h>
4 #include <talloc.h>
5 #include <time.h>
6
7 #include <trust_router/tr_name.h>
8 #include <trp_internal.h>
9 #include <trp_rtable.h>
10 #include <tr_debug.h>
11
12 /* Note: be careful mixing talloc with glib. */
13
14 static int trp_rentry_destructor(void *obj)
15 {
16   TRP_RENTRY *entry=talloc_get_type_abort(obj, TRP_RENTRY);
17   if (entry->apc!=NULL)
18     tr_free_name(entry->apc);
19   if (entry->realm!=NULL)
20     tr_free_name(entry->realm);
21   if (entry->trust_router!=NULL)
22     tr_free_name(entry->trust_router);
23   if (entry->peer!=NULL)
24     tr_free_name(entry->peer);
25   if (entry->next_hop!=NULL)
26     tr_free_name(entry->next_hop);
27   return 0;
28 }
29
30 TRP_RENTRY *trp_rentry_new(TALLOC_CTX *mem_ctx)
31 {
32   TRP_RENTRY *entry=talloc(mem_ctx, TRP_RENTRY);
33   if (entry!=NULL) {
34     entry->apc=NULL;
35     entry->realm=NULL;
36     entry->trust_router=NULL;
37     entry->peer=NULL;
38     entry->next_hop=NULL;
39     entry->selected=0;
40     entry->interval=0;
41     entry->expiry=talloc(entry, struct timespec);
42     if (entry->expiry==NULL) {
43       talloc_free(entry);
44       return NULL;
45     }
46     talloc_set_destructor((void *)entry, trp_rentry_destructor);
47   }
48   return entry;
49 }
50
51 void trp_rentry_free(TRP_RENTRY *entry)
52 {
53   if (entry!=NULL)
54     talloc_free(entry);
55 }
56
57 void trp_rentry_set_apc(TRP_RENTRY *entry, TR_NAME *apc)
58 {
59   entry->apc=apc;
60 }
61
62 TR_NAME *trp_rentry_get_apc(TRP_RENTRY *entry)
63 {
64   return entry->apc;
65 }
66
67 void trp_rentry_set_realm(TRP_RENTRY *entry, TR_NAME *realm)
68 {
69   entry->realm=realm;
70 }
71
72 TR_NAME *trp_rentry_get_realm(TRP_RENTRY *entry)
73 {
74   return entry->realm;
75 }
76
77 void trp_rentry_set_trust_router(TRP_RENTRY *entry, TR_NAME *tr)
78 {
79   entry->trust_router=tr;
80 }
81
82 TR_NAME *trp_rentry_get_trust_router(TRP_RENTRY *entry)
83 {
84   return entry->trust_router;
85 }
86
87 void trp_rentry_set_peer(TRP_RENTRY *entry, TR_NAME *peer)
88 {
89   entry->peer=peer;
90 }
91
92 TR_NAME *trp_rentry_get_peer(TRP_RENTRY *entry)
93 {
94   return entry->peer;
95 }
96
97 void trp_rentry_set_metric(TRP_RENTRY *entry, unsigned int metric)
98 {
99   entry->metric=metric;
100 }
101
102 unsigned int trp_rentry_get_metric(TRP_RENTRY *entry)
103 {
104   return entry->metric;
105 }
106
107 void trp_rentry_set_next_hop(TRP_RENTRY *entry, TR_NAME *next_hop)
108 {
109   entry->next_hop=next_hop;
110 }
111
112 TR_NAME *trp_rentry_get_next_hop(TRP_RENTRY *entry)
113 {
114   return entry->next_hop;
115 }
116
117 void trp_rentry_set_selected(TRP_RENTRY *entry, int sel)
118 {
119   entry->selected=sel;
120 }
121
122 int trp_rentry_get_selected(TRP_RENTRY *entry)
123 {
124   return entry->selected;
125 }
126
127 void trp_rentry_set_interval(TRP_RENTRY *entry, int interval)
128 {
129   entry->interval=interval;
130 }
131
132 int trp_rentry_get_interval(TRP_RENTRY *entry)
133 {
134   return entry->interval;
135 }
136
137 /* copies incoming value, does not assume responsibility for freeing */
138 void trp_rentry_set_expiry(TRP_RENTRY *entry, struct timespec *exp)
139 {
140   entry->expiry->tv_sec=exp->tv_sec;
141   entry->expiry->tv_nsec=exp->tv_nsec;
142 }
143
144 struct timespec *trp_rentry_get_expiry(TRP_RENTRY *entry)
145 {
146   return entry->expiry;
147 }
148
149
150 /* result must be freed with g_free */
151 static gchar *tr_name_to_g_str(const TR_NAME *n)
152 {
153   gchar *s=g_strndup(n->buf, n->len);
154   return s;
155 }
156
157 /* hash function for TR_NAME keys */
158 static guint trp_tr_name_hash(gconstpointer key)
159 {
160   const TR_NAME *name=(TR_NAME *)key;
161   gchar *s=tr_name_to_g_str(name);
162   guint hash=g_str_hash(s);
163   g_free(s);
164   return hash;
165 }
166
167 /* hash equality function for TR_NAME keys */
168 static gboolean trp_tr_name_equal(gconstpointer key1, gconstpointer key2)
169 {
170   const TR_NAME *n1=(TR_NAME *)key1;
171   const TR_NAME *n2=(TR_NAME *)key2;
172   gchar *s1=tr_name_to_g_str(n1);
173   gchar *s2=tr_name_to_g_str(n2);
174   gboolean equal=g_str_equal(s1, s2);
175   g_free(s1);
176   g_free(s2);
177   return equal;
178 }
179
180 /* free a value to the top level rtable (a hash of all entries in the apc) */
181 static void trp_rtable_destroy_table(gpointer data)
182 {
183   g_hash_table_destroy(data);
184 }
185
186 static void trp_rtable_destroy_rentry(gpointer data)
187 {
188   trp_rentry_free(data);
189 }
190
191 static void trp_rtable_destroy_tr_name(gpointer data)
192 {
193   tr_free_name(data);
194 }
195
196 TRP_RTABLE *trp_rtable_new(void)
197 {
198   GHashTable *new=g_hash_table_new_full(trp_tr_name_hash,
199                                         trp_tr_name_equal,
200                                         trp_rtable_destroy_tr_name,
201                                         trp_rtable_destroy_table);
202   return new;
203 }
204
205 void trp_rtable_free(TRP_RTABLE *rtbl)
206 {
207   g_hash_table_destroy(rtbl);
208 }
209
210 static GHashTable *trp_rtbl_get_or_add_table(GHashTable *tbl, TR_NAME *key, GDestroyNotify destroy)
211 {
212   GHashTable *val_tbl=NULL;
213
214   val_tbl=g_hash_table_lookup(tbl, key);
215   if (val_tbl==NULL) {
216     val_tbl=g_hash_table_new_full(trp_tr_name_hash,
217                                   trp_tr_name_equal,
218                                   trp_rtable_destroy_tr_name,
219                                   destroy);
220     g_hash_table_insert(tbl, tr_dup_name(key), val_tbl);
221   }
222   return val_tbl;
223 }
224
225 void trp_rtable_add(TRP_RTABLE *rtbl, TRP_RENTRY *entry)
226 {
227   GHashTable *apc_tbl=NULL;
228   GHashTable *realm_tbl=NULL;
229
230   apc_tbl=trp_rtbl_get_or_add_table(rtbl, entry->apc, trp_rtable_destroy_table);
231   realm_tbl=trp_rtbl_get_or_add_table(apc_tbl, entry->realm, trp_rtable_destroy_rentry);
232   g_hash_table_insert(realm_tbl, tr_dup_name(entry->peer), entry); /* destroys and replaces a duplicate */
233 }
234
235 /* note: the entry pointer passed in is invalid after calling this because the entry is freed */
236 void trp_rtable_remove(TRP_RTABLE *rtbl, TRP_RENTRY *entry)
237 {
238   GHashTable *apc_tbl=NULL;
239   GHashTable *realm_tbl=NULL;
240
241   apc_tbl=g_hash_table_lookup(rtbl, entry->apc);
242   if (apc_tbl==NULL)
243     return;
244
245   realm_tbl=g_hash_table_lookup(apc_tbl, entry->realm);
246   if (realm_tbl==NULL)
247     return;
248
249   /* remove the element */
250   g_hash_table_remove(realm_tbl, entry->peer);
251   /* if that was the last entry in the realm, remove the realm table */
252   if (g_hash_table_size(realm_tbl)==0)
253     g_hash_table_remove(apc_tbl, entry->realm);
254   /* if that was the last realm in the apc, remove the apc table */
255   if (g_hash_table_size(apc_tbl)==0)
256     g_hash_table_remove(rtbl, entry->apc);
257 }
258
259 /* gets the actual hash table, for internal use only */
260 static GHashTable *trp_rtable_get_apc_table(TRP_RTABLE *rtbl, TR_NAME *apc)
261 {
262   return g_hash_table_lookup(rtbl, apc);
263 }
264
265 /* gets the actual hash table, for internal use only */
266 static GHashTable *trp_rtable_get_realm_table(TRP_RTABLE *rtbl, TR_NAME *apc, TR_NAME *realm)
267 {
268   GHashTable *apc_tbl=trp_rtable_get_apc_table(rtbl, apc);
269   if (apc_tbl==NULL)
270     return NULL;
271   else
272     return g_hash_table_lookup(apc_tbl, realm);
273 }
274
275 struct table_size_cookie {
276   TRP_RTABLE *rtbl;
277   size_t size;
278 };
279 static void trp_rtable_size_helper(gpointer key, gpointer value, gpointer user_data)
280 {
281   struct table_size_cookie *data=(struct table_size_cookie *)user_data;
282   data->size += trp_rtable_apc_size(data->rtbl, (TR_NAME *)key);
283 };
284 size_t trp_rtable_size(TRP_RTABLE *rtbl)
285 {
286   struct table_size_cookie data={rtbl, 0};
287   g_hash_table_foreach(rtbl, trp_rtable_size_helper, &data);
288   return data.size;
289 }
290
291 struct table_apc_size_cookie {
292   TR_NAME *apc;
293   TRP_RTABLE *rtbl;
294   size_t size;
295 };
296 static void table_apc_size_helper(gpointer key, gpointer value, gpointer user_data)
297 {
298   struct table_apc_size_cookie *data=(struct table_apc_size_cookie *)user_data;
299   data->size += trp_rtable_realm_size(data->rtbl, data->apc, (TR_NAME *)key);
300 }
301 size_t trp_rtable_apc_size(TRP_RTABLE *rtbl, TR_NAME *apc)
302 {
303   struct table_apc_size_cookie data={apc, rtbl, 0};
304   GHashTable *apc_tbl=trp_rtable_get_apc_table(rtbl, apc);
305   if (apc_tbl==NULL)
306     return 0;;
307   g_hash_table_foreach(apc_tbl, table_apc_size_helper, &data);
308   return data.size;
309 }
310
311 size_t trp_rtable_realm_size(TRP_RTABLE *rtbl, TR_NAME *apc, TR_NAME *realm)
312 {
313   GHashTable *realm_tbl=trp_rtable_get_realm_table(rtbl, apc, realm);
314   if (realm_tbl==NULL)
315     return 0;
316   else
317     return g_hash_table_size(g_hash_table_lookup(
318                                g_hash_table_lookup(rtbl, apc),
319                                realm));
320 }
321
322 /* Returns an array of pointers to TRP_RENTRY, length of array in n_out.
323  * Caller must free the array (in the talloc NULL context), but must
324  * not free its contents. */
325 TRP_RENTRY **trp_rtable_get_entries(TRP_RTABLE *rtbl, size_t *n_out)
326 {
327   TRP_RENTRY **ret=NULL;
328   TR_NAME **apc=NULL;
329   size_t n_apc=0;
330   TRP_RENTRY **apc_entries=NULL;
331   size_t n_entries=0;
332   size_t ii_ret=0;
333
334   *n_out=trp_rtable_size(rtbl);
335   if (*n_out==0)
336     return NULL;
337
338   ret=talloc_array(NULL, TRP_RENTRY *, *n_out);
339   if (ret==NULL) {
340     tr_crit("trp_rtable_get_entries: unable to allocate return array.");
341     *n_out=0;
342     return NULL;
343   }
344
345   ii_ret=0; /* counts output entries */
346   apc=trp_rtable_get_apcs(rtbl, &n_apc);
347   while(n_apc--) {
348     apc_entries=trp_rtable_get_apc_entries(rtbl, apc[n_apc], &n_entries);
349     while (n_entries--)
350       ret[ii_ret++]=apc_entries[n_entries];
351     talloc_free(apc_entries);
352   }
353   talloc_free(apc);
354
355   if (ii_ret!=*n_out) {
356     tr_crit("trp_rtable_get_entries: found incorrect number of entries.");
357     talloc_free(ret);
358     *n_out=0;
359     return NULL;
360   }
361   return ret;
362 }
363
364 /* Returns an array of pointers to TR_NAME, length of array in n_out.
365  * Caller must free the array (in the talloc NULL context). */
366 TR_NAME **trp_rtable_get_apcs(TRP_RTABLE *rtbl, size_t *n_out)
367 {
368   size_t len=g_hash_table_size(rtbl); /* known apcs are keys in top level hash table */
369   size_t ii=0;
370   GList *apcs=NULL;;
371   GList *p=NULL;
372   TR_NAME **ret=NULL;
373
374   if (len==0) {
375     *n_out=0;
376     return NULL;
377   }
378     
379   ret=talloc_array(NULL, TR_NAME *, len);
380   if (ret==NULL) {
381     tr_crit("trp_rtable_get_apcs: unable to allocate return array.");
382     *n_out=0;
383     return NULL;
384   }
385   apcs=g_hash_table_get_keys(rtbl);
386   for (ii=0,p=apcs; p!=NULL; ii++,p=g_list_next(p))
387     ret[ii]=(TR_NAME *)p->data;
388
389   g_list_free(apcs);
390
391   *n_out=len;
392   return ret;
393 }
394
395 /* Returns an array of pointers to TR_NAME, length of array in n_out.
396  * Caller must free the array (in the talloc NULL context). */
397 TR_NAME **trp_rtable_get_apc_realms(TRP_RTABLE *rtbl, TR_NAME *apc, size_t *n_out)
398 {
399   size_t ii=0;
400   TRP_RTABLE *apc_tbl=g_hash_table_lookup(rtbl, apc);;
401   GList *entries=NULL;
402   GList *p=NULL;
403   TR_NAME **ret=NULL;
404
405   if (apc_tbl==NULL) {
406     *n_out=0;
407     return NULL;
408   }
409   *n_out=g_hash_table_size(apc_tbl); /* set output length */
410   ret=talloc_array(NULL, TR_NAME *, *n_out);
411   entries=g_hash_table_get_keys(apc_tbl);
412   for (ii=0,p=entries; p!=NULL; ii++,p=g_list_next(p))
413     ret[ii]=(TR_NAME *)p->data;
414
415   g_list_free(entries);
416   return ret;
417 }
418
419 /* Get all entries in an apc. Returns an array of pointers in NULL talloc context.
420  * Caller must free this list with talloc_free, but must not free the entries in the
421  * list.. */
422 TRP_RENTRY **trp_rtable_get_apc_entries(TRP_RTABLE *rtbl, TR_NAME *apc, size_t *n_out)
423 {
424   size_t ii=0, jj=0;
425   TR_NAME **realm=NULL;
426   size_t n_realms=0;
427   TRP_RENTRY **realm_entries=NULL;
428   size_t n_entries=0;
429   TRP_RENTRY **ret=NULL;
430   size_t ii_ret=0;
431
432   *n_out=trp_rtable_apc_size(rtbl, apc);
433   if (*n_out==0)
434     return NULL;
435
436   ret=talloc_array(NULL, TRP_RENTRY *, *n_out);
437   if (ret==NULL) {
438     tr_crit("trp_rtable_get_apc_entries: could not allocate return array.");
439     *n_out=0;
440     return NULL;
441   }
442   
443   ii_ret=0; /* counts entries in the output array */
444   realm=trp_rtable_get_apc_realms(rtbl, apc, &n_realms);
445   for (ii=0; ii<n_realms; ii++) {
446     realm_entries=trp_rtable_get_realm_entries(rtbl, apc, realm[ii], &n_entries);
447     for (jj=0; jj<n_entries; jj++)
448       ret[ii_ret++]=realm_entries[jj];
449     talloc_free(realm_entries);
450   }
451   talloc_free(realm);
452
453   if (ii_ret!=*n_out) {
454     tr_crit("trp_rtable_get_apc_entries: found incorrect number of entries.");
455     talloc_free(ret);
456     *n_out=0;
457     return NULL;
458   }
459
460   return ret;
461 }
462
463 /* Get all entries in an apc/realm. Returns an array of pointers in NULL talloc context.
464  * Caller must free this list with talloc_free, but must not free the entries in the
465  * list.. */
466 TRP_RENTRY **trp_rtable_get_realm_entries(TRP_RTABLE *rtbl, TR_NAME *apc, TR_NAME *realm, size_t *n_out)
467 {
468   size_t ii=0;
469   TRP_RENTRY **ret=NULL;
470   TR_NAME **peer=NULL;
471
472   peer=trp_rtable_get_apc_realm_peers(rtbl, apc, realm, n_out);
473   ret=talloc_array(NULL, TRP_RENTRY *, *n_out);
474   if (ret==NULL) {
475     tr_crit("trp_rtable_get_realm_entries: could not allocate return array.");
476     talloc_free(peer);
477     n_out=0;
478     return NULL;
479   }
480   for (ii=0; ii<*n_out; ii++)
481     ret[ii]=trp_rtable_get_entry(rtbl, apc, realm, peer[ii]);
482   talloc_free(peer);
483   return ret;
484 }
485
486 TR_NAME **trp_rtable_get_apc_realm_peers(TRP_RTABLE *rtbl, TR_NAME *apc, TR_NAME *realm, size_t *n_out)
487 {
488   TR_NAME **ret=NULL;
489   GHashTable *realm_tbl=NULL;
490   GList *keys=NULL;
491   GList *p=NULL;
492   size_t ii=0;
493
494   *n_out=trp_rtable_realm_size(rtbl, apc, realm);
495   if (*n_out==0)
496     return NULL;
497   realm_tbl=trp_rtable_get_realm_table(rtbl, apc, realm);
498   ret=talloc_array(NULL, TR_NAME *, *n_out);
499   if (ret==NULL) {
500     tr_crit("trp_rtable_get_apc_realm_peers: could not allocate return array.");
501     *n_out=0;
502     return NULL;
503   }
504   keys=g_hash_table_get_keys(realm_tbl);
505   for (ii=0,p=keys; p!=NULL; ii++,p=g_list_next(p))
506     ret[ii]=(TR_NAME *)p->data;
507   g_list_free(keys);
508   return ret;
509 }
510
511 /* Gets a single entry. Do not free it. */
512 TRP_RENTRY *trp_rtable_get_entry(TRP_RTABLE *rtbl, TR_NAME *apc, TR_NAME *realm, TR_NAME *peer)
513 {
514   GHashTable *realm_tbl=NULL;
515   
516   realm_tbl=trp_rtable_get_realm_table(rtbl, apc, realm);
517   if (realm_tbl==NULL)
518     return NULL;
519   return g_hash_table_lookup(realm_tbl, peer); /* does not copy or increment ref count */
520 }
521
522 static char *timespec_to_str(struct timespec *ts)
523 {
524   struct tm tm;
525   char *s=NULL;
526
527   if (localtime_r(&(ts->tv_sec), &tm)==NULL)
528     return NULL;
529
530   s=malloc(40); /* long enough to contain strftime result */
531   if (s==NULL)
532     return NULL;
533
534   if (strftime(s, 40, "%F %T", &tm)==0) {
535     free(s);
536     return NULL;
537   }
538   return s;
539 }
540
541 TRP_RENTRY *trp_rtable_get_selected_entry(TRP_RTABLE *rtbl, TR_NAME *apc, TR_NAME *realm)
542 {
543   size_t n=0;
544   TRP_RENTRY **entry=trp_rtable_get_realm_entries(rtbl, apc, realm, &n);
545   TRP_RENTRY *selected=NULL;
546
547   if (n==0)
548     return NULL;
549
550   while(n-- && !trp_rentry_get_selected(entry[n])) { }
551   selected=entry[n];
552   talloc_free(entry);
553   return selected;
554 }
555
556 /* Pretty print a route table entry to a newly allocated string. If sep is NULL,
557  * returns comma+space separated string. */
558 char *trp_rentry_to_str(TALLOC_CTX *mem_ctx, TRP_RENTRY *entry, const char *sep)
559 {
560   char *apc=tr_name_strdup(entry->apc);
561   char *realm=tr_name_strdup(entry->realm);
562   char *peer=tr_name_strdup(entry->peer);
563   char *trust_router=tr_name_strdup(entry->trust_router);
564   char *next_hop=tr_name_strdup(entry->next_hop);
565   char *expiry=timespec_to_str(entry->expiry);
566   char *result=NULL;
567
568   if (sep==NULL)
569     sep=", ";
570
571   result=talloc_asprintf(mem_ctx,
572                          "%s%s%s%s%s%s%u%s%s%s%s%s%d%s%s",
573                          apc, sep,
574                          realm, sep,
575                          peer, sep,
576                          entry->metric, sep,
577                          trust_router, sep,
578                          next_hop, sep,
579                          entry->selected, sep,
580                          expiry);
581   free(apc);
582   free(realm);
583   free(peer);
584   free(trust_router);
585   free(next_hop);
586   free(expiry);
587   return result;
588 }
589
590 static int sort_tr_names_cmp(const void *a, const void *b)
591 {
592   TR_NAME **n1=(TR_NAME **)a;
593   TR_NAME **n2=(TR_NAME **)b;
594   return tr_name_cmp(*n1, *n2);
595 }
596
597 static void sort_tr_names(TR_NAME **names, size_t n_names)
598 {
599   qsort(names, n_names, sizeof(TR_NAME *), sort_tr_names_cmp);
600 }
601
602 char *trp_rtable_to_str(TALLOC_CTX *mem_ctx, TRP_RTABLE *rtbl, const char *sep, const char *lineterm)
603 {
604   TALLOC_CTX *tmp_ctx=talloc_new(NULL);
605   TR_NAME **apcs=NULL;
606   size_t n_apcs=0;
607   TR_NAME **realms=NULL;
608   size_t n_realms=0;
609   TRP_RENTRY **entries=NULL;
610   size_t n_entries=0;
611   char **tbl_strings=NULL;
612   size_t ii_tbl=0; /* counts tbl_strings */
613   size_t tbl_size=0;
614   size_t len=0;
615   size_t ii=0, jj=0, kk=0;
616   char *p=NULL;
617   char *result=NULL;
618
619   if (lineterm==NULL)
620     lineterm="\n";
621
622   tbl_size=trp_rtable_size(rtbl);
623   if (tbl_size==0) {
624     result=talloc_strdup(mem_ctx, lineterm);
625     goto cleanup;
626   }
627
628   tbl_strings=talloc_array(tmp_ctx, char *, tbl_size);
629   if (tbl_strings==NULL) {
630     result=talloc_strdup(mem_ctx, "error");
631     goto cleanup;
632   }
633   
634   apcs=trp_rtable_get_apcs(rtbl, &n_apcs);
635   talloc_steal(tmp_ctx, apcs);
636   sort_tr_names(apcs, n_apcs);
637   ii_tbl=0;
638   len=0;
639   for (ii=0; ii<n_apcs; ii++) {
640     realms=trp_rtable_get_apc_realms(rtbl, apcs[ii], &n_realms);
641     talloc_steal(tmp_ctx, realms);
642     sort_tr_names(realms, n_realms);
643     for (jj=0; jj<n_realms; jj++) {
644       entries=trp_rtable_get_realm_entries(rtbl, apcs[ii], realms[jj], &n_entries);
645       talloc_steal(tmp_ctx, entries);
646       for (kk=0; kk<n_entries; kk++) {
647         tbl_strings[ii_tbl]=trp_rentry_to_str(tmp_ctx, entries[kk], sep);
648         len+=strlen(tbl_strings[ii_tbl]);
649         ii_tbl++;
650       }
651       talloc_free(entries);
652     }
653     talloc_free(realms);
654   }
655   talloc_free(apcs);
656
657   /* now combine all the strings */
658   len += tbl_size*strlen(lineterm); /* space for line terminations*/
659   len += 1; /* nul terminator */
660   result=(char *)talloc_size(tmp_ctx, len);
661   for (p=result,ii=0; ii < tbl_size; ii++) {
662     p+=sprintf(p, "%s%s", tbl_strings[ii], lineterm);
663   }
664   talloc_steal(mem_ctx, result);
665   
666 cleanup:
667   talloc_free(tmp_ctx);
668   return result;
669 }