Import of the watch repository from Pebble

This commit is contained in:
Matthieu Jeanson 2024-12-12 16:43:03 -08:00 committed by Katharine Berry
commit 3b92768480
10334 changed files with 2564465 additions and 0 deletions

View file

@ -0,0 +1,511 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "console/control_protocol.h"
#include "console/control_protocol_impl.h"
#include "console/pulse2_transport_impl.h"
#include "kernel/events.h"
#include "kernel/util/sleep.h"
#include "services/common/new_timer/new_timer.h"
#include "system/logging.h"
#include "system/passert.h"
#include <util/attributes.h>
#include <util/math.h>
#include <util/net.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#define MAX_CONFIGURE (10)
#define MAX_TERMINATE (2)
#define RESTART_TIMEOUT_MS (150)
#define LCP_HEADER_LEN (sizeof(LCPPacket))
static void prv_on_timeout(void *context);
static void prv_start_timer(PPPControlProtocol *this) {
PBL_ASSERTN(this->state->restart_timer != TIMER_INVALID_ID);
new_timer_start(this->state->restart_timer, RESTART_TIMEOUT_MS,
prv_on_timeout, (void *)this, 0);
}
static void prv_stop_timer(PPPControlProtocol *this) {
new_timer_stop(this->state->restart_timer);
}
static void prv_transition_to(PPPControlProtocol *this,
enum LinkState nextstate) {
if (nextstate == LinkState_Initial ||
nextstate == LinkState_Starting ||
nextstate == LinkState_Closed ||
nextstate == LinkState_Stopped ||
nextstate == LinkState_Opened) {
prv_stop_timer(this);
}
if (nextstate == LinkState_Opened &&
this->state->link_state != LinkState_Opened) {
this->on_this_layer_up(this);
}
if (this->state->link_state == LinkState_Opened &&
nextstate != LinkState_Opened) {
this->on_this_layer_down(this);
}
this->state->link_state = nextstate;
}
static void prv_send_configure_request(PPPControlProtocol *this) {
this->state->restart_count--;
prv_start_timer(this);
// Don't try to be fancy about changing the request identifier only when
// necessary; keep it simple and increment it for every request sent.
uint8_t id = this->state->last_configure_request_id + 1;
this->state->last_configure_request_id = id;
struct LCPPacket *request = pulse_link_send_begin(this->protocol_number);
*request = (struct LCPPacket) {
.code = ControlCode_ConfigureRequest,
.identifier = id,
.length = hton16(LCP_HEADER_LEN),
};
pulse_link_send(request, LCP_HEADER_LEN);
}
static void prv_send_configure_ack(PPPControlProtocol *this,
struct LCPPacket *triggering_packet) {
if (ntoh16(triggering_packet->length) > pulse_link_max_send_size()) {
// Too big to send and truncation will corrupt the packet.
PBL_LOG(LOG_LEVEL_ERROR, "Configure-Request too large to Ack");
return;
}
struct LCPPacket *packet = pulse_link_send_begin(this->protocol_number);
memcpy(packet, triggering_packet, ntoh16(triggering_packet->length));
packet->code = ControlCode_ConfigureAck;
pulse_link_send(packet, ntoh16(triggering_packet->length));
}
static void prv_send_configure_reject(PPPControlProtocol *this,
struct LCPPacket *bad_packet) {
if (ntoh16(bad_packet->length) > pulse_link_max_send_size()) {
// Too big to send and truncation will corrupt the packet.
// There isn't really anything we can do.
PBL_LOG(LOG_LEVEL_ERROR, "Configure-Request too large to Reject");
return;
}
struct LCPPacket *packet = pulse_link_send_begin(this->protocol_number);
memcpy(packet, bad_packet, ntoh16(bad_packet->length));
packet->code = ControlCode_ConfigureReject;
pulse_link_send(packet, ntoh16(bad_packet->length));
}
static void prv_send_terminate_request(PPPControlProtocol *this) {
this->state->restart_count--;
prv_start_timer(this);
uint8_t id = this->state->next_terminate_id++;
struct LCPPacket *packet = pulse_link_send_begin(this->protocol_number);
*packet = (struct LCPPacket) {
.code = ControlCode_TerminateRequest,
.identifier = id,
.length = hton16(LCP_HEADER_LEN),
};
pulse_link_send(packet, LCP_HEADER_LEN);
}
static void prv_send_terminate_ack(PPPControlProtocol *this, int identifier) {
if (identifier < 0) { // Not in response to a Terminate-Request
// Pick an arbitrary identifier to send in ack
identifier = this->state->next_terminate_id++;
} else {
// Update the next-terminate-id so that the next ack sent not in response to
// a Terminate-Request does not look like a retransmission.
this->state->next_terminate_id = identifier + 1;
}
struct LCPPacket *packet = pulse_link_send_begin(this->protocol_number);
*packet = (struct LCPPacket) {
.code = ControlCode_TerminateAck,
.identifier = identifier,
.length = hton16(LCP_HEADER_LEN),
};
pulse_link_send(packet, LCP_HEADER_LEN);
}
static void prv_send_code_reject(PPPControlProtocol *this,
struct LCPPacket *bad_packet) {
struct LCPPacket *packet = pulse_link_send_begin(this->protocol_number);
packet->code = ControlCode_CodeReject;
packet->identifier = this->state->next_code_reject_id++;
size_t body_len = MIN(ntoh16(bad_packet->length),
pulse_link_max_send_size() - LCP_HEADER_LEN);
memcpy(packet->data, bad_packet, body_len);
pulse_link_send(packet, LCP_HEADER_LEN + body_len);
}
static void prv_on_timeout(void *context) {
PPPControlProtocol *this = context;
mutex_lock(this->state->lock);
if (this->state->restart_count > 0) { // TO+
switch (this->state->link_state) {
case LinkState_Closing:
case LinkState_Stopping:
prv_send_terminate_request(this);
break;
case LinkState_RequestSent:
case LinkState_AckReceived:
case LinkState_AckSent:
prv_send_configure_request(this);
if (this->state->link_state == LinkState_AckReceived) {
prv_transition_to(this, LinkState_RequestSent);
}
break;
default:
break;
}
} else { // TO-
switch (this->state->link_state) {
case LinkState_Stopping:
case LinkState_RequestSent:
case LinkState_AckReceived:
case LinkState_AckSent:
prv_transition_to(this, LinkState_Stopped);
break;
case LinkState_Closing:
prv_transition_to(this, LinkState_Closed);
break;
default:
break;
}
}
mutex_unlock(this->state->lock);
}
static bool prv_handle_configure_request(PPPControlProtocol *this,
struct LCPPacket *packet) {
if (ntoh16(packet->length) == LCP_HEADER_LEN) { // The request has no options
prv_send_configure_ack(this, packet);
return true;
} else {
// Packet has options but we don't support any options yet.
prv_send_configure_reject(this, packet);
return false;
}
}
static void prv_on_configure_request(PPPControlProtocol *this,
struct LCPPacket *packet) {
switch (this->state->link_state) {
case LinkState_Closing:
case LinkState_Stopping:
// Do nothing
break;
case LinkState_Closed:
prv_send_terminate_ack(this, -1);
break;
case LinkState_Stopped:
this->state->restart_count = MAX_CONFIGURE;
// fallthrough
case LinkState_Opened:
prv_send_configure_request(this);
// fallthrough
case LinkState_RequestSent:
case LinkState_AckSent:
if (prv_handle_configure_request(this, packet)) {
prv_transition_to(this, LinkState_AckSent);
} else {
prv_transition_to(this, LinkState_RequestSent);
}
break;
case LinkState_AckReceived:
if (prv_handle_configure_request(this, packet)) {
prv_transition_to(this, LinkState_Opened);
}
break;
default:
break;
}
}
static void prv_on_configure_ack(PPPControlProtocol *this,
struct LCPPacket *packet) {
if (packet->identifier != this->state->last_configure_request_id) {
// Invalid packet; silently discard
return;
}
if (ntoh16(packet->length) != LCP_HEADER_LEN) {
// Only configure requests with no options are sent at the moment.
// If the length is greater than four, there are options in the Ack
// which means that the Ack'ed options list does not match the
// options list from the request. The Ack packet is invalid.
PBL_LOG(LOG_LEVEL_WARNING,
"Configure-Ack received with options list which differs from "
"the sent Configure-Request. Discarding.");
return;
}
switch (this->state->link_state) {
case LinkState_Closed:
case LinkState_Stopped:
prv_send_terminate_ack(this, -1);
break;
case LinkState_Closing:
case LinkState_Stopping:
// Do nothing
break;
case LinkState_RequestSent:
this->state->restart_count = MAX_CONFIGURE;
prv_transition_to(this, LinkState_AckReceived);
break;
case LinkState_AckReceived:
case LinkState_Opened:
PBL_LOG(LOG_LEVEL_WARNING, "Unexpected duplicate Configure-Ack");
prv_send_configure_request(this);
prv_transition_to(this, LinkState_RequestSent);
break;
case LinkState_AckSent:
this->state->restart_count = MAX_CONFIGURE;
prv_transition_to(this, LinkState_Opened);
break;
default:
break;
}
}
static void prv_handle_nak_or_reject(PPPControlProtocol *this,
struct LCPPacket *packet) {
// Process nak/rej options
// respond with new configure request
// TODO: we don't send options, so no nak/rej is expected yet
}
static void prv_on_configure_nak_or_reject(PPPControlProtocol *this,
struct LCPPacket *packet) {
if (packet->identifier != this->state->last_configure_request_id) {
// Invalid packet; silently discard
return;
}
switch (this->state->link_state) {
case LinkState_Closed:
case LinkState_Stopped:
prv_send_terminate_ack(this, -1);
break;
case LinkState_Closing:
case LinkState_Stopping:
// Do nothing
break;
case LinkState_RequestSent:
this->state->restart_count = MAX_CONFIGURE;
// fallthrough
case LinkState_AckReceived:
case LinkState_Opened:
PBL_LOG(LOG_LEVEL_WARNING,
"Unexpected Configure-Nak/Rej received after Ack");
prv_handle_nak_or_reject(this, packet);
prv_transition_to(this, LinkState_RequestSent);
break;
case LinkState_AckSent:
prv_handle_nak_or_reject(this, packet);
break;
default:
break;
}
}
static void prv_on_terminate_request(PPPControlProtocol *this,
struct LCPPacket *packet) {
if (this->state->link_state == LinkState_AckReceived ||
this->state->link_state == LinkState_AckSent) {
prv_transition_to(this, LinkState_RequestSent);
} else if (this->state->link_state == LinkState_Opened) {
this->state->restart_count = 0;
prv_start_timer(this);
prv_transition_to(this, LinkState_Stopping);
}
prv_send_terminate_ack(this, packet->identifier);
}
static void prv_on_terminate_ack(PPPControlProtocol *this,
struct LCPPacket *packet) {
if (this->state->link_state == LinkState_Closing) {
prv_transition_to(this, LinkState_Closed);
} else if (this->state->link_state == LinkState_Stopping) {
prv_transition_to(this, LinkState_Stopped);
} else if (this->state->link_state == LinkState_AckReceived) {
prv_transition_to(this, LinkState_RequestSent);
} else if (this->state->link_state == LinkState_Opened) {
PBL_LOG(LOG_LEVEL_WARNING, "Terminate-Ack received on an open connection");
prv_send_configure_request(this);
prv_transition_to(this, LinkState_RequestSent);
}
}
// Protected interface (control_protocol_impl.h)
// =============================================
void ppp_control_protocol_init(PPPControlProtocol *this) {
*this->state = (PPPControlProtocolState) {
.lock = mutex_create(),
.link_state = LinkState_Initial,
.restart_count = 0,
.restart_timer = new_timer_create(),
.last_configure_request_id = -1,
.next_code_reject_id = 0,
.next_terminate_id = 0,
};
}
// Public interface (control_protocol.h)
// =====================================
void ppp_control_protocol_lower_layer_is_up(PPPControlProtocol *this) {
mutex_lock(this->state->lock);
if (this->state->link_state == LinkState_Initial) {
prv_transition_to(this, LinkState_Closed);
} else if (this->state->link_state == LinkState_Starting) {
this->state->restart_count = MAX_CONFIGURE;
prv_send_configure_request(this);
prv_transition_to(this, LinkState_RequestSent);
}
mutex_unlock(this->state->lock);
}
void ppp_control_protocol_lower_layer_is_down(PPPControlProtocol *this) {
mutex_lock(this->state->lock);
switch (this->state->link_state) {
case LinkState_Closed:
case LinkState_Closing:
prv_transition_to(this, LinkState_Initial);
break;
case LinkState_Stopped:
case LinkState_Stopping:
case LinkState_RequestSent:
case LinkState_AckReceived:
case LinkState_AckSent:
case LinkState_Opened:
prv_transition_to(this, LinkState_Starting);
break;
default:
break;
}
mutex_unlock(this->state->lock);
}
void ppp_control_protocol_open(PPPControlProtocol *this) {
mutex_lock(this->state->lock);
if (this->state->link_state == LinkState_Initial) {
prv_transition_to(this, LinkState_Starting);
} else if (this->state->link_state == LinkState_Closed) {
this->state->restart_count = MAX_CONFIGURE;
prv_send_configure_request(this);
prv_transition_to(this, LinkState_RequestSent);
} else if (this->state->link_state == LinkState_Stopping) {
prv_transition_to(this, LinkState_Closing);
}
mutex_unlock(this->state->lock);
}
void ppp_control_protocol_close(PPPControlProtocol *this,
PPPCPCloseWait wait) {
mutex_lock(this->state->lock);
switch (this->state->link_state) {
case LinkState_Starting:
prv_transition_to(this, LinkState_Initial);
break;
case LinkState_Stopped:
prv_transition_to(this, LinkState_Closed);
break;
case LinkState_RequestSent:
case LinkState_AckReceived:
case LinkState_AckSent:
case LinkState_Opened:
this->state->restart_count = MAX_TERMINATE;
prv_send_terminate_request(this);
// fallthrough
case LinkState_Stopping:
prv_transition_to(this, LinkState_Closing);
break;
default:
break;
}
mutex_unlock(this->state->lock);
if (wait == PPPCPCloseWait_WaitForClosed) {
// Poll for the state machine to finish closing.
while (1) {
mutex_lock(this->state->lock);
LinkState state = this->state->link_state;
mutex_unlock(this->state->lock);
if (state == LinkState_Initial || state == LinkState_Closed) {
return;
}
psleep(2);
}
}
}
void ppp_control_protocol_handle_incoming_packet(
PPPControlProtocol *this, void *raw_packet, size_t length) {
mutex_lock(this->state->lock);
if (this->state->link_state == LinkState_Initial ||
this->state->link_state == LinkState_Starting) {
// No packets should be received while the lower layer is down;
// silently discard.
goto done;
}
struct LCPPacket *packet = raw_packet;
if (length < sizeof(*packet) ||
ntoh16(packet->length) < sizeof(*packet) ||
length < ntoh16(packet->length)) {
// Invalid packet; silently discard
goto done;
}
switch (packet->code) {
case ControlCode_ConfigureRequest:
prv_on_configure_request(this, packet);
break;
case ControlCode_ConfigureAck:
prv_on_configure_ack(this, packet);
break;
case ControlCode_ConfigureNak:
case ControlCode_ConfigureReject:
prv_on_configure_nak_or_reject(this, packet);
break;
case ControlCode_TerminateRequest:
prv_on_terminate_request(this, packet);
break;
case ControlCode_TerminateAck:
prv_on_terminate_ack(this, packet);
break;
case ControlCode_CodeReject:
this->on_receive_code_reject(this, packet);
break;
default:
if (!this->on_receive_unrecognized_code ||
!this->on_receive_unrecognized_code(this, packet)) {
prv_send_code_reject(this, packet);
}
break;
}
done:
mutex_unlock(this->state->lock);
}