mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-19 06:09:55 +01:00
Changed the message format to store the author and group inline - this
doesn't take a huge amount of space and allows every message to be self-certifying.
This commit is contained in:
12
api/net/sf/briar/api/protocol/Author.java
Normal file
12
api/net/sf/briar/api/protocol/Author.java
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package net.sf.briar.api.protocol;
|
||||||
|
|
||||||
|
import net.sf.briar.api.serial.Writable;
|
||||||
|
|
||||||
|
public interface Author extends Writable {
|
||||||
|
|
||||||
|
AuthorId getId();
|
||||||
|
|
||||||
|
String getName();
|
||||||
|
|
||||||
|
byte[] getPublicKey();
|
||||||
|
}
|
||||||
10
api/net/sf/briar/api/protocol/AuthorFactory.java
Normal file
10
api/net/sf/briar/api/protocol/AuthorFactory.java
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package net.sf.briar.api.protocol;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public interface AuthorFactory {
|
||||||
|
|
||||||
|
Author createAuthor(String name, byte[] publicKey) throws IOException;
|
||||||
|
|
||||||
|
Author createAuthor(AuthorId id, String name, byte[] publicKey);
|
||||||
|
}
|
||||||
@@ -8,6 +8,9 @@ import net.sf.briar.api.serial.Writer;
|
|||||||
/** Type-safe wrapper for a byte array that uniquely identifies an author. */
|
/** Type-safe wrapper for a byte array that uniquely identifies an author. */
|
||||||
public class AuthorId extends UniqueId {
|
public class AuthorId extends UniqueId {
|
||||||
|
|
||||||
|
/** Used to indicate that a message is anonymous. */
|
||||||
|
public static final AuthorId NONE = new AuthorId(new byte[UniqueId.LENGTH]);
|
||||||
|
|
||||||
public AuthorId(byte[] id) {
|
public AuthorId(byte[] id) {
|
||||||
super(id);
|
super(id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
package net.sf.briar.api.protocol;
|
package net.sf.briar.api.protocol;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
public interface GroupFactory {
|
public interface GroupFactory {
|
||||||
|
|
||||||
|
Group createGroup(String name, byte[] publicKey) throws IOException;
|
||||||
|
|
||||||
Group createGroup(GroupId id, String name, byte[] publicKey);
|
Group createGroup(GroupId id, String name, byte[] publicKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,14 @@ package net.sf.briar.api.protocol;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.KeyPair;
|
import java.security.PrivateKey;
|
||||||
|
|
||||||
public interface MessageEncoder {
|
public interface MessageEncoder {
|
||||||
|
|
||||||
Message encodeMessage(MessageId parent, GroupId group, String nick,
|
Message encodeMessage(MessageId parent, Group group, byte[] body)
|
||||||
KeyPair keyPair, byte[] body) throws IOException,
|
throws IOException;
|
||||||
|
|
||||||
|
Message encodeMessage(MessageId parent, Group group, Author author,
|
||||||
|
PrivateKey privateKey, byte[] body) throws IOException,
|
||||||
GeneralSecurityException;
|
GeneralSecurityException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,8 @@ import net.sf.briar.api.serial.Writer;
|
|||||||
public class MessageId extends UniqueId {
|
public class MessageId extends UniqueId {
|
||||||
|
|
||||||
/** Used to indicate that the first message in a thread has no parent. */
|
/** Used to indicate that the first message in a thread has no parent. */
|
||||||
public static final MessageId NONE = new MessageId(new byte[] {
|
public static final MessageId NONE =
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
new MessageId(new byte[UniqueId.LENGTH]);
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
|
||||||
});
|
|
||||||
|
|
||||||
public MessageId(byte[] id) {
|
public MessageId(byte[] id) {
|
||||||
super(id);
|
super(id);
|
||||||
|
|||||||
@@ -8,13 +8,14 @@ package net.sf.briar.api.protocol;
|
|||||||
public interface Tags {
|
public interface Tags {
|
||||||
|
|
||||||
static final int ACK = 0;
|
static final int ACK = 0;
|
||||||
static final int AUTHOR_ID = 1;
|
static final int AUTHOR = 1;
|
||||||
static final int BATCH = 2;
|
static final int AUTHOR_ID = 2;
|
||||||
static final int BATCH_ID = 3;
|
static final int BATCH = 3;
|
||||||
static final int GROUP = 4;
|
static final int BATCH_ID = 4;
|
||||||
static final int GROUP_ID = 5;
|
static final int GROUP = 5;
|
||||||
static final int MESSAGE = 6;
|
static final int GROUP_ID = 6;
|
||||||
static final int MESSAGE_ID = 7;
|
static final int MESSAGE = 7;
|
||||||
static final int SUBSCRIPTIONS = 8;
|
static final int MESSAGE_ID = 8;
|
||||||
static final int TRANSPORTS = 9;
|
static final int SUBSCRIPTIONS = 9;
|
||||||
|
static final int TRANSPORTS = 10;
|
||||||
}
|
}
|
||||||
|
|||||||
42
components/net/sf/briar/protocol/AuthorFactoryImpl.java
Normal file
42
components/net/sf/briar/protocol/AuthorFactoryImpl.java
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package net.sf.briar.protocol;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
|
||||||
|
import net.sf.briar.api.crypto.CryptoComponent;
|
||||||
|
import net.sf.briar.api.protocol.Author;
|
||||||
|
import net.sf.briar.api.protocol.AuthorFactory;
|
||||||
|
import net.sf.briar.api.protocol.AuthorId;
|
||||||
|
import net.sf.briar.api.serial.Writer;
|
||||||
|
import net.sf.briar.api.serial.WriterFactory;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
|
||||||
|
class AuthorFactoryImpl implements AuthorFactory {
|
||||||
|
|
||||||
|
private final CryptoComponent crypto;
|
||||||
|
private final WriterFactory writerFactory;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
AuthorFactoryImpl(CryptoComponent crypto, WriterFactory writerFactory) {
|
||||||
|
this.crypto = crypto;
|
||||||
|
this.writerFactory = writerFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Author createAuthor(String name, byte[] publicKey)
|
||||||
|
throws IOException {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
Writer w = writerFactory.createWriter(out);
|
||||||
|
new AuthorImpl(null, name, publicKey).writeTo(w);
|
||||||
|
MessageDigest messageDigest = crypto.getMessageDigest();
|
||||||
|
messageDigest.reset();
|
||||||
|
messageDigest.update(out.toByteArray());
|
||||||
|
AuthorId id = new AuthorId(messageDigest.digest());
|
||||||
|
return new AuthorImpl(id, name, publicKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Author createAuthor(AuthorId id, String name, byte[] publicKey) {
|
||||||
|
return new AuthorImpl(id, name, publicKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
39
components/net/sf/briar/protocol/AuthorImpl.java
Normal file
39
components/net/sf/briar/protocol/AuthorImpl.java
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package net.sf.briar.protocol;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import net.sf.briar.api.protocol.Author;
|
||||||
|
import net.sf.briar.api.protocol.AuthorId;
|
||||||
|
import net.sf.briar.api.protocol.Tags;
|
||||||
|
import net.sf.briar.api.serial.Writer;
|
||||||
|
|
||||||
|
class AuthorImpl implements Author {
|
||||||
|
|
||||||
|
private final AuthorId id;
|
||||||
|
private final String name;
|
||||||
|
private final byte[] publicKey;
|
||||||
|
|
||||||
|
AuthorImpl(AuthorId id, String name, byte[] publicKey) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
this.publicKey = publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthorId getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getPublicKey() {
|
||||||
|
return publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeTo(Writer w) throws IOException {
|
||||||
|
w.writeUserDefinedTag(Tags.AUTHOR);
|
||||||
|
w.writeString(name);
|
||||||
|
w.writeBytes(publicKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
38
components/net/sf/briar/protocol/AuthorReader.java
Normal file
38
components/net/sf/briar/protocol/AuthorReader.java
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package net.sf.briar.protocol;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
|
||||||
|
import net.sf.briar.api.crypto.CryptoComponent;
|
||||||
|
import net.sf.briar.api.protocol.Author;
|
||||||
|
import net.sf.briar.api.protocol.AuthorFactory;
|
||||||
|
import net.sf.briar.api.protocol.AuthorId;
|
||||||
|
import net.sf.briar.api.protocol.Tags;
|
||||||
|
import net.sf.briar.api.serial.ObjectReader;
|
||||||
|
import net.sf.briar.api.serial.Reader;
|
||||||
|
|
||||||
|
class AuthorReader implements ObjectReader<Author> {
|
||||||
|
|
||||||
|
private final MessageDigest messageDigest;
|
||||||
|
private final AuthorFactory authorFactory;
|
||||||
|
|
||||||
|
AuthorReader(CryptoComponent crypto, AuthorFactory authorFactory) {
|
||||||
|
messageDigest = crypto.getMessageDigest();
|
||||||
|
this.authorFactory = authorFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Author readObject(Reader r) throws IOException {
|
||||||
|
// Initialise the consumer
|
||||||
|
DigestingConsumer digesting = new DigestingConsumer(messageDigest);
|
||||||
|
messageDigest.reset();
|
||||||
|
// Read and digest the data
|
||||||
|
r.addConsumer(digesting);
|
||||||
|
r.readUserDefinedTag(Tags.AUTHOR);
|
||||||
|
String name = r.readString();
|
||||||
|
byte[] publicKey = r.readBytes();
|
||||||
|
r.removeConsumer(digesting);
|
||||||
|
// Build and return the author
|
||||||
|
AuthorId id = new AuthorId(messageDigest.digest());
|
||||||
|
return authorFactory.createAuthor(id, name, publicKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import java.io.IOException;
|
|||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import net.sf.briar.api.crypto.CryptoComponent;
|
||||||
import net.sf.briar.api.protocol.Batch;
|
import net.sf.briar.api.protocol.Batch;
|
||||||
import net.sf.briar.api.protocol.BatchId;
|
import net.sf.briar.api.protocol.BatchId;
|
||||||
import net.sf.briar.api.protocol.Message;
|
import net.sf.briar.api.protocol.Message;
|
||||||
@@ -17,9 +18,9 @@ class BatchReader implements ObjectReader<Batch> {
|
|||||||
private final ObjectReader<Message> messageReader;
|
private final ObjectReader<Message> messageReader;
|
||||||
private final BatchFactory batchFactory;
|
private final BatchFactory batchFactory;
|
||||||
|
|
||||||
BatchReader(MessageDigest messageDigest,
|
BatchReader(CryptoComponent crypto, ObjectReader<Message> messageReader,
|
||||||
ObjectReader<Message> messageReader, BatchFactory batchFactory) {
|
BatchFactory batchFactory) {
|
||||||
this.messageDigest = messageDigest;
|
messageDigest = crypto.getMessageDigest();
|
||||||
this.messageReader = messageReader;
|
this.messageReader = messageReader;
|
||||||
this.batchFactory = batchFactory;
|
this.batchFactory = batchFactory;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,40 @@
|
|||||||
package net.sf.briar.protocol;
|
package net.sf.briar.protocol;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
|
||||||
|
import net.sf.briar.api.crypto.CryptoComponent;
|
||||||
import net.sf.briar.api.protocol.Group;
|
import net.sf.briar.api.protocol.Group;
|
||||||
import net.sf.briar.api.protocol.GroupFactory;
|
import net.sf.briar.api.protocol.GroupFactory;
|
||||||
import net.sf.briar.api.protocol.GroupId;
|
import net.sf.briar.api.protocol.GroupId;
|
||||||
|
import net.sf.briar.api.serial.Writer;
|
||||||
|
import net.sf.briar.api.serial.WriterFactory;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
|
||||||
class GroupFactoryImpl implements GroupFactory {
|
class GroupFactoryImpl implements GroupFactory {
|
||||||
|
|
||||||
|
private final CryptoComponent crypto;
|
||||||
|
private final WriterFactory writerFactory;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
GroupFactoryImpl(CryptoComponent crypto, WriterFactory writerFactory) {
|
||||||
|
this.crypto = crypto;
|
||||||
|
this.writerFactory = writerFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Group createGroup(String name, byte[] publicKey) throws IOException {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
Writer w = writerFactory.createWriter(out);
|
||||||
|
new GroupImpl(null, name, publicKey).writeTo(w);
|
||||||
|
MessageDigest messageDigest = crypto.getMessageDigest();
|
||||||
|
messageDigest.reset();
|
||||||
|
messageDigest.update(out.toByteArray());
|
||||||
|
GroupId id = new GroupId(messageDigest.digest());
|
||||||
|
return new GroupImpl(id, name, publicKey);
|
||||||
|
}
|
||||||
|
|
||||||
public Group createGroup(GroupId id, String name, byte[] publicKey) {
|
public Group createGroup(GroupId id, String name, byte[] publicKey) {
|
||||||
return new GroupImpl(id, name, publicKey);
|
return new GroupImpl(id, name, publicKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package net.sf.briar.protocol;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
|
|
||||||
|
import net.sf.briar.api.crypto.CryptoComponent;
|
||||||
import net.sf.briar.api.protocol.Group;
|
import net.sf.briar.api.protocol.Group;
|
||||||
import net.sf.briar.api.protocol.GroupFactory;
|
import net.sf.briar.api.protocol.GroupFactory;
|
||||||
import net.sf.briar.api.protocol.GroupId;
|
import net.sf.briar.api.protocol.GroupId;
|
||||||
@@ -15,8 +16,8 @@ class GroupReader implements ObjectReader<Group> {
|
|||||||
private final MessageDigest messageDigest;
|
private final MessageDigest messageDigest;
|
||||||
private final GroupFactory groupFactory;
|
private final GroupFactory groupFactory;
|
||||||
|
|
||||||
GroupReader(MessageDigest messageDigest, GroupFactory groupFactory) {
|
GroupReader(CryptoComponent crypto, GroupFactory groupFactory) {
|
||||||
this.messageDigest = messageDigest;
|
messageDigest = crypto.getMessageDigest();
|
||||||
this.groupFactory = groupFactory;
|
this.groupFactory = groupFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ package net.sf.briar.protocol;
|
|||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
|
import java.security.PrivateKey;
|
||||||
import java.security.Signature;
|
import java.security.Signature;
|
||||||
|
|
||||||
import net.sf.briar.api.crypto.CryptoComponent;
|
import net.sf.briar.api.crypto.CryptoComponent;
|
||||||
|
import net.sf.briar.api.protocol.Author;
|
||||||
import net.sf.briar.api.protocol.AuthorId;
|
import net.sf.briar.api.protocol.AuthorId;
|
||||||
import net.sf.briar.api.protocol.GroupId;
|
import net.sf.briar.api.protocol.Group;
|
||||||
import net.sf.briar.api.protocol.Message;
|
import net.sf.briar.api.protocol.Message;
|
||||||
import net.sf.briar.api.protocol.MessageEncoder;
|
import net.sf.briar.api.protocol.MessageEncoder;
|
||||||
import net.sf.briar.api.protocol.MessageId;
|
import net.sf.briar.api.protocol.MessageId;
|
||||||
@@ -32,8 +33,30 @@ class MessageEncoderImpl implements MessageEncoder {
|
|||||||
this.writerFactory = writerFactory;
|
this.writerFactory = writerFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Message encodeMessage(MessageId parent, GroupId group, String nick,
|
public Message encodeMessage(MessageId parent, Group group, byte[] body)
|
||||||
KeyPair keyPair, byte[] body) throws IOException,
|
throws IOException {
|
||||||
|
long timestamp = System.currentTimeMillis();
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
Writer w = writerFactory.createWriter(out);
|
||||||
|
// Write the message
|
||||||
|
w.writeUserDefinedTag(Tags.MESSAGE);
|
||||||
|
parent.writeTo(w);
|
||||||
|
group.writeTo(w);
|
||||||
|
w.writeNull(); // No author
|
||||||
|
w.writeInt64(timestamp);
|
||||||
|
w.writeBytes(body);
|
||||||
|
w.writeNull(); // No author's signature
|
||||||
|
byte[] raw = out.toByteArray();
|
||||||
|
// The message ID is the hash of the entire message
|
||||||
|
messageDigest.reset();
|
||||||
|
messageDigest.update(raw);
|
||||||
|
MessageId id = new MessageId(messageDigest.digest());
|
||||||
|
return new MessageImpl(id, parent, group.getId(), AuthorId.NONE,
|
||||||
|
timestamp, raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Message encodeMessage(MessageId parent, Group group, Author author,
|
||||||
|
PrivateKey privateKey, byte[] body) throws IOException,
|
||||||
GeneralSecurityException {
|
GeneralSecurityException {
|
||||||
long timestamp = System.currentTimeMillis();
|
long timestamp = System.currentTimeMillis();
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
@@ -42,13 +65,12 @@ class MessageEncoderImpl implements MessageEncoder {
|
|||||||
w.writeUserDefinedTag(Tags.MESSAGE);
|
w.writeUserDefinedTag(Tags.MESSAGE);
|
||||||
parent.writeTo(w);
|
parent.writeTo(w);
|
||||||
group.writeTo(w);
|
group.writeTo(w);
|
||||||
|
author.writeTo(w);
|
||||||
w.writeInt64(timestamp);
|
w.writeInt64(timestamp);
|
||||||
w.writeString(nick);
|
|
||||||
w.writeBytes(keyPair.getPublic().getEncoded());
|
|
||||||
w.writeBytes(body);
|
w.writeBytes(body);
|
||||||
// Sign the message
|
// Sign the message
|
||||||
byte[] signable = out.toByteArray();
|
byte[] signable = out.toByteArray();
|
||||||
signature.initSign(keyPair.getPrivate());
|
signature.initSign(privateKey);
|
||||||
signature.update(signable);
|
signature.update(signable);
|
||||||
byte[] sig = signature.sign();
|
byte[] sig = signature.sign();
|
||||||
signable = null;
|
signable = null;
|
||||||
@@ -59,14 +81,14 @@ class MessageEncoderImpl implements MessageEncoder {
|
|||||||
messageDigest.reset();
|
messageDigest.reset();
|
||||||
messageDigest.update(raw);
|
messageDigest.update(raw);
|
||||||
MessageId id = new MessageId(messageDigest.digest());
|
MessageId id = new MessageId(messageDigest.digest());
|
||||||
// The author ID is the hash of the author's nick and public key
|
// The author ID is the hash of the author object
|
||||||
out.reset();
|
out.reset();
|
||||||
w = writerFactory.createWriter(out);
|
w = writerFactory.createWriter(out);
|
||||||
w.writeString(nick);
|
author.writeTo(w);
|
||||||
w.writeBytes(keyPair.getPublic().getEncoded());
|
|
||||||
messageDigest.reset();
|
messageDigest.reset();
|
||||||
messageDigest.update(out.toByteArray());
|
messageDigest.update(out.toByteArray());
|
||||||
AuthorId authorId = new AuthorId(messageDigest.digest());
|
AuthorId authorId = new AuthorId(messageDigest.digest());
|
||||||
return new MessageImpl(id, parent, group, authorId, timestamp, raw);
|
return new MessageImpl(id, parent, group.getId(), authorId, timestamp,
|
||||||
|
raw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ import java.security.MessageDigest;
|
|||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.security.Signature;
|
import java.security.Signature;
|
||||||
import java.security.SignatureException;
|
import java.security.SignatureException;
|
||||||
import java.security.spec.InvalidKeySpecException;
|
|
||||||
|
|
||||||
|
import net.sf.briar.api.crypto.CryptoComponent;
|
||||||
import net.sf.briar.api.crypto.KeyParser;
|
import net.sf.briar.api.crypto.KeyParser;
|
||||||
|
import net.sf.briar.api.protocol.Author;
|
||||||
import net.sf.briar.api.protocol.AuthorId;
|
import net.sf.briar.api.protocol.AuthorId;
|
||||||
import net.sf.briar.api.protocol.GroupId;
|
import net.sf.briar.api.protocol.Group;
|
||||||
import net.sf.briar.api.protocol.Message;
|
import net.sf.briar.api.protocol.Message;
|
||||||
import net.sf.briar.api.protocol.MessageId;
|
import net.sf.briar.api.protocol.MessageId;
|
||||||
import net.sf.briar.api.protocol.Tags;
|
import net.sf.briar.api.protocol.Tags;
|
||||||
@@ -21,15 +22,19 @@ import net.sf.briar.api.serial.Reader;
|
|||||||
|
|
||||||
class MessageReader implements ObjectReader<Message> {
|
class MessageReader implements ObjectReader<Message> {
|
||||||
|
|
||||||
|
private final ObjectReader<Group> groupReader;
|
||||||
|
private final ObjectReader<Author> authorReader;
|
||||||
private final KeyParser keyParser;
|
private final KeyParser keyParser;
|
||||||
private final Signature signature;
|
private final Signature signature;
|
||||||
private final MessageDigest messageDigest;
|
private final MessageDigest messageDigest;
|
||||||
|
|
||||||
MessageReader(KeyParser keyParser, Signature signature,
|
MessageReader(CryptoComponent crypto, ObjectReader<Group> groupReader,
|
||||||
MessageDigest messageDigest) {
|
ObjectReader<Author> authorReader) {
|
||||||
this.keyParser = keyParser;
|
this.groupReader = groupReader;
|
||||||
this.signature = signature;
|
this.authorReader = authorReader;
|
||||||
this.messageDigest = messageDigest;
|
keyParser = crypto.getKeyParser();
|
||||||
|
signature = crypto.getSignature();
|
||||||
|
messageDigest = crypto.getMessageDigest();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Message readObject(Reader r) throws IOException {
|
public Message readObject(Reader r) throws IOException {
|
||||||
@@ -44,49 +49,49 @@ class MessageReader implements ObjectReader<Message> {
|
|||||||
byte[] b = r.readBytes();
|
byte[] b = r.readBytes();
|
||||||
if(b.length != UniqueId.LENGTH) throw new FormatException();
|
if(b.length != UniqueId.LENGTH) throw new FormatException();
|
||||||
MessageId parent = new MessageId(b);
|
MessageId parent = new MessageId(b);
|
||||||
// Read the group ID
|
// Read the group
|
||||||
r.readUserDefinedTag(Tags.GROUP_ID);
|
r.addObjectReader(Tags.GROUP, groupReader);
|
||||||
b = r.readBytes();
|
Group group = r.readUserDefined(Tags.GROUP, Group.class);
|
||||||
if(b.length != UniqueId.LENGTH) throw new FormatException();
|
r.removeObjectReader(Tags.GROUP);
|
||||||
GroupId group = new GroupId(b);
|
// Read the author, if there is one
|
||||||
|
r.addObjectReader(Tags.AUTHOR, authorReader);
|
||||||
|
Author author = null;
|
||||||
|
if(r.hasNull()) r.readNull();
|
||||||
|
else author = r.readUserDefined(Tags.AUTHOR, Author.class);
|
||||||
|
r.removeObjectReader(Tags.AUTHOR);
|
||||||
// Read the timestamp
|
// Read the timestamp
|
||||||
long timestamp = r.readInt64();
|
long timestamp = r.readInt64();
|
||||||
if(timestamp < 0L) throw new FormatException();
|
if(timestamp < 0L) throw new FormatException();
|
||||||
// Hash the author's nick and public key to get the author ID
|
|
||||||
DigestingConsumer digesting = new DigestingConsumer(messageDigest);
|
|
||||||
messageDigest.reset();
|
|
||||||
r.addConsumer(digesting);
|
|
||||||
r.readString();
|
|
||||||
byte[] encodedKey = r.readBytes();
|
|
||||||
r.removeConsumer(digesting);
|
|
||||||
AuthorId author = new AuthorId(messageDigest.digest());
|
|
||||||
// Skip the message body
|
// Skip the message body
|
||||||
r.readBytes();
|
r.readBytes();
|
||||||
// Record the length of the signed data
|
// Record the length of the signed data
|
||||||
int messageLength = (int) counting.getCount();
|
int messageLength = (int) counting.getCount();
|
||||||
// Read the signature
|
// Read the author's signature, if there is one
|
||||||
byte[] sig = r.readBytes();
|
byte[] authorSig = null;
|
||||||
|
if(author == null) r.readNull();
|
||||||
|
else authorSig = r.readBytes();
|
||||||
|
// That's all, folks
|
||||||
r.removeConsumer(counting);
|
r.removeConsumer(counting);
|
||||||
r.removeConsumer(copying);
|
r.removeConsumer(copying);
|
||||||
// Verify the signature
|
|
||||||
PublicKey publicKey;
|
|
||||||
try {
|
|
||||||
publicKey = keyParser.parsePublicKey(encodedKey);
|
|
||||||
} catch(InvalidKeySpecException e) {
|
|
||||||
throw new FormatException();
|
|
||||||
}
|
|
||||||
byte[] raw = copying.getCopy();
|
byte[] raw = copying.getCopy();
|
||||||
try {
|
// Verify the author's signature, if there is one
|
||||||
signature.initVerify(publicKey);
|
if(author != null) {
|
||||||
signature.update(raw, 0, messageLength);
|
try {
|
||||||
if(!signature.verify(sig)) throw new SignatureException();
|
PublicKey publicKey =
|
||||||
} catch(GeneralSecurityException e) {
|
keyParser.parsePublicKey(author.getPublicKey());
|
||||||
throw new FormatException();
|
signature.initVerify(publicKey);
|
||||||
|
signature.update(raw, 0, messageLength);
|
||||||
|
if(!signature.verify(authorSig)) throw new SignatureException();
|
||||||
|
} catch(GeneralSecurityException e) {
|
||||||
|
throw new FormatException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Hash the message, including the signature, to get the message ID
|
// Hash the message, including the signature, to get the message ID
|
||||||
messageDigest.reset();
|
messageDigest.reset();
|
||||||
messageDigest.update(raw);
|
messageDigest.update(raw);
|
||||||
MessageId id = new MessageId(messageDigest.digest());
|
MessageId id = new MessageId(messageDigest.digest());
|
||||||
return new MessageImpl(id, parent, group, author, timestamp, raw);
|
AuthorId authorId = author == null ? AuthorId.NONE : author.getId();
|
||||||
|
return new MessageImpl(id, parent, group.getId(), authorId, timestamp,
|
||||||
|
raw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package net.sf.briar.protocol;
|
package net.sf.briar.protocol;
|
||||||
|
|
||||||
|
import net.sf.briar.api.protocol.AuthorFactory;
|
||||||
import net.sf.briar.api.protocol.GroupFactory;
|
import net.sf.briar.api.protocol.GroupFactory;
|
||||||
import net.sf.briar.api.protocol.MessageEncoder;
|
import net.sf.briar.api.protocol.MessageEncoder;
|
||||||
|
|
||||||
@@ -10,6 +11,7 @@ public class ProtocolModule extends AbstractModule {
|
|||||||
@Override
|
@Override
|
||||||
protected void configure() {
|
protected void configure() {
|
||||||
bind(AckFactory.class).to(AckFactoryImpl.class);
|
bind(AckFactory.class).to(AckFactoryImpl.class);
|
||||||
|
bind(AuthorFactory.class).to(AuthorFactoryImpl.class);
|
||||||
bind(BatchFactory.class).to(BatchFactoryImpl.class);
|
bind(BatchFactory.class).to(BatchFactoryImpl.class);
|
||||||
bind(GroupFactory.class).to(GroupFactoryImpl.class);
|
bind(GroupFactory.class).to(GroupFactoryImpl.class);
|
||||||
bind(MessageEncoder.class).to(MessageEncoderImpl.class);
|
bind(MessageEncoder.class).to(MessageEncoderImpl.class);
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import net.sf.briar.serial.SerialModule;
|
|||||||
|
|
||||||
import org.jmock.Expectations;
|
import org.jmock.Expectations;
|
||||||
import org.jmock.Mockery;
|
import org.jmock.Mockery;
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import com.google.inject.Guice;
|
import com.google.inject.Guice;
|
||||||
@@ -33,7 +32,7 @@ public class BatchReaderTest extends TestCase {
|
|||||||
|
|
||||||
private final ReaderFactory readerFactory;
|
private final ReaderFactory readerFactory;
|
||||||
private final WriterFactory writerFactory;
|
private final WriterFactory writerFactory;
|
||||||
private final MessageDigest messageDigest;
|
private final CryptoComponent crypto;
|
||||||
private final Mockery context;
|
private final Mockery context;
|
||||||
private final Message message;
|
private final Message message;
|
||||||
|
|
||||||
@@ -43,21 +42,16 @@ public class BatchReaderTest extends TestCase {
|
|||||||
new CryptoModule());
|
new CryptoModule());
|
||||||
readerFactory = i.getInstance(ReaderFactory.class);
|
readerFactory = i.getInstance(ReaderFactory.class);
|
||||||
writerFactory = i.getInstance(WriterFactory.class);
|
writerFactory = i.getInstance(WriterFactory.class);
|
||||||
messageDigest = i.getInstance(CryptoComponent.class).getMessageDigest();
|
crypto = i.getInstance(CryptoComponent.class);
|
||||||
context = new Mockery();
|
context = new Mockery();
|
||||||
message = context.mock(Message.class);
|
message = context.mock(Message.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
messageDigest.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFormatExceptionIfBatchIsTooLarge() throws Exception {
|
public void testFormatExceptionIfBatchIsTooLarge() throws Exception {
|
||||||
ObjectReader<Message> messageReader = new TestMessageReader();
|
ObjectReader<Message> messageReader = new TestMessageReader();
|
||||||
BatchFactory batchFactory = context.mock(BatchFactory.class);
|
BatchFactory batchFactory = context.mock(BatchFactory.class);
|
||||||
BatchReader batchReader = new BatchReader(messageDigest, messageReader,
|
BatchReader batchReader = new BatchReader(crypto, messageReader,
|
||||||
batchFactory);
|
batchFactory);
|
||||||
|
|
||||||
byte[] b = createBatch(Batch.MAX_SIZE + 1);
|
byte[] b = createBatch(Batch.MAX_SIZE + 1);
|
||||||
@@ -76,7 +70,7 @@ public class BatchReaderTest extends TestCase {
|
|||||||
public void testNoFormatExceptionIfBatchIsMaximumSize() throws Exception {
|
public void testNoFormatExceptionIfBatchIsMaximumSize() throws Exception {
|
||||||
ObjectReader<Message> messageReader = new TestMessageReader();
|
ObjectReader<Message> messageReader = new TestMessageReader();
|
||||||
final BatchFactory batchFactory = context.mock(BatchFactory.class);
|
final BatchFactory batchFactory = context.mock(BatchFactory.class);
|
||||||
BatchReader batchReader = new BatchReader(messageDigest, messageReader,
|
BatchReader batchReader = new BatchReader(crypto, messageReader,
|
||||||
batchFactory);
|
batchFactory);
|
||||||
final Batch batch = context.mock(Batch.class);
|
final Batch batch = context.mock(Batch.class);
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
@@ -98,13 +92,14 @@ public class BatchReaderTest extends TestCase {
|
|||||||
public void testBatchId() throws Exception {
|
public void testBatchId() throws Exception {
|
||||||
byte[] b = createBatch(Batch.MAX_SIZE);
|
byte[] b = createBatch(Batch.MAX_SIZE);
|
||||||
// Calculate the expected batch ID
|
// Calculate the expected batch ID
|
||||||
|
MessageDigest messageDigest = crypto.getMessageDigest();
|
||||||
|
messageDigest.reset();
|
||||||
messageDigest.update(b);
|
messageDigest.update(b);
|
||||||
final BatchId id = new BatchId(messageDigest.digest());
|
final BatchId id = new BatchId(messageDigest.digest());
|
||||||
messageDigest.reset();
|
|
||||||
|
|
||||||
ObjectReader<Message> messageReader = new TestMessageReader();
|
ObjectReader<Message> messageReader = new TestMessageReader();
|
||||||
final BatchFactory batchFactory = context.mock(BatchFactory.class);
|
final BatchFactory batchFactory = context.mock(BatchFactory.class);
|
||||||
BatchReader batchReader = new BatchReader(messageDigest, messageReader,
|
BatchReader batchReader = new BatchReader(crypto, messageReader,
|
||||||
batchFactory);
|
batchFactory);
|
||||||
final Batch batch = context.mock(Batch.class);
|
final Batch batch = context.mock(Batch.class);
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
@@ -126,7 +121,7 @@ public class BatchReaderTest extends TestCase {
|
|||||||
public void testEmptyBatch() throws Exception {
|
public void testEmptyBatch() throws Exception {
|
||||||
ObjectReader<Message> messageReader = new TestMessageReader();
|
ObjectReader<Message> messageReader = new TestMessageReader();
|
||||||
final BatchFactory batchFactory = context.mock(BatchFactory.class);
|
final BatchFactory batchFactory = context.mock(BatchFactory.class);
|
||||||
BatchReader batchReader = new BatchReader(messageDigest, messageReader,
|
BatchReader batchReader = new BatchReader(crypto, messageReader,
|
||||||
batchFactory);
|
batchFactory);
|
||||||
final Batch batch = context.mock(Batch.class);
|
final Batch batch = context.mock(Batch.class);
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
package net.sf.briar.protocol;
|
package net.sf.briar.protocol;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.Signature;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
@@ -14,13 +11,13 @@ import java.util.Iterator;
|
|||||||
import junit.framework.TestCase;
|
import junit.framework.TestCase;
|
||||||
import net.sf.briar.TestUtils;
|
import net.sf.briar.TestUtils;
|
||||||
import net.sf.briar.api.crypto.CryptoComponent;
|
import net.sf.briar.api.crypto.CryptoComponent;
|
||||||
import net.sf.briar.api.crypto.KeyParser;
|
|
||||||
import net.sf.briar.api.protocol.Ack;
|
import net.sf.briar.api.protocol.Ack;
|
||||||
|
import net.sf.briar.api.protocol.Author;
|
||||||
|
import net.sf.briar.api.protocol.AuthorFactory;
|
||||||
import net.sf.briar.api.protocol.Batch;
|
import net.sf.briar.api.protocol.Batch;
|
||||||
import net.sf.briar.api.protocol.BatchId;
|
import net.sf.briar.api.protocol.BatchId;
|
||||||
import net.sf.briar.api.protocol.Group;
|
import net.sf.briar.api.protocol.Group;
|
||||||
import net.sf.briar.api.protocol.GroupFactory;
|
import net.sf.briar.api.protocol.GroupFactory;
|
||||||
import net.sf.briar.api.protocol.GroupId;
|
|
||||||
import net.sf.briar.api.protocol.Message;
|
import net.sf.briar.api.protocol.Message;
|
||||||
import net.sf.briar.api.protocol.MessageEncoder;
|
import net.sf.briar.api.protocol.MessageEncoder;
|
||||||
import net.sf.briar.api.protocol.MessageId;
|
import net.sf.briar.api.protocol.MessageId;
|
||||||
@@ -33,16 +30,13 @@ import net.sf.briar.api.protocol.writers.BatchWriter;
|
|||||||
import net.sf.briar.api.protocol.writers.PacketWriterFactory;
|
import net.sf.briar.api.protocol.writers.PacketWriterFactory;
|
||||||
import net.sf.briar.api.protocol.writers.SubscriptionWriter;
|
import net.sf.briar.api.protocol.writers.SubscriptionWriter;
|
||||||
import net.sf.briar.api.protocol.writers.TransportWriter;
|
import net.sf.briar.api.protocol.writers.TransportWriter;
|
||||||
import net.sf.briar.api.serial.ObjectReader;
|
|
||||||
import net.sf.briar.api.serial.Reader;
|
import net.sf.briar.api.serial.Reader;
|
||||||
import net.sf.briar.api.serial.ReaderFactory;
|
import net.sf.briar.api.serial.ReaderFactory;
|
||||||
import net.sf.briar.api.serial.Writer;
|
|
||||||
import net.sf.briar.api.serial.WriterFactory;
|
import net.sf.briar.api.serial.WriterFactory;
|
||||||
import net.sf.briar.crypto.CryptoModule;
|
import net.sf.briar.crypto.CryptoModule;
|
||||||
import net.sf.briar.protocol.writers.WritersModule;
|
import net.sf.briar.protocol.writers.WritersModule;
|
||||||
import net.sf.briar.serial.SerialModule;
|
import net.sf.briar.serial.SerialModule;
|
||||||
|
|
||||||
import org.apache.commons.io.output.ByteArrayOutputStream;
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -56,19 +50,17 @@ public class FileReadWriteTest extends TestCase {
|
|||||||
private final File file = new File(testDir, "foo");
|
private final File file = new File(testDir, "foo");
|
||||||
|
|
||||||
private final BatchId ack = new BatchId(TestUtils.getRandomId());
|
private final BatchId ack = new BatchId(TestUtils.getRandomId());
|
||||||
private final GroupId sub = new GroupId(TestUtils.getRandomId());
|
private final String authorName = "Foo Bar";
|
||||||
private final String nick = "Foo Bar";
|
|
||||||
private final String messageBody = "This is the message body! Wooooooo!";
|
private final String messageBody = "This is the message body! Wooooooo!";
|
||||||
private final long start = System.currentTimeMillis();
|
private final long start = System.currentTimeMillis();
|
||||||
|
|
||||||
private final ReaderFactory readerFactory;
|
private final ReaderFactory readerFactory;
|
||||||
private final WriterFactory writerFactory;
|
private final WriterFactory writerFactory;
|
||||||
private final PacketWriterFactory packetWriterFactory;
|
private final PacketWriterFactory packetWriterFactory;
|
||||||
private final Signature signature;
|
private final CryptoComponent crypto;
|
||||||
private final MessageDigest messageDigest, batchDigest;
|
private final Author author;
|
||||||
private final KeyParser keyParser;
|
|
||||||
private final Message message;
|
|
||||||
private final Group group;
|
private final Group group;
|
||||||
|
private final Message message;
|
||||||
|
|
||||||
public FileReadWriteTest() throws Exception {
|
public FileReadWriteTest() throws Exception {
|
||||||
super();
|
super();
|
||||||
@@ -78,30 +70,21 @@ public class FileReadWriteTest extends TestCase {
|
|||||||
readerFactory = i.getInstance(ReaderFactory.class);
|
readerFactory = i.getInstance(ReaderFactory.class);
|
||||||
writerFactory = i.getInstance(WriterFactory.class);
|
writerFactory = i.getInstance(WriterFactory.class);
|
||||||
packetWriterFactory = i.getInstance(PacketWriterFactory.class);
|
packetWriterFactory = i.getInstance(PacketWriterFactory.class);
|
||||||
CryptoComponent crypto = i.getInstance(CryptoComponent.class);
|
crypto = i.getInstance(CryptoComponent.class);
|
||||||
keyParser = crypto.getKeyParser();
|
assertEquals(crypto.getMessageDigest().getDigestLength(),
|
||||||
signature = crypto.getSignature();
|
UniqueId.LENGTH);
|
||||||
messageDigest = crypto.getMessageDigest();
|
// Create a group
|
||||||
batchDigest = crypto.getMessageDigest();
|
|
||||||
assertEquals(messageDigest.getDigestLength(), UniqueId.LENGTH);
|
|
||||||
assertEquals(batchDigest.getDigestLength(), UniqueId.LENGTH);
|
|
||||||
// Create and encode a test message
|
|
||||||
MessageEncoder messageEncoder = i.getInstance(MessageEncoder.class);
|
|
||||||
KeyPair keyPair = crypto.generateKeyPair();
|
|
||||||
message = messageEncoder.encodeMessage(MessageId.NONE, sub, nick,
|
|
||||||
keyPair, messageBody.getBytes("UTF-8"));
|
|
||||||
// Create a test group, then write and read it to calculate its ID
|
|
||||||
GroupFactory groupFactory = i.getInstance(GroupFactory.class);
|
GroupFactory groupFactory = i.getInstance(GroupFactory.class);
|
||||||
Group noId = groupFactory.createGroup(
|
group = groupFactory.createGroup("Group name", null);
|
||||||
new GroupId(new byte[UniqueId.LENGTH]), "Group name", null);
|
// Create an author
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
AuthorFactory authorFactory = i.getInstance(AuthorFactory.class);
|
||||||
Writer w = writerFactory.createWriter(out);
|
KeyPair keyPair = crypto.generateKeyPair();
|
||||||
noId.writeTo(w);
|
author = authorFactory.createAuthor(authorName,
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
|
keyPair.getPublic().getEncoded());
|
||||||
Reader r = readerFactory.createReader(in);
|
// Create and encode a test message, signed by the author
|
||||||
ObjectReader<Group> groupReader = new GroupReader(batchDigest,
|
MessageEncoder messageEncoder = i.getInstance(MessageEncoder.class);
|
||||||
groupFactory);
|
message = messageEncoder.encodeMessage(MessageId.NONE, group, author,
|
||||||
group = groupReader.readObject(r);
|
keyPair.getPrivate(), messageBody.getBytes("UTF-8"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@@ -138,17 +121,19 @@ public class FileReadWriteTest extends TestCase {
|
|||||||
|
|
||||||
testWriteFile();
|
testWriteFile();
|
||||||
|
|
||||||
MessageReader messageReader =
|
GroupReader groupReader = new GroupReader(crypto,
|
||||||
new MessageReader(keyParser, signature, messageDigest);
|
new GroupFactoryImpl(crypto, writerFactory));
|
||||||
ObjectReader<Ack> ackReader = new AckReader(new BatchIdReader(),
|
AuthorReader authorReader = new AuthorReader(crypto,
|
||||||
|
new AuthorFactoryImpl(crypto, writerFactory));
|
||||||
|
MessageReader messageReader = new MessageReader(crypto, groupReader,
|
||||||
|
authorReader);
|
||||||
|
AckReader ackReader = new AckReader(new BatchIdReader(),
|
||||||
new AckFactoryImpl());
|
new AckFactoryImpl());
|
||||||
ObjectReader<Batch> batchReader = new BatchReader(batchDigest,
|
BatchReader batchReader = new BatchReader(crypto, messageReader,
|
||||||
messageReader, new BatchFactoryImpl());
|
new BatchFactoryImpl());
|
||||||
ObjectReader<Group> groupReader = new GroupReader(batchDigest,
|
SubscriptionReader subscriptionReader =
|
||||||
new GroupFactoryImpl());
|
|
||||||
ObjectReader<Subscriptions> subscriptionReader =
|
|
||||||
new SubscriptionReader(groupReader, new SubscriptionFactoryImpl());
|
new SubscriptionReader(groupReader, new SubscriptionFactoryImpl());
|
||||||
ObjectReader<Transports> transportReader =
|
TransportReader transportReader =
|
||||||
new TransportReader(new TransportFactoryImpl());
|
new TransportReader(new TransportFactoryImpl());
|
||||||
|
|
||||||
FileInputStream in = new FileInputStream(file);
|
FileInputStream in = new FileInputStream(file);
|
||||||
|
|||||||
Reference in New Issue
Block a user