Now chats are creating!!! Changed many things in app.

This commit is contained in:
ChronosX88 2019-03-22 20:08:29 +04:00
parent 2680a86ba8
commit 347e195909
Signed by: ChronosXYZ
GPG Key ID: 085A69A82C8C511A
30 changed files with 676 additions and 225 deletions

View File

@ -3,6 +3,9 @@
<component name="GradleSettings"> <component name="GradleSettings">
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
<compositeConfiguration>
<compositeBuild compositeDefinitionSource="SCRIPT" />
</compositeConfiguration>
<option name="distributionType" value="DEFAULT_WRAPPED" /> <option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules"> <option name="modules">

View File

@ -9,6 +9,11 @@ android {
versionCode 1 versionCode 1
versionName "1.0" versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
}
}
} }
buildTypes { buildTypes {
release { release {
@ -44,5 +49,5 @@ dependencies {
implementation 'com.google.android.material:material:1.1.0-alpha04' implementation 'com.google.android.material:material:1.1.0-alpha04'
implementation 'androidx.preference:preference:1.1.0-alpha03' implementation 'androidx.preference:preference:1.1.0-alpha03'
implementation 'com.google.code.gson:gson:2.8.5' implementation 'com.google.code.gson:gson:2.8.5'
//implementation group: 'com.github.demidenko05', name: 'a-javabeans', version: '1.0.4' implementation group: 'org.springframework.security', name: 'spring-security-crypto', version: '3.1.0.RELEASE'
} }

View File

@ -0,0 +1,102 @@
{
"formatVersion": 1,
"database": {
"version": 2,
"identityHash": "1fdccce4cf398929958d6bf1f0ccfea6",
"entities": [
{
"tableName": "messages",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `chatID` TEXT, `sender` TEXT, `date` TEXT, `text` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "chatID",
"columnName": "chatID",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "sender",
"columnName": "sender",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "date",
"columnName": "date",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "text",
"columnName": "text",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "chats",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `chatID` TEXT, `name` TEXT, `peerAddresses` BLOB, `keyPairID` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "chatID",
"columnName": "chatID",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "peerAddresses",
"columnName": "peerAddresses",
"affinity": "BLOB",
"notNull": false
},
{
"fieldPath": "keyPairID",
"columnName": "keyPairID",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"1fdccce4cf398929958d6bf1f0ccfea6\")"
]
}
}

View File

@ -1,7 +1,11 @@
package io.github.chronosx88.influence.contracts.chatlist; package io.github.chronosx88.influence.contracts.chatlist;
import java.util.List;
import io.github.chronosx88.influence.helpers.ChatListAdapter; import io.github.chronosx88.influence.helpers.ChatListAdapter;
import io.github.chronosx88.influence.models.roomEntities.ChatEntity;
public interface ChatListViewContract { public interface ChatListViewContract {
void setRecycleAdapter(ChatListAdapter adapter); void setRecycleAdapter(ChatListAdapter adapter);
void updateChatList(ChatListAdapter adapter, List<ChatEntity> chats);
} }

View File

@ -0,0 +1,5 @@
package io.github.chronosx88.influence.contracts.observer;
public interface NetworkObserver {
void handleEvent(Object object);
}

View File

@ -3,7 +3,10 @@ package io.github.chronosx88.influence.contracts.observer;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
public interface Observable { public interface Observable {
void register(Observer observer, int channelID); void register(Observer observer);
void unregister(Observer observer, int channelID); void register(NetworkObserver networkObserver);
void notifyObservers(JsonObject jsonObject, int channelID); void unregister(Observer observer);
void unregister(NetworkObserver networkObserver);
void notifyUIObservers(JsonObject jsonObject);
void notifyNetworkObservers(Object object);
} }

View File

@ -18,6 +18,7 @@ public class AppHelper extends Application {
private static String peerID; private static String peerID;
private static PeerDHT peerDHT; private static PeerDHT peerDHT;
private static RoomHelper chatDB; private static RoomHelper chatDB;
private static NetworkHandler networkHandler;
@Override @Override
public void onCreate() { public void onCreate() {
@ -27,7 +28,6 @@ public class AppHelper extends Application {
chatDB = Room.databaseBuilder(getApplicationContext(), RoomHelper.class, "chatDB") chatDB = Room.databaseBuilder(getApplicationContext(), RoomHelper.class, "chatDB")
.allowMainThreadQueries() .allowMainThreadQueries()
.build(); .build();
} }
public static void storePeerID(String peerID1) { peerID = peerID1; } public static void storePeerID(String peerID1) { peerID = peerID1; }
@ -45,4 +45,6 @@ public class AppHelper extends Application {
public static PeerDHT getPeerDHT() { return peerDHT; } public static PeerDHT getPeerDHT() { return peerDHT; }
public static RoomHelper getChatDB() { return chatDB; } public static RoomHelper getChatDB() { return chatDB; }
public static void initNetworkHandler() { networkHandler = new NetworkHandler(); }
} }

View File

@ -0,0 +1,34 @@
package io.github.chronosx88.influence.helpers;
import java.io.Serializable;
import java.math.BigInteger;
public class DSAKey implements Serializable {
private BigInteger Q;
private BigInteger P;
private BigInteger Y;
private BigInteger G;
public DSAKey(BigInteger Q, BigInteger P, BigInteger Y, BigInteger G) {
this.Q = Q;
this.P = P;
this.Y = Y;
this.G = G;
}
public BigInteger getY() {
return Y;
}
public BigInteger getG() {
return G;
}
public BigInteger getP() {
return P;
}
public BigInteger getQ() {
return Q;
}
}

View File

@ -14,6 +14,9 @@ public class KeyPairManager {
public KeyPairManager() { public KeyPairManager() {
this.keyPairDir = new File(AppHelper.getContext().getFilesDir().getAbsoluteFile(), "keyPairs"); this.keyPairDir = new File(AppHelper.getContext().getFilesDir().getAbsoluteFile(), "keyPairs");
if(!this.keyPairDir.exists()) {
this.keyPairDir.mkdir();
}
} }
public KeyPair openMainKeyPair() { public KeyPair openMainKeyPair() {
@ -37,7 +40,7 @@ public class KeyPairManager {
byte[] serializedKeyPair = new byte[(int) keyPairFile.length()]; byte[] serializedKeyPair = new byte[(int) keyPairFile.length()];
inputStream.read(serializedKeyPair); inputStream.read(serializedKeyPair);
inputStream.close(); inputStream.close();
keyPair = Serializer.deserializeObject(new String(serializedKeyPair, StandardCharsets.UTF_8)); keyPair = (KeyPair) Serializer.deserialize(serializedKeyPair);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
@ -48,9 +51,9 @@ public class KeyPairManager {
KeyPair keyPair = null; KeyPair keyPair = null;
try { try {
keyPairFile.createNewFile(); keyPairFile.createNewFile();
keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); keyPair = KeyPairGenerator.getInstance("DSA").generateKeyPair();
FileOutputStream outputStream = new FileOutputStream(keyPairFile); FileOutputStream outputStream = new FileOutputStream(keyPairFile);
outputStream.write(Serializer.serializeObject(keyPair).getBytes(StandardCharsets.UTF_8)); outputStream.write(Serializer.serialize(keyPair));
outputStream.close(); outputStream.close();
} catch (IOException | NoSuchAlgorithmException e) { } catch (IOException | NoSuchAlgorithmException e) {
e.printStackTrace(); e.printStackTrace();
@ -63,7 +66,7 @@ public class KeyPairManager {
if(!keyPairFile.exists()) { if(!keyPairFile.exists()) {
try { try {
FileOutputStream outputStream = new FileOutputStream(keyPairFile); FileOutputStream outputStream = new FileOutputStream(keyPairFile);
outputStream.write(Serializer.serializeObject(keyPair).getBytes(StandardCharsets.UTF_8)); outputStream.write(Serializer.serialize(keyPair));
outputStream.close(); outputStream.close();
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();

View File

@ -1,62 +1,77 @@
package io.github.chronosx88.influence.helpers; package io.github.chronosx88.influence.helpers;
import android.util.Log;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import net.tomp2p.dht.PeerDHT; import net.tomp2p.dht.PeerDHT;
import net.tomp2p.peers.PeerAddress; import net.tomp2p.peers.PeerAddress;
import io.github.chronosx88.influence.contracts.observer.Observer; import java.util.List;
import io.github.chronosx88.influence.contracts.observer.NetworkObserver;
import io.github.chronosx88.influence.helpers.actions.NetworkActions; import io.github.chronosx88.influence.helpers.actions.NetworkActions;
import io.github.chronosx88.influence.helpers.actions.UIActions; import io.github.chronosx88.influence.helpers.actions.UIActions;
import io.github.chronosx88.influence.models.NewChatRequestMessage;
import io.github.chronosx88.influence.models.roomEntities.ChatEntity; import io.github.chronosx88.influence.models.roomEntities.ChatEntity;
import io.github.chronosx88.influence.observable.MainObservable;
public class NetworkHandler implements Observer { public class NetworkHandler implements NetworkObserver {
private final static String LOG_TAG = "NetworkHandler";
private Gson gson; private Gson gson;
private PeerDHT peerDHT; private PeerDHT peerDHT;
public NetworkHandler() { public NetworkHandler() {
gson = new Gson(); gson = new Gson();
peerDHT = AppHelper.getPeerDHT(); peerDHT = AppHelper.getPeerDHT();
AppHelper.getObservable().register(this, MainObservable.OTHER_ACTIONS_CHANNEL); AppHelper.getObservable().register(this);
} }
@Override @Override
public void handleEvent(JsonObject object) { public void handleEvent(Object object) {
new Thread(() -> { new Thread(() -> {
switch (object.get("action").getAsInt()) { switch (getMessageAction((String) object)) {
case NetworkActions.START_CHAT: { case NetworkActions.CREATE_CHAT: {
String chatStarterPlainAddress = object.get("senderAddress").getAsString(); NewChatRequestMessage newChatRequestMessage = gson.fromJson((String) object, NewChatRequestMessage.class);
createChatEntry(object.get("chatID").getAsString(), object.get("senderID").getAsString(), chatStarterPlainAddress); createChatEntry(newChatRequestMessage.getChatID(), newChatRequestMessage.getChatID(), newChatRequestMessage.getSenderPeerAddress());
handleIncomingChat(object.get("chatID").getAsString(), PrepareData.prepareFromStore(chatStarterPlainAddress)); handleIncomingChat(newChatRequestMessage.getChatID(), newChatRequestMessage.getSenderPeerAddress());
JsonObject jsonObject = new JsonObject(); JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("action", UIActions.NEW_CHAT); jsonObject.addProperty("action", UIActions.NEW_CHAT);
AppHelper.getObservable().notifyObservers(jsonObject, MainObservable.UI_ACTIONS_CHANNEL); AppHelper.getObservable().notifyUIObservers(jsonObject);
break; break;
} }
case NetworkActions.SUCCESSFULL_CREATE_CHAT: { case NetworkActions.SUCCESSFULL_CREATE_CHAT: {
createChatEntry(object.get("chatID").getAsString(), object.get("senderID").getAsString(), object.get("senderAddress").getAsString()); NewChatRequestMessage newChatRequestMessage = gson.fromJson((String) object, NewChatRequestMessage.class);
createChatEntry(newChatRequestMessage.getChatID(), newChatRequestMessage.getSenderID(), newChatRequestMessage.getSenderPeerAddress());
JsonObject jsonObject = new JsonObject(); JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("action", UIActions.NEW_CHAT); jsonObject.addProperty("action", UIActions.NEW_CHAT);
AppHelper.getObservable().notifyObservers(jsonObject, MainObservable.UI_ACTIONS_CHANNEL); AppHelper.getObservable().notifyUIObservers(jsonObject);
break; break;
} }
} }
}).start(); }).start();
} }
private void createChatEntry(String chatID, String name, String peerAddress) { private int getMessageAction(String json) {
AppHelper.getChatDB().chatDao().addChat(new ChatEntity(chatID, name, peerAddress, "")); JsonObject jsonObject = new JsonParser().parse(json).getAsJsonObject();
return jsonObject.get("action").getAsInt();
}
private void createChatEntry(String chatID, String name, PeerAddress peerAddress) {
List<ChatEntity> chatEntities = AppHelper.getChatDB().chatDao().getChatByChatID(chatID);
if (chatEntities.size() > 0) {
Log.e(LOG_TAG, "Failed to create chat " + chatID + " because chat exists!");
return;
}
AppHelper.getChatDB().chatDao().addChat(new ChatEntity(chatID, name, "", Serializer.serialize(peerAddress)));
} }
private void handleIncomingChat(String chatID, PeerAddress chatStarterAddress) { private void handleIncomingChat(String chatID, PeerAddress chatStarterAddress) {
JsonObject jsonObject = new JsonObject(); NewChatRequestMessage newChatRequestMessage = new NewChatRequestMessage(AppHelper.getPeerID(), peerDHT.peerAddress());
jsonObject.addProperty("action", NetworkActions.SUCCESSFULL_CREATE_CHAT); newChatRequestMessage.setChatID(chatID);
jsonObject.addProperty("chatID", chatID); newChatRequestMessage.setAction(NetworkActions.SUCCESSFULL_CREATE_CHAT);
jsonObject.addProperty("senderID", AppHelper.getPeerID()); AppHelper.getPeerDHT().peer().sendDirect(chatStarterAddress).object(gson.toJson(newChatRequestMessage)).start().awaitUninterruptibly();
jsonObject.addProperty("senderAddress", PrepareData.prepareToStore(peerDHT.peerAddress()));
AppHelper.getPeerDHT().peer().sendDirect(chatStarterAddress).object(gson.toJson(jsonObject)).start().awaitUninterruptibly();
} }
} }

View File

@ -1,17 +1,19 @@
package io.github.chronosx88.influence.helpers; package io.github.chronosx88.influence.helpers;
import android.util.Base64; import org.springframework.security.crypto.codec.Base64;
import java.nio.charset.StandardCharsets; import java.io.Serializable;
public class PrepareData { public class PrepareData {
public static <T> String prepareToStore(T object) { public static <T> String prepareToStore(T object) {
String serializedObject = Serializer.serializeObject(object); if(object instanceof Serializable) {
return Base64.encodeToString(serializedObject.getBytes(StandardCharsets.UTF_8), Base64.URL_SAFE); byte[] serializedObject = Serializer.serialize(object);
return new String(Base64.encode(serializedObject));
}
return null;
} }
public static <T> T prepareFromStore(String object) { public static Object prepareFromStore(String object) {
String decodedString = new String(Base64.decode(object, Base64.URL_SAFE), StandardCharsets.UTF_8); return Serializer.deserialize(Base64.decode(object.getBytes()));
return Serializer.deserializeObject(decodedString);
} }
} }

View File

@ -7,7 +7,7 @@ import io.github.chronosx88.influence.models.daos.MessageDao;
import io.github.chronosx88.influence.models.roomEntities.ChatEntity; import io.github.chronosx88.influence.models.roomEntities.ChatEntity;
import io.github.chronosx88.influence.models.roomEntities.MessageEntity; import io.github.chronosx88.influence.models.roomEntities.MessageEntity;
@Database(entities = { MessageEntity.class, ChatEntity.class }, version = 1) @Database(entities = { MessageEntity.class, ChatEntity.class }, version = 2)
public abstract class RoomHelper extends RoomDatabase { public abstract class RoomHelper extends RoomDatabase {
public abstract ChatDao chatDao(); public abstract ChatDao chatDao();
public abstract MessageDao messageDao(); public abstract MessageDao messageDao();

View File

@ -1,45 +1,48 @@
package io.github.chronosx88.influence.helpers; package io.github.chronosx88.influence.helpers;
import org.jboss.serial.io.JBossObjectInputStream;
import org.jboss.serial.io.JBossObjectOutputStream;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
public class Serializer { public class Serializer {
public static <T> String serializeObject(T t) { public static byte[] serialize(Object object) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
try { try {
JBossObjectOutputStream out = new JBossObjectOutputStream(outputStream); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArray);
out.writeObject(t); objectOutputStream.writeObject(object);;
out.close(); objectOutputStream.close();
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
try { return byteArray.toByteArray();
return outputStream.toString("UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
} }
public static <T> T deserializeObject(String str) { public static Object deserialize(String serializedObject) {
ByteArrayInputStream inputStream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8)); ByteArrayInputStream inputStream = new ByteArrayInputStream(serializedObject.getBytes());
T obj = null; Object object = null;
try { try {
JBossObjectInputStream in = new JBossObjectInputStream(inputStream); ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
obj = (T) in.readObject(); object = objectInputStream.readObject();
in.close(); } catch (ClassNotFoundException | IOException e) {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace(); e.printStackTrace();
} }
return obj; return object;
}
public static Object deserialize(byte[] serializedObject) {
if(serializedObject == null)
return null;
ByteArrayInputStream inputStream = new ByteArrayInputStream(serializedObject);
Object object = null;
try {
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
object = objectInputStream.readObject();
} catch (ClassNotFoundException | IOException e) {
e.printStackTrace();
}
return object;
} }
} }

View File

@ -1,17 +1,30 @@
package io.github.chronosx88.influence.helpers; package io.github.chronosx88.influence.helpers;
import android.util.Log;
import net.tomp2p.connection.DSASignatureFactory;
import net.tomp2p.connection.SignatureFactory;
import net.tomp2p.dht.Storage; import net.tomp2p.dht.Storage;
import net.tomp2p.peers.Number160; import net.tomp2p.peers.Number160;
import net.tomp2p.peers.Number320; import net.tomp2p.peers.Number320;
import net.tomp2p.peers.Number480; import net.tomp2p.peers.Number480;
import net.tomp2p.peers.Number640; import net.tomp2p.peers.Number640;
import net.tomp2p.storage.AlternativeCompositeByteBuf;
import net.tomp2p.storage.Data; import net.tomp2p.storage.Data;
import org.h2.mvstore.MVMap; import org.h2.mvstore.MVMap;
import org.h2.mvstore.MVStore; import org.h2.mvstore.MVStore;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.SignatureException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -24,26 +37,37 @@ import java.util.SortedMap;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import static io.github.chronosx88.influence.helpers.Serializer.deserialize;
import static io.github.chronosx88.influence.helpers.Serializer.serialize;
public class StorageMVStore implements Storage { public class StorageMVStore implements Storage {
private final String LOG_TAG = "StorageMVStore";
private MVStore db; private MVStore db;
// Core // Core
final private MVMap<String, String> dataMap; // <Number 640, Data> final private MVMap<byte[], byte[]> dataMap; // <Number 640, Data>
// Maintenance // Maintenance
final private MVMap<String, String> timeoutMap; // <Number640, Long> final private MVMap<byte[], byte[]> timeoutMap; // <Number640, Long>
final private MVMap<String, String> timeoutMapRev; // <Long, Set<Number640>> final private MVMap<byte[], byte[]> timeoutMapRev; // <Long, Set<Number640>>
// Protection // Protection
final private MVMap<String, String> protectedDomainMap; // <Number320, PublicKey> final private MVMap<byte[], byte[]> protectedDomainMap; // <Number320, PublicKey>
final private MVMap<String, String> protectedEntryMap; // <Number480, PublicKey> final private MVMap<byte[], byte[]> protectedEntryMap; // <Number480, PublicKey>
// Responsibility // Responsibility
final private MVMap<String, String> responsibilityMap; // <Number160, Number160> final private MVMap<byte[], byte[]> responsibilityMap; // <Number160, Number160>
final private MVMap<String, String> responsibilityMapRev; // <Number160, Set<Number160>> final private MVMap<byte[], byte[]> responsibilityMapRev; // <Number160, Set<Number160>>
final private int storageCheckIntervalMillis; final private int storageCheckIntervalMillis;
final private SignatureFactory signatureFactory;
final private KeyPairManager keyPairManager;
public StorageMVStore(Number160 peerID, File path) { public StorageMVStore(Number160 peerID, File path) {
db = new MVStore.Builder() db = new MVStore.Builder()
.fileName(path.getAbsolutePath() + "/coreDB.db") .fileName(path.getAbsolutePath() + "/coreDB.db")
@ -56,31 +80,33 @@ public class StorageMVStore implements Storage {
responsibilityMap = db.openMap("responsibilityMap_" + peerID.toString()); responsibilityMap = db.openMap("responsibilityMap_" + peerID.toString());
responsibilityMapRev = db.openMap("responsibilityMapRev_ " + peerID.toString()); responsibilityMapRev = db.openMap("responsibilityMapRev_ " + peerID.toString());
storageCheckIntervalMillis = 60 * 1000; storageCheckIntervalMillis = 60 * 1000;
signatureFactory = new DSASignatureFactory();
keyPairManager = new KeyPairManager();
Runtime.getRuntime().addShutdownHook(new JVMShutdownHook(this)); Runtime.getRuntime().addShutdownHook(new JVMShutdownHook(this));
} }
@Override @Override
public Data put(Number640 key, Data value) { public Data put(Number640 key, Data value) {
Data oldData = Serializer.deserializeObject(dataMap.put(Serializer.serializeObject(key), Serializer.serializeObject(value))); Data oldData = deserializeData(dataMap.put(serialize(key), serializeData(value)));
db.commit(); db.commit();
return oldData; return oldData;
} }
@Override @Override
public Data get(Number640 key) { public Data get(Number640 key) {
return Serializer.deserializeObject(dataMap.get(Serializer.serializeObject(key))); return deserializeData(dataMap.get(serialize(key)));
} }
@Override @Override
public boolean contains(Number640 key) { public boolean contains(Number640 key) {
return dataMap.containsKey(Serializer.serializeObject(key)); return dataMap.containsKey(serialize(key));
} }
@Override @Override
public int contains(Number640 from, Number640 to) { public int contains(Number640 from, Number640 to) {
TreeMap<Number640, Data> tmp = new TreeMap<>(); TreeMap<Number640, Data> tmp = new TreeMap<>();
for (Map.Entry<String, String> entry: dataMap.entrySet()) { for (Map.Entry<byte[], byte[]> entry: dataMap.entrySet()) {
tmp.put(Serializer.deserializeObject(entry.getKey()), Serializer.deserializeObject(entry.getValue())); tmp.put((Number640) deserialize(entry.getKey()), deserializeData(entry.getValue()));
} }
return tmp.subMap(from, true, to, true).size(); return tmp.subMap(from, true, to, true).size();
@ -88,7 +114,7 @@ public class StorageMVStore implements Storage {
@Override @Override
public Data remove(Number640 key, boolean returnData) { public Data remove(Number640 key, boolean returnData) {
Data retVal = Serializer.deserializeObject(dataMap.remove(Serializer.serializeObject(key))); Data retVal = deserializeData(dataMap.remove(serialize(key)));
db.commit(); db.commit();
return retVal; return retVal;
} }
@ -96,13 +122,13 @@ public class StorageMVStore implements Storage {
@Override @Override
public NavigableMap<Number640, Data> remove(Number640 from, Number640 to) { public NavigableMap<Number640, Data> remove(Number640 from, Number640 to) {
TreeMap<Number640, Data> tmp = new TreeMap<>(); TreeMap<Number640, Data> tmp = new TreeMap<>();
for (Map.Entry<String, String> entry: dataMap.entrySet()) { for (Map.Entry<byte[], byte[]> entry: dataMap.entrySet()) {
tmp.put(Serializer.deserializeObject(entry.getKey()), Serializer.deserializeObject(entry.getValue())); tmp.put((Number640) deserialize(entry.getKey()), deserializeData(entry.getValue()));
} }
NavigableMap<Number640, Data> tmpSubMap = tmp.subMap(from, true, to, true); NavigableMap<Number640, Data> tmpSubMap = tmp.subMap(from, true, to, true);
for (Map.Entry<Number640, Data> entry : tmpSubMap.entrySet()) { for (Map.Entry<Number640, Data> entry : tmpSubMap.entrySet()) {
dataMap.remove(Serializer.serializeObject(entry.getKey())); dataMap.remove(serialize(entry.getKey()));
} }
db.commit(); db.commit();
@ -112,8 +138,8 @@ public class StorageMVStore implements Storage {
@Override @Override
public NavigableMap<Number640, Data> subMap(Number640 from, Number640 to, int limit, boolean ascending) { public NavigableMap<Number640, Data> subMap(Number640 from, Number640 to, int limit, boolean ascending) {
TreeMap<Number640, Data> tmpDataMap = new TreeMap<>(); TreeMap<Number640, Data> tmpDataMap = new TreeMap<>();
for (Map.Entry<String, String> entry: dataMap.entrySet()) { for (Map.Entry<byte[], byte[]> entry: dataMap.entrySet()) {
tmpDataMap.put(Serializer.deserializeObject(entry.getKey()), Serializer.deserializeObject(entry.getValue())); tmpDataMap.put((Number640) deserialize(entry.getKey()), deserializeData(entry.getValue()));
} }
NavigableMap<Number640, Data> tmp = tmpDataMap.subMap(from, true, to, true); NavigableMap<Number640, Data> tmp = tmpDataMap.subMap(from, true, to, true);
@ -138,8 +164,8 @@ public class StorageMVStore implements Storage {
@Override @Override
public NavigableMap<Number640, Data> map() { public NavigableMap<Number640, Data> map() {
final NavigableMap<Number640, Data> retVal = new TreeMap<>(); final NavigableMap<Number640, Data> retVal = new TreeMap<>();
for(final Map.Entry<String, String> entry:dataMap.entrySet()) { for(final Map.Entry<byte[], byte[]> entry : dataMap.entrySet()) {
retVal.put(Serializer.deserializeObject(entry.getKey()), Serializer.deserializeObject(entry.getValue())); retVal.put((Number640) deserialize(entry.getKey()), deserializeData(entry.getValue()));
} }
return retVal; return retVal;
@ -152,7 +178,7 @@ public class StorageMVStore implements Storage {
@Override @Override
public void addTimeout(Number640 key, long expiration) { public void addTimeout(Number640 key, long expiration) {
Long oldExpiration = Serializer.deserializeObject(timeoutMap.put(Serializer.serializeObject(key), Serializer.serializeObject(expiration))); Long oldExpiration = (Long) deserialize(timeoutMap.put(serialize(key), serialize(expiration)));
putIfAbsent2(expiration, key); putIfAbsent2(expiration, key);
if (oldExpiration == null) { if (oldExpiration == null) {
return; return;
@ -162,29 +188,29 @@ public class StorageMVStore implements Storage {
} }
private void putIfAbsent2(long expiration, Number640 key) { private void putIfAbsent2(long expiration, Number640 key) {
Set<Number640> timeouts = Serializer.deserializeObject(timeoutMapRev.get(Serializer.serializeObject(expiration))); Set<Number640> timeouts = (Set<Number640>) deserialize(timeoutMapRev.get(serialize(expiration)));
if(timeouts == null) { if(timeouts == null) {
timeouts = Collections.newSetFromMap(new ConcurrentHashMap<Number640, Boolean>()); timeouts = Collections.newSetFromMap(new ConcurrentHashMap<>());
} }
timeouts.add(key); timeouts.add(key);
timeoutMapRev.put(Serializer.serializeObject(expiration), Serializer.serializeObject(timeouts)); timeoutMapRev.put(serialize(expiration), serialize(timeouts));
} }
private void removeRevTimeout(Number640 key, Long expiration) { private void removeRevTimeout(Number640 key, Long expiration) {
Set<Number640> tmp = Serializer.deserializeObject(timeoutMapRev.get(Serializer.serializeObject(expiration))); Set<Number640> tmp = (Set<Number640>) deserialize(timeoutMapRev.get(serialize(expiration)));
if (tmp != null) { if (tmp != null) {
tmp.remove(key); tmp.remove(key);
if (tmp.isEmpty()) { if (tmp.isEmpty()) {
timeoutMapRev.remove(expiration); timeoutMapRev.remove(serialize(expiration));
} else { } else {
timeoutMapRev.put(Serializer.serializeObject(expiration), Serializer.serializeObject(tmp)); timeoutMapRev.put(serialize(expiration), serialize(tmp));
} }
} }
} }
@Override @Override
public void removeTimeout(Number640 key) { public void removeTimeout(Number640 key) {
Long expiration = Serializer.deserializeObject(timeoutMap.remove(Serializer.serializeObject(key))); Long expiration = (Long) deserialize(timeoutMap.remove(serialize(key)));
if (expiration == null) { if (expiration == null) {
return; return;
} }
@ -195,8 +221,8 @@ public class StorageMVStore implements Storage {
@Override @Override
public Collection<Number640> subMapTimeout(long to) { public Collection<Number640> subMapTimeout(long to) {
TreeMap<Long, Set<Number640>> tmpTimeoutMapRev = new TreeMap<>(); TreeMap<Long, Set<Number640>> tmpTimeoutMapRev = new TreeMap<>();
for (Map.Entry<String, String> entry: timeoutMapRev.entrySet()) { for (Map.Entry<byte[], byte[]> entry: timeoutMapRev.entrySet()) {
tmpTimeoutMapRev.put(Serializer.deserializeObject(entry.getKey()), Serializer.deserializeObject(entry.getValue())); tmpTimeoutMapRev.put( (Long) deserialize(entry.getKey()), (Set<Number640>) deserialize(entry.getValue()));
} }
SortedMap<Long, Set<Number640>> tmp = tmpTimeoutMapRev.subMap(0L, to); SortedMap<Long, Set<Number640>> tmp = tmpTimeoutMapRev.subMap(0L, to);
@ -214,13 +240,13 @@ public class StorageMVStore implements Storage {
@Override @Override
public boolean protectDomain(Number320 key, PublicKey publicKey) { public boolean protectDomain(Number320 key, PublicKey publicKey) {
protectedDomainMap.put(Serializer.serializeObject(key), Serializer.serializeObject(publicKey)); protectedDomainMap.put(serialize(key), serialize(publicKey));
return true; return true;
} }
@Override @Override
public boolean isDomainProtectedByOthers(Number320 key, PublicKey publicKey) { public boolean isDomainProtectedByOthers(Number320 key, PublicKey publicKey) {
PublicKey other = Serializer.deserializeObject(protectedDomainMap.get(Serializer.serializeObject(key))); PublicKey other = (PublicKey) deserialize(protectedDomainMap.get(serialize(key)));
if (other == null) { if (other == null) {
return false; return false;
} }
@ -229,13 +255,13 @@ public class StorageMVStore implements Storage {
@Override @Override
public boolean protectEntry(Number480 key, PublicKey publicKey) { public boolean protectEntry(Number480 key, PublicKey publicKey) {
protectedEntryMap.put(Serializer.serializeObject(key), Serializer.serializeObject(publicKey)); protectedEntryMap.put(serialize(key), serialize(publicKey));
return true; return true;
} }
@Override @Override
public boolean isEntryProtectedByOthers(Number480 key, PublicKey publicKey) { public boolean isEntryProtectedByOthers(Number480 key, PublicKey publicKey) {
PublicKey other = Serializer.deserializeObject(protectedEntryMap.get(Serializer.serializeObject(key))); PublicKey other = (PublicKey) deserialize(protectedEntryMap.get(serialize(key)));
if (other == null) { if (other == null) {
return false; return false;
} }
@ -244,17 +270,17 @@ public class StorageMVStore implements Storage {
@Override @Override
public Number160 findPeerIDsForResponsibleContent(Number160 locationKey) { public Number160 findPeerIDsForResponsibleContent(Number160 locationKey) {
return Serializer.deserializeObject(responsibilityMap.get(Serializer.serializeObject(locationKey))); return (Number160) deserialize(responsibilityMap.get(serialize(locationKey)));
} }
@Override @Override
public Collection<Number160> findContentForResponsiblePeerID(Number160 peerID) { public Collection<Number160> findContentForResponsiblePeerID(Number160 peerID) {
return Serializer.deserializeObject(responsibilityMapRev.get(Serializer.serializeObject(peerID))); return (Collection<Number160>) deserialize(responsibilityMapRev.get(serialize(peerID)));
} }
@Override @Override
public boolean updateResponsibilities(Number160 locationKey, Number160 peerId) { public boolean updateResponsibilities(Number160 locationKey, Number160 peerId) {
final Number160 oldPeerID = Serializer.deserializeObject(responsibilityMap.put(Serializer.serializeObject(locationKey), Serializer.serializeObject(peerId))); final Number160 oldPeerID = (Number160) deserialize(responsibilityMap.put(serialize(locationKey), serialize(peerId)));
final boolean hasChanged; final boolean hasChanged;
if(oldPeerID != null) { if(oldPeerID != null) {
if(oldPeerID.equals(peerId)) { if(oldPeerID.equals(peerId)) {
@ -266,30 +292,103 @@ public class StorageMVStore implements Storage {
} else { } else {
hasChanged = true; hasChanged = true;
} }
Set<Number160> contentIDs = Serializer.deserializeObject(responsibilityMapRev.get(Serializer.serializeObject(peerId))); Set<Number160> contentIDs = (Set<Number160>) deserialize(responsibilityMapRev.get(serialize(peerId)));
if(contentIDs == null) { if(contentIDs == null) {
contentIDs = new HashSet<Number160>(); contentIDs = new HashSet<>();
} }
contentIDs.add(locationKey); contentIDs.add(locationKey);
responsibilityMapRev.put(Serializer.serializeObject(peerId), Serializer.serializeObject(contentIDs)); responsibilityMapRev.put(serialize(peerId), serialize(contentIDs));
db.commit(); db.commit();
return hasChanged; return hasChanged;
} }
@Override @Override
public void removeResponsibility(Number160 locationKey) { public void removeResponsibility(Number160 locationKey) {
final Number160 peerId = new Number160(responsibilityMap.remove(serialize(locationKey)));
if(peerId != null) {
removeRevResponsibility(peerId, locationKey);
}
db.commit();
} }
private void removeRevResponsibility(Number160 peerId, Number160 locationKey) { private void removeRevResponsibility(Number160 peerId, Number160 locationKey) {
Set<Number160> contentIDs = Serializer.deserializeObject(responsibilityMapRev.get(Serializer.serializeObject(peerId))); Set<Number160> contentIDs = (Set<Number160>) deserialize(responsibilityMapRev.get(serialize(peerId)));
if (contentIDs != null) { if (contentIDs != null) {
contentIDs.remove(locationKey); contentIDs.remove(locationKey);
if (contentIDs.isEmpty()) { if (contentIDs.isEmpty()) {
responsibilityMapRev.remove(peerId); responsibilityMapRev.remove(serialize(peerId));
} else { } else {
responsibilityMapRev.put(Serializer.serializeObject(peerId), Serializer.serializeObject(contentIDs)); responsibilityMapRev.put(serialize(peerId), serialize(contentIDs));
} }
} }
} }
private byte[] serializeData(Data data) {
KeyPair mainKeyPair = keyPairManager.openMainKeyPair();
KeyPair forSigningKP = keyPairManager.getKeyPair("mainSigningKeyPair");
data.sign(forSigningKP).protectEntry(mainKeyPair);
ByteArrayOutputStream out = new ByteArrayOutputStream();
AlternativeCompositeByteBuf acb = AlternativeCompositeByteBuf.compBuffer(AlternativeCompositeByteBuf.UNPOOLED_HEAP);
try {
// header first
data.encodeHeader(acb, signatureFactory);
writeData(out, acb.nioBuffers());
acb.skipBytes(acb.writerIndex());
// next data - no need to copy to another buffer, just take the data
// from memory
writeData(out, data.toByteBuffers());
// rest
data.encodeDone(acb, signatureFactory);
writeData(out, acb.nioBuffers());
} catch (SignatureException | InvalidKeyException | IOException e) {
e.printStackTrace();
}
return out.toByteArray();
}
private void writeData(OutputStream out, ByteBuffer[] nioBuffers) throws IOException {
final int length = nioBuffers.length;
for(int i=0;i < length; i++) {
int remaining = nioBuffers[i].remaining();
if(nioBuffers[i].hasArray()) {
out.write(nioBuffers[i].array(), nioBuffers[i].arrayOffset(), remaining);
} else {
byte[] me = new byte[remaining];
nioBuffers[i].get(me);
out.write(me);
}
}
}
private Data deserializeData(byte[] serializedData) {
if(serializedData == null) {
return null;
}
ByteArrayInputStream in = new ByteArrayInputStream(serializedData);
ByteBuf buf = Unpooled.buffer();
Data data = null;
while(data == null) {
buf.writeByte(in.read());
data = Data.decodeHeader(buf, signatureFactory);
}
int len = data.length();
byte me[] = new byte[len];
try {
in.read(me);
} catch (IOException e) {
e.printStackTrace();
}
buf = Unpooled.wrappedBuffer(me);
boolean retVal = data.decodeBuffer(buf);
if(!retVal) {
Log.e(LOG_TAG, "# ERROR: Data could not be deserialized!");
}
retVal = data.decodeDone(buf, signatureFactory);
if(!retVal) {
Log.e(LOG_TAG, "# ERROR: Signature could not be read!");
}
return data;
}
} }

View File

@ -1,7 +1,7 @@
package io.github.chronosx88.influence.helpers.actions; package io.github.chronosx88.influence.helpers.actions;
public class NetworkActions { public class NetworkActions {
public static final int START_CHAT = 0x0; public static final int CREATE_CHAT = 0x0;
public static final int NEW_MESSAGE = 0x1; public static final int NEW_MESSAGE = 0x1;
public static final int MESSAGE_SENT = 0x2; public static final int MESSAGE_SENT = 0x2;
public static final int PING = 0x3; public static final int PING = 0x3;

View File

@ -9,6 +9,7 @@ import com.google.gson.Gson;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import net.tomp2p.dht.FuturePut;
import net.tomp2p.dht.PeerBuilderDHT; import net.tomp2p.dht.PeerBuilderDHT;
import net.tomp2p.dht.PeerDHT; import net.tomp2p.dht.PeerDHT;
import net.tomp2p.futures.FutureBootstrap; import net.tomp2p.futures.FutureBootstrap;
@ -30,15 +31,25 @@ import java.net.Inet4Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.spec.DSAPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.util.UUID; import java.util.UUID;
import io.github.chronosx88.influence.contracts.main.MainLogicContract; import io.github.chronosx88.influence.contracts.main.MainLogicContract;
import io.github.chronosx88.influence.helpers.AppHelper; import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.DSAKey;
import io.github.chronosx88.influence.helpers.KeyPairManager; import io.github.chronosx88.influence.helpers.KeyPairManager;
import io.github.chronosx88.influence.helpers.PrepareData;
import io.github.chronosx88.influence.helpers.Serializer; import io.github.chronosx88.influence.helpers.Serializer;
import io.github.chronosx88.influence.helpers.StorageMVStore; import io.github.chronosx88.influence.helpers.StorageMVStore;
import io.github.chronosx88.influence.helpers.actions.NetworkActions; import io.github.chronosx88.influence.helpers.actions.NetworkActions;
import io.github.chronosx88.influence.helpers.actions.UIActions; import io.github.chronosx88.influence.helpers.actions.UIActions;
import io.github.chronosx88.influence.models.BasicNetworkMessage;
import io.github.chronosx88.influence.models.NewChatRequestMessage;
import io.github.chronosx88.influence.models.PublicUserProfile;
import io.github.chronosx88.influence.observable.MainObservable; import io.github.chronosx88.influence.observable.MainObservable;
public class MainLogic implements MainLogicContract { public class MainLogic implements MainLogicContract {
@ -92,47 +103,52 @@ public class MainLogic implements MainLogicContract {
} catch (NullPointerException e) { } catch (NullPointerException e) {
JsonObject jsonObject = new JsonObject(); JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("action", UIActions.BOOTSTRAP_NOT_SPECIFIED); jsonObject.addProperty("action", UIActions.BOOTSTRAP_NOT_SPECIFIED);
AppHelper.getObservable().notifyObservers(jsonObject, MainObservable.UI_ACTIONS_CHANNEL); AppHelper.getObservable().notifyUIObservers(jsonObject);
peerDHT.shutdown(); peerDHT.shutdown();
return; return;
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
JsonObject jsonObject = new JsonObject(); JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("action", UIActions.NETWORK_ERROR); jsonObject.addProperty("action", UIActions.NETWORK_ERROR);
AppHelper.getObservable().notifyObservers(jsonObject, MainObservable.UI_ACTIONS_CHANNEL); AppHelper.getObservable().notifyUIObservers(jsonObject);
peerDHT.shutdown(); peerDHT.shutdown();
return; return;
} }
boolean discoveredExternalAddress = false;
if(!discoverExternalAddress()) { if(!discoverExternalAddress()) {
JsonObject jsonObject = new JsonObject(); JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("action", UIActions.PORT_FORWARDING_ERROR); jsonObject.addProperty("action", UIActions.PORT_FORWARDING_ERROR);
AppHelper.getObservable().notifyObservers(jsonObject, MainObservable.UI_ACTIONS_CHANNEL); AppHelper.getObservable().notifyUIObservers(jsonObject);
} else {
discoveredExternalAddress = true;
} }
if(!discoveredExternalAddress) {
if(!setupConnectionToRelay()) { if(!setupConnectionToRelay()) {
JsonObject jsonObject = new JsonObject(); JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("action", UIActions.RELAY_CONNECTION_ERROR); jsonObject.addProperty("action", UIActions.RELAY_CONNECTION_ERROR);
AppHelper.getObservable().notifyObservers(jsonObject, MainObservable.UI_ACTIONS_CHANNEL); AppHelper.getObservable().notifyUIObservers(jsonObject);
return; return;
} }
}
if(!bootstrapPeer()) { if(!bootstrapPeer()) {
JsonObject jsonObject = new JsonObject(); JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("action", UIActions.BOOTSTRAP_ERROR); jsonObject.addProperty("action", UIActions.BOOTSTRAP_ERROR);
AppHelper.getObservable().notifyObservers(jsonObject, MainObservable.UI_ACTIONS_CHANNEL); AppHelper.getObservable().notifyUIObservers(jsonObject);
return; return;
} }
JsonObject jsonObject = new JsonObject(); JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("action", UIActions.BOOTSTRAP_SUCCESS); jsonObject.addProperty("action", UIActions.BOOTSTRAP_SUCCESS);
AppHelper.getObservable().notifyObservers(jsonObject, MainObservable.UI_ACTIONS_CHANNEL); AppHelper.getObservable().notifyUIObservers(jsonObject);
AppHelper.storePeerID(preferences.getString("peerID", null)); AppHelper.storePeerID(preferences.getString("peerID", null));
AppHelper.storePeerDHT(peerDHT); AppHelper.storePeerDHT(peerDHT);
AppHelper.initNetworkHandler();
setReceiveHandler(); setReceiveHandler();
Gson gson = new Gson(); gson = new Gson();
JsonObject publicProfile = new JsonObject(); publicProfileToDHT();
publicProfile.addProperty("peerAddress", Base64.encodeToString(Serializer.serializeObject(peerDHT.peerAddress()).getBytes(StandardCharsets.UTF_8), Base64.URL_SAFE));
peerDHT.put(Number160.createHash(preferences.getString("peerID", null) + "_profile")).data(new Data(gson.toJson(publicProfile)).protectEntry(keyPairManager.openMainKeyPair())).start().awaitUninterruptibly();
replication = new AutoReplication(peerDHT.peer()).start(); replication = new AutoReplication(peerDHT.peer()).start();
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
@ -185,21 +201,20 @@ public class MainLogic implements MainLogicContract {
private void setReceiveHandler() { private void setReceiveHandler() {
AppHelper.getPeerDHT().peer().objectDataReply((s, r) -> { AppHelper.getPeerDHT().peer().objectDataReply((s, r) -> {
Log.i(LOG_TAG, "# Incoming message: " + r); Log.i(LOG_TAG, "# Incoming message: " + r);
JSONObject incomingObject = new JSONObject((String) r); AppHelper.getObservable().notifyNetworkObservers(r);
if(incomingObject.getInt("action") == NetworkActions.PING) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("action", NetworkActions.PONG);
return gson.toJson(jsonObject);
}
AppHelper.getObservable().notifyObservers(new JsonParser().parse((String) r).getAsJsonObject(), MainObservable.OTHER_ACTIONS_CHANNEL);
return null; return null;
}); });
} }
@Override @Override
public void shutdownPeer() { public void shutdownPeer() {
new Thread(() -> {
if(replication != null) {
replication.shutdown().start(); replication.shutdown().start();
}
peerDHT.peer().announceShutdown().start().awaitUninterruptibly(); peerDHT.peer().announceShutdown().start().awaitUninterruptibly();
peerDHT.peer().shutdown();
}).start();
} }
private boolean checkFirstRun() { private boolean checkFirstRun() {
@ -211,4 +226,34 @@ public class MainLogic implements MainLogicContract {
} }
return false; return false;
} }
private void publicProfileToDHT() {
KeyPair mainKeyPair = keyPairManager.openMainKeyPair();
KeyFactory factory = null;
try {
factory = KeyFactory.getInstance("DSA");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
PublicUserProfile userProfile = null;
try {
DSAPublicKeySpec dsaKey = factory.getKeySpec(mainKeyPair.getPublic(), DSAPublicKeySpec.class);
userProfile = new PublicUserProfile(AppHelper.getPeerID(), peerDHT.peerAddress(), new DSAKey(dsaKey.getQ(), dsaKey.getP(), dsaKey.getY(), dsaKey.getG()));
} catch (InvalidKeySpecException e) {
e.printStackTrace();
}
Data serializedUserProfile = null;
try {
serializedUserProfile = new Data(gson.toJson(userProfile))
.protectEntry(mainKeyPair.getPrivate())
.sign(keyPairManager.getKeyPair("mainSigningKeyPair"));
} catch (IOException e) {
e.printStackTrace();
}
FuturePut futurePut = peerDHT.put(Number160.createHash(AppHelper.getPeerID() + "_profile"))
.data(serializedUserProfile)
.start()
.awaitUninterruptibly();
Log.i(LOG_TAG, futurePut.isSuccess() ? "# Profile successfully published!" : "# Profile publishing failed!");
}
} }

View File

@ -1,6 +1,5 @@
package io.github.chronosx88.influence.logic; package io.github.chronosx88.influence.logic;
import android.util.Base64;
import android.util.Log; import android.util.Log;
import com.google.gson.Gson; import com.google.gson.Gson;
@ -16,16 +15,16 @@ import net.tomp2p.peers.PeerAddress;
import net.tomp2p.storage.Data; import net.tomp2p.storage.Data;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.UUID; import java.util.UUID;
import io.github.chronosx88.influence.contracts.startchat.StartChatLogicContract; import io.github.chronosx88.influence.contracts.startchat.StartChatLogicContract;
import io.github.chronosx88.influence.helpers.AppHelper; import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.KeyPairManager; import io.github.chronosx88.influence.helpers.KeyPairManager;
import io.github.chronosx88.influence.helpers.PrepareData; import io.github.chronosx88.influence.helpers.PrepareData;
import io.github.chronosx88.influence.helpers.Serializer;
import io.github.chronosx88.influence.helpers.actions.NetworkActions; import io.github.chronosx88.influence.helpers.actions.NetworkActions;
import io.github.chronosx88.influence.helpers.actions.UIActions; import io.github.chronosx88.influence.helpers.actions.UIActions;
import io.github.chronosx88.influence.models.NewChatRequestMessage;
import io.github.chronosx88.influence.models.PublicUserProfile;
import io.github.chronosx88.influence.observable.MainObservable; import io.github.chronosx88.influence.observable.MainObservable;
public class StartChatLogic implements StartChatLogicContract { public class StartChatLogic implements StartChatLogicContract {
@ -43,35 +42,27 @@ public class StartChatLogic implements StartChatLogicContract {
@Override @Override
public void sendStartChatMessage(String peerID) { public void sendStartChatMessage(String peerID) {
new Thread(() -> { new Thread(() -> {
JsonObject recipientPublicProfile = getPublicProfile(peerID); PublicUserProfile recipientPublicProfile = getPublicProfile(peerID);
if(recipientPublicProfile == null) { if(recipientPublicProfile == null) {
JsonObject jsonObject = new JsonObject(); JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("action", UIActions.PEER_NOT_EXIST); jsonObject.addProperty("action", UIActions.PEER_NOT_EXIST);
AppHelper.getObservable().notifyObservers(jsonObject, MainObservable.UI_ACTIONS_CHANNEL); AppHelper.getObservable().notifyUIObservers(jsonObject);
return; return;
} }
String peerAddressString = recipientPublicProfile.get("peerAddress").getAsString(); PeerAddress recipientPeerAddress = getPublicProfile(peerID).getPeerAddress();
PeerAddress peerAddress = Serializer.deserializeObject(new String(Base64.decode(peerAddressString, Base64.URL_SAFE), StandardCharsets.UTF_8));
String chatID = UUID.randomUUID().toString();
JsonObject jsonObject = new JsonObject(); FuturePing ping = peerDHT.peer().ping().peerAddress(recipientPeerAddress).start().awaitUninterruptibly();
jsonObject.addProperty("action", NetworkActions.START_CHAT);
jsonObject.addProperty("chatID", chatID);
jsonObject.addProperty("senderID", AppHelper.getPeerID());
// TODO: Append public key to new chat request (for encryption)
jsonObject.addProperty("senderAddress", PrepareData.prepareToStore(peerDHT.peerAddress()));
FuturePing ping = peerDHT.peer().ping().peerAddress(peerAddress).start().awaitUninterruptibly();
if(ping.isSuccess()) { if(ping.isSuccess()) {
peerDHT.peer().sendDirect(peerAddress).object(gson.toJson(jsonObject)).start(); peerDHT.peer().sendDirect(recipientPeerAddress).object(gson.toJson(new NewChatRequestMessage(AppHelper.getPeerID(), peerDHT.peerAddress()))).start();
} else { } else {
try { try {
NewChatRequestMessage newChatRequestMessage = new NewChatRequestMessage(AppHelper.getPeerID(), peerDHT.peerAddress());
FuturePut futurePut = peerDHT FuturePut futurePut = peerDHT
.put(Number160.createHash(peerID)) .put(Number160.createHash(peerID))
.data(Number160.createHash(UUID.randomUUID().toString()), new Data(gson.toJson(jsonObject)) .data(Number160.createHash(UUID.randomUUID().toString()), new Data(gson.toJson(newChatRequestMessage))
.protectEntry(keyPairManager.openMainKeyPair())).start().awaitUninterruptibly(); .protectEntry(keyPairManager.openMainKeyPair())).start().awaitUninterruptibly();
if(futurePut.isSuccess()) { if(futurePut.isSuccess()) {
Log.i(LOG_TAG, "# Create new offline chat request is successful! ChatID: " + chatID); Log.i(LOG_TAG, "# Create new offline chat request is successful! ChatID: " + newChatRequestMessage.getChatID());
} else { } else {
Log.e(LOG_TAG, "# Failed to create chat: " + futurePut.failedReason()); Log.e(LOG_TAG, "# Failed to create chat: " + futurePut.failedReason());
} }
@ -82,8 +73,8 @@ public class StartChatLogic implements StartChatLogicContract {
}).start(); }).start();
} }
private JsonObject getPublicProfile(String peerID) { private PublicUserProfile getPublicProfile(String peerID) {
JsonObject publicProfile; PublicUserProfile publicProfile = null;
FutureGet futureGetProfile = peerDHT.get(Number160.createHash(peerID + "_profile")).start().awaitUninterruptibly(); FutureGet futureGetProfile = peerDHT.get(Number160.createHash(peerID + "_profile")).start().awaitUninterruptibly();
if (futureGetProfile.isSuccess()) { if (futureGetProfile.isSuccess()) {
String jsonString = null; String jsonString = null;
@ -94,7 +85,13 @@ public class StartChatLogic implements StartChatLogicContract {
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
publicProfile = new JsonParser().parse(jsonString).getAsJsonObject(); try {
publicProfile = gson.fromJson((String) futureGetProfile.data().object(), PublicUserProfile.class);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return publicProfile; return publicProfile;
} }
return null; return null;

View File

@ -0,0 +1,48 @@
package io.github.chronosx88.influence.models;
import net.tomp2p.peers.PeerAddress;
import java.io.Serializable;
/**
* Абстрактный класс-модель для любых сообщений, которые передаются по DHT-сети
*/
public class BasicNetworkMessage implements Serializable {
private int action;
private String senderID;
private PeerAddress senderPeerAddress;
public BasicNetworkMessage() {
//
}
public BasicNetworkMessage(int action, String senderID, PeerAddress senderPeerAddress) {
this.action = action;
this.senderID = senderID;
this.senderPeerAddress = senderPeerAddress;
}
public int getAction() {
return action;
}
public String getSenderID() {
return senderID;
}
public PeerAddress getSenderPeerAddress() {
return senderPeerAddress;
}
public void setAction(int action) {
this.action = action;
}
public void setSenderID(String senderID) {
this.senderID = senderID;
}
public void setSenderPeerAddress(PeerAddress senderPeerAddress) {
this.senderPeerAddress = senderPeerAddress;
}
}

View File

@ -0,0 +1,25 @@
package io.github.chronosx88.influence.models;
import net.tomp2p.peers.PeerAddress;
import java.io.Serializable;
import java.util.UUID;
import io.github.chronosx88.influence.helpers.actions.NetworkActions;
public class NewChatRequestMessage extends BasicNetworkMessage implements Serializable {
private String chatID;
public NewChatRequestMessage(String senderID, PeerAddress senderPeerAddress) {
super(NetworkActions.CREATE_CHAT, senderID, senderPeerAddress);
this.chatID = UUID.randomUUID().toString();
}
public String getChatID() {
return chatID;
}
public void setChatID(String chatID) {
this.chatID = chatID;
}
}

View File

@ -0,0 +1,46 @@
package io.github.chronosx88.influence.models;
import net.tomp2p.peers.PeerAddress;
import java.io.Serializable;
import io.github.chronosx88.influence.helpers.DSAKey;
/**
* Класс-модель публичного профиля для размещения в DHT-сети
*/
public class PublicUserProfile implements Serializable {
private String userName;
private PeerAddress peerAddress;
private DSAKey publicKey;
public PublicUserProfile(String userName, PeerAddress peerAddress, DSAKey publicKey) {
this.userName = userName;
this.peerAddress = peerAddress;
this.publicKey = publicKey;
}
public String getUserName() {
return userName;
}
public PeerAddress getPeerAddress() {
return peerAddress;
}
public void setPeerAddress(PeerAddress peerAddress) {
this.peerAddress = peerAddress;
}
public void setUserName(String userName) {
this.userName = userName;
}
public DSAKey getPublicKey() {
return publicKey;
}
public void setPublicKey(DSAKey publicKey) {
this.publicKey = publicKey;
}
}

View File

@ -18,6 +18,9 @@ public interface ChatDao {
@Query("SELECT * FROM chats") @Query("SELECT * FROM chats")
List<ChatEntity> getAllChats(); List<ChatEntity> getAllChats();
@Query("SELECT * FROM chats WHERE id = :chatID") @Query("SELECT * FROM chats WHERE chatID = :chatID")
List<ChatEntity> getChatByID(String chatID); List<ChatEntity> getChatByChatID(String chatID);
@Query("SELECT * FROM chats WHERE id = :id")
List<ChatEntity> getChatByID(String id);
} }

View File

@ -1,5 +1,10 @@
package io.github.chronosx88.influence.models.roomEntities; package io.github.chronosx88.influence.models.roomEntities;
import net.tomp2p.peers.PeerAddress;
import java.util.ArrayList;
import java.util.Collections;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.room.ColumnInfo; import androidx.room.ColumnInfo;
import androidx.room.Entity; import androidx.room.Entity;
@ -7,19 +12,20 @@ import androidx.room.PrimaryKey;
@Entity(tableName = "chats") @Entity(tableName = "chats")
public class ChatEntity { public class ChatEntity {
@NonNull @PrimaryKey String id; @PrimaryKey(autoGenerate = true) public int id;
@ColumnInfo public String chatID;
@ColumnInfo public String name; @ColumnInfo public String name;
@ColumnInfo public String peerAddresses; @ColumnInfo public byte[] peerAddresses;
@ColumnInfo public String keyPairID; @ColumnInfo public String keyPairID;
public ChatEntity(String id, String name, String peerAddresses, String keyPairID) { public ChatEntity(String chatID, String name, String keyPairID, byte[] peerAddresses) {
this.id = id; this.chatID = chatID;
this.name = name; this.name = name;
this.peerAddresses = peerAddresses; this.peerAddresses = peerAddresses;
this.keyPairID = keyPairID; this.keyPairID = keyPairID;
} }
public String getId() { public int getId() {
return id; return id;
} }
@ -27,9 +33,13 @@ public class ChatEntity {
return keyPairID; return keyPairID;
} }
public String getPeerAddress() { return peerAddresses; } public byte[] getPeerAddress() { return peerAddresses; }
public String getName() { public String getName() {
return name; return name;
} }
public String getChatID() {
return chatID;
}
} }

View File

@ -8,14 +8,16 @@ import androidx.room.PrimaryKey;
@Entity(tableName = "messages") @Entity(tableName = "messages")
public class MessageEntity { public class MessageEntity {
@NonNull @PrimaryKey String id; @NonNull @PrimaryKey String id;
@ColumnInfo String chatID; @ColumnInfo public String chatID;
@ColumnInfo String sender; @ColumnInfo public String sender;
@ColumnInfo String text; @ColumnInfo public String date;
@ColumnInfo public String text;
public MessageEntity(String id, String chatID, String sender, String text) { public MessageEntity(String id, String chatID, String sender, String date, String text) {
this.id = id; this.id = id;
this.chatID = chatID; this.chatID = chatID;
this.sender = sender; this.sender = sender;
this.date = date;
this.text = text; this.text = text;
} }

View File

@ -4,6 +4,7 @@ import com.google.gson.JsonObject;
import java.util.ArrayList; import java.util.ArrayList;
import io.github.chronosx88.influence.contracts.observer.NetworkObserver;
import io.github.chronosx88.influence.contracts.observer.Observable; import io.github.chronosx88.influence.contracts.observer.Observable;
import io.github.chronosx88.influence.contracts.observer.Observer; import io.github.chronosx88.influence.contracts.observer.Observer;
@ -12,70 +13,44 @@ public class MainObservable implements Observable {
public static final int OTHER_ACTIONS_CHANNEL = 1; public static final int OTHER_ACTIONS_CHANNEL = 1;
private ArrayList<Observer> uiObservers; private ArrayList<Observer> uiObservers;
private ArrayList<Observer> otherObservers; private ArrayList<NetworkObserver> networkObservers;
public MainObservable() { public MainObservable() {
this.uiObservers = new ArrayList<>(); this.uiObservers = new ArrayList<>();
this.otherObservers = new ArrayList<>(); this.networkObservers = new ArrayList<>();
} }
@Override @Override
public void register(Observer observer, int channelID) { public void register(Observer observer) {
switch (channelID) {
case UI_ACTIONS_CHANNEL: {
uiObservers.add(observer); uiObservers.add(observer);
break;
}
case OTHER_ACTIONS_CHANNEL: {
otherObservers.add(observer);
break;
}
default: {
otherObservers.add(observer);
break;
}
}
} }
@Override @Override
public void unregister(Observer observer, int channelID) { public void register(NetworkObserver observer) {
switch (channelID) { networkObservers.add(observer);
case UI_ACTIONS_CHANNEL: { }
@Override
public void unregister(Observer observer) {
uiObservers.remove(observer); uiObservers.remove(observer);
break;
}
case OTHER_ACTIONS_CHANNEL: {
otherObservers.remove(observer);
break;
}
}
} }
@Override @Override
public void notifyObservers(JsonObject jsonObject, int channelID) { public void unregister(NetworkObserver observer) {
switch (channelID) { networkObservers.remove(observer);
case UI_ACTIONS_CHANNEL: { }
@Override
public void notifyUIObservers(JsonObject jsonObject) {
for (Observer observer : uiObservers) { for (Observer observer : uiObservers) {
observer.handleEvent(jsonObject); observer.handleEvent(jsonObject);
} }
break;
} }
case OTHER_ACTIONS_CHANNEL: { @Override
for (Observer observer : otherObservers) { public void notifyNetworkObservers(Object object) {
observer.handleEvent(jsonObject); for (NetworkObserver observer : networkObservers) {
} observer.handleEvent(object);
break;
}
default: {
for (Observer observer : otherObservers) {
observer.handleEvent(jsonObject);
}
break;
}
} }
} }
} }

View File

@ -22,13 +22,12 @@ public class ChatListPresenter implements ChatListPresenterContract, Observer {
chatListAdapter = new ChatListAdapter(); chatListAdapter = new ChatListAdapter();
this.logic = new ChatListLogic(); this.logic = new ChatListLogic();
this.view.setRecycleAdapter(chatListAdapter); this.view.setRecycleAdapter(chatListAdapter);
AppHelper.getObservable().register(this, MainObservable.UI_ACTIONS_CHANNEL); AppHelper.getObservable().register(this);
} }
@Override @Override
public void updateChatList() { public void updateChatList() {
chatListAdapter.setChatList(logic.loadAllChats()); view.updateChatList(chatListAdapter, logic.loadAllChats());
chatListAdapter.notifyDataSetChanged();
} }
@Override @Override

View File

@ -18,7 +18,7 @@ public class StartChatPresenter implements StartChatPresenterContract, Observer
public StartChatPresenter(StartChatViewContract view) { public StartChatPresenter(StartChatViewContract view) {
this.view = view; this.view = view;
this.logic = new StartChatLogic(); this.logic = new StartChatLogic();
AppHelper.getObservable().register(this, MainObservable.UI_ACTIONS_CHANNEL); AppHelper.getObservable().register(this);
} }
@Override @Override

View File

@ -70,7 +70,7 @@ public class MainActivity extends AppCompatActivity implements Observer, MainVie
.commit(); .commit();
presenter = new MainPresenter(this); presenter = new MainPresenter(this);
AppHelper.getObservable().register(this, MainObservable.UI_ACTIONS_CHANNEL); AppHelper.getObservable().register(this);
progressDialog = new ProgressDialog(MainActivity.this, R.style.AlertDialogTheme); progressDialog = new ProgressDialog(MainActivity.this, R.style.AlertDialogTheme);
progressDialog.setCancelable(false); progressDialog.setCancelable(false);
@ -83,7 +83,7 @@ public class MainActivity extends AppCompatActivity implements Observer, MainVie
protected void onDestroy() { protected void onDestroy() {
super.onDestroy(); super.onDestroy();
presenter.onDestroy(); presenter.onDestroy();
AppHelper.getObservable().unregister(this, MainObservable.UI_ACTIONS_CHANNEL); AppHelper.getObservable().unregister(this);
} }
@Override @Override

View File

@ -7,6 +7,8 @@ import android.view.ViewGroup;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import java.util.List;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
@ -19,6 +21,7 @@ import io.github.chronosx88.influence.contracts.observer.Observer;
import io.github.chronosx88.influence.helpers.AppHelper; import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.ChatListAdapter; import io.github.chronosx88.influence.helpers.ChatListAdapter;
import io.github.chronosx88.influence.helpers.actions.UIActions; import io.github.chronosx88.influence.helpers.actions.UIActions;
import io.github.chronosx88.influence.models.roomEntities.ChatEntity;
import io.github.chronosx88.influence.observable.MainObservable; import io.github.chronosx88.influence.observable.MainObservable;
import io.github.chronosx88.influence.presenters.ChatListPresenter; import io.github.chronosx88.influence.presenters.ChatListPresenter;
@ -29,7 +32,7 @@ public class ChatListFragment extends Fragment implements ChatListViewContract,
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
AppHelper.getObservable().register(this, MainObservable.UI_ACTIONS_CHANNEL); AppHelper.getObservable().register(this);
} }
@Override @Override
@ -66,4 +69,12 @@ public class ChatListFragment extends Fragment implements ChatListViewContract,
} }
} }
} }
@Override
public void updateChatList(ChatListAdapter adapter, List<ChatEntity> chats) {
getActivity().runOnUiThread(() -> {
adapter.setChatList(chats);
adapter.notifyDataSetChanged();
});
}
} }

View File

@ -5,6 +5,7 @@ import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.textfield.TextInputLayout; import com.google.android.material.textfield.TextInputLayout;
@ -14,10 +15,13 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import io.github.chronosx88.influence.R; import io.github.chronosx88.influence.R;
import io.github.chronosx88.influence.contracts.startchat.StartChatViewContract; import io.github.chronosx88.influence.contracts.startchat.StartChatViewContract;
import io.github.chronosx88.influence.presenters.StartChatPresenter;
public class StartChatFragment extends Fragment implements StartChatViewContract { public class StartChatFragment extends Fragment implements StartChatViewContract {
private TextInputLayout textInputPeerID; private TextInputLayout textInputPeerID;
private ProgressDialog progressDialog; private ProgressDialog progressDialog;
private Button createChatButton;
private StartChatPresenter presenter;
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
@ -28,10 +32,15 @@ public class StartChatFragment extends Fragment implements StartChatViewContract
@Override @Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
presenter = new StartChatPresenter(this);
textInputPeerID = view.findViewById(R.id.textInputPeerID); textInputPeerID = view.findViewById(R.id.textInputPeerID);
progressDialog = new ProgressDialog(getActivity(), R.style.AlertDialogTheme); progressDialog = new ProgressDialog(getActivity(), R.style.AlertDialogTheme);
progressDialog.setCancelable(false); progressDialog.setCancelable(false);
progressDialog.setProgressStyle(android.R.style.Widget_ProgressBar_Small); progressDialog.setProgressStyle(android.R.style.Widget_ProgressBar_Small);
createChatButton = view.findViewById(R.id.create_chat_button);
createChatButton.setOnClickListener((v) -> {
presenter.startChatWithPeer(textInputPeerID.getEditText().getText().toString());
});
} }
@Override @Override

View File

@ -24,6 +24,7 @@
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<Button <Button
android:id="@+id/create_chat_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Создать чат"/> android:text="Создать чат"/>