/* * 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 "poll_remote.h" #include "services/common/comm_session/protocol.h" #include "services/common/comm_session/session.h" #include "services/common/system_task.h" #include "system/logging.h" #include "system/passert.h" #include "util/attributes.h" #include /* * Private */ static const uint8_t MIN_INTERVAL_MINUTES = 1; static const uint16_t ENDPOINT_ID = 0xcafe; static bool s_running = false; typedef enum { CMD_POLL = 0x0, // Formerly command poll mail LEGACY_CMD_REQUEST_INTERVAL = 0x1, // for backwards compatibility CMD_SET_INTERVAL = 0x2, CMD_REQUEST_POLL = 0x3, } PollRemoteCommand; // Deprecated -- used to set the mail poll interval typedef struct PACKED { uint8_t cmd; uint8_t interval_minutes; } PollLegacySetIntervalMessage; // Poll a service at a specific interval typedef struct PACKED { PollRemoteCommand cmd; PollRemoteService service; uint8_t interval_minutes; } PollSetIntervalMessage; // Request to poll a service now typedef struct PACKED { PollRemoteCommand cmd; PollRemoteService service; } PollRequestMessage; typedef struct PACKED { PollRemoteCommand cmd; PollRemoteService service; } PollRemoteMessage; //! Struct that holds all the state required for a remote polling subsystem. typedef struct { PollRemoteService service; //! The minimum interval between two "poll services" requests. //! Calls to poll_remote_send_request() will be no-ops if min_interval_minutes has not been reached. uint8_t min_interval_minutes; //! The maximum interval between two "poll services" requests. //! The automatic sending of poll requests will only occur when max_interval_minutes is reached. uint8_t max_interval_minutes; uint8_t counted_minutes; //!< Number of minutes passed since the last request } PollRemoteContext; static void poll_service_timer_callback(); static RegularTimerInfo s_poll_timer = { .cb = poll_service_timer_callback, .cb_data = NULL, }; static PollRemoteContext s_poll_remote_contexts[NUM_POLL_REMOTE_SERVICES]; static void for_each_context(void (*poll_remote_function)(PollRemoteContext *ctx)) { for (int i = 0; i < NUM_POLL_REMOTE_SERVICES; i++) { poll_remote_function(&s_poll_remote_contexts[i]); } } static bool has_min_interval_passed(PollRemoteContext *ctx) { return (ctx->counted_minutes >= ctx->min_interval_minutes); } static bool has_max_interval_passed(PollRemoteContext *ctx) { return (ctx->counted_minutes >= ctx->max_interval_minutes); } // SystemTaskCallback static void prv_send_request(PollRemoteContext *ctx) { if (has_min_interval_passed(ctx) == false) { return; } CommSession *session = comm_session_get_system_session(); if (!session) { return; } // [MT]: comm_session_send_data() doesn't make the link active, // which is what we want here. If this this changes in the future // we need to take measures here to make sure we don't pull the link active. const PollRemoteMessage msg = { .cmd = CMD_POLL, .service = ctx->service }; comm_session_send_data(session, ENDPOINT_ID, (const uint8_t *)&msg, sizeof(PollRemoteMessage), COMM_SESSION_DEFAULT_TIMEOUT); ctx->counted_minutes = 0; } static void context_interval_check(PollRemoteContext *ctx) { if (ctx->max_interval_minutes == 0) { return; } ctx->counted_minutes++; if (has_max_interval_passed(ctx)) { prv_send_request(ctx); } } static void start(PollRemoteContext *ctx) { ctx->counted_minutes = 0; } static void set_intervals(PollRemoteContext *ctx, const uint8_t min_interval_minutes, const uint8_t max_interval_minutes) { ctx->min_interval_minutes = min_interval_minutes; ctx->max_interval_minutes = max_interval_minutes; } void comm_poll_remote_protocol_msg_callback(CommSession *session, const uint8_t* data, size_t length) { PollRemoteCommand cmd = data[0]; switch (cmd) { case CMD_REQUEST_POLL: { PollRequestMessage *msg = (PollRequestMessage *)data; if (msg->service >= NUM_POLL_REMOTE_SERVICES) { return; } prv_send_request(&s_poll_remote_contexts[msg->service]); break; } case LEGACY_CMD_REQUEST_INTERVAL: { PollLegacySetIntervalMessage *msg = (PollLegacySetIntervalMessage *)data; poll_remote_set_intervals(POLL_REMOTE_SERVICE_MAIL, MIN_INTERVAL_MINUTES, msg->interval_minutes); break; } case CMD_SET_INTERVAL: { PollSetIntervalMessage *msg = (PollSetIntervalMessage *)data; if (msg->service >= NUM_POLL_REMOTE_SERVICES) { return; } poll_remote_set_intervals(msg->service, MIN_INTERVAL_MINUTES, msg->interval_minutes); break; } default: { PBL_LOG(LOG_LEVEL_ERROR, "Invalid command."); return; } } } /* * Public */ static void poll_service_system_task_callback(void *data) { PBL_ASSERTN(s_running); for_each_context(context_interval_check); } static void poll_service_timer_callback(void *data) { system_task_add_callback(poll_service_system_task_callback, data); } void poll_remote_init(void) { for (int i = 0; i < NUM_POLL_REMOTE_SERVICES; i++) { s_poll_remote_contexts[i].service = i; } } void poll_remote_send_request(PollRemoteService service) { prv_send_request(&s_poll_remote_contexts[service]); } void poll_remote_start(void) { if (s_running) { return; } s_running = true; for_each_context(start); regular_timer_add_minutes_callback(&s_poll_timer); } void poll_remote_stop(void) { if (!s_running) { return; } s_running = false; regular_timer_remove_callback(&s_poll_timer); } void poll_remote_set_intervals(PollRemoteService service, const uint8_t min_interval_minutes, const uint8_t max_interval_minutes) { set_intervals(&s_poll_remote_contexts[service], min_interval_minutes, max_interval_minutes); (max_interval_minutes == 0) ? poll_remote_stop() : poll_remote_start(); }