Author: toad
Date: 2006-01-18 16:38:29 +0000 (Wed, 18 Jan 2006)
New Revision: 7873

Added:
   trunk/freenet/src/freenet/node/fcp/
   trunk/freenet/src/freenet/node/fcp/AllDataMessage.java
   trunk/freenet/src/freenet/node/fcp/ClientGet.java
   trunk/freenet/src/freenet/node/fcp/ClientGetMessage.java
   trunk/freenet/src/freenet/node/fcp/ClientHelloMessage.java
   trunk/freenet/src/freenet/node/fcp/ClientRequest.java
   trunk/freenet/src/freenet/node/fcp/DataCarryingMessage.java
   trunk/freenet/src/freenet/node/fcp/DataFoundMessage.java
   trunk/freenet/src/freenet/node/fcp/FCPConnectionHandler.java
   trunk/freenet/src/freenet/node/fcp/FCPConnectionInputHandler.java
   trunk/freenet/src/freenet/node/fcp/FCPConnectionOutputHandler.java
   trunk/freenet/src/freenet/node/fcp/FCPMessage.java
   trunk/freenet/src/freenet/node/fcp/FCPServer.java
   trunk/freenet/src/freenet/node/fcp/FetchErrorMessage.java
   trunk/freenet/src/freenet/node/fcp/IdentifierCollisionMessage.java
   trunk/freenet/src/freenet/node/fcp/MessageInvalidException.java
   trunk/freenet/src/freenet/node/fcp/NodeHelloMessage.java
   trunk/freenet/src/freenet/node/fcp/ProtocolErrorMessage.java
   trunk/freenet/src/freenet/support/io/LineReader.java
Modified:
   trunk/freenet/src/freenet/client/BlockFetcher.java
   trunk/freenet/src/freenet/client/FailureCodeTracker.java
   trunk/freenet/src/freenet/client/FetchException.java
   trunk/freenet/src/freenet/client/Fetcher.java
   trunk/freenet/src/freenet/client/FetcherContext.java
   trunk/freenet/src/freenet/client/HighLevelSimpleClient.java
   trunk/freenet/src/freenet/client/HighLevelSimpleClientImpl.java
   trunk/freenet/src/freenet/client/Segment.java
   trunk/freenet/src/freenet/client/SplitFetcher.java
   trunk/freenet/src/freenet/node/Node.java
   trunk/freenet/src/freenet/node/QueuedDataRequest.java
   trunk/freenet/src/freenet/node/QueueingSimpleLowLevelClient.java
   trunk/freenet/src/freenet/node/RealNodeRequestInsertTest.java
   trunk/freenet/src/freenet/node/RequestHandler.java
   trunk/freenet/src/freenet/node/RequestStarterClient.java
   trunk/freenet/src/freenet/node/SimpleLowLevelClient.java
   trunk/freenet/src/freenet/node/TextModeClientInterface.java
   trunk/freenet/src/freenet/node/Version.java
   trunk/freenet/src/freenet/support/BucketTools.java
   trunk/freenet/src/freenet/support/SimpleFieldSet.java
   trunk/freenet/src/freenet/support/io/LineReadingInputStream.java
Log:
361: Basic FCP support (request only). Also lots of bug fixes.

Modified: trunk/freenet/src/freenet/client/BlockFetcher.java
===================================================================
--- trunk/freenet/src/freenet/client/BlockFetcher.java  2006-01-18 14:35:15 UTC 
(rev 7872)
+++ trunk/freenet/src/freenet/client/BlockFetcher.java  2006-01-18 16:38:29 UTC 
(rev 7873)
@@ -60,37 +60,11 @@
                        fatalError(e, FetchException.INVALID_METADATA);
                } catch (FetchException e) {
                        int code = e.getMode();
-                       switch(code) {
-                       case FetchException.ARCHIVE_FAILURE:
-                       case FetchException.BLOCK_DECODE_ERROR:
-                       case FetchException.HAS_MORE_METASTRINGS:
-                       case FetchException.INVALID_METADATA:
-                       case FetchException.NOT_IN_ARCHIVE:
-                       case FetchException.TOO_DEEP_ARCHIVE_RECURSION:
-                       case FetchException.TOO_MANY_ARCHIVE_RESTARTS:
-                       case FetchException.TOO_MANY_METADATA_LEVELS:
-                       case FetchException.TOO_MANY_REDIRECTS:
-                       case FetchException.TOO_MUCH_RECURSION:
-                       case FetchException.UNKNOWN_METADATA:
-                       case FetchException.UNKNOWN_SPLITFILE_METADATA:
-                               // Fatal, probably an error on insert
+                       boolean isFatal = e.isFatal();
+                       if(isFatal)
                                fatalError(e, code);
-                               return;
-                       
-                       case FetchException.DATA_NOT_FOUND:
-                       case FetchException.ROUTE_NOT_FOUND:
-                       case FetchException.REJECTED_OVERLOAD:
-                       case FetchException.TRANSFER_FAILED:
-                               // Non-fatal
+                       else
                                nonfatalError(e, code);
-                               return;
-                               
-                       case FetchException.BUCKET_ERROR:
-                       case FetchException.INTERNAL_ERROR:
-                               // Maybe fatal
-                               nonfatalError(e, code);
-                               return;
-                       }
                } catch (ArchiveFailureException e) {
                        fatalError(e, FetchException.ARCHIVE_FAILURE);
                } catch (ArchiveRestartException e) {

Modified: trunk/freenet/src/freenet/client/FailureCodeTracker.java
===================================================================
--- trunk/freenet/src/freenet/client/FailureCodeTracker.java    2006-01-18 
14:35:15 UTC (rev 7872)
+++ trunk/freenet/src/freenet/client/FailureCodeTracker.java    2006-01-18 
16:38:29 UTC (rev 7873)
@@ -4,6 +4,8 @@
 import java.util.HashMap;
 import java.util.Iterator;

+import freenet.support.SimpleFieldSet;
+
 /**
  * Essentially a map of integer to incrementible integer.
  * FIXME maybe move this to support, give it a better name?
@@ -11,6 +13,7 @@
 public class FailureCodeTracker {

        public final boolean insert;
+       private int total;

        public FailureCodeTracker(boolean insert) {
                this.insert = insert;
@@ -28,6 +31,7 @@
                if(i == null)
                        map.put(key, i = new Item());
                i.x++;
+               total++;
        }

        public synchronized void inc(Integer key, int val) {
@@ -35,6 +39,7 @@
                if(i == null)
                        map.put(key, i = new Item());
                i.x+=val;
+               total += val;
        }

        public synchronized String toVerboseString() {
@@ -53,14 +58,53 @@
                return sb.toString();
        }

-       public synchronized FailureCodeTracker merge(FailureCodeTracker 
accumulatedFatalErrorCodes) {
-               Iterator keys = map.keySet().iterator();
+       /**
+        * Merge codes from another tracker into this one.
+        */
+       public synchronized FailureCodeTracker merge(FailureCodeTracker source) 
{
+               Iterator keys = source.map.keySet().iterator();
                while(keys.hasNext()) {
                        Integer k = (Integer) keys.next();
-                       Item item = (Item) map.get(k);
+                       Item item = (Item) source.map.get(k);
                        inc(k, item.x);
                }
                return this;
        }
+
+       public void merge(FetchException e) {
+               if(insert) throw new IllegalStateException("Merging a 
FetchException in an insert!");
+               if(e.errorCodes != null) {
+                       merge(e.errorCodes);
+               }
+               // Increment mode anyway, so we get the splitfile error as well.
+               inc(e.mode);
+       }
+
+       public synchronized int totalCount() {
+               return total;
+       }
+
+       /** Copy verbosely to a SimpleFieldSet */
+       public synchronized void copyToFieldSet(SimpleFieldSet sfs, String 
prefix) {
+               Iterator keys = map.keySet().iterator();
+               while(keys.hasNext()) {
+                       Integer k = (Integer) keys.next();
+                       Item item = (Item) map.get(k);
+                       int code = k.intValue();
+                       // prefix.num.Description=<code description>
+                       // prefix.num.Count=<count>
+                       
sfs.put(prefix+Integer.toHexString(code)+".Description", 
+                                       insert ? 
InserterException.getMessage(code) : FetchException.getMessage(code));
+                       sfs.put(prefix+Integer.toHexString(code)+".Count", 
Integer.toHexString(item.x));
+               }
+       }
+
+       public synchronized boolean isOneCodeOnly() {
+               return map.size() == 1;
+       }
+
+       public synchronized int getFirstCode() {
+               return ((Integer) map.keySet().toArray()[0]).intValue();
+       }

 }

Modified: trunk/freenet/src/freenet/client/FetchException.java
===================================================================
--- trunk/freenet/src/freenet/client/FetchException.java        2006-01-18 
14:35:15 UTC (rev 7872)
+++ trunk/freenet/src/freenet/client/FetchException.java        2006-01-18 
16:38:29 UTC (rev 7873)
@@ -1,7 +1,5 @@
 package freenet.client;

-import java.io.IOException;
-
 import freenet.support.Logger;

 /**
@@ -17,6 +15,8 @@
        /** For collection errors */
        public final FailureCodeTracker errorCodes;

+       public final String extraMessage;
+       
        /** Get the failure mode. */
        public int getMode() {
                return mode;
@@ -24,6 +24,7 @@

        public FetchException(int m) {
                super(getMessage(m));
+               extraMessage = null;
                mode = m;
                errorCodes = null;
                Logger.minor(this, "FetchException("+getMessage(mode)+")", 
this);
@@ -31,6 +32,7 @@

        public FetchException(MetadataParseException e) {
                super(getMessage(INVALID_METADATA)+": "+e.getMessage());
+               extraMessage = e.getMessage();
                mode = INVALID_METADATA;
                errorCodes = null;
                initCause(e);
@@ -39,6 +41,7 @@

        public FetchException(ArchiveFailureException e) {
                super(getMessage(INVALID_METADATA)+": "+e.getMessage());
+               extraMessage = e.getMessage();
                mode = ARCHIVE_FAILURE;
                errorCodes = null;
                initCause(e);
@@ -47,6 +50,7 @@

        public FetchException(int mode, Throwable t) {
                super(getMessage(mode)+": "+t.getMessage());
+               extraMessage = t.getMessage();
                this.mode = mode;
                errorCodes = null;
                initCause(t);
@@ -55,6 +59,7 @@

        public FetchException(int mode, FailureCodeTracker errorCodes) {
                super(getMessage(mode));
+               extraMessage = null;
                this.mode = mode;
                this.errorCodes = errorCodes;
                Logger.minor(this, "FetchException("+getMessage(mode)+")");
@@ -63,6 +68,7 @@

        public FetchException(int mode, String msg) {
                super(getMessage(mode)+": "+msg);
+               extraMessage = msg;
                errorCodes = null;
                this.mode = mode;
                Logger.minor(this, "FetchException("+getMessage(mode)+"): 
"+msg,this);
@@ -118,6 +124,8 @@
                        return "Too many blocks per segment";
                case NOT_ENOUGH_METASTRINGS:
                        return "No default document; give more metastrings in 
URI";
+               case CANCELLED:
+                       return "Cancelled by caller";
                default:
                        return "Unknown fetch error code: "+mode;
                }
@@ -173,4 +181,50 @@
        public static final int TOO_MANY_BLOCKS_PER_SEGMENT = 23;
        /** Not enough meta strings in URI given and no default document */
        public static final int NOT_ENOUGH_METASTRINGS = 24;
+       /** Explicitly cancelled */
+       public static final int CANCELLED = 25;
+
+       /** Is an error fatal i.e. is there no point retrying? */
+       public boolean isFatal() {
+               switch(mode) {
+               // Problems with the data as inserted. No point retrying.
+               case FetchException.ARCHIVE_FAILURE:
+               case FetchException.BLOCK_DECODE_ERROR:
+               case FetchException.HAS_MORE_METASTRINGS:
+               case FetchException.INVALID_METADATA:
+               case FetchException.NOT_IN_ARCHIVE:
+               case FetchException.TOO_DEEP_ARCHIVE_RECURSION:
+               case FetchException.TOO_MANY_ARCHIVE_RESTARTS:
+               case FetchException.TOO_MANY_METADATA_LEVELS:
+               case FetchException.TOO_MANY_REDIRECTS:
+               case FetchException.TOO_MUCH_RECURSION:
+               case FetchException.UNKNOWN_METADATA:
+               case FetchException.UNKNOWN_SPLITFILE_METADATA:
+                       return true;
+
+               // Low level errors, can be retried
+               case FetchException.DATA_NOT_FOUND:
+               case FetchException.ROUTE_NOT_FOUND:
+               case FetchException.REJECTED_OVERLOAD:
+               case FetchException.TRANSFER_FAILED:
+                       return false;
+                       
+               case FetchException.BUCKET_ERROR:
+               case FetchException.INTERNAL_ERROR:
+                       // Maybe fatal
+                       return false;
+                       
+               case FetchException.SPLITFILE_ERROR:
+                       // Fatal, because there are internal retries
+                       return true;
+                       
+               case FetchException.CANCELLED:
+                       // Fatal
+                       return true;
+                       
+               default:
+                       Logger.error(this, "Do not know if error code is fatal: 
"+getMessage(mode));
+                       return false; // assume it isn't
+               }
+       }
 }

Modified: trunk/freenet/src/freenet/client/Fetcher.java
===================================================================
--- trunk/freenet/src/freenet/client/Fetcher.java       2006-01-18 14:35:15 UTC 
(rev 7872)
+++ trunk/freenet/src/freenet/client/Fetcher.java       2006-01-18 16:38:29 UTC 
(rev 7873)
@@ -2,7 +2,6 @@

 import java.io.IOException;
 import java.net.MalformedURLException;
-import java.util.Iterator;
 import java.util.LinkedList;

 import freenet.client.events.DecodedBlockEvent;
@@ -22,7 +21,7 @@
 /** Class that does the actual fetching. Does not have to have a user friendly
  * interface!
  */
-class Fetcher {
+public class Fetcher {

        /** The original URI to be fetched. */
        final FreenetURI origURI;
@@ -37,7 +36,7 @@
        /**
         * Local-only constructor, with ArchiveContext, for recursion via e.g. 
archives.
         */
-       Fetcher(FreenetURI uri, FetcherContext fctx, ArchiveContext actx) {
+       public Fetcher(FreenetURI uri, FetcherContext fctx, ArchiveContext 
actx) {
                if(uri == null) throw new NullPointerException();
                origURI = uri;
                ctx = fctx;
@@ -61,8 +60,32 @@
         * by this driver routine.
         */
        public FetchResult run() throws FetchException {
+               FailureCodeTracker tracker = new FailureCodeTracker(false);
+               FetchException lastThrown = null;
+               for(int j=0;j<ctx.maxNonSplitfileRetries+1;j++) {
+                       try {
+                               return runOnce();
+                       } catch (FetchException e) {
+                               lastThrown = e;
+                               tracker.merge(e);
+                               if(e.isFatal()) throw e;
+                               Logger.normal(this, "Possibly retrying "+this+" 
despite "+e, e);
+                               continue;
+                       }
+               }
+               if(tracker.totalCount() == 1)
+                       throw lastThrown;
+               else {
+                       if(tracker.isOneCodeOnly())
+                               throw new 
FetchException(tracker.getFirstCode());
+                       throw new 
FetchException(FetchException.SPLITFILE_ERROR, tracker);
+               }
+       }
+       
+       public FetchResult runOnce() throws FetchException {
                for(int i=0;i<ctx.maxArchiveRestarts;i++) {
                        try {
+                               if(ctx.isCancelled()) throw new 
FetchException(FetchException.CANCELLED);
                                ClientMetadata dm = new ClientMetadata();
                                ClientKey key;
                                try {
@@ -121,6 +144,7 @@
        FetchResult realRun(ClientMetadata dm, int recursionLevel, ClientKey 
key, LinkedList metaStrings, boolean dontEnterImplicitArchives, boolean 
localOnly) 
        throws FetchException, MetadataParseException, ArchiveFailureException, 
ArchiveRestartException {
                Logger.minor(this, "Running fetch for: "+key);
+               if(ctx.isCancelled()) throw new 
FetchException(FetchException.CANCELLED);
                recursionLevel++;
                if(recursionLevel > ctx.maxRecursionLevel)
                        throw new 
FetchException(FetchException.TOO_MUCH_RECURSION, ""+recursionLevel+" should be 
< "+ctx.maxRecursionLevel);
@@ -128,7 +152,8 @@
                // Do the fetch
                ClientKeyBlock block;
                try {
-                       block = ctx.client.getKey(key, localOnly, 
ctx.starterClient, ctx.cacheLocalRequests);
+                       block = ctx.client.getKey(key, localOnly, 
ctx.starterClient, ctx.cacheLocalRequests, ctx.ignoreStore);
+                       if(ctx.isCancelled()) throw new 
FetchException(FetchException.CANCELLED);
                } catch (LowLevelGetException e) {
                        switch(e.code) {
                        case LowLevelGetException.DATA_NOT_FOUND:
@@ -206,6 +231,7 @@
                        Metadata metadata, ArchiveHandler container, FreenetURI 
thisKey, boolean dontEnterImplicitArchives, boolean localOnly) 
        throws MetadataParseException, FetchException, ArchiveFailureException, 
ArchiveRestartException {

+               if(ctx.isCancelled()) throw new 
FetchException(FetchException.CANCELLED);
                if(metadata.isSimpleManifest()) {
                        String name;
                        if(metaStrings.isEmpty())

Modified: trunk/freenet/src/freenet/client/FetcherContext.java
===================================================================
--- trunk/freenet/src/freenet/client/FetcherContext.java        2006-01-18 
14:35:15 UTC (rev 7872)
+++ trunk/freenet/src/freenet/client/FetcherContext.java        2006-01-18 
16:38:29 UTC (rev 7873)
@@ -9,35 +9,41 @@
 /** Context for a Fetcher. Contains all the settings a Fetcher needs to know 
about. */
 public class FetcherContext implements Cloneable {

+       public static final int IDENTICAL_MASK = 0;
        static final int SPLITFILE_DEFAULT_BLOCK_MASK = 1;
        static final int SPLITFILE_DEFAULT_MASK = 2;
        static final int SPLITFILE_USE_LENGTHS_MASK = 3;
        /** Low-level client to send low-level requests to. */
        final SimpleLowLevelClient client;
-       final long maxOutputLength;
-       final long maxTempLength;
+       public long maxOutputLength;
+       public long maxTempLength;
        final ArchiveManager archiveManager;
-       final BucketFactory bucketFactory;
-       final int maxRecursionLevel;
-       final int maxArchiveRestarts;
-       final boolean dontEnterImplicitArchives;
-       final int maxSplitfileThreads;
-       final int maxSplitfileBlockRetries;
-       final int maxNonSplitfileRetries;
-       final RandomSource random;
-       final boolean allowSplitfiles;
-       final boolean followRedirects;
-       final boolean localRequestOnly;
-       final ClientEventProducer eventProducer;
+       public final BucketFactory bucketFactory;
+       public int maxRecursionLevel;
+       public int maxArchiveRestarts;
+       public boolean dontEnterImplicitArchives;
+       public int maxSplitfileThreads;
+       public int maxSplitfileBlockRetries;
+       public int maxNonSplitfileRetries;
+       public final RandomSource random;
+       public boolean allowSplitfiles;
+       public boolean followRedirects;
+       public boolean localRequestOnly;
+       public boolean ignoreStore;
+       public final ClientEventProducer eventProducer;
        /** Whether to allow non-full blocks, or blocks which are not direct 
CHKs, in splitfiles.
         * Set by the splitfile metadata and the mask constructor, so we don't 
need to pass it in. */
-       final boolean splitfileUseLengths;
-       final int maxMetadataSize;
-       final int maxDataBlocksPerSegment;
-       final int maxCheckBlocksPerSegment;
-       final RequestStarterClient starterClient;
-       final boolean cacheLocalRequests;
+       public boolean splitfileUseLengths;
+       public int maxMetadataSize;
+       public int maxDataBlocksPerSegment;
+       public int maxCheckBlocksPerSegment;
+       public final RequestStarterClient starterClient;
+       public boolean cacheLocalRequests;
+       private boolean cancelled;

+       public final boolean isCancelled() {
+               return cancelled;
+       }

        public FetcherContext(SimpleLowLevelClient client, long curMaxLength, 
                        long curMaxTempLength, int maxMetadataSize, int 
maxRecursionLevel, int maxArchiveRestarts,
@@ -72,13 +78,36 @@
        }

        public FetcherContext(FetcherContext ctx, int maskID) {
-               if(maskID == SPLITFILE_DEFAULT_BLOCK_MASK) {
+               if(maskID == IDENTICAL_MASK) {
                        this.client = ctx.client;
                        this.maxOutputLength = ctx.maxOutputLength;
                        this.maxMetadataSize = ctx.maxMetadataSize;
                        this.maxTempLength = ctx.maxTempLength;
                        this.archiveManager = ctx.archiveManager;
                        this.bucketFactory = ctx.bucketFactory;
+                       this.maxRecursionLevel = ctx.maxRecursionLevel;
+                       this.maxArchiveRestarts = ctx.maxArchiveRestarts;
+                       this.dontEnterImplicitArchives = 
ctx.dontEnterImplicitArchives;
+                       this.random = ctx.random;
+                       this.maxSplitfileThreads = ctx.maxSplitfileThreads;
+                       this.maxSplitfileBlockRetries = 
ctx.maxSplitfileBlockRetries;
+                       this.maxNonSplitfileRetries = 
ctx.maxNonSplitfileRetries;
+                       this.allowSplitfiles = ctx.allowSplitfiles;
+                       this.followRedirects = ctx.followRedirects;
+                       this.localRequestOnly = ctx.localRequestOnly;
+                       this.splitfileUseLengths = ctx.splitfileUseLengths;
+                       this.eventProducer = ctx.eventProducer;
+                       this.maxDataBlocksPerSegment = 
ctx.maxDataBlocksPerSegment;
+                       this.maxCheckBlocksPerSegment = 
ctx.maxCheckBlocksPerSegment;
+                       this.starterClient = ctx.starterClient;
+                       this.cacheLocalRequests = ctx.cacheLocalRequests;
+               } else if(maskID == SPLITFILE_DEFAULT_BLOCK_MASK) {
+                       this.client = ctx.client;
+                       this.maxOutputLength = ctx.maxOutputLength;
+                       this.maxMetadataSize = ctx.maxMetadataSize;
+                       this.maxTempLength = ctx.maxTempLength;
+                       this.archiveManager = ctx.archiveManager;
+                       this.bucketFactory = ctx.bucketFactory;
                        this.maxRecursionLevel = 1;
                        this.maxArchiveRestarts = 0;
                        this.dontEnterImplicitArchives = true;
@@ -144,6 +173,10 @@
                } else throw new IllegalArgumentException();
        }

+       public void cancel() {
+               this.cancelled = true;
+       }
+       
        /** Make public, but just call parent for a field for field copy */
        public Object clone() {
                try {

Modified: trunk/freenet/src/freenet/client/HighLevelSimpleClient.java
===================================================================
--- trunk/freenet/src/freenet/client/HighLevelSimpleClient.java 2006-01-18 
14:35:15 UTC (rev 7872)
+++ trunk/freenet/src/freenet/client/HighLevelSimpleClient.java 2006-01-18 
16:38:29 UTC (rev 7873)
@@ -40,6 +40,8 @@
         */
        public FreenetURI insertManifest(FreenetURI insertURI, HashMap 
bucketsByName, String defaultName) throws InserterException;

+       public FetcherContext getFetcherContext();
+       
        /**
         * Add a ClientEventListener.
         */

Modified: trunk/freenet/src/freenet/client/HighLevelSimpleClientImpl.java
===================================================================
--- trunk/freenet/src/freenet/client/HighLevelSimpleClientImpl.java     
2006-01-18 14:35:15 UTC (rev 7872)
+++ trunk/freenet/src/freenet/client/HighLevelSimpleClientImpl.java     
2006-01-18 16:38:29 UTC (rev 7873)
@@ -95,12 +95,7 @@
         */
        public FetchResult fetch(FreenetURI uri) throws FetchException {
                if(uri == null) throw new NullPointerException();
-               FetcherContext context = new FetcherContext(client, 
curMaxLength, curMaxTempLength, curMaxMetadataLength, 
-                               MAX_RECURSION, MAX_ARCHIVE_RESTARTS, 
DONT_ENTER_IMPLICIT_ARCHIVES, 
-                               SPLITFILE_THREADS, SPLITFILE_BLOCK_RETRIES, 
NON_SPLITFILE_RETRIES,
-                               FETCH_SPLITFILES, FOLLOW_REDIRECTS, 
LOCAL_REQUESTS_ONLY,
-                               MAX_SPLITFILE_BLOCKS_PER_SEGMENT, 
MAX_SPLITFILE_CHECK_BLOCKS_PER_SEGMENT,
-                               random, archiveManager, bucketFactory, 
globalEventProducer, requestStarter, cacheLocalRequests);
+               FetcherContext context = getFetcherContext();
                Fetcher f = new Fetcher(uri, context);
                return f.run();
        }
@@ -146,4 +141,14 @@
        public void addGlobalHook(ClientEventListener listener) {
                globalEventProducer.addEventListener(listener);
        }
+
+       public FetcherContext getFetcherContext() {
+               return                  
+                       new FetcherContext(client, curMaxLength, 
curMaxTempLength, curMaxMetadataLength, 
+                               MAX_RECURSION, MAX_ARCHIVE_RESTARTS, 
DONT_ENTER_IMPLICIT_ARCHIVES, 
+                               SPLITFILE_THREADS, SPLITFILE_BLOCK_RETRIES, 
NON_SPLITFILE_RETRIES,
+                               FETCH_SPLITFILES, FOLLOW_REDIRECTS, 
LOCAL_REQUESTS_ONLY,
+                               MAX_SPLITFILE_BLOCKS_PER_SEGMENT, 
MAX_SPLITFILE_CHECK_BLOCKS_PER_SEGMENT,
+                               random, archiveManager, bucketFactory, 
globalEventProducer, requestStarter, cacheLocalRequests);
+       }
 }

Modified: trunk/freenet/src/freenet/client/Segment.java
===================================================================
--- trunk/freenet/src/freenet/client/Segment.java       2006-01-18 14:35:15 UTC 
(rev 7872)
+++ trunk/freenet/src/freenet/client/Segment.java       2006-01-18 16:38:29 UTC 
(rev 7873)
@@ -261,6 +261,12 @@
        }

        public void onProgress() {
+               if(fetcherContext.isCancelled()) {
+                       finished = true;
+                       tracker.kill();
+                       failureException = new 
FetchException(FetchException.CANCELLED);
+                       parentFetcher.gotBlocks(this);
+               }
                parentFetcher.onProgress();
        }


Modified: trunk/freenet/src/freenet/client/SplitFetcher.java
===================================================================
--- trunk/freenet/src/freenet/client/SplitFetcher.java  2006-01-18 14:35:15 UTC 
(rev 7872)
+++ trunk/freenet/src/freenet/client/SplitFetcher.java  2006-01-18 16:38:29 UTC 
(rev 7873)
@@ -61,6 +61,7 @@
        public SplitFetcher(Metadata metadata, ArchiveContext archiveContext, 
FetcherContext ctx, int recursionLevel) throws MetadataParseException, 
FetchException {
                actx = archiveContext;
                fctx = ctx;
+               if(fctx.isCancelled()) throw new 
FetchException(FetchException.CANCELLED);
                overrideLength = metadata.dataLength;
                this.maxTempLength = ctx.maxTempLength;
                splitfileType = metadata.getSplitfileType();
@@ -137,6 +138,7 @@
                 */
                while(true) {
                        synchronized(this) {
+                               if(fctx.isCancelled()) throw new 
FetchException(FetchException.CANCELLED);
                                if(fetchingSegment == null) {
                                        // Pick a random segment
                                        fetchingSegment = 
chooseUnstartedSegment();

Modified: trunk/freenet/src/freenet/node/Node.java
===================================================================
--- trunk/freenet/src/freenet/node/Node.java    2006-01-18 14:35:15 UTC (rev 
7872)
+++ trunk/freenet/src/freenet/node/Node.java    2006-01-18 16:38:29 UTC (rev 
7873)
@@ -57,6 +57,7 @@
 import freenet.keys.NodeSSK;
 import freenet.keys.SSKBlock;
 import freenet.keys.SSKVerifyException;
+import freenet.node.fcp.FCPServer;
 import freenet.store.BerkeleyDBFreenetStore;
 import freenet.store.FreenetStore;
 import freenet.support.BucketFactory;
@@ -181,7 +182,7 @@

     // Client stuff
     final ArchiveManager archiveManager;
-    final BucketFactory tempBucketFactory;
+    public final BucketFactory tempBucketFactory;
     final RequestThrottle requestThrottle;
     final RequestStarter requestStarter;
     final RequestThrottle insertThrottle;
@@ -331,10 +332,12 @@
         Thread t = new Thread(new MemoryChecker(), "Memory checker");
         t.setPriority(Thread.MAX_PRIORITY);
         t.start();
-        SimpleToadletServer server = new SimpleToadletServer(port+2001);
+        SimpleToadletServer server = new SimpleToadletServer(port+2000);
         FproxyToadlet fproxy = new 
FproxyToadlet(n.makeClient(RequestStarter.INTERACTIVE_PRIORITY_CLASS, 
(short)0));
         server.register(fproxy, "/", false);
-        System.out.println("Starting fproxy on port "+(port+2001));
+        System.out.println("Starting fproxy on port "+(port+2000));
+        new FCPServer(port+3000, n);
+        System.out.println("Starting FCP server on port "+(port+3000));
         //server.register(fproxy, "/SSK@", false);
         //server.register(fproxy, "/KSK@", false);
     }
@@ -466,23 +469,23 @@
         usm.start();
     }

-    public ClientKeyBlock getKey(ClientKey key, boolean localOnly, 
RequestStarterClient client, boolean cache) throws LowLevelGetException {
+    public ClientKeyBlock getKey(ClientKey key, boolean localOnly, 
RequestStarterClient client, boolean cache, boolean ignoreStore) throws 
LowLevelGetException {
        if(key instanceof ClientSSK) {
                ClientSSK k = (ClientSSK) key;
                if(k.getPubKey() != null)
                        cacheKey(k.pubKeyHash, k.getPubKey());
        }
        if(localOnly)
-               return realGetKey(key, localOnly, cache);
+               return realGetKey(key, localOnly, cache, ignoreStore);
        else
-               return client.getKey(key, localOnly, cache);
+               return client.getKey(key, localOnly, cache, ignoreStore);
     }

-    public ClientKeyBlock realGetKey(ClientKey key, boolean localOnly, boolean 
cache) throws LowLevelGetException {
+    public ClientKeyBlock realGetKey(ClientKey key, boolean localOnly, boolean 
cache, boolean ignoreStore) throws LowLevelGetException {
        if(key instanceof ClientCHK)
-               return realGetCHK((ClientCHK)key, localOnly, cache);
+               return realGetCHK((ClientCHK)key, localOnly, cache, 
ignoreStore);
        else if(key instanceof ClientSSK)
-               return realGetSSK((ClientSSK)key, localOnly, cache);
+               return realGetSSK((ClientSSK)key, localOnly, cache, 
ignoreStore);
        else
                throw new IllegalArgumentException("Not a CHK or SSK: "+key);
     }
@@ -491,14 +494,14 @@
      * Really trivially simple client interface.
      * Either it succeeds or it doesn't.
      */
-    ClientCHKBlock realGetCHK(ClientCHK key, boolean localOnly, boolean cache) 
throws LowLevelGetException {
+    ClientCHKBlock realGetCHK(ClientCHK key, boolean localOnly, boolean cache, 
boolean ignoreStore) throws LowLevelGetException {
        long startTime = System.currentTimeMillis();
        long uid = random.nextLong();
         if(!lockUID(uid)) {
             Logger.error(this, "Could not lock UID just randomly generated: 
"+uid+" - probably indicates broken PRNG");
             throw new 
LowLevelGetException(LowLevelGetException.INTERNAL_ERROR);
         }
-        Object o = makeRequestSender(key.getNodeCHK(), MAX_HTL, uid, null, 
lm.loc.getValue(), localOnly, cache);
+        Object o = makeRequestSender(key.getNodeCHK(), MAX_HTL, uid, null, 
lm.loc.getValue(), localOnly, cache, ignoreStore);
         if(o instanceof CHKBlock) {
             try {
                 return new ClientCHKBlock((CHKBlock)o, key);
@@ -579,14 +582,14 @@
      * Really trivially simple client interface.
      * Either it succeeds or it doesn't.
      */
-    ClientSSKBlock realGetSSK(ClientSSK key, boolean localOnly, boolean cache) 
throws LowLevelGetException {
+    ClientSSKBlock realGetSSK(ClientSSK key, boolean localOnly, boolean cache, 
boolean ignoreStore) throws LowLevelGetException {
        long startTime = System.currentTimeMillis();
        long uid = random.nextLong();
         if(!lockUID(uid)) {
             Logger.error(this, "Could not lock UID just randomly generated: 
"+uid+" - probably indicates broken PRNG");
             throw new 
LowLevelGetException(LowLevelGetException.INTERNAL_ERROR);
         }
-        Object o = makeRequestSender(key.getNodeKey(), MAX_HTL, uid, null, 
lm.loc.getValue(), localOnly, cache);
+        Object o = makeRequestSender(key.getNodeKey(), MAX_HTL, uid, null, 
lm.loc.getValue(), localOnly, cache, ignoreStore);
         if(o instanceof SSKBlock) {
             try {
                SSKBlock block = (SSKBlock)o;
@@ -981,10 +984,11 @@
      * a RequestSender, unless the HTL is 0, in which case NULL.
      * RequestSender.
      */
-    public synchronized Object makeRequestSender(Key key, short htl, long uid, 
PeerNode source, double closestLocation, boolean localOnly, boolean cache) {
+    public synchronized Object makeRequestSender(Key key, short htl, long uid, 
PeerNode source, double closestLocation, boolean localOnly, boolean cache, 
boolean ignoreStore) {
         Logger.minor(this, 
"makeRequestSender("+key+","+htl+","+uid+","+source+") on "+portNumber);
         // In store?
         KeyBlock chk = null;
+        if(!ignoreStore) {
         try {
                if(key instanceof NodeCHK)
                        chk = chkDatastore.fetch((NodeCHK)key, !cache);
@@ -1012,6 +1016,7 @@
             Logger.error(this, "Error accessing store: "+e, e);
         }
         if(chk != null) return chk;
+        }
         if(localOnly) return null;
         Logger.minor(this, "Not in store locally");


Modified: trunk/freenet/src/freenet/node/QueuedDataRequest.java
===================================================================
--- trunk/freenet/src/freenet/node/QueuedDataRequest.java       2006-01-18 
14:35:15 UTC (rev 7872)
+++ trunk/freenet/src/freenet/node/QueuedDataRequest.java       2006-01-18 
16:38:29 UTC (rev 7873)
@@ -9,18 +9,20 @@
        private final ClientKey key;
        private final boolean localOnly;
        private final boolean cache;
+       private final boolean ignoreStore;
        private QueueingSimpleLowLevelClient client;

-       public QueuedDataRequest(ClientKey key, boolean localOnly, boolean 
cache, QueueingSimpleLowLevelClient client) {
+       public QueuedDataRequest(ClientKey key, boolean localOnly, boolean 
cache, QueueingSimpleLowLevelClient client, boolean ignoreStore) {
                this.key = key;
                this.localOnly = localOnly;
                this.client = client;
                this.cache = cache;
+               this.ignoreStore = ignoreStore;
        }

        public ClientKeyBlock waitAndFetch() throws LowLevelGetException {
                waitForSendClearance();
-               return client.realGetKey(key, localOnly, cache);
+               return client.realGetKey(key, localOnly, cache, ignoreStore);
        }

 }

Modified: trunk/freenet/src/freenet/node/QueueingSimpleLowLevelClient.java
===================================================================
--- trunk/freenet/src/freenet/node/QueueingSimpleLowLevelClient.java    
2006-01-18 14:35:15 UTC (rev 7872)
+++ trunk/freenet/src/freenet/node/QueueingSimpleLowLevelClient.java    
2006-01-18 16:38:29 UTC (rev 7873)
@@ -1,15 +1,12 @@
 package freenet.node;

-import freenet.client.InsertBlock;
-import freenet.keys.ClientCHKBlock;
 import freenet.keys.ClientKey;
 import freenet.keys.ClientKeyBlock;
-import freenet.keys.KeyBlock;

 interface QueueingSimpleLowLevelClient extends SimpleLowLevelClient {

        /** Unqueued version. Only call from QueuedDataRequest ! */
-       ClientKeyBlock realGetKey(ClientKey key, boolean localOnly, boolean 
cache) throws LowLevelGetException;
+       ClientKeyBlock realGetKey(ClientKey key, boolean localOnly, boolean 
cache, boolean ignoreStore) throws LowLevelGetException;

        /** Ditto */
        void realPut(ClientKeyBlock block, boolean cache) throws 
LowLevelPutException;

Modified: trunk/freenet/src/freenet/node/RealNodeRequestInsertTest.java
===================================================================
--- trunk/freenet/src/freenet/node/RealNodeRequestInsertTest.java       
2006-01-18 14:35:15 UTC (rev 7872)
+++ trunk/freenet/src/freenet/node/RealNodeRequestInsertTest.java       
2006-01-18 16:38:29 UTC (rev 7873)
@@ -191,7 +191,7 @@
                     node2 = random.nextInt(NUMBER_OF_NODES);
                 } while(node2 == node1);
                 Node fetchNode = nodes[node2];
-                block = (ClientCHKBlock) fetchNode.getKey((ClientKey) chk, 
false, starters[node2], true);
+                block = (ClientCHKBlock) fetchNode.getKey((ClientKey) chk, 
false, starters[node2], true, false);
                 if(block == null) {
                     Logger.error(RealNodeRequestInsertTest.class, "Fetch 
FAILED from "+node2);
                     requestsAvg.report(0.0);

Modified: trunk/freenet/src/freenet/node/RequestHandler.java
===================================================================
--- trunk/freenet/src/freenet/node/RequestHandler.java  2006-01-18 14:35:15 UTC 
(rev 7872)
+++ trunk/freenet/src/freenet/node/RequestHandler.java  2006-01-18 16:38:29 UTC 
(rev 7873)
@@ -58,7 +58,7 @@
         Message accepted = DMT.createFNPAccepted(uid);
         source.send(accepted);

-        Object o = node.makeRequestSender(key, htl, uid, source, closestLoc, 
false, true);
+        Object o = node.makeRequestSender(key, htl, uid, source, closestLoc, 
false, true, false);
         if(o instanceof KeyBlock) {
             KeyBlock block = (KeyBlock) o;
             Message df = createDataFound(block);

Modified: trunk/freenet/src/freenet/node/RequestStarterClient.java
===================================================================
--- trunk/freenet/src/freenet/node/RequestStarterClient.java    2006-01-18 
14:35:15 UTC (rev 7872)
+++ trunk/freenet/src/freenet/node/RequestStarterClient.java    2006-01-18 
16:38:29 UTC (rev 7873)
@@ -44,8 +44,8 @@
         * Blocking fetch of a key.
         * @throws LowLevelGetException If the fetch failed for some reason.
         */
-       public ClientKeyBlock getKey(ClientKey key, boolean localOnly, boolean 
cache) throws LowLevelGetException {
-               QueuedDataRequest qdr = new QueuedDataRequest(key, localOnly, 
cache, client);
+       public ClientKeyBlock getKey(ClientKey key, boolean localOnly, boolean 
cache, boolean ignoreStore) throws LowLevelGetException {
+               QueuedDataRequest qdr = new QueuedDataRequest(key, localOnly, 
cache, client, ignoreStore);
                addRequest(qdr);
                return qdr.waitAndFetch();
        }

Modified: trunk/freenet/src/freenet/node/SimpleLowLevelClient.java
===================================================================
--- trunk/freenet/src/freenet/node/SimpleLowLevelClient.java    2006-01-18 
14:35:15 UTC (rev 7872)
+++ trunk/freenet/src/freenet/node/SimpleLowLevelClient.java    2006-01-18 
16:38:29 UTC (rev 7873)
@@ -20,7 +20,7 @@
      * @param cache If false, don't cache the data. See the comments at the top
      * of Node.java.
      */
-    public ClientKeyBlock getKey(ClientKey key, boolean localOnly, 
RequestStarterClient client, boolean cache) throws LowLevelGetException;
+    public ClientKeyBlock getKey(ClientKey key, boolean localOnly, 
RequestStarterClient client, boolean cache, boolean ignoreStore) throws 
LowLevelGetException;

     /**
      * Insert a key.

Modified: trunk/freenet/src/freenet/node/TextModeClientInterface.java
===================================================================
--- trunk/freenet/src/freenet/node/TextModeClientInterface.java 2006-01-18 
14:35:15 UTC (rev 7872)
+++ trunk/freenet/src/freenet/node/TextModeClientInterface.java 2006-01-18 
16:38:29 UTC (rev 7873)
@@ -159,6 +159,9 @@
                                System.out.println();
                        } catch (FetchException e) {
                                System.out.println("Error: "+e.getMessage());
+               if(e.getMode() == e.SPLITFILE_ERROR && e.errorCodes != null) {
+                       System.out.println(e.errorCodes.toVerboseString());
+               }
                        }
         } else if(uline.startsWith("GETFILE:")) {
             // Should have a key next
@@ -699,10 +702,12 @@
         try {
             pn = new PeerNode(fs, n);
         } catch (FSParseException e1) {
-            System.err.println("Did not parse: "+e1.getMessage());
+            System.err.println("Did not parse: "+e1);
+            Logger.error(this, "Did not parse: "+e1, e1);
             return;
         } catch (PeerParseException e1) {
-            System.err.println("Did not parse: "+e1.getMessage());
+            System.err.println("Did not parse: "+e1);
+            Logger.error(this, "Did not parse: "+e1, e1);
             return;
         }
         if(n.peers.addPeer(pn))

Modified: trunk/freenet/src/freenet/node/Version.java
===================================================================
--- trunk/freenet/src/freenet/node/Version.java 2006-01-18 14:35:15 UTC (rev 
7872)
+++ trunk/freenet/src/freenet/node/Version.java 2006-01-18 16:38:29 UTC (rev 
7873)
@@ -20,7 +20,7 @@
        public static final String protocolVersion = "1.0";

        /** The build number of the current revision */
-       public static final int buildNumber = 359;
+       public static final int buildNumber = 361;

        /** Oldest build of Fred we will talk to */
        public static final int lastGoodBuild = 359;

Added: trunk/freenet/src/freenet/node/fcp/AllDataMessage.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/AllDataMessage.java      2006-01-18 
14:35:15 UTC (rev 7872)
+++ trunk/freenet/src/freenet/node/fcp/AllDataMessage.java      2006-01-18 
16:38:29 UTC (rev 7873)
@@ -0,0 +1,42 @@
+package freenet.node.fcp;
+
+import freenet.node.Node;
+import freenet.support.Bucket;
+import freenet.support.SimpleFieldSet;
+
+/**
+ * All the data, all in one big chunk. Obviously we must already have
+ * all the data to send it. We do not want to have to block on a request,
+ * especially as there may be errors.
+ */
+public class AllDataMessage extends DataCarryingMessage {
+
+       final long dataLength;
+       final String identifier;
+       
+       public AllDataMessage(FCPConnectionHandler handler, Bucket bucket, 
String identifier) {
+               this.bucket = bucket;
+               this.dataLength = bucket.size();
+               this.identifier = identifier;
+       }
+
+       long dataLength() {
+               return dataLength;
+       }
+
+       public SimpleFieldSet getFieldSet() {
+               SimpleFieldSet fs = new SimpleFieldSet();
+               fs.put("DataLength", Long.toHexString(dataLength));
+               fs.put("Identifier", identifier);
+               return fs;
+       }
+
+       public String getName() {
+               return "AllData";
+       }
+
+       public void run(FCPConnectionHandler handler, Node node) throws 
MessageInvalidException {
+               throw new 
MessageInvalidException(ProtocolErrorMessage.INVALID_MESSAGE, "AllData goes 
from server to client not the other way around");
+       }
+
+}

Added: trunk/freenet/src/freenet/node/fcp/ClientGet.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/ClientGet.java   2006-01-18 14:35:15 UTC 
(rev 7872)
+++ trunk/freenet/src/freenet/node/fcp/ClientGet.java   2006-01-18 16:38:29 UTC 
(rev 7873)
@@ -0,0 +1,70 @@
+package freenet.node.fcp;
+
+import freenet.client.FetchException;
+import freenet.client.FetchResult;
+import freenet.client.Fetcher;
+import freenet.client.FetcherContext;
+import freenet.keys.FreenetURI;
+import freenet.support.Logger;
+
+/**
+ * A simple client fetch. This can of course fetch arbitrarily large
+ * files, including splitfiles, redirects, etc.
+ */
+public class ClientGet extends ClientRequest implements Runnable {
+
+       private final FreenetURI uri;
+       private final FetcherContext fctx;
+       private final Fetcher f;
+       private final String identifier;
+       private final int verbosity;
+       private final FCPConnectionHandler handler;
+       
+       public ClientGet(FCPConnectionHandler handler, ClientGetMessage 
message) {
+               uri = message.uri;
+               // Create a Fetcher directly in order to get more fine-grained 
control,
+               // since the client may override a few context elements.
+               this.handler = handler;
+               fctx = new FetcherContext(handler.defaultFetchContext, 
FetcherContext.IDENTICAL_MASK);
+               // ignoreDS
+               fctx.localRequestOnly = message.dsOnly;
+               fctx.ignoreStore = message.ignoreDS;
+               fctx.maxNonSplitfileRetries = message.maxRetries;
+               fctx.maxSplitfileBlockRetries = 
Math.max(fctx.maxSplitfileBlockRetries, message.maxRetries);
+               this.identifier = message.identifier;
+               this.verbosity = message.verbosity;
+               // FIXME do something with verbosity !!
+               // Has already been checked
+               if(message.returnType != ClientGetMessage.RETURN_TYPE_DIRECT)
+                       throw new IllegalStateException("Unknown return type: 
"+message.returnType);
+               fctx.maxOutputLength = message.maxSize;
+               fctx.maxTempLength = message.maxTempSize;
+               f = new Fetcher(uri, fctx);
+               Thread t = new Thread(this, "FCP fetcher for "+uri+" 
("+identifier+") on "+handler);
+               t.setDaemon(true);
+               t.start();
+       }
+
+       public void cancel() {
+               fctx.cancel();
+       }
+
+       public void run() {
+               try {
+                       FetchResult fr = f.run();
+                       // Success!!!
+                       FCPMessage msg = new DataFoundMessage(handler, fr, 
identifier);
+                       handler.outputHandler.queue(msg);
+                       // Send all the data at once
+                       // FIXME there should be other options
+                       msg = new AllDataMessage(handler, fr.asBucket(), 
identifier);
+                       handler.outputHandler.queue(msg);
+               } catch (FetchException e) {
+                       // Error
+                       Logger.minor(this, "Caught "+e, e);
+                       FCPMessage msg = new FetchErrorMessage(handler, e, 
identifier);
+                       handler.outputHandler.queue(msg);
+               }
+       }
+
+}

Added: trunk/freenet/src/freenet/node/fcp/ClientGetMessage.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/ClientGetMessage.java    2006-01-18 
14:35:15 UTC (rev 7872)
+++ trunk/freenet/src/freenet/node/fcp/ClientGetMessage.java    2006-01-18 
16:38:29 UTC (rev 7873)
@@ -0,0 +1,130 @@
+package freenet.node.fcp;
+
+import java.net.MalformedURLException;
+
+import freenet.keys.FreenetURI;
+import freenet.node.Node;
+import freenet.support.SimpleFieldSet;
+
+/**
+ * ClientGet message.
+ * 
+ * Example:
+ * 
+ * ClientGet
+ * IgnoreDS=false // true = ignore the datastore
+ * DSOnly=false // true = only check the datastore, don't route (~= htl 0)
+ * URI=KSK at gpl.txt
+ * Identifier=Request Number One
+ * Verbosity=0 // no status, just tell us when it's done
+ * ReturnType=direct // return all at once over the FCP connection
+ * MaxSize=100 // maximum size of returned data (all numbers in hex)
+ * MaxTempSize=1000 // maximum size of intermediary data
+ * MaxRetries=100 // automatic retry supported as an option
+ * EndMessage
+ */
+public class ClientGetMessage extends FCPMessage {
+
+       final boolean ignoreDS;
+       final boolean dsOnly;
+       final FreenetURI uri;
+       final String identifier;
+       final int verbosity;
+       final int returnType;
+       final long maxSize;
+       final long maxTempSize;
+       final int maxRetries;
+       
+       // FIXME move these to the actual getter process
+       static final int RETURN_TYPE_DIRECT = 0;
+       
+       public ClientGetMessage(SimpleFieldSet fs) throws 
MessageInvalidException {
+               ignoreDS = Boolean.getBoolean(fs.get("IgnoreDS"));
+               dsOnly = Boolean.getBoolean(fs.get("DSOnly"));
+               try {
+                       uri = new FreenetURI(fs.get("URI"));
+               } catch (MalformedURLException e) {
+                       throw new 
MessageInvalidException(ProtocolErrorMessage.URI_PARSE_ERROR, e.getMessage());
+               }
+               identifier = fs.get("Identifier");
+               if(identifier == null)
+                       throw new 
MessageInvalidException(ProtocolErrorMessage.MISSING_FIELD, "No Identifier");
+               String verbosityString = fs.get("Verbosity");
+               if(verbosityString == null)
+                       verbosity = 0;
+               else {
+                       try {
+                               verbosity = Integer.parseInt(verbosityString, 
16);
+                       } catch (NumberFormatException e) {
+                               throw new 
MessageInvalidException(ProtocolErrorMessage.ERROR_PARSING_NUMBER, "Error 
parsing Verbosity field: "+e.getMessage());
+                       }
+               }
+               String returnTypeString = fs.get("ReturnType");
+               if(returnTypeString == null || 
returnTypeString.equalsIgnoreCase("direct"))
+                       returnType = RETURN_TYPE_DIRECT;
+               else
+                       throw new 
MessageInvalidException(ProtocolErrorMessage.MESSAGE_PARSE_ERROR, "Unknown 
return-type");
+               String maxSizeString = fs.get("MaxSize");
+               if(maxSizeString == null)
+                       // default to unlimited
+                       maxSize = Long.MAX_VALUE;
+               else {
+                       try {
+                               maxSize = Long.parseLong(maxSizeString, 16);
+                       } catch (NumberFormatException e) {
+                               throw new 
MessageInvalidException(ProtocolErrorMessage.ERROR_PARSING_NUMBER, "Error 
parsing MaxSize field: "+e.getMessage());
+                       }
+               }
+               String maxTempSizeString = fs.get("MaxTempSize");
+               if(maxTempSizeString == null)
+                       // default to unlimited
+                       maxTempSize = Long.MAX_VALUE;
+               else {
+                       try {
+                               maxTempSize = Long.parseLong(maxTempSizeString, 
16);
+                       } catch (NumberFormatException e) {
+                               throw new 
MessageInvalidException(ProtocolErrorMessage.ERROR_PARSING_NUMBER, "Error 
parsing MaxSize field: "+e.getMessage());
+                       }
+               }
+               String maxRetriesString = fs.get("MaxRetries");
+               if(maxRetriesString == null)
+                       // default to 0
+                       maxRetries = 0;
+               else {
+                       try {
+                               maxRetries = Integer.parseInt(maxRetriesString, 
16);
+                       } catch (NumberFormatException e) {
+                               throw new 
MessageInvalidException(ProtocolErrorMessage.ERROR_PARSING_NUMBER, "Error 
parsing MaxSize field: "+e.getMessage());
+                       }
+               }
+       }
+
+       public SimpleFieldSet getFieldSet() {
+               SimpleFieldSet fs = new SimpleFieldSet();
+               fs.put("IgnoreDS", Boolean.toString(ignoreDS));
+               fs.put("URI", uri.toString(false));
+               fs.put("Identifier", identifier);
+               fs.put("Verbosity", Integer.toHexString(verbosity));
+               fs.put("ReturnType", getReturnTypeString());
+               fs.put("MaxSize", Long.toHexString(maxSize));
+               fs.put("MaxTempSize", Long.toHexString(maxTempSize));
+               fs.put("MaxRetries", Integer.toHexString(maxRetries));
+               return fs;
+       }
+
+       private String getReturnTypeString() {
+               if(returnType == RETURN_TYPE_DIRECT)
+                       return "direct";
+               else
+                       throw new IllegalStateException("Unknown return type: 
"+returnType);
+       }
+
+       public String getName() {
+               return "ClientGet";
+       }
+
+       public void run(FCPConnectionHandler handler, Node node) {
+               handler.startClientGet(this);
+       }
+
+}

Added: trunk/freenet/src/freenet/node/fcp/ClientHelloMessage.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/ClientHelloMessage.java  2006-01-18 
14:35:15 UTC (rev 7872)
+++ trunk/freenet/src/freenet/node/fcp/ClientHelloMessage.java  2006-01-18 
16:38:29 UTC (rev 7873)
@@ -0,0 +1,45 @@
+package freenet.node.fcp;
+
+import freenet.node.Node;
+import freenet.support.SimpleFieldSet;
+
+/**
+ *  ClientHello
+ *  Name=Toad's Test Client
+ *  ExpectedVersion=0.7.0
+ *  End
+ */
+public class ClientHelloMessage extends FCPMessage {
+
+       String clientName;
+       String clientExpectedVersion;
+       
+       public ClientHelloMessage(SimpleFieldSet fs) throws 
MessageInvalidException {
+               clientName = fs.get("Name");
+               clientExpectedVersion = fs.get("ExpectedVersion");
+               if(clientName == null)
+                       throw new 
MessageInvalidException(ProtocolErrorMessage.MISSING_FIELD, "ClientHello must 
contain a Name field");
+               if(clientExpectedVersion == null)
+                       throw new 
MessageInvalidException(ProtocolErrorMessage.MISSING_FIELD, "ClientHello must 
contain a ExpectedVersion field");
+               // FIXME check the expected version
+       }
+
+       public SimpleFieldSet getFieldSet() {
+               SimpleFieldSet sfs = new SimpleFieldSet();
+               sfs.put("Name", clientName);
+               sfs.put("ExpectedVersion", clientExpectedVersion);
+               return sfs;
+       }
+
+       public String getName() {
+               return "ClientHello";
+       }
+
+       public void run(FCPConnectionHandler handler, Node node) {
+               // We know the Hello is valid.
+               handler.setClientName(clientName);
+               FCPMessage msg = new NodeHelloMessage();
+               handler.outputHandler.queue(msg);
+       }
+
+}

Added: trunk/freenet/src/freenet/node/fcp/ClientRequest.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/ClientRequest.java       2006-01-18 
14:35:15 UTC (rev 7872)
+++ trunk/freenet/src/freenet/node/fcp/ClientRequest.java       2006-01-18 
16:38:29 UTC (rev 7873)
@@ -0,0 +1,12 @@
+package freenet.node.fcp;
+
+/**
+ * A request process carried out by the node for an FCP client.
+ * Examples: ClientGet, ClientPut, MultiGet.
+ */
+public abstract class ClientRequest {
+
+       /** Cancel */
+       public abstract void cancel();
+
+}

Added: trunk/freenet/src/freenet/node/fcp/DataCarryingMessage.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/DataCarryingMessage.java 2006-01-18 
14:35:15 UTC (rev 7872)
+++ trunk/freenet/src/freenet/node/fcp/DataCarryingMessage.java 2006-01-18 
16:38:29 UTC (rev 7873)
@@ -0,0 +1,36 @@
+package freenet.node.fcp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import freenet.support.Bucket;
+import freenet.support.BucketFactory;
+import freenet.support.BucketTools;
+
+
+public abstract class DataCarryingMessage extends FCPMessage {
+
+       protected Bucket bucket;
+       
+       abstract long dataLength();
+
+       public void readFrom(InputStream is, BucketFactory bf) throws 
IOException {
+               long len = dataLength();
+               if(len < 0)
+                       throw new IllegalArgumentException("Invalid length: 
"+len);
+               Bucket bucket = bf.makeBucket(len);
+               BucketTools.copyFrom(bucket, is, len);
+               this.bucket = bucket;
+       }
+       
+       public void send(OutputStream os) throws IOException {
+               super.send(os);
+               BucketTools.copyTo(bucket, os, dataLength());
+       }
+
+       String getEndString() {
+               return "Data";
+       }
+       
+}

Added: trunk/freenet/src/freenet/node/fcp/DataFoundMessage.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/DataFoundMessage.java    2006-01-18 
14:35:15 UTC (rev 7872)
+++ trunk/freenet/src/freenet/node/fcp/DataFoundMessage.java    2006-01-18 
16:38:29 UTC (rev 7873)
@@ -0,0 +1,35 @@
+package freenet.node.fcp;
+
+import freenet.client.FetchResult;
+import freenet.node.Node;
+import freenet.support.SimpleFieldSet;
+
+public class DataFoundMessage extends FCPMessage {
+
+       final String identifier;
+       final String mimeType;
+       final long dataLength;
+       
+       public DataFoundMessage(FCPConnectionHandler handler, FetchResult fr, 
String identifier) {
+               this.identifier = identifier;
+               this.mimeType = fr.getMimeType();
+               this.dataLength = fr.size();
+       }
+
+       public SimpleFieldSet getFieldSet() {
+               SimpleFieldSet fs = new SimpleFieldSet();
+               fs.put("Identifier", identifier);
+               fs.put("Metadata.ContentType", mimeType);
+               fs.put("DataLength", Long.toHexString(dataLength));
+               return fs;
+       }
+
+       public String getName() {
+               return "DataFound";
+       }
+
+       public void run(FCPConnectionHandler handler, Node node) throws 
MessageInvalidException {
+               throw new 
MessageInvalidException(ProtocolErrorMessage.INVALID_MESSAGE, "DataFound goes 
from server to client not the other way around");
+       }
+
+}

Added: trunk/freenet/src/freenet/node/fcp/FCPConnectionHandler.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/FCPConnectionHandler.java        
2006-01-18 14:35:15 UTC (rev 7872)
+++ trunk/freenet/src/freenet/node/fcp/FCPConnectionHandler.java        
2006-01-18 16:38:29 UTC (rev 7873)
@@ -0,0 +1,108 @@
+package freenet.node.fcp;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.util.HashMap;
+
+import freenet.client.FetcherContext;
+import freenet.node.Node;
+import freenet.support.BucketFactory;
+import freenet.support.Logger;
+
+public class FCPConnectionHandler {
+
+       final Socket sock;
+       final FCPConnectionInputHandler inputHandler;
+       final FCPConnectionOutputHandler outputHandler;
+       final Node node;
+       private boolean isClosed;
+       private boolean inputClosed;
+       private boolean outputClosed;
+       private String clientName;
+       final BucketFactory bf;
+       final HashMap requestsByIdentifier;
+       final FetcherContext defaultFetchContext;
+       
+       public FCPConnectionHandler(Socket s, Node node) {
+               this.sock = s;
+               this.node = node;
+               this.inputHandler = new FCPConnectionInputHandler(this);
+               this.outputHandler = new FCPConnectionOutputHandler(this);
+               isClosed = false;
+               this.bf = node.tempBucketFactory;
+               requestsByIdentifier = new HashMap();
+               defaultFetchContext = 
+                       node.makeClient((short)0,(short)0).getFetcherContext();
+       }
+       
+       public void close() {
+               ClientRequest[] requests;
+               synchronized(this) {
+                       isClosed = true;
+                       requests = new 
ClientRequest[requestsByIdentifier.size()];
+                       requests = (ClientRequest[]) 
requestsByIdentifier.values().toArray(requests);
+               }
+               for(int i=0;i<requests.length;i++)
+                       requests[i].cancel();
+       }
+       
+       public boolean isClosed() {
+               return isClosed;
+       }
+       
+       public void closedInput() {
+               try {
+                       sock.shutdownInput();
+               } catch (IOException e) {
+                       // Ignore
+               }
+               synchronized(this) {
+                       inputClosed = true;
+                       if(!outputClosed) return;
+               }
+               try {
+                       sock.close();
+               } catch (IOException e) {
+                       // Ignore
+               }
+       }
+       
+       public void closedOutput() {
+               try {
+                       sock.shutdownOutput();
+               } catch (IOException e) {
+                       // Ignore
+               }
+               synchronized(this) {
+                       outputClosed = true;
+                       if(!inputClosed) return;
+               }
+               try {
+                       sock.close();
+               } catch (IOException e) {
+                       // Ignore
+               }
+       }
+
+       public void setClientName(String name) {
+               this.clientName = name;
+       }
+       
+       public String getClientName() {
+               return clientName;
+       }
+
+       public void startClientGet(ClientGetMessage message) {
+               String id = message.identifier;
+               if(requestsByIdentifier.containsKey(id)) {
+                       Logger.normal(this, "Identifier collision on "+this);
+                       FCPMessage msg = new IdentifierCollisionMessage(id);
+                       outputHandler.queue(msg);
+                       return;
+               }
+               synchronized(this) {
+                       if(isClosed) return;
+                       ClientGet cg = new ClientGet(this, message);
+               }
+       }
+}

Added: trunk/freenet/src/freenet/node/fcp/FCPConnectionInputHandler.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/FCPConnectionInputHandler.java   
2006-01-18 14:35:15 UTC (rev 7872)
+++ trunk/freenet/src/freenet/node/fcp/FCPConnectionInputHandler.java   
2006-01-18 16:38:29 UTC (rev 7873)
@@ -0,0 +1,78 @@
+package freenet.node.fcp;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import freenet.support.Logger;
+import freenet.support.SimpleFieldSet;
+import freenet.support.io.LineReadingInputStream;
+
+public class FCPConnectionInputHandler implements Runnable {
+
+       final FCPConnectionHandler handler;
+       
+       public FCPConnectionInputHandler(FCPConnectionHandler handler) {
+               this.handler = handler;
+               Thread t = new Thread(this, "FCP input handler for 
"+handler.sock.getRemoteSocketAddress()+":"+handler.sock.getPort());
+               t.setDaemon(true);
+               t.start();
+       }
+
+       public void run() {
+               try {
+                       realRun();
+               } catch (IOException e) {
+                       Logger.minor(this, "Caught "+e, e);
+               } catch (Throwable t) {
+                       Logger.error(this, "Caught "+t, t);
+               }
+               handler.close();
+               handler.closedInput();
+       }
+       
+       public void realRun() throws IOException {
+               InputStream is = handler.sock.getInputStream();
+               LineReadingInputStream lis = new LineReadingInputStream(is);
+               
+               boolean firstMessage = true;
+               
+               while(true) {
+                       SimpleFieldSet fs;
+                       // Read a message
+                       String messageType = lis.readLine(64, 64);
+                       fs = new SimpleFieldSet(lis, 4096, 128);
+                       FCPMessage msg;
+                       try {
+                               msg = FCPMessage.create(messageType, fs);
+                               if(msg == null) continue;
+                       } catch (MessageInvalidException e) {
+                               FCPMessage err = new 
ProtocolErrorMessage(e.protocolCode, false, e.getMessage());
+                               handler.outputHandler.queue(err);
+                               continue;
+                       }
+                       if(firstMessage && !(msg instanceof 
ClientHelloMessage)) {
+                               FCPMessage err = new 
ProtocolErrorMessage(ProtocolErrorMessage.CLIENT_HELLO_MUST_BE_FIRST_MESSAGE, 
true, null);
+                               handler.outputHandler.queue(err);
+                               handler.close();
+                               continue;
+                       }
+                       if(msg instanceof DataCarryingMessage) {
+                               ((DataCarryingMessage)msg).readFrom(lis, 
handler.bf);
+                       }
+                       if((!firstMessage) && msg instanceof 
ClientHelloMessage) {
+                               FCPMessage err = new 
ProtocolErrorMessage(ProtocolErrorMessage.NO_LATE_CLIENT_HELLOS, false, null);
+                               handler.outputHandler.queue(err);
+                               continue;
+                       }
+                       try {
+                               msg.run(handler, handler.node);
+                       } catch (MessageInvalidException e) {
+                               FCPMessage err = new 
ProtocolErrorMessage(e.protocolCode, false, e.getMessage());
+                               handler.outputHandler.queue(err);
+                               continue;
+                       }
+                       firstMessage = false;
+                       if(handler.isClosed()) return;
+               }
+       }
+}

Added: trunk/freenet/src/freenet/node/fcp/FCPConnectionOutputHandler.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/FCPConnectionOutputHandler.java  
2006-01-18 14:35:15 UTC (rev 7872)
+++ trunk/freenet/src/freenet/node/fcp/FCPConnectionOutputHandler.java  
2006-01-18 16:38:29 UTC (rev 7873)
@@ -0,0 +1,65 @@
+package freenet.node.fcp;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.LinkedList;
+
+import freenet.support.Logger;
+
+public class FCPConnectionOutputHandler implements Runnable {
+
+       final FCPConnectionHandler handler;
+       final LinkedList outQueue;
+       
+       public FCPConnectionOutputHandler(FCPConnectionHandler handler) {
+               this.handler = handler;
+               this.outQueue = new LinkedList();
+               Thread t = new Thread(this, "FCP output handler for 
"+handler.sock.getRemoteSocketAddress()+":"+handler.sock.getPort());
+               t.setDaemon(true);
+               t.start();
+       }
+
+       public void run() {
+               try {
+                       realRun();
+               } catch (IOException e) {
+                       Logger.minor(this, "Caught "+e, e);
+               } catch (Throwable t) {
+                       Logger.minor(this, "Caught "+t, t);
+               }
+               handler.close();
+               handler.closedOutput();
+       }
+
+       private void realRun() throws IOException {
+               OutputStream os = handler.sock.getOutputStream();
+               while(true) {
+                       FCPMessage msg;
+                       synchronized(outQueue) {
+                               while(true) {
+                                       if(outQueue.isEmpty()) {
+                                               if(handler.isClosed()) return;
+                                               try {
+                                                       outQueue.wait(10000);
+                                               } catch (InterruptedException 
e) {
+                                                       // Ignore
+                                               }
+                                               continue;
+                                       }
+                                       msg = (FCPMessage) 
outQueue.removeFirst();
+                                       break;
+                               }
+                       }
+                       msg.send(os);
+                       if(handler.isClosed()) return;
+               }
+       }
+
+       public void queue(FCPMessage msg) {
+               synchronized(outQueue) {
+                       outQueue.add(msg);
+                       outQueue.notifyAll();
+               }
+       }
+
+}

Added: trunk/freenet/src/freenet/node/fcp/FCPMessage.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/FCPMessage.java  2006-01-18 14:35:15 UTC 
(rev 7872)
+++ trunk/freenet/src/freenet/node/fcp/FCPMessage.java  2006-01-18 16:38:29 UTC 
(rev 7873)
@@ -0,0 +1,44 @@
+package freenet.node.fcp;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import freenet.node.Node;
+import freenet.support.SimpleFieldSet;
+
+public abstract class FCPMessage {
+
+       public void send(OutputStream os) throws IOException {
+               SimpleFieldSet sfs = getFieldSet();
+               sfs.setEndMarker(getEndString());
+               String msg = sfs.toString();
+               os.write((getName()+"\n").getBytes("UTF-8"));
+               os.write(msg.getBytes("UTF-8"));
+       }
+
+       String getEndString() {
+               return "EndMessage";
+       }
+       
+       public abstract SimpleFieldSet getFieldSet();
+
+       public abstract String getName();
+       
+       public static FCPMessage create(String name, SimpleFieldSet fs) throws 
MessageInvalidException {
+               if(name.equals("ClientHello"))
+                       return new ClientHelloMessage(fs);
+               if(name.equals("ClientGet"))
+                       return new ClientGetMessage(fs);
+               if(name.equals("Void"))
+                       return null;
+//             if(name.equals("ClientPut"))
+//                     return new ClientPutFCPMessage(fs);
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       /** Do whatever it is that we do with this type of message. 
+        * @throws MessageInvalidException */
+       public abstract void run(FCPConnectionHandler handler, Node node) 
throws MessageInvalidException;
+
+}

Added: trunk/freenet/src/freenet/node/fcp/FCPServer.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/FCPServer.java   2006-01-18 14:35:15 UTC 
(rev 7872)
+++ trunk/freenet/src/freenet/node/fcp/FCPServer.java   2006-01-18 16:38:29 UTC 
(rev 7873)
@@ -0,0 +1,45 @@
+package freenet.node.fcp;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+import freenet.node.Node;
+import freenet.support.Logger;
+
+/**
+ * FCP server process.
+ */
+public class FCPServer implements Runnable {
+
+       final ServerSocket sock;
+       final Node node;
+       
+       public FCPServer(int port, Node node) throws IOException {
+               this.sock = new ServerSocket(port, 0, 
InetAddress.getByName("127.0.0.1"));
+               this.node = node;
+               Thread t = new Thread(this, "FCP server");
+               t.setDaemon(true);
+               t.start();
+       }
+       
+       public void run() {
+               while(true) {
+                       try {
+                               realRun();
+                       } catch (IOException e) {
+                               Logger.minor(this, "Caught "+e, e);
+                       } catch (Throwable t) {
+                               Logger.error(this, "Caught "+t, t);
+                       }
+               }
+       }
+
+       private void realRun() throws IOException {
+               // Accept a connection
+               Socket s = sock.accept();
+               FCPConnectionHandler handler = new FCPConnectionHandler(s, 
node);
+       }
+
+}

Added: trunk/freenet/src/freenet/node/fcp/FetchErrorMessage.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/FetchErrorMessage.java   2006-01-18 
14:35:15 UTC (rev 7872)
+++ trunk/freenet/src/freenet/node/fcp/FetchErrorMessage.java   2006-01-18 
16:38:29 UTC (rev 7873)
@@ -0,0 +1,45 @@
+package freenet.node.fcp;
+
+import freenet.client.FailureCodeTracker;
+import freenet.client.FetchException;
+import freenet.node.Node;
+import freenet.support.SimpleFieldSet;
+
+public class FetchErrorMessage extends FCPMessage {
+
+       final int code;
+       final String codeDescription;
+       final String extraDescription;
+       final FailureCodeTracker tracker;
+       final boolean isFatal;
+       
+       public FetchErrorMessage(FCPConnectionHandler handler, FetchException 
e, String identifier) {
+               this.tracker = e.errorCodes;
+               this.code = e.mode;
+               this.codeDescription = FetchException.getMessage(code);
+               this.extraDescription = e.extraMessage;
+               this.isFatal = e.isFatal();
+       }
+
+       public SimpleFieldSet getFieldSet() {
+               SimpleFieldSet sfs = new SimpleFieldSet();
+               sfs.put("Code", Integer.toHexString(code));
+               sfs.put("CodeDescription", codeDescription);
+               if(extraDescription != null)
+                       sfs.put("ExtraDescription", extraDescription);
+               sfs.put("Fatal", Boolean.toString(isFatal));
+               if(tracker != null) {
+                       tracker.copyToFieldSet(sfs, "Errors.");
+               }
+               return sfs;
+       }
+
+       public String getName() {
+               return "FetchError";
+       }
+
+       public void run(FCPConnectionHandler handler, Node node) throws 
MessageInvalidException {
+               throw new 
MessageInvalidException(ProtocolErrorMessage.INVALID_MESSAGE, "FetchError goes 
from server to client not the other way around");
+       }
+
+}

Added: trunk/freenet/src/freenet/node/fcp/IdentifierCollisionMessage.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/IdentifierCollisionMessage.java  
2006-01-18 14:35:15 UTC (rev 7872)
+++ trunk/freenet/src/freenet/node/fcp/IdentifierCollisionMessage.java  
2006-01-18 16:38:29 UTC (rev 7873)
@@ -0,0 +1,29 @@
+package freenet.node.fcp;
+
+import freenet.node.Node;
+import freenet.support.SimpleFieldSet;
+
+public class IdentifierCollisionMessage extends FCPMessage {
+
+       final String identifier;
+       
+       public IdentifierCollisionMessage(String id) {
+               this.identifier = id;
+       }
+
+       public SimpleFieldSet getFieldSet() {
+               SimpleFieldSet sfs = new SimpleFieldSet();
+               sfs.put("Identifier", identifier);
+               return sfs;
+       }
+
+       public String getName() {
+               return "IdentifierCollision";
+       }
+
+       public void run(FCPConnectionHandler handler, Node node)
+                       throws MessageInvalidException {
+               throw new 
MessageInvalidException(ProtocolErrorMessage.INVALID_MESSAGE, 
"IdentifierCollision goes from server to client not the other way around");
+       }
+
+}

Added: trunk/freenet/src/freenet/node/fcp/MessageInvalidException.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/MessageInvalidException.java     
2006-01-18 14:35:15 UTC (rev 7872)
+++ trunk/freenet/src/freenet/node/fcp/MessageInvalidException.java     
2006-01-18 16:38:29 UTC (rev 7873)
@@ -0,0 +1,17 @@
+package freenet.node.fcp;
+
+/**
+ * Thrown when an FCP message is invalid. This is after we have a
+ * SimpleFieldSet; one example is if the fields necessary do not exist.
+ * This is a catch-all error; it corresponds to MESSAGE_PARSE_ERROR on
+ * ProtocolError.
+ */
+public class MessageInvalidException extends Exception {
+
+       int protocolCode;
+       
+       public MessageInvalidException(int protocolCode, String extra) {
+               super(extra);
+       }
+
+}

Added: trunk/freenet/src/freenet/node/fcp/NodeHelloMessage.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/NodeHelloMessage.java    2006-01-18 
14:35:15 UTC (rev 7872)
+++ trunk/freenet/src/freenet/node/fcp/NodeHelloMessage.java    2006-01-18 
16:38:29 UTC (rev 7873)
@@ -0,0 +1,36 @@
+package freenet.node.fcp;
+
+import freenet.node.Node;
+import freenet.node.Version;
+import freenet.support.SimpleFieldSet;
+
+/**
+ * NodeHello
+ *
+ * NodeHello
+ * FCPVersion=<protocol version>
+ * Node=Fred
+ * Version=0.7.0,401
+ * EndMessage
+ */
+public class NodeHelloMessage extends FCPMessage {
+
+       public SimpleFieldSet getFieldSet() {
+               SimpleFieldSet sfs = new SimpleFieldSet();
+               // FIXME
+               sfs.put("FCPVersion", "0.7.0");
+               sfs.put("Node", "Fred");
+               sfs.put("Version", Version.getVersionString());
+               return sfs;
+       }
+
+       public String getName() {
+               return "NodeHello";
+       }
+
+       public void run(FCPConnectionHandler handler, Node node) {
+               throw new UnsupportedOperationException();
+               // Client should not be sending this!
+       }
+
+}

Added: trunk/freenet/src/freenet/node/fcp/ProtocolErrorMessage.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/ProtocolErrorMessage.java        
2006-01-18 14:35:15 UTC (rev 7872)
+++ trunk/freenet/src/freenet/node/fcp/ProtocolErrorMessage.java        
2006-01-18 16:38:29 UTC (rev 7873)
@@ -0,0 +1,78 @@
+package freenet.node.fcp;
+
+import freenet.node.Node;
+import freenet.support.Logger;
+import freenet.support.SimpleFieldSet;
+
+/**
+ * ProtocolError (some problem parsing the other side's FCP messages)
+ * 
+ * ProtocolError
+ * Code=1
+ * CodeDescription=ClientHello must be first message
+ * ExtraDescription=Duh
+ * Fatal=false // means the connection stays open
+ * EndMessage
+ */
+public class ProtocolErrorMessage extends FCPMessage {
+
+       static final int CLIENT_HELLO_MUST_BE_FIRST_MESSAGE = 1;
+       static final int NO_LATE_CLIENT_HELLOS = 2;
+       static final int MESSAGE_PARSE_ERROR = 3;
+       static final int URI_PARSE_ERROR = 4;
+       static final int MISSING_FIELD = 5;
+       static final int ERROR_PARSING_NUMBER = 6;
+       static final int INVALID_MESSAGE = 7;
+       
+       final int code;
+       final String extra;
+       final boolean fatal;
+       
+       private String codeDescription() {
+               switch(code) {
+               case CLIENT_HELLO_MUST_BE_FIRST_MESSAGE:
+                       return "ClientHello must be first message";
+               case NO_LATE_CLIENT_HELLOS:
+                       return "No late ClientHello's accepted";
+               case MESSAGE_PARSE_ERROR:
+                       return "Unknown message parsing error";
+               case URI_PARSE_ERROR:
+                       return "Error parsing URI";
+               case MISSING_FIELD:
+                       return "Missing field";
+               case ERROR_PARSING_NUMBER:
+                       return "Error parsing a numeric field";
+               case INVALID_MESSAGE:
+                       return "Don't know what to do with message";
+               default:
+                       Logger.error(this, "Unknown error code: "+code, new 
Exception("debug"));
+               return "(Unknown)";
+               }
+       }
+
+       public ProtocolErrorMessage(int code, boolean fatal, String extra) {
+               this.code = code;
+               this.extra = extra;
+               this.fatal = fatal;
+       }
+
+       public SimpleFieldSet getFieldSet() {
+               SimpleFieldSet sfs = new SimpleFieldSet();
+               sfs.put("Code", Integer.toHexString(code));
+               sfs.put("CodeDescription", codeDescription());
+               if(extra != null)
+                       sfs.put("ExtraDescription", extra);
+               sfs.put("Fatal", Boolean.toString(fatal));
+               return sfs;
+       }
+
+       public void run(FCPConnectionHandler handler, Node node) {
+               Logger.error(this, "Client reported protocol error");
+               if(fatal) handler.close();
+       }
+
+       public String getName() {
+               return "ProtocolError";
+       }
+
+}

Modified: trunk/freenet/src/freenet/support/BucketTools.java
===================================================================
--- trunk/freenet/src/freenet/support/BucketTools.java  2006-01-18 14:35:15 UTC 
(rev 7872)
+++ trunk/freenet/src/freenet/support/BucketTools.java  2006-01-18 16:38:29 UTC 
(rev 7873)
@@ -399,6 +399,32 @@
                }
        }

+       /** Copy data from an InputStream into a Bucket. */
+       public static void copyFrom(Bucket bucket, InputStream is, long 
truncateLength) throws IOException {
+               OutputStream os = bucket.getOutputStream();
+               byte[] buf = new byte[4096];
+               if(truncateLength < 0) truncateLength = Long.MAX_VALUE;
+               try {
+                       long moved = 0;
+                       while(moved < truncateLength) {
+                               // DO NOT move the (int) inside the Math.min()! 
big numbers truncate to negative numbers.
+                               int bytes = (int) Math.min(buf.length, 
truncateLength - moved);
+                               if(bytes <= 0)
+                                       throw new 
IllegalStateException("bytes="+bytes+", truncateLength="+truncateLength+", 
moved="+moved);
+                               bytes = is.read(buf, 0, bytes);
+                               if(bytes <= 0) {
+                                       if(truncateLength == Long.MAX_VALUE)
+                                               break;
+                                       throw new IOException("Could not move 
required quantity of data: "+bytes+" (moved "+moved+" of "+truncateLength+")");
+                               }
+                               os.write(buf, 0, bytes);
+                               moved += bytes;
+                       }
+               } finally {
+                       os.close();
+               }
+       }
+
        /**
         * Split the data into a series of read-only Bucket's.
         * @param origData The original data Bucket.

Modified: trunk/freenet/src/freenet/support/SimpleFieldSet.java
===================================================================
--- trunk/freenet/src/freenet/support/SimpleFieldSet.java       2006-01-18 
14:35:15 UTC (rev 7872)
+++ trunk/freenet/src/freenet/support/SimpleFieldSet.java       2006-01-18 
16:38:29 UTC (rev 7873)
@@ -11,6 +11,8 @@
 import java.util.Map;
 import java.util.Set;

+import freenet.support.io.LineReader;
+
 /**
  * @author amphibian
  * 
@@ -20,12 +22,18 @@
 public class SimpleFieldSet {

     final Map map;
+    String endMarker;

     public SimpleFieldSet(BufferedReader br) throws IOException {
         map = new HashMap();
         read(br);
     }

+    public SimpleFieldSet(LineReader lis, int maxLineLength, int 
lineBufferSize) throws IOException {
+       map = new HashMap();
+       read(lis, maxLineLength, lineBufferSize);
+    }
+    
     /**
      * Empty constructor
      */
@@ -67,13 +75,44 @@
                 String after = line.substring(index+1);
                 map.put(before, after);
             } else {
-                if(line.equals("End")) return;
-                throw new IOException("Unknown end-marker: \""+line+"\"");
+               endMarker = line;
+               return;
             }

         }
     }

+    /**
+     * Read from disk
+     * Format:
+     * blah=blah
+     * blah=blah
+     * End
+     */
+    private void read(LineReader br, int maxLength, int bufferSize) throws 
IOException {
+        boolean firstLine = true;
+        while(true) {
+            String line = br.readLine(maxLength, bufferSize);
+            if(line == null) {
+                if(firstLine) throw new EOFException();
+                throw new IOException();
+            }
+            firstLine = false;
+            int index = line.indexOf('=');
+            if(index >= 0) {
+                // Mapping
+                String before = line.substring(0, index);
+                String after = line.substring(index+1);
+                map.put(before, after);
+            } else {
+               endMarker = line;
+               return;
+            }
+            
+        }
+    }
+
+    
     public String get(String key) {
         return (String) map.get(key);
     }
@@ -95,7 +134,10 @@
             String value = (String) entry.getValue();
             w.write(key+"="+value+"\n");
         }
-        w.write("End\n");
+        if(endMarker != null)
+               w.write(endMarker+"\n");
+        else
+               w.write("End\n");
     }

     public String toString() {
@@ -107,4 +149,12 @@
         }
         return sw.toString();
     }
+    
+    public String getEndMarker() {
+       return endMarker;
+    }
+    
+    public void setEndMarker(String s) {
+       endMarker = s;
+    }
 }

Added: trunk/freenet/src/freenet/support/io/LineReader.java
===================================================================
--- trunk/freenet/src/freenet/support/io/LineReader.java        2006-01-18 
14:35:15 UTC (rev 7872)
+++ trunk/freenet/src/freenet/support/io/LineReader.java        2006-01-18 
16:38:29 UTC (rev 7873)
@@ -0,0 +1,9 @@
+package freenet.support.io;
+
+import java.io.IOException;
+
+public interface LineReader {
+
+       public String readLine(int maxLength, int bufferSize) throws 
IOException;
+       
+}

Modified: trunk/freenet/src/freenet/support/io/LineReadingInputStream.java
===================================================================
--- trunk/freenet/src/freenet/support/io/LineReadingInputStream.java    
2006-01-18 14:35:15 UTC (rev 7872)
+++ trunk/freenet/src/freenet/support/io/LineReadingInputStream.java    
2006-01-18 16:38:29 UTC (rev 7873)
@@ -8,7 +8,7 @@
 /**
  * A FilterInputStream which provides readLine().
  */
-public class LineReadingInputStream extends FilterInputStream {
+public class LineReadingInputStream extends FilterInputStream implements 
LineReader {

        public LineReadingInputStream(InputStream in) {
                super(in);


Reply via email to