[PoC PATCH JGIT 1/2] Freenet Freenet Client Protocol (FCP) 2.0 Client
Signed-off-by: Daniel Cheng (aka SDiZ) <j16sdiz+freenet-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
---
.../jgit/transport/fcpv2/FCPConnection.java | 427 ++++++++++++++++++++
1 files changed, 427 insertions(+), 0 deletions(-)
create mode 100644 org.spearce.jgit/src/org/spearce/jgit/transport/fcpv2/FCPConnection.java
diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/fcpv2/FCPConnection.java b/org.spearce.jgit/src/org/spearce/jgit/transport/fcpv2/FCPConnection.java
new file mode 100644
index 0000000..ce2ca90
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/fcpv2/FCPConnection.java
@@ -0,0 +1,427 @@
+/*
+ * Copyright (C) 2008, CHENG Yuk-Pong, Daniel <j16sdiz+freenet-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Git Development Community nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.spearce.jgit.transport.fcpv2;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.spearce.jgit.lib.ProgressMonitor;
+
+/**
+ * Freenet Client Protocol 2.0 Connection
+ *
+ * A simplified interface to Freenet 0.7 node using FCPv2. This is not a
+ * full-blown interface to Freenet node, but rather some "quick and dirty" hack
+ * just enough to upload/download simple file from the network.
+ * <p>
+ * See <a href="http://wiki.freenetproject.org/FreenetFCPSpec2Point0">Freenet
+ * Client Protocol 2.0 Specification</a> for detail on this protocol.
+ */
+public class FCPConnection {
+ /** Default FCP port */
+ public static final int DEFAULT_FCP_PORT = 9481;
+
+ protected InetAddress addr;
+
+ protected int port;
+
+ protected Socket socket;
+
+ protected InputStream is;
+
+ protected OutputStream os;
+
+ /**
+ * Create a new FCP Connection to default host and port (
+ * <code>localhost:9481</code>)
+ *
+ * @throws UnknownHostException
+ * if no IP address for the <code>localhost</code> could be
+ * found.
+ */
+ public FCPConnection() throws UnknownHostException {
+ this(InetAddress.getAllByName("127.0.0.1")[0], DEFAULT_FCP_PORT);
+ }
+
+ /**
+ * Create a new FCP Connection to the specified IP address and port
+ *
+ * @param address
+ * the node IP address
+ * @param port
+ * the port number
+ */
+ public FCPConnection(InetAddress address, int port) {
+ this.addr = address;
+ this.port = port;
+ }
+
+ /**
+ * Connect to the node
+ *
+ * @throws IOException
+ * if any I/O error occurred
+ */
+ public void connect() throws IOException {
+ socket = new Socket(addr, port);
+
+ is = new BufferedInputStream(socket.getInputStream());
+ os = new BufferedOutputStream(socket.getOutputStream());
+ }
+
+ /**
+ * Hello Message
+ *
+ * Send handshake <code>ClientHello</code> message to node. Block until
+ * <code>NodeHello</code> is received.
+ *
+ * @param clientName
+ * Client name, must be unique in the freenet node
+ *
+ * @throws IOException
+ * if any I/O error occurred
+ */
+ public void hello(String clientName) throws IOException {
+ FCPMessage msg = new FCPMessage();
+ msg.messageType = "ClientHello";
+ msg.put("ExpectedVersion", "2.0");
+ msg.put("Name", clientName);
+
+ msg.writeTo(os);
+
+ while (true) {
+ FCPMessage reply = FCPMessage.parse(is);
+ if ("NodeHello".equals(reply.messageType))
+ return;
+ if ("ProtocolError".equals(reply.messageType))
+ throw new IOException("Protocol error");
+ }
+ }
+
+ /**
+ * Simple FCP Put
+ *
+ * This method block until the data are fetchable or <code>PutFailed</code>
+ * received.
+ *
+ * @param freenetURI
+ * the freenet URI
+ * @param data
+ * the data to be put
+ * @param monitor
+ * progress monitor to post write completion to during the
+ * stream's close method.
+ * @param monitorTask
+ * task name to display during the close method.
+ * @return <code>true</code> iff <code>PutFetchable</code>,
+ * <code>PutSuccessful</code> or <code>PutFetchable</code> with
+ * insert collision (data already inserted)
+ * @throws IOException
+ * if any I/O error occurred
+ */
+ public boolean simplePut(String freenetURI, byte[] data,
+ ProgressMonitor monitor, String monitorTask) throws IOException {
+ System.err.println(freenetURI);
+
+ FCPMessage msg = new FCPMessage();
+ msg.messageType = "ClientPut";
+ msg.put("URI", freenetURI);
+ msg.put("Identifier", freenetURI);
+ msg.put("Verbosity", "1");
+ msg.put("Global", "false");
+ msg.put("UploadFrom", "direct");
+ msg.put("DataLength", "" + data.length);
+ msg.extraData = data;
+
+ msg.writeTo(os);
+
+ int totalBlocks = -1;
+ int completedBlocks = 0;
+
+ try {
+ while (true) {
+ FCPMessage reply = FCPMessage.parse(is);
+
+ if ("ProtocolError".equals(reply.messageType))
+ throw new IOException("Protocol error");
+ if (!freenetURI.equals(reply.get("Identifier"))) // identifier don't match, ignore it
+ continue;
+ if ("IdentifierCollision".equals(reply.messageType))
+ throw new IOException("IdentifierCollision");
+
+ if ("PutFailed".equals(reply.messageType)) {
+ if ("9".equals(reply.get("Code"))) // collision, data already inserted
+ return true;
+ return false;
+ }
+
+ if ("SimpleProgress".equals(reply.messageType)) {
+ if (totalBlocks == -1) {
+ totalBlocks = Integer.parseInt(reply.get("Total"));
+ monitor.beginTask(monitorTask, totalBlocks);
+ }
+ int tmp = Integer.parseInt(reply.get("Succeeded"));
+ if (tmp < totalBlocks)
+ monitor.update(tmp - completedBlocks);
+ completedBlocks = tmp;
+ }
+
+ if ("PutSuccessful".equals(reply.messageType))
+ return true;
+ if ("PutFetchable".equals(reply.messageType))
+ return true;
+ }
+ } finally {
+ if (totalBlocks == -1)
+ monitor.beginTask(monitorTask, 1);
+ monitor.endTask();
+ }
+ }
+
+ /**
+ * Simple FCP Get
+ *
+ * @param freenetURI
+ * the freenet URI
+ * @return the data
+ * @throws IOException
+ * if any I/O error occurred
+ * @throws FileNotFoundException
+ * if data not found
+ */
+ public byte[] simpleGet(String freenetURI) throws IOException,
+ FileNotFoundException {
+ FCPMessage msg = new FCPMessage();
+ msg.messageType = "ClientGet";
+ msg.put("URI", freenetURI);
+ msg.put("Identifier", freenetURI);
+ msg.put("Verbosity", "1");
+ msg.put("MaxSize", "2147483647");
+ msg.put("Global", "false");
+ msg.put("ClientToken", freenetURI);
+ msg.put("ReturnType", "direct");
+
+ msg.writeTo(os);
+
+ while (true) {
+ FCPMessage reply = FCPMessage.parse(is);
+ if ("ProtocolError".equals(reply.messageType))
+ throw new IOException("Protocol error");
+ if (!freenetURI.equals(reply.get("Identifier"))) // identifier don't match, ignore it
+ continue;
+ if ("IdentifierCollision".equals(reply.messageType))
+ throw new IOException("IdentifierCollision");
+
+ if ("GetFailed".equals(reply.messageType)) {
+ String redirectURI = reply.get("RedirectURI");
+ if (redirectURI != null)
+ return simpleGet(redirectURI);
+
+ System.err.println(" -> nf");
+ throw new FileNotFoundException();
+ }
+
+ if ("AllData".equals(reply.messageType))
+ return reply.extraData;
+ }
+ }
+
+ /**
+ * Generate SSK Key Pair
+ *
+ * @return the generated SSK key pair. <code>key[0]</code> is the public
+ * key, <code>key[1]</code> is the private key.
+ * @throws IOException
+ * if any I/O error occurred
+ */
+ public String[] generateSSK() throws IOException {
+ FCPMessage msg = new FCPMessage();
+ msg.messageType = "GenerateSSK";
+ msg.put("Identifier", "GenerateSSK");
+
+ msg.writeTo(os);
+
+ while (true) {
+ FCPMessage reply = FCPMessage.parse(is);
+ if ("ProtocolError".equals(reply.messageType))
+ throw new IOException("Protocol error");
+
+ if ("SSKKeypair".equals(reply.messageType)) {
+ String[] keys = new String[2];
+ keys[0] = reply.get("RequestURI");
+ keys[1] = reply.get("InsertURI");
+ return keys;
+ }
+ }
+ }
+
+ /**
+ * Close the connection
+ *
+ * @throws IOException
+ * if any I/O error occurred
+ */
+ public void close() throws IOException {
+ os.close();
+ is.close();
+ socket.close();
+ }
+
+ private static class FCPMessage {
+ private String messageType;
+
+ private Map<String, String> parameter = new HashMap<String, String>();
+
+ byte[] extraData;
+
+ FCPMessage() {
+ // default constructor
+ }
+
+ private static FCPMessage parse(InputStream in) throws IOException {
+ FCPMessage ret = new FCPMessage();
+ String line = readLine(in);
+ ret.messageType = line;
+
+ while (true) {
+ line = readLine(in);
+
+ if (line == null)
+ throw new IOException("Malformed FCP message");
+ if ("EndMessage".equals(line))
+ break;
+ if ("Data".equals(line)) {
+ String strLen = ret.parameter.get("DataLength");
+ if (strLen == null)
+ throw new IOException("DataLength not found");
+ int len;
+ try {
+ len = Integer.parseInt(strLen);
+ } catch (NumberFormatException e) {
+ throw new IOException("DataLength malformed");
+ }
+ ret.extraData = readData(in, len);
+ break;
+ }
+
+ String[] v = line.split("=", 2);
+ if (v.length != 2)
+ throw new IOException("No '=' found in: " + line);
+ ret.parameter.put(v[0], v[1]);
+ }
+
+ return ret;
+ }
+
+ void put(String key, String value) {
+ parameter.put(key, value);
+ }
+
+ String get(String key) {
+ return parameter.get(key);
+ }
+
+ void writeTo(OutputStream os) throws IOException {
+ os.write(messageType.getBytes("UTF-8"));
+ os.write('\n');
+
+ for (Map.Entry<String, String> e : parameter.entrySet()) {
+ String l = e.getKey() + '=' + e.getValue();
+ os.write(l.getBytes("UTF-8"));
+ os.write('\n');
+ }
+
+ os.write("EndMessage".getBytes("UTF-8"));
+ os.write('\n');
+
+ if (extraData != null)
+ os.write(extraData);
+ os.flush();
+ }
+ }
+
+ private static String readLine(InputStream in) throws IOException {
+ byte[] buf = new byte[256];
+ int offset = 0;
+
+ while (true) {
+ int b = in.read();
+ if (b == -1)
+ return null;
+ if (b == '\n') {
+ if (offset == 0)
+ continue; // skip empty line
+ break;
+ }
+
+ if (offset == buf.length) {
+ if (offset >= 4096)
+ throw new IOException("line too long");
+
+ byte[] buf2 = new byte[buf.length * 2];
+ System.arraycopy(buf, 0, buf2, 0, buf.length);
+ buf = buf2;
+ }
+
+ buf[offset++] = (byte) b;
+ }
+
+ return new String(buf, 0, offset, "UTF-8");
+ }
+
+ private static byte[] readData(InputStream in, int len) throws IOException {
+ byte[] buf = new byte[len];
+ int read = 0;
+ while (read < len) {
+ int r = in.read(buf, read, len - read);
+ if (r == -1)
+ throw new IOException("Not enough data");
+ read += r;
+ }
+ return buf;
+ }
+}
--
1.6.2.rc2