Home | History | Annotate | Download | only in json
      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    */
    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