forked from I2P_Developers/i2p.i2p
Hide BEP 48 padding directory from UI Check for and reject BEP 52 info multihashes for now Use cached fai.isDirectory for efficiency Use storage.getFileCount() instead of meta.getFiles() to prep for padding files Add notes for padding file TODOs
296 lines
8.1 KiB
Java
296 lines
8.1 KiB
Java
/* Message - A protocol message which can be send through a DataOutputStream.
|
|
Copyright (C) 2003 Mark J. Wielaard
|
|
|
|
This file is part of Snark.
|
|
|
|
This program 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 2, or (at your option)
|
|
any later version.
|
|
|
|
This program 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 this program; if not, write to the Free Software Foundation,
|
|
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
package org.klomp.snark;
|
|
|
|
import java.io.DataOutputStream;
|
|
import java.io.IOException;
|
|
|
|
import net.i2p.data.ByteArray;
|
|
import net.i2p.util.ByteCache;
|
|
|
|
/**
|
|
* Used to queue outgoing connections
|
|
* sendMessage() should be used to translate them to wire format.
|
|
*/
|
|
class Message
|
|
{
|
|
final static byte KEEP_ALIVE = -1;
|
|
final static byte CHOKE = 0;
|
|
final static byte UNCHOKE = 1;
|
|
final static byte INTERESTED = 2;
|
|
final static byte UNINTERESTED = 3;
|
|
final static byte HAVE = 4;
|
|
final static byte BITFIELD = 5;
|
|
final static byte REQUEST = 6;
|
|
final static byte PIECE = 7;
|
|
final static byte CANCEL = 8;
|
|
final static byte PORT = 9; // DHT (BEP 5)
|
|
final static byte SUGGEST = 13; // Fast (BEP 6)
|
|
final static byte HAVE_ALL = 14; // Fast (BEP 6)
|
|
final static byte HAVE_NONE = 15; // Fast (BEP 6)
|
|
final static byte REJECT = 16; // Fast (BEP 6)
|
|
final static byte ALLOWED_FAST = 17; // Fast (BEP 6)
|
|
final static byte EXTENSION = 20; // BEP 10
|
|
final static byte HASH_REQUEST = 21; // BEP 52
|
|
final static byte HASHES = 22; // BEP 52
|
|
final static byte HASH_REJECT = 23; // BEP 52
|
|
|
|
// Not all fields are used for every message.
|
|
// KEEP_ALIVE doesn't have a real wire representation
|
|
final byte type;
|
|
|
|
// Used for HAVE, REQUEST, PIECE and CANCEL messages.
|
|
// Also SUGGEST, REJECT, ALLOWED_FAST
|
|
// low byte used for EXTENSION message
|
|
// low two bytes used for PORT message
|
|
final int piece;
|
|
|
|
// Used for REQUEST, PIECE and CANCEL messages.
|
|
// Also REJECT
|
|
final int begin;
|
|
final int length;
|
|
|
|
// Used for PIECE and BITFIELD and EXTENSION messages
|
|
byte[] data;
|
|
final int off;
|
|
final int len;
|
|
|
|
// Used to do deferred fetch of data
|
|
private final DataLoader dataLoader;
|
|
|
|
// now unused
|
|
//SimpleTimer.TimedEvent expireEvent;
|
|
|
|
private static final int BUFSIZE = PeerState.PARTSIZE;
|
|
private static final ByteCache _cache = ByteCache.getInstance(16, BUFSIZE);
|
|
|
|
/**
|
|
* For types KEEP_ALIVE, CHOKE, UNCHOKE, INTERESTED, UNINTERESTED, HAVE_ALL, HAVE_NONE
|
|
* @since 0.9.32
|
|
*/
|
|
Message(byte type) {
|
|
this(type, 0, 0, 0, null, 0, 0, null);
|
|
}
|
|
|
|
/**
|
|
* For types HAVE, PORT, SUGGEST, ALLOWED_FAST
|
|
* @since 0.9.32
|
|
*/
|
|
Message(byte type, int piece) {
|
|
this(type, piece, 0, 0, null, 0, 0, null);
|
|
}
|
|
|
|
/**
|
|
* For types REQUEST, REJECT, CANCEL
|
|
* @since 0.9.32
|
|
*/
|
|
Message(byte type, int piece, int begin, int length) {
|
|
this(type, piece, begin, length, null, 0, 0, null);
|
|
}
|
|
|
|
/**
|
|
* For type BITFIELD
|
|
* @since 0.9.32
|
|
*/
|
|
Message(byte[] data) {
|
|
this(BITFIELD, 0, 0, 0, data, 0, data.length, null);
|
|
}
|
|
|
|
/**
|
|
* For type EXTENSION
|
|
* @since 0.9.32
|
|
*/
|
|
Message(int id, byte[] data) {
|
|
this(EXTENSION, id, 0, 0, data, 0, data.length, null);
|
|
}
|
|
|
|
/**
|
|
* For type PIECE with deferred data
|
|
* @since 0.9.32
|
|
*/
|
|
Message(int piece, int begin, int length, DataLoader loader) {
|
|
this(PIECE, piece, begin, length, null, 0, length, loader);
|
|
}
|
|
|
|
/**
|
|
* For type PIECE with data
|
|
* We don't use this anymore.
|
|
* @since 0.9.32
|
|
*/
|
|
/****
|
|
Message(int piece, int begin, int length, byte[] data) {
|
|
this(PIECE, piece, begin, length, data, 0, length, null);
|
|
}
|
|
****/
|
|
|
|
/**
|
|
* @since 0.9.32
|
|
*/
|
|
private Message(byte type, int piece, int begin, int length, byte[] data, int off, int len, DataLoader loader) {
|
|
this.type = type;
|
|
this.piece = piece;
|
|
this.begin = begin;
|
|
this.length = length;
|
|
this.data = data;
|
|
this.off = off;
|
|
this.len = len;
|
|
dataLoader = loader;
|
|
}
|
|
|
|
/** Utility method for sending a message through a DataStream. */
|
|
void sendMessage(DataOutputStream dos) throws IOException
|
|
{
|
|
// KEEP_ALIVE is special.
|
|
if (type == KEEP_ALIVE)
|
|
{
|
|
dos.writeInt(0);
|
|
return;
|
|
}
|
|
|
|
ByteArray ba;
|
|
// Get deferred data
|
|
if (data == null && dataLoader != null) {
|
|
ba = dataLoader.loadData(piece, begin, length);
|
|
if (ba == null)
|
|
return; // hmm will get retried, but shouldn't happen
|
|
data = ba.getData();
|
|
} else {
|
|
ba = null;
|
|
}
|
|
|
|
// Calculate the total length in bytes
|
|
|
|
// Type is one byte.
|
|
int datalen = 1;
|
|
|
|
// piece is 4 bytes.
|
|
if (type == HAVE || type == REQUEST || type == PIECE || type == CANCEL ||
|
|
type == SUGGEST || type == REJECT || type == ALLOWED_FAST)
|
|
datalen += 4;
|
|
|
|
// begin/offset is 4 bytes
|
|
if (type == REQUEST || type == PIECE || type == CANCEL ||
|
|
type == REJECT)
|
|
datalen += 4;
|
|
|
|
// length is 4 bytes
|
|
if (type == REQUEST || type == CANCEL ||
|
|
type == REJECT)
|
|
datalen += 4;
|
|
|
|
// msg type is 1 byte
|
|
else if (type == EXTENSION)
|
|
datalen += 1;
|
|
|
|
else if (type == PORT)
|
|
datalen += 2;
|
|
|
|
// add length of data for piece or bitfield array.
|
|
if (type == BITFIELD || type == PIECE || type == EXTENSION)
|
|
datalen += len;
|
|
|
|
// Send length
|
|
dos.writeInt(datalen);
|
|
dos.writeByte(type & 0xFF);
|
|
|
|
// Send additional info (piece number)
|
|
if (type == HAVE || type == REQUEST || type == PIECE || type == CANCEL ||
|
|
type == SUGGEST || type == REJECT || type == ALLOWED_FAST)
|
|
dos.writeInt(piece);
|
|
|
|
// Send additional info (begin/offset)
|
|
if (type == REQUEST || type == PIECE || type == CANCEL ||
|
|
type == REJECT)
|
|
dos.writeInt(begin);
|
|
|
|
// Send additional info (length); for PIECE this is implicit.
|
|
if (type == REQUEST || type == CANCEL ||
|
|
type == REJECT)
|
|
dos.writeInt(length);
|
|
|
|
else if (type == EXTENSION)
|
|
dos.writeByte((byte) piece & 0xff);
|
|
|
|
else if (type == PORT)
|
|
dos.writeShort(piece & 0xffff);
|
|
|
|
// Send actual data
|
|
if (type == BITFIELD || type == PIECE || type == EXTENSION)
|
|
dos.write(data, off, len);
|
|
|
|
// Was pulled from cache in Storage.getPiece() via dataLoader
|
|
if (ba != null && ba.getData().length == BUFSIZE)
|
|
_cache.release(ba, false);
|
|
}
|
|
|
|
@Override
|
|
public String toString()
|
|
{
|
|
switch (type)
|
|
{
|
|
case KEEP_ALIVE:
|
|
return "KEEP_ALIVE";
|
|
case CHOKE:
|
|
return "CHOKE";
|
|
case UNCHOKE:
|
|
return "UNCHOKE";
|
|
case INTERESTED:
|
|
return "INTERESTED";
|
|
case UNINTERESTED:
|
|
return "UNINTERESTED";
|
|
case HAVE:
|
|
return "HAVE(" + piece + ')';
|
|
case BITFIELD:
|
|
return "BITFIELD";
|
|
case REQUEST:
|
|
return "REQUEST(" + piece + ',' + begin + ',' + length + ')';
|
|
case PIECE:
|
|
return "PIECE(" + piece + ',' + begin + ',' + length + ')';
|
|
case CANCEL:
|
|
return "CANCEL(" + piece + ',' + begin + ',' + length + ')';
|
|
case PORT:
|
|
return "PORT(" + piece + ')';
|
|
case EXTENSION:
|
|
return "EXTENSION(" + piece + ',' + data.length + ')';
|
|
// fast extensions below here
|
|
case SUGGEST:
|
|
return "SUGGEST(" + piece + ')';
|
|
case HAVE_ALL:
|
|
return "HAVE_ALL";
|
|
case HAVE_NONE:
|
|
return "HAVE_NONE";
|
|
case REJECT:
|
|
return "REJECT(" + piece + ',' + begin + ',' + length + ')';
|
|
case ALLOWED_FAST:
|
|
return "ALLOWED_FAST(" + piece + ')';
|
|
// BEP 52 below here
|
|
case HASH_REQUEST:
|
|
return "HASH_REQUEST";
|
|
case HASHES:
|
|
return "HASHES";
|
|
case HASH_REJECT:
|
|
return "HASH_REJECT";
|
|
default:
|
|
return "UNKNOWN (" + type + ')';
|
|
}
|
|
}
|
|
}
|