• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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