codetics-ocpp-ws 0.1.0

Codetics OCPP: dependency-light RFC 6455 WebSocket with RFC 7692 permessage-deflate, tuned for many long-lived OCPP connections
Documentation
// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 Kevin Vavelin, Codetics (https://codetics.fr)

//! Opening handshake helpers (RFC 6455 §4).

use crate::{base64, sha1};

const GUID: &str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

/// Computes `Sec-WebSocket-Accept` for a `Sec-WebSocket-Key`.
pub fn accept_key(key: &str) -> String {
    let mut input = key.trim().as_bytes().to_vec();
    input.extend_from_slice(GUID.as_bytes());
    base64::encode(&sha1::sha1(&input))
}

/// `true` if a comma-separated header value contains `token` (case-insensitive).
pub fn header_has_token(value: &str, token: &str) -> bool {
    value
        .split(',')
        .any(|t| t.trim().eq_ignore_ascii_case(token))
}

/// Validates a 16-byte base64 `Sec-WebSocket-Key`.
pub fn valid_client_key(key: &str) -> bool {
    base64::decode(key).is_some_and(|k| k.len() == 16)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn rfc_example() {
        // RFC 6455 §1.3 and the OCPP-J examples.
        assert_eq!(
            accept_key("dGhlIHNhbXBsZSBub25jZQ=="),
            "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="
        );
        assert_eq!(
            accept_key("x3JJHMbDL1EzLkh9GBhXDw=="),
            "HSmrc0sMlYUkAGmm5OPpG2HaGWk="
        );
        assert!(valid_client_key("dGhlIHNhbXBsZSBub25jZQ=="));
        assert!(!valid_client_key("abc"));
        assert!(header_has_token("keep-alive, Upgrade", "upgrade"));
    }
}