[webrtc] Don't crash the server if the client doesn't know the protocol

Bug: 147522086
Test: locally with dumb client
Change-Id: Iadf8cc315128e7296e224bf4c9ce6fd232dabe90
diff --git a/host/frontend/gcastv2/webrtc/MyWebSocketHandler.cpp b/host/frontend/gcastv2/webrtc/MyWebSocketHandler.cpp
index 7faf9af..c05b9d6 100644
--- a/host/frontend/gcastv2/webrtc/MyWebSocketHandler.cpp
+++ b/host/frontend/gcastv2/webrtc/MyWebSocketHandler.cpp
@@ -25,6 +25,37 @@
 
 #include <webrtc/Keyboard.h>
 
+namespace {
+
+// helper method to ensure a json object has the required fields convertible
+// to the appropriate types.
+bool validateJsonObject(
+    const Json::Value &obj, const std::string &type,
+    const std::vector<std::pair<std::string, Json::ValueType>> &fields,
+    std::function<void(const std::string&)> onError) {
+    for (const auto &field_spec : fields) {
+        const auto &field_name = field_spec.first;
+        auto field_type = field_spec.second;
+        if (!(obj.isMember(field_name) &&
+            obj[field_name].isConvertibleTo(field_type))) {
+            std::string error_msg = "Expected a field named '";
+            error_msg += field_name + "' of type '";
+            error_msg += std::to_string(field_type);
+            error_msg += "'";
+            if (!type.empty()) {
+                error_msg += " in message of type '" + type + "'";
+            }
+            error_msg += ".";
+            LOG(WARNING) << error_msg;
+            onError(error_msg);
+            return false;
+        }
+    }
+    return true;
+}
+
+} // namespace
+
 MyWebSocketHandler::MyWebSocketHandler(
         std::shared_ptr<RunLoop> runLoop,
         std::shared_ptr<ServerState> serverState,
@@ -55,7 +86,14 @@
 
     LOG(VERBOSE) << obj.toStyledString();
 
-    if (!obj.isMember("type")) {
+    auto sendMessageOnError =
+        [this](const std::string &error_msg) {
+          auto reply = "{\"error\": \"" + error_msg + "\"}";
+          sendMessage(reply.c_str(), reply.size());
+        };
+
+    if (!validateJsonObject(obj, "", {{"type", Json::ValueType::stringValue}},
+                            sendMessageOnError)) {
         return -EINVAL;
     }
     std::string type = obj["type"].asString();
@@ -78,7 +116,9 @@
 
         prepareSessions();
     } else if (type == "set-remote-desc") {
-        if (!obj.isMember("sdp")) {
+        if (!validateJsonObject(obj, type,
+                                {{"sdp", Json::ValueType::stringValue}},
+                                sendMessageOnError)) {
             return -EINVAL;
         }
 
@@ -219,7 +259,10 @@
         auto replyAsString = json_writer.write(reply);
         sendMessage(replyAsString.c_str(), replyAsString.size());
     } else if (type == "get-ice-candidate") {
-        CHECK(obj.isMember("mid"));
+        if (!validateJsonObject(obj, type, {{"mid", Json::ValueType::intValue}},
+                                sendMessageOnError)) {
+            return -EINVAL;
+        }
         int32_t mid = obj["mid"].asInt();
 
         bool success = getCandidate(mid);
@@ -232,11 +275,13 @@
             sendMessage(replyAsString.c_str(), replyAsString.size());
         }
     } else if (type == "set-mouse-position") {
-        CHECK(obj.isMember("down"));
+        if (!validateJsonObject(obj, type, {{"down", Json::ValueType::intValue},
+                                            {"x", Json::ValueType::intValue},
+                                            {"y", Json::ValueType::intValue}},
+                                sendMessageOnError)) {
+            return -EINVAL;
+        }
         int32_t down = obj["down"].asInt();
-
-        CHECK(obj.isMember("x"));
-        CHECK(obj.isMember("y"));
         int32_t x = obj["x"].asInt();
         int32_t y = obj["y"].asInt();
 
@@ -245,11 +290,14 @@
 
         mTouchSink->injectTouchEvent(x, y, down != 0);
     } else if (type == "inject-multi-touch") {
-        CHECK(obj.isMember("id"));
-        CHECK(obj.isMember("initialDown"));
-        CHECK(obj.isMember("x"));
-        CHECK(obj.isMember("y"));
-        CHECK(obj.isMember("slot"));
+        if (!validateJsonObject(obj, type, {{"id", Json::ValueType::intValue},
+                                            {"initialDown", Json::ValueType::intValue},
+                                            {"x", Json::ValueType::intValue},
+                                            {"y", Json::ValueType::intValue},
+                                            {"slot", Json::ValueType::intValue}},
+                                sendMessageOnError)) {
+            return -EINVAL;
+        }
         int32_t id = obj["id"].asInt();
         int32_t initialDown = obj["initialDown"].asInt();
         int32_t x = obj["x"].asInt();
@@ -270,11 +318,13 @@
 
         mTouchSink->injectMultiTouchEvent(id, slot, x, y, initialDown);
     } else if (type == "key-event") {
-        CHECK(obj.isMember("event_type"));
+        if (!validateJsonObject(obj, type, {{"event_type", Json::ValueType::stringValue},
+                                            {"keycode", Json::ValueType::stringValue}},
+                                sendMessageOnError)) {
+            return -EINVAL;
+        }
         auto down = obj["event_type"].asString() == std::string("keydown");
-        CHECK(obj.isMember("keycode"));
         auto code = DomKeyCodeToLinux(obj["keycode"].asString());
-        CHECK(code);
         mKeyboardSink->injectEvent(down, code);
     }