/* * Copyright 2012 The Stanford MobiSocial Laboratory * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package mobisocial.musubi.encoding; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import mobisocial.musubi.encoding.DiscardMessage.BadObjFormat; import mobisocial.musubi.model.MApp; import mobisocial.musubi.model.MDevice; import mobisocial.musubi.model.MFeed; import mobisocial.musubi.model.MFeed.FeedType; import mobisocial.musubi.model.MIdentity; import mobisocial.musubi.model.MObject; import mobisocial.socialkit.Obj; import mobisocial.socialkit.obj.MemObj; import org.codehaus.jackson.map.DeserializationConfig.Feature; import org.codehaus.jackson.map.ObjectMapper; import org.json.JSONException; import org.json.JSONObject; import de.undercouch.bson4jackson.BsonParser; import de.undercouch.bson4jackson.BsonFactory; /** * <p>Encodes and decodes data available to Musubi for transmission across the network. * * <p>Data is encoded as follows. The first four bytes are the int value 30050081, * indicating the use of {@see ObjFormat}. The next four bytes are reserved. * The following n bytes are a bson encoded data structure. */ public class ObjEncoder { public static final int VERSION_HEADER = 30050081; public static final int DEFAULT_FLAGS = 0x0; private static final int HEADER_GUESSTIMATE = 200; private static ObjectMapper sMapper; // final but lazy public static byte[] encode(ObjFormat decoded) { int approx = decoded.raw == null ? HEADER_GUESSTIMATE : decoded.raw.length + HEADER_GUESSTIMATE; ByteArrayOutputStream byteOut = new ByteArrayOutputStream(approx); DataOutputStream dataOut = new DataOutputStream(byteOut); try { dataOut.writeInt(VERSION_HEADER); dataOut.writeInt(DEFAULT_FLAGS); dataOut.write(getObjectMapper().writeValueAsBytes(decoded)); return byteOut.toByteArray(); } catch (IOException e) { throw new RuntimeException("Bad encoding", e); } finally { try { byteOut.close(); dataOut.close(); } catch (IOException e) {} } } public static ObjFormat decode(byte[] encoded) throws BadObjFormat { ObjFormat obj = null; ByteArrayInputStream inputStream = new ByteArrayInputStream(encoded); DataInputStream dataStream = new DataInputStream(inputStream); try { int version = dataStream.readInt(); if (version != VERSION_HEADER) { throw new BadObjFormat("Bad version header " + version); } dataStream.readInt(); // RFU flags obj = getObjectMapper().readValue(dataStream, ObjFormat.class); } catch (IOException e) { throw new BadObjFormat("Bad encoding", e); } finally { try { dataStream.close(); inputStream.close(); } catch (IOException e) {} } return obj; } public static void populate(MObject object, Obj with) { object.type_ = with.getType(); if (with.getJson() != null) { object.json_ = with.getJson().toString(); } object.raw_ = with.getRaw(); object.intKey_ = with.getIntKey(); object.stringKey_ = with.getStringKey(); } public static byte[] computeUniversalHash(MIdentity from, MDevice device, byte[] hash) { MessageDigest md; try { md = MessageDigest.getInstance("SHA-256"); md.update((byte)from.type_.ordinal()); md.update(from.principalHash_); byte[] deviceName = new byte[8]; ByteBuffer buf = ByteBuffer.wrap(deviceName); buf.putLong(device.deviceName_); md.update(deviceName); md.update(hash); return md.digest(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("your platform does not support sha256", e); } } public static ObjFormat getPreparedObj(MApp app, MFeed feed, MObject object) throws DiscardMessage { JSONObject json = null; if (object.json_ != null) { try { json = new JSONObject(object.json_); } catch (JSONException e) { throw new DiscardMessage.Corrupted("Bad json", e); } } Obj data = new MemObj(object.type_, json, object.raw_, object.intKey_, object.stringKey_); FeedType feedType = feed.type_; byte[] feedCapability = feed.capability_; return new ObjFormat(feedType, feedCapability, app.appId_, object.timestamp_, data); } private static ObjectMapper getObjectMapper() { if (sMapper == null) { sMapper = new ObjectMapper(new BsonFactory().enable(BsonParser.Feature.HONOR_DOCUMENT_LENGTH)). configure(Feature.FAIL_ON_UNKNOWN_PROPERTIES, false); sMapper.configure(Feature.FAIL_ON_UNKNOWN_PROPERTIES, false); } return sMapper; } }