#ifdef ESP_PLATFORM #include #include #include #include #include #include #include #include #include #include #include #include #include namespace tt::service::development { extern const ServiceManifest manifest; static const auto LOGGER = Logger("DevService"); bool DevelopmentService::onStart(ServiceContext& service) { std::stringstream stream; stream << "{"; stream << "\"cpuFamily\":\"" << CONFIG_IDF_TARGET << "\", "; stream << "\"osVersion\":\"" << TT_VERSION << "\", "; stream << "\"protocolVersion\":\"1.0.0\""; stream << "}"; deviceResponse = stream.str(); setEnabled(shouldEnableOnBoot()); return true; } void DevelopmentService::onStop(ServiceContext& service) { setEnabled(false); } // region Enable/disable void DevelopmentService::setEnabled(bool enabled) { auto lock = mutex.asScopedLock(); lock.lock(); if (enabled) { if (!httpServer.isStarted()) { httpServer.start(); } } else { if (httpServer.isStarted()) { httpServer.stop(); } } } bool DevelopmentService::isEnabled() const { auto lock = mutex.asScopedLock(); lock.lock(); return httpServer.isStarted(); } // region endpoints esp_err_t DevelopmentService::handleGetInfo(httpd_req_t* request) { LOGGER.info("GET /device"); if (httpd_resp_set_type(request, "application/json") != ESP_OK) { LOGGER.warn("Failed to send header"); return ESP_FAIL; } auto* service = static_cast(request->user_ctx); if (httpd_resp_sendstr(request, service->deviceResponse.c_str()) != ESP_OK) { LOGGER.warn("Failed to send response body"); return ESP_FAIL; } LOGGER.info("[200] /device"); return ESP_OK; } esp_err_t DevelopmentService::handleAppRun(httpd_req_t* request) { LOGGER.info("POST /app/run"); std::string query; if (!network::getQueryOrSendError(request, query)) { return ESP_FAIL; } auto parameters = network::parseUrlQuery(query); auto id_key_pos = parameters.find("id"); if (id_key_pos == parameters.end()) { LOGGER.warn("[400] /app/run id not specified"); httpd_resp_send_err(request, HTTPD_400_BAD_REQUEST, "id not specified"); return ESP_FAIL; } const auto& app_id = id_key_pos->second; if (app::isRunning(app_id)) { app::stopAll(app_id); } app::start(app_id); LOGGER.info("[200] /app/run {}", id_key_pos->second); httpd_resp_send(request, nullptr, 0); return ESP_OK; } esp_err_t DevelopmentService::handleAppInstall(httpd_req_t* request) { LOGGER.info("PUT /app/install"); std::string boundary; if (!network::getMultiPartBoundaryOrSendError(request, boundary)) { return false; } size_t content_left = request->content_len; // Skip newline after reading boundary auto content_headers_data = network::receiveTextUntil(request, "\r\n\r\n"); content_left -= content_headers_data.length(); auto raw_headers = string::split(content_headers_data, "\r\n"); std::vector content_headers; content_headers.reserve(raw_headers.size()); for (const auto& line : raw_headers) { if (line.length() > 0) { content_headers.push_back(line); } } auto content_disposition_map = network::parseContentDisposition(content_headers); if (content_disposition_map.empty()) { httpd_resp_send_err(request, HTTPD_400_BAD_REQUEST, "Multipart form error: invalid content disposition"); return ESP_FAIL; } auto name_entry = content_disposition_map.find("name"); auto filename_entry = content_disposition_map.find("filename"); if ( name_entry == content_disposition_map.end() || filename_entry == content_disposition_map.end() || name_entry->second != "elf" ) { httpd_resp_send_err(request, HTTPD_400_BAD_REQUEST, "Multipart form error: name or filename parameter missing or mismatching"); return ESP_FAIL; } // Receive boundary auto boundary_and_newlines_after_file = std::format("\r\n--{}--\r\n", boundary); auto file_size = content_left - boundary_and_newlines_after_file.length(); // Create tmp directory const std::string tmp_path = getTempPath(); if (!file::findOrCreateDirectory(tmp_path, 0777)) { httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to save file"); return ESP_FAIL; } auto file_path = std::format("{}/{}", tmp_path, filename_entry->second); if (network::receiveFile(request, file_size, file_path) != file_size) { httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to save file"); return ESP_FAIL; } content_left -= file_size; // Read and verify part if (!network::readAndDiscardOrSendError(request, boundary_and_newlines_after_file)) { return ESP_FAIL; } content_left -= boundary_and_newlines_after_file.length(); if (content_left != 0) { LOGGER.warn("We have more bytes at the end of the request parsing?!"); } if (!app::install(file_path)) { httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to install"); return ESP_FAIL; } if (!file::deleteFile(file_path)) { LOGGER.warn("Failed to delete {}", file_path); } LOGGER.info("[200] /app/install -> {}", file_path); httpd_resp_send(request, nullptr, 0); return ESP_OK; } esp_err_t DevelopmentService::handleAppUninstall(httpd_req_t* request) { LOGGER.info("PUT /app/uninstall"); std::string query; if (!network::getQueryOrSendError(request, query)) { return ESP_FAIL; } auto parameters = network::parseUrlQuery(query); auto id_key_pos = parameters.find("id"); if (id_key_pos == parameters.end()) { LOGGER.warn("[400] /app/uninstall id not specified"); httpd_resp_send_err(request, HTTPD_400_BAD_REQUEST, "id not specified"); return ESP_FAIL; } if (!app::findAppManifestById(id_key_pos->second)) { LOGGER.info("[200] /app/uninstall {} (app wasn't installed)", id_key_pos->second); httpd_resp_send(request, nullptr, 0); return ESP_OK; } if (app::uninstall(id_key_pos->second)) { LOGGER.info("[200] /app/uninstall {}", id_key_pos->second); httpd_resp_send(request, nullptr, 0); return ESP_OK; } else { LOGGER.warn("[500] /app/uninstall {}", id_key_pos->second); httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to uninstall"); return ESP_FAIL; } } // endregion std::shared_ptr findService() { return std::static_pointer_cast( findServiceById(manifest.id) ); } extern const ServiceManifest manifest = { .id = "Development", .createService = create }; } #endif // ESP_PLATFORM