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
17package com.google.polo.wire.json;
18
19import com.google.polo.exception.BadSecretException;
20import com.google.polo.exception.NoConfigurationException;
21import com.google.polo.exception.PoloException;
22import com.google.polo.exception.ProtocolErrorException;
23import com.google.polo.json.JSONArray;
24import com.google.polo.json.JSONException;
25import com.google.polo.json.JSONObject;
26import com.google.polo.pairing.message.ConfigurationAckMessage;
27import com.google.polo.pairing.message.ConfigurationMessage;
28import com.google.polo.pairing.message.EncodingOption;
29import com.google.polo.pairing.message.EncodingOption.EncodingType;
30import com.google.polo.pairing.message.OptionsMessage;
31import com.google.polo.pairing.message.OptionsMessage.ProtocolRole;
32import com.google.polo.pairing.message.PairingRequestAckMessage;
33import com.google.polo.pairing.message.PairingRequestMessage;
34import com.google.polo.pairing.message.PoloMessage;
35import com.google.polo.pairing.message.PoloMessage.PoloMessageType;
36import com.google.polo.pairing.message.SecretAckMessage;
37import com.google.polo.pairing.message.SecretMessage;
38
39import java.io.UnsupportedEncodingException;
40import 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 */
49public 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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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