| 1782 | } |
| 1783 | |
| 1784 | void CloudTunnel::HandleDataChannelMessage(const std::string& vm_id, const nlohmann::json& message) { |
| 1785 | if (!message.is_object()) return; |
| 1786 | const std::string type = message.value("type", ""); |
| 1787 | if (type.empty()) return; |
| 1788 | // Input fast-path: pointer/wheel arrive on `input-fast` (best-effort); |
| 1789 | // key/pointer.button arrive on `control` (reliable). Either way both |
| 1790 | // dispatch to the same RuntimeManager IPC. |
| 1791 | if (type == "pointer") { |
| 1792 | runtime_manager_.SendPointerEvent( |
| 1793 | vm_id, |
| 1794 | message.value("x", 0), |
| 1795 | message.value("y", 0), |
| 1796 | message.value("buttons", static_cast<uint32_t>(0))); |
| 1797 | } else if (type == "wheel") { |
| 1798 | runtime_manager_.SendWheelEvent(vm_id, message.value("delta", 0)); |
| 1799 | } else if (type == "key") { |
| 1800 | runtime_manager_.SendKeyEvent( |
| 1801 | vm_id, |
| 1802 | message.value("key_code", static_cast<uint32_t>(0)), |
| 1803 | message.value("pressed", false)); |
| 1804 | } else if (type == "clipboard.grab") { |
| 1805 | const uint32_t selection = message.value("selection", static_cast<uint32_t>(0)); |
| 1806 | std::vector<uint32_t> codes; |
| 1807 | if (message.contains("types") && message["types"].is_array()) { |
| 1808 | for (const auto& entry : message["types"]) { |
| 1809 | if (!entry.is_string()) continue; |
| 1810 | const uint32_t code = MimeToVdAgentType(entry.get<std::string>()); |
| 1811 | if (code) codes.push_back(code); |
| 1812 | } |
| 1813 | } |
| 1814 | if (!codes.empty()) runtime_manager_.SendClipboardGrabToGuest(vm_id, selection, codes); |
| 1815 | } else if (type == "clipboard.request") { |
| 1816 | const uint32_t selection = message.value("selection", static_cast<uint32_t>(0)); |
| 1817 | const uint32_t code = MimeToVdAgentType(message.value("mime", std::string())); |
| 1818 | if (code) runtime_manager_.SendClipboardRequestToGuest(vm_id, selection, code); |
| 1819 | } else if (type == "clipboard.data") { |
| 1820 | const uint32_t selection = message.value("selection", static_cast<uint32_t>(0)); |
| 1821 | const uint32_t code = MimeToVdAgentType(message.value("mime", std::string())); |
| 1822 | if (!code) return; |
| 1823 | std::vector<uint8_t> bytes = Base64Decode(message.value("bytes_base64", std::string())); |
| 1824 | runtime_manager_.SendClipboardDataToGuest(vm_id, selection, code, bytes); |
| 1825 | } else if (type == "clipboard.release") { |
| 1826 | const uint32_t selection = message.value("selection", static_cast<uint32_t>(0)); |
| 1827 | runtime_manager_.SendClipboardReleaseToGuest(vm_id, selection); |
| 1828 | } else if (type == "vm.reboot" || type == "vm.shutdown") { |
| 1829 | // Guest-OS facing operations: must travel end-to-end over the |
| 1830 | // already-authenticated WebRTC `control` channel rather than the |
| 1831 | // cloud HTTP path, so the cloud never sees plaintext intent. |
| 1832 | std::string error; |
| 1833 | const bool ok = (type == "vm.reboot") |
| 1834 | ? runtime_manager_.RebootVm(vm_id, &error) |
| 1835 | : runtime_manager_.ShutdownVm(vm_id, &error); |
| 1836 | std::shared_ptr<WebRtcPeer> peer; |
| 1837 | if (auto session = remote_sessions_.GetByVm(vm_id)) { |
| 1838 | std::lock_guard<std::mutex> lock(remote_peers_mu_); |
| 1839 | auto it = remote_peers_.find(session->session_id); |
| 1840 | if (it != remote_peers_.end()) peer = it->second; |
| 1841 | } |
nothing calls this directly
no test coverage detected