* Implement deletion of email packets from the DHT and deletion of index packet entries

* Version 2 of the network protocol
This commit is contained in:
HungryHobo
2010-01-18 06:51:27 +00:00
parent 525cd894f5
commit b4072d4813
35 changed files with 1182 additions and 296 deletions

View File

@ -76,13 +76,43 @@ Below is a diagram of how an email packet is routed from a sender to a recipient
`-------' `-------' `---------'
The number of relays for sending or retrieving an email is user-configurable. For higher performance (but
reduced anonymity), it is possible to use no relays, no storers/fetchers, i.e. sender and recipient
talk to the storage nodes directly.
The number of relays for sending or retrieving an email is user-configurable.
TODO explain how an index packet is relayed back to the recipient, who then uses another chain to get the email packets
referenced in the index packet.
For higher performance (but reduced anonymity), it is possible to use no relays, no storers/fetchers,
i.e. sender and recipient talk to the storage nodes directly. If both sender and recipient chose not to
use relays, the diagram looks like this:
.--------.
| Sender | ----------------------------.
`--------' `\
|
V
.--------------- Kademlia DHT -------------------------.
| .---------. |
| .---------. | Storage | |
| | Storage | .---------. | Node | |
| | Node | | Storage | `---------' |
| `---------' | Node | |
| `---------' .---------. |
| .---------. .---------. | Storage | |
| | Storage | | Storage | .---------. | Node | |
| | Node | | Node | | Storage | `---------' |
| `---------' `---------' | Node | |
| `---------' |
`------------------------------------------------------'
|
|
.-----------. _'
| Recipient | <-------------------------'
`-----------'
The code is licensed under the GPL. The author can be reached at HungryHobo@mail.i2p, either in
German or English.
@ -112,7 +142,7 @@ TODO mention the problem of highly unbalanced trees and the sibling list solutio
Relay Request | | | Request to a node to forward a Relay Packet
| PFX | 4 bytes | Packet prefix, must be 0x6D 0x30 0x52 0xE9
| TYPE | 1 byte | Value = 'Y'
| VER | 1 byte | Format version (only ver. 1 is supported)
| VER | 1 byte | Protocol version
| PID | 32 bytes | Packet ID, used for delivery confirmation
| HLEN | 2 bytes | HashCash length
| HK | byte[] | HashCash token (HLEN bytes)
@ -122,7 +152,7 @@ TODO mention the problem of highly unbalanced trees and the sibling list solutio
Fetch Request | | | Request to a chain end point to fetch emails
| PFX | 4 bytes | Packet prefix, must be 0x6D 0x30 0x52 0xE9
| TYPE | 1 byte | Value = 'T'
| VER | 1 byte | Format version (only ver. 1 is supported)
| VER | 1 byte | Protocol version
| PID | 32 bytes | Packet ID, used for delivery confirmation
| DTYP | 1 byte | Type of data to retrieve:
| KEY | 32 bytes | DHT key to look up
@ -134,7 +164,7 @@ TODO mention the problem of highly unbalanced trees and the sibling list solutio
| | | Find Close Peers Request, or a Relay List Request
| PFX | 4 bytes | Packet prefix, must be 0x6D 0x30 0x52 0xE9
| TYPE | 1 byte | Value = 'N'
| VER | 1 byte | Format version (only ver. 1 is supported)
| VER | 1 byte | Protocol version
| PID | 32 bytes | Packet Id of the request packet
| STA | 1 byte | Status code:
| | | 0 = OK
@ -147,30 +177,46 @@ TODO mention the problem of highly unbalanced trees and the sibling list solutio
| DLEN | 2 bytes | Length of DATA field; can be 0 if no payload
| DATA | byte[] | A Payload Packet
---------------------+-------+------------+----------------------------------------------------
Delete Request | | | Request to delete all copies of a packet by DHT key
Email Packet | | | Request to delete an Email Packet by DHT key
Delete Request | | |
| PFX | 4 bytes | Packet prefix, must be 0x6D 0x30 0x52 0xE9
| TYPE | 1 byte | Value = 'D'
| VER | 1 byte | Format version (only ver. 1 is supported)
| KEY | 32 bytes | DHT key of the packet that is to be deleted
| DEL | 32 bytes | Deletion key, encrypts to DEL value in the email pkt
| VER | 1 byte | Protocol version
| KEY | 32 bytes | DHT key of the Email Packet to delete
| DEL | 32 bytes | Deletion key, must match DEL value in the email pkt
---------------------+-------+------------+----------------------------------------------------
Index Packet | | | Request to remove one or more entries (Email
Delete Request | | | Packet key / del key pairs) from an Index Packet
| PFX | 4 bytes | Packet prefix, must be 0x6D 0x30 0x52 0xE9
| TYPE | 1 byte | Value = 'X'
| VER | 1 byte | Protocol version
| DH | 32 bytes | The Email Destination hash of the Index Packet
| N | 1 byte | Number of entries in the packet
| DHT1 | 32 bytes | First DHT key to remove
| DEL1 | 32 bytes | Deletion key for DHT1
| DHT2 | 32 bytes | Second DHT key to remove
| DEL2 | 32 bytes | Deletion key for DHT2
| ... | ... | ...
| DHTn | 32 bytes | n-th DHT key to remove
| DELn | 32 bytes | Deletion key for DHTn
---------------------+-------+------------+-------------------------------------------------
Ping | | | Check to see if a host is still up.
| PFX | 4 bytes | Packet prefix, must be 0x6D 0x30 0x52 0xE9
| TYPE | 1 byte | Value = 'P'
| VER | 1 byte | Format version (only ver. 1 is supported)
| VER | 1 byte | Protocol version
| PID | 32 bytes | Packet ID
---------------------+-------+------------+-------------------------------------------------
Pong | | | Let the the pinger know we're still there.
| PFX | 4 bytes | Packet prefix, must be 0x6D 0x30 0x52 0xE9
| TYPE | 1 byte | Value = 'O'
| VER | 1 byte | Format version (only ver. 1 is supported)
| TYPE | 1 byte | Value = 'O' (the letter O)
| VER | 1 byte | Protocol version
| PID | 32 bytes | Packet ID of the corresponding Ping packet
| HKS | 1 byte | Minimum HashCash strength this node will accept
---------------------+-------+------------+----------------------------------------------------
Relay List Request | | | "Send me your list of relay peers"
| PFX | 4 bytes | Packet prefix, must be 0x6D 0x30 0x52 0xE9
| TYPE | 1 byte | Value = 'A'
| VER | 1 byte | Format version (only ver. 1 is supported)
| VER | 1 byte | Protocol version
| PID | 32 bytes | Packet ID
---------------------+-------+------------+----------------------------------------------------
@ -181,7 +227,7 @@ TODO mention the problem of highly unbalanced trees and the sibling list solutio
Retrieve Request | | | DHT Request for a value for a key
| PFX | 4 bytes | Packet prefix, must be 0x6D 0x30 0x52 0xE9
| TYPE | 1 byte | Value = 'Q'
| VER | 1 byte | Format version (only ver. 1 is supported)
| VER | 1 byte | Protocol version
| PID | 32 bytes | Packet ID, used for delivery confirmation
| DTYP | 1 byte | Type of data to retrieve:
| | | 'I' = Index Packet
@ -192,7 +238,7 @@ TODO mention the problem of highly unbalanced trees and the sibling list solutio
Store Request | | | DHT Store Request
| PFX | 4 bytes | Packet prefix, must be 0x6D 0x30 0x52 0xE9
| TYPE | 1 byte | Value = 'S'
| VER | 1 byte | Format version (only ver. 1 is supported)
| VER | 1 byte | Protocol version
| PID | 32 bytes | Packet ID, used for delivery confirmation
| HLEN | 2 bytes | HashCash length
| HK | byte[] | HashCash token (HLEN bytes)
@ -203,7 +249,7 @@ TODO mention the problem of highly unbalanced trees and the sibling list solutio
Find Close Peers | | | Request for k peers close to a key
| PFX | 4 bytes | Packet prefix, must be 0x6D 0x30 0x52 0xE9
| TYPE | 1 byte | Value = 'F'
| VER | 1 byte | Format version (only ver. 1 is supported)
| VER | 1 byte | Protocol version
| PID | 32 bytes | Packet ID, used for delivery confirmation
| KEY | 32 bytes | DHT key
---------------------+-------+------------+----------------------------------------------------
@ -217,6 +263,7 @@ TODO mention the problem of highly unbalanced trees and the sibling list solutio
---------------------+-------+------------+----------------------------------------------------
Email Packet | | | An email or email fragment, 1 recipient.
(encrypted) | TYPE | 1 byte | Value = 'E'
| VER | 1 byte | Protocol version
| KEY | 32 bytes | The DHT key of the packet (generated randomly)
| DELP | 32 bytes | Deletion key in plaintext, zeroed out for retrieving
| LEN | 2 bytes | Length of the encrypted part of the packet
@ -224,8 +271,7 @@ TODO mention the problem of highly unbalanced trees and the sibling list solutio
---------------------+-------+------------+----------------------------------------------------
Email Packet | | | Storage format for the Incomplete Email Folder.
(unencrypted) | TYPE | 1 byte | Value = 'U'
| DELP | 32 bytes | Deletion key in plaintext, zeroed out for retrieving
| DELV | 32 bytes | Deletion key for verification
| VER | 1 byte | Protocol version
| MSID | 32 bytes | Message ID in binary format
| FRID | 2 bytes | Fragment Index of this packet (0..NFR-1)
| NFR | 2 bytes | Number of fragments in the email
@ -234,15 +280,20 @@ TODO mention the problem of highly unbalanced trees and the sibling list solutio
---------------------+-------+------------+----------------------------------------------------
Index Packet | | | Contains the DHT keys of one or more Email Packets.
| TYPE | 1 byte | Value = 'I'
| VER | 1 byte | Protocol version
| DH | 32 bytes | SHA-256 hash of the recipient's email destination
| NP | 1 byte | Number of keys in the packet
| KEY1 | 32 bytes | DHT key of the first Email Packet
| KEY1 | 32 bytes | DHT key of the second Email Packet
| DHT1 | 32 bytes | DHT key of the first Email Packet
| DEL1 | 32 bytes | Deletion key of the first Email Packet
| DHT2 | 32 bytes | DHT key of the second Email Packet
| DEL2 | 32 bytes | Deletion key of the second Email Packet
| ... | ... | ...
| KEYn | 32 bytes | DHT key of the n-th Email Packet
| DHTn | 32 bytes | DHT key of the n-th Email Packet
| DELn | 32 bytes | Deletion key of the n-th Email Packet
---------------------+-------+------------+----------------------------------------------------
Relay Packet | | | The payload of a Relay Request
| TYPE | 1 byte | Value = 'R'
| VER | 1 byte | Protocol version
| TMIN | 4 bytes | Earliest send time
| TMAX | 4 bytes | Latest sent time (no guarantee!)
| XK | 32 bytes | Key to XOR the payload with
@ -255,6 +306,7 @@ TODO mention the problem of highly unbalanced trees and the sibling list solutio
Peer List | | | Response to a Find Close Peers Request or
| | | a Relay List Request
| TYPE | 1 byte | Value = 'L'
| VER | 1 byte | Protocol version
| NUMP | 2 bytes | Number of peers in the list
| P1 | 384 bytes | Destination key
| P2 | 384 bytes | Destination key
@ -315,15 +367,38 @@ TODO mention the problem of highly unbalanced trees and the sibling list solutio
4.7. Deleting an Email Packet
Every time a recipient receives an email packet (from one or more storage nodes), it asks the storage
node(s) to delete the packet.
[TODO: This doesn't work for relayed email packets because the recipient doesn't know the storage nodes.
Maybe include the storage node in an email packet? Or relay the delete request and have the
relay endpoint do a findClosestNodes + delete? Or just delete the index packet entry and let
replication take care of deleting the email packets?]
It also sends a delete request to the nodes storing the index packet, asking them to delete the DHT key
of the email packet. Each index packet node then removes the DHT key from the stored index packet, and
adds the DHT key to a list of deleted keys it maintains for each index packet (DHT key + deletion key,
actually). The purpose of this is so a node that is about to replicate an email packet can find out if
it missed an earlier delete request for that packet, in which case the node "replicates" the delete
request rather than the packet itself. This helps reduce storage space by removing old Email Packets
from nodes that weren't online at the time the delete request was sent initially.
1) Recipient knows the deletion key after it decrypts the email packet (the packet also contains a
"plaintext deletion key" field, which is all zeros after it leaves a storage node).
2) Recipient sends a Delete Request to all storage nodes close to the DHT key of the Email Packet
2) Recipient sends a Delete Request to all nodes that responded to the query for the Email Destination,
asking them to mark the Email Packet's DHT key "deleted".
***** TODO deletion key for index packets? use signed del requests instead? *****
3) Recipient sends a Delete Request to all peers that responded to the query for the Email Packet
4.8. Looking up an email address in the directory
4.8. Replication of Email Packets
4.9. Announcing a new email address to the directory
4.9. Replication of Index Packets
4.10. Updating or deleting an email address from the directory
4.10. Looking up an email address in the directory
4.11. Announcing a new email address to the directory
4.12. Updating or deleting an email address from the directory
5. Algorithms Used By Nodes Locally
@ -470,6 +545,12 @@ I2P destination
11. To Do
* don't send delete requests to self, just delete the file locally
* rename test/.../kademlia package to dht for consistency
* make blue background solid, lighter color so chinese chars are readable
* show # of new emails next to folder name, show new + total count on a tool tip
* don't print subject lines in bold unless the email is new (store msg ids of read emails in a file in the email folder)
* button to delete all packets with an invalid protocol version
* show "to" address in inbox
* put sender name in "From" header field, if available
* auto-refresh network.jsp

View File

@ -25,20 +25,20 @@ import i2p.bote.email.Email;
import i2p.bote.email.EmailDestination;
import i2p.bote.email.EmailIdentity;
import i2p.bote.email.Identities;
import i2p.bote.folder.DhtPacketFolder;
import i2p.bote.folder.EmailFolder;
import i2p.bote.folder.EmailPacketFolder;
import i2p.bote.folder.IncompleteEmailFolder;
import i2p.bote.folder.IndexPacketFolder;
import i2p.bote.folder.Outbox;
import i2p.bote.folder.PacketFolder;
import i2p.bote.network.BanList;
import i2p.bote.network.CheckEmailTask;
import i2p.bote.network.NetworkStatus;
import i2p.bote.network.DHT;
import i2p.bote.network.I2PPacketDispatcher;
import i2p.bote.network.I2PSendQueue;
import i2p.bote.network.NetworkStatus;
import i2p.bote.network.PeerManager;
import i2p.bote.network.kademlia.KademliaDHT;
import i2p.bote.packet.DataPacket;
import i2p.bote.packet.EncryptedEmailPacket;
import i2p.bote.packet.IndexPacket;
import i2p.bote.packet.RelayPacket;
@ -76,13 +76,13 @@ import net.i2p.data.Base64;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer;
/**
* This is the core class of the application. Is is implemented as a singleton.
*/
public class I2PBote {
private static final String VERSION = "0.1.5";
public static final int PROTOCOL_VERSION = 2;
private static final String APP_VERSION = "0.1.5";
private static final int STARTUP_DELAY = 3 * 60 * 1000; // the number of milliseconds to wait before connecting to I2P (this gives the router time to get ready)
private static I2PBote instance;
@ -97,7 +97,7 @@ public class I2PBote {
private EmailFolder inbox; // stores incoming emails for all local users
private PacketFolder<RelayPacket> relayPacketFolder; // stores email packets we're forwarding for other machines
private IncompleteEmailFolder incompleteEmailFolder; // stores email packets addressed to a local user
private DhtPacketFolder<? extends DataPacket> emailDhtStorageFolder; // stores email packets and index packets for other peers
private EmailPacketFolder emailDhtStorageFolder; // stores email packets for other peers
private IndexPacketFolder indexPacketDhtStorageFolder; // stores index packets
//TODO private PacketFolder<> addressDhtStorageFolder; // stores email address-destination mappings
private SMTPService smtpService;
@ -139,7 +139,7 @@ public class I2PBote {
outbox = new Outbox(configuration.getLocalOutboxDir());
relayPacketFolder = new PacketFolder<RelayPacket>(configuration.getRelayOutboxDir());
incompleteEmailFolder = new IncompleteEmailFolder(configuration.getIncompleteDir(), inbox);
emailDhtStorageFolder = new DhtPacketFolder<EncryptedEmailPacket>(configuration.getEmailDhtStorageDir());
emailDhtStorageFolder = new EmailPacketFolder(configuration.getEmailDhtStorageDir());
indexPacketDhtStorageFolder = new IndexPacketFolder(configuration.getIndexPacketDhtStorageDir());
}
@ -212,6 +212,9 @@ public class I2PBote {
dht.setStorageHandler(IndexPacket.class, indexPacketDhtStorageFolder);
//TODO dht.setStorageHandler(AddressPacket.class, );
dispatcher.addPacketListener(emailDhtStorageFolder);
dispatcher.addPacketListener(indexPacketDhtStorageFolder);
peerManager = new PeerManager();
outboxProcessor = new OutboxProcessor(dht, outbox, configuration, peerManager, appContext);
@ -252,8 +255,8 @@ public class I2PBote {
return instance;
}
public String getVersion() {
return VERSION;
public String getAppVersion() {
return APP_VERSION;
}
public Identities getIdentities() {
@ -285,7 +288,7 @@ dht.store(new IndexPacket(encryptedPackets, emailDestination));
if (!isCheckingForMail()) {
pendingMailCheckTasks = Collections.synchronizedCollection(new ArrayList<Future<Boolean>>());
for (EmailIdentity identity: getIdentities()) {
Callable<Boolean> checkMailTask = new CheckEmailTask(identity, dht, peerManager, incompleteEmailFolder, appContext);
Callable<Boolean> checkMailTask = new CheckEmailTask(identity, dht, peerManager, sendQueue, incompleteEmailFolder, appContext);
Future<Boolean> task = mailCheckExecutor.submit(checkMailTask);
pendingMailCheckTasks.add(task);
}

View File

@ -46,7 +46,7 @@ public class UniqueId implements Comparable<UniqueId> {
}
/**
* Create a packet id from a 32 bytes of an array, starting at <code>offset</code>.
* Create a packet id from 32 bytes of an array, starting at <code>offset</code>.
* @param bytes
*/
public UniqueId(byte[] bytes, int offset) {

View File

@ -302,9 +302,8 @@ public class Email implements FolderElement {
// make a new array with the right length
byte[] block = new byte[blockSize];
System.arraycopy(emailArray, blockStart, block, 0, blockSize);
UniqueId deletionKeyPlain = new UniqueId();
UniqueId deletionKeyEncrypted = deletionKeyPlain.clone(); // encryption happens in the constructor call below
UnencryptedEmailPacket packet = new UnencryptedEmailPacket(deletionKeyPlain, deletionKeyEncrypted, messageId, fragmentIndex, numFragments, block);
UniqueId deletionKey = new UniqueId();
UnencryptedEmailPacket packet = new UnencryptedEmailPacket(messageId, fragmentIndex, numFragments, block, deletionKey);
packets.add(packet);
fragmentIndex++;
blockStart += blockSize;

View File

@ -47,12 +47,20 @@ public class DhtPacketFolder<T extends DhtStorablePacket> extends PacketFolder<T
add(packetToStore, getFilename(packetToStore));
}
private String getFilename(DhtStorablePacket packet) {
protected String getFilename(DhtStorablePacket packet) {
return packet.getDhtKey().toBase64() + PACKET_FILE_EXTENSION;
}
@Override
public DhtStorablePacket retrieve(Hash dhtKey) {
File packetFile = findPacketFile(dhtKey);
if (packetFile != null)
return DhtStorablePacket.createPacket(packetFile);
else
return null;
}
protected File findPacketFile(Hash dhtKey) {
final String base64Key = dhtKey.toBase64();
File[] files = storageDir.listFiles(new FilenameFilter() {
@ -66,7 +74,7 @@ public class DhtPacketFolder<T extends DhtStorablePacket> extends PacketFolder<T
log.warn("More than one packet files found for DHT key " + dhtKey);
if (files.length > 0) {
File file = files[0];
return DhtStorablePacket.createPacket(file);
return file;
}
return null;
}
@ -74,4 +82,14 @@ public class DhtPacketFolder<T extends DhtStorablePacket> extends PacketFolder<T
protected boolean filenameMatches(String filename, String base64DhtKey) {
return filename.startsWith(base64DhtKey);
}
public void delete(Hash dhtKey) {
File packetFile = findPacketFile(dhtKey);
if (packetFile != null) {
if (!packetFile.delete())
log.warn("File cannot be deleted: <" + packetFile.getAbsolutePath() + ">");
}
else
log.debug("No file found for DHT key: " + dhtKey);
}
}

View File

@ -0,0 +1,67 @@
/**
* Copyright (C) 2009 HungryHobo@mail.i2p
*
* The GPG fingerprint for HungryHobo@mail.i2p is:
* 6DD3 EAA2 9990 29BC 4AD2 7486 1E2C 7B61 76DC DC12
*
* This file is part of I2P-Bote.
* I2P-Bote is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* I2P-Bote is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with I2P-Bote. If not, see <http://www.gnu.org/licenses/>.
*/
package i2p.bote.folder;
import i2p.bote.UniqueId;
import i2p.bote.network.PacketListener;
import i2p.bote.packet.CommunicationPacket;
import i2p.bote.packet.EmailPacketDeleteRequest;
import i2p.bote.packet.EncryptedEmailPacket;
import i2p.bote.packet.dht.DhtStorablePacket;
import java.io.File;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.util.Log;
/**
* A subclass of {@link DhtPacketFolder} that stores email packets and deletes them
* upon {@link EmailPacketDeleteRequest}s.
*/
public class EmailPacketFolder extends DhtPacketFolder<EncryptedEmailPacket> implements PacketListener {
private Log log = new Log(EmailPacketFolder.class);
public EmailPacketFolder(File storageDir) {
super(storageDir);
}
@Override
public void packetReceived(CommunicationPacket packet, Destination sender, long receiveTime) {
if (packet instanceof EmailPacketDeleteRequest) {
EmailPacketDeleteRequest delRequest = (EmailPacketDeleteRequest)packet;
Hash dhtKey = delRequest.getDhtKey();
DhtStorablePacket storedPacket = retrieve(dhtKey);
if (storedPacket instanceof EncryptedEmailPacket) {
UniqueId storedDeletionKey = ((EncryptedEmailPacket)storedPacket).getPlaintextDeletionKey();
if (storedDeletionKey.equals(delRequest.getDeletionKey()))
delete(dhtKey);
else
log.debug("Deletion key in EmailPacketDeleteRequest does not match. Should be: <" + storedDeletionKey + ">, is <" + delRequest.getDeletionKey() +">");
}
else
log.debug("EncryptedEmailPacket expected for DHT key <" + dhtKey + ">, found " + storedPacket.getClass().getSimpleName());
}
}
}

View File

@ -21,19 +21,33 @@
package i2p.bote.folder;
import i2p.bote.UniqueId;
import i2p.bote.network.PacketListener;
import i2p.bote.packet.CommunicationPacket;
import i2p.bote.packet.I2PBotePacket;
import i2p.bote.packet.IndexPacket;
import i2p.bote.packet.IndexPacketDeleteRequest;
import i2p.bote.packet.dht.DhtStorablePacket;
import java.io.File;
import java.util.Collection;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.util.Log;
/**
* This class differs from {@link DhtPacketFolder} in that it doesn't overwrite an existing
* packet when a new packet is stored under the same key, but merges the packets.
* This class uses Email Destination hashes for DHT keys.
* It differs from {@link DhtPacketFolder} in two ways:
* * It doesn't overwrite an existing packet when a new packet is stored under the same key,
* but merges the packets.
* * It retains DHT keys of deleted packets in a file named <code>DEL_<dht_key>.pkt</code>
* for later reference. These files use the same format as Index Packet files.
*
*/
public class IndexPacketFolder extends DhtPacketFolder<IndexPacket> {
public class IndexPacketFolder extends DhtPacketFolder<IndexPacket> implements PacketListener {
private static final String DEL_FILE_PREFIX = "DEL_";
private final Log log = new Log(I2PBotePacket.class);
public IndexPacketFolder(File storageDir) {
@ -58,4 +72,78 @@ public class IndexPacketFolder extends DhtPacketFolder<IndexPacket> {
super.store(packetToStore);
}
@Override
public void delete(Hash dhtKey) {
throw new UnsupportedOperationException("Index packets are never deleted. Use remove(Hash, Hash) to remove an entry from an index packet file.");
}
/**
* Deletes an entry from an {@link IndexPacket} and saves the packet to disk.
* @param indexPacket
* @param emailPacketKey The entry to delete
*/
public void remove(IndexPacket indexPacket, Hash emailPacketKey) {
log.debug("Removing DHT key " + emailPacketKey + " from Index Packet for Email Dest " + indexPacket.getDhtKey());
UniqueId deletionKey = indexPacket.getDeletionKey(emailPacketKey);
if (deletionKey == null)
log.debug("DHT key " + emailPacketKey + " not found in index packet " + indexPacket);
else {
indexPacket.remove(emailPacketKey);
addToDeletedPackets(indexPacket, emailPacketKey, deletionKey);
}
super.store(indexPacket); // don't merge, but overwrite the file with the key removed
}
/**
* Adds a DHT key of an Email Packet to the list of deleted packets.
* If the key is already on the list, nothing happens.
* @param indexPacket
* @param dhtKey
* @param deletionKey
*/
private void addToDeletedPackets(IndexPacket indexPacket, Hash dhtKey, UniqueId deletionKey) {
String delFileName = DEL_FILE_PREFIX + getFilename(indexPacket);
File delFile = new File(storageDir, delFileName);
// read delete list from file or create a new one if file doesn't exist
DhtStorablePacket delListPacket;
if (!delFile.exists())
delListPacket = new IndexPacket(indexPacket);
else {
delListPacket = DhtStorablePacket.createPacket(delFile);
if (!(delListPacket instanceof IndexPacket)) {
log.error("Not an Index Packet file: <" + delFile + ">");
return;
}
}
((IndexPacket)delListPacket).put(dhtKey, deletionKey);
add(delListPacket, delFileName);
}
@Override
public void packetReceived(CommunicationPacket packet, Destination sender, long receiveTime) {
if (packet instanceof IndexPacketDeleteRequest) {
IndexPacketDeleteRequest delRequest = (IndexPacketDeleteRequest)packet;
log.debug("IndexPacketDeleteRequest received. #entries=" + delRequest.getNumEntries());
Hash dhtKey = delRequest.getEmailDestHash();
DhtStorablePacket storedPacket = retrieve(dhtKey);
if (storedPacket instanceof IndexPacket) {
IndexPacket indexPacket = (IndexPacket)storedPacket;
Collection<Hash> keysToDelete = delRequest.getDhtKeys();
for (Hash keyToDelete: keysToDelete) {
UniqueId deletionKeyFromRequest = delRequest.getDeletionKey(keyToDelete);
UniqueId storedDeletionKey = indexPacket.getDeletionKey(keyToDelete);
if (storedDeletionKey.equals(deletionKeyFromRequest))
remove(indexPacket, keyToDelete);
else
log.debug("Deletion key in IndexPacketDeleteRequest does not match. Should be: <" + storedDeletionKey + ">, is <" + deletionKeyFromRequest +">");
}
}
else
log.debug("IndexPacket expected for DHT key <" + dhtKey + ">, found " + storedPacket.getClass().getSimpleName());
}
}
}

View File

@ -44,16 +44,17 @@ public class PacketFolder<PacketType extends DataPacket> extends Folder<PacketTy
super(storageDir, PACKET_FILE_EXTENSION);
}
// TODO rename to write because existing files are overwritten
public <T extends PacketType> void add(T packetToStore) {
String filename = new UniqueId().toBase64() + PACKET_FILE_EXTENSION;
add(packetToStore, filename);
}
/**
*
* @param packetToStore
* @param filename A filename relative to this folder's storage directory.
*/
// TODO rename to write because existing files are overwritten
protected void add(DataPacket packetToStore, String filename) {
FileOutputStream outputStream = null;
try {
@ -74,9 +75,9 @@ public class PacketFolder<PacketType extends DataPacket> extends Folder<PacketTy
}
}
public void delete(UniqueId packetId) {
/* public void delete(UniqueId packetId) {
// TODO
}
}*/
@Override
@SuppressWarnings("unchecked")

View File

@ -25,13 +25,16 @@ import i2p.bote.UniqueId;
import i2p.bote.Util;
import i2p.bote.email.EmailIdentity;
import i2p.bote.folder.IncompleteEmailFolder;
import i2p.bote.packet.EmailPacketDeleteRequest;
import i2p.bote.packet.EncryptedEmailPacket;
import i2p.bote.packet.IndexPacket;
import i2p.bote.packet.IndexPacketDeleteRequest;
import i2p.bote.packet.UnencryptedEmailPacket;
import i2p.bote.packet.dht.DhtStorablePacket;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
@ -41,6 +44,7 @@ import java.util.concurrent.ThreadFactory;
import net.i2p.I2PAppContext;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.util.Log;
@ -58,14 +62,17 @@ public class CheckEmailTask implements Callable<Boolean> {
private EmailIdentity identity;
private DHT dht;
private PeerManager peerManager;
private I2PSendQueue sendQueue;
private IncompleteEmailFolder incompleteEmailFolder;
private I2PAppContext appContext;
private ExecutorService executor;
public CheckEmailTask(EmailIdentity identity, DHT dht, PeerManager peerManager, IncompleteEmailFolder incompleteEmailFolder, I2PAppContext appContext) {
// TODO move appContext into EncryptedEmailPacket so there is one less parameter here
public CheckEmailTask(EmailIdentity identity, DHT dht, PeerManager peerManager, I2PSendQueue sendQueue, IncompleteEmailFolder incompleteEmailFolder, I2PAppContext appContext) {
this.identity = identity;
this.dht = dht;
this.peerManager = peerManager;
this.sendQueue = sendQueue;
this.incompleteEmailFolder = incompleteEmailFolder;
this.appContext = appContext;
executor = Executors.newFixedThreadPool(MAX_THREADS, EMAIL_PACKET_TASK_THREAD_FACTORY);
@ -77,11 +84,15 @@ public class CheckEmailTask implements Callable<Boolean> {
*/
@Override
public Boolean call() {
Collection<Hash> emailPacketKeys = findEmailPacketKeys();
// Use findAll rather than findOne because some peers might have an incomplete set of
// Email Packet keys, and because we want to send IndexPacketDeleteRequests to all of them.
DhtResults indexPacketResults = dht.findAll(identity.getHash(), IndexPacket.class);
Collection<Hash> emailPacketKeys = findEmailPacketKeys(indexPacketResults.getPackets());
Collection<Future<Boolean>> results = new ArrayList<Future<Boolean>>();
for (Hash dhtKey: emailPacketKeys) {
Future<Boolean> result = executor.submit(new EmailPacketTask(dhtKey));
for (Hash emailPacketKey: emailPacketKeys) {
Future<Boolean> result = executor.submit(new EmailPacketTask(emailPacketKey, identity.getHash(), indexPacketResults.getPeers()));
results.add(result);
}
@ -100,17 +111,15 @@ public class CheckEmailTask implements Callable<Boolean> {
/**
* Queries the DHT for new index packets and returns the DHT keys contained in them.
* @param indexPacketResults TODO comment
* @return A <code>Collection</code> containing zero or more DHT keys
*/
private Collection<Hash> findEmailPacketKeys() {
private Collection<Hash> findEmailPacketKeys(Collection<DhtStorablePacket> indexPacketResults) {
log.debug("Querying the DHT for index packets with key " + identity.getHash());
// Use findAll rather than findOne because some peers might not have all Email Packet keys.
Collection<DhtStorablePacket> packets = dht.findAll(identity.getHash(), IndexPacket.class);
// build an Collection of index packets
// build a Collection of index packets
Collection<IndexPacket> indexPackets = new ArrayList<IndexPacket>();
for (DhtStorablePacket packet: packets)
for (DhtStorablePacket packet: indexPacketResults)
if (packet instanceof IndexPacket)
indexPackets.add((IndexPacket)packet);
else
@ -126,14 +135,21 @@ public class CheckEmailTask implements Callable<Boolean> {
* and deletes the packet from the DHT.
*/
private class EmailPacketTask implements Callable<Boolean> {
private Hash dhtKey;
private Hash emailPacketKey;
private Hash emailIdentityHash;
private Set<Destination> indexPacketPeers;
/**
*
* @param dhtKey The DHT key of the email packet to retrieve
* @param emailPacketKey The DHT key of the email packet to retrieve
* @param emailIdentityHash The DHT key of the packet recipient's email identity
* @param indexPacketPeers All peers that are known to be storing an index packet
* for the email destination the email packet is being sent to.
*/
public EmailPacketTask(Hash dhtKey) {
this.dhtKey = dhtKey;
public EmailPacketTask(Hash emailPacketKey, Hash emailIdentityHash, Set<Destination> indexPacketPeers) {
this.emailPacketKey = emailPacketKey;
this.emailIdentityHash = emailIdentityHash;
this.indexPacketPeers = indexPacketPeers;
}
/**
@ -143,32 +159,58 @@ public class CheckEmailTask implements Callable<Boolean> {
@Override
public Boolean call() {
boolean emailCompleted = false;
DhtStorablePacket packet = dht.findOne(dhtKey, EncryptedEmailPacket.class);
if (packet instanceof EncryptedEmailPacket) {
EncryptedEmailPacket emailPacket = (EncryptedEmailPacket)packet;
try {
UnencryptedEmailPacket decryptedPacket = emailPacket.decrypt(identity, appContext);
emailCompleted = incompleteEmailFolder.addEmailPacket(decryptedPacket);
sendDeleteRequest(dhtKey, decryptedPacket.getVerificationDeletionKey());
}
catch (DataFormatException e) {
log.error("Can't decrypt email packet: " + emailPacket, e);
// TODO propagate error message to UI
// Use findAll rather than findOne because after we receive an email packet, we want
// to send delete requests to as many of the storage nodes as possible.
DhtResults results = dht.findAll(emailPacketKey, EncryptedEmailPacket.class);
EncryptedEmailPacket validPacket = null; // stays null until a valid packet is found in the loop below
IndexPacketDeleteRequest indexDelRequest = new IndexPacketDeleteRequest(emailIdentityHash);
for (Destination peer: results.getPeers()) {
DhtStorablePacket packet = results.getPacket(peer);
if (packet instanceof EncryptedEmailPacket) {
EncryptedEmailPacket emailPacket = (EncryptedEmailPacket)packet;
try {
UnencryptedEmailPacket decryptedPacket = emailPacket.decrypt(identity, appContext);
if (validPacket == null) {
emailCompleted = incompleteEmailFolder.addEmailPacket(decryptedPacket);
validPacket = emailPacket;
}
UniqueId delKey = decryptedPacket.getVerificationDeletionKey();
sendDeleteRequest(emailPacketKey, delKey, peer);
indexDelRequest.put(emailPacketKey, delKey);
}
catch (DataFormatException e) {
log.error("Can't decrypt email packet: " + emailPacket, e);
// TODO propagate error message to UI
}
}
else
if (packet != null)
log.error("DHT returned packet of class " + packet.getClass().getSimpleName() + ", expected EmailPacket.");
}
else
if (packet != null)
log.error("DHT returned packet of class " + packet.getClass().getSimpleName() + ", expected EmailPacket.");
if (indexDelRequest.getNumEntries() > 0)
sendDeleteRequest(indexDelRequest, indexPacketPeers);
return emailCompleted;
}
/**
* Sends a delete request to the DHT.
* Sends an Email Packet Delete Request to a peer.
* @param dhtKey The DHT key of the email packet that is to be deleted
* @param deletionKey The deletion key for the email packet
* @param peer
*/
private void sendDeleteRequest(Hash dhtKey, UniqueId deletionKey) {
// TODO
private void sendDeleteRequest(Hash dhtKey, UniqueId deletionKey, Destination peer) {
EmailPacketDeleteRequest packet = new EmailPacketDeleteRequest(dhtKey, deletionKey);
log.debug("Sending an EmailPacketDeleteRequest for DHT key " + dhtKey + " to " + peer.calculateHash());
sendQueue.send(packet, peer);
}
private void sendDeleteRequest(IndexPacketDeleteRequest indexDelRequest, Set<Destination> peers) {
log.debug("Sending an IndexPacketDeleteRequest to " + peers.size() + " peers: " + indexDelRequest);
for (Destination peer: peers)
sendQueue.send(indexDelRequest, peer);
}
}
}

View File

@ -22,18 +22,15 @@
package i2p.bote.network;
import i2p.bote.packet.dht.DhtStorablePacket;
import java.util.Collection;
import net.i2p.data.Hash;
public interface DHT {
void store(DhtStorablePacket packet) throws Exception;
DhtStorablePacket findOne(Hash key, Class<? extends DhtStorablePacket> dataType);
DhtResults findOne(Hash key, Class<? extends DhtStorablePacket> dataType);
Collection<DhtStorablePacket> findAll(Hash key, Class<? extends DhtStorablePacket> dataType);
DhtResults findAll(Hash key, Class<? extends DhtStorablePacket> dataType);
/**
* Registers a <code>DhtStorageHandler</code> that handles incoming storage requests of a certain

View File

@ -0,0 +1,75 @@
/**
* Copyright (C) 2009 HungryHobo@mail.i2p
*
* The GPG fingerprint for HungryHobo@mail.i2p is:
* 6DD3 EAA2 9990 29BC 4AD2 7486 1E2C 7B61 76DC DC12
*
* This file is part of I2P-Bote.
* I2P-Bote is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* I2P-Bote is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with I2P-Bote. If not, see <http://www.gnu.org/licenses/>.
*/
package i2p.bote.network;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import net.i2p.data.Destination;
import i2p.bote.packet.dht.DhtStorablePacket;
public class DhtResults implements Iterable<DhtStorablePacket> {
private Map<Destination, DhtStorablePacket> map;
public DhtResults() {
map = new ConcurrentHashMap<Destination, DhtStorablePacket>();
}
public void put(Destination peer, DhtStorablePacket packet) {
map.put(peer, packet);
}
public int getNumResults() {
return map.size();
}
public Collection<DhtStorablePacket> getPackets() {
return map.values();
}
public Set<Destination> getPeers() {
return map.keySet();
}
public DhtStorablePacket getPacket(Destination peer) {
return map.get(peer);
}
@Override
public Iterator<DhtStorablePacket> iterator() {
return getPackets().iterator();
}
// Returns all packets except for the local result.
/* public Collection<DhtStorablePacket> getRemoteResults() {
ArrayList<DhtStorablePacket> packets = new ArrayList<DhtStorablePacket>();
for (Destination peer: map.keySet())
if (!peer.calculateHash().equals(localDestHash))
packets.add(map.get(peer));
return packets;
}*/
}

View File

@ -172,7 +172,7 @@ public class I2PSendQueue extends I2PBoteThread implements PacketListener {
for (PacketBatch batch: runningBatches)
if (batch.contains(packetId))
batch.addResponse(((ResponsePacket)packet).getPayload());
batch.addResponse(sender, ((ResponsePacket)packet).getPayload());
}
}

View File

@ -27,13 +27,11 @@ import i2p.bote.packet.DataPacket;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import net.i2p.data.Destination;
import net.i2p.util.ConcurrentHashSet;
import net.i2p.util.Log;
/**
@ -44,13 +42,13 @@ import net.i2p.util.Log;
public class PacketBatch implements Iterable<PacketBatchItem> {
private final Log log = new Log(PacketBatch.class);
private Map<UniqueId, PacketBatchItem> outgoingPackets;
private Set<DataPacket> incomingPackets;
private Map<Destination, DataPacket> incomingPackets;
private CountDownLatch sentSignal; // this field is initialized by I2PSendQueue when the batch is submitted for sending
private CountDownLatch firstReplyReceivedSignal;
public PacketBatch() {
outgoingPackets = new ConcurrentHashMap<UniqueId, PacketBatchItem>();
incomingPackets = new ConcurrentHashSet<DataPacket>();
incomingPackets = new ConcurrentHashMap<Destination, DataPacket>();
sentSignal = new CountDownLatch(0);
firstReplyReceivedSignal = new CountDownLatch(1);
}
@ -102,12 +100,12 @@ public class PacketBatch implements Iterable<PacketBatchItem> {
outgoingPackets.get(packetId).confirmDelivery();
}*/
void addResponse(DataPacket packet) {
incomingPackets.add(packet);
void addResponse(Destination peer, DataPacket packet) {
incomingPackets.put(peer, packet);
firstReplyReceivedSignal.countDown();
}
public Set<DataPacket> getResponses() {
public Map<Destination, DataPacket> getResponses() {
return incomingPackets;
}

View File

@ -23,13 +23,14 @@ package i2p.bote.network.kademlia;
import i2p.bote.Util;
import i2p.bote.network.DHT;
import i2p.bote.network.DhtResults;
import i2p.bote.network.DhtStorageHandler;
import i2p.bote.network.I2PPacketDispatcher;
import i2p.bote.network.I2PSendQueue;
import i2p.bote.network.PacketBatch;
import i2p.bote.network.PacketListener;
import i2p.bote.packet.CommunicationPacket;
import i2p.bote.packet.I2PBotePacket;
import i2p.bote.packet.DataPacket;
import i2p.bote.packet.PeerList;
import i2p.bote.packet.ResponsePacket;
import i2p.bote.packet.StatusCode;
@ -47,10 +48,10 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@ -130,16 +131,12 @@ public class KademliaDHT extends I2PBoteThread implements DHT, PacketListener {
}
@Override
public DhtStorablePacket findOne(Hash key, Class<? extends DhtStorablePacket> dataType) {
Collection<DhtStorablePacket> results = find(key, dataType, false);
if (results.isEmpty())
return null;
else
return results.iterator().next();
public DhtResults findOne(Hash key, Class<? extends DhtStorablePacket> dataType) {
return find(key, dataType, false);
}
@Override
public Collection<DhtStorablePacket> findAll(Hash key, Class<? extends DhtStorablePacket> dataType) {
public DhtResults findAll(Hash key, Class<? extends DhtStorablePacket> dataType) {
return find(key, dataType, true);
}
@ -158,7 +155,7 @@ public class KademliaDHT extends I2PBoteThread implements DHT, PacketListener {
return bucketManager.getPeerCount();
}
private Collection<DhtStorablePacket> find(Hash key, Class<? extends DhtStorablePacket> dataType, boolean exhaustive) {
private DhtResults find(Hash key, Class<? extends DhtStorablePacket> dataType, boolean exhaustive) {
final Collection<Destination> closeNodes = getClosestNodes(key);
log.debug("Querying localhost + " + closeNodes.size() + " peers for data type " + dataType.getSimpleName() + ", Kademlia key " + key);
@ -166,9 +163,9 @@ public class KademliaDHT extends I2PBoteThread implements DHT, PacketListener {
// if a local packet exists and one result is requested, return the local packet
if (!exhaustive && localResult!=null) {
log.debug("Locally stored packet found for hash " + key + " and data type " + dataType);
Collection<DhtStorablePacket> storablePackets = new ArrayList<DhtStorablePacket>();
storablePackets.add(localResult);
return storablePackets;
DhtResults results = new DhtResults();
results.put(localDestination, localResult);
return results;
}
// Send the retrieve requests
@ -197,8 +194,7 @@ public class KademliaDHT extends I2PBoteThread implements DHT, PacketListener {
sendQueue.remove(batch);
ConcurrentHashSet<DhtStorablePacket> storablePackets = getStorablePackets(batch, localResult);
return storablePackets;
return getDhtResults(batch, localResult);
}
private DhtStorablePacket findLocally(Hash key, Class<? extends DhtStorablePacket> dataType) {
@ -216,16 +212,18 @@ public class KademliaDHT extends I2PBoteThread implements DHT, PacketListener {
* @param localResult
* @return
*/
private ConcurrentHashSet<DhtStorablePacket> getStorablePackets(PacketBatch batch, DhtStorablePacket localResult) {
ConcurrentHashSet<DhtStorablePacket> storablePackets = new ConcurrentHashSet<DhtStorablePacket>();
for (I2PBotePacket packet: batch.getResponses())
private DhtResults getDhtResults(PacketBatch batch, DhtStorablePacket localResult) {
DhtResults results = new DhtResults();
for (Entry<Destination, DataPacket> result: batch.getResponses().entrySet()) {
DataPacket packet = result.getValue();
if (packet instanceof DhtStorablePacket)
storablePackets.add((DhtStorablePacket)packet);
results.put(result.getKey(), (DhtStorablePacket)packet);
}
if (localResult != null)
storablePackets.add(localResult);
results.put(localDestination, localResult);
return storablePackets;
return results;
}
@Override

View File

@ -21,6 +21,7 @@
package i2p.bote.packet;
import i2p.bote.I2PBote;
import i2p.bote.UniqueId;
import java.io.IOException;
@ -31,7 +32,6 @@ import java.util.concurrent.TimeUnit;
import net.i2p.util.Log;
public abstract class CommunicationPacket extends I2PBotePacket {
private static final byte PACKET_VERSION = 1;
protected static final int HEADER_LENGTH = 6 + UniqueId.LENGTH; // length of the common packet header in the byte array representation; this is where subclasses start reading
private static final byte[] PACKET_PREFIX = new byte[] {(byte)0x6D, (byte)0x30, (byte)0x52, (byte)0xE9};
private static Log static_log = new Log(CommunicationPacket.class);
@ -46,16 +46,20 @@ public abstract class CommunicationPacket extends I2PBotePacket {
}
protected CommunicationPacket(UniqueId packetId) {
super(I2PBote.PROTOCOL_VERSION);
this.packetId = packetId;
sentSignal = new CountDownLatch(1);
sentTime = -1;
}
/**
* Creates a packet and initializes the header fields shared by all Communication Packets: packet type and packet id.
* Creates a packet and initializes the header fields shared by all Communication Packets:
* packet type, protocol version, and packet id.
* Subclasses should start reading at byte <code>HEADER_LENGTH</code> after calling this constructor.
* @param data
*/
protected CommunicationPacket(byte[] data) {
super(data[5]); // byte 5 is the protocol version in a communication packet
verifyHeader(data);
checkPacketType(data[4]);
packetId = new UniqueId(data, 6);
@ -86,13 +90,14 @@ public abstract class CommunicationPacket extends I2PBotePacket {
}
}
// check the packet prefix and version number of a packet
/**
* Checks that the packet has the correct packet prefix.
* @param packet
*/
private void verifyHeader(byte[] packet) {
for (int i=0; i<PACKET_PREFIX.length; i++)
if (packet[i] != PACKET_PREFIX[i])
log.error("Packet prefix invalid at byte " + i + ". Expected = " + PACKET_PREFIX[i] + ", actual = " + packet[i]);
if (packet[5] != 1)
log.error("Unsupported packet version: " + packet[5]);
}
public void setPacketId(UniqueId packetId) {
@ -121,14 +126,14 @@ public abstract class CommunicationPacket extends I2PBotePacket {
}
/**
* Writes the Prefix, Version, Type, and Packet Id fields of a I2PBote packet to
* Writes the Prefix, Version, Type, and Packet Id fields of a Communication Packet to
* an {@link OutputStream}.
* @param outputStream
*/
protected void writeHeader(OutputStream outputStream) throws IOException {
outputStream.write(PACKET_PREFIX);
outputStream.write((byte)getPacketTypeCode());
outputStream.write(PACKET_VERSION);
outputStream.write(getProtocolVersion());
outputStream.write(packetId.toByteArray());
}

View File

@ -21,6 +21,7 @@
package i2p.bote.packet;
import i2p.bote.I2PBote;
import i2p.bote.Util;
import i2p.bote.folder.FolderElement;
@ -36,13 +37,26 @@ import net.i2p.util.Log;
* The superclass of all "payload" packet types.
*/
public abstract class DataPacket extends I2PBotePacket implements FolderElement {
protected static final int HEADER_LENGTH = 2; // length of the common packet header in the byte array representation; this is where subclasses start reading
private static Log log = new Log(DataPacket.class);
private File file;
public DataPacket() {
}
/**
* Creates a <code>DataPacket</code> from raw datagram data. The only thing that is initialized
* is the protocol version. The packet type code is verified.
* Subclasses should start reading at byte <code>HEADER_LENGTH</code> after calling this constructor.
* @param data
*/
public DataPacket(byte[] data) {
super(data[1]); // byte 1 is the protocol version in a data packet
if (data[0] != getPacketTypeCode())
log.error("Wrong type code for " + getClass().getSimpleName() + ". Expected <" + getPacketTypeCode() + ">, got <" + (char)data[0] + ">");
}
/**
* Writes the packet to an <code>OutputStream</code> in binary representation.
*/
@ -51,7 +65,20 @@ public abstract class DataPacket extends I2PBotePacket implements FolderElement
outputStream.write(toByteArray());
}
/**
* Writes the Type and Protocol Version fields of a Data Packet to
* an {@link OutputStream}.
* @param outputStream
*/
protected void writeHeader(OutputStream outputStream) throws IOException {
outputStream.write((byte)getPacketTypeCode());
outputStream.write(getProtocolVersion());
}
public static DataPacket createPacket(File file) {
if (file==null || !file.exists())
return null;
InputStream inputStream = null;
try {
inputStream = new FileInputStream(file);
@ -87,13 +114,21 @@ public abstract class DataPacket extends I2PBotePacket implements FolderElement
}
Class<? extends DataPacket> dataPacketType = packetType.asSubclass(DataPacket.class);
DataPacket packet = null;
try {
return dataPacketType.getConstructor(byte[].class).newInstance(data);
packet = dataPacketType.getConstructor(byte[].class).newInstance(data);
}
catch (Exception e) {
log.warn("Can't instantiate packet for type code <" + packetTypeCode + ">", e);
return null;
}
if (packet.getProtocolVersion() != I2PBote.PROTOCOL_VERSION) {
log.warn("Ignoring " + packetType.getSimpleName() + " packet with protocol version " + packet.getProtocolVersion());
return null;
}
return packet;
}
// FolderElement implementation

View File

@ -0,0 +1,78 @@
/**
* Copyright (C) 2009 HungryHobo@mail.i2p
*
* The GPG fingerprint for HungryHobo@mail.i2p is:
* 6DD3 EAA2 9990 29BC 4AD2 7486 1E2C 7B61 76DC DC12
*
* This file is part of I2P-Bote.
* I2P-Bote is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* I2P-Bote is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with I2P-Bote. If not, see <http://www.gnu.org/licenses/>.
*/
package i2p.bote.packet;
import i2p.bote.UniqueId;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import net.i2p.data.Hash;
import net.i2p.util.Log;
@TypeCode('D')
public class EmailPacketDeleteRequest extends CommunicationPacket {
private Log log = new Log(EmailPacketDeleteRequest.class);
private Hash dhtKey;
private UniqueId deletionKey;
public EmailPacketDeleteRequest(Hash dhtKey, UniqueId deletionKey) {
this.dhtKey = dhtKey;
this.deletionKey = deletionKey;
}
public EmailPacketDeleteRequest(byte[] data) {
super(data);
ByteBuffer buffer = ByteBuffer.wrap(data, HEADER_LENGTH, data.length-HEADER_LENGTH);
dhtKey = readHash(buffer);
deletionKey = new UniqueId(buffer);
if (buffer.hasRemaining())
log.debug("Email Packet Delete Request has " + buffer.remaining() + " extra bytes.");
}
public Hash getDhtKey() {
return dhtKey;
}
public UniqueId getDeletionKey() {
return deletionKey;
}
@Override
public byte[] toByteArray() {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
writeHeader(outputStream);
outputStream.write(dhtKey.toByteArray());
outputStream.write(deletionKey.toByteArray());
}
catch (IOException e) {
log.error("Can't write to ByteArrayOutputStream.", e);
}
return outputStream.toByteArray();
}
}

View File

@ -42,6 +42,17 @@ import net.i2p.data.SessionKey;
import net.i2p.util.Log;
import net.i2p.util.RandomSource;
/**
* An <code>EncryptedEmailPacket</code> basically contains an encrypted <code>UnencryptedEmailPacket</code>
* and an unencrypted copy of the deletion key. The unencrypted deletion key (<code>deletionKeyPlain</code>)
* is used for validation of delete requests (the sender of a delete request has to decrypt the deletion key
* using the decryption key for the Email Destination because it doesn't have the unencrypted copy).
* deletion key).
* An alternative to this scheme would have been to only use plain-text deletion keys in email packets and
* have the recipient sign deletion requests, but this would require a storage node to know the recipient's
* public signing key.
*
*/
@TypeCode('E')
public class EncryptedEmailPacket extends DhtStorablePacket {
private static final int PADDED_SIZE = PrivateKey.KEYSIZE_BYTES; // TODO is this a good choice?
@ -51,24 +62,6 @@ public class EncryptedEmailPacket extends DhtStorablePacket {
private UniqueId deletionKeyPlain;
private byte[] encryptedData; // the encrypted fields of an I2PBote email packet: Encrypted Deletion Key, Message ID, Fragment Index, Number of Fragments, and Content.
/**
* Creates an <code>EncryptedEmailPacket</code> from raw datagram data.
* To read the encrypted parts of the packet, <code>decrypt</code> must be called first.
* @param data
*/
public EncryptedEmailPacket(byte[] data) {
ByteBuffer buffer = ByteBuffer.wrap(data);
if (buffer.get() != getPacketTypeCode())
log.error("Wrong type code for EncryptedEmailPacket. Expected <" + getPacketTypeCode() + ">, got <" + (char)data[0] + ">");
dhtKey = readHash(buffer);
deletionKeyPlain = new UniqueId(buffer);
int encryptedLength = buffer.getShort(); // length of the encrypted part of the packet
encryptedData = new byte[encryptedLength];
buffer.get(encryptedData);
}
/**
* Creates an <code>EncryptedEmailPacket</code> from an <code>UnencryptedEmailPacket</code>.
* The public key of <code>emailDestination</code> is used for encryption.
@ -78,13 +71,13 @@ public class EncryptedEmailPacket extends DhtStorablePacket {
*/
public EncryptedEmailPacket(UnencryptedEmailPacket unencryptedPacket, EmailDestination emailDestination, I2PAppContext appContext) {
dhtKey = generateRandomHash();
deletionKeyPlain = unencryptedPacket.getPlaintextDeletionKey();
deletionKeyPlain = unencryptedPacket.getVerificationDeletionKey();
// put all the encrypted fields into an array
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
DataOutputStream dataStream = new DataOutputStream(byteStream);
try {
unencryptedPacket.getVerificationDeletionKey().writeTo(dataStream);
deletionKeyPlain.writeTo(dataStream);
unencryptedPacket.getMessageId().writeTo(dataStream);
dataStream.writeShort(unencryptedPacket.getFragmentIndex());
dataStream.writeShort(unencryptedPacket.getNumFragments());
@ -100,14 +93,32 @@ public class EncryptedEmailPacket extends DhtStorablePacket {
}
}
private Hash generateRandomHash() {
RandomSource randomSource = RandomSource.getInstance();
/**
* Creates an <code>EncryptedEmailPacket</code> from raw datagram data.
* To read the encrypted parts of the packet, <code>decrypt</code> must be called first.
* @param data
*/
public EncryptedEmailPacket(byte[] data) {
super(data);
byte[] bytes = new byte[Hash.HASH_LENGTH];
for (int i=0; i<bytes.length; i++)
bytes[i] = (byte)randomSource.nextInt(256);
return new Hash(bytes);
ByteBuffer buffer = ByteBuffer.wrap(data, HEADER_LENGTH, data.length-HEADER_LENGTH);
dhtKey = readHash(buffer);
deletionKeyPlain = new UniqueId(buffer);
int encryptedLength = buffer.getShort(); // length of the encrypted part of the packet
encryptedData = new byte[encryptedLength];
buffer.get(encryptedData);
}
private Hash generateRandomHash() {
RandomSource randomSource = RandomSource.getInstance();
byte[] bytes = new byte[Hash.HASH_LENGTH];
for (int i=0; i<bytes.length; i++)
bytes[i] = (byte)randomSource.nextInt(256);
return new Hash(bytes);
}
/**
@ -116,12 +127,12 @@ public class EncryptedEmailPacket extends DhtStorablePacket {
* @param appContext
* @throws DataFormatException
*/
public UnencryptedEmailPacket decrypt(EmailIdentity identity, I2PAppContext appContext) throws DataFormatException {
byte[] decryptedData = decrypt(encryptedData, identity, appContext);
ByteBuffer buffer = ByteBuffer.wrap(decryptedData);
UniqueId deletionKeyVerify = new UniqueId(buffer);
UniqueId messageId = new UniqueId(buffer);
public UnencryptedEmailPacket decrypt(EmailIdentity identity, I2PAppContext appContext) throws DataFormatException {
byte[] decryptedData = decrypt(encryptedData, identity, appContext);
ByteBuffer buffer = ByteBuffer.wrap(decryptedData);
UniqueId deletionKeyVerify = new UniqueId(buffer);
UniqueId messageId = new UniqueId(buffer);
int fragmentIndex = buffer.getShort();
int numFragments = buffer.getShort();
@ -129,8 +140,8 @@ public class EncryptedEmailPacket extends DhtStorablePacket {
byte[] content = new byte[contentLength];
buffer.get(content);
return new UnencryptedEmailPacket(deletionKeyPlain, deletionKeyVerify, messageId, fragmentIndex, numFragments, content);
}
return new UnencryptedEmailPacket(messageId, fragmentIndex, numFragments, content, deletionKeyVerify);
}
/**
* Decrypts data with an email identity's private key.
@ -139,10 +150,10 @@ public class EncryptedEmailPacket extends DhtStorablePacket {
* @param appContext
* @return The decrypted data
*/
private byte[] decrypt(byte[] data, EmailIdentity identity, I2PAppContext appContext) throws DataFormatException {
private byte[] decrypt(byte[] data, EmailIdentity identity, I2PAppContext appContext) throws DataFormatException {
PrivateKey privateKey = identity.getPrivateEncryptionKey();
return appContext.elGamalAESEngine().decrypt(data, privateKey, appContext.sessionKeyManager());
}
}
/**
* Encrypts data with an email destination's public key.
@ -151,7 +162,7 @@ public class EncryptedEmailPacket extends DhtStorablePacket {
* @param appContext
* @return The encrypted data
*/
// TODO should this method be in this class?
// TODO should this method be in this class?
public byte[] encrypt(byte[] data, EmailDestination emailDestination, I2PAppContext appContext) {
PublicKey publicKey = emailDestination.getPublicEncryptionKey();
SessionKey sessionKey = appContext.sessionKeyManager().createSession(publicKey);
@ -169,7 +180,7 @@ public class EncryptedEmailPacket extends DhtStorablePacket {
public Hash getDhtKey() {
return dhtKey;
}
/**
* Returns the value of the "plain-text deletion key" field.
* Storage nodes set this to all zero bytes when the packet is retrieved.
@ -179,13 +190,13 @@ public class EncryptedEmailPacket extends DhtStorablePacket {
return deletionKeyPlain;
}
@Override
public byte[] toByteArray() {
ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream();
DataOutputStream dataStream = new DataOutputStream(byteArrayStream);
@Override
public byte[] toByteArray() {
ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream();
DataOutputStream dataStream = new DataOutputStream(byteArrayStream);
try {
dataStream.write((byte)getPacketTypeCode());
writeHeader(dataStream);
dataStream.write(dhtKey.toByteArray());
dataStream.write(deletionKeyPlain.toByteArray());
dataStream.writeShort(encryptedData.length);
@ -194,14 +205,14 @@ public class EncryptedEmailPacket extends DhtStorablePacket {
catch (IOException e) {
log.error("Can't write to ByteArrayOutputStream.", e);
}
return byteArrayStream.toByteArray();
}
return byteArrayStream.toByteArray();
}
// Returns the number of bytes in the packet.
// TODO just return content.length+CONST so we don't call toByteArray every time
public int getSize() {
return toByteArray().length;
}
// Returns the number of bytes in the packet.
// TODO just return content.length+CONST so we don't call toByteArray every time
public int getSize() {
return toByteArray().length;
}
@Override
public String toString() {

View File

@ -21,11 +21,13 @@
package i2p.bote.packet;
import java.nio.ByteBuffer;
import i2p.bote.I2PBote;
import i2p.bote.packet.dht.FindClosePeersPacket;
import i2p.bote.packet.dht.RetrieveRequest;
import i2p.bote.packet.dht.StoreRequest;
import java.nio.ByteBuffer;
import net.i2p.data.Hash;
import net.i2p.util.Log;
@ -35,15 +37,30 @@ public abstract class I2PBotePacket {
@SuppressWarnings("unchecked")
private static Class<? extends I2PBotePacket>[] ALL_PACKET_TYPES = new Class[] {
RelayPacket.class, ResponsePacket.class, RetrieveRequest.class, StoreRequest.class, FindClosePeersPacket.class,
PeerList.class, EncryptedEmailPacket.class, UnencryptedEmailPacket.class, IndexPacket.class
PeerList.class, EncryptedEmailPacket.class, UnencryptedEmailPacket.class, IndexPacket.class,
EmailPacketDeleteRequest.class, IndexPacketDeleteRequest.class
};
private int protocolVersion;
/**
* Creates a new <code>I2PBotePacket</code> with the current protocol version.
*/
protected I2PBotePacket() {
protocolVersion = I2PBote.PROTOCOL_VERSION;
}
protected I2PBotePacket(int protocolVersion) {
this.protocolVersion = protocolVersion;
}
public abstract byte[] toByteArray();
/**
* Returns the size of the packet in bytes.
* @return
*/
// TODO rename to getPacketSize
public int getSize() {
return toByteArray().length;
}
@ -77,9 +94,13 @@ public abstract class I2PBotePacket {
checkPacketType((char)packetTypeCode);
}
/* protected void checkPacketVersion(byte version, byte minVersion, byte maxVersion) {
// TODO
}*/
/**
* Returns the version of the I2P-Bote network protocol used by this packet.
* @return
*/
public int getProtocolVersion() {
return protocolVersion;
}
/**
* Creates a {@link Hash} from bytes read from a {@link ByteBuffer}.

View File

@ -21,52 +21,46 @@
package i2p.bote.packet;
import i2p.bote.UniqueId;
import i2p.bote.email.EmailDestination;
import i2p.bote.packet.dht.DhtStorablePacket;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import net.i2p.data.DataFormatException;
import net.i2p.data.Hash;
import net.i2p.util.Log;
/**
* Stores DHT keys of Email Packets and their deletion keys.
*
* This class is not thread-safe.
*/
@TypeCode('I')
public class IndexPacket extends DhtStorablePacket {
private Log log = new Log(IndexPacket.class);
private Collection<Hash> dhtKeys; // DHT keys of email packets
// private Collection<Entry> entries; should probably use a Map and get rid of the Entry class
private Map<Hash, UniqueId> entries;
private Hash destinationHash; // The DHT key of this packet
public IndexPacket(byte[] data) {
ByteBuffer dataBuffer = ByteBuffer.wrap(data);
if (dataBuffer.get() != getPacketTypeCode())
log.error("Wrong type code for IndexPacket. Expected <" + getPacketTypeCode() + ">, got <" + (char)data[0] + ">");
destinationHash = readHash(dataBuffer);
int numKeys = dataBuffer.get();
dhtKeys = new ArrayList<Hash>();
for (int i=0; i<numKeys; i++) {
Hash dhtKey = readHash(dataBuffer);
dhtKeys.add(dhtKey);
}
// TODO catch BufferUnderflowException; warn if extra bytes in the array
}
/**
*
* @param emailPackets One or more email packets
* @param emailDestination Determines the DHT key of this Index Packet
*/
public IndexPacket(Collection<EncryptedEmailPacket> emailPackets, EmailDestination emailDestination) {
dhtKeys = new ArrayList<Hash>();
// entries = new ArrayList<Entry>();
entries = new ConcurrentHashMap<Hash, UniqueId>();
for (EncryptedEmailPacket emailPacket: emailPackets)
dhtKeys.add(emailPacket.getDhtKey());
// Entry entry = new Entry(emailPacket.getDhtKey(), emailPacket.getPlaintextDeletionKey());
entries.put(emailPacket.getDhtKey(), emailPacket.getPlaintextDeletionKey());
destinationHash = emailDestination.getHash();
}
@ -82,9 +76,9 @@ public class IndexPacket extends DhtStorablePacket {
throw new IllegalArgumentException("This method must be invoked with at least one index packet.");
destinationHash = indexPackets.iterator().next().getDhtKey();
dhtKeys = new HashSet<Hash>();
entries = new ConcurrentHashMap<Hash, UniqueId>();
for (IndexPacket packet: indexPackets)
dhtKeys.addAll(packet.getDhtKeys());
entries.putAll(packet.getEntries());
}
/**
@ -95,15 +89,36 @@ public class IndexPacket extends DhtStorablePacket {
this(Arrays.asList(indexPackets));
}
public IndexPacket(byte[] data) {
super(data);
ByteBuffer buffer = ByteBuffer.wrap(data, HEADER_LENGTH, data.length-HEADER_LENGTH);
destinationHash = readHash(buffer);
int numKeys = buffer.get();
entries = new ConcurrentHashMap<Hash, UniqueId>();
for (int i=0; i<numKeys; i++) {
Hash dhtKey = readHash(buffer);
UniqueId delKey = new UniqueId(buffer);
entries.put(dhtKey, delKey);
}
// TODO catch BufferUnderflowException; warn if extra bytes in the array
}
@Override
public byte[] toByteArray() {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write((byte)getPacketTypeCode());
try {
writeHeader(outputStream);
destinationHash.writeBytes(outputStream);
outputStream.write((byte)dhtKeys.size());
for (Hash dhtKey: dhtKeys)
dhtKey.writeBytes(outputStream);
outputStream.write((byte)entries.size());
for (Entry<Hash, UniqueId> entry: entries.entrySet()) {
entry.getKey().writeBytes(outputStream);
entry.getValue().writeTo(outputStream);
}
// TODO in the unit test, verify that toByteArray().length = Hash.NUM_BYTES + 1 + dhtKeys.size()*Hash.NUM_BYTES
} catch (DataFormatException e) {
log.error("Invalid format for email destination.", e);
@ -114,18 +129,79 @@ public class IndexPacket extends DhtStorablePacket {
}
/**
* Returns the DHT keys of the {@link EncryptedEmailPacket}s referenced by this {@link IndexPacket}.
* Returns the deletion key for a given DHT key of an Email Packet, or <code>null</code> if
* the <code>IndexPacket</code> doesn't contain the DHT key.
* @param dhtKey
* @return
*/
public Collection<Hash> getDhtKeys() {
return dhtKeys;
public UniqueId getDeletionKey(Hash dhtKey) {
return entries.get(dhtKey);
}
/**
* Returns the DHT key of this packet.
* Returns all DHT keys in this <code>IndexPacket</code>.
* @return
*/
public Collection<Hash> getDhtKeys() {
return entries.keySet();
}
/**
* Adds an entry to the <code>IndexPacket</code>. If the DHT key exists in the packet already,
* nothing happens.
* @param dhtKey
* @param deletionKey
*/
public void put(Hash dhtKey, UniqueId deletionKey) {
entries.put(dhtKey, deletionKey);
}
/**
* Removes an entry from the <code>IndexPacket</code>.
* @param dhtKey
*/
public void remove(Hash dhtKey) {
entries.remove(dhtKey);
}
/**
* Tests if the <code>IndexPacket</code> contains a given DHT key.
* @param dhtKey
* @return <code>true</code> if the packet containes the DHT key, <code>false</code> otherwise.
*/
public boolean contains(Hash dhtKey) {
return entries.containsKey(dhtKey);
}
/**
* Returns the DHT key / deletion key pairs for the {@link EncryptedEmailPacket}s referenced
* by this <code>IndexPacket</code>.
* @return
*/
// public Collection<Entry> getEntries() {
public Map<Hash, UniqueId> getEntries() {
return entries;
}
/**
* Returns the DHT key of this packet. The DHT key of an <code>IndexPacket</code> is the
* hash of the Email Destination whose entries this packet stores.
*/
@Override
public Hash getDhtKey() {
return destinationHash;
}
/**
* This class holds a DHT key and a deletion key for an email packet.
*/
/* public class Entry {
public Hash dhtKey; // The DHT key of an email packet
public UniqueId deletionKey; // The deletion key of the email packet
public Entry(Hash dhtKey, UniqueId deletionKey) {
this.dhtKey = dhtKey;
this.deletionKey = deletionKey;
}
}*/
}

View File

@ -0,0 +1,119 @@
/**
* Copyright (C) 2009 HungryHobo@mail.i2p
*
* The GPG fingerprint for HungryHobo@mail.i2p is:
* 6DD3 EAA2 9990 29BC 4AD2 7486 1E2C 7B61 76DC DC12
*
* This file is part of I2P-Bote.
* I2P-Bote is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* I2P-Bote is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with I2P-Bote. If not, see <http://www.gnu.org/licenses/>.
*/
package i2p.bote.packet;
import i2p.bote.UniqueId;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import net.i2p.data.DataFormatException;
import net.i2p.data.Hash;
import net.i2p.util.Log;
/**
* This class is not thread-safe.
*/
@TypeCode('X')
public class IndexPacketDeleteRequest extends CommunicationPacket {
private Log log = new Log(IndexPacketDeleteRequest.class);
private Hash emailDestHash;
private Map<Hash, UniqueId> entries;
public IndexPacketDeleteRequest(Hash emailDestHash) {
this.emailDestHash = emailDestHash;
entries = new HashMap<Hash, UniqueId>();
}
public IndexPacketDeleteRequest(byte[] data) {
super(data);
ByteBuffer buffer = ByteBuffer.wrap(data, HEADER_LENGTH, data.length-HEADER_LENGTH);
emailDestHash = readHash(buffer);
entries = new HashMap<Hash, UniqueId>();
int numEntries = buffer.getShort();
for (int i=0; i<numEntries; i++) {
Hash dhtKey = readHash(buffer);
UniqueId deletionKey = new UniqueId(buffer);
entries.put(dhtKey, deletionKey);
}
if (buffer.hasRemaining())
log.debug("Index Packet Delete Request has " + buffer.remaining() + " extra bytes.");
}
public void put(Hash dhtKey, UniqueId deletionKey) {
entries.put(dhtKey, deletionKey);
}
/* public Map<Hash, UniqueId> getAll() {
return entries;
}*/
public Hash getEmailDestHash() {
return emailDestHash;
}
public Set<Hash> getDhtKeys() {
return entries.keySet();
}
public UniqueId getDeletionKey(Hash dhtKey) {
return entries.get(dhtKey);
}
public void remove(Hash dhtKey) {
entries.remove(dhtKey);
}
public int getNumEntries() {
return entries.size();
}
@Override
public byte[] toByteArray() {
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
DataOutputStream dataStream = new DataOutputStream(byteStream);
try {
writeHeader(dataStream);
emailDestHash.writeBytes(dataStream);
dataStream.writeShort(entries.size());
for (Entry<Hash, UniqueId> entry: entries.entrySet()) {
dataStream.write(entry.getKey().toByteArray());
dataStream.write(entry.getValue().toByteArray());
}
} catch (DataFormatException e) {
log.error("Invalid format for email destination.", e);
} catch (IOException e) {
log.error("Can't write to ByteArrayOutputStream.", e);
}
return byteStream.toByteArray();
}
}

View File

@ -45,12 +45,11 @@ public class PeerList extends DataPacket {
}
public PeerList(byte[] data) throws DataFormatException {
ByteBuffer buffer = ByteBuffer.wrap(data);
if (buffer.get() != getPacketTypeCode())
log.error("Wrong type code for PeerList. Expected <" + getPacketTypeCode() + ">, got <" + (char)data[0] + ">");
super(data);
ByteBuffer buffer = ByteBuffer.wrap(data, HEADER_LENGTH, data.length-HEADER_LENGTH);
int numPeers = buffer.getShort();
peers = new ArrayList<KademliaPeer>();
for (int i=0; i<numPeers; i++) {
Destination destination = new Destination();
@ -77,7 +76,7 @@ public class PeerList extends DataPacket {
DataOutputStream dataStream = new DataOutputStream(arrayOutputStream);
try {
dataStream.write((byte)getPacketTypeCode());
writeHeader(dataStream);
dataStream.writeShort(peers.size());
for (KademliaPeer peer: peers)
// write the first 384 bytes (the two public keys)

View File

@ -50,8 +50,18 @@ public class RelayPacket extends DataPacket {
this.latestSendTime = latestSendTime;
}
public RelayPacket(DataPacket dataPacket, Destination nextDestination, long earliestSendTime, long latestSendTime, byte[] xorKey) throws DataFormatException {
// TODO
}
public RelayPacket(InputStream inputStream) throws IOException, DataFormatException {
this(Util.readInputStream(inputStream));
}
public RelayPacket(byte[] data) throws DataFormatException {
ByteBuffer buffer = ByteBuffer.wrap(data);
super(data);
ByteBuffer buffer = ByteBuffer.wrap(data, HEADER_LENGTH, data.length-HEADER_LENGTH);
earliestSendTime = buffer.getInt();
latestSendTime = buffer.getInt();
@ -72,14 +82,6 @@ public class RelayPacket extends DataPacket {
log.debug("Relay Packet has " + buffer.remaining() + " extra bytes.");
}
public RelayPacket(DataPacket dataPacket, Destination nextDestination, long earliestSendTime, long latestSendTime, byte[] xorKey) throws DataFormatException {
// TODO
}
public RelayPacket(InputStream inputStream) throws IOException, DataFormatException {
this(Util.readInputStream(inputStream));
}
public Destination getNextDestination() {
return nextDestination;
}
@ -98,6 +100,7 @@ public class RelayPacket extends DataPacket {
DataOutputStream dataStream = new DataOutputStream(arrayOutputStream);
try {
writeHeader(dataStream);
dataStream.writeInt((int)earliestSendTime);
dataStream.writeInt((int)latestSendTime);
dataStream.write(xorKey);

View File

@ -87,6 +87,7 @@ public class ResponsePacket extends CommunicationPacket {
@Override
public String toString() {
return super.toString() + ", status=" + statusCode + ", ploadType=" + payload.getClass().getSimpleName();
String payloadClassName = payload==null?"<null>":payload.getClass().getSimpleName();
return super.toString() + ", status=" + statusCode + ", ploadType=" + payloadClassName;
}
}

View File

@ -35,20 +35,39 @@ import net.i2p.util.Log;
@TypeCode('U')
public class UnencryptedEmailPacket extends DataPacket {
private Log log = new Log(UnencryptedEmailPacket.class);
private UniqueId deletionKeyPlain;
private UniqueId deletionKeyVerify;
private UniqueId messageId;
private int fragmentIndex;
private int numFragments;
private byte[] content;
private UniqueId deletionKeyVerify; // the decrypted deletion key if this UnencryptedEmailPacket was created from an EncryptedEmailPacket; null otherwise
public UnencryptedEmailPacket(byte[] data) {
ByteBuffer buffer = ByteBuffer.wrap(data);
if (buffer.get() != getPacketTypeCode())
log.error("Wrong type code for UnencryptedEmailPacket. Expected <" + getPacketTypeCode() + ">, got <" + (char)data[0] + ">");
public UnencryptedEmailPacket(InputStream inputStream) throws IOException {
this(Util.readInputStream(inputStream));
}
/**
* Creates an <code>UnencryptedEmailPacket</code> from a <code>byte</code> array that contains MIME data.
* @param messageId
* @param fragmentIndex
* @param numFragments
* @param content
* @param deletionKeyVerify The decrypted deletion key from an encrypted email packet
*/
public UnencryptedEmailPacket(UniqueId messageId, int fragmentIndex, int numFragments, byte[] content, UniqueId deletionKeyVerify) {
this.messageId = messageId;
this.fragmentIndex = fragmentIndex;
this.numFragments = numFragments;
this.content = content;
this.deletionKeyVerify = deletionKeyVerify;
verify();
}
public UnencryptedEmailPacket(byte[] data) {
super(data);
deletionKeyPlain = new UniqueId(buffer);
deletionKeyVerify = new UniqueId(buffer);
ByteBuffer buffer = ByteBuffer.wrap(data, HEADER_LENGTH, data.length-HEADER_LENGTH);
messageId = new UniqueId(buffer);
fragmentIndex = buffer.getShort();
numFragments = buffer.getShort();
@ -56,36 +75,10 @@ public class UnencryptedEmailPacket extends DataPacket {
int contentLength = buffer.getShort();
content = new byte[contentLength];
buffer.get(content);
}
public UnencryptedEmailPacket(InputStream inputStream) throws IOException {
this(Util.readInputStream(inputStream));
}
/**
* Creates an <code>UnencryptedEmailPacket</code> from a <code>byte</code> array that contains MIME data.
* @param deletionKeyPlain
* @param deletionKeyVerify
* @param messageId
* @param fragmentIndex
* @param numFragments
* @param content
*/
public UnencryptedEmailPacket(UniqueId deletionKeyPlain, UniqueId deletionKeyVerify, UniqueId messageId, int fragmentIndex, int numFragments, byte[] content) {
this.deletionKeyPlain = deletionKeyPlain;
this.deletionKeyVerify = deletionKeyVerify;
this.messageId = messageId;
this.fragmentIndex = fragmentIndex;
this.numFragments = numFragments;
this.content = content;
verify();
}
public UniqueId getPlaintextDeletionKey() {
return deletionKeyPlain;
}
verify();
}
public UniqueId getVerificationDeletionKey() {
return deletionKeyVerify;
}
@ -116,16 +109,14 @@ public class UnencryptedEmailPacket extends DataPacket {
// TODO more sanity checks?
}
@Override
public byte[] toByteArray() {
ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream();
DataOutputStream dataStream = new DataOutputStream(byteArrayStream);
@Override
public byte[] toByteArray() {
ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream();
DataOutputStream dataStream = new DataOutputStream(byteArrayStream);
try {
dataStream.write((byte)getPacketTypeCode());
deletionKeyPlain.writeTo(dataStream);
deletionKeyVerify.writeTo(dataStream);
messageId.writeTo(dataStream);
writeHeader(dataStream);
messageId.writeTo(dataStream);
dataStream.writeShort(fragmentIndex);
dataStream.writeShort(numFragments);
dataStream.writeShort(content.length);
@ -134,12 +125,12 @@ public class UnencryptedEmailPacket extends DataPacket {
catch (IOException e) {
log.error("Can't write to ByteArrayOutputStream.", e);
}
return byteArrayStream.toByteArray();
}
@Override
public String toString() {
return "Type=" + UnencryptedEmailPacket.class.getSimpleName() + ", delKeyPln=" + deletionKeyPlain + ", delKeyVfy=" + deletionKeyVerify +
", msgId=" + messageId + ", fragIdx=" + fragmentIndex + ", numFrags=" + numFragments + ", contLen=" + content.length;
}
return byteArrayStream.toByteArray();
}
@Override
public String toString() {
return "Type=" + UnencryptedEmailPacket.class.getSimpleName() + ", msgId=" + messageId +
", fragIdx=" + fragmentIndex + ", numFrags=" + numFragments + ", contLen=" + content.length;
}
}

View File

@ -32,6 +32,16 @@ import net.i2p.util.Log;
public abstract class DhtStorablePacket extends DataPacket {
private static Log log = new Log(DhtStorablePacket.class);
public DhtStorablePacket() {
}
/**
* @see DataPacket(byte[])
*/
public DhtStorablePacket(byte[] data) {
super(data);
}
public abstract Hash getDhtKey();
/**
@ -62,7 +72,15 @@ public abstract class DhtStorablePacket extends DataPacket {
}
}
/**
* Returns <code>null</code> if the file doesn't exist.
* @param file
* @return
*/
public static DhtStorablePacket createPacket(File file) {
if (file==null || !file.exists())
return null;
DataPacket dataPacket;
dataPacket = DataPacket.createPacket(file);
if (dataPacket instanceof DhtStorablePacket)

View File

@ -73,6 +73,7 @@ public class RetrieveRequest extends CommunicationPacket {
try {
writeHeader(outputStream);
// TODO doesn't the type code already get written in writeHeader?
outputStream.write(getPacketTypeCode(dataType));
outputStream.write(key.toByteArray());
}

View File

@ -39,7 +39,7 @@ public class PrintAppVersionTag extends SimpleTagSupport {
JspWriter out = pageContext.getOut();
try {
out.println(I2PBote.getInstance().getVersion());
out.println(I2PBote.getInstance().getAppVersion());
} catch (IOException e) {
log.error("Can't write output to HTML page", e);
}

View File

@ -0,0 +1,53 @@
/**
* Copyright (C) 2009 HungryHobo@mail.i2p
*
* The GPG fingerprint for HungryHobo@mail.i2p is:
* 6DD3 EAA2 9990 29BC 4AD2 7486 1E2C 7B61 76DC DC12
*
* This file is part of I2P-Bote.
* I2P-Bote is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* I2P-Bote is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with I2P-Bote. If not, see <http://www.gnu.org/licenses/>.
*/
package i2p.bote.packet;
import static junit.framework.Assert.assertTrue;
import i2p.bote.UniqueId;
import java.util.Arrays;
import net.i2p.data.Hash;
import org.junit.Before;
import org.junit.Test;
// TODO add to AllTests
public class EmailPacketDeleteRequestTest {
private EmailPacketDeleteRequest delRequest;
@Before
public void setUp() throws Exception {
Hash dhtKey = new Hash(new byte[] {-48, 78, 66, 58, -79, 87, 38, -103, -60, -27, 108, 55, 117, 37, -99, 93, -23, -102, -83, 20, 44, -80, 65, 89, -68, -73, 69, 51, 115, 79, 24, 127});
byte[] packetIdBytes = new byte[] {120, 120, -8, -88, 21, 126, 46, -61, 18, -101, 15, 53, 20, -44, -112, 42, 86, -117, 30, -96, -66, 33, 71, -55, -102, -78, 78, -82, -105, 66, -116, 43};
UniqueId deletionKey = new UniqueId(packetIdBytes, 0);
delRequest = new EmailPacketDeleteRequest(dhtKey, deletionKey);
}
@Test
public void toByteArrayAndBack() {
byte[] arrayA = delRequest.toByteArray();
byte[] arrayB;
arrayB = new EmailPacketDeleteRequest(arrayA).toByteArray();
assertTrue("The two arrays differ!", Arrays.equals(arrayA, arrayB));
}
}

View File

@ -43,10 +43,9 @@ public class EncryptedEmailPacketTest {
@Before
public void setUp() throws Exception {
byte[] content = message.getBytes();
UniqueId deletionKeyPlain = new UniqueId(new byte[] {72, -18, -72, -39, 122, 40, -104, -66, -54, -61, -108, 72, 54, 30, 37, 76, 44, 86, 104, -124, -31, 32, -82, -27, 26, 76, 7, 106, -76, 72, 49, -44}, 0);
UniqueId deletionKeyVerify = new UniqueId(new byte[] {-62, -112, 99, -65, 13, 44, -117, -111, 96, 45, -6, 64, 78, 57, 117, 103, -24, 101, 106, -116, -18, 62, 99, -49, 60, -81, 8, 64, 27, -41, -104, 58}, 0);
byte[] messageIdBytes = new byte[] {2, -69, -24, -109, 1, 69, -122, -69, 113, -68, -90, 55, -28, 105, 97, 125, 70, 51, 58, 14, 2, -13, -53, 90, -29, 36, 67, 36, -94, -108, -125, 11, 123};
byte[] messageIdBytes = new byte[] {-69, -24, -109, 1, 69, -122, -69, 113, -68, -90, 55, -28, 105, 97, 125, 70, 51, 58, 14, 2, -13, -53, 90, -29, 36, 67, 36, -94, -108, -125, 11, 123};
UniqueId messageId = new UniqueId(messageIdBytes, 0);
int fragmentIndex = 0;
@ -56,7 +55,7 @@ public class EncryptedEmailPacketTest {
identity = new EmailIdentity(base64Identity);
EmailDestination destination = new EmailDestination(identity.getKey()); // corresponds to identity
UnencryptedEmailPacket plaintextPacket = new UnencryptedEmailPacket(deletionKeyPlain, deletionKeyVerify, messageId, fragmentIndex, numFragments, content);
UnencryptedEmailPacket plaintextPacket = new UnencryptedEmailPacket(messageId, fragmentIndex, numFragments, content, deletionKeyVerify);
encryptedPacket = new EncryptedEmailPacket(plaintextPacket, destination, appContext);
}

View File

@ -0,0 +1,83 @@
/**
* Copyright (C) 2009 HungryHobo@mail.i2p
*
* The GPG fingerprint for HungryHobo@mail.i2p is:
* 6DD3 EAA2 9990 29BC 4AD2 7486 1E2C 7B61 76DC DC12
*
* This file is part of I2P-Bote.
* I2P-Bote is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* I2P-Bote is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with I2P-Bote. If not, see <http://www.gnu.org/licenses/>.
*/
package i2p.bote.packet;
import static org.junit.Assert.assertEquals;
import i2p.bote.UniqueId;
import java.util.Arrays;
import java.util.Comparator;
import net.i2p.data.Hash;
import org.junit.Before;
import org.junit.Test;
public class IndexPacketDeleteRequestTest {
IndexPacketDeleteRequest delRequest;
Hash[] dhtKeys;
UniqueId[] delKeys;
@Before
public void setUp() throws Exception {
Hash emailDestHash = new Hash(new byte[] {-48, 78, 66, 58, -79, 87, 38, -103, -60, -27, 108, 55, 117, 37, -99, 93, -23, -102, -83, 20, 44, -80, 65, 89, -68, -73, 69, 51, 115, 79, 24, 127});
delRequest = new IndexPacketDeleteRequest(emailDestHash);
dhtKeys = new Hash[3];
delKeys = new UniqueId[3];
dhtKeys[0] = new Hash(new byte[] {120, 120, -8, -88, 21, 126, 46, -61, 18, -101, 15, 53, 20, -44, -112, 42, 86, -117, 30, -96, -66, 33, 71, -55, -102, -78, 78, -82, -105, 66, -116, 43});
delKeys[0] = new UniqueId(new byte[] {-62, -112, 99, -65, 13, 44, -117, -111, 96, 45, -6, 64, 78, 57, 117, 103, -24, 101, 106, -116, -18, 62, 99, -49, 60, -81, 8, 64, 27, -41, -104, 58}, 0);
delRequest.put(dhtKeys[0], delKeys[0]);
dhtKeys[1] = new Hash(new byte[] {-69, -24, -109, 1, 69, -122, -69, 113, -68, -90, 55, -28, 105, 97, 125, 70, 51, 58, 14, 2, -13, -53, 90, -29, 36, 67, 36, -94, -108, -125, 11, 123});
delKeys[1] = new UniqueId(new byte[] {-16, 67, 107, 80, 27, 65, 81, 71, 61, 70, -72, 126, 64, -10, 57, -128, 111, -107, -42, 24, -90, -4, -46, 63, 7, -6, 43, 76, -9, -81, 8, -68}, 0);
delRequest.put(dhtKeys[1], delKeys[1]);
dhtKeys[2] = new Hash(new byte[] {-37, -8, 37, 82, -40, -34, 68, -51, -16, 74, 27, 89, 113, -15, 112, 69, 92, 102, 62, 111, 99, -27, -42, -71, 6, 38, 106, 121, 21, -72, -83, 3});
delKeys[2] = new UniqueId(new byte[] {6, -32, -23, 17, 55, 15, -45, -19, 91, 100, -76, -76, 118, -118, -53, -109, -108, 113, -112, 81, 117, 9, -126, 20, 0, -83, -89, 7, 48, 76, -58, 83}, 0);
delRequest.put(dhtKeys[2], delKeys[2]);
}
@Test
public void toByteArrayAndBack() {
assertEquals(3, delRequest.getDhtKeys().size());
Comparator<Hash> hashComparator = new Comparator<Hash>() {
@Override
public int compare(Hash key1, Hash key2) {
return key1.toString().compareTo(key2.toString());
}
};
byte[] bytes = delRequest.toByteArray();
IndexPacketDeleteRequest delRequest2 = new IndexPacketDeleteRequest(bytes);
// Make an array of DHT keys from delRequest2.
// Sort the array elements first because they are not guaranteed to be in any order.
Arrays.sort(dhtKeys, hashComparator);
Hash[] dhtKeys2 = delRequest2.getDhtKeys().toArray(new Hash[0]);
Arrays.sort(dhtKeys2, hashComparator);
assertEquals("The two arrays are different lengths", dhtKeys.length, dhtKeys2.length);
for (int i=0; i<dhtKeys.length; i++)
assertEquals("DHT keys differ: " + dhtKeys[i] + " vs " + dhtKeys2[i], dhtKeys[i], dhtKeys2[i]);
}
}

View File

@ -0,0 +1,29 @@
package i2p.bote.packet;
import static org.junit.Assert.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class IndexPacketTest {
@Before
public void setUp() throws Exception {
}
@After
public void tearDown() throws Exception {
}
@Test
public void testToByteArray() {
fail("Not yet implemented");
}
@Test
public void testGetDhtKeys() {
fail("Not yet implemented");
}
}

View File

@ -48,12 +48,11 @@ public class ResponsePacketTest {
private DataPacket createDataPacket() throws NoSuchAlgorithmException {
byte[] dataToStore = new byte[] {2, 109, -80, -37, -106, 83, -33, -39, -94, -76, -112, -98, 99, 25, -61, 44, -92, -85, 1, 10, -128, -2, -27, -86, -126, -33, -11, 42, 56, 3, -97, -101, 111, 7, -96, 25, 121, 74, 89, -40, -33, 82, -50, -18, 49, 106, 13, -121, 53, -83, -2, 35, -7, 71, -71, 26, 90, 1};
UniqueId deletionKeyPlain = new UniqueId(new byte[] {72, -18, -72, -39, 122, 40, -104, -66, -54, -61, -108, 72, 54, 30, 37, 76, 44, 86, 104, -124, -31, 32, -82, -27, 26, 76, 7, 106, -76, 72, 49, -44}, 0);
UniqueId deletionKeyEncrypted = new UniqueId(new byte[] {-62, -112, 99, -65, 13, 44, -117, -111, 96, 45, -6, 64, 78, 57, 117, 103, -24, 101, 106, -116, -18, 62, 99, -49, 60, -81, 8, 64, 27, -41, -104, 58}, 0);
byte[] messageIdBytes = new byte[] {2, -69, -24, -109, 1, 69, -122, -69, 113, -68, -90, 55, -28, 105, 97, 125, 70, 51, 58, 14, 2, -13, -53, 90, -29, 36, 67, 36, -94, -108, -125, 11, 123};
UniqueId deletionKeyVerify = new UniqueId(new byte[] {-62, -112, 99, -65, 13, 44, -117, -111, 96, 45, -6, 64, 78, 57, 117, 103, -24, 101, 106, -116, -18, 62, 99, -49, 60, -81, 8, 64, 27, -41, -104, 58}, 0);
byte[] messageIdBytes = new byte[] {-69, -24, -109, 1, 69, -122, -69, 113, -68, -90, 55, -28, 105, 97, 125, 70, 51, 58, 14, 2, -13, -53, 90, -29, 36, 67, 36, -94, -108, -125, 11, 123};
UniqueId messageId = new UniqueId(messageIdBytes, 0);
UnencryptedEmailPacket unencryptedPacket = new UnencryptedEmailPacket(deletionKeyPlain, deletionKeyEncrypted, messageId, 0, 1, dataToStore);
UnencryptedEmailPacket unencryptedPacket = new UnencryptedEmailPacket(messageId, 0, 1, dataToStore, deletionKeyVerify);
EmailDestination destination = new EmailDestination("0XuJjhgp58aOhvHHgpaxoQYsCUfDS6BECMEoVxFGEFPdk3y8lbzIsq9eUyeizFleMacYwoscCir8nQLlW34lxfRmirkNpD9vU1XnmjnZ5hGdnor1qIDqz3KJ040dVQ617MwyG97xxYLT0FsH907vBXgdc4RCHwKd1~9siagA5CSMaA~wM8ymKXLypiZGYexENLmim7nMzJTQYoOM~fVS99UaGJleDBN3pgZ2EvRYDQV2VqKH7Gee07R3y7b~c0tAKVHS0IbPQfTVJigrIHjTl~ZczxpaeTM04T8IgxKnO~lSmR1w7Ik8TpEkETwT9PDwUqQsjmlSY8E~WwwGMRJVyIRZUkHeRZ0aFq7us8W9EKzYtjjiU1z0QFpZrTfJE8oqCbnH5Lqv5Q86UdTPpriJC1N99E77TpCTnNzcBnpp6ko2JCy2IJUveaigKxS6EmS9KarkkkBRsckOKZZ6UNTOqPZsBCsx0Q9WvDF-Uc3dtouXWyenxRptaQsdkZyYlEQv");
return new EncryptedEmailPacket(unencryptedPacket, destination, appContext);
}

View File

@ -37,16 +37,15 @@ public class UnencryptedEmailPacketTest {
public void setUp() throws Exception {
String message = "This is a test message. Test 1 2 3 Test";
byte[] content = message.getBytes();
UniqueId deletionKeyPlain = new UniqueId(new byte[] {72, -18, -72, -39, 122, 40, -104, -66, -54, -61, -108, 72, 54, 30, 37, 76, 44, 86, 104, -124, -31, 32, -82, -27, 26, 76, 7, 106, -76, 72, 49, -44}, 0);
UniqueId deletionKeyVerify = new UniqueId(new byte[] {-62, -112, 99, -65, 13, 44, -117, -111, 96, 45, -6, 64, 78, 57, 117, 103, -24, 101, 106, -116, -18, 62, 99, -49, 60, -81, 8, 64, 27, -41, -104, 58}, 0);
byte[] messageIdBytes = new byte[] {2, -69, -24, -109, 1, 69, -122, -69, 113, -68, -90, 55, -28, 105, 97, 125, 70, 51, 58, 14, 2, -13, -53, 90, -29, 36, 67, 36, -94, -108, -125, 11, 123};
byte[] messageIdBytes = new byte[] {-69, -24, -109, 1, 69, -122, -69, 113, -68, -90, 55, -28, 105, 97, 125, 70, 51, 58, 14, 2, -13, -53, 90, -29, 36, 67, 36, -94, -108, -125, 11, 123};
UniqueId messageId = new UniqueId(messageIdBytes, 0);
int fragmentIndex = 0;
int numFragments = 1;
packet = new UnencryptedEmailPacket(deletionKeyPlain, deletionKeyVerify, messageId, fragmentIndex, numFragments, content);
packet = new UnencryptedEmailPacket(messageId, fragmentIndex, numFragments, content, deletionKeyVerify);
}
@Test

View File

@ -46,13 +46,12 @@ public class StoreRequestTest {
@Before
public void setUp() throws Exception {
byte[] emailContent = new byte[] {2, 109, -80, -37, -106, 83, -33, -39, -94, -76, -112, -98, 99, 25, -61, 44, -92, -85, 1, 10, -128, -2, -27, -86, -126, -33, -11, 42, 56, 3, -97, -101, 111, 7, -96, 25, 121, 74, 89, -40, -33, 82, -50, -18, 49, 106, 13, -121, 53, -83, -2, 35, -7, 71, -71, 26, 90, 1};
UniqueId deletionKeyPlain = new UniqueId(new byte[] {72, -18, -72, -39, 122, 40, -104, -66, -54, -61, -108, 72, 54, 30, 37, 76, 44, 86, 104, -124, -31, 32, -82, -27, 26, 76, 7, 106, -76, 72, 49, -44}, 0);
UniqueId deletionKeyEncrypted = new UniqueId(new byte[] {-62, -112, 99, -65, 13, 44, -117, -111, 96, 45, -6, 64, 78, 57, 117, 103, -24, 101, 106, -116, -18, 62, 99, -49, 60, -81, 8, 64, 27, -41, -104, 58}, 0);
UniqueId messageId = new UniqueId(new byte[] {2, -69, -24, -109, 1, 69, -122, -69, 113, -68, -90, 55, -28, 105, 97, 125, 70, 51, 58, 14, 2, -13, -53, 90, -29, 36, 67, 36, -94, -108, -125, 11, 123}, 0);
UniqueId deletionKeyVerify = new UniqueId(new byte[] {-62, -112, 99, -65, 13, 44, -117, -111, 96, 45, -6, 64, 78, 57, 117, 103, -24, 101, 106, -116, -18, 62, 99, -49, 60, -81, 8, 64, 27, -41, -104, 58}, 0);
UniqueId messageId = new UniqueId(new byte[] {-69, -24, -109, 1, 69, -122, -69, 113, -68, -90, 55, -28, 105, 97, 125, 70, 51, 58, 14, 2, -13, -53, 90, -29, 36, 67, 36, -94, -108, -125, 11, 123}, 0);
String base64Identity = "piYT1uJ3O8~bBPZmTvehMbp3-Zksg5enhvIlp2X8txqL25l0WdQMWwyt30UAOVQqxGdnMPTqqjh~-zoa~rCQORo~J1gRxLwCX9LlHQqaIimJilrbN-rhKy4Xlft054wbgQjLSC-WICE4W64KDfitwRzdr7lV6lz~0KFiZ8erZ-~WPMG1CgWEku9lILQUdUHyFBguPcK9oPDq7oGBuFGy8w0CvAq7ex3nmbL7zQVA~VqILtOGeGK2fidCuuofj4AQsTcXmH9O0nxZGCIJBhf~4EWmazvxu8XVB8pabNQvRDbmFu6q85JTwmxC45lCjqNw30hp8q2zoqP-zchjWOrxFUhSumpBdD0xXJR~qmhejh4WnuRnnam9j3fcxH5i~T7xWgmvIbpZEI4kyc9VEbXbLI7k-bU2A6sdP-AGt5~TjGLcxpdsPnOLRXO-Dsi7E9-3Kc84s4TmdpEJdtHn1dxYyeeT-ysVOqXjv5w5Cuk0XJpUIJG8n7aXHpNb-QLxPD3yAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADWF3qnAX-p41Po~VNmOUzS-Yt~noD8-e~L3P5rZXBWf-XtB4hkloo6m1jwqphEdf1";
EmailIdentity identity = new EmailIdentity(base64Identity);
EmailDestination destination = new EmailDestination(identity.getKey());
UnencryptedEmailPacket emailPacket = new UnencryptedEmailPacket(deletionKeyPlain, deletionKeyEncrypted, messageId, 0, 1, emailContent);
UnencryptedEmailPacket emailPacket = new UnencryptedEmailPacket(messageId, 0, 1, emailContent, deletionKeyVerify);
EncryptedEmailPacket encryptedPacket = new EncryptedEmailPacket(emailPacket, destination, appContext);
HashCash hashCash = HashCash.mintCash("1234", 1);