1 /* 2 * Copyright (C) 2009 Google Inc. All rights reserved. 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.google.polo.wire.json; 18 19 import com.google.polo.exception.BadSecretException; 20 import com.google.polo.exception.NoConfigurationException; 21 import com.google.polo.exception.PoloException; 22 import com.google.polo.exception.ProtocolErrorException; 23 import com.google.polo.json.JSONArray; 24 import com.google.polo.json.JSONException; 25 import com.google.polo.json.JSONObject; 26 import com.google.polo.pairing.message.ConfigurationAckMessage; 27 import com.google.polo.pairing.message.ConfigurationMessage; 28 import com.google.polo.pairing.message.EncodingOption; 29 import com.google.polo.pairing.message.EncodingOption.EncodingType; 30 import com.google.polo.pairing.message.OptionsMessage; 31 import com.google.polo.pairing.message.OptionsMessage.ProtocolRole; 32 import com.google.polo.pairing.message.PairingRequestAckMessage; 33 import com.google.polo.pairing.message.PairingRequestMessage; 34 import com.google.polo.pairing.message.PoloMessage; 35 import com.google.polo.pairing.message.PoloMessage.PoloMessageType; 36 import com.google.polo.pairing.message.SecretAckMessage; 37 import com.google.polo.pairing.message.SecretMessage; 38 39 import java.io.UnsupportedEncodingException; 40 import java.nio.charset.Charset; 41 42 /** 43 * A collection of methods to convert {@link PoloMessage}s to and from JSON 44 * format. 45 * <p> 46 * Messages are based on the descriptions found in the file polo.proto. This 47 * mimics the field name and message compositions found in that file. 48 */ 49 public class JsonMessageBuilder { 50 51 public static final int PROTOCOL_VERSION = 1; 52 53 /* 54 * Status types. These match the values defined by OuterMessage.MessageType 55 * in polo.proto. 56 */ 57 58 public static final int STATUS_OK = 200; 59 public static final int STATUS_ERROR = 400; 60 public static final int STATUS_BAD_CONFIGURATION = 401; 61 public static final int STATUS_BAD_SECRET = 403; 62 63 /* 64 * Key names for JSON versions of messages. 65 */ 66 67 // OuterMessage JSON key names 68 private static final String OUTER_FIELD_PAYLOAD = "payload"; 69 private static final String OUTER_FIELD_TYPE = "type"; 70 private static final String OUTER_FIELD_STATUS = "status"; 71 private static final String OUTER_FIELD_PROTOCOL_VERSION = "protocol_version"; 72 73 // PairingRequestMessage JSON key names 74 private static final String PAIRING_REQUEST_FIELD_SERVICE_NAME = 75 "service_name"; 76 private static final String PAIRING_REQUEST_FIELD_CLIENT_NAME = 77 "client_name"; 78 79 // PairingRequestAckMessage JSON key names 80 private static final String PAIRING_REQUEST_ACK_FIELD_SERVER_NAME = 81 "server_name"; 82 83 // OptionsMessage JSON key names 84 private static final String OPTIONS_FIELD_PREFERRED_ROLE = "preferred_role"; 85 private static final String OPTIONS_FIELD_OUTPUT_ENCODINGS = 86 "output_encodings"; 87 private static final String OPTIONS_FIELD_INPUT_ENCODINGS = "input_encodings"; 88 89 // ConfigurationMessage JSON key names 90 private static final String CONFIG_FIELD_CLIENT_ROLE = "client_role"; 91 private static final String CONFIG_FIELD_ENCODING = "encoding"; 92 93 // EncodingOption JSON key names 94 private static final String ENCODING_FIELD_TYPE = "type"; 95 private static final String ENCODING_FIELD_SYMBOL_LENGTH = "symbol_length"; 96 97 // SecretMessage JSON key names 98 private static final String SECRET_FIELD_SECRET = "secret"; 99 100 // SecretAckMessage JSON key names 101 private static final String SECRET_ACK_FIELD_SECRET = "secret"; 102 103 104 /** 105 * Builds a {@link PoloMessage} from the JSON version of the outer message. 106 * 107 * @param outerMessage a {@link JSONObject} corresponding to the 108 * outermost wire message 109 * @return a new {@link PoloMessage} 110 * @throws PoloException on error parsing the {@link JSONObject} 111 */ outerJsonToPoloMessage(JSONObject outerMessage)112 public static PoloMessage outerJsonToPoloMessage(JSONObject outerMessage) 113 throws PoloException { 114 JSONObject payload; 115 int status; 116 PoloMessageType messageType; 117 118 try { 119 status = outerMessage.getInt(OUTER_FIELD_STATUS); 120 if (status != STATUS_OK) { 121 throw new ProtocolErrorException("Peer reported an error."); 122 } 123 payload = outerMessage.getJSONObject(OUTER_FIELD_PAYLOAD); 124 int msgIntVal = outerMessage.getInt(OUTER_FIELD_TYPE); 125 messageType = PoloMessageType.fromIntVal(msgIntVal); 126 } catch (JSONException e) { 127 throw new PoloException("Bad outer message.", e); 128 } 129 130 switch (messageType) { 131 case PAIRING_REQUEST: 132 return getPairingRequest(payload); 133 case PAIRING_REQUEST_ACK: 134 return getPairingRequestAck(payload); 135 case OPTIONS: 136 return getOptionsMessage(payload); 137 case CONFIGURATION: 138 return getConfigMessage(payload); 139 case CONFIGURATION_ACK: 140 return getConfigAckMessage(payload); 141 case SECRET: 142 return getSecretMessage(payload); 143 case SECRET_ACK: 144 return getSecretAckMessage(payload); 145 default: 146 return null; 147 } 148 } 149 150 // 151 // Methods to convert JSON messages to PoloMessage instances 152 // 153 154 /** 155 * Generates a new {@link PairingRequestMessage} from a JSON payload. 156 * 157 * @param body the JSON payload 158 * @return the new message 159 * @throws PoloException on error parsing the {@link JSONObject} 160 */ getPairingRequest(JSONObject body)161 static PairingRequestMessage getPairingRequest(JSONObject body) 162 throws PoloException { 163 try { 164 String serviceName = body.getString(PAIRING_REQUEST_FIELD_SERVICE_NAME); 165 String clientName = null; 166 if (body.has(PAIRING_REQUEST_FIELD_CLIENT_NAME)) { 167 clientName = body.getString(PAIRING_REQUEST_FIELD_CLIENT_NAME); 168 } 169 return new PairingRequestMessage(serviceName, clientName); 170 } catch (JSONException e) { 171 throw new PoloException("Malformed message.", e); 172 } 173 } 174 175 /** 176 * Generates a new {@link PairingRequestAckMessage} from a JSON payload. 177 * 178 * @param body the JSON payload 179 * @return the new message 180 */ getPairingRequestAck(JSONObject body)181 static PairingRequestAckMessage getPairingRequestAck(JSONObject body) 182 throws PoloException { 183 try { 184 String serverName = null; 185 if (body.has(PAIRING_REQUEST_ACK_FIELD_SERVER_NAME)) { 186 serverName = body.getString(PAIRING_REQUEST_ACK_FIELD_SERVER_NAME); 187 } 188 return new PairingRequestAckMessage(serverName); 189 } catch (JSONException e) { 190 throw new PoloException("Malformed message.", e); 191 } 192 } 193 194 /** 195 * Generates a new {@link OptionsMessage} from a JSON payload. 196 * 197 * @param body the JSON payload 198 * @return the new message 199 * @throws PoloException on error parsing the {@link JSONObject} 200 */ getOptionsMessage(JSONObject body)201 static OptionsMessage getOptionsMessage(JSONObject body) 202 throws PoloException { 203 OptionsMessage options = new OptionsMessage(); 204 try { 205 // Input encodings 206 JSONArray inEncodings = new JSONArray(); 207 try { 208 if (body.has(OPTIONS_FIELD_INPUT_ENCODINGS)) { 209 inEncodings = body.getJSONArray(OPTIONS_FIELD_INPUT_ENCODINGS); 210 } 211 } catch (JSONException e) { 212 throw new PoloException("Bad input encodings", e); 213 } 214 215 for (int i = 0; i < inEncodings.length(); i++) { 216 JSONObject enc = inEncodings.getJSONObject(i); 217 options.addInputEncoding(getEncodingOption(enc)); 218 } 219 220 // Output encodings 221 JSONArray outEncodings = new JSONArray(); 222 try { 223 if (body.has(OPTIONS_FIELD_OUTPUT_ENCODINGS)) { 224 outEncodings = body.getJSONArray(OPTIONS_FIELD_OUTPUT_ENCODINGS); 225 } 226 } catch (JSONException e) { 227 throw new PoloException("Bad output encodings", e); 228 } 229 230 for (int i = 0; i < outEncodings.length(); i++) { 231 JSONObject enc = outEncodings.getJSONObject(i); 232 options.addOutputEncoding(getEncodingOption(enc)); 233 } 234 235 // Role 236 ProtocolRole role = ProtocolRole.fromIntVal( 237 body.getInt(OPTIONS_FIELD_PREFERRED_ROLE)); 238 options.setProtocolRolePreference(role); 239 } catch (JSONException e) { 240 throw new PoloException("Malformed message.", e); 241 } 242 243 return options; 244 } 245 246 /** 247 * Generates a new {@link ConfigurationMessage} from a JSON payload. 248 * 249 * @param body the JSON payload 250 * @return the new message 251 * @throws PoloException on error parsing the {@link JSONObject} 252 */ getConfigMessage(JSONObject body)253 static ConfigurationMessage getConfigMessage(JSONObject body) 254 throws PoloException { 255 try { 256 EncodingOption encoding = getEncodingOption( 257 body.getJSONObject(CONFIG_FIELD_ENCODING)); 258 ProtocolRole role = ProtocolRole.fromIntVal( 259 body.getInt(CONFIG_FIELD_CLIENT_ROLE)); 260 return new ConfigurationMessage(encoding, role); 261 } catch (JSONException e) { 262 throw new PoloException("Malformed message.", e); 263 } 264 } 265 266 /** 267 * Generates a new {@link ConfigurationAckMessage} from a JSON payload. 268 * 269 * @param body the JSON payload 270 * @return the new message 271 */ getConfigAckMessage(JSONObject body)272 static ConfigurationAckMessage getConfigAckMessage(JSONObject body) { 273 return new ConfigurationAckMessage(); 274 } 275 276 /** 277 * Generates a new {@link SecretMessage} from a JSON payload. 278 * 279 * @param body the JSON payload 280 * @return the new message 281 * @throws PoloException on error parsing the {@link JSONObject} 282 */ getSecretMessage(JSONObject body)283 static SecretMessage getSecretMessage(JSONObject body) throws PoloException { 284 try { 285 byte[] secretBytes = Base64.decode( 286 body.getString(SECRET_FIELD_SECRET).getBytes()); 287 return new SecretMessage(secretBytes); 288 } catch (JSONException e) { 289 throw new PoloException("Malformed message.", e); 290 } 291 } 292 293 /** 294 * Generates a new {@link SecretAckMessage} from a JSON payload. 295 * 296 * @param body the JSON payload 297 * @return the new message 298 * @throws PoloException on error parsing the {@link JSONObject} 299 */ getSecretAckMessage(JSONObject body)300 static SecretAckMessage getSecretAckMessage(JSONObject body) 301 throws PoloException { 302 try { 303 byte[] secretBytes = Base64.decode( 304 body.getString(SECRET_ACK_FIELD_SECRET).getBytes()); 305 return new SecretAckMessage(secretBytes); 306 } catch (JSONException e) { 307 throw new PoloException("Malformed message.", e); 308 } 309 } 310 311 /** 312 * Generates a new {@link EncodingOption} from a JSON sub-dictionary. 313 * 314 * @param option the JSON sub-dictionary describing the option 315 * @return the new {@link EncodingOption} 316 * @throws JSONException on error parsing the {@link JSONObject} 317 */ getEncodingOption(JSONObject option)318 static EncodingOption getEncodingOption(JSONObject option) 319 throws JSONException { 320 int length = option.getInt(ENCODING_FIELD_SYMBOL_LENGTH); 321 int intType = option.getInt(ENCODING_FIELD_TYPE); 322 EncodingType type = EncodingType.fromIntVal(intType); 323 return new EncodingOption(type, length); 324 } 325 326 /** 327 * Converts a {@link PoloMessage} to a {@link JSONObject} 328 * 329 * @param message the message to convert 330 * @return the same message, as translated to JSON 331 * @throws PoloException if the message could not be generated 332 */ poloMessageToJson(PoloMessage message)333 public static JSONObject poloMessageToJson(PoloMessage message) 334 throws PoloException { 335 try { 336 if (message instanceof PairingRequestMessage) { 337 return toJson((PairingRequestMessage) message); 338 } else if (message instanceof PairingRequestAckMessage) { 339 return toJson((PairingRequestAckMessage) message); 340 } else if (message instanceof OptionsMessage) { 341 return toJson((OptionsMessage) message); 342 } else if (message instanceof ConfigurationMessage) { 343 return toJson((ConfigurationMessage) message); 344 } else if (message instanceof ConfigurationAckMessage) { 345 return toJson((ConfigurationAckMessage) message); 346 } else if (message instanceof SecretMessage) { 347 return toJson((SecretMessage) message); 348 } else if (message instanceof SecretAckMessage) { 349 return toJson((SecretAckMessage) message); 350 } 351 } catch (JSONException e) { 352 throw new PoloException("Error generating message.", e); 353 } 354 throw new PoloException("Unknown PoloMessage type."); 355 } 356 357 /** 358 * Generates a JSONObject corresponding to a full wire message (wrapped in 359 * an outer message) for the given payload. 360 * 361 * @param message the payload to wrap 362 * @return a {@link JSONObject} corresponding to the complete 363 * wire message 364 * @throws PoloException on error building the {@link JSONObject} 365 */ getOuterJson(PoloMessage message)366 public static JSONObject getOuterJson(PoloMessage message) 367 throws PoloException { 368 JSONObject out = new JSONObject(); 369 int msgType = message.getType().getAsInt(); 370 JSONObject innerJson = poloMessageToJson(message); 371 372 try { 373 out.put(OUTER_FIELD_PROTOCOL_VERSION, PROTOCOL_VERSION); 374 out.put(OUTER_FIELD_STATUS, STATUS_OK); 375 out.put(OUTER_FIELD_TYPE, msgType); 376 out.put(OUTER_FIELD_PAYLOAD, innerJson); 377 } catch (JSONException e) { 378 throw new PoloException("Error serializing outer message", e); 379 } 380 return out; 381 } 382 383 /** 384 * Generates a {@link JSONObject} corresponding to a wire message with an 385 * error code in the status field. The error code is determined by the type 386 * of the exception. 387 * 388 * @param exception the {@link Exception} to use to determine the error 389 * code 390 * @return a {@link JSONObject} corresponding to the complete 391 * wire message 392 * @throws PoloException on error building the {@link JSONObject} 393 */ getErrorJson(Exception exception)394 public static JSONObject getErrorJson(Exception exception) 395 throws PoloException { 396 JSONObject out = new JSONObject(); 397 398 int errorStatus = STATUS_ERROR; 399 400 if (exception instanceof NoConfigurationException) { 401 errorStatus = STATUS_BAD_CONFIGURATION; 402 } else if (exception instanceof BadSecretException) { 403 errorStatus = STATUS_BAD_SECRET; 404 } 405 406 try { 407 out.put(OUTER_FIELD_PROTOCOL_VERSION, PROTOCOL_VERSION); 408 out.put(OUTER_FIELD_STATUS, errorStatus); 409 } catch (JSONException e) { 410 throw new PoloException("Error serializing outer message", e); 411 } 412 return out; 413 414 } 415 416 /** 417 * Translates a {@link PairingRequestMessage} to a {@link JSONObject}. 418 * 419 * @throws JSONException on error generating the {@link JSONObject} 420 */ toJson(PairingRequestMessage message)421 static JSONObject toJson(PairingRequestMessage message) throws JSONException { 422 JSONObject jsonObj = new JSONObject(); 423 jsonObj.put(PAIRING_REQUEST_FIELD_SERVICE_NAME, message.getServiceName()); 424 if (message.hasClientName()) { 425 jsonObj.put(PAIRING_REQUEST_FIELD_CLIENT_NAME, message.getClientName()); 426 } 427 return jsonObj; 428 } 429 430 /** 431 * Translates a {@link PairingRequestAckMessage} to a {@link JSONObject}. 432 * @throws JSONException 433 */ toJson(PairingRequestAckMessage message)434 static JSONObject toJson(PairingRequestAckMessage message) 435 throws JSONException { 436 JSONObject jsonObj = new JSONObject(); 437 if (message.hasServerName()) { 438 jsonObj.put(PAIRING_REQUEST_ACK_FIELD_SERVER_NAME, 439 message.getServerName()); 440 } 441 return jsonObj; 442 } 443 444 /** 445 * Translates a {@link OptionsMessage} to a {@link JSONObject}. 446 * 447 * @throws JSONException on error generating the {@link JSONObject} 448 */ toJson(OptionsMessage message)449 static JSONObject toJson(OptionsMessage message) throws JSONException { 450 JSONObject jsonObj = new JSONObject(); 451 452 JSONArray inEncsArray = new JSONArray(); 453 for (EncodingOption encoding : message.getInputEncodingSet()) { 454 inEncsArray.put(toJson(encoding)); 455 } 456 jsonObj.put(OPTIONS_FIELD_INPUT_ENCODINGS, inEncsArray); 457 458 JSONArray outEncsArray = new JSONArray(); 459 for (EncodingOption encoding : message.getOutputEncodingSet()) { 460 outEncsArray.put(toJson(encoding)); 461 } 462 jsonObj.put(OPTIONS_FIELD_OUTPUT_ENCODINGS, outEncsArray); 463 464 int intRole = message.getProtocolRolePreference().getAsInt(); 465 jsonObj.put(OPTIONS_FIELD_PREFERRED_ROLE, intRole); 466 return jsonObj; 467 } 468 469 /** 470 * Translates a {@link ConfigurationMessage} to a {@link JSONObject}. 471 * 472 * @throws JSONException on error generating the {@link JSONObject} 473 */ toJson(ConfigurationMessage message)474 static JSONObject toJson(ConfigurationMessage message) throws JSONException { 475 JSONObject jsonObj = new JSONObject(); 476 JSONObject encoding = toJson(message.getEncoding()); 477 jsonObj.put(CONFIG_FIELD_ENCODING, encoding); 478 int intRole = message.getClientRole().getAsInt(); 479 jsonObj.put(CONFIG_FIELD_CLIENT_ROLE, intRole); 480 return jsonObj; 481 } 482 483 /** 484 * Translates a {@link ConfigurationAckMessage} to a {@link JSONObject}. 485 */ toJson(ConfigurationAckMessage message)486 static JSONObject toJson(ConfigurationAckMessage message) { 487 return new JSONObject(); 488 } 489 490 /** 491 * Translates a {@link SecretMessage} to a {@link JSONObject}. 492 * 493 * @throws JSONException on error generating the {@link JSONObject} 494 */ toJson(SecretMessage message)495 static JSONObject toJson(SecretMessage message) throws JSONException { 496 JSONObject jsonObj = new JSONObject(); 497 String bytesStr; 498 String charsetName = Charset.defaultCharset().name(); 499 try { 500 bytesStr = new String(Base64.encode(message.getSecret(), charsetName)); 501 } catch (UnsupportedEncodingException e) { 502 // Should never happen. 503 bytesStr = ""; 504 } 505 jsonObj.put(SECRET_FIELD_SECRET, bytesStr); 506 return jsonObj; 507 } 508 509 /** 510 * Translates a {@link SecretAckMessage} to a {@link JSONObject}. 511 * 512 * @throws JSONException on error generating the {@link JSONObject} 513 */ toJson(SecretAckMessage message)514 static JSONObject toJson(SecretAckMessage message) throws JSONException { 515 JSONObject jsonObj = new JSONObject(); 516 String bytesStr; 517 String charsetName = Charset.defaultCharset().name(); 518 try { 519 bytesStr = new String(Base64.encode(message.getSecret(), charsetName)); 520 } catch (UnsupportedEncodingException e) { 521 // Should never happen. 522 bytesStr = ""; 523 } 524 jsonObj.put(SECRET_ACK_FIELD_SECRET, bytesStr); 525 return jsonObj; 526 } 527 528 /** 529 * Translates a {@link EncodingOption} to a {@link JSONObject}. 530 * 531 * @throws JSONException on error generating the {@link JSONObject} 532 */ toJson(EncodingOption encoding)533 static JSONObject toJson(EncodingOption encoding) throws JSONException { 534 JSONObject result = new JSONObject(); 535 int intType = encoding.getType().getAsInt(); 536 result.put(ENCODING_FIELD_TYPE, intType); 537 result.put(ENCODING_FIELD_SYMBOL_LENGTH, encoding.getSymbolLength()); 538 return result; 539 } 540 541 } 542