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