1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.googlecode.android_scripting.jsonrpc; 18 19 import com.googlecode.android_scripting.Log; 20 import com.googlecode.android_scripting.Sl4aErrors; 21 import com.googlecode.android_scripting.rpc.MethodDescriptor; 22 23 import org.json.JSONArray; 24 import org.json.JSONException; 25 import org.json.JSONObject; 26 27 /** 28 * A class that parses given JSON RPC Messages, and handles their execution. 29 */ 30 public class JsonRpcHandler { 31 private int mNextSessionId = 1; 32 private String mSessionId = ""; 33 private final RpcReceiverManagerFactory mManagerFactory; 34 JsonRpcHandler(RpcReceiverManagerFactory managerFactory)35 public JsonRpcHandler(RpcReceiverManagerFactory managerFactory) { 36 mManagerFactory = managerFactory; 37 } 38 39 /** 40 * Returns the response object for the given request. 41 * <p> 42 * The response will be returned as a JSONObject, unless something fatal has occurred. In that 43 * case, the resulting object will be a JSON formatted string. 44 * 45 * @param request the received request as a string 46 * @return the response to the request 47 */ getResponse(String request)48 public Object getResponse(String request) { 49 try { 50 return getResponse(new JSONObject(request)); 51 } catch (JSONException ignored) { 52 // May have been a bad request. 53 } 54 try { 55 return Sl4aErrors.JSON_RPC_REQUEST_NOT_JSON.toJson(JSONObject.NULL); 56 } catch (JSONException e) { 57 // This error will never occur. 58 Log.e("Received JSONException on invalid request.", e); 59 return JsonRpcResult.wtf(); 60 } 61 } 62 63 /** 64 * Returns the response for the given request. 65 * <p> 66 * This message will be returned as a JSONObject, unless something fatal has occurred. In that 67 * case, the resulting object will be a JSON formatted string. 68 * 69 * @param request the JSON-RPC request message as a JSONObject 70 * @return the response to the request 71 */ getResponse(JSONObject request)72 public Object getResponse(JSONObject request) { 73 if (isSessionManagementRpc(request)) { 74 return handleSessionRpc(request); 75 } 76 77 Object validationErrors = getValidationErrors(request); 78 if (validationErrors != null) { 79 return validationErrors; 80 } 81 82 try { 83 Object id = request.get("id"); 84 String method = request.getString("method"); 85 86 JSONArray params = new JSONArray(); 87 if (request.has("params")) { 88 params = request.getJSONArray("params"); 89 } 90 91 RpcReceiverManager receiverManager = 92 mManagerFactory.getRpcReceiverManagers().get(mSessionId); 93 if (receiverManager == null) { 94 Log.e("Unable to find sessionId \"" + mSessionId + "\"."); 95 for (String key : mManagerFactory.getRpcReceiverManagers().keySet()) { 96 Log.d("Available key: " + key); 97 } 98 return Sl4aErrors.SESSION_INVALID.toJson(id, mSessionId); 99 } 100 MethodDescriptor rpc = receiverManager.getMethodDescriptor(method); 101 if (rpc == null) { 102 return Sl4aErrors.JSON_RPC_UNKNOWN_RPC_METHOD.toJson(id, method); 103 } 104 Object rpcResult; 105 try { 106 rpcResult = rpc.invoke(receiverManager, params); 107 } catch (Throwable t) { 108 Log.e("RPC call threw an error.", t); 109 return Sl4aErrors.JSON_RPC_FACADE_EXCEPTION_OCCURRED.toJson(id, t); 110 } 111 try { 112 return JsonRpcResult.result(id, rpcResult); 113 } catch (JSONException e) { 114 // This error is rare, but may occur when the JsonBuilder fails to properly 115 // convert the resulting class from the RPC into valid JSON. This may happen 116 // when a resulting number is returned as NaN or infinite, but is not converted 117 // to a String first. 118 Log.e("Unable to build object of class \"" + rpcResult.getClass() + "\".", e); 119 return Sl4aErrors.JSON_RPC_RESULT_NOT_JSON_VALID.toJson(id, e); 120 } catch (Throwable t) { 121 // This error will occur whenever the underlying Android APIs built against are 122 // unavailable for the version of Android SL4A is installed onto. 123 Log.e("Unable to build object of class \"" + rpcResult.getClass() + "\".", t); 124 return Sl4aErrors.JSON_RPC_FAILED_TO_BUILD_RESULT.toJson(id, t); 125 } 126 } catch (JSONException exception) { 127 // This error should never happen. NULL and all values found within Sl4aErrors are 128 // guaranteed not to raise an error when being converted to JSON. 129 // Also, all JSONObject.get() calls are only done after they have been validated to 130 // exist after a corresponding JSONObject.has() call has been made. 131 // Returning a raw string here to prevent the need to catch JsonRpcResult.error(). 132 Log.e(exception); 133 return JsonRpcResult.wtf(); 134 } 135 } 136 137 /** 138 * Returns a validation error if the request is invalid. Null otherwise. 139 * @param message the request message 140 * @return a JSONObject error response, or null if the message is valid 141 */ getValidationErrors(JSONObject message)142 private Object getValidationErrors(JSONObject message) { 143 Object id = JSONObject.NULL; 144 try { 145 if (!message.has("id")) { 146 return Sl4aErrors.JSON_RPC_MISSING_ID.toJson(id); 147 } else { 148 id = message.get("id"); 149 } 150 if (!message.has("method")) { 151 return Sl4aErrors.JSON_RPC_MISSING_METHOD.toJson(id); 152 } else if (!(message.get("method") instanceof String)) { 153 return Sl4aErrors.JSON_RPC_METHOD_NOT_STRING.toJson(id); 154 } 155 if (message.has("params") && !(message.get("params") instanceof JSONArray)) { 156 return Sl4aErrors.JSON_RPC_PARAMS_NOT_AN_ARRAY.toJson(id); 157 } 158 } catch (JSONException exception) { 159 // This error should never happen. NULL and all values found within Sl4aException are 160 // guaranteed not to raise an error when being converted to JSON. 161 // Also, all JSONObject.get() calls are only done after they have been validated to 162 // exist after a corresponding JSONObject.has() call has been made. 163 // Returning a raw string here to prevent the need to wrap JsonRpcResult.error(). 164 Log.e(exception); 165 return JsonRpcResult.wtf(); 166 } 167 return null; 168 } 169 170 //TODO(markdr): Refactor this function into a class that handles session management. isSessionManagementRpc(JSONObject request)171 private boolean isSessionManagementRpc(JSONObject request) { 172 return request.has("cmd") && request.has("uid"); 173 } 174 175 //TODO(markdr): Refactor this function into a class that handles session management. handleSessionRpc(JSONObject request)176 private Object handleSessionRpc(JSONObject request) { 177 try { 178 Object id = request.get("uid"); 179 String cmd = request.getString("cmd"); 180 switch (cmd) { 181 case "initiate": { 182 String newId; 183 synchronized (mManagerFactory) { 184 newId = "" + mNextSessionId; 185 while (mManagerFactory.getRpcReceiverManagers().containsKey(newId)) { 186 newId = "" + mNextSessionId++; 187 } 188 mSessionId = newId; 189 mManagerFactory.create(newId); 190 } 191 return "{\"uid\": \"" + newId + "\", \"status\":true}"; 192 } 193 case "continue": { 194 for (String key : mManagerFactory.getRpcReceiverManagers().keySet()) { 195 Log.d("Available key: " + key); 196 } 197 String stringId = id.toString(); 198 boolean canContinue = 199 mManagerFactory.getRpcReceiverManagers().containsKey(stringId); 200 if (canContinue) { 201 mSessionId = stringId; 202 return "{\"uid\":\"" + id + "\",\"status\":true}"; 203 } 204 return "{\"uid\":\"" + id + "\",\"status\":false," 205 + "\"error\":\"Session does not exist.\"}"; 206 } 207 default: { 208 // This case should never be reached if isSessionManagementRpc is called first. 209 Log.e("Hit default case for handling session rpcs."); 210 return JsonRpcResult.wtf(); 211 } 212 } 213 } catch (JSONException jsonException) { 214 // This error will never occur, because the above IDs and objects will always be JSON 215 // serializable. 216 Log.e("Exception during handleRpcCall: " + jsonException); 217 return JsonRpcResult.wtf(); 218 } 219 } 220 } 221