Add firmware slices 1–2: skeleton and Wi-Fi provisioning
Implement ESP-IDF walking skeleton with SoftAP, health API, and host tests, then Slice 2 ISecureStore/NvsSecureStore, STA join, POST /api/wifi, and the provisioning web UI. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* @file FirmwareVersion.hpp
|
||||
* @brief Strong type for the firmware release identifier string.
|
||||
*
|
||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||
*
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* @brief FirmwareVersion — validated firmware release identifier.
|
||||
*
|
||||
* @dname FirmwareVersion
|
||||
* @param version Non-empty semantic version string (e.g. "0.1.0").
|
||||
* @return n/a (type)
|
||||
* @pubstate Owns version_ (immutable after construction). No public data
|
||||
* members.
|
||||
*
|
||||
* Wraps the release string so API endpoints never pass a bare char*.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
class FirmwareVersion {
|
||||
public:
|
||||
/**
|
||||
* @brief FirmwareVersion — construct from a non-empty version string.
|
||||
*
|
||||
* @dname FirmwareVersion
|
||||
* @param version Non-empty semantic version (e.g. "0.1.0").
|
||||
* @pubstate writes version_.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
explicit FirmwareVersion(std::string_view version);
|
||||
|
||||
/**
|
||||
* @brief value — read the version string.
|
||||
*
|
||||
* @dname value
|
||||
* @return The stored version string view.
|
||||
* @pubstate reads version_.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
[[nodiscard]] std::string_view value() const noexcept;
|
||||
|
||||
private:
|
||||
std::string version_;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* @file HealthStatus.hpp
|
||||
* @brief Health-check DTO returned by GET /api/health.
|
||||
*
|
||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||
*
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "core/FirmwareVersion.hpp"
|
||||
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* @brief HealthState — coarse health indicator for the API.
|
||||
*
|
||||
* @dname HealthState
|
||||
* @return n/a (type)
|
||||
* @pubstate n/a
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
enum class HealthState {
|
||||
Ok,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief HealthStatus — immutable health-check response payload.
|
||||
*
|
||||
* @dname HealthStatus
|
||||
* @param firmware Release identifier included in the response.
|
||||
* @return n/a (type)
|
||||
* @pubstate Owns state_ and firmware_. Factory ok() builds the nominal
|
||||
* response. No public data members.
|
||||
*
|
||||
* Pure domain type; serialisation lives in HealthStatusJson.hpp.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
class HealthStatus {
|
||||
public:
|
||||
/**
|
||||
* @brief ok — build the nominal health response.
|
||||
*
|
||||
* @dname ok
|
||||
* @param firmware Active firmware version to report.
|
||||
* @return HealthStatus with HealthState::Ok.
|
||||
* @pubstate none
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
[[nodiscard]] static HealthStatus ok(FirmwareVersion firmware);
|
||||
|
||||
/**
|
||||
* @brief state — read the health indicator.
|
||||
*
|
||||
* @dname state
|
||||
* @return Current HealthState.
|
||||
* @pubstate reads state_.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
[[nodiscard]] HealthState state() const noexcept;
|
||||
|
||||
/**
|
||||
* @brief firmware — read the reported firmware version.
|
||||
*
|
||||
* @dname firmware
|
||||
* @return The firmware version carried in this DTO.
|
||||
* @pubstate reads firmware_.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
[[nodiscard]] const FirmwareVersion& firmware() const noexcept;
|
||||
|
||||
private:
|
||||
explicit HealthStatus(HealthState state, FirmwareVersion firmware);
|
||||
|
||||
HealthState state_;
|
||||
FirmwareVersion firmware_;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* @file HealthStatusJson.hpp
|
||||
* @brief JSON serialisation for HealthStatus (pure core, no ESP-IDF).
|
||||
*
|
||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||
*
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "core/HealthStatus.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* @brief serializeHealthStatusJson — render HealthStatus as JSON.
|
||||
*
|
||||
* @dname serializeHealthStatusJson
|
||||
* @param status Health DTO to serialise.
|
||||
* @return JSON object string, e.g. {"status":"ok","fw":"0.1.0"}.
|
||||
* @pubstate none
|
||||
*
|
||||
* Deterministic, allocation-on-stack-then-heap for the returned string.
|
||||
* Used by the HTTP shell; tested on the host without hardware.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
[[nodiscard]] std::string serializeHealthStatusJson(const HealthStatus& status);
|
||||
|
||||
} // namespace core
|
||||
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* @file ISecureStore.hpp
|
||||
* @brief Abstract secure persistence for credentials and lists.
|
||||
*
|
||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||
*
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "core/StoreError.hpp"
|
||||
#include "core/WifiCredentials.hpp"
|
||||
|
||||
#include <expected>
|
||||
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* @brief ISecureStore — persistence boundary for secrets at rest.
|
||||
*
|
||||
* @dname ISecureStore
|
||||
* @return n/a (type)
|
||||
* @pubstate Implementations own NVS/flash handles in the shell. The pure
|
||||
* core and host tests use fakes; never touch real keys here.
|
||||
*
|
||||
* Slice 2 stores Wi-Fi credentials. Station list and user credentials
|
||||
* arrive in later slices on the same interface.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
class ISecureStore {
|
||||
public:
|
||||
/**
|
||||
* @brief ~ISecureStore — virtual destructor for interface.
|
||||
*
|
||||
* @dname ~ISecureStore
|
||||
* @pubstate n/a
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
virtual ~ISecureStore() = default;
|
||||
|
||||
/**
|
||||
* @brief hasWifiCredentials — check whether STA creds are stored.
|
||||
*
|
||||
* @dname hasWifiCredentials
|
||||
* @return true when loadWifiCredentials would succeed.
|
||||
* @pubstate reads backing storage via implementation.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
[[nodiscard]] virtual bool hasWifiCredentials() const = 0;
|
||||
|
||||
/**
|
||||
* @brief saveWifiCredentials — persist validated STA credentials.
|
||||
*
|
||||
* @dname saveWifiCredentials
|
||||
* @param creds Domain credentials; password stays wrapped in Secret.
|
||||
* @return Ok on success, or StoreError::IoFailed.
|
||||
* @pubstate writes backing storage via implementation.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
[[nodiscard]] virtual std::expected<void, StoreError>
|
||||
saveWifiCredentials(const WifiCredentials& creds) = 0;
|
||||
|
||||
/**
|
||||
* @brief loadWifiCredentials — read stored STA credentials.
|
||||
*
|
||||
* @dname loadWifiCredentials
|
||||
* @return WifiCredentials on success, or StoreError::NotFound /
|
||||
* StoreError::InvalidData / StoreError::IoFailed.
|
||||
* @pubstate reads backing storage via implementation.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
[[nodiscard]] virtual std::expected<WifiCredentials, StoreError>
|
||||
loadWifiCredentials() const = 0;
|
||||
|
||||
/**
|
||||
* @brief clearWifiCredentials — erase stored STA credentials.
|
||||
*
|
||||
* @dname clearWifiCredentials
|
||||
* @return Ok on success, or StoreError::IoFailed.
|
||||
* @pubstate clears backing storage via implementation.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
[[nodiscard]] virtual std::expected<void, StoreError>
|
||||
clearWifiCredentials() = 0;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @file ParseError.hpp
|
||||
* @brief Typed errors for untrusted JSON parsing at the boundary.
|
||||
*
|
||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||
*
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* @brief ParseError — failure causes when parsing network input.
|
||||
*
|
||||
* @dname ParseError
|
||||
* @return n/a (type)
|
||||
* @pubstate n/a
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
enum class ParseError {
|
||||
InvalidJson,
|
||||
MissingField,
|
||||
InvalidSsid,
|
||||
InvalidPassword,
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* @file Secret.hpp
|
||||
* @brief Opaque wrapper for sensitive strings (passwords, keys).
|
||||
*
|
||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||
*
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* @brief Secret — holds sensitive bytes, zeroised on destruction.
|
||||
*
|
||||
* @dname Secret
|
||||
* @return n/a (type)
|
||||
* @pubstate Owns bytes_ (immutable after construction except via move).
|
||||
* No operator<<; never log or serialise this type to plaintext.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
class Secret {
|
||||
public:
|
||||
/**
|
||||
* @brief Secret — construct from a plaintext value at the boundary.
|
||||
*
|
||||
* @dname Secret
|
||||
* @param value Sensitive string moved into protected storage.
|
||||
* @pubstate writes bytes_.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
explicit Secret(std::string value);
|
||||
|
||||
Secret(const Secret&) = delete;
|
||||
Secret& operator=(const Secret&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Secret — move-construct, transferring protected storage.
|
||||
*
|
||||
* @dname Secret
|
||||
* @param other Source secret; zeroised after the move.
|
||||
* @pubstate transfers bytes_ from other.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
Secret(Secret&& other) noexcept;
|
||||
|
||||
/**
|
||||
* @brief operator= — move-assign, transferring protected storage.
|
||||
*
|
||||
* @dname operator=
|
||||
* @param other Source secret; zeroised after the move.
|
||||
* @return Reference to this instance.
|
||||
* @pubstate transfers bytes_ from other.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
Secret& operator=(Secret&& other) noexcept;
|
||||
|
||||
/**
|
||||
* @brief ~Secret — zeroise stored bytes.
|
||||
*
|
||||
* @dname ~Secret
|
||||
* @pubstate clears bytes_ securely.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
~Secret();
|
||||
|
||||
/**
|
||||
* @brief length — byte length of the protected value.
|
||||
*
|
||||
* @dname length
|
||||
* @return Number of stored bytes.
|
||||
* @pubstate reads bytes_.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
[[nodiscard]] std::size_t length() const noexcept;
|
||||
|
||||
/**
|
||||
* @brief usePlaintext — invoke a callable with a borrowed view.
|
||||
*
|
||||
* @dname usePlaintext
|
||||
* @param fn Callable invoked with the secret as string_view.
|
||||
* @return Whatever fn returns.
|
||||
* @pubstate reads bytes_; fn must not retain the view past its scope.
|
||||
*
|
||||
* Shell-only escape hatch for APIs (e.g. esp_wifi_set_config) that
|
||||
* require a transient C string. Never log inside fn.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
template<typename Fn>
|
||||
[[nodiscard]] auto usePlaintext(Fn&& fn) const
|
||||
-> decltype(fn(std::declval<std::string_view>()))
|
||||
{
|
||||
return std::forward<Fn>(fn)(bytes_);
|
||||
}
|
||||
|
||||
private:
|
||||
void zeroize() noexcept;
|
||||
|
||||
std::string bytes_;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @file StoreError.hpp
|
||||
* @brief Typed errors for secure-store operations.
|
||||
*
|
||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||
*
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* @brief StoreError — failure causes for ISecureStore operations.
|
||||
*
|
||||
* @dname StoreError
|
||||
* @return n/a (type)
|
||||
* @pubstate n/a
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
enum class StoreError {
|
||||
NotFound,
|
||||
IoFailed,
|
||||
InvalidData,
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* @file WifiCredentials.hpp
|
||||
* @brief Domain type for stored Wi-Fi STA credentials.
|
||||
*
|
||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||
*
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "core/Secret.hpp"
|
||||
#include "core/WifiSsid.hpp"
|
||||
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* @brief WifiCredentials — SSID plus protected PSK for STA join.
|
||||
*
|
||||
* @dname WifiCredentials
|
||||
* @return n/a (type)
|
||||
* @pubstate Owns ssid_ and password_. Password is never exposed as a
|
||||
* loggable string; use Secret::usePlaintext in the shell only.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
class WifiCredentials {
|
||||
public:
|
||||
/** Maximum WPA-PSK length accepted at the boundary. */
|
||||
static constexpr std::size_t kMaxPasswordLength = 63;
|
||||
|
||||
/**
|
||||
* @brief isPasswordValid — validate a PSK length at the boundary.
|
||||
*
|
||||
* @dname isPasswordValid
|
||||
* @param raw Untrusted password from network input.
|
||||
* @return true when length is 0 (open) or 8–63 (WPA).
|
||||
* @pubstate none
|
||||
*
|
||||
* Open networks use an empty password; WPA-PSK requires 8–63 chars.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
[[nodiscard]] static bool isPasswordValid(std::string_view raw) noexcept;
|
||||
|
||||
/**
|
||||
* @brief WifiCredentials — construct from validated domain parts.
|
||||
*
|
||||
* @dname WifiCredentials
|
||||
* @param ssid Validated network name.
|
||||
* @param password Protected pre-shared key (may be empty for open).
|
||||
* @pubstate writes ssid_ and password_.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
WifiCredentials(WifiSsid ssid, Secret password);
|
||||
|
||||
/**
|
||||
* @brief ssid — read the network name.
|
||||
*
|
||||
* @dname ssid
|
||||
* @return The stored SSID value object.
|
||||
* @pubstate reads ssid_.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
[[nodiscard]] const WifiSsid& ssid() const noexcept;
|
||||
|
||||
/**
|
||||
* @brief password — borrow the protected PSK.
|
||||
*
|
||||
* @dname password
|
||||
* @return Const reference to the Secret wrapper.
|
||||
* @pubstate reads password_.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
[[nodiscard]] const Secret& password() const noexcept;
|
||||
|
||||
private:
|
||||
WifiSsid ssid_;
|
||||
Secret password_;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* @file WifiProvisionJson.hpp
|
||||
* @brief Parse and serialise Wi-Fi provisioning JSON at the boundary.
|
||||
*
|
||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||
*
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "core/ParseError.hpp"
|
||||
#include "core/WifiCredentials.hpp"
|
||||
|
||||
#include <expected>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* @brief parseWifiProvisionJson — validate POST body into credentials.
|
||||
*
|
||||
* @dname parseWifiProvisionJson
|
||||
* @param json Untrusted request body, e.g.
|
||||
* {"ssid":"MyNet","password":"secret"}.
|
||||
* @return WifiCredentials on success, or a ParseError.
|
||||
* @pubstate none
|
||||
*
|
||||
* Minimal parser for the Slice 2 provisioning endpoint; rejects malformed
|
||||
* input before any persistence or Wi-Fi driver calls.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
[[nodiscard]] std::expected<WifiCredentials, ParseError>
|
||||
parseWifiProvisionJson(std::string_view json);
|
||||
|
||||
/**
|
||||
* @brief serializeWifiProvisionSavedJson — success response body.
|
||||
*
|
||||
* @dname serializeWifiProvisionSavedJson
|
||||
* @param rebootInSec Seconds until the device reboots into STA mode.
|
||||
* @return JSON object string.
|
||||
* @pubstate none
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
[[nodiscard]] std::string
|
||||
serializeWifiProvisionSavedJson(unsigned rebootInSec);
|
||||
|
||||
/**
|
||||
* @brief serializeWifiProvisionErrorJson — rejection response body.
|
||||
*
|
||||
* @dname serializeWifiProvisionErrorJson
|
||||
* @param reason Short machine-readable cause (never a secret).
|
||||
* @return JSON object string.
|
||||
* @pubstate none
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
[[nodiscard]] std::string
|
||||
serializeWifiProvisionErrorJson(std::string_view reason);
|
||||
|
||||
} // namespace core
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* @file WifiSsid.hpp
|
||||
* @brief Strong type for a Wi-Fi network name (802.11 SSID).
|
||||
*
|
||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||
*
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* @brief WifiSsid — validated Wi-Fi SSID (1–32 bytes).
|
||||
*
|
||||
* @dname WifiSsid
|
||||
* @return n/a (type)
|
||||
* @pubstate Owns ssid_ (immutable after construction).
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
class WifiSsid {
|
||||
public:
|
||||
/** Maximum SSID length per 802.11. */
|
||||
static constexpr std::size_t kMaxLength = 32;
|
||||
|
||||
/**
|
||||
* @brief tryFrom — parse and validate an SSID at the boundary.
|
||||
*
|
||||
* @dname tryFrom
|
||||
* @param raw Untrusted SSID string from network input.
|
||||
* @return WifiSsid on success, or empty optional if invalid.
|
||||
* @pubstate none
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
[[nodiscard]] static bool isValid(std::string_view raw) noexcept;
|
||||
|
||||
/**
|
||||
* @brief WifiSsid — construct from an already-validated SSID.
|
||||
*
|
||||
* @dname WifiSsid
|
||||
* @param raw Non-empty SSID up to 32 bytes.
|
||||
* @pubstate writes ssid_.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
explicit WifiSsid(std::string_view raw);
|
||||
|
||||
/**
|
||||
* @brief value — read the SSID string.
|
||||
*
|
||||
* @dname value
|
||||
* @return Stored SSID as a string view.
|
||||
* @pubstate reads ssid_.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
[[nodiscard]] std::string_view value() const noexcept;
|
||||
|
||||
private:
|
||||
std::string ssid_;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
Reference in New Issue
Block a user