Skip to main content

oxihuman_export/
websocket_msg_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! WebSocket message export stub — packages mesh/animation data as WebSocket frames.
6
7/// WebSocket opcode.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum WsOpcode {
10    Text,
11    Binary,
12    Ping,
13    Pong,
14    Close,
15}
16
17/// A WebSocket frame stub.
18#[derive(Debug, Clone)]
19pub struct WsFrame {
20    pub opcode: WsOpcode,
21    pub payload: Vec<u8>,
22    pub is_final: bool,
23    pub masked: bool,
24}
25
26/// A WebSocket message export session.
27#[derive(Debug, Default)]
28pub struct WsMessageExport {
29    pub frames: Vec<WsFrame>,
30    pub url: String,
31}
32
33/// Create a new WebSocket message export session.
34pub fn new_ws_export(url: &str) -> WsMessageExport {
35    WsMessageExport {
36        frames: Vec::new(),
37        url: url.to_owned(),
38    }
39}
40
41/// Add a text frame.
42pub fn ws_send_text(export: &mut WsMessageExport, text: &str) {
43    export.frames.push(WsFrame {
44        opcode: WsOpcode::Text,
45        payload: text.as_bytes().to_vec(),
46        is_final: true,
47        masked: true,
48    });
49}
50
51/// Add a binary frame.
52pub fn ws_send_binary(export: &mut WsMessageExport, data: Vec<u8>) {
53    export.frames.push(WsFrame {
54        opcode: WsOpcode::Binary,
55        payload: data,
56        is_final: true,
57        masked: true,
58    });
59}
60
61/// Add a ping frame.
62pub fn ws_send_ping(export: &mut WsMessageExport, data: Vec<u8>) {
63    export.frames.push(WsFrame {
64        opcode: WsOpcode::Ping,
65        payload: data,
66        is_final: true,
67        masked: true,
68    });
69}
70
71/// Number of frames.
72pub fn ws_frame_count(export: &WsMessageExport) -> usize {
73    export.frames.len()
74}
75
76/// Total payload bytes.
77pub fn total_ws_bytes(export: &WsMessageExport) -> usize {
78    export.frames.iter().map(|f| f.payload.len()).sum()
79}
80
81/// Count frames of a given opcode.
82pub fn frames_of_opcode(export: &WsMessageExport, opcode: WsOpcode) -> usize {
83    export.frames.iter().filter(|f| f.opcode == opcode).count()
84}
85
86/// Opcode name string.
87pub fn opcode_name(op: WsOpcode) -> &'static str {
88    match op {
89        WsOpcode::Text => "text",
90        WsOpcode::Binary => "binary",
91        WsOpcode::Ping => "ping",
92        WsOpcode::Pong => "pong",
93        WsOpcode::Close => "close",
94    }
95}
96
97/// Serialize metadata to JSON-style string.
98pub fn ws_export_to_json(export: &WsMessageExport) -> String {
99    format!(
100        r#"{{"url":"{}", "frame_count":{}, "total_bytes":{}}}"#,
101        export.url,
102        ws_frame_count(export),
103        total_ws_bytes(export)
104    )
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn new_export_empty() {
113        /* fresh export has no frames */
114        let e = new_ws_export("wss://localhost:8080");
115        assert_eq!(ws_frame_count(&e), 0);
116    }
117
118    #[test]
119    fn send_text_increments_count() {
120        /* sending text increments frame count */
121        let mut e = new_ws_export("wss://localhost:8080");
122        ws_send_text(&mut e, "hello");
123        assert_eq!(ws_frame_count(&e), 1);
124    }
125
126    #[test]
127    fn send_binary_increments_count() {
128        /* sending binary increments frame count */
129        let mut e = new_ws_export("wss://localhost:8080");
130        ws_send_binary(&mut e, vec![1, 2, 3]);
131        assert_eq!(ws_frame_count(&e), 1);
132    }
133
134    #[test]
135    fn total_bytes_counted() {
136        /* 5-byte text payload counted */
137        let mut e = new_ws_export("wss://localhost:8080");
138        ws_send_text(&mut e, "hello");
139        assert_eq!(total_ws_bytes(&e), 5);
140    }
141
142    #[test]
143    fn frames_of_opcode_text() {
144        /* count of Text frames is 1 */
145        let mut e = new_ws_export("wss://localhost");
146        ws_send_text(&mut e, "x");
147        ws_send_binary(&mut e, vec![]);
148        assert_eq!(frames_of_opcode(&e, WsOpcode::Text), 1);
149    }
150
151    #[test]
152    fn ping_frame_opcode_correct() {
153        /* ping frame has Ping opcode */
154        let mut e = new_ws_export("wss://localhost");
155        ws_send_ping(&mut e, vec![]);
156        assert_eq!(e.frames[0].opcode, WsOpcode::Ping);
157    }
158
159    #[test]
160    fn opcode_name_text() {
161        /* Text opcode name is "text" */
162        assert_eq!(opcode_name(WsOpcode::Text), "text");
163    }
164
165    #[test]
166    fn opcode_name_binary() {
167        /* Binary opcode name is "binary" */
168        assert_eq!(opcode_name(WsOpcode::Binary), "binary");
169    }
170
171    #[test]
172    fn json_contains_url() {
173        /* JSON includes URL */
174        let e = new_ws_export("wss://ws.example.com");
175        assert!(ws_export_to_json(&e).contains("ws.example.com"));
176    }
177}