Home | History | Annotate | Line # | Download | only in common
      1 /*
      2  * Generic advertisement service (GAS) server
      3  * Copyright (c) 2017, Qualcomm Atheros, Inc.
      4  * Copyright (c) 2020, The Linux Foundation
      5  * Copyright (c) 2022, Qualcomm Innovation Center, Inc.
      6  *
      7  * This software may be distributed under the terms of the BSD license.
      8  * See README for more details.
      9  */
     10 
     11 #include "includes.h"
     12 
     13 #include "utils/common.h"
     14 #include "utils/list.h"
     15 #include "utils/eloop.h"
     16 #include "ieee802_11_defs.h"
     17 #include "gas.h"
     18 #include "gas_server.h"
     19 
     20 
     21 #define MAX_ADV_PROTO_ID_LEN 10
     22 #define GAS_QUERY_TIMEOUT 60
     23 
     24 struct gas_server_handler {
     25 	struct dl_list list;
     26 	u8 adv_proto_id[MAX_ADV_PROTO_ID_LEN];
     27 	u8 adv_proto_id_len;
     28 	struct wpabuf * (*req_cb)(void *ctx, void *resp_ctx, const u8 *sa,
     29 				  const u8 *query, size_t query_len,
     30 				  int *comeback_delay);
     31 	void (*status_cb)(void *ctx, struct wpabuf *resp, int ok);
     32 	void *ctx;
     33 	struct gas_server *gas;
     34 };
     35 
     36 struct gas_server_response {
     37 	struct dl_list list;
     38 	size_t offset;
     39 	u8 frag_id;
     40 	struct wpabuf *resp;
     41 	int freq;
     42 	u8 dst[ETH_ALEN];
     43 	u8 dialog_token;
     44 	struct gas_server_handler *handler;
     45 	u16 comeback_delay;
     46 	bool initial_resp_sent;
     47 };
     48 
     49 struct gas_server {
     50 	struct dl_list handlers; /* struct gas_server_handler::list */
     51 	struct dl_list responses; /* struct gas_server_response::list */
     52 	void (*tx)(void *ctx, int freq, const u8 *da, struct wpabuf *resp,
     53 		   unsigned int wait_time);
     54 	void *ctx;
     55 };
     56 
     57 static void gas_server_free_response(struct gas_server_response *response);
     58 
     59 
     60 static void gas_server_response_timeout(void *eloop_ctx, void *user_ctx)
     61 {
     62 	struct gas_server_response *response = eloop_ctx;
     63 
     64 	wpa_printf(MSG_DEBUG, "GAS: Response @%p timeout for " MACSTR
     65 		   " (dialog_token=%u freq=%d frag_id=%u sent=%lu/%lu) - drop pending data",
     66 		   response, MAC2STR(response->dst), response->dialog_token,
     67 		   response->freq, response->frag_id,
     68 		   (unsigned long) response->offset,
     69 		   (unsigned long) (response->resp ?
     70 				    wpabuf_len(response->resp) : 0));
     71 	response->handler->status_cb(response->handler->ctx,
     72 				     response->resp, 0);
     73 	response->resp = NULL;
     74 	dl_list_del(&response->list);
     75 	gas_server_free_response(response);
     76 }
     77 
     78 
     79 static void gas_server_free_response(struct gas_server_response *response)
     80 {
     81 	if (!response)
     82 		return;
     83 	wpa_printf(MSG_DEBUG, "DPP: Free GAS response @%p", response);
     84 	eloop_cancel_timeout(gas_server_response_timeout, response, NULL);
     85 	wpabuf_free(response->resp);
     86 	os_free(response);
     87 }
     88 
     89 
     90 static void
     91 gas_server_send_resp(struct gas_server *gas,
     92 		     struct gas_server_response *response,
     93 		     struct wpabuf *query_resp, u16 comeback_delay)
     94 {
     95 	struct gas_server_handler *handler = response->handler;
     96 	size_t max_len = (response->freq > 56160) ? 928 : 1400;
     97 	size_t hdr_len = 24 + 2 + 5 + 3 + handler->adv_proto_id_len + 2;
     98 	size_t resp_frag_len;
     99 	struct wpabuf *resp;
    100 
    101 	if (comeback_delay == 0 && !query_resp) {
    102 		dl_list_del(&response->list);
    103 		gas_server_free_response(response);
    104 		return;
    105 	}
    106 
    107 	if (comeback_delay) {
    108 		/* Need more time to prepare the response */
    109 		resp_frag_len = 0;
    110 		response->comeback_delay = comeback_delay;
    111 	} else if (hdr_len + wpabuf_len(query_resp) > max_len) {
    112 		/* Need to use comeback to initiate fragmentation */
    113 		comeback_delay = 1;
    114 		resp_frag_len = 0;
    115 	} else {
    116 		/* Full response fits into the initial response */
    117 		comeback_delay = 0;
    118 		resp_frag_len = wpabuf_len(query_resp);
    119 	}
    120 
    121 	resp = gas_build_initial_resp(response->dialog_token,
    122 				      WLAN_STATUS_SUCCESS,
    123 				      comeback_delay,
    124 				      handler->adv_proto_id_len +
    125 				      resp_frag_len);
    126 	if (!resp) {
    127 		wpabuf_free(query_resp);
    128 		dl_list_del(&response->list);
    129 		gas_server_free_response(response);
    130 		return;
    131 	}
    132 
    133 	/* Advertisement Protocol element */
    134 	wpabuf_put_u8(resp, WLAN_EID_ADV_PROTO);
    135 	wpabuf_put_u8(resp, 1 + handler->adv_proto_id_len); /* Length */
    136 	wpabuf_put_u8(resp, 0x7f);
    137 	/* Advertisement Protocol ID */
    138 	wpabuf_put_data(resp, handler->adv_proto_id, handler->adv_proto_id_len);
    139 
    140 	/* Query Response Length */
    141 	wpabuf_put_le16(resp, resp_frag_len);
    142 	if (!comeback_delay && query_resp)
    143 		wpabuf_put_buf(resp, query_resp);
    144 
    145 	if (comeback_delay && !query_resp) {
    146 		wpa_printf(MSG_DEBUG, "GAS: No response available yet");
    147 	} else if (comeback_delay) {
    148 		wpa_printf(MSG_DEBUG,
    149 			   "GAS: Need to fragment query response");
    150 	} else {
    151 		wpa_printf(MSG_DEBUG,
    152 			   "GAS: Full query response fits in the GAS Initial Response frame");
    153 	}
    154 	response->offset = resp_frag_len;
    155 	response->resp = query_resp;
    156 	response->initial_resp_sent = true;
    157 	gas->tx(gas->ctx, response->freq, response->dst, resp,
    158 		comeback_delay ? 2000 : 0);
    159 	wpabuf_free(resp);
    160 	eloop_register_timeout(GAS_QUERY_TIMEOUT, 0,
    161 			       gas_server_response_timeout, response, NULL);
    162 }
    163 
    164 
    165 static int
    166 gas_server_rx_initial_req(struct gas_server *gas, const u8 *da, const u8 *sa,
    167 			  const u8 *bssid, int freq, u8 dialog_token,
    168 			  const u8 *data, size_t len)
    169 {
    170 	const u8 *pos, *end, *adv_proto, *query_req;
    171 	u8 adv_proto_len;
    172 	u16 query_req_len;
    173 	struct gas_server_handler *handler;
    174 	struct wpabuf *resp;
    175 	struct gas_server_response *response;
    176 
    177 	wpa_hexdump(MSG_MSGDUMP, "GAS: Received GAS Initial Request frame",
    178 		    data, len);
    179 	pos = data;
    180 	end = data + len;
    181 
    182 	if (end - pos < 2 || pos[0] != WLAN_EID_ADV_PROTO) {
    183 		wpa_printf(MSG_DEBUG,
    184 			   "GAS: No Advertisement Protocol element found");
    185 		return -1;
    186 	}
    187 	pos++;
    188 	adv_proto_len = *pos++;
    189 	if (end - pos < adv_proto_len || adv_proto_len < 2) {
    190 		wpa_printf(MSG_DEBUG,
    191 			   "GAS: Truncated Advertisement Protocol element");
    192 		return -1;
    193 	}
    194 
    195 	adv_proto = pos;
    196 	pos += adv_proto_len;
    197 	wpa_hexdump(MSG_MSGDUMP, "GAS: Advertisement Protocol element",
    198 		    adv_proto, adv_proto_len);
    199 
    200 	if (end - pos < 2) {
    201 		wpa_printf(MSG_DEBUG, "GAS: No Query Request Length field");
    202 		return -1;
    203 	}
    204 	query_req_len = WPA_GET_LE16(pos);
    205 	pos += 2;
    206 	if (end - pos < query_req_len) {
    207 		wpa_printf(MSG_DEBUG, "GAS: Truncated Query Request field");
    208 		return -1;
    209 	}
    210 	query_req = pos;
    211 	pos += query_req_len;
    212 	wpa_hexdump(MSG_MSGDUMP, "GAS: Query Request",
    213 		    query_req, query_req_len);
    214 
    215 	if (pos < end) {
    216 		wpa_hexdump(MSG_MSGDUMP,
    217 			    "GAS: Ignored extra data after Query Request field",
    218 			    pos, end - pos);
    219 	}
    220 
    221 	response = os_zalloc(sizeof(*response));
    222 	if (!response)
    223 		return -1;
    224 
    225 	wpa_printf(MSG_DEBUG, "DPP: Allocated GAS response @%p", response);
    226 	dl_list_for_each(handler, &gas->handlers, struct gas_server_handler,
    227 			 list) {
    228 		int comeback_delay = 0;
    229 
    230 		if (adv_proto_len < 1 + handler->adv_proto_id_len ||
    231 		    os_memcmp(adv_proto + 1, handler->adv_proto_id,
    232 			      handler->adv_proto_id_len) != 0)
    233 			continue;
    234 
    235 		response->freq = freq;
    236 		response->handler = handler;
    237 		os_memcpy(response->dst, sa, ETH_ALEN);
    238 		response->dialog_token = dialog_token;
    239 		dl_list_add(&gas->responses, &response->list);
    240 
    241 		wpa_printf(MSG_DEBUG,
    242 			   "GAS: Calling handler for the requested Advertisement Protocol ID");
    243 		resp = handler->req_cb(handler->ctx, response, sa, query_req,
    244 				       query_req_len, &comeback_delay);
    245 		wpa_hexdump_buf(MSG_MSGDUMP, "GAS: Response from the handler",
    246 				resp);
    247 		if (comeback_delay < 0) {
    248 			wpa_printf(MSG_DEBUG,
    249 				   "GAS: Handler requested short delay before sending out the initial response");
    250 			return 0;
    251 		}
    252 		if (comeback_delay)
    253 			wpa_printf(MSG_DEBUG,
    254 				   "GAS: Handler requested comeback delay: %u TU",
    255 				   comeback_delay);
    256 		gas_server_send_resp(gas, response, resp, comeback_delay);
    257 		return 0;
    258 	}
    259 
    260 	wpa_printf(MSG_DEBUG,
    261 		   "GAS: No registered handler for the requested Advertisement Protocol ID");
    262 	gas_server_free_response(response);
    263 	return -1;
    264 }
    265 
    266 
    267 static void
    268 gas_server_handle_rx_comeback_req(struct gas_server_response *response)
    269 {
    270 	struct gas_server_handler *handler = response->handler;
    271 	struct gas_server *gas = handler->gas;
    272 	size_t max_len = (response->freq > 56160) ? 928 : 1400;
    273 	size_t hdr_len = 24 + 2 + 6 + 3 + handler->adv_proto_id_len + 2;
    274 	size_t remaining, resp_frag_len;
    275 	struct wpabuf *resp;
    276 	unsigned int wait_time = 0;
    277 
    278 	if (!response->resp) {
    279 		resp = gas_build_comeback_resp(response->dialog_token,
    280 					       WLAN_STATUS_SUCCESS, 0, 0,
    281 					       response->comeback_delay,
    282 					       handler->adv_proto_id_len);
    283 		if (!resp) {
    284 			dl_list_del(&response->list);
    285 			gas_server_free_response(response);
    286 			return;
    287 		}
    288 
    289 		/* Advertisement Protocol element */
    290 		wpabuf_put_u8(resp, WLAN_EID_ADV_PROTO);
    291 		wpabuf_put_u8(resp, 1 + handler->adv_proto_id_len); /* Length */
    292 		wpabuf_put_u8(resp, 0x7f);
    293 		/* Advertisement Protocol ID */
    294 		wpabuf_put_data(resp, handler->adv_proto_id,
    295 				handler->adv_proto_id_len);
    296 
    297 		/* Query Response Length */
    298 		wpabuf_put_le16(resp, 0);
    299 		goto send_resp;
    300 	}
    301 
    302 	remaining = wpabuf_len(response->resp) - response->offset;
    303 	if (hdr_len + remaining > max_len)
    304 		resp_frag_len = max_len - hdr_len;
    305 	else
    306 		resp_frag_len = remaining;
    307 	wpa_printf(MSG_DEBUG,
    308 		   "GAS: Sending out %u/%u remaining Query Response octets",
    309 		   (unsigned int) resp_frag_len, (unsigned int) remaining);
    310 
    311 	resp = gas_build_comeback_resp(response->dialog_token,
    312 				       WLAN_STATUS_SUCCESS,
    313 				       response->frag_id++,
    314 				       resp_frag_len < remaining, 0,
    315 				       handler->adv_proto_id_len +
    316 				       resp_frag_len);
    317 	if (!resp) {
    318 		dl_list_del(&response->list);
    319 		gas_server_free_response(response);
    320 		return;
    321 	}
    322 
    323 	/* Advertisement Protocol element */
    324 	wpabuf_put_u8(resp, WLAN_EID_ADV_PROTO);
    325 	wpabuf_put_u8(resp, 1 + handler->adv_proto_id_len); /* Length */
    326 	wpabuf_put_u8(resp, 0x7f);
    327 	/* Advertisement Protocol ID */
    328 	wpabuf_put_data(resp, handler->adv_proto_id, handler->adv_proto_id_len);
    329 
    330 	/* Query Response Length */
    331 	wpabuf_put_le16(resp, resp_frag_len);
    332 	wpabuf_put_data(resp, wpabuf_head_u8(response->resp) + response->offset,
    333 			resp_frag_len);
    334 
    335 	response->offset += resp_frag_len;
    336 
    337 	if (remaining > resp_frag_len)
    338 		wait_time = 2000;
    339 
    340 send_resp:
    341 	gas->tx(gas->ctx, response->freq, response->dst, resp, wait_time);
    342 	wpabuf_free(resp);
    343 }
    344 
    345 
    346 static int
    347 gas_server_rx_comeback_req(struct gas_server *gas, const u8 *da, const u8 *sa,
    348 			   const u8 *bssid, int freq, u8 dialog_token)
    349 {
    350 	struct gas_server_response *response;
    351 
    352 	dl_list_for_each(response, &gas->responses, struct gas_server_response,
    353 			 list) {
    354 		if (response->dialog_token != dialog_token ||
    355 		    !ether_addr_equal(sa, response->dst))
    356 			continue;
    357 		gas_server_handle_rx_comeback_req(response);
    358 		return 0;
    359 	}
    360 
    361 	wpa_printf(MSG_DEBUG, "GAS: No pending GAS response for " MACSTR
    362 		   " (dialog token %u)", MAC2STR(sa), dialog_token);
    363 	return -1;
    364 }
    365 
    366 
    367 /**
    368  * gas_query_rx - Indicate reception of a Public Action or Protected Dual frame
    369  * @gas: GAS query data from gas_server_init()
    370  * @da: Destination MAC address of the Action frame
    371  * @sa: Source MAC address of the Action frame
    372  * @bssid: BSSID of the Action frame
    373  * @categ: Category of the Action frame
    374  * @data: Payload of the Action frame
    375  * @len: Length of @data
    376  * @freq: Frequency (in MHz) on which the frame was received
    377  * Returns: 0 if the Public Action frame was a GAS request frame or -1 if not
    378  */
    379 int gas_server_rx(struct gas_server *gas, const u8 *da, const u8 *sa,
    380 		  const u8 *bssid, u8 categ, const u8 *data, size_t len,
    381 		  int freq)
    382 {
    383 	u8 action, dialog_token;
    384 	const u8 *pos, *end;
    385 
    386 	if (!gas || len < 2)
    387 		return -1;
    388 
    389 	if (categ == WLAN_ACTION_PROTECTED_DUAL)
    390 		return -1; /* Not supported for now */
    391 
    392 	pos = data;
    393 	end = data + len;
    394 	action = *pos++;
    395 	dialog_token = *pos++;
    396 
    397 	if (action != WLAN_PA_GAS_INITIAL_REQ &&
    398 	    action != WLAN_PA_GAS_COMEBACK_REQ)
    399 		return -1; /* Not a GAS request */
    400 
    401 	wpa_printf(MSG_DEBUG, "GAS: Received GAS %s Request frame DA=" MACSTR
    402 		   " SA=" MACSTR " BSSID=" MACSTR
    403 		   " freq=%d dialog_token=%u len=%u",
    404 		   action == WLAN_PA_GAS_INITIAL_REQ ? "Initial" : "Comeback",
    405 		   MAC2STR(da), MAC2STR(sa), MAC2STR(bssid), freq, dialog_token,
    406 		   (unsigned int) len);
    407 
    408 	if (action == WLAN_PA_GAS_INITIAL_REQ)
    409 		return gas_server_rx_initial_req(gas, da, sa, bssid,
    410 						 freq, dialog_token,
    411 						 pos, end - pos);
    412 	return gas_server_rx_comeback_req(gas, da, sa, bssid,
    413 					  freq, dialog_token);
    414 }
    415 
    416 
    417 static void gas_server_handle_tx_status(struct gas_server_response *response,
    418 					int ack)
    419 {
    420 	if (ack && response->resp &&
    421 	    response->offset < wpabuf_len(response->resp)) {
    422 		wpa_printf(MSG_DEBUG,
    423 			   "GAS: More fragments remaining - keep pending entry");
    424 		return;
    425 	}
    426 
    427 	if (ack && !response->resp && response->comeback_delay) {
    428 		wpa_printf(MSG_DEBUG,
    429 			   "GAS: Waiting for response - keep pending entry");
    430 		return;
    431 	}
    432 
    433 	if (!ack)
    434 		wpa_printf(MSG_DEBUG,
    435 			   "GAS: No ACK received - drop pending entry");
    436 	else
    437 		wpa_printf(MSG_DEBUG,
    438 			   "GAS: Last fragment of the response sent out - drop pending entry");
    439 
    440 	response->handler->status_cb(response->handler->ctx,
    441 				     response->resp, ack);
    442 	response->resp = NULL;
    443 	dl_list_del(&response->list);
    444 	gas_server_free_response(response);
    445 }
    446 
    447 
    448 void gas_server_tx_status(struct gas_server *gas, const u8 *dst, const u8 *data,
    449 			  size_t data_len, int ack)
    450 {
    451 	const u8 *pos;
    452 	u8 action, code, dialog_token;
    453 	struct gas_server_response *response;
    454 
    455 	if (data_len < 24 + 3)
    456 		return;
    457 	pos = data + 24;
    458 	action = *pos++;
    459 	code = *pos++;
    460 	dialog_token = *pos++;
    461 	if (action != WLAN_ACTION_PUBLIC ||
    462 	    (code != WLAN_PA_GAS_INITIAL_RESP &&
    463 	     code != WLAN_PA_GAS_COMEBACK_RESP))
    464 		return;
    465 	wpa_printf(MSG_DEBUG, "GAS: TX status dst=" MACSTR
    466 		   " ack=%d %s dialog_token=%u",
    467 		   MAC2STR(dst), ack,
    468 		   code == WLAN_PA_GAS_INITIAL_RESP ? "initial" : "comeback",
    469 		   dialog_token);
    470 	dl_list_for_each(response, &gas->responses, struct gas_server_response,
    471 			 list) {
    472 		if (response->dialog_token != dialog_token ||
    473 		    !ether_addr_equal(dst, response->dst))
    474 			continue;
    475 		gas_server_handle_tx_status(response, ack);
    476 		return;
    477 	}
    478 
    479 	wpa_printf(MSG_DEBUG, "GAS: No pending response matches TX status");
    480 }
    481 
    482 
    483 int gas_server_set_resp(struct gas_server *gas, void *resp_ctx,
    484 			struct wpabuf *resp)
    485 {
    486 	struct gas_server_response *tmp, *response = NULL;
    487 
    488 	dl_list_for_each(tmp, &gas->responses, struct gas_server_response,
    489 			 list) {
    490 		if (tmp == resp_ctx) {
    491 			response = tmp;
    492 			break;
    493 		}
    494 	}
    495 
    496 	if (!response || response->resp)
    497 		return -1;
    498 
    499 	if (!response->initial_resp_sent) {
    500 		wpa_printf(MSG_DEBUG, "GAS: Send the delayed initial response");
    501 		gas_server_send_resp(gas, response, resp, 0);
    502 		return 0;
    503 	}
    504 
    505 	response->resp = resp;
    506 	return 0;
    507 }
    508 
    509 
    510 int gas_server_set_comeback_delay(struct gas_server *gas, void *resp_ctx,
    511 				  u16 comeback_delay)
    512 {
    513 	struct gas_server_response *tmp, *response = NULL;
    514 
    515 	dl_list_for_each(tmp, &gas->responses, struct gas_server_response,
    516 			 list) {
    517 		if (tmp == resp_ctx) {
    518 			response = tmp;
    519 			break;
    520 		}
    521 	}
    522 
    523 	if (!response || response->initial_resp_sent)
    524 		return -1;
    525 
    526 	wpa_printf(MSG_DEBUG,
    527 		   "GAS: Send the delayed initial response with comeback delay %u",
    528 		   comeback_delay);
    529 	gas_server_send_resp(gas, response, NULL, comeback_delay);
    530 
    531 	return 0;
    532 }
    533 
    534 
    535 bool gas_server_response_sent(struct gas_server *gas, void *resp_ctx)
    536 {
    537 	struct gas_server_response *tmp;
    538 
    539 	dl_list_for_each(tmp, &gas->responses, struct gas_server_response,
    540 			 list) {
    541 		if (tmp == resp_ctx)
    542 			return tmp->resp &&
    543 				tmp->offset == wpabuf_len(tmp->resp);
    544 	}
    545 
    546 	return false;
    547 }
    548 
    549 
    550 struct gas_server * gas_server_init(void *ctx,
    551 				    void (*tx)(void *ctx, int freq,
    552 					       const u8 *da,
    553 					       struct wpabuf *buf,
    554 					       unsigned int wait_time))
    555 {
    556 	struct gas_server *gas;
    557 
    558 	gas = os_zalloc(sizeof(*gas));
    559 	if (!gas)
    560 		return NULL;
    561 	gas->ctx = ctx;
    562 	gas->tx = tx;
    563 	dl_list_init(&gas->handlers);
    564 	dl_list_init(&gas->responses);
    565 	return gas;
    566 }
    567 
    568 
    569 void gas_server_deinit(struct gas_server *gas)
    570 {
    571 	struct gas_server_handler *handler, *tmp;
    572 	struct gas_server_response *response, *tmp_r;
    573 
    574 	if (!gas)
    575 		return;
    576 
    577 	dl_list_for_each_safe(handler, tmp, &gas->handlers,
    578 			      struct gas_server_handler, list) {
    579 		dl_list_del(&handler->list);
    580 		os_free(handler);
    581 	}
    582 
    583 	dl_list_for_each_safe(response, tmp_r, &gas->responses,
    584 			      struct gas_server_response, list) {
    585 		dl_list_del(&response->list);
    586 		gas_server_free_response(response);
    587 	}
    588 
    589 	os_free(gas);
    590 }
    591 
    592 
    593 int gas_server_register(struct gas_server *gas,
    594 			const u8 *adv_proto_id, u8 adv_proto_id_len,
    595 			struct wpabuf *
    596 			(*req_cb)(void *ctx, void *resp_ctx, const u8 *sa,
    597 				  const u8 *query, size_t query_len,
    598 				  int *comeback_delay),
    599 			void (*status_cb)(void *ctx, struct wpabuf *resp,
    600 					  int ok),
    601 			void *ctx)
    602 {
    603 	struct gas_server_handler *handler;
    604 
    605 	if (!gas || adv_proto_id_len > MAX_ADV_PROTO_ID_LEN)
    606 		return -1;
    607 	handler = os_zalloc(sizeof(*handler));
    608 	if (!handler)
    609 		return -1;
    610 
    611 	os_memcpy(handler->adv_proto_id, adv_proto_id, adv_proto_id_len);
    612 	handler->adv_proto_id_len = adv_proto_id_len;
    613 	handler->req_cb = req_cb;
    614 	handler->status_cb = status_cb;
    615 	handler->ctx = ctx;
    616 	handler->gas = gas;
    617 	dl_list_add(&gas->handlers, &handler->list);
    618 
    619 	return 0;
    620 }
    621