package org.bitseal.core; import org.bitseal.data.BMObject; import org.bitseal.pow.POWProcessor; import org.bitseal.util.ArrayCopier; import org.bitseal.util.ByteFormatter; import org.bitseal.util.ByteUtils; import org.bitseal.util.TimeUtils; import org.bitseal.util.VarintEncoder; /** * A class which provides various methods used for processing * Bitmessage Objects within Bitseal. * * @author Jonathan Coe */ public class ObjectProcessor { private static final long MAX_TIME_TILL_EXPIRATION = 2430000; // 28 days and 3 hours private static final int MIN_VALID_OBJECT_TYPE = 0; private static final int MAX_VALID_OBJECT_TYPE = 3; private static final int MIN_VALID_OBJECT_VERSION = 1; private static final int MAX_VALID_OBJECT_VERSION = 4; private static final int MIN_VALID_STREAM_NUMBER = 1; private static final int MAX_VALID_STREAM_NUMBER = 1; /** In Bitmessage protocol version 3, the network standard value for nonce trials per byte is 1000. */ public static final int NETWORK_NONCE_TRIALS_PER_BYTE = 1000; /** In Bitmessage protocol version 3, the network standard value for extra bytes is 1000. */ public static final int NETWORK_EXTRA_BYTES = 1000; /** * Validates a set of bytes that may contain a Bitmessage Object * * @param objectBytes - A byte[] containing the Object data * * @return A boolean indicating whether or not the provided bytes are * a valid Bitmessage Object */ public boolean validateObject (byte[] objectBytes) { try { parseObject(objectBytes); return true; } catch(RuntimeException e) { return false; } } /** * Takes a byte[] containing the data of a Bitmessage Object (e.g. a msg) * and parses it, returning a BMObject. * * @param objectBytes - A byte[] containing the Object data * * @return A BMObject created from the parsed data */ public BMObject parseObject (byte[] objectBytes) { // Read the POW Nonce int readPosition = 0; long powNonce = ByteUtils.bytesToLong((ArrayCopier.copyOfRange(objectBytes, readPosition, readPosition + 8))); readPosition += 8; //The POW nonce should always be 8 bytes in length // Read and check the expiration time long expirationTime = ByteUtils.bytesToLong((ArrayCopier.copyOfRange(objectBytes, readPosition, readPosition + 8))); readPosition += 8; long currentTime = System.currentTimeMillis() / 1000; if (expirationTime < currentTime) { throw new RuntimeException("While running ObjectProcessor.parseObject(), it was found that the object's expiration time passed " + TimeUtils.getTimeMessage(currentTime - expirationTime) + " ago.\n" + "The full object containing the passed expriation time was: " + ByteFormatter.byteArrayToHexString(objectBytes)); } else if (expirationTime > currentTime + MAX_TIME_TILL_EXPIRATION) { throw new RuntimeException("While running ObjectProcessor.parseObject(), the embedded expiration time was found to be too far in the future. \n" + "The embedded expiration time was " + expirationTime + ", which is " + TimeUtils.getTimeMessage(expirationTime - currentTime) + " in the future.\n" + "The full object containing the invalid expiration time was: " + ByteFormatter.byteArrayToHexString(objectBytes)); } // Read and check the object type int objectType = ByteUtils.bytesToInt((ArrayCopier.copyOfRange(objectBytes, readPosition, readPosition + 4))); readPosition += 4; if (objectType < MIN_VALID_OBJECT_TYPE || objectType > MAX_VALID_OBJECT_TYPE) { throw new RuntimeException("While running ObjectProcessor.parseObject(), the decoded object type number was invalid. The invalid value was " + objectType + ".\n" + "The full object containing the invalid object type number was: " + ByteFormatter.byteArrayToHexString(objectBytes)); } // Read and check the object version long[] decoded = VarintEncoder.decode(ArrayCopier.copyOfRange(objectBytes, readPosition, readPosition + 9)); // Take 9 bytes, the maximum length for an encoded var_int int objectVersion = (int) decoded[0]; // Get the var_int encoded value readPosition += (int) decoded[1]; // Find out how many bytes the var_int was in length and adjust the read position accordingly if (objectVersion < MIN_VALID_OBJECT_VERSION || objectVersion > MAX_VALID_OBJECT_VERSION) { throw new RuntimeException("While running ObjectProcessor.parseObject(), the decoded object version number was invalid. The invalid value was " + objectVersion + ".\n" + "The full object containing the invalid object version number was: " + ByteFormatter.byteArrayToHexString(objectBytes)); } // Read and check the stream number decoded = VarintEncoder.decode(ArrayCopier.copyOfRange(objectBytes, readPosition, readPosition + 9)); // Take 9 bytes, the maximum length for an encoded var_int int streamNumber = (int) decoded[0]; // Get the var_int encoded value readPosition += (int) decoded[1]; // Find out how many bytes the var_int was in length and adjust the read position accordingly if (streamNumber < MIN_VALID_STREAM_NUMBER || streamNumber > MAX_VALID_STREAM_NUMBER) { throw new RuntimeException("While running ObjectProcessor.parseObject(), the decoded object stream number was invalid. The invalid value was " + streamNumber + ".\n" + "The full object containing the invalid stream number was: " + ByteFormatter.byteArrayToHexString(objectBytes)); } // Read the remaining data. byte[] payload = ArrayCopier.copyOfRange(objectBytes, readPosition, objectBytes.length); // Check whether the POW for this Object is valid byte[] powPayload = ArrayCopier.copyOfRange(objectBytes, 8, objectBytes.length); boolean powValid = new POWProcessor().checkPOW(powPayload, powNonce, expirationTime, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES); if (powValid == false) { throw new RuntimeException("While running ObjectProcessor.parseObject(), the POW nonce was found to be invalid. The invalid value was " + powNonce + ".\n" + "The full object containing the invalid POW nonce was: " + ByteFormatter.byteArrayToHexString(objectBytes)); } // Create a new BMObject and use the parsed data to populate its fields BMObject bmObject = new BMObject(); bmObject.setBelongsToMe(false); // i.e. this BMObject was not created by me bmObject.setPOWNonce(powNonce); bmObject.setExpirationTime(expirationTime); bmObject.setObjectType(objectType); bmObject.setObjectVersion(objectVersion); bmObject.setStreamNumber(streamNumber); bmObject.setPayload(payload); return bmObject; } }