diff --git a/pom.xml b/pom.xml index 4fbe583..23a7968 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.gossiperl gossiperl-core - 1.0.0 + 2.0.0 jar @@ -14,6 +14,9 @@ 0.9.2 3.0 2.7 + 4.12 + 1.7.7 + 1.2.16 @@ -48,6 +51,31 @@ libthrift ${thrift.version} + + junit + junit + ${junit.version} + + + org.slf4j + slf4j-api + ${slf4j-api.version} + + + org.slf4j + jcl-over-slf4j + ${slf4j-api.version} + + + org.slf4j + slf4j-log4j12 + ${slf4j-api.version} + + + log4j + log4j + ${log4j.version} + \ No newline at end of file diff --git a/src/main/java/com/gossiperl/client/Util.java b/src/main/java/com/gossiperl/client/Util.java new file mode 100644 index 0000000..0bbb481 --- /dev/null +++ b/src/main/java/com/gossiperl/client/Util.java @@ -0,0 +1,7 @@ +package com.gossiperl.client; + +public class Util { + public static long getTimestamp() { + return (long)System.currentTimeMillis()/1000; + } +} diff --git a/src/main/java/com/gossiperl/client/encryption/Aes256.java b/src/main/java/com/gossiperl/client/encryption/Aes256.java new file mode 100644 index 0000000..b6b32f6 --- /dev/null +++ b/src/main/java/com/gossiperl/client/encryption/Aes256.java @@ -0,0 +1,58 @@ +package com.gossiperl.client.encryption; + +import org.apache.log4j.Logger; + +import javax.crypto.*; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.*; +import java.security.*; + +public class Aes256 { + + private SecretKeySpec key; + private static Logger LOG = Logger.getLogger(Aes256.class); + + public Aes256(String key) throws NoSuchAlgorithmException, UnsupportedEncodingException { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + byte[] digestBytes = md.digest(key.getBytes("utf-8")); + this.key = new SecretKeySpec(digestBytes, "AES"); + } + + public byte[] encrypt(byte[] data) throws NoSuchAlgorithmException, + NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, + IllegalBlockSizeException, BadPaddingException, NoSuchProviderException { + byte[] ivBytes = generateIv(); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, this.key, new IvParameterSpec(ivBytes)); + try { + byte[] encrypted = cipher.doFinal(data); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + outputStream.write(ivBytes); + outputStream.write(encrypted); + return outputStream.toByteArray(); + } catch (Exception ex) { + return null; + } + } + + public byte[] decrypt(byte[] data) throws NoSuchAlgorithmException, + NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, + IllegalBlockSizeException, BadPaddingException, NoSuchProviderException { + byte[] ivBytes = new byte[16]; + byte[] message = new byte[ data.length - 16 ]; + System.arraycopy(data, 0, ivBytes, 0, ivBytes.length); + System.arraycopy(data, ivBytes.length, message, 0, message.length); + Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); + cipher.init(Cipher.DECRYPT_MODE, this.key, new IvParameterSpec(ivBytes)); + return cipher.doFinal(message); + } + + private byte[] generateIv() { + SecureRandom random = new SecureRandom(); + byte[] ivBytes = new byte[16]; + random.nextBytes(ivBytes); + return ivBytes; + } + +} diff --git a/src/main/java/com/gossiperl/client/exceptions/GossiperlClientException.java b/src/main/java/com/gossiperl/client/exceptions/GossiperlClientException.java new file mode 100644 index 0000000..877ce9c --- /dev/null +++ b/src/main/java/com/gossiperl/client/exceptions/GossiperlClientException.java @@ -0,0 +1,8 @@ +package com.gossiperl.client.exceptions; + +public class GossiperlClientException extends Exception { + public GossiperlClientException() {} + public GossiperlClientException(String message) { super(message); } + public GossiperlClientException(Throwable cause) { super(cause); } + public GossiperlClientException(String message, Throwable cause) { super(message, cause); } +} diff --git a/src/main/java/com/gossiperl/client/exceptions/GossiperlUnsupportedSerializableTypeException.java b/src/main/java/com/gossiperl/client/exceptions/GossiperlUnsupportedSerializableTypeException.java new file mode 100644 index 0000000..907f3a1 --- /dev/null +++ b/src/main/java/com/gossiperl/client/exceptions/GossiperlUnsupportedSerializableTypeException.java @@ -0,0 +1,5 @@ +package com.gossiperl.client.exceptions; + +public class GossiperlUnsupportedSerializableTypeException extends Exception { + public GossiperlUnsupportedSerializableTypeException(String message) { super(message); } +} diff --git a/src/main/java/com/gossiperl/client/serialization/CustomDigestField.java b/src/main/java/com/gossiperl/client/serialization/CustomDigestField.java new file mode 100644 index 0000000..51547f1 --- /dev/null +++ b/src/main/java/com/gossiperl/client/serialization/CustomDigestField.java @@ -0,0 +1,106 @@ +package com.gossiperl.client.serialization; + +import com.gossiperl.client.exceptions.GossiperlClientException; +import org.apache.thrift.protocol.TType; + +public class CustomDigestField { + + private String fieldName; + private Object value; + private byte type; + private short fieldOrder; + + public CustomDigestField(String fieldName, String value, int fieldOrder) + throws GossiperlClientException { + if ( fieldOrder < 0 || fieldOrder > Short.MAX_VALUE ) { + throw new GossiperlClientException("Field ID must be at least 0 and no greater than " + Short.MAX_VALUE + "."); + } + this.fieldName = fieldName; + this.fieldOrder = (short)fieldOrder; + this.value = value; + this.type = TType.STRING; + } + + public CustomDigestField(String fieldName, boolean value, int fieldOrder) + throws GossiperlClientException { + if ( fieldOrder < 0 || fieldOrder > Short.MAX_VALUE ) { + throw new GossiperlClientException("Field ID must be at least 0 and no greater than " + Short.MAX_VALUE + "."); + } + this.fieldName = fieldName; + this.fieldOrder = (short)fieldOrder; + this.value = new Boolean(value); + this.type = TType.BOOL; + } + + public CustomDigestField(String fieldName, byte value, int fieldOrder) + throws GossiperlClientException { + if ( fieldOrder < 0 || fieldOrder > Short.MAX_VALUE ) { + throw new GossiperlClientException("Field ID must be at least 0 and no greater than " + Short.MAX_VALUE + "."); + } + this.fieldName = fieldName; + this.fieldOrder = (short)fieldOrder; + this.value = new Byte(value); + this.type = TType.BYTE; + } + + public CustomDigestField(String fieldName, double value, int fieldOrder) + throws GossiperlClientException { + if ( fieldOrder < 0 || fieldOrder > Short.MAX_VALUE ) { + throw new GossiperlClientException("Field ID must be at least 0 and no greater than " + Short.MAX_VALUE + "."); + } + this.fieldName = fieldName; + this.fieldOrder = (short)fieldOrder; + this.value = new Double(value); + this.type = TType.DOUBLE; + } + + public CustomDigestField(String fieldName, short value, int fieldOrder) + throws GossiperlClientException { + if ( fieldOrder < 0 || fieldOrder > Short.MAX_VALUE ) { + throw new GossiperlClientException("Field ID must be at least 0 and no greater than " + Short.MAX_VALUE + "."); + } + this.fieldName = fieldName; + this.fieldOrder = (short)fieldOrder; + this.value = new Short(value); + this.type = TType.I16; + } + + public CustomDigestField(String fieldName, int value, int fieldOrder) + throws GossiperlClientException { + if ( fieldOrder < 0 || fieldOrder > Short.MAX_VALUE ) { + throw new GossiperlClientException("Field ID must be at least 0 and no greater than " + Short.MAX_VALUE + "."); + } + this.fieldName = fieldName; + this.fieldOrder = (short)fieldOrder; + this.value = new Integer(value); + this.type = TType.I32; + } + + public CustomDigestField(String fieldName, long value, int fieldOrder) + throws GossiperlClientException { + if ( fieldOrder < 0 || fieldOrder > Short.MAX_VALUE ) { + throw new GossiperlClientException("Field ID must be at least 0 and no greater than " + Short.MAX_VALUE + "."); + } + this.fieldName = fieldName; + this.fieldOrder = (short)fieldOrder; + this.value = new Long(value); + this.type = TType.I64; + } + + public String getFieldName() { + return fieldName; + } + + public Object getValue() { + return value; + } + + public byte getType() { + return type; + } + + public short getFieldOrder() { + return fieldOrder; + } + +} diff --git a/src/main/java/com/gossiperl/client/serialization/DeserializeKillPill.java b/src/main/java/com/gossiperl/client/serialization/DeserializeKillPill.java new file mode 100644 index 0000000..22f86d8 --- /dev/null +++ b/src/main/java/com/gossiperl/client/serialization/DeserializeKillPill.java @@ -0,0 +1,7 @@ +package com.gossiperl.client.serialization; + +/** + * Created by rad on 18/12/14. + */ +public class DeserializeKillPill extends DeserializeResult { +} diff --git a/src/main/java/com/gossiperl/client/serialization/DeserializeResult.java b/src/main/java/com/gossiperl/client/serialization/DeserializeResult.java new file mode 100644 index 0000000..bc2fd85 --- /dev/null +++ b/src/main/java/com/gossiperl/client/serialization/DeserializeResult.java @@ -0,0 +1,4 @@ +package com.gossiperl.client.serialization; + +public abstract class DeserializeResult { +} diff --git a/src/main/java/com/gossiperl/client/serialization/DeserializeResultCustomOK.java b/src/main/java/com/gossiperl/client/serialization/DeserializeResultCustomOK.java new file mode 100644 index 0000000..b78d932 --- /dev/null +++ b/src/main/java/com/gossiperl/client/serialization/DeserializeResultCustomOK.java @@ -0,0 +1,18 @@ +package com.gossiperl.client.serialization; + +import java.util.Map; + +public class DeserializeResultCustomOK extends DeserializeResult { + private String digestType; + private Map resultData; + public DeserializeResultCustomOK(String digestType, Map resultData) { + this.digestType = digestType; + this.resultData = resultData; + } + public String getDigestType() { + return this.digestType; + } + public Map getResultData() { + return this.resultData; + } +} diff --git a/src/main/java/com/gossiperl/client/serialization/DeserializeResultError.java b/src/main/java/com/gossiperl/client/serialization/DeserializeResultError.java new file mode 100644 index 0000000..ea211bb --- /dev/null +++ b/src/main/java/com/gossiperl/client/serialization/DeserializeResultError.java @@ -0,0 +1,13 @@ +package com.gossiperl.client.serialization; + +import com.gossiperl.client.exceptions.GossiperlClientException; + +public class DeserializeResultError extends DeserializeResult { + private GossiperlClientException cause; + public DeserializeResultError(GossiperlClientException ex) { + this.cause = ex; + } + public GossiperlClientException getCause() { + return this.cause; + } +} diff --git a/src/main/java/com/gossiperl/client/serialization/DeserializeResultForward.java b/src/main/java/com/gossiperl/client/serialization/DeserializeResultForward.java new file mode 100644 index 0000000..4af1b07 --- /dev/null +++ b/src/main/java/com/gossiperl/client/serialization/DeserializeResultForward.java @@ -0,0 +1,29 @@ +package com.gossiperl.client.serialization; + +import com.gossiperl.client.thrift.DigestEnvelope; +import org.apache.thrift.TBase; + +public class DeserializeResultForward extends DeserializeResult { + + private String digestType; + private byte[] binaryEnvelope; + private String envelopeId; + + public DeserializeResultForward(String digestType, byte[] binaryEnvelope, String envelopeId) { + this.digestType = digestType; + this.binaryEnvelope = binaryEnvelope; + this.envelopeId = envelopeId; + } + + public String getDigestType() { + return digestType; + } + + public byte[] getBinaryEnvelope() { + return binaryEnvelope; + } + + public String getEnvelopeId() { + return envelopeId; + } +} diff --git a/src/main/java/com/gossiperl/client/serialization/DeserializeResultOK.java b/src/main/java/com/gossiperl/client/serialization/DeserializeResultOK.java new file mode 100644 index 0000000..4111e22 --- /dev/null +++ b/src/main/java/com/gossiperl/client/serialization/DeserializeResultOK.java @@ -0,0 +1,18 @@ +package com.gossiperl.client.serialization; + +import org.apache.thrift.TBase; + +public class DeserializeResultOK extends DeserializeResult { + private String digestType; + private TBase digest; + public DeserializeResultOK(String digestType, TBase digest) { + this.digestType = digestType; + this.digest = digest; + } + public String getDigestType() { + return this.digestType; + } + public TBase getDigest() { + return this.digest; + } +} diff --git a/src/main/java/com/gossiperl/client/serialization/Serializer.java b/src/main/java/com/gossiperl/client/serialization/Serializer.java new file mode 100644 index 0000000..526ad45 --- /dev/null +++ b/src/main/java/com/gossiperl/client/serialization/Serializer.java @@ -0,0 +1,237 @@ +package com.gossiperl.client.serialization; + +import com.gossiperl.client.exceptions.GossiperlClientException; +import com.gossiperl.client.thrift.DigestEnvelope; +import com.sun.org.apache.xerces.internal.impl.dv.util.Base64; +import org.apache.thrift.TBase; +import org.apache.thrift.TException; +import org.apache.thrift.TSerializer; +import org.apache.thrift.protocol.*; +import org.apache.thrift.transport.TIOStreamTransport; +import org.apache.thrift.transport.TMemoryInputTransport; +import org.apache.thrift.transport.TTransport; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.UnsupportedEncodingException; +import java.util.HashMap; +import java.util.List; +import java.util.UUID; + +public class Serializer { + + private HashMap types = new HashMap(); + + public static final String DIGEST_ERROR = "digestError"; + public static final String DIGEST_FORWARDED_ACK = "digestForwardedAck"; + public static final String DIGEST_ENVELOPE = "digestEnvelope"; + public static final String DIGEST = "digest"; + public static final String DIGEST_ACK = "digestAck"; + public static final String DIGEST_SUBSCRIPTIONS = "digestSubscriptions"; + public static final String DIGEST_EXIT = "digestExit"; + public static final String DIGEST_SUBSCRIBE = "digestSubscribe"; + public static final String DIGEST_SUBSCRIBE_ACK = "digestSubscribeAck"; + public static final String DIGEST_UNSUBSCRIBE = "digestUnsubscribe"; + public static final String DIGEST_UNSUBSCRIBE_ACK = "digestUnsubscribeAck"; + public static final String DIGEST_EVENT = "digestEvent"; + + public Serializer() { + this.types.put(DIGEST_ERROR, com.gossiperl.client.thrift.DigestError.class); + this.types.put(DIGEST_FORWARDED_ACK, com.gossiperl.client.thrift.DigestForwardedAck.class); + this.types.put(DIGEST_ENVELOPE, com.gossiperl.client.thrift.DigestEnvelope.class); + this.types.put(DIGEST, com.gossiperl.client.thrift.Digest.class); + this.types.put(DIGEST_ACK, com.gossiperl.client.thrift.DigestAck.class); + this.types.put(DIGEST_SUBSCRIPTIONS, com.gossiperl.client.thrift.DigestSubscriptions.class); + this.types.put(DIGEST_EXIT, com.gossiperl.client.thrift.DigestExit.class); + this.types.put(DIGEST_SUBSCRIBE, com.gossiperl.client.thrift.DigestSubscribe.class); + this.types.put(DIGEST_SUBSCRIBE_ACK, com.gossiperl.client.thrift.DigestSubscribeAck.class); + this.types.put(DIGEST_UNSUBSCRIBE, com.gossiperl.client.thrift.DigestUnsubscribe.class); + this.types.put(DIGEST_UNSUBSCRIBE_ACK, com.gossiperl.client.thrift.DigestUnsubscribeAck.class); + this.types.put(DIGEST_EVENT, com.gossiperl.client.thrift.DigestEvent.class); + + + } + + public byte[] serializeArbitrary(String digestType, List digestData) throws TException, GossiperlClientException { + return this.serializeArbitrary( digestType, digestData, 1024 ); + } + + public byte[] serializeArbitrary(String digestType, List digestData, int thriftWindowSize) + throws TException, GossiperlClientException { + ByteArrayOutputStream stream = new ByteArrayOutputStream( thriftWindowSize ); + TTransport transport = new TIOStreamTransport( stream ); + TProtocol protocol = new TBinaryProtocol(transport); + protocol.writeStructBegin(new TStruct( digestType )); + for (CustomDigestField field : digestData) { + protocol.writeFieldBegin( new TField( field.getFieldName(), field.getType(), field.getFieldOrder() )); + if ( field.getType() == TType.BOOL ) { + try { + protocol.writeBool( ((Boolean)field.getValue()).booleanValue() ); + } catch (Exception ex) { + throw new GossiperlClientException("Failed to write value " + field.getValue().toString() + " as BOOL.", ex); + } + } else if ( field.getType() == TType.BYTE ) { + try { + protocol.writeByte(((Byte) field.getValue()).byteValue()); + } catch (Exception ex) { + throw new GossiperlClientException("Failed to write value " + field.getValue().toString() + " as BYTE.", ex); + } + } else if ( field.getType() == TType.DOUBLE ) { + try { + protocol.writeDouble(((Double) field.getValue()).doubleValue()); + } catch (Exception ex) { + throw new GossiperlClientException("Failed to write value " + field.getValue().toString() + " as DOUBLE.", ex); + } + } else if ( field.getType() == TType.I16 ) { + try { + protocol.writeI16(((Long) field.getValue()).shortValue()); + } catch (Exception ex) { + throw new GossiperlClientException("Failed to write value " + field.getValue().toString() + " as I16.", ex); + } + } else if ( field.getType() == TType.I32 ) { + try { + protocol.writeI32(((Long) field.getValue()).intValue()); + } catch (Exception ex) { + throw new GossiperlClientException("Failed to write value " + field.getValue().toString() + " as I32.", ex); + } + } else if ( field.getType() == TType.I64 ) { + try { + protocol.writeI64(((Long)field.getValue()).longValue()); + } catch (Exception ex) { + throw new GossiperlClientException("Failed to write value " + field.getValue().toString() + " as I64.", ex); + } + } else if ( field.getType() == TType.STRING ) { + try { + protocol.writeString((String) field.getValue()); + } catch (Exception ex) { + throw new GossiperlClientException("Failed to write value " + field.getValue().toString() + " as STRING.", ex); + } + } + protocol.writeFieldEnd(); + } + protocol.writeFieldStop(); + protocol.writeStructEnd(); + DigestEnvelope envelope = new DigestEnvelope(); + envelope.setPayload_type( digestType ); + envelope.setBin_payload(Base64.encode( stream.toByteArray() )); + envelope.setId(UUID.randomUUID().toString()); + return digestToBinary( envelope ); + } + + public byte[] serialize(org.apache.thrift.TBase digest) throws GossiperlClientException, + UnsupportedEncodingException { + String digestType = this.getDigestName( digest ); + if ( digestType.equals(DIGEST_ENVELOPE) ) { + return digestToBinary( digest ); + } + DigestEnvelope envelope = new DigestEnvelope(); + envelope.setPayload_type( digestType ); + envelope.setBin_payload(Base64.encode(digestToBinary(digest))); + envelope.setId(UUID.randomUUID().toString()); + return digestToBinary( envelope ); + } + + public DeserializeResult deserializeArbitrary(String digestType, byte[] binDigest, List digestInfo) + throws GossiperlClientException, UnsupportedEncodingException, TException { + DigestEnvelope envelope = (DigestEnvelope)digestFromBinary( DIGEST_ENVELOPE, binDigest ); + + byte[] digest = Base64.decode(envelope.getBin_payload()); + TTransport transport = new TIOStreamTransport(new ByteArrayInputStream( digest )); + TBinaryProtocol protocol = new TBinaryProtocol( transport ); + protocol.readStructBegin(); + HashMap< String, Object > result = new HashMap(); + for (int i=0; i digestInfo ) { + for ( int i=0; i digestInfo; + + public void setUp() { + this.serializer = new Serializer(); + this.digestType = "someDigestType"; + this.digestInfo = new ArrayList(); + try { + this.digestInfo.add(new CustomDigestField("some_property", "this is some string value to test", 1)); + this.digestInfo.add(new CustomDigestField("some_other_property", 1234L, 2)); + } catch (Exception ex) { + System.out.println("[Serialization test]: Error in set up."); + ex.printStackTrace(); + } + } + + public void tearDown() { + } + + public void testSerializeDeserialize() throws Exception { + byte[] envelope = this.serializer.serializeArbitrary( this.digestType, this.digestInfo ); + DeserializeResult result = this.serializer.deserializeArbitrary( this.digestType, envelope, this.digestInfo ); + assertTrue(result.getClass().getName().equals(DeserializeResultCustomOK.class.getName())); + DeserializeResultCustomOK finalResult = (DeserializeResultCustomOK)result; + assertEquals(this.digestType, finalResult.getDigestType()); + assertTrue( finalResult.getResultData().containsKey("some_property") ); + assertTrue( finalResult.getResultData().containsKey("some_other_property") ); + } + +}