/* Cesar project {{{ * * Copyright (C) 2009 Spidcom * * <<>> * * }}} */ /** * \file cp/sta/action/src/sc.c * \brief STA action, Simple Connect procedure functions definitions. * \ingroup cp_sta_action */ #include "common/std.h" #include "cp/inc/context.h" #include "cp/sta/action/inc/context.h" #include "cp/sta/action/assoc.h" #include "cp/sta/core/core.h" #include "cp/msg/inc/msg_cm.h" #include "cp/msg/inc/msg_drv.h" #include "cp/fsm/inc/events.h" #include "cp/fsm/fsm.h" #include "cp/cco/action/cco_action.h" #include "cp/secu/defs.h" #include "cp/sta/action/sc.h" /** * SC timeout. */ #define SC_TIMEOUT_MS 30000 /** * Minimum value of the timer for periodic broadcast of SC_JOIN.REQ MME. */ #define SC_PERIODIC_SC_JOIN_REQ_BCAST_MS_MIN 750 /** * Maximum value of the timer for periodic broadcast of SC_JOIN.REQ MME. */ #define SC_PERIODIC_SC_JOIN_REQ_BCAST_MS_MAX 1250 /** * Start SC timer. * \param ctx control plane context. */ void cp_sta_action_sc__start_timer (cp_t *ctx); /** * Stop SC timer. * \param ctx control plane context. */ void cp_sta_action_sc__stop_timer (cp_t *ctx); /** * Start the SC procedure as the "add" STA. * \param ctx control plane context. * \param peer peer with which we are doing the SC procedure. * * This function send the SC_JOIN.CNF and make the top FSM move to CCo if * needed. */ void cp_sta_action_sc__start_sc_in_add (cp_t *ctx, cp_mme_peer_t *peer); /** * Send a CM_SC_JOIN.REQ to all STA in broadcast. * \param ctx control plane context. */ void cp_sta_action_sc__send_cm_sc_join_req (cp_t *ctx); /** * Start the SC JOIN.REQ timer. * \param ctx control plane context. */ void cp_sta_action_sc__start_sc_join_req_timer (cp_t *ctx); void cp_sta_action_sc__start_timer (cp_t *ctx) { /* Check parameter. */ dbg_assert (ctx); /* Event to generate when SC timeout. */ cp_fsm_event_t *event = cp_fsm_event_bare_new (ctx, CP_FSM_EVENT_TYPE_sc_timeout); /* Enable SC timeout timer. */ cp_sta_core_gen_timed_event (ctx, &ctx->sta_action.sc.timeout_timer, event, SC_TIMEOUT_MS); } void cp_sta_action_sc__stop_timer (cp_t *ctx) { /* Check parameter. */ dbg_assert (ctx); /* Disable SC timeout timer. */ cp_sta_core_stop_timed_or_cyclic_event (ctx, &ctx->sta_action.sc.timeout_timer); } void cp_sta_action_sc__start_sc_in_add (cp_t *ctx, cp_mme_peer_t *peer) { /* Check parameters. */ dbg_assert (ctx); dbg_assert (peer); /* Keep track of peer with witch we are doing SC procedure. */ ctx->sta_action.sc.peer = *peer; /* Create reply (CM_SC_JOIN.CNF). */ cp_msg_cm_sc_join_cnf_t cnf; cnf.nid = cp_sta_own_data_get_nid (ctx); cnf.avln_status = MAC_TEI_IS_STA (cp_sta_own_data_get_tei (ctx)); cnf.cco_cap = CP_CCO_LEVEL; cnf.pco_cap = CP_PCO_CAP; cnf.backup_cco_cap = CP_BACKUP_CCO_CAP; cnf.pco_status = cp_sta_own_data_get_pco_status (ctx); cnf.backup_cco_status = cp_sta_own_data_get_backup_cco_status (ctx); if (cnf.avln_status) { /* We are on AVLN, give our CCo status. */ cnf.cco_status = cp_sta_own_data_get_cco_status (ctx); } else { /* We are not on an AVLN, we must create one and become CCo. */ cnf.cco_status = true; /* Make the main CP FSM move to CCo. */ cp_fsm_event_t *event = cp_fsm_event_bare_new (ctx, CP_FSM_EVENT_TYPE_sc_get_cco_functionality); cp_fsm_trigger (ctx, event); } /* Send the reply. */ cp_msg_cm_sc_join_cnf_send (ctx, peer, &cnf); } void cp_sta_action_sc__send_cm_sc_join_req (cp_t *ctx) { /* Check parameter. */ dbg_assert (ctx); /* Send the SC_JOIN MME. */ cp_mme_peer_t peer = CP_MME_PEER (MAC_BROADCAST, MAC_TEI_UNASSOCIATED); cp_msg_cm_sc_join_req_send (ctx, &peer); } void cp_sta_action_sc__start_sc_join_req_timer (cp_t *ctx) { /* Check parameter. */ dbg_assert (ctx); /* Event to generate when SC_JOIN.REQ timer times-out. */ cp_fsm_event_t *event = cp_fsm_event_bare_new (ctx, CP_FSM_EVENT_TYPE_sc_join_req_timeout); /* Generate a random value to wait for the broadcast of SC_JOIN.REQ. */ uint rnd_wait = lib_rnd_uniform (&ctx->rnd, SC_PERIODIC_SC_JOIN_REQ_BCAST_MS_MAX - SC_PERIODIC_SC_JOIN_REQ_BCAST_MS_MIN); rnd_wait += SC_PERIODIC_SC_JOIN_REQ_BCAST_MS_MIN; /* Start the SC JOIN REQ timer. */ cp_sta_core_gen_timed_event (ctx, &ctx->sta_action.sc.timer_sc_join_req_broadcast, event, rnd_wait); } void cp_sta_action_sc__start_sc_join (cp_t *ctx) { /* Check parameter. */ dbg_assert (ctx); /* Generate the event to make the SC FSM start. */ cp_fsm_event_t *event = cp_fsm_event_bare_new (ctx, CP_FSM_EVENT_TYPE_sc_start_join); cp_fsm_post (ctx, event); } void cp_sta_action_sc__start_sc_add (cp_t *ctx) { /* Check parameter. */ dbg_assert (ctx); /* Generate the event to make the SC FSM start. */ cp_fsm_event_t *event = cp_fsm_event_bare_new (ctx, CP_FSM_EVENT_TYPE_sc_start_add); cp_fsm_post (ctx, event); } void cp_sta_action_sc__sc_idle__enter (cp_t *ctx) { /* Stop SC timer. */ cp_sta_action_sc__stop_timer (ctx); cp_sta_own_data_set_sc (ctx, false); } void cp_sta_action_sc__sc_idle__leave (cp_t *ctx) { /* Check parameter. */ dbg_assert (ctx); /* Start SC timer. */ cp_sta_action_sc__start_timer (ctx); cp_sta_own_data_set_sc (ctx, true); } void cp_sta_action_sc__sc_join__enter (cp_t *ctx) { /* Send a SC_JOIN.REQ. */ cp_sta_action_sc__start_sc_join_req_timer (ctx); /* Start the timer. */ cp_sta_action_sc__send_cm_sc_join_req (ctx); } void cp_sta_action_sc__sc_join__leave (cp_t *ctx) { /* Check parameter. */ dbg_assert (ctx); /* Disable SC timeout timer. */ cp_sta_core_stop_timed_or_cyclic_event (ctx, &ctx->sta_action.sc.timer_sc_join_req_broadcast); } void cp_sta_action_sc__sc_timeout (cp_t *ctx) { /* Stop assoc FSM. */ cp_fsm_trigger_new_event (ctx, bare, to_leave); /* Generate the event for the CP FSM. */ cp_fsm_post_new_event (ctx, bare, sc_failed); } void cp_sta_action_sc_add__sc_timeout (cp_t *ctx) { /* Generate the event for the CP FSM. */ cp_fsm_post_new_event (ctx, bare, sc_failed); } void cp_sta_action_sc__sc_add__cm_sc_join_req (cp_t *ctx, cp_mme_rx_t *mme) { /* Check parameters. */ dbg_assert (ctx); dbg_assert (mme); uint cco_cap; /* Check received MME. */ if ((mme->peer.tei != 0x0) || (!cp_msg_cm_sc_join_req_receive (ctx, mme, &cco_cap)) ) { /* Error in received MME. */ cp_fsm_branch (ctx, SC_ADD, CM_SC_JOIN_REQ, nok); } else { /* Let's try SC procedure with peer. */ cp_sta_action_sc__start_sc_in_add (ctx, &mme->peer); /* Move FSM. */ cp_fsm_branch (ctx, SC_ADD, CM_SC_JOIN_REQ, ok); } } void cp_sta_action_sc__to_stop (cp_t *ctx) { /* Generate the event for the CP FSM. */ cp_fsm_event_t *event = cp_fsm_event_bare_new (ctx, CP_FSM_EVENT_TYPE_sc_failed); cp_fsm_post (ctx, event); } void cp_sta_action_sc__sc_wait_peer_associated__new_sta_associated (cp_t *ctx, cp_net_t *net, cp_sta_t *sta) { /* Check parameters. */ dbg_assert (ctx); dbg_assert (net); dbg_assert (sta); /* If it is our net and the STA we are doing the SC procedure. */ if (ctx->sta_action.sc.peer.mac == cp_sta_get_mac_address (sta)) { /* Store its TEI. */ ctx->sta_action.sc.peer.tei = cp_sta_get_tei (sta); /* Start the exchange of hash key to build the TEK. */ cp_msg_cm_get_key_req_t req; req.relayed = false; req.key_type = CP_MSG_KEY_HASH_KEY; req.nid = cp_sta_own_data_get_nid (ctx); /* Generate hash key. */ cp_secu_generate_hash (ctx, lib_rnd32 (&ctx->rnd), req.hash_key, CP_SECU_HASH_KEY_FOR_TEK_SIZE); /* Keep a copy of the hash key. */ memcpy (ctx->sta_action.sc.hash_key, req.hash_key, CP_HASH_KEY_SIZE); cp_secu_protocol_run_new (&ctx->sta_action.sc.prun, 3, &ctx->rnd); /* Send GET_KEY_REQ_PID3. */ cp_msg_cm_get_key_req_send (ctx, &ctx->sta_action.sc.peer, CP_MME_PEKS_SPC_NOT_EMBEDDED, /* Not encrypted! */ &ctx->sta_action.sc.prun, &req); /* Update FSM. */ cp_fsm_branch (ctx, SC_WAIT_PEER_ASSOCIATED, new_sta_associated, sta_is_sc_peer); } else { /* Nothing to do. */ /* Update FSM. */ cp_fsm_branch (ctx, SC_WAIT_PEER_ASSOCIATED, new_sta_associated, sta_not_sc_peer); } } void cp_sta_action_sc__sc_join__cm_sc_join_req (cp_t *ctx, cp_mme_rx_t *mme) { /* Check parameters. */ dbg_assert (ctx); dbg_assert (mme); uint cco_cap; /* Check SC_JOIN.REQ */ if (!cp_msg_cm_sc_join_req_receive (ctx, mme, &cco_cap)) { /* Incorrect MME. */ cp_fsm_branch (ctx, SC_JOIN, CM_SC_JOIN_REQ, nok); } else { /* SC_JOIN.REQ is correct, let's compare CCo capabilities to know * which one is going to SC_ADD. */ /* To prevent a warning. */ const uint my_cco_cap = CP_CCO_LEVEL; if ((my_cco_cap > cco_cap) || ((my_cco_cap == cco_cap) && (cp_sta_own_data_get_mac_address (ctx) > mme->peer.mac))) { /* My CCo capabilities are greater than the peer. */ /* Act if we were in SC_ADD procedure. */ cp_sta_action_sc__start_sc_in_add (ctx, &mme->peer); /* Update FSM. */ cp_fsm_branch (ctx, SC_JOIN, CM_SC_JOIN_REQ, ok_my_cco_cap_greater); } else { /* Peer CCo capabilities are greater than mine. */ /* Nothing to do, update FSM. */ cp_fsm_branch (ctx, SC_JOIN, CM_SC_JOIN_REQ, ok_my_cco_cap_lower); } } } void cp_sta_action_sc__sc_join__cm_sc_join_cnf (cp_t *ctx, cp_mme_rx_t *mme) { /* Check parameters. */ dbg_assert (ctx); dbg_assert (mme); /* Check MME. */ cp_msg_cm_sc_join_cnf_t cnf; if (!cp_msg_cm_sc_join_cnf_receive (ctx, mme, &cnf)) { /* Error in MME. */ cp_fsm_branch (ctx, SC_JOIN, CM_SC_JOIN_CNF, nok); } else { /* Set NID from MME. */ cp_sta_own_data_set_nid (ctx, cnf.nid); /* Copy SC peer information. */ ctx->sta_action.sc.peer = mme->peer; /* We need to wait for beacon. */ cp_fsm_branch (ctx, SC_JOIN, CM_SC_JOIN_CNF, ok); } } void cp_sta_action_sc__sc_join__sc_join_req_timeout (cp_t *ctx) { /* Send the SC_JOIN.REQ MME. */ cp_sta_action_sc__start_sc_join_req_timer (ctx); /* Restart timer. */ cp_sta_action_sc__send_cm_sc_join_req (ctx); } void cp_sta_action_sc__sc_wait_beacon__beacon (cp_t *ctx, cp_beacon_desc_t *beacon, cp_net_t *net, cp_sta_t *sta) { /* Check parameters. */ dbg_assert (ctx); dbg_assert (beacon); dbg_assert (net); dbg_assert (sta); if (cp_sta_get_cco_status (sta)) { /* Extract some data. */ cp_sta_own_data_t *own_data = cp_sta_mgr_get_sta_own_data (ctx); cp_nid_t our_nid = cp_sta_own_data_get_nid (ctx); cp_nid_t its_nid = cp_net_get_nid (ctx, net); u8 its_snid = cp_net_get_snid (ctx, net); /* If we want this AVLN. */ if (!own_data->nid_track /* No AVLN tracked. */ || (our_nid != own_data->nid_track && our_nid == its_nid) /* Better NID match. */ ) { /* Track the AVLN with the corresponding NID. */ own_data->nid_track = its_nid; cp_sta_own_data_set_snid (ctx, its_snid); /* Ask beacon module to track this AVLN. */ cp_beacon_process_tracked_avln (ctx, beacon, net); } if (its_nid == our_nid) { /* Start assoc procedure. */ cp_sta_action_sc_assoc_start (ctx, net, sta); /* Inform main FSM we have start SC association procedure. */ cp_fsm_trigger_new_event (ctx, bare, sc_succeed); cp_fsm_branch (ctx, SC_WAIT_BEACON, BEACON, nid_match); } else { cp_fsm_branch (ctx, SC_WAIT_BEACON, BEACON, nid_differs); } } else { cp_fsm_branch (ctx, SC_WAIT_BEACON, BEACON, nid_differs); } } void cp_sta_action_sc__sc_building_tek__cm_get_key_cnf_pid3 (cp_t *ctx, cp_mme_rx_t *mme) { /* Check parameters. */ dbg_assert (ctx); dbg_assert (mme); cp_msg_cm_get_key_cnf_t cnf; /* Check received MME. */ if (!cp_mme_peer_cmp (&mme->peer, &ctx->sta_action.sc.peer) || !cp_msg_cm_get_key_cnf_receive (ctx, mme, &cnf) || (!cp_secu_protocol_check (&ctx->sta_action.sc.prun, &mme->prun, CP_SECU_PROTOCOL_RUN_CHECK_RESULT_NEXT)) || (cnf.eks < CP_MME_PEKS_TEK_MIN) || (cnf.eks > CP_MME_PEKS_TEK_MAX)) { /* Error in MME. */ cp_fsm_branch (ctx, SC_BUILDING_TEK, CM_GET_KEY_CNF_PID3, unrelated); } else { if (cnf.result != CP_MSG_CM_GET_KEY_CNF_RESULT_KEY_GRANTED) { /* Hash key exchange fails. */ cp_fsm_branch (ctx, SC_BUILDING_TEK, CM_GET_KEY_CNF_PID3, nok); } else { /* Hash key exchange for TEK succeed. */ /* Compute TEK. */ cp_secu_tek_gen ((u32 *)ctx->sta_action.sc.hash_key, (u32 *)cnf.hash_key, &ctx->sta_action.sc.tek); /* Set the TEK for encryption. */ cp_sta_own_data_set_tek (ctx, ctx->sta_action.sc.tek); /* Next protocol run. */ ctx->sta_action.sc.prun = mme->prun; cp_secu_protocol_next (&ctx->sta_action.sc.prun, &ctx->rnd, false); /* Create reply. */ cp_msg_cm_set_key_req_t req; req.key_type = CP_MSG_KEY_NMK; req.cco_cap = CP_CCO_LEVEL; req.nid = cp_sta_own_data_get_nid (ctx); /* Check this. */ req.new_eks = CP_MME_PEKS_NMK; req.new_key = cp_sta_own_data_get_nmk (ctx); /* Send the NMK encrypted with TEK. */ cp_msg_cm_set_key_req_send (ctx, &mme->peer, CP_MME_PEKS_TEK_MIN, &ctx->sta_action.sc.prun, &req); cp_fsm_branch (ctx, SC_BUILDING_TEK, CM_GET_KEY_CNF_PID3, ok); } } } void cp_sta_action_sc__sc_nmk_exchange__cm_set_key_cnf_pid3 (cp_t *ctx, cp_mme_rx_t *mme) { /* Check parameters. */ dbg_assert (ctx); dbg_assert (mme); cp_msg_cm_set_key_cnf_t cnf; /* Check received MME. */ if (!cp_mme_peer_cmp (&mme->peer, &ctx->sta_action.sc.peer) || !cp_msg_cm_set_key_cnf_receive (ctx, mme, &cnf) || (!cp_secu_protocol_check (&ctx->sta_action.sc.prun, &mme->prun, CP_SECU_PROTOCOL_RUN_CHECK_RESULT_LAST))) { /* Error in MME. */ cp_fsm_branch (ctx, SC_NMK_EXCHANGE, CM_SET_KEY_CNF_PID3, unrelated); } else { if (cnf.result != CP_MSG_CM_SET_KEY_CNF_RESULT_SUCCESS) { /* NMK exchange failed, inform main FSM. */ cp_fsm_event_t *event = cp_fsm_event_bare_new (ctx, CP_FSM_EVENT_TYPE_sc_failed); cp_fsm_post (ctx, event); /* Move the FSM. */ cp_fsm_branch (ctx, SC_NMK_EXCHANGE, CM_SET_KEY_CNF_PID3, nok); } else { /* NMK exchange succeed, inform main FSM. */ cp_fsm_event_t *event = cp_fsm_event_bare_new (ctx, CP_FSM_EVENT_TYPE_sc_succeed); cp_fsm_post (ctx, event); /* Move the FSM. */ cp_fsm_branch (ctx, SC_NMK_EXCHANGE, CM_SET_KEY_CNF_PID3, ok); } } } void cp_sta_action_sc__sc_usta_sc_get_cco_functionality (cp_t *ctx) { /* Become CCo. */ cp_cco_action__unassoc__cco_start (ctx); } void cp_sta_action_sc__sc_usta_track_beacon (cp_t *ctx, cp_beacon_desc_t *beacon, cp_net_t *net, cp_sta_t *sta) { /* Check parameters. */ dbg_assert (beacon); dbg_assert (net); dbg_assert (sta); cp_sta_own_data_t *own_data = cp_sta_mgr_get_sta_own_data (ctx); cp_nid_t our_nid = cp_sta_own_data_get_nid (ctx); cp_nid_t its_nid = cp_net_get_nid (ctx, net); u8 its_snid = cp_net_get_snid (ctx, net); /* Does not support proxy networking for the moment, only handle CCo * beacon. */ if (cp_sta_get_cco_status (sta)) { /* Should we track this beacon? */ if (!own_data->nid_track /* No AVLN tracked. */ || (our_nid != own_data->nid_track && our_nid == its_nid) /* Better NID match. */ ) { own_data->nid_track = its_nid; cp_sta_own_data_set_snid (ctx, its_snid); cp_beacon_process_tracked_avln (ctx, beacon, net); } } } void cp_sta_action_sc__sc_ucco_sc_get_cco_functionality (cp_t *ctx) { /* Become CCo. */ cp_cco_action__unassoc__cco_start (ctx); } void cp_sta_action_sc__sc_ucco_track_beacon (cp_t *ctx, cp_beacon_desc_t *beacon, cp_net_t *net, cp_sta_t *sta) { dbg_assert (ctx); dbg_assert (beacon); dbg_assert (net); dbg_assert (sta); /* Change to STA behaviour. */ cp_beacon_reconfigure_timer (ctx, false); /* Handle incoming beacon. */ cp_sta_action_sc__sc_usta_track_beacon (ctx, beacon, net, sta); } void cp_sta_action_sc__sc_sta__avln_failure (cp_t *ctx) { cp_fsm_event_t *event = cp_fsm_event_bare_new (ctx, CP_FSM_EVENT_TYPE_to_stop); cp_fsm_post (ctx, event); } void cp_sta_action_sc__sc_cco__sc_failed (cp_t *ctx) { dbg_assert (ctx); cp_net_t *our_net = cp_sta_mgr_get_our_avln (ctx); if (cp_net_is_empty (ctx, our_net)) { /* Leave CCo role. */ cp_cco_action__cco__unassoc_stop (ctx); if (cp_sta_mgr_net_list_is_empty (ctx)) cp_fsm_branch (ctx, SC_CCO, sc_failed, no_sta_no_avln); else cp_fsm_branch (ctx, SC_CCO, sc_failed, no_sta_avln); } else { /* Ok, stay CCo. */ cp_fsm_branch (ctx, SC_CCO, sc_failed, sta); } }