updates.
[silc.git] / apps / silcd / idlist.c
1 /*
2
3   idlist.c
4
5   Author: Pekka Riikonen <priikone@poseidon.pspt.fi>
6
7   Copyright (C) 1997 - 2001 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; either version 2 of the License, or
12   (at your option) any later version.
13   
14   This program is distributed in the hope that it will be useful,
15   but WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   GNU General Public License for more details.
18
19 */
20 /* $Id$ */
21
22 #include "serverincludes.h"
23 #include "idlist.h"
24
25 /******************************************************************************
26
27                              Common functions
28
29 ******************************************************************************/
30
31 /* This function is used to add keys and stuff to common ID entry data
32    structure. */
33
34 void silc_idlist_add_data(void *entry, SilcIDListData idata)
35 {
36   SilcIDListData data = (SilcIDListData)entry;
37   data->send_key = idata->send_key;
38   data->receive_key = idata->receive_key;
39   data->send_enc_key = idata->send_enc_key;
40   data->enc_key_len = idata->enc_key_len;
41   data->pfs = idata->pfs;
42   data->hash = idata->hash;
43   data->hmac = idata->hmac;
44   data->public_key = idata->public_key;
45   data->last_receive = idata->last_receive;
46   data->last_sent = idata->last_sent;
47   data->registered = idata->registered;
48 }
49
50 /* Free's all data in the common ID entry data structure. */
51
52 void silc_idlist_del_data(void *entry)
53 {
54   SilcIDListData idata = (SilcIDListData)entry;
55   if (idata->send_key)
56     silc_cipher_free(idata->send_key);
57   if (idata->receive_key)
58     silc_cipher_free(idata->receive_key);
59   if (idata->send_enc_key) {
60     memset(idata->send_enc_key, 0, idata->enc_key_len);
61     silc_free(idata->send_enc_key);
62   }
63   if (idata->hmac)
64     silc_hmac_free(idata->hmac);
65   if (idata->public_key)
66     silc_pkcs_public_key_free(idata->public_key);
67 }
68
69 /* Purges ID cache */
70
71 SILC_TASK_CALLBACK_GLOBAL(silc_idlist_purge)
72 {
73   SilcIDListPurge i = (SilcIDListPurge)context;
74
75   SILC_LOG_DEBUG(("Start"));
76
77   silc_idcache_purge(i->cache);
78   silc_task_register(i->timeout_queue, 0, 
79                      silc_idlist_purge,
80                      (void *)i, 600, 0,
81                      SILC_TASK_TIMEOUT, SILC_TASK_PRI_LOW);
82 }
83
84 /******************************************************************************
85
86                           Server entry functions
87
88 ******************************************************************************/
89
90 /* Add new server entry. This adds the new server entry to ID cache and
91    returns the allocated entry object or NULL on error. This is called
92    when new server connects to us. We also add ourselves to cache with
93    this function. */
94
95 SilcServerEntry 
96 silc_idlist_add_server(SilcIDList id_list, 
97                        char *server_name, int server_type,
98                        SilcServerID *id, SilcServerEntry router,
99                        void *connection)
100 {
101   SilcServerEntry server;
102
103   SILC_LOG_DEBUG(("Adding new server entry"));
104
105   server = silc_calloc(1, sizeof(*server));
106   server->server_name = server_name;
107   server->server_type = server_type;
108   server->id = id;
109   server->router = router;
110   server->connection = connection;
111
112   if (!silc_idcache_add(id_list->servers, server->server_name, 
113                         server->server_name ? strlen(server->server_name) : 0,
114                         SILC_ID_SERVER, (void *)server->id, 
115                         (void *)server, TRUE, FALSE)) {
116     silc_free(server);
117     return NULL;
118   }
119
120   return server;
121 }
122
123 /* Finds server by Server ID */
124
125 SilcServerEntry
126 silc_idlist_find_server_by_id(SilcIDList id_list, SilcServerID *id,
127                               SilcIDCacheEntry *ret_entry)
128 {
129   SilcIDCacheEntry id_cache = NULL;
130   SilcServerEntry server;
131
132   if (!id)
133     return NULL;
134
135   SILC_LOG_DEBUG(("Server ID (%s)",
136                   silc_id_render(id, SILC_ID_SERVER)));
137
138   if (!silc_idcache_find_by_id_one(id_list->servers, (void *)id, 
139                                    SILC_ID_SERVER, &id_cache))
140     return NULL;
141
142   server = (SilcServerEntry)id_cache->context;
143
144   if (ret_entry)
145     *ret_entry = id_cache;
146
147   return server;
148 }
149
150 /* Find server by name */
151
152 SilcServerEntry
153 silc_idlist_find_server_by_name(SilcIDList id_list, char *name,
154                                 SilcIDCacheEntry *ret_entry)
155 {
156   SilcIDCacheEntry id_cache = NULL;
157   SilcServerEntry server;
158
159   SILC_LOG_DEBUG(("Server by name `%s'", name));
160
161   if (!silc_idcache_find_by_data_one(id_list->servers, name, &id_cache))
162     return NULL;
163
164   server = (SilcServerEntry)id_cache->context;
165   
166   if (ret_entry)
167     *ret_entry = id_cache;
168
169   SILC_LOG_DEBUG(("Found"));
170
171   return server;
172 }
173
174 /* Find server by connection parameters, hostname and port */
175
176 SilcServerEntry
177 silc_idlist_find_server_by_conn(SilcIDList id_list, char *hostname,
178                                 int port, SilcIDCacheEntry *ret_entry)
179 {
180   SilcIDCacheList list = NULL;
181   SilcIDCacheEntry id_cache = NULL;
182   SilcServerEntry server = NULL;
183   SilcSocketConnection sock;
184  
185   SILC_LOG_DEBUG(("Server by hostname %s and port %d", hostname, port));
186
187   if (!silc_idcache_find_by_id(id_list->servers, SILC_ID_CACHE_ANY, 
188                                SILC_ID_SERVER, &list))
189     return NULL;
190
191   if (!silc_idcache_list_first(list, &id_cache)) {
192     silc_idcache_list_free(list);
193     return NULL;
194   }
195
196   while (id_cache) {
197     server = (SilcServerEntry)id_cache->context;
198     sock = (SilcSocketConnection)server->connection;
199     
200     if (sock && ((sock->hostname && !strcmp(sock->hostname, hostname)) ||
201                  (sock->ip && !strcmp(sock->ip, hostname)))
202         && sock->port == port)
203       break;
204
205     id_cache = NULL;
206     server = NULL;
207
208     if (!silc_idcache_list_next(list, &id_cache))
209       break;
210   }
211   
212   silc_idcache_list_free(list);
213
214   if (ret_entry)
215     *ret_entry = id_cache;
216
217   SILC_LOG_DEBUG(("Found"));
218
219   return server;
220 }
221
222 /* Replaces old Server ID with new one */ 
223
224 SilcServerEntry
225 silc_idlist_replace_server_id(SilcIDList id_list, SilcServerID *old_id,
226                               SilcServerID *new_id)
227 {
228   SilcIDCacheEntry id_cache = NULL;
229   SilcServerEntry server;
230
231   if (!old_id || !new_id)
232     return NULL;
233
234   SILC_LOG_DEBUG(("Replacing Server ID"));
235
236   if (!silc_idcache_find_by_id_one(id_list->servers, (void *)old_id, 
237                                    SILC_ID_SERVER, &id_cache))
238     return NULL;
239
240   server = (SilcServerEntry)id_cache->context;
241   silc_free(server->id);
242   server->id = new_id;
243   id_cache->id = (void *)new_id;
244
245   SILC_LOG_DEBUG(("Found"));
246
247   return server;
248 }
249
250 /* Removes and free's server entry from ID list */
251
252 int silc_idlist_del_server(SilcIDList id_list, SilcServerEntry entry)
253 {
254   if (entry) {
255     /* Remove from cache */
256     if (entry->id)
257       if (!silc_idcache_del_by_id(id_list->servers, SILC_ID_SERVER, 
258                                   (void *)entry->id))
259         return FALSE;
260
261     /* Free data */
262     if (entry->server_name)
263       silc_free(entry->server_name);
264     if (entry->id)
265       silc_free(entry->id);
266
267     memset(entry, 'F', sizeof(*entry));
268     silc_free(entry);
269     return TRUE;
270   }
271
272   return FALSE;
273 }
274
275 /******************************************************************************
276
277                           Client entry functions
278
279 ******************************************************************************/
280
281 /* Add new client entry. This adds the client entry to ID cache system
282    and returns the allocated client entry or NULL on error.  This is
283    called when new client connection is accepted to the server. If The
284    `router' is provided then the all server routines assume that the client
285    is not directly connected local client but it has router set and is
286    remote.  If this is the case then `connection' must be NULL.  If, on the
287    other hand, the `connection' is provided then the client is assumed
288    to be directly connected local client and `router' must be NULL. */
289
290 SilcClientEntry
291 silc_idlist_add_client(SilcIDList id_list, unsigned char *nickname, 
292                        uint32 nickname_len, char *username, 
293                        char *userinfo, SilcClientID *id, 
294                        SilcServerEntry router, void *connection)
295 {
296   SilcClientEntry client;
297
298   SILC_LOG_DEBUG(("Adding new client entry"));
299
300   client = silc_calloc(1, sizeof(*client));
301   client->nickname = nickname;
302   client->username = username;
303   client->userinfo = userinfo;
304   client->id = id;
305   client->router = router;
306   client->connection = connection;
307   silc_list_init(client->channels, struct SilcChannelClientEntryStruct, 
308                  client_list);
309
310   if (!silc_idcache_add(id_list->clients, nickname,  nickname_len,
311                         SILC_ID_CLIENT, (void *)client->id, 
312                         (void *)client, TRUE, FALSE)) {
313     silc_free(client);
314     return NULL;
315   }
316
317   return client;
318 }
319
320 /* Free client entry. This free's everything and removes the entry
321    from ID cache. Call silc_idlist_del_data before calling this one. */
322
323 int silc_idlist_del_client(SilcIDList id_list, SilcClientEntry entry)
324 {
325   if (entry) {
326     /* Remove from cache */
327     if (entry->id)
328       if (!silc_idcache_del_by_id(id_list->clients, SILC_ID_CLIENT, 
329                                   (void *)entry->id))
330         return FALSE;
331
332     /* Free data */
333     if (entry->nickname)
334       silc_free(entry->nickname);
335     if (entry->username)
336       silc_free(entry->username);
337     if (entry->userinfo)
338       silc_free(entry->userinfo);
339     if (entry->id)
340       silc_free(entry->id);
341
342     memset(entry, 'F', sizeof(*entry));
343     silc_free(entry);
344
345     return TRUE;
346   }
347
348   return FALSE;
349 }
350
351 /* Returns all clients matching requested nickname. Number of clients is
352    returned to `clients_count'. Caller must free the returned table. */
353
354 int silc_idlist_get_clients_by_nickname(SilcIDList id_list, char *nickname,
355                                         char *server, 
356                                         SilcClientEntry **clients,
357                                         uint32 *clients_count)
358 {
359   SilcIDCacheList list = NULL;
360   SilcIDCacheEntry id_cache = NULL;
361   int i;
362
363   SILC_LOG_DEBUG(("Start"));
364
365   if (!silc_idcache_find_by_data(id_list->clients, nickname, &list))
366     return FALSE;
367
368   *clients = silc_realloc(*clients, 
369                           (silc_idcache_list_count(list) + *clients_count) * 
370                           sizeof(**clients));
371
372   i = 0;
373   silc_idcache_list_first(list, &id_cache);
374   (*clients)[i++] = (SilcClientEntry)id_cache->context;
375
376   while (silc_idcache_list_next(list, &id_cache))
377     (*clients)[i++] = (SilcClientEntry)id_cache->context;
378   
379   silc_idcache_list_free(list);
380   
381   *clients_count += i;
382
383   return TRUE;
384 }
385
386 /* Returns all clients matching requested nickname. Number of clients is
387    returned to `clients_count'. Caller must free the returned table. */
388 /* XXX This actually checks the data, which can be hash of the nickname
389    but is not if the client is local client. Global client on global
390    list may have hash.  Thus, this is not fully reliable function.
391    Instead this should probably check the hash from the list of client ID's. */
392
393 int silc_idlist_get_clients_by_hash(SilcIDList id_list, char *nickname,
394                                     SilcHash md5hash,
395                                     SilcClientEntry **clients,
396                                     uint32 *clients_count)
397 {
398   SilcIDCacheList list = NULL;
399   SilcIDCacheEntry id_cache = NULL;
400   unsigned char hash[32];
401   int i;
402
403   SILC_LOG_DEBUG(("Start"));
404
405   silc_hash_make(md5hash, nickname, strlen(nickname), hash);
406
407   if (!silc_idcache_find_by_data(id_list->clients, hash, &list))
408     return FALSE;
409
410   *clients = silc_realloc(*clients, 
411                           (silc_idcache_list_count(list) + *clients_count) * 
412                           sizeof(**clients));
413
414   i = 0;
415   silc_idcache_list_first(list, &id_cache);
416   (*clients)[i++] = (SilcClientEntry)id_cache->context;
417
418   while (silc_idcache_list_next(list, &id_cache))
419     (*clients)[i++] = (SilcClientEntry)id_cache->context;
420   
421   silc_idcache_list_free(list);
422   
423   *clients_count += i;
424
425   return TRUE;
426 }
427
428 /* Finds client by nickname hash. */
429
430 SilcClientEntry
431 silc_idlist_find_client_by_hash(SilcIDList id_list, char *nickname,
432                                 SilcHash md5hash, SilcIDCacheEntry *ret_entry)
433 {
434   SilcIDCacheList list = NULL;
435   SilcIDCacheEntry id_cache = NULL;
436   SilcClientEntry client = NULL;
437   unsigned char hash[32];
438
439   SILC_LOG_DEBUG(("Client by hash"));
440
441   silc_hash_make(md5hash, nickname, strlen(nickname), hash);
442
443   if (!silc_idcache_find_by_id(id_list->clients, SILC_ID_CACHE_ANY, 
444                                SILC_ID_CLIENT, &list))
445     return NULL;
446
447   if (!silc_idcache_list_first(list, &id_cache)) {
448     silc_idcache_list_free(list);
449     return NULL;
450   }
451
452   while (id_cache) {
453     client = (SilcClientEntry)id_cache->context;
454     
455     if (client && !SILC_ID_COMPARE_HASH(client->id, hash))
456       break;
457
458     id_cache = NULL;
459     client = NULL;
460
461     if (!silc_idcache_list_next(list, &id_cache))
462       break;
463   }
464   
465   silc_idcache_list_free(list);
466
467   if (ret_entry)
468     *ret_entry = id_cache;
469
470   SILC_LOG_DEBUG(("Found"));
471
472   return client;
473 }
474
475 /* Finds client by Client ID */
476
477 SilcClientEntry
478 silc_idlist_find_client_by_id(SilcIDList id_list, SilcClientID *id,
479                               SilcIDCacheEntry *ret_entry)
480 {
481   SilcIDCacheEntry id_cache = NULL;
482   SilcClientEntry client;
483
484   if (!id)
485     return NULL;
486
487   SILC_LOG_DEBUG(("Client ID (%s)", 
488                   silc_id_render(id, SILC_ID_CLIENT)));
489
490   if (!silc_idcache_find_by_id_one(id_list->clients, (void *)id, 
491                                    SILC_ID_CLIENT, &id_cache))
492     return NULL;
493
494   client = (SilcClientEntry)id_cache->context;
495
496   if (ret_entry)
497     *ret_entry = id_cache;
498
499   SILC_LOG_DEBUG(("Found"));
500
501   return client;
502 }
503
504 /* Replaces old Client ID with new one */
505
506 SilcClientEntry
507 silc_idlist_replace_client_id(SilcIDList id_list, SilcClientID *old_id,
508                               SilcClientID *new_id)
509 {
510   SilcIDCacheEntry id_cache = NULL;
511   SilcClientEntry client;
512
513   if (!old_id || !new_id)
514     return NULL;
515
516   SILC_LOG_DEBUG(("Replacing Client ID"));
517
518   if (!silc_idcache_find_by_id_one(id_list->clients, (void *)old_id, 
519                                    SILC_ID_CLIENT, &id_cache))
520     return NULL;
521
522   client = (SilcClientEntry)id_cache->context;
523   silc_free(client->id);
524   client->id = new_id;
525   id_cache->id = (void *)new_id;
526
527   /* If the old ID Cache data was the hash value of the old Client ID
528      replace it with the hash of new Client ID */
529   if (id_cache->data && !SILC_ID_COMPARE_HASH(old_id, id_cache->data)) {
530     silc_free(id_cache->data);
531     id_cache->data = silc_calloc(sizeof(new_id->hash), sizeof(unsigned char));
532     memcpy(id_cache->data, new_id->hash, sizeof(new_id->hash));
533     silc_idcache_sort_by_data(id_list->clients);
534   }
535
536   SILC_LOG_DEBUG(("Replaced"));
537
538   return client;
539 }
540
541 /* Client cache entry destructor that is called when the cache is purged. */
542
543 void silc_idlist_client_destructor(SilcIDCache cache,
544                                    SilcIDCacheEntry entry)
545 {
546   SilcClientEntry client;
547
548   SILC_LOG_DEBUG(("Start"));
549
550   client = (SilcClientEntry)entry->context;
551   if (client) {
552     if (client->nickname)
553       silc_free(client->nickname);
554     if (client->username)
555       silc_free(client->username);
556     if (client->userinfo)
557       silc_free(client->userinfo);
558     if (client->id)
559       silc_free(client->id);
560
561     memset(client, 'F', sizeof(*client));
562     silc_free(client);
563   }
564 }
565
566 /******************************************************************************
567
568                           Channel entry functions
569
570 ******************************************************************************/
571
572 /* Add new channel entry. This add the new channel entry to the ID cache
573    system and returns the allocated entry or NULL on error. */
574
575 SilcChannelEntry
576 silc_idlist_add_channel(SilcIDList id_list, char *channel_name, int mode,
577                         SilcChannelID *id, SilcServerEntry router,
578                         SilcCipher channel_key, SilcHmac hmac)
579 {
580   SilcChannelEntry channel;
581
582   channel = silc_calloc(1, sizeof(*channel));
583   channel->channel_name = channel_name;
584   channel->mode = mode;
585   channel->id = id;
586   channel->router = router;
587   channel->channel_key = channel_key;
588   channel->hmac = hmac;
589   if (!channel->hmac)
590     if (!silc_hmac_alloc("hmac-sha1-96", NULL, &channel->hmac)) {
591       silc_free(channel);
592       return NULL;
593     }
594
595   silc_list_init(channel->user_list, struct SilcChannelClientEntryStruct, 
596                  channel_list);
597
598   if (!silc_idcache_add(id_list->channels, channel->channel_name, 
599                         channel->channel_name ? strlen(channel->channel_name) :
600                         0, SILC_ID_CHANNEL, 
601                         (void *)channel->id, (void *)channel, TRUE, FALSE)) {
602     silc_free(channel);
603     return NULL;
604   }
605
606   return channel;
607 }
608
609 /* Free channel entry.  This free's everything. */
610
611 int silc_idlist_del_channel(SilcIDList id_list, SilcChannelEntry entry)
612 {
613   if (entry) {
614     SilcChannelClientEntry chl;
615
616     /* Remove from cache */
617     if (entry->id)
618       if (!silc_idcache_del_by_id(id_list->channels, SILC_ID_CHANNEL, 
619                                   (void *)entry->id))
620         return FALSE;
621
622     /* Free data */
623     if (entry->channel_name)
624       silc_free(entry->channel_name);
625     if (entry->id)
626       silc_free(entry->id);
627     if (entry->topic)
628       silc_free(entry->topic);
629     if (entry->channel_key)
630       silc_cipher_free(entry->channel_key);
631     if (entry->key) {
632       memset(entry->key, 0, entry->key_len / 8);
633       silc_free(entry->key);
634     }
635     if (entry->cipher)
636       silc_free(entry->cipher);
637     if (entry->hmac_name)
638       silc_free(entry->hmac_name);
639     if (entry->rekey)
640       silc_free(entry->rekey);
641
642     /* Free all data, free also any reference from the client's channel
643        list since they share the same memory. */
644     silc_list_start(entry->user_list);
645     while ((chl = silc_list_get(entry->user_list)) != SILC_LIST_END) {
646       silc_list_del(chl->client->channels, chl);
647       silc_list_del(entry->user_list, chl);
648       silc_free(chl);
649     }
650
651     memset(entry, 'F', sizeof(*entry));
652     silc_free(entry);
653     return TRUE;
654   }
655
656   return FALSE;
657 }
658
659 /* Finds channel by channel name. Channel names are unique and they
660    are not case-sensitive. */
661
662 SilcChannelEntry
663 silc_idlist_find_channel_by_name(SilcIDList id_list, char *name,
664                                  SilcIDCacheEntry *ret_entry)
665 {
666   SilcIDCacheList list = NULL;
667   SilcIDCacheEntry id_cache = NULL;
668   SilcChannelEntry channel;
669
670   SILC_LOG_DEBUG(("Channel by name"));
671
672   if (!silc_idcache_find_by_data_loose(id_list->channels, name, &list))
673     return NULL;
674   
675   if (!silc_idcache_list_first(list, &id_cache)) {
676     silc_idcache_list_free(list);
677     return NULL;
678   }
679
680   channel = (SilcChannelEntry)id_cache->context;
681
682   if (ret_entry)
683     *ret_entry = id_cache;
684
685   silc_idcache_list_free(list);
686
687   SILC_LOG_DEBUG(("Found"));
688
689   return channel;
690 }
691
692 /* Finds channel by Channel ID. */
693
694 SilcChannelEntry
695 silc_idlist_find_channel_by_id(SilcIDList id_list, SilcChannelID *id,
696                                SilcIDCacheEntry *ret_entry)
697 {
698   SilcIDCacheEntry id_cache = NULL;
699   SilcChannelEntry channel;
700
701   if (!id)
702     return NULL;
703
704   SILC_LOG_DEBUG(("Channel ID (%s)",
705                   silc_id_render(id, SILC_ID_CHANNEL)));
706
707   if (!silc_idcache_find_by_id_one(id_list->channels, (void *)id, 
708                                    SILC_ID_CHANNEL, &id_cache))
709     return NULL;
710
711   channel = (SilcChannelEntry)id_cache->context;
712
713   if (ret_entry)
714     *ret_entry = id_cache;
715
716   SILC_LOG_DEBUG(("Found"));
717
718   return channel;
719 }
720
721 /* Replaces old Channel ID with new one. This is done when router forces
722    normal server to change Channel ID. */
723
724 SilcChannelEntry
725 silc_idlist_replace_channel_id(SilcIDList id_list, SilcChannelID *old_id,
726                                SilcChannelID *new_id)
727 {
728   SilcIDCacheEntry id_cache = NULL;
729   SilcChannelEntry channel;
730
731   if (!old_id || !new_id)
732     return NULL;
733
734   SILC_LOG_DEBUG(("Replacing Channel ID"));
735
736   if (!silc_idcache_find_by_id_one(id_list->channels, (void *)old_id, 
737                                    SILC_ID_CHANNEL, &id_cache))
738     return NULL;
739
740   channel = (SilcChannelEntry)id_cache->context;
741   silc_free(channel->id);
742   channel->id = new_id;
743   id_cache->id = (void *)new_id;
744
745   SILC_LOG_DEBUG(("Replaced"));
746
747   return channel;
748 }
749
750 /* Returns channels from the ID list. If the `channel_id' is NULL then
751    all channels are returned. */
752
753 SilcChannelEntry *
754 silc_idlist_get_channels(SilcIDList id_list, SilcChannelID *channel_id,
755                          uint32 *channels_count)
756 {
757   SilcIDCacheList list = NULL;
758   SilcIDCacheEntry id_cache = NULL;
759   SilcChannelEntry *channels;
760   int i;
761
762   SILC_LOG_DEBUG(("Start"));
763
764   if (!silc_idcache_find_by_id(id_list->channels, channel_id ? channel_id :
765                                SILC_ID_CACHE_ANY, SILC_ID_CHANNEL, &list))
766     return NULL;
767
768   channels = silc_calloc(silc_idcache_list_count(list), sizeof(*channels));
769
770   i = 0;
771   silc_idcache_list_first(list, &id_cache);
772   channels[i++] = (SilcChannelEntry)id_cache->context;
773
774   while (silc_idcache_list_next(list, &id_cache))
775     channels[i++] = (SilcChannelEntry)id_cache->context;
776   
777   silc_idcache_list_free(list);
778   
779   if (channels_count)
780     *channels_count = i;
781
782   return channels;
783 }