| /* | 
 |  * Copyright (c) 2019 Intel Corporation. | 
 |  * | 
 |  * SPDX-License-Identifier: Apache-2.0 | 
 |  */ | 
 |  | 
 | #include <zephyr/logging/log.h> | 
 | LOG_MODULE_DECLARE(net_l2_ppp, CONFIG_NET_L2_PPP_LOG_LEVEL); | 
 |  | 
 | #include <zephyr/net/net_core.h> | 
 | #include <zephyr/net/net_pkt.h> | 
 | #include <zephyr/net/net_if.h> | 
 |  | 
 | #include <zephyr/net/ppp.h> | 
 | #include <zephyr/random/random.h> | 
 |  | 
 | #include "net_private.h" | 
 |  | 
 | #include "ppp_internal.h" | 
 |  | 
 | /* This timeout is in milliseconds */ | 
 | #define FSM_TIMEOUT K_MSEC(CONFIG_NET_L2_PPP_TIMEOUT) | 
 |  | 
 | #define MAX_NACK_LOOPS CONFIG_NET_L2_PPP_MAX_NACK_LOOPS | 
 |  | 
 | struct ppp_context *ppp_fsm_ctx(struct ppp_fsm *fsm) | 
 | { | 
 | 	if (fsm->protocol == PPP_LCP) { | 
 | 		return CONTAINER_OF(fsm, struct ppp_context, lcp.fsm); | 
 | #if defined(CONFIG_NET_IPV4) | 
 | 	} else if (fsm->protocol == PPP_IPCP) { | 
 | 		return CONTAINER_OF(fsm, struct ppp_context, ipcp.fsm); | 
 | #endif | 
 | #if defined(CONFIG_NET_IPV6) | 
 | 	} else if (fsm->protocol == PPP_IPV6CP) { | 
 | 		return CONTAINER_OF(fsm, struct ppp_context, ipv6cp.fsm); | 
 | #endif | 
 | #if defined(CONFIG_NET_L2_PPP_PAP) | 
 | 	} else if (fsm->protocol == PPP_PAP) { | 
 | 		return CONTAINER_OF(fsm, struct ppp_context, pap.fsm); | 
 | #endif | 
 | 	} | 
 |  | 
 | 	return NULL; | 
 | } | 
 |  | 
 | struct net_if *ppp_fsm_iface(struct ppp_fsm *fsm) | 
 | { | 
 | 	struct ppp_context *ctx = ppp_fsm_ctx(fsm); | 
 |  | 
 | 	NET_ASSERT(ctx->iface); | 
 |  | 
 | 	return ctx->iface; | 
 | } | 
 |  | 
 | static void fsm_send_configure_req(struct ppp_fsm *fsm, bool retransmit) | 
 | { | 
 | 	struct net_pkt *pkt = NULL; | 
 |  | 
 | 	if (fsm->state != PPP_ACK_RECEIVED && | 
 | 	    fsm->state != PPP_ACK_SENT && | 
 | 	    fsm->state != PPP_REQUEST_SENT) { | 
 | 		/* If we are not negotiating options, then reset them */ | 
 | 		if (fsm->cb.config_info_reset) { | 
 | 			fsm->cb.config_info_reset(fsm); | 
 | 		} | 
 |  | 
 | 		fsm->recv_nack_loops = 0; | 
 | 		fsm->nack_loops = 0; | 
 | 	} | 
 |  | 
 | 	if (!retransmit) { | 
 | 		fsm->retransmits = MAX_CONFIGURE_REQ; | 
 | 		fsm->req_id = ++fsm->id; | 
 | 	} | 
 |  | 
 | 	fsm->ack_received = false; | 
 |  | 
 | 	if (fsm->cb.config_info_add) { | 
 | 		pkt = fsm->cb.config_info_add(fsm); | 
 | 	} | 
 |  | 
 | 	NET_DBG("[%s/%p] Sending %s (%d) id %d to peer while in %s (%d)", | 
 | 		fsm->name, fsm, ppp_pkt_type2str(PPP_CONFIGURE_REQ), | 
 | 		PPP_CONFIGURE_REQ, fsm->req_id, ppp_state_str(fsm->state), | 
 | 		fsm->state); | 
 |  | 
 | 	(void)ppp_send_pkt(fsm, NULL, PPP_CONFIGURE_REQ, fsm->req_id, | 
 | 			   pkt, pkt ? net_pkt_get_len(pkt) : 0); | 
 |  | 
 | 	fsm->retransmits--; | 
 |  | 
 | 	(void)k_work_reschedule(&fsm->timer, FSM_TIMEOUT); | 
 | } | 
 |  | 
 | static void ppp_fsm_timeout(struct k_work *work) | 
 | { | 
 | 	struct k_work_delayable *dwork = k_work_delayable_from_work(work); | 
 | 	struct ppp_fsm *fsm = CONTAINER_OF(dwork, struct ppp_fsm, timer); | 
 |  | 
 | 	NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm, | 
 | 		ppp_state_str(fsm->state), fsm->state); | 
 |  | 
 | 	switch (fsm->state) { | 
 | 	case PPP_ACK_RECEIVED: | 
 | 	case PPP_ACK_SENT: | 
 | 	case PPP_REQUEST_SENT: | 
 | 		if (fsm->retransmits <= 0) { | 
 | 			NET_DBG("[%s/%p] %s retransmit limit %d reached", | 
 | 				fsm->name, fsm, | 
 | 				ppp_pkt_type2str(PPP_CONFIGURE_REQ), | 
 | 				fsm->retransmits); | 
 |  | 
 | 			ppp_change_state(fsm, PPP_STOPPED); | 
 |  | 
 | 			if (fsm->cb.finished) { | 
 | 				fsm->cb.finished(fsm); | 
 | 			} | 
 | 		} else { | 
 | 			if (fsm->cb.retransmit) { | 
 | 				fsm->cb.retransmit(fsm); | 
 | 			} | 
 |  | 
 | 			fsm_send_configure_req(fsm, true); | 
 |  | 
 | 			if (fsm->state == PPP_ACK_RECEIVED) { | 
 | 				ppp_change_state(fsm, PPP_REQUEST_SENT); | 
 | 			} | 
 | 		} | 
 |  | 
 | 		break; | 
 |  | 
 | 	case PPP_CLOSING: | 
 | 	case PPP_STOPPING: | 
 | 		if (fsm->retransmits <= 0) { | 
 | 			ppp_change_state(fsm, | 
 | 					 fsm->state == PPP_CLOSING ? | 
 | 					 PPP_CLOSED : PPP_STOPPED); | 
 |  | 
 | 			if (fsm->cb.finished) { | 
 | 				fsm->cb.finished(fsm); | 
 | 			} | 
 | 		} else { | 
 | 			fsm->req_id = ++fsm->id; | 
 |  | 
 | 			ppp_send_pkt(fsm, NULL, PPP_TERMINATE_REQ, fsm->req_id, | 
 | 				     fsm->terminate_reason, | 
 | 				     strlen(fsm->terminate_reason)); | 
 |  | 
 | 			fsm->retransmits--; | 
 |  | 
 | 			(void)k_work_reschedule(&fsm->timer, FSM_TIMEOUT); | 
 | 		} | 
 |  | 
 | 		break; | 
 |  | 
 | 	default: | 
 | 		NET_DBG("[%s/%p] %s state %s (%d)", fsm->name, fsm, "Invalid", | 
 | 			ppp_state_str(fsm->state), fsm->state); | 
 | 		break; | 
 | 	} | 
 | } | 
 |  | 
 | void ppp_fsm_init(struct ppp_fsm *fsm, uint16_t protocol) | 
 | { | 
 | 	fsm->protocol = protocol; | 
 | 	fsm->state = PPP_INITIAL; | 
 | 	fsm->flags = 0U; | 
 |  | 
 | 	k_work_init_delayable(&fsm->timer, ppp_fsm_timeout); | 
 | } | 
 |  | 
 | static void fsm_down(struct ppp_fsm *fsm) | 
 | { | 
 | 	size_t i; | 
 |  | 
 | 	for (i = 0; i < fsm->my_options.count; i++) { | 
 | 		fsm->my_options.data[i].flags = 0; | 
 | 	} | 
 |  | 
 | 	if (fsm->cb.down) { | 
 | 		fsm->cb.down(fsm); | 
 | 	} | 
 | } | 
 |  | 
 | static void terminate(struct ppp_fsm *fsm, enum ppp_state next_state) | 
 | { | 
 | 	if (fsm->state != PPP_OPENED) { | 
 | 		k_work_cancel_delayable(&fsm->timer); | 
 | 	} else { | 
 | 		fsm_down(fsm); | 
 | 	} | 
 |  | 
 | 	fsm->retransmits = MAX_TERMINATE_REQ; | 
 | 	fsm->req_id = ++fsm->id; | 
 |  | 
 | 	(void)ppp_send_pkt(fsm, NULL, PPP_TERMINATE_REQ, fsm->req_id, | 
 | 			   fsm->terminate_reason, | 
 | 			   strlen(fsm->terminate_reason)); | 
 |  | 
 | 	if (fsm->retransmits == 0) { | 
 | 		ppp_change_state(fsm, next_state); | 
 |  | 
 | 		if (fsm->cb.finished) { | 
 | 			fsm->cb.finished(fsm); | 
 | 		} | 
 |  | 
 | 		return; | 
 | 	} | 
 |  | 
 | 	(void)k_work_reschedule(&fsm->timer, FSM_TIMEOUT); | 
 |  | 
 | 	fsm->retransmits--; | 
 |  | 
 | 	ppp_change_state(fsm, next_state); | 
 | } | 
 |  | 
 | void ppp_fsm_close(struct ppp_fsm *fsm, const uint8_t *reason) | 
 | { | 
 | 	NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm, | 
 | 		ppp_state_str(fsm->state), fsm->state); | 
 |  | 
 | 	switch (fsm->state) { | 
 | 	case PPP_ACK_RECEIVED: | 
 | 	case PPP_ACK_SENT: | 
 | 	case PPP_OPENED: | 
 | 	case PPP_REQUEST_SENT: | 
 | 		if (reason) { | 
 | 			int limit_len = sizeof(fsm->terminate_reason) - 1; | 
 |  | 
 | 			strncpy(fsm->terminate_reason, reason, limit_len); | 
 | 			fsm->terminate_reason[limit_len] = '\0'; | 
 | 		} | 
 |  | 
 | 		terminate(fsm, PPP_CLOSING); | 
 | 		break; | 
 |  | 
 | 	case PPP_INITIAL: | 
 | 	case PPP_STARTING: | 
 | 		ppp_change_state(fsm, PPP_INITIAL); | 
 | 		break; | 
 |  | 
 | 	case PPP_STOPPED: | 
 | 		ppp_change_state(fsm, PPP_CLOSED); | 
 | 		break; | 
 |  | 
 | 	case PPP_STOPPING: | 
 | 		ppp_change_state(fsm, PPP_CLOSING); | 
 | 		break; | 
 |  | 
 | 	default: | 
 | 		NET_DBG("[%s/%p] %s state %s (%d)", fsm->name, fsm, "Invalid", | 
 | 			ppp_state_str(fsm->state), fsm->state); | 
 | 		break; | 
 | 	} | 
 | } | 
 |  | 
 | void ppp_fsm_lower_down(struct ppp_fsm *fsm) | 
 | { | 
 | 	NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm, | 
 | 		ppp_state_str(fsm->state), fsm->state); | 
 |  | 
 | 	switch (fsm->state) { | 
 | 	case PPP_ACK_RECEIVED: | 
 | 	case PPP_ACK_SENT: | 
 | 	case PPP_REQUEST_SENT: | 
 | 	case PPP_STOPPING: | 
 | 		ppp_change_state(fsm, PPP_STARTING); | 
 | 		k_work_cancel_delayable(&fsm->timer); | 
 | 		break; | 
 |  | 
 | 	case PPP_CLOSED: | 
 | 		ppp_change_state(fsm, PPP_INITIAL); | 
 | 		break; | 
 |  | 
 | 	case PPP_CLOSING: | 
 | 		ppp_change_state(fsm, PPP_INITIAL); | 
 | 		k_work_cancel_delayable(&fsm->timer); | 
 | 		break; | 
 |  | 
 | 	case PPP_OPENED: | 
 | 		ppp_change_state(fsm, PPP_STARTING); | 
 | 		fsm_down(fsm); | 
 |  | 
 | 		break; | 
 |  | 
 | 	case PPP_STOPPED: | 
 | 		ppp_change_state(fsm, PPP_STARTING); | 
 | 		if (fsm->cb.starting) { | 
 | 			fsm->cb.starting(fsm); | 
 | 		} | 
 |  | 
 | 		break; | 
 |  | 
 | 	default: | 
 | 		NET_DBG("[%s/%p] %s state %s (%d)", fsm->name, fsm, "Invalid", | 
 | 			ppp_state_str(fsm->state), fsm->state); | 
 | 		break; | 
 | 	} | 
 | } | 
 |  | 
 | void ppp_fsm_lower_up(struct ppp_fsm *fsm) | 
 | { | 
 | 	NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm, | 
 | 		ppp_state_str(fsm->state), fsm->state); | 
 |  | 
 | 	switch (fsm->state) { | 
 | 	case PPP_CLOSED: | 
 | 		break; | 
 |  | 
 | 	case PPP_INITIAL: | 
 | 		ppp_change_state(fsm, PPP_CLOSED); | 
 | 		break; | 
 |  | 
 | 	case PPP_STARTING: | 
 | 		fsm_send_configure_req(fsm, false); | 
 | 		ppp_change_state(fsm, PPP_REQUEST_SENT); | 
 |  | 
 | 		break; | 
 |  | 
 | 	default: | 
 | 		NET_DBG("[%s/%p] %s state %s (%d)", fsm->name, fsm, "Invalid", | 
 | 			ppp_state_str(fsm->state), fsm->state); | 
 | 		break; | 
 | 	} | 
 | } | 
 |  | 
 | void ppp_fsm_open(struct ppp_fsm *fsm) | 
 | { | 
 | 	NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm, | 
 | 		ppp_state_str(fsm->state), fsm->state); | 
 |  | 
 | 	switch (fsm->state) { | 
 | 	case PPP_CLOSED: | 
 | 		ppp_change_state(fsm, PPP_REQUEST_SENT); | 
 | 		fsm_send_configure_req(fsm, false); | 
 | 		break; | 
 |  | 
 | 	case PPP_CLOSING: | 
 | 		ppp_change_state(fsm, PPP_STOPPING); | 
 | 		if (fsm->flags & FSM_RESTART) { | 
 | 			ppp_fsm_lower_down(fsm); | 
 | 			ppp_fsm_lower_up(fsm); | 
 | 		} | 
 |  | 
 | 		break; | 
 |  | 
 | 	case PPP_INITIAL: | 
 | 		ppp_change_state(fsm, PPP_STARTING); | 
 | 		if (fsm->cb.starting) { | 
 | 			fsm->cb.starting(fsm); | 
 | 		} | 
 |  | 
 | 		break; | 
 |  | 
 | 	case PPP_OPENED: | 
 | 	case PPP_STOPPED: | 
 | 		if (fsm->flags & FSM_RESTART) { | 
 | 			ppp_fsm_lower_down(fsm); | 
 | 			ppp_fsm_lower_up(fsm); | 
 | 		} | 
 |  | 
 | 		break; | 
 |  | 
 | 	default: | 
 | 		NET_DBG("[%s/%p] %s state %s (%d)", fsm->name, fsm, "Invalid", | 
 | 			ppp_state_str(fsm->state), fsm->state); | 
 | 		break; | 
 | 	} | 
 | } | 
 |  | 
 | int ppp_send_pkt(struct ppp_fsm *fsm, struct net_if *iface, | 
 | 		 enum ppp_packet_type type, uint8_t id, | 
 | 		 void *data, uint32_t data_len) | 
 | { | 
 | 	/* Note that the data parameter is the received PPP packet if | 
 | 	 * we want to send PROTOCOL or CODE reject packet. | 
 | 	 */ | 
 | 	struct net_pkt *req_pkt = data; | 
 | 	uint16_t protocol = 0; | 
 | 	size_t len = 0; | 
 | 	struct ppp_packet ppp; | 
 | 	struct net_pkt *pkt = NULL; | 
 | 	int ret; | 
 |  | 
 | 	if (!iface) { | 
 | 		if (!fsm) { | 
 | 			return -ENOENT; | 
 | 		} | 
 |  | 
 | 		iface = ppp_fsm_iface(fsm); | 
 | 	} | 
 |  | 
 | 	if (!net_if_is_carrier_ok(iface)) { | 
 | 		return -ENETDOWN; | 
 | 	} | 
 |  | 
 | 	if (fsm) { | 
 | 		protocol = fsm->protocol; | 
 | 	} | 
 |  | 
 | 	switch (type) { | 
 | 	case PPP_CODE_REJ: { | 
 | 		struct ppp_context *ctx = ppp_fsm_ctx(fsm); | 
 |  | 
 | 		len = net_pkt_get_len(req_pkt); | 
 | 		len = MIN(len, ctx->lcp.my_options.mru); | 
 | 		break; | 
 | 	} | 
 |  | 
 | 	case PPP_CONFIGURE_ACK: | 
 | 	case PPP_CONFIGURE_NACK: | 
 | 	case PPP_CONFIGURE_REJ: | 
 | 	case PPP_CONFIGURE_REQ: | 
 | 		pkt = data; | 
 | 		/* 2 + 1 + 1 (configure-[req|ack|nack|rej]) + | 
 | 		 * data_len (options) | 
 | 		 */ | 
 | 		len = sizeof(ppp) + data_len; | 
 | 		break; | 
 |  | 
 | 	case PPP_DISCARD_REQ: | 
 | 		break; | 
 |  | 
 | 	case PPP_ECHO_REQ: | 
 | 		len = sizeof(ppp) + sizeof(uint32_t) + data_len; | 
 | 		break; | 
 |  | 
 | 	case PPP_ECHO_REPLY: | 
 | 		len = sizeof(ppp) + net_pkt_remaining_data(req_pkt); | 
 | 		break; | 
 |  | 
 | 	case PPP_PROTOCOL_REJ: | 
 | 		len = sizeof(ppp) + sizeof(uint16_t) + | 
 | 			net_pkt_remaining_data(req_pkt); | 
 | 		protocol = PPP_LCP; | 
 | 		break; | 
 |  | 
 | 	case PPP_TERMINATE_REQ: | 
 | 	case PPP_TERMINATE_ACK: | 
 | 		len = sizeof(ppp); | 
 | 		break; | 
 |  | 
 | 	default: | 
 | 		break; | 
 | 	} | 
 |  | 
 | 	if (len < sizeof(ppp)) { | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	ppp.code = type; | 
 | 	ppp.id = id; | 
 | 	ppp.length = htons(len); | 
 |  | 
 | 	if (!pkt) { | 
 | 		pkt = net_pkt_alloc_with_buffer(iface, | 
 | 						sizeof(uint16_t) + len, | 
 | 						AF_UNSPEC, 0, | 
 | 						PPP_BUF_ALLOC_TIMEOUT); | 
 | 		if (!pkt) { | 
 | 			goto out_of_mem; | 
 | 		} | 
 | 	} else { | 
 | 		struct net_buf *buf; | 
 |  | 
 | 		buf = net_pkt_get_reserve_tx_data(sizeof(uint16_t) + len, | 
 | 						  PPP_BUF_ALLOC_TIMEOUT); | 
 | 		if (!buf) { | 
 | 			LOG_ERR("failed to allocate buffer"); | 
 | 			goto out_of_mem; | 
 | 		} | 
 |  | 
 | 		net_pkt_frag_insert(pkt, buf); | 
 | 		net_pkt_cursor_init(pkt); | 
 | 	} | 
 |  | 
 | 	ret = net_pkt_write_be16(pkt, protocol); | 
 | 	if (ret < 0) { | 
 | 		goto out_of_mem; | 
 | 	} | 
 |  | 
 | 	ret = net_pkt_write(pkt, &ppp, sizeof(ppp)); | 
 | 	if (ret < 0) { | 
 | 		goto out_of_mem; | 
 | 	} | 
 |  | 
 | 	if (type == PPP_CODE_REJ) { | 
 | 		if (!req_pkt) { | 
 | 			goto out_of_mem; | 
 | 		} | 
 |  | 
 | 		net_pkt_cursor_init(req_pkt); | 
 | 		net_pkt_copy(pkt, req_pkt, len); | 
 |  | 
 | 	} else if (type == PPP_ECHO_REQ) { | 
 | 		struct ppp_context *ctx = CONTAINER_OF(fsm, struct ppp_context, | 
 | 						       lcp.fsm); | 
 | 		if (ctx->lcp.magic) { | 
 | 			ctx->lcp.magic = sys_rand32_get(); | 
 | 		} | 
 |  | 
 | 		ret = net_pkt_write_be32(pkt, ctx->lcp.magic); | 
 | 		if (ret < 0) { | 
 | 			goto out_of_mem; | 
 | 		} | 
 |  | 
 | 		data_len = MIN(data_len, ctx->lcp.my_options.mru); | 
 | 		if (data_len > 0) { | 
 | 			if (data_len == sizeof(uint32_t)) { | 
 | 				ret = net_pkt_write_be32(pkt, | 
 | 						       POINTER_TO_UINT(data)); | 
 | 			} else { | 
 | 				ret = net_pkt_write(pkt, data, data_len); | 
 | 			} | 
 |  | 
 | 			if (ret < 0) { | 
 | 				goto out_of_mem; | 
 | 			} | 
 | 		} | 
 | 	} else if (type == PPP_ECHO_REPLY) { | 
 | 		net_pkt_copy(pkt, req_pkt, len); | 
 | 	} else if (type == PPP_PROTOCOL_REJ) { | 
 | 		net_pkt_cursor_init(req_pkt); | 
 | 		net_pkt_copy(pkt, req_pkt, len); | 
 | 	} | 
 |  | 
 | 	NET_DBG("[%s/%p] Sending %zd bytes pkt %p (options len %d)", | 
 | 		fsm ? fsm->name : "?", fsm, net_pkt_get_len(pkt), pkt, | 
 | 		data_len); | 
 |  | 
 | 	net_pkt_set_ppp(pkt, true); | 
 |  | 
 | 	if (fsm) { | 
 | 		/* Do not call net_send_data() directly in order to make this | 
 | 		 * thread run before the sending happens. If we call the | 
 | 		 * net_send_data() from this thread, then in fast link (like | 
 | 		 * when running inside QEMU) the reply might arrive before we | 
 | 		 * have returned from this function. That is bad because the | 
 | 		 * fsm would be in wrong state and the received pkt is dropped. | 
 | 		 */ | 
 | 		ppp_queue_pkt(pkt); | 
 | 	} else { | 
 | 		ret = net_send_data(pkt); | 
 | 		if (ret < 0) { | 
 | 			net_pkt_unref(pkt); | 
 | 		} | 
 | 	} | 
 |  | 
 | 	return 0; | 
 |  | 
 | out_of_mem: | 
 | 	if (pkt) { | 
 | 		net_pkt_unref(pkt); | 
 | 	} | 
 |  | 
 | 	return -ENOMEM; | 
 | } | 
 |  | 
 | static enum net_verdict fsm_recv_configure_req(struct ppp_fsm *fsm, | 
 | 					       uint8_t id, | 
 | 					       struct net_pkt *pkt, | 
 | 					       uint16_t remaining_len) | 
 | { | 
 | 	struct net_pkt *out = NULL; | 
 | 	int len = 0; | 
 | 	enum ppp_packet_type code; | 
 |  | 
 | 	NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm, | 
 | 		ppp_state_str(fsm->state), fsm->state); | 
 |  | 
 | 	switch (fsm->state) { | 
 | 	case PPP_ACK_SENT: | 
 | 	case PPP_ACK_RECEIVED: | 
 | 		break; | 
 |  | 
 | 	case PPP_CLOSED: | 
 | 		(void)ppp_send_pkt(fsm, net_pkt_iface(pkt), PPP_TERMINATE_ACK, | 
 | 				   id, NULL, 0); | 
 | 		return NET_OK; | 
 |  | 
 | 	case PPP_CLOSING: | 
 | 	case PPP_STOPPING: | 
 | 		return NET_OK; | 
 |  | 
 | 	case PPP_OPENED: | 
 | 		fsm_down(fsm); | 
 |  | 
 | 		fsm_send_configure_req(fsm, false); | 
 | 		ppp_change_state(fsm, PPP_REQUEST_SENT); | 
 | 		break; | 
 |  | 
 | 	case PPP_REQUEST_SENT: | 
 | 		/* Received request while waiting ACK */ | 
 | 		break; | 
 |  | 
 | 	case PPP_STOPPED: | 
 | 		fsm_send_configure_req(fsm, false); | 
 | 		ppp_change_state(fsm, PPP_REQUEST_SENT); | 
 | 		break; | 
 |  | 
 | 	default: | 
 | 		NET_DBG("[%s/%p] %s state %s (%d)", fsm->name, fsm, "Invalid", | 
 | 			ppp_state_str(fsm->state), fsm->state); | 
 | 		return NET_DROP; | 
 | 	} | 
 |  | 
 | 	out = net_pkt_alloc_with_buffer(net_pkt_iface(pkt), | 
 | 					sizeof(uint16_t) + sizeof(uint16_t) + | 
 | 						sizeof(uint8_t) + sizeof(uint8_t) + | 
 | 						remaining_len, | 
 | 					AF_UNSPEC, 0, PPP_BUF_ALLOC_TIMEOUT); | 
 | 	if (!out) { | 
 | 		return NET_DROP; | 
 | 	} | 
 |  | 
 | 	net_pkt_cursor_init(out); | 
 |  | 
 | 	if (fsm->cb.config_info_req) { | 
 | 		int ret; | 
 |  | 
 | 		ret = fsm->cb.config_info_req(fsm, pkt, remaining_len, out); | 
 | 		if (ret < 0) { | 
 | 			goto unref_out_pkt; | 
 | 		} | 
 |  | 
 | 		if (fsm->nack_loops >= MAX_NACK_LOOPS && | 
 | 		    ret == PPP_CONFIGURE_NACK) { | 
 | 			ret = PPP_CONFIGURE_REJ; | 
 | 		} | 
 |  | 
 | 		code = ret; | 
 | 		len = net_pkt_get_len(out); | 
 | 	} else if (remaining_len) { | 
 | 		code = PPP_CONFIGURE_REJ; | 
 |  | 
 | 		net_pkt_copy(out, pkt, remaining_len); | 
 | 		len = remaining_len; | 
 | 	} else { | 
 | 		code = PPP_CONFIGURE_ACK; | 
 | 	} | 
 |  | 
 | 	NET_DBG("[%s/%p] Sending %s (%d) id %d to peer while in %s (%d)", | 
 | 		fsm->name, fsm, ppp_pkt_type2str(code), code, id, | 
 | 		ppp_state_str(fsm->state), fsm->state); | 
 |  | 
 | 	(void)ppp_send_pkt(fsm, NULL, code, id, out, len); | 
 |  | 
 | 	if (code == PPP_CONFIGURE_ACK) { | 
 | 		if (fsm->state == PPP_ACK_RECEIVED) { | 
 | 			k_work_cancel_delayable(&fsm->timer); | 
 |  | 
 | 			ppp_change_state(fsm, PPP_OPENED); | 
 |  | 
 | 			if (fsm->cb.up) { | 
 | 				fsm->cb.up(fsm); | 
 | 			} | 
 | 		} else { | 
 | 			ppp_change_state(fsm, PPP_ACK_SENT); | 
 | 		} | 
 |  | 
 | 		fsm->nack_loops = 0; | 
 | 	} else { | 
 | 		if (fsm->state != PPP_ACK_RECEIVED) { | 
 | 			ppp_change_state(fsm, PPP_REQUEST_SENT); | 
 | 		} | 
 |  | 
 | 		if (code == PPP_CONFIGURE_NACK) { | 
 | 			fsm->nack_loops++; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	return NET_OK; | 
 |  | 
 | unref_out_pkt: | 
 | 	net_pkt_unref(out); | 
 |  | 
 | 	return NET_DROP; | 
 | } | 
 |  | 
 | static enum net_verdict fsm_recv_configure_ack(struct ppp_fsm *fsm, uint8_t id, | 
 | 					       struct net_pkt *pkt, | 
 | 					       uint16_t remaining_len) | 
 | { | 
 | 	NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm, | 
 | 		ppp_state_str(fsm->state), fsm->state); | 
 |  | 
 | 	if (id != fsm->req_id || fsm->ack_received) { | 
 | 		return NET_DROP; | 
 | 	} | 
 |  | 
 | 	if (fsm->cb.config_info_ack) { | 
 | 		if (fsm->cb.config_info_ack(fsm, pkt, remaining_len) < 0) { | 
 | 			NET_DBG("[%s/%p] %s %s received", fsm->name, fsm, | 
 | 				"Invalid", | 
 | 				ppp_pkt_type2str(PPP_CONFIGURE_ACK)); | 
 | 			return NET_DROP; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	fsm->ack_received = true; | 
 | 	fsm->recv_nack_loops = 0; | 
 |  | 
 | 	switch (fsm->state) { | 
 | 	case PPP_ACK_RECEIVED: | 
 | 		k_work_cancel_delayable(&fsm->timer); | 
 | 		fsm_send_configure_req(fsm, false); | 
 | 		ppp_change_state(fsm, PPP_REQUEST_SENT); | 
 | 		break; | 
 |  | 
 | 	case PPP_ACK_SENT: | 
 | 		k_work_cancel_delayable(&fsm->timer); | 
 | 		ppp_change_state(fsm, PPP_OPENED); | 
 | 		fsm->retransmits = MAX_CONFIGURE_REQ; | 
 | 		if (fsm->cb.up) { | 
 | 			fsm->cb.up(fsm); | 
 | 		} | 
 |  | 
 | 		break; | 
 |  | 
 | 	case PPP_CLOSED: | 
 | 	case PPP_STOPPED: | 
 | 		(void)ppp_send_pkt(fsm, net_pkt_iface(pkt), PPP_TERMINATE_ACK, | 
 | 				   id, NULL, 0); | 
 | 		break; | 
 |  | 
 | 	case PPP_OPENED: | 
 | 		fsm_down(fsm); | 
 |  | 
 | 		fsm_send_configure_req(fsm, false); | 
 | 		ppp_change_state(fsm, PPP_REQUEST_SENT); | 
 | 		break; | 
 |  | 
 | 	case PPP_REQUEST_SENT: | 
 | 		ppp_change_state(fsm, PPP_ACK_RECEIVED); | 
 | 		fsm->retransmits = MAX_CONFIGURE_REQ; | 
 | 		break; | 
 |  | 
 | 	default: | 
 | 		NET_DBG("[%s/%p] %s state %s (%d)", fsm->name, fsm, "Invalid", | 
 | 			ppp_state_str(fsm->state), fsm->state); | 
 | 		return NET_DROP; | 
 | 	} | 
 |  | 
 | 	return NET_OK; | 
 | } | 
 |  | 
 | static enum net_verdict fsm_recv_configure_nack_rej(struct ppp_fsm *fsm, | 
 | 						    enum ppp_packet_type code, | 
 | 						    uint8_t id, | 
 | 						    struct net_pkt *pkt, | 
 | 						    uint16_t length) | 
 | { | 
 | 	bool ret = false; | 
 |  | 
 | 	NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm, | 
 | 		ppp_state_str(fsm->state), fsm->state); | 
 |  | 
 | 	if (id != fsm->req_id || fsm->ack_received) { | 
 | 		return NET_DROP; | 
 | 	} | 
 |  | 
 | 	if (code == PPP_CONFIGURE_NACK) { | 
 | 		bool rejected = false; | 
 |  | 
 | 		fsm->recv_nack_loops++; | 
 |  | 
 | 		if (fsm->recv_nack_loops >= MAX_NACK_LOOPS) { | 
 | 			rejected = true; | 
 | 		} | 
 |  | 
 | 		if (fsm->cb.config_info_nack) { | 
 | 			int err; | 
 |  | 
 | 			err = fsm->cb.config_info_nack(fsm, pkt, length, | 
 | 						       rejected); | 
 | 			if (err < 0) { | 
 | 				NET_DBG("[%s/%p] %s failed (%d)", | 
 | 					fsm->name, fsm, "Nack", err); | 
 | 			} else { | 
 | 				ret = true; | 
 | 			} | 
 | 		} | 
 |  | 
 | 		if (!ret) { | 
 | 			NET_DBG("[%s/%p] %s %s (id %d)", fsm->name, fsm, | 
 | 				"Invalid", ppp_pkt_type2str(code), id); | 
 | 			return NET_DROP; | 
 | 		} | 
 | 	} else { | 
 | 		fsm->recv_nack_loops = 0; | 
 |  | 
 | 		if (fsm->cb.config_info_rej) { | 
 | 			int err; | 
 |  | 
 | 			err = fsm->cb.config_info_rej(fsm, pkt, length); | 
 | 			if (err < 0) { | 
 | 				NET_DBG("[%s/%p] %s failed (%d)", | 
 | 					fsm->name, fsm, "Reject", err); | 
 | 			} else { | 
 | 				ret = true; | 
 | 			} | 
 | 		} | 
 |  | 
 | 		if (!ret) { | 
 | 			NET_DBG("[%s/%p] %s %s (id %d)", fsm->name, fsm, | 
 | 				"Invalid", ppp_pkt_type2str(code), id); | 
 | 			return NET_DROP; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	fsm->ack_received = true; | 
 |  | 
 | 	switch (fsm->state) { | 
 | 	case PPP_ACK_RECEIVED: | 
 | 		k_work_cancel_delayable(&fsm->timer); | 
 | 		fsm_send_configure_req(fsm, false); | 
 | 		ppp_change_state(fsm, PPP_REQUEST_SENT); | 
 | 		break; | 
 |  | 
 | 	case PPP_ACK_SENT: | 
 | 	case PPP_REQUEST_SENT: | 
 | 		k_work_cancel_delayable(&fsm->timer); | 
 | 		fsm_send_configure_req(fsm, false); | 
 | 		break; | 
 |  | 
 | 	case PPP_CLOSED: | 
 | 	case PPP_STOPPED: | 
 | 		(void)ppp_send_pkt(fsm, net_pkt_iface(pkt), PPP_TERMINATE_ACK, | 
 | 				   id, NULL, 0); | 
 | 		break; | 
 |  | 
 | 	case PPP_OPENED: | 
 | 		fsm_down(fsm); | 
 |  | 
 | 		fsm_send_configure_req(fsm, false); | 
 | 		ppp_change_state(fsm, PPP_REQUEST_SENT); | 
 | 		break; | 
 |  | 
 | 	default: | 
 | 		NET_DBG("[%s/%p] %s state %s (%d)", fsm->name, fsm, "Invalid", | 
 | 			ppp_state_str(fsm->state), fsm->state); | 
 | 		return NET_DROP; | 
 | 	} | 
 |  | 
 | 	return NET_OK; | 
 | } | 
 |  | 
 | static enum net_verdict fsm_recv_terminate_req(struct ppp_fsm *fsm, uint8_t id, | 
 | 					       struct net_pkt *pkt, | 
 | 					       uint16_t length) | 
 | { | 
 | 	NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm, | 
 | 		ppp_state_str(fsm->state), fsm->state); | 
 |  | 
 | 	switch (fsm->state) { | 
 | 	case PPP_ACK_RECEIVED: | 
 | 	case PPP_ACK_SENT: | 
 | 		ppp_change_state(fsm, PPP_REQUEST_SENT); | 
 | 		break; | 
 |  | 
 | 	case PPP_OPENED: | 
 | 		if (length > 0) { | 
 | 			net_pkt_read(pkt, fsm->terminate_reason, | 
 | 				     MIN(length, | 
 | 					 sizeof(fsm->terminate_reason) - 1)); | 
 |  | 
 | 			NET_DBG("[%s/%p] %s (%s)", | 
 | 				fsm->name, fsm, "Terminated by peer", | 
 | 				fsm->terminate_reason); | 
 | 		} else { | 
 | 			NET_DBG("[%s/%p] Terminated by peer", | 
 | 				fsm->name, fsm); | 
 | 		} | 
 |  | 
 | 		fsm->retransmits = 0; | 
 | 		ppp_change_state(fsm, PPP_STOPPING); | 
 |  | 
 | 		fsm_down(fsm); | 
 |  | 
 | 		(void)k_work_reschedule(&fsm->timer, FSM_TIMEOUT); | 
 | 		break; | 
 |  | 
 | 	default: | 
 | 		NET_DBG("[%s/%p] %s state %s (%d)", fsm->name, fsm, "Invalid", | 
 | 			ppp_state_str(fsm->state), fsm->state); | 
 | 		return NET_DROP; | 
 | 	} | 
 |  | 
 | 	(void)ppp_send_pkt(fsm, net_pkt_iface(pkt), PPP_TERMINATE_ACK, id, | 
 | 			   NULL, 0); | 
 |  | 
 | 	return NET_OK; | 
 | } | 
 |  | 
 | static enum net_verdict fsm_recv_terminate_ack(struct ppp_fsm *fsm, uint8_t id, | 
 | 					       struct net_pkt *pkt, | 
 | 					       uint16_t length) | 
 | { | 
 | 	enum ppp_state new_state; | 
 |  | 
 | 	NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm, | 
 | 		ppp_state_str(fsm->state), fsm->state); | 
 |  | 
 | 	switch (fsm->state) { | 
 | 	case PPP_CLOSING: | 
 | 		new_state = PPP_CLOSED; | 
 | 		goto stopped; | 
 |  | 
 | 	case PPP_OPENED: | 
 | 		fsm_down(fsm); | 
 |  | 
 | 		fsm_send_configure_req(fsm, false); | 
 | 		ppp_change_state(fsm, PPP_REQUEST_SENT); | 
 | 		break; | 
 |  | 
 | 	case PPP_STOPPING: | 
 | 		new_state = PPP_STOPPED; | 
 | 		goto stopped; | 
 |  | 
 | 	case PPP_ACK_RECEIVED: | 
 | 		ppp_change_state(fsm, PPP_REQUEST_SENT); | 
 | 		break; | 
 |  | 
 | 	default: | 
 | 		NET_DBG("[%s/%p] %s state %s (%d)", fsm->name, fsm, "Invalid", | 
 | 			ppp_state_str(fsm->state), fsm->state); | 
 | 		return NET_DROP; | 
 | 	} | 
 |  | 
 | 	return NET_OK; | 
 |  | 
 | stopped: | 
 | 	k_work_cancel_delayable(&fsm->timer); | 
 | 	ppp_change_state(fsm, new_state); | 
 |  | 
 | 	if (fsm->cb.finished) { | 
 | 		fsm->cb.finished(fsm); | 
 | 	} | 
 |  | 
 | 	return NET_OK; | 
 | } | 
 |  | 
 | static enum net_verdict fsm_recv_code_rej(struct ppp_fsm *fsm, | 
 | 					  struct net_pkt *pkt) | 
 | { | 
 | 	uint8_t code, id; | 
 | 	int ret; | 
 |  | 
 | 	NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm, | 
 | 		ppp_state_str(fsm->state), fsm->state); | 
 |  | 
 | 	ret = net_pkt_read_u8(pkt, &code); | 
 | 	if (ret < 0) { | 
 | 		return NET_DROP; | 
 | 	} | 
 |  | 
 | 	ret = net_pkt_read_u8(pkt, &id); | 
 | 	if (ret < 0) { | 
 | 		return NET_DROP; | 
 | 	} | 
 |  | 
 | 	NET_DBG("[%s/%p] Received Code-Rej code %d id %d", fsm->name, fsm, | 
 | 		code, id); | 
 |  | 
 | 	if (fsm->state == PPP_ACK_RECEIVED) { | 
 | 		ppp_change_state(fsm, PPP_REQUEST_SENT); | 
 | 	} | 
 |  | 
 | 	return NET_OK; | 
 | } | 
 |  | 
 | void ppp_fsm_proto_reject(struct ppp_fsm *fsm) | 
 | { | 
 | 	NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm, | 
 | 		ppp_state_str(fsm->state), fsm->state); | 
 |  | 
 | 	switch (fsm->state) { | 
 | 	case PPP_ACK_RECEIVED: | 
 | 	case PPP_ACK_SENT: | 
 | 	case PPP_STOPPING: | 
 | 	case PPP_REQUEST_SENT: | 
 | 		k_work_cancel_delayable(&fsm->timer); | 
 | 		ppp_change_state(fsm, PPP_STOPPED); | 
 | 		if (fsm->cb.finished) { | 
 | 			fsm->cb.finished(fsm); | 
 | 		} | 
 |  | 
 | 		break; | 
 |  | 
 | 	case PPP_CLOSED: | 
 | 		ppp_change_state(fsm, PPP_CLOSED); | 
 | 		if (fsm->cb.finished) { | 
 | 			fsm->cb.finished(fsm); | 
 | 		} | 
 |  | 
 | 		break; | 
 |  | 
 | 	case PPP_CLOSING: | 
 | 		k_work_cancel_delayable(&fsm->timer); | 
 | 		ppp_change_state(fsm, PPP_CLOSED); | 
 | 		if (fsm->cb.finished) { | 
 | 			fsm->cb.finished(fsm); | 
 | 		} | 
 |  | 
 | 		break; | 
 |  | 
 | 	case PPP_OPENED: | 
 | 		terminate(fsm, PPP_STOPPING); | 
 | 		break; | 
 |  | 
 | 	case PPP_STOPPED: | 
 | 		ppp_change_state(fsm, PPP_STOPPED); | 
 | 		if (fsm->cb.finished) { | 
 | 			fsm->cb.finished(fsm); | 
 | 		} | 
 |  | 
 | 		break; | 
 |  | 
 | 	default: | 
 | 		NET_DBG("[%s/%p] %s state %s (%d)", fsm->name, fsm, "Invalid", | 
 | 			ppp_state_str(fsm->state), fsm->state); | 
 | 		break; | 
 | 	} | 
 | } | 
 |  | 
 | enum net_verdict ppp_fsm_input(struct ppp_fsm *fsm, uint16_t proto, | 
 | 			       struct net_pkt *pkt) | 
 | { | 
 | 	uint8_t code, id; | 
 | 	uint16_t length; | 
 | 	int ret; | 
 | 	struct ppp_context *ctx = ppp_fsm_ctx(fsm); | 
 |  | 
 | 	ret = net_pkt_read_u8(pkt, &code); | 
 | 	if (ret < 0) { | 
 | 		NET_DBG("[%s/%p] Cannot read %s (pkt len %zd)", | 
 | 			fsm->name, fsm, "code", net_pkt_get_len(pkt)); | 
 | 		return NET_DROP; | 
 | 	} | 
 |  | 
 | 	ret = net_pkt_read_u8(pkt, &id); | 
 | 	if (ret < 0) { | 
 | 		NET_DBG("[%s/%p] Cannot read %s (pkt len %zd)", | 
 | 			fsm->name, fsm, "id", net_pkt_get_len(pkt)); | 
 | 		return NET_DROP; | 
 | 	} | 
 |  | 
 | 	ret = net_pkt_read_be16(pkt, &length); | 
 | 	if (ret < 0) { | 
 | 		NET_DBG("[%s/%p] Cannot read %s (pkt len %zd)", | 
 | 			fsm->name, fsm, "length", net_pkt_get_len(pkt)); | 
 | 		return NET_DROP; | 
 | 	} | 
 |  | 
 | 	if (length > ctx->lcp.my_options.mru) { | 
 | 		NET_DBG("[%s/%p] Too long msg %d", fsm->name, fsm, length); | 
 | 		return NET_DROP; | 
 | 	} | 
 |  | 
 | 	if (fsm->state == PPP_INITIAL || fsm->state == PPP_STARTING) { | 
 | 		NET_DBG("[%s/%p] Received %s packet in wrong state %s (%d)", | 
 | 			fsm->name, fsm, ppp_proto2str(proto), | 
 | 			ppp_state_str(fsm->state), fsm->state); | 
 | 		return NET_DROP; | 
 | 	} | 
 |  | 
 | 	/* Length will only contain payload/data length */ | 
 | 	length -= sizeof(code) + sizeof(id) + sizeof(length); | 
 |  | 
 | 	NET_DBG("[%s/%p] %s %s (%d) id %d payload len %d", fsm->name, fsm, | 
 | 		ppp_proto2str(proto), ppp_pkt_type2str(code), code, id, | 
 | 		length); | 
 |  | 
 | 	switch (code) { | 
 | 	case PPP_CODE_REJ: | 
 | 		return fsm_recv_code_rej(fsm, pkt); | 
 |  | 
 | 	case PPP_CONFIGURE_ACK: | 
 | 		return fsm_recv_configure_ack(fsm, id, pkt, length); | 
 |  | 
 | 	case PPP_CONFIGURE_NACK: | 
 | 		return fsm_recv_configure_nack_rej(fsm, code, id, pkt, length); | 
 |  | 
 | 	case PPP_CONFIGURE_REQ: | 
 | 		return fsm_recv_configure_req(fsm, id, pkt, length); | 
 |  | 
 | 	case PPP_CONFIGURE_REJ: | 
 | 		return fsm_recv_configure_nack_rej(fsm, code, id, pkt, length); | 
 |  | 
 | 	case PPP_TERMINATE_ACK: | 
 | 		return fsm_recv_terminate_ack(fsm, id, pkt, length); | 
 |  | 
 | 	case PPP_TERMINATE_REQ: | 
 | 		return fsm_recv_terminate_req(fsm, id, pkt, length); | 
 |  | 
 | 	default: | 
 | 		if (fsm->cb.proto_extension) { | 
 | 			enum net_verdict verdict; | 
 |  | 
 | 			verdict = fsm->cb.proto_extension(fsm, code, id, pkt); | 
 | 			if (verdict != NET_DROP) { | 
 | 				return verdict; | 
 | 			} | 
 | 		} | 
 |  | 
 | 		(void)ppp_send_pkt(fsm, net_pkt_iface(pkt), PPP_CODE_REJ, | 
 | 				   id, pkt, 0); | 
 | 	} | 
 |  | 
 | 	return NET_DROP; | 
 | } | 
 |  | 
 | enum net_verdict ppp_fsm_recv_protocol_rej(struct ppp_fsm *fsm, | 
 | 					   uint8_t id, | 
 | 					   struct net_pkt *pkt) | 
 | { | 
 | 	NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm, | 
 | 		ppp_state_str(fsm->state), fsm->state); | 
 |  | 
 | 	return NET_DROP; | 
 | } | 
 |  | 
 | enum net_verdict ppp_fsm_recv_echo_req(struct ppp_fsm *fsm, | 
 | 				       uint8_t id, | 
 | 				       struct net_pkt *pkt) | 
 | { | 
 | 	NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm, | 
 | 		ppp_state_str(fsm->state), fsm->state); | 
 |  | 
 | 	(void)ppp_send_pkt(fsm, net_pkt_iface(pkt), PPP_ECHO_REPLY, | 
 | 		id, pkt, 0); | 
 |  | 
 | 	return NET_OK; | 
 | } | 
 |  | 
 | enum net_verdict ppp_fsm_recv_echo_reply(struct ppp_fsm *fsm, | 
 | 					 uint8_t id, | 
 | 					 struct net_pkt *pkt) | 
 | { | 
 | 	NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm, | 
 | 		ppp_state_str(fsm->state), fsm->state); | 
 |  | 
 | #if defined(CONFIG_NET_SHELL) | 
 | 	struct ppp_context *ctx = CONTAINER_OF(fsm, struct ppp_context, | 
 | 					       lcp.fsm); | 
 | 	if (ctx->shell.echo_reply.cb) { | 
 | 		ctx->shell.echo_reply.cb(ctx->shell.echo_reply.user_data, | 
 | 					 ctx->shell.echo_reply.user_data_len); | 
 | 	} | 
 | #endif /* CONFIG_NET_SHELL */ | 
 |  | 
 | 	return NET_OK; | 
 | } | 
 |  | 
 | enum net_verdict ppp_fsm_recv_discard_req(struct ppp_fsm *fsm, | 
 | 					  uint8_t id, | 
 | 					  struct net_pkt *pkt) | 
 | { | 
 | 	NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm, | 
 | 		ppp_state_str(fsm->state), fsm->state); | 
 |  | 
 | 	return NET_OK; | 
 | } | 
 |  | 
 | void ppp_send_proto_rej(struct net_if *iface, struct net_pkt *pkt, | 
 | 			uint16_t protocol) | 
 | { | 
 | 	uint8_t code, id; | 
 | 	int ret; | 
 |  | 
 | 	ret = net_pkt_read_u8(pkt, &code); | 
 | 	if (ret < 0) { | 
 | 		goto quit; | 
 | 	} | 
 |  | 
 | 	ret = net_pkt_read_u8(pkt, &id); | 
 | 	if (ret < 0) { | 
 | 		goto quit; | 
 | 	} | 
 |  | 
 | 	(void)ppp_send_pkt(NULL, iface, PPP_PROTOCOL_REJ, id, pkt, 0); | 
 |  | 
 | quit: | 
 | 	return; | 
 | } |