hostapd: Update vendor branch to 0.6.10
[dragonfly.git] / contrib / hostapd / src / drivers / driver_broadcom.c
1 /*
2  * WPA Supplicant - driver interaction with old Broadcom wl.o driver
3  * Copyright (c) 2004, Nikki Chumkov <nikki@gattaca.ru>
4  * Copyright (c) 2004, Jouni Malinen <j@w1.fi>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License version 2 as
8  * published by the Free Software Foundation.
9  *
10  * Alternatively, this software may be distributed under the terms of BSD
11  * license.
12  *
13  * See README and COPYING for more details.
14  *
15  * Please note that the newer Broadcom driver ("hybrid Linux driver") supports
16  * Linux wireless extensions and does not need (or even work) with this old
17  * driver wrapper. Use driver_wext.c with that driver.
18  */
19
20 #include "includes.h"
21
22 #include <sys/ioctl.h>
23
24 #include "common.h"
25
26 #if 0
27 #include <netpacket/packet.h>
28 #include <net/ethernet.h>     /* the L2 protocols */
29 #else
30 #include <linux/if_packet.h>
31 #include <linux/if_ether.h>   /* The L2 protocols */
32 #endif
33 #include <net/if.h>
34 #include <typedefs.h>
35
36 /* wlioctl.h is a Broadcom header file and it is available, e.g., from Linksys
37  * WRT54G GPL tarball. */
38 #include <wlioctl.h>
39
40 #include "driver.h"
41 #include "eloop.h"
42
43 struct wpa_driver_broadcom_data {
44         void *ctx;
45         int ioctl_sock;
46         int event_sock;
47         char ifname[IFNAMSIZ + 1];
48 };
49
50
51 #ifndef WLC_DEAUTHENTICATE
52 #define WLC_DEAUTHENTICATE 143
53 #endif
54 #ifndef WLC_DEAUTHENTICATE_WITH_REASON
55 #define WLC_DEAUTHENTICATE_WITH_REASON 201
56 #endif
57 #ifndef WLC_SET_TKIP_COUNTERMEASURES
58 #define WLC_SET_TKIP_COUNTERMEASURES 202
59 #endif
60
61 #if !defined(PSK_ENABLED) /* NEW driver interface */
62 #define WL_VERSION 360130
63 /* wireless authentication bit vector */
64 #define WPA_ENABLED 1
65 #define PSK_ENABLED 2
66                                                                                 
67 #define WAUTH_WPA_ENABLED(wauth)  ((wauth) & WPA_ENABLED)
68 #define WAUTH_PSK_ENABLED(wauth)  ((wauth) & PSK_ENABLED)
69 #define WAUTH_ENABLED(wauth)    ((wauth) & (WPA_ENABLED | PSK_ENABLED))
70
71 #define WSEC_PRIMARY_KEY WL_PRIMARY_KEY
72
73 typedef wl_wsec_key_t wsec_key_t;
74 #endif
75
76 typedef struct {
77         uint32 val;
78         struct ether_addr ea;
79         uint16 res;
80 } wlc_deauth_t;
81
82
83 static void wpa_driver_broadcom_scan_timeout(void *eloop_ctx,
84                                              void *timeout_ctx);
85
86 static int broadcom_ioctl(struct wpa_driver_broadcom_data *drv, int cmd,
87                           void *buf, int len)
88 {
89         struct ifreq ifr;
90         wl_ioctl_t ioc;
91         int ret = 0;
92
93         wpa_printf(MSG_MSGDUMP, "BROADCOM: wlioctl(%s,%d,len=%d,val=%p)",
94                    drv->ifname, cmd, len, buf);
95         /* wpa_hexdump(MSG_MSGDUMP, "BROADCOM: wlioctl buf", buf, len); */
96
97         ioc.cmd = cmd;
98         ioc.buf = buf;
99         ioc.len = len;
100         os_strlcpy(ifr.ifr_name, drv->ifname, IFNAMSIZ);
101         ifr.ifr_data = (caddr_t) &ioc;
102         if ((ret = ioctl(drv->ioctl_sock, SIOCDEVPRIVATE, &ifr)) < 0) {
103                 if (cmd != WLC_GET_MAGIC)
104                         perror(ifr.ifr_name);
105                 wpa_printf(MSG_MSGDUMP, "BROADCOM: wlioctl cmd=%d res=%d",
106                            cmd, ret);
107         }
108
109         return ret;
110 }
111
112 static int wpa_driver_broadcom_get_bssid(void *priv, u8 *bssid)
113 {
114         struct wpa_driver_broadcom_data *drv = priv;
115         if (broadcom_ioctl(drv, WLC_GET_BSSID, bssid, ETH_ALEN) == 0)
116                 return 0;
117         
118         os_memset(bssid, 0, ETH_ALEN);
119         return -1;
120 }
121
122 static int wpa_driver_broadcom_get_ssid(void *priv, u8 *ssid)
123 {
124         struct wpa_driver_broadcom_data *drv = priv;
125         wlc_ssid_t s;
126         
127         if (broadcom_ioctl(drv, WLC_GET_SSID, &s, sizeof(s)) == -1)
128                 return -1;
129
130         os_memcpy(ssid, s.SSID, s.SSID_len);
131         return s.SSID_len;
132 }
133
134 static int wpa_driver_broadcom_set_wpa(void *priv, int enable)
135 {
136         struct wpa_driver_broadcom_data *drv = priv;
137         unsigned int wauth, wsec;
138         struct ether_addr ea;
139
140         os_memset(&ea, enable ? 0xff : 0, sizeof(ea));
141         if (broadcom_ioctl(drv, WLC_GET_WPA_AUTH, &wauth, sizeof(wauth)) ==
142             -1 ||
143             broadcom_ioctl(drv, WLC_GET_WSEC, &wsec, sizeof(wsec)) == -1)
144                 return -1;
145
146         if (enable) {
147                 wauth = PSK_ENABLED;
148                 wsec = TKIP_ENABLED;
149         } else {
150                 wauth = 255;
151                 wsec &= ~(TKIP_ENABLED | AES_ENABLED);
152         }
153
154         if (broadcom_ioctl(drv, WLC_SET_WPA_AUTH, &wauth, sizeof(wauth)) ==
155             -1 ||
156             broadcom_ioctl(drv, WLC_SET_WSEC, &wsec, sizeof(wsec)) == -1)
157                 return -1;
158
159         /* FIX: magic number / error handling? */
160         broadcom_ioctl(drv, 122, &ea, sizeof(ea));
161
162         return 0;
163 }
164
165 static int wpa_driver_broadcom_set_key(void *priv, wpa_alg alg,
166                                        const u8 *addr, int key_idx, int set_tx,
167                                        const u8 *seq, size_t seq_len,
168                                        const u8 *key, size_t key_len)
169 {
170         struct wpa_driver_broadcom_data *drv = priv;
171         int ret;
172         wsec_key_t wkt;
173
174         os_memset(&wkt, 0, sizeof wkt);
175         wpa_printf(MSG_MSGDUMP, "BROADCOM: SET %sKEY[%d] alg=%d",
176                    set_tx ? "PRIMARY " : "", key_idx, alg);
177         if (key && key_len > 0)
178                 wpa_hexdump_key(MSG_MSGDUMP, "BROADCOM: key", key, key_len);
179
180         switch (alg) {
181         case WPA_ALG_NONE:
182                 wkt.algo = CRYPTO_ALGO_OFF;
183                 break;
184         case WPA_ALG_WEP:
185                 wkt.algo = CRYPTO_ALGO_WEP128; /* CRYPTO_ALGO_WEP1? */
186                 break;
187         case WPA_ALG_TKIP:
188                 wkt.algo = 0; /* CRYPTO_ALGO_TKIP? */
189                 break;
190         case WPA_ALG_CCMP:
191                 wkt.algo = 0; /* CRYPTO_ALGO_AES_CCM;
192                                * AES_OCB_MSDU, AES_OCB_MPDU? */
193                 break;
194         default:
195                 wkt.algo = CRYPTO_ALGO_NALG;
196                 break;
197         }
198
199         if (seq && seq_len > 0)
200                 wpa_hexdump(MSG_MSGDUMP, "BROADCOM: SEQ", seq, seq_len);
201
202         if (addr)
203                 wpa_hexdump(MSG_MSGDUMP, "BROADCOM: addr", addr, ETH_ALEN);
204
205         wkt.index = key_idx;
206         wkt.len = key_len;
207         if (key && key_len > 0) {
208                 os_memcpy(wkt.data, key, key_len);
209                 if (key_len == 32) {
210                         /* hack hack hack XXX */
211                         os_memcpy(&wkt.data[16], &key[24], 8);
212                         os_memcpy(&wkt.data[24], &key[16], 8);
213                 }
214         }
215         /* wkt.algo = CRYPTO_ALGO_...; */
216         wkt.flags = set_tx ? 0 : WSEC_PRIMARY_KEY;
217         if (addr && set_tx)
218                 os_memcpy(&wkt.ea, addr, sizeof(wkt.ea));
219         ret = broadcom_ioctl(drv, WLC_SET_KEY, &wkt, sizeof(wkt));
220         if (addr && set_tx) {
221                 /* FIX: magic number / error handling? */
222                 broadcom_ioctl(drv, 121, &wkt.ea, sizeof(wkt.ea));
223         }
224         return ret;
225 }
226
227
228 static void wpa_driver_broadcom_event_receive(int sock, void *ctx,
229                                               void *sock_ctx)
230 {
231         char buf[8192];
232         int left;
233         wl_wpa_header_t *wwh;
234         union wpa_event_data data;
235         
236         if ((left = recv(sock, buf, sizeof buf, 0)) < 0)
237                 return;
238
239         wpa_hexdump(MSG_DEBUG, "RECEIVE EVENT", (u8 *) buf, left);
240
241         if ((size_t) left < sizeof(wl_wpa_header_t))
242                 return;
243
244         wwh = (wl_wpa_header_t *) buf;
245
246         if (wwh->snap.type != WL_WPA_ETHER_TYPE)
247                 return;
248         if (os_memcmp(&wwh->snap, wl_wpa_snap_template, 6) != 0)
249                 return;
250
251         os_memset(&data, 0, sizeof(data));
252
253         switch (wwh->type) {
254         case WLC_ASSOC_MSG:
255                 left -= WL_WPA_HEADER_LEN;
256                 wpa_printf(MSG_DEBUG, "BROADCOM: ASSOC MESSAGE (left: %d)",
257                            left);
258                 if (left > 0) {
259                         data.assoc_info.resp_ies = os_malloc(left);
260                         if (data.assoc_info.resp_ies == NULL)
261                                 return;
262                         os_memcpy(data.assoc_info.resp_ies,
263                                   buf + WL_WPA_HEADER_LEN, left);
264                         data.assoc_info.resp_ies_len = left;
265                         wpa_hexdump(MSG_MSGDUMP, "BROADCOM: copying %d bytes "
266                                     "into resp_ies",
267                                     data.assoc_info.resp_ies, left);
268                 }
269                 /* data.assoc_info.req_ies = NULL; */
270                 /* data.assoc_info.req_ies_len = 0; */
271
272                 wpa_supplicant_event(ctx, EVENT_ASSOCINFO, &data);
273                 wpa_supplicant_event(ctx, EVENT_ASSOC, NULL);
274                 break;
275         case WLC_DISASSOC_MSG:
276                 wpa_printf(MSG_DEBUG, "BROADCOM: DISASSOC MESSAGE");
277                 wpa_supplicant_event(ctx, EVENT_DISASSOC, NULL);
278                 break;
279         case WLC_PTK_MIC_MSG:
280                 wpa_printf(MSG_DEBUG, "BROADCOM: PTK MIC MSG MESSAGE");
281                 data.michael_mic_failure.unicast = 1;
282                 wpa_supplicant_event(ctx, EVENT_MICHAEL_MIC_FAILURE, &data);
283                 break;
284         case WLC_GTK_MIC_MSG:
285                 wpa_printf(MSG_DEBUG, "BROADCOM: GTK MIC MSG MESSAGE");
286                 data.michael_mic_failure.unicast = 0;
287                 wpa_supplicant_event(ctx, EVENT_MICHAEL_MIC_FAILURE, &data);
288                 break;
289         default:
290                 wpa_printf(MSG_DEBUG, "BROADCOM: UNKNOWN MESSAGE (%d)",
291                            wwh->type);
292                 break;
293         }
294         os_free(data.assoc_info.resp_ies);
295 }       
296
297 static void * wpa_driver_broadcom_init(void *ctx, const char *ifname)
298 {
299         int s;
300         struct sockaddr_ll ll;
301         struct wpa_driver_broadcom_data *drv;
302         struct ifreq ifr;
303
304         /* open socket to kernel */
305         if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
306                 perror("socket");
307                 return NULL;
308         }
309         /* do it */
310         os_strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);
311         if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) {
312                 perror(ifr.ifr_name);
313                 return NULL;
314         }
315
316
317         drv = os_zalloc(sizeof(*drv));
318         if (drv == NULL)
319                 return NULL;
320         drv->ctx = ctx;
321         os_strlcpy(drv->ifname, ifname, sizeof(drv->ifname));
322         drv->ioctl_sock = s;
323
324         s = socket(PF_PACKET, SOCK_RAW, ntohs(ETH_P_802_2));
325         if (s < 0) {
326                 perror("socket(PF_PACKET, SOCK_RAW, ntohs(ETH_P_802_2))");
327                 close(drv->ioctl_sock);
328                 os_free(drv);
329                 return NULL;
330         }
331
332         os_memset(&ll, 0, sizeof(ll));
333         ll.sll_family = AF_PACKET;
334         ll.sll_protocol = ntohs(ETH_P_802_2);
335         ll.sll_ifindex = ifr.ifr_ifindex;
336         ll.sll_hatype = 0;
337         ll.sll_pkttype = PACKET_HOST;
338         ll.sll_halen = 0;
339
340         if (bind(s, (struct sockaddr *) &ll, sizeof(ll)) < 0) {
341                 perror("bind(netlink)");
342                 close(s);
343                 close(drv->ioctl_sock);
344                 os_free(drv);
345                 return NULL;
346         }
347
348         eloop_register_read_sock(s, wpa_driver_broadcom_event_receive, ctx,
349                                  NULL);
350         drv->event_sock = s;
351
352         return drv;
353 }
354
355 static void wpa_driver_broadcom_deinit(void *priv)
356 {
357         struct wpa_driver_broadcom_data *drv = priv;
358         eloop_cancel_timeout(wpa_driver_broadcom_scan_timeout, drv, drv->ctx);
359         eloop_unregister_read_sock(drv->event_sock);
360         close(drv->event_sock);
361         close(drv->ioctl_sock);
362         os_free(drv);
363 }
364
365 static int wpa_driver_broadcom_set_countermeasures(void *priv,
366                                                    int enabled)
367 {
368 #if 0
369         struct wpa_driver_broadcom_data *drv = priv;
370         /* FIX: ? */
371         return broadcom_ioctl(drv, WLC_SET_TKIP_COUNTERMEASURES, &enabled,
372                               sizeof(enabled));
373 #else
374         return 0;
375 #endif
376 }
377
378 static int wpa_driver_broadcom_set_drop_unencrypted(void *priv, int enabled)
379 {
380         struct wpa_driver_broadcom_data *drv = priv;
381         /* SET_EAP_RESTRICT, SET_WEP_RESTRICT */
382         int restrict = (enabled ? 1 : 0);
383         
384         if (broadcom_ioctl(drv, WLC_SET_WEP_RESTRICT, 
385                            &restrict, sizeof(restrict)) < 0 ||
386             broadcom_ioctl(drv, WLC_SET_EAP_RESTRICT,
387                            &restrict, sizeof(restrict)) < 0)
388                 return -1;
389
390         return 0;
391 }
392
393 static void wpa_driver_broadcom_scan_timeout(void *eloop_ctx,
394                                              void *timeout_ctx)
395 {
396         wpa_printf(MSG_DEBUG, "Scan timeout - try to get results");
397         wpa_supplicant_event(timeout_ctx, EVENT_SCAN_RESULTS, NULL);
398 }
399
400 static int wpa_driver_broadcom_scan(void *priv, const u8 *ssid,
401                                     size_t ssid_len)
402 {
403         struct wpa_driver_broadcom_data *drv = priv;
404         wlc_ssid_t wst = { 0, "" };
405
406         if (ssid && ssid_len > 0 && ssid_len <= sizeof(wst.SSID)) {
407                 wst.SSID_len = ssid_len;
408                 os_memcpy(wst.SSID, ssid, ssid_len);
409         }
410         
411         if (broadcom_ioctl(drv, WLC_SCAN, &wst, sizeof(wst)) < 0)
412                 return -1;
413
414         eloop_cancel_timeout(wpa_driver_broadcom_scan_timeout, drv, drv->ctx);
415         eloop_register_timeout(3, 0, wpa_driver_broadcom_scan_timeout, drv,
416                                drv->ctx);
417         return 0;
418 }
419
420
421 static const int frequency_list[] = { 
422         2412, 2417, 2422, 2427, 2432, 2437, 2442,
423         2447, 2452, 2457, 2462, 2467, 2472, 2484 
424 };
425
426 struct bss_ie_hdr {
427         u8 elem_id;
428         u8 len;
429         u8 oui[3];
430         /* u8 oui_type; */
431         /* u16 version; */
432 } __attribute__ ((packed));
433
434 static int
435 wpa_driver_broadcom_get_scan_results(void *priv,
436                                      struct wpa_scan_result *results,
437                                      size_t max_size)
438 {
439         struct wpa_driver_broadcom_data *drv = priv;
440         char *buf;
441         wl_scan_results_t *wsr;
442         wl_bss_info_t *wbi;
443         size_t ap_num;
444
445         buf = os_malloc(WLC_IOCTL_MAXLEN);
446         if (buf == NULL)
447                 return -1;
448
449         wsr = (wl_scan_results_t *) buf;
450
451         wsr->buflen = WLC_IOCTL_MAXLEN - sizeof(wsr);
452         wsr->version = 107;
453         wsr->count = 0;
454
455         if (broadcom_ioctl(drv, WLC_SCAN_RESULTS, buf, WLC_IOCTL_MAXLEN) < 0) {
456                 os_free(buf);
457                 return -1;
458         }
459
460         os_memset(results, 0, max_size * sizeof(struct wpa_scan_result));
461
462         for (ap_num = 0, wbi = wsr->bss_info; ap_num < wsr->count; ++ap_num) {
463                 int left;
464                 struct bss_ie_hdr *ie;
465                 
466                 os_memcpy(results[ap_num].bssid, &wbi->BSSID, ETH_ALEN);
467                 os_memcpy(results[ap_num].ssid, wbi->SSID, wbi->SSID_len);
468                 results[ap_num].ssid_len = wbi->SSID_len;
469                 results[ap_num].freq = frequency_list[wbi->channel - 1];
470                 /* get ie's */
471                 wpa_hexdump(MSG_MSGDUMP, "BROADCOM: AP IEs",
472                             (u8 *) wbi + sizeof(*wbi), wbi->ie_length);
473                 ie = (struct bss_ie_hdr *) ((u8 *) wbi + sizeof(*wbi));
474                 for (left = wbi->ie_length; left > 0;
475                      left -= (ie->len + 2), ie = (struct bss_ie_hdr *)
476                              ((u8 *) ie + 2 + ie->len)) {
477                         wpa_printf(MSG_MSGDUMP, "BROADCOM: IE: id:%x, len:%d",
478                                    ie->elem_id, ie->len);
479                         if (ie->len >= 3) 
480                                 wpa_printf(MSG_MSGDUMP,
481                                            "BROADCOM: oui:%02x%02x%02x",
482                                            ie->oui[0], ie->oui[1], ie->oui[2]);
483                         if (ie->elem_id != 0xdd ||
484                             ie->len < 6 ||
485                             os_memcmp(ie->oui, WPA_OUI, 3) != 0)
486                                 continue;
487                         os_memcpy(results[ap_num].wpa_ie, ie, ie->len + 2);
488                         results[ap_num].wpa_ie_len = ie->len + 2;
489                         break;
490                 }
491
492                 wbi = (wl_bss_info_t *) ((u8 *) wbi + wbi->length);
493         }
494
495         wpa_printf(MSG_MSGDUMP, "Received %d bytes of scan results (%lu "
496                    "BSSes)",
497                    wsr->buflen, (unsigned long) ap_num);
498         
499         os_free(buf);
500         return ap_num;
501 }
502
503 static int wpa_driver_broadcom_deauthenticate(void *priv, const u8 *addr,
504                                               int reason_code)
505 {
506         struct wpa_driver_broadcom_data *drv = priv;
507         wlc_deauth_t wdt;
508         wdt.val = reason_code;
509         os_memcpy(&wdt.ea, addr, sizeof wdt.ea);
510         wdt.res = 0x7fff;
511         return broadcom_ioctl(drv, WLC_DEAUTHENTICATE_WITH_REASON, &wdt,
512                               sizeof(wdt));
513 }
514
515 static int wpa_driver_broadcom_disassociate(void *priv, const u8 *addr,
516                                             int reason_code)
517 {
518         struct wpa_driver_broadcom_data *drv = priv;
519         return broadcom_ioctl(drv, WLC_DISASSOC, 0, 0);
520 }
521
522 static int
523 wpa_driver_broadcom_associate(void *priv,
524                               struct wpa_driver_associate_params *params)
525 {
526         struct wpa_driver_broadcom_data *drv = priv;
527         wlc_ssid_t s;
528         int infra = 1;
529         int auth = 0;
530         int wsec = 4;
531         int dummy;
532         int wpa_auth;
533         
534         s.SSID_len = params->ssid_len;
535         os_memcpy(s.SSID, params->ssid, params->ssid_len);
536
537         switch (params->pairwise_suite) {
538         case CIPHER_WEP40:
539         case CIPHER_WEP104:
540                 wsec = 1;
541                 break;
542
543         case CIPHER_TKIP:
544                 wsec = 2;
545                 break;
546
547         case CIPHER_CCMP:
548                 wsec = 4;
549                 break;
550
551         default:
552                 wsec = 0;
553                 break;
554         }
555
556         switch (params->key_mgmt_suite) {
557         case KEY_MGMT_802_1X:
558                 wpa_auth = 1;
559                 break;
560
561         case KEY_MGMT_PSK:
562                 wpa_auth = 2;
563                 break;
564
565         default:
566                 wpa_auth = 255;
567                 break;
568         }
569
570         /* printf("broadcom_associate: %u %u %u\n", pairwise_suite,
571          * group_suite, key_mgmt_suite);
572          * broadcom_ioctl(ifname, WLC_GET_WSEC, &wsec, sizeof(wsec));
573          * wl join uses wlc_sec_wep here, not wlc_set_wsec */
574
575         if (broadcom_ioctl(drv, WLC_SET_WSEC, &wsec, sizeof(wsec)) < 0 ||
576             broadcom_ioctl(drv, WLC_SET_WPA_AUTH, &wpa_auth,
577                            sizeof(wpa_auth)) < 0 ||
578             broadcom_ioctl(drv, WLC_GET_WEP, &dummy, sizeof(dummy)) < 0 ||
579             broadcom_ioctl(drv, WLC_SET_INFRA, &infra, sizeof(infra)) < 0 ||
580             broadcom_ioctl(drv, WLC_SET_AUTH, &auth, sizeof(auth)) < 0 ||
581             broadcom_ioctl(drv, WLC_SET_WEP, &wsec, sizeof(wsec)) < 0 ||
582             broadcom_ioctl(drv, WLC_SET_SSID, &s, sizeof(s)) < 0)
583                 return -1;
584
585         return 0;
586 }
587
588 const struct wpa_driver_ops wpa_driver_broadcom_ops = {
589         .name = "broadcom",
590         .desc = "Broadcom wl.o driver",
591         .get_bssid = wpa_driver_broadcom_get_bssid,
592         .get_ssid = wpa_driver_broadcom_get_ssid,
593         .set_wpa = wpa_driver_broadcom_set_wpa,
594         .set_key = wpa_driver_broadcom_set_key,
595         .init = wpa_driver_broadcom_init,
596         .deinit = wpa_driver_broadcom_deinit,
597         .set_countermeasures = wpa_driver_broadcom_set_countermeasures,
598         .set_drop_unencrypted = wpa_driver_broadcom_set_drop_unencrypted,
599         .scan = wpa_driver_broadcom_scan,
600         .get_scan_results = wpa_driver_broadcom_get_scan_results,
601         .deauthenticate = wpa_driver_broadcom_deauthenticate,
602         .disassociate = wpa_driver_broadcom_disassociate,
603         .associate = wpa_driver_broadcom_associate,
604 };