gas_serv.c revision 04949598a23f501be6eec21697465fd46a28840a
1/* 2 * Generic advertisement service (GAS) server 3 * Copyright (c) 2011-2012, Qualcomm Atheros, Inc. 4 * 5 * This software may be distributed under the terms of the BSD license. 6 * See README for more details. 7 */ 8 9#include "includes.h" 10 11#include "common.h" 12#include "common/ieee802_11_defs.h" 13#include "common/gas.h" 14#include "utils/eloop.h" 15#include "hostapd.h" 16#include "ap_config.h" 17#include "ap_drv_ops.h" 18#include "sta_info.h" 19#include "gas_serv.h" 20 21 22static struct gas_dialog_info * 23gas_dialog_create(struct hostapd_data *hapd, const u8 *addr, u8 dialog_token) 24{ 25 struct sta_info *sta; 26 struct gas_dialog_info *dia = NULL; 27 int i, j; 28 29 sta = ap_get_sta(hapd, addr); 30 if (!sta) { 31 /* 32 * We need a STA entry to be able to maintain state for 33 * the GAS query. 34 */ 35 wpa_printf(MSG_DEBUG, "ANQP: Add a temporary STA entry for " 36 "GAS query"); 37 sta = ap_sta_add(hapd, addr); 38 if (!sta) { 39 wpa_printf(MSG_DEBUG, "Failed to add STA " MACSTR 40 " for GAS query", MAC2STR(addr)); 41 return NULL; 42 } 43 sta->flags |= WLAN_STA_GAS; 44 /* 45 * The default inactivity is 300 seconds. We don't need 46 * it to be that long. 47 */ 48 ap_sta_session_timeout(hapd, sta, 5); 49 } 50 51 if (sta->gas_dialog == NULL) { 52 sta->gas_dialog = os_zalloc(GAS_DIALOG_MAX * 53 sizeof(struct gas_dialog_info)); 54 if (sta->gas_dialog == NULL) 55 return NULL; 56 } 57 58 for (i = sta->gas_dialog_next, j = 0; j < GAS_DIALOG_MAX; i++, j++) { 59 if (i == GAS_DIALOG_MAX) 60 i = 0; 61 if (sta->gas_dialog[i].valid) 62 continue; 63 dia = &sta->gas_dialog[i]; 64 dia->valid = 1; 65 dia->index = i; 66 dia->dialog_token = dialog_token; 67 sta->gas_dialog_next = (++i == GAS_DIALOG_MAX) ? 0 : i; 68 return dia; 69 } 70 71 wpa_msg(hapd->msg_ctx, MSG_ERROR, "ANQP: Could not create dialog for " 72 MACSTR " dialog_token %u. Consider increasing " 73 "GAS_DIALOG_MAX.", MAC2STR(addr), dialog_token); 74 75 return NULL; 76} 77 78 79struct gas_dialog_info * 80gas_serv_dialog_find(struct hostapd_data *hapd, const u8 *addr, 81 u8 dialog_token) 82{ 83 struct sta_info *sta; 84 int i; 85 86 sta = ap_get_sta(hapd, addr); 87 if (!sta) { 88 wpa_printf(MSG_DEBUG, "ANQP: could not find STA " MACSTR, 89 MAC2STR(addr)); 90 return NULL; 91 } 92 for (i = 0; sta->gas_dialog && i < GAS_DIALOG_MAX; i++) { 93 if (sta->gas_dialog[i].dialog_token != dialog_token || 94 !sta->gas_dialog[i].valid) 95 continue; 96 return &sta->gas_dialog[i]; 97 } 98 wpa_printf(MSG_DEBUG, "ANQP: Could not find dialog for " 99 MACSTR " dialog_token %u", MAC2STR(addr), dialog_token); 100 return NULL; 101} 102 103 104void gas_serv_dialog_clear(struct gas_dialog_info *dia) 105{ 106 wpabuf_free(dia->sd_resp); 107 os_memset(dia, 0, sizeof(*dia)); 108} 109 110 111static void gas_serv_free_dialogs(struct hostapd_data *hapd, 112 const u8 *sta_addr) 113{ 114 struct sta_info *sta; 115 int i; 116 117 sta = ap_get_sta(hapd, sta_addr); 118 if (sta == NULL || sta->gas_dialog == NULL) 119 return; 120 121 for (i = 0; i < GAS_DIALOG_MAX; i++) { 122 if (sta->gas_dialog[i].valid) 123 return; 124 } 125 126 os_free(sta->gas_dialog); 127 sta->gas_dialog = NULL; 128} 129 130 131static void anqp_add_capab_list(struct hostapd_data *hapd, 132 struct wpabuf *buf) 133{ 134 u8 *len; 135 136 len = gas_anqp_add_element(buf, ANQP_CAPABILITY_LIST); 137 wpabuf_put_le16(buf, ANQP_CAPABILITY_LIST); 138 if (hapd->conf->venue_name) 139 wpabuf_put_le16(buf, ANQP_VENUE_NAME); 140 if (hapd->conf->roaming_consortium) 141 wpabuf_put_le16(buf, ANQP_ROAMING_CONSORTIUM); 142 gas_anqp_set_element_len(buf, len); 143} 144 145 146static void anqp_add_venue_name(struct hostapd_data *hapd, struct wpabuf *buf) 147{ 148 if (hapd->conf->venue_name) { 149 u8 *len; 150 unsigned int i; 151 len = gas_anqp_add_element(buf, ANQP_VENUE_NAME); 152 wpabuf_put_u8(buf, hapd->conf->venue_group); 153 wpabuf_put_u8(buf, hapd->conf->venue_type); 154 for (i = 0; i < hapd->conf->venue_name_count; i++) { 155 struct hostapd_venue_name *vn; 156 vn = &hapd->conf->venue_name[i]; 157 wpabuf_put_u8(buf, 3 + vn->name_len); 158 wpabuf_put_data(buf, vn->lang, 3); 159 wpabuf_put_data(buf, vn->name, vn->name_len); 160 } 161 gas_anqp_set_element_len(buf, len); 162 } 163} 164 165 166static void anqp_add_roaming_consortium(struct hostapd_data *hapd, 167 struct wpabuf *buf) 168{ 169 unsigned int i; 170 u8 *len; 171 172 len = gas_anqp_add_element(buf, ANQP_ROAMING_CONSORTIUM); 173 for (i = 0; i < hapd->conf->roaming_consortium_count; i++) { 174 struct hostapd_roaming_consortium *rc; 175 rc = &hapd->conf->roaming_consortium[i]; 176 wpabuf_put_u8(buf, rc->len); 177 wpabuf_put_data(buf, rc->oi, rc->len); 178 } 179 gas_anqp_set_element_len(buf, len); 180} 181 182 183static struct wpabuf * 184gas_serv_build_gas_resp_payload(struct hostapd_data *hapd, 185 unsigned int request, 186 struct gas_dialog_info *di) 187{ 188 struct wpabuf *buf; 189 190 buf = wpabuf_alloc(1400); 191 if (buf == NULL) 192 return NULL; 193 194 if (request & ANQP_REQ_CAPABILITY_LIST) 195 anqp_add_capab_list(hapd, buf); 196 if (request & ANQP_REQ_VENUE_NAME) 197 anqp_add_venue_name(hapd, buf); 198 if (request & ANQP_REQ_ROAMING_CONSORTIUM) 199 anqp_add_roaming_consortium(hapd, buf); 200 201 return buf; 202} 203 204 205static void gas_serv_clear_cached_ies(void *eloop_data, void *user_ctx) 206{ 207 struct gas_dialog_info *dia = eloop_data; 208 209 wpa_printf(MSG_DEBUG, "GAS: Timeout triggered, clearing dialog for " 210 "dialog token %d", dia->dialog_token); 211 212 gas_serv_dialog_clear(dia); 213} 214 215 216struct anqp_query_info { 217 unsigned int request; 218 unsigned int remote_request; 219 const void *param; 220 u32 param_arg; 221 u16 remote_delay; 222}; 223 224 225static void set_anqp_req(unsigned int bit, const char *name, int local, 226 unsigned int remote, u16 remote_delay, 227 struct anqp_query_info *qi) 228{ 229 qi->request |= bit; 230 if (local) { 231 wpa_printf(MSG_DEBUG, "ANQP: %s (local)", name); 232 } else if (bit & remote) { 233 wpa_printf(MSG_DEBUG, "ANQP: %s (remote)", name); 234 qi->remote_request |= bit; 235 if (remote_delay > qi->remote_delay) 236 qi->remote_delay = remote_delay; 237 } else { 238 wpa_printf(MSG_DEBUG, "ANQP: %s not available", name); 239 } 240} 241 242 243static void rx_anqp_query_list_id(struct hostapd_data *hapd, u16 info_id, 244 struct anqp_query_info *qi) 245{ 246 switch (info_id) { 247 case ANQP_CAPABILITY_LIST: 248 set_anqp_req(ANQP_REQ_CAPABILITY_LIST, "Capability List", 1, 0, 249 0, qi); 250 break; 251 case ANQP_VENUE_NAME: 252 set_anqp_req(ANQP_REQ_VENUE_NAME, "Venue Name", 253 hapd->conf->venue_name != NULL, 0, 0, qi); 254 break; 255 case ANQP_ROAMING_CONSORTIUM: 256 set_anqp_req(ANQP_REQ_ROAMING_CONSORTIUM, "Roaming Consortium", 257 hapd->conf->roaming_consortium != NULL, 0, 0, qi); 258 break; 259 default: 260 wpa_printf(MSG_DEBUG, "ANQP: Unsupported Info Id %u", 261 info_id); 262 break; 263 } 264} 265 266 267static void rx_anqp_query_list(struct hostapd_data *hapd, 268 const u8 *pos, const u8 *end, 269 struct anqp_query_info *qi) 270{ 271 wpa_printf(MSG_DEBUG, "ANQP: %u Info IDs requested in Query list", 272 (unsigned int) (end - pos) / 2); 273 274 while (pos + 2 <= end) { 275 rx_anqp_query_list_id(hapd, WPA_GET_LE16(pos), qi); 276 pos += 2; 277 } 278} 279 280 281static void gas_serv_req_local_processing(struct hostapd_data *hapd, 282 const u8 *sa, u8 dialog_token, 283 struct anqp_query_info *qi) 284{ 285 struct wpabuf *buf, *tx_buf; 286 287 buf = gas_serv_build_gas_resp_payload(hapd, qi->request, NULL); 288 wpa_hexdump_buf(MSG_MSGDUMP, "ANQP: Locally generated ANQP responses", 289 buf); 290 if (!buf) 291 return; 292 293 if (wpabuf_len(buf) > hapd->gas_frag_limit || 294 hapd->conf->gas_comeback_delay) { 295 struct gas_dialog_info *di; 296 u16 comeback_delay = 1; 297 298 if (hapd->conf->gas_comeback_delay) { 299 /* Testing - allow overriding of the delay value */ 300 comeback_delay = hapd->conf->gas_comeback_delay; 301 } 302 303 wpa_printf(MSG_DEBUG, "ANQP: Too long response to fit in " 304 "initial response - use GAS comeback"); 305 di = gas_dialog_create(hapd, sa, dialog_token); 306 if (!di) { 307 wpa_printf(MSG_INFO, "ANQP: Could not create dialog " 308 "for " MACSTR " (dialog token %u)", 309 MAC2STR(sa), dialog_token); 310 wpabuf_free(buf); 311 return; 312 } 313 di->sd_resp = buf; 314 di->sd_resp_pos = 0; 315 tx_buf = gas_anqp_build_initial_resp_buf( 316 dialog_token, WLAN_STATUS_SUCCESS, comeback_delay, 317 NULL); 318 } else { 319 wpa_printf(MSG_DEBUG, "ANQP: Initial response (no comeback)"); 320 tx_buf = gas_anqp_build_initial_resp_buf( 321 dialog_token, WLAN_STATUS_SUCCESS, 0, buf); 322 wpabuf_free(buf); 323 } 324 if (!tx_buf) 325 return; 326 327 hostapd_drv_send_action(hapd, hapd->iface->freq, 0, sa, 328 wpabuf_head(tx_buf), wpabuf_len(tx_buf)); 329 wpabuf_free(tx_buf); 330} 331 332 333static void gas_serv_rx_gas_initial_req(struct hostapd_data *hapd, 334 const u8 *sa, 335 const u8 *data, size_t len) 336{ 337 const u8 *pos = data; 338 const u8 *end = data + len; 339 const u8 *next; 340 u8 dialog_token; 341 u16 slen; 342 struct anqp_query_info qi; 343 const u8 *adv_proto; 344 345 if (len < 1 + 2) 346 return; 347 348 os_memset(&qi, 0, sizeof(qi)); 349 350 dialog_token = *pos++; 351 wpa_msg(hapd->msg_ctx, MSG_DEBUG, 352 "GAS: GAS Initial Request from " MACSTR " (dialog token %u) ", 353 MAC2STR(sa), dialog_token); 354 355 if (*pos != WLAN_EID_ADV_PROTO) { 356 wpa_msg(hapd->msg_ctx, MSG_DEBUG, 357 "GAS: Unexpected IE in GAS Initial Request: %u", *pos); 358 return; 359 } 360 adv_proto = pos++; 361 362 slen = *pos++; 363 next = pos + slen; 364 if (next > end || slen < 2) { 365 wpa_msg(hapd->msg_ctx, MSG_DEBUG, 366 "GAS: Invalid IE in GAS Initial Request"); 367 return; 368 } 369 pos++; /* skip QueryRespLenLimit and PAME-BI */ 370 371 if (*pos != ACCESS_NETWORK_QUERY_PROTOCOL) { 372 struct wpabuf *buf; 373 wpa_msg(hapd->msg_ctx, MSG_DEBUG, 374 "GAS: Unsupported GAS advertisement protocol id %u", 375 *pos); 376 if (sa[0] & 0x01) 377 return; /* Invalid source address - drop silently */ 378 buf = gas_build_initial_resp( 379 dialog_token, WLAN_STATUS_GAS_ADV_PROTO_NOT_SUPPORTED, 380 0, 2 + slen + 2); 381 if (buf == NULL) 382 return; 383 wpabuf_put_data(buf, adv_proto, 2 + slen); 384 wpabuf_put_le16(buf, 0); /* Query Response Length */ 385 hostapd_drv_send_action(hapd, hapd->iface->freq, 0, sa, 386 wpabuf_head(buf), wpabuf_len(buf)); 387 wpabuf_free(buf); 388 return; 389 } 390 391 pos = next; 392 /* Query Request */ 393 if (pos + 2 > end) 394 return; 395 slen = WPA_GET_LE16(pos); 396 pos += 2; 397 if (pos + slen > end) 398 return; 399 end = pos + slen; 400 401 /* ANQP Query Request */ 402 while (pos < end) { 403 u16 info_id, elen; 404 405 if (pos + 4 > end) 406 return; 407 408 info_id = WPA_GET_LE16(pos); 409 pos += 2; 410 elen = WPA_GET_LE16(pos); 411 pos += 2; 412 413 if (pos + elen > end) { 414 wpa_printf(MSG_DEBUG, "ANQP: Invalid Query Request"); 415 return; 416 } 417 418 switch (info_id) { 419 case ANQP_QUERY_LIST: 420 rx_anqp_query_list(hapd, pos, pos + elen, &qi); 421 break; 422 default: 423 wpa_printf(MSG_DEBUG, "ANQP: Unsupported Query " 424 "Request element %u", info_id); 425 break; 426 } 427 428 pos += elen; 429 } 430 431 gas_serv_req_local_processing(hapd, sa, dialog_token, &qi); 432} 433 434 435void gas_serv_tx_gas_response(struct hostapd_data *hapd, const u8 *dst, 436 struct gas_dialog_info *dialog) 437{ 438 struct wpabuf *buf, *tx_buf; 439 u8 dialog_token = dialog->dialog_token; 440 size_t frag_len; 441 442 if (dialog->sd_resp == NULL) { 443 buf = gas_serv_build_gas_resp_payload(hapd, 444 dialog->all_requested, 445 dialog); 446 wpa_hexdump_buf(MSG_MSGDUMP, "ANQP: Generated ANQP responses", 447 buf); 448 if (!buf) 449 goto tx_gas_response_done; 450 dialog->sd_resp = buf; 451 dialog->sd_resp_pos = 0; 452 } 453 frag_len = wpabuf_len(dialog->sd_resp) - dialog->sd_resp_pos; 454 if (frag_len > hapd->gas_frag_limit || dialog->comeback_delay || 455 hapd->conf->gas_comeback_delay) { 456 u16 comeback_delay_tus = dialog->comeback_delay + 457 GAS_SERV_COMEBACK_DELAY_FUDGE; 458 u32 comeback_delay_secs, comeback_delay_usecs; 459 460 if (hapd->conf->gas_comeback_delay) { 461 /* Testing - allow overriding of the delay value */ 462 comeback_delay_tus = hapd->conf->gas_comeback_delay; 463 } 464 465 wpa_printf(MSG_DEBUG, "GAS: Response frag_len %u (frag limit " 466 "%u) and comeback delay %u, " 467 "requesting comebacks", (unsigned int) frag_len, 468 (unsigned int) hapd->gas_frag_limit, 469 dialog->comeback_delay); 470 tx_buf = gas_anqp_build_initial_resp_buf(dialog_token, 471 WLAN_STATUS_SUCCESS, 472 comeback_delay_tus, 473 NULL); 474 if (tx_buf) { 475 wpa_msg(hapd->msg_ctx, MSG_DEBUG, 476 "GAS: Tx GAS Initial Resp (comeback = 10TU)"); 477 hostapd_drv_send_action(hapd, hapd->iface->freq, 0, 478 dst, 479 wpabuf_head(tx_buf), 480 wpabuf_len(tx_buf)); 481 } 482 wpabuf_free(tx_buf); 483 484 /* start a timer of 1.5 * comeback-delay */ 485 comeback_delay_tus = comeback_delay_tus + 486 (comeback_delay_tus / 2); 487 comeback_delay_secs = (comeback_delay_tus * 1024) / 1000000; 488 comeback_delay_usecs = (comeback_delay_tus * 1024) - 489 (comeback_delay_secs * 1000000); 490 eloop_register_timeout(comeback_delay_secs, 491 comeback_delay_usecs, 492 gas_serv_clear_cached_ies, dialog, 493 NULL); 494 goto tx_gas_response_done; 495 } 496 497 buf = wpabuf_alloc_copy(wpabuf_head_u8(dialog->sd_resp) + 498 dialog->sd_resp_pos, frag_len); 499 if (buf == NULL) { 500 wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: Buffer allocation " 501 "failed"); 502 goto tx_gas_response_done; 503 } 504 tx_buf = gas_anqp_build_initial_resp_buf(dialog_token, 505 WLAN_STATUS_SUCCESS, 0, buf); 506 wpabuf_free(buf); 507 if (tx_buf == NULL) 508 goto tx_gas_response_done; 509 wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: Tx GAS Initial " 510 "Response (frag_id %d frag_len %d)", 511 dialog->sd_frag_id, (int) frag_len); 512 dialog->sd_frag_id++; 513 514 hostapd_drv_send_action(hapd, hapd->iface->freq, 0, dst, 515 wpabuf_head(tx_buf), wpabuf_len(tx_buf)); 516 wpabuf_free(tx_buf); 517tx_gas_response_done: 518 gas_serv_clear_cached_ies(dialog, NULL); 519} 520 521 522static void gas_serv_rx_gas_comeback_req(struct hostapd_data *hapd, 523 const u8 *sa, 524 const u8 *data, size_t len) 525{ 526 struct gas_dialog_info *dialog; 527 struct wpabuf *buf, *tx_buf; 528 u8 dialog_token; 529 size_t frag_len; 530 int more = 0; 531 532 wpa_hexdump(MSG_DEBUG, "GAS: RX GAS Comeback Request", data, len); 533 if (len < 1) 534 return; 535 dialog_token = *data; 536 wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: Dialog Token: %u", 537 dialog_token); 538 539 dialog = gas_serv_dialog_find(hapd, sa, dialog_token); 540 if (!dialog) { 541 wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: No pending SD " 542 "response fragment for " MACSTR " dialog token %u", 543 MAC2STR(sa), dialog_token); 544 545 if (sa[0] & 0x01) 546 return; /* Invalid source address - drop silently */ 547 tx_buf = gas_anqp_build_comeback_resp_buf( 548 dialog_token, WLAN_STATUS_NO_OUTSTANDING_GAS_REQ, 0, 0, 549 0, NULL); 550 if (tx_buf == NULL) 551 return; 552 goto send_resp; 553 } 554 555 if (dialog->sd_resp == NULL) { 556 wpa_printf(MSG_DEBUG, "GAS: Remote request 0x%x received 0x%x", 557 dialog->requested, dialog->received); 558 if ((dialog->requested & dialog->received) != 559 dialog->requested) { 560 wpa_printf(MSG_DEBUG, "GAS: Did not receive response " 561 "from remote processing"); 562 gas_serv_dialog_clear(dialog); 563 tx_buf = gas_anqp_build_comeback_resp_buf( 564 dialog_token, 565 WLAN_STATUS_GAS_RESP_NOT_RECEIVED, 0, 0, 0, 566 NULL); 567 if (tx_buf == NULL) 568 return; 569 goto send_resp; 570 } 571 572 buf = gas_serv_build_gas_resp_payload(hapd, 573 dialog->all_requested, 574 dialog); 575 wpa_hexdump_buf(MSG_MSGDUMP, "ANQP: Generated ANQP responses", 576 buf); 577 if (!buf) 578 goto rx_gas_comeback_req_done; 579 dialog->sd_resp = buf; 580 dialog->sd_resp_pos = 0; 581 } 582 frag_len = wpabuf_len(dialog->sd_resp) - dialog->sd_resp_pos; 583 if (frag_len > hapd->gas_frag_limit) { 584 frag_len = hapd->gas_frag_limit; 585 more = 1; 586 } 587 wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: resp frag_len %u", 588 (unsigned int) frag_len); 589 buf = wpabuf_alloc_copy(wpabuf_head_u8(dialog->sd_resp) + 590 dialog->sd_resp_pos, frag_len); 591 if (buf == NULL) { 592 wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: Failed to allocate " 593 "buffer"); 594 goto rx_gas_comeback_req_done; 595 } 596 tx_buf = gas_anqp_build_comeback_resp_buf(dialog_token, 597 WLAN_STATUS_SUCCESS, 598 dialog->sd_frag_id, 599 more, 0, buf); 600 wpabuf_free(buf); 601 if (tx_buf == NULL) 602 goto rx_gas_comeback_req_done; 603 wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: Tx GAS Comeback Response " 604 "(frag_id %d more=%d frag_len=%d)", 605 dialog->sd_frag_id, more, (int) frag_len); 606 dialog->sd_frag_id++; 607 dialog->sd_resp_pos += frag_len; 608 609 if (more) { 610 wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: %d more bytes remain " 611 "to be sent", 612 (int) (wpabuf_len(dialog->sd_resp) - 613 dialog->sd_resp_pos)); 614 } else { 615 wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: All fragments of " 616 "SD response sent"); 617 gas_serv_dialog_clear(dialog); 618 gas_serv_free_dialogs(hapd, sa); 619 } 620 621send_resp: 622 hostapd_drv_send_action(hapd, hapd->iface->freq, 0, sa, 623 wpabuf_head(tx_buf), wpabuf_len(tx_buf)); 624 wpabuf_free(tx_buf); 625 return; 626 627rx_gas_comeback_req_done: 628 gas_serv_clear_cached_ies(dialog, NULL); 629} 630 631 632static void gas_serv_rx_public_action(void *ctx, const u8 *buf, size_t len, 633 int freq) 634{ 635 struct hostapd_data *hapd = ctx; 636 const struct ieee80211_mgmt *mgmt; 637 size_t hdr_len; 638 const u8 *sa, *data; 639 640 mgmt = (const struct ieee80211_mgmt *) buf; 641 hdr_len = (const u8 *) &mgmt->u.action.u.vs_public_action.action - buf; 642 if (hdr_len > len) 643 return; 644 if (mgmt->u.action.category != WLAN_ACTION_PUBLIC) 645 return; 646 sa = mgmt->sa; 647 len -= hdr_len; 648 data = &mgmt->u.action.u.public_action.action; 649 switch (data[0]) { 650 case WLAN_PA_GAS_INITIAL_REQ: 651 gas_serv_rx_gas_initial_req(hapd, sa, data + 1, len - 1); 652 break; 653 case WLAN_PA_GAS_COMEBACK_REQ: 654 gas_serv_rx_gas_comeback_req(hapd, sa, data + 1, len - 1); 655 break; 656 } 657} 658 659 660int gas_serv_init(struct hostapd_data *hapd) 661{ 662 hapd->public_action_cb = gas_serv_rx_public_action; 663 hapd->public_action_cb_ctx = hapd; 664 hapd->gas_frag_limit = 1400; 665 if (hapd->conf->gas_frag_limit > 0) 666 hapd->gas_frag_limit = hapd->conf->gas_frag_limit; 667 return 0; 668} 669 670 671void gas_serv_deinit(struct hostapd_data *hapd) 672{ 673} 674