The latest version of freenetlib is attached to this.  It now
supports QueryRestarted messages and automatically closes connections
when the corresponding Freenet object is garbage collected.

-- 
Travis Bemann
-------------- next part --------------
#!/usr/bin/env python

# A Freenet client module.  May rapidly change during the development of
# the Freenet protocal.
#
# freenetlib.py 0.05
#
# Copyright (C) 2000 Travis Bemann, Itamar Shtull-Trauring
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# more information on Freenet is availiable at http://freenet.sourceforge.net/

import re
import socket
import string
import os
import whrandom
import sha
import struct
import sys

# Default values for Freenet init parameters

FREENET_DEFAULT_HOST = 'localhost'
FREENET_DEFAULT_PORT = 19114
FREENET_DEFAULT_SEARCH_DEPTH = 10
FREENET_VERSION = '1.2'
FREENET_DEFAULT_MIN_DEPTH = 5
FREENET_DEFAULT_MAX_DEPTH = 30
FREENET_DEFAULT_KEEP_ALIVE = 1
FREENET_DEFAULT_AUTO_CONNECT = 1

try:
        dev_random = open("/dev/urandom", "r")
except:
        dev_random = None

DEBUG = 0

error_reply = 'freenetlib.error_reply'
error_uniqueid = 'freenetlib.error_uniqueid'
error_version = 'freenetlib.error_version'
error_nodata = 'freenetlib.error_nodata'
error_keyexists = 'freenetlib.error_keyexists'

# Note -  This is a slightly modified version of a bit of Python
# code sent to me by Bill Trost <trost at cloud.rain.com>.
# This is fast as in it is fast to use in a program, not that the
# implementation used is necessarily fast.
def quick_sha1(s):
        return "%X%X%X%X%X" % struct.unpack("!lllll", sha.new(s).digest())

class Freenet:

        # Initialize an instance
        # - host: host of FreeNet server
        # - port: port of FreeNet server
        # - search_depth: maximum key search depth
        # - min_depth: minimum starting message depth
        # - max_depth: maximum starting message depth
        # - keep_alive: whether to keep the connection open
        # - auto_connect: whether to handshake automatically

        def __init__(self,
                        host = FREENET_DEFAULT_HOST,
                        port = FREENET_DEFAULT_PORT,
                        search_depth = FREENET_DEFAULT_SEARCH_DEPTH,
                        min_depth = FREENET_DEFAULT_MIN_DEPTH,
                        max_depth = FREENET_DEFAULT_MAX_DEPTH,
                        keep_alive = FREENET_DEFAULT_KEEP_ALIVE,
                        auto_connect = FREENET_DEFAULT_AUTO_CONNECT):
                self.host = host
                self.port = port
                if search_depth < 1:
                        raise ValueError, \
                                'search_depth must be larger than zero'
                self.search_depth = search_depth
                if min_depth < 1:
                        raise ValueError, 'min_depth must be larger than zero'
                if max_depth < 1:
                        raise ValueError, 'max_depth must be larger than zero'
                if min_depth > max_depth:
                        raise ValueError, \
                                'min_depth may not be larger than max_depth'
                self.min_depth = min_depth
                self.max_depth = max_depth
                self.keep_alive = keep_alive
                self.response_closed = 1                
                if auto_connect:
                        self.connect()

        def __del__(self):
                if not self.response_closed:
                        self.close()

        def sendmesg(self, type, items, datafile=None, datalength=0):
                """ Construct message from dictionary, with optional file for 
data """
                if DEBUG:
                        print "Sent:", type, items
                f = self.file
                f.write(type + '\n')
                for key, value in items.items():
                        f.write("%s=%s\n" % (key, value))

                if datafile:
                        if datalength:
                                f.write("DataLength=%s\n" % datalength)
                                f.write("Data\n")
                                line = datafile.readline()
                                while line:
                                        f.write(line)
                                        line = datafile.readline()
                        else:
                                datafile.seek(0, 2)
                datalength = datafile.tell()
                datafile.seek(0, 0)
                                f.write('DataLength=%s\n' % datalength)
                                f.write('Data\n')
                                line = datafile.readline()
                                while line:
                                        f.write(line)
                                        line = datafile.readline()
                else:
                        f.write("EndMessage\n")

                f.flush()                       

        def recvmesg(self, savefile=None):
                """ Read a message, writing it to a given file if need be """
                f = self.file
                dict = {}
                messagetype = string.rstrip(f.readline())

                line = string.rstrip(f.readline())
                while line and (line not in ['Data', 'EndMessage']):
                        key, value = string.split(line, '=')
                        dict[key] = value
                        line = string.rstrip(f.readline())
                if line == 'Data':                      
                        #if not dict.has_key('DataLength'):
                        #       raise error_nodatalength, "Message has Data but 
no DataLength."
                        line = f.readline()
                        readlength = 0
                        while line:
                                savefile.write(line)
                                readlength = readlength + len(line)
                                line = f.readline()
                        #if readlength != int(dict['DataLength']):
                        #       raise error_nodata, """DataLength supposed to 
be %s, but
                        #               actual Data length is %s.""" % 
(dict['DataLength'], readlength)
                if DEBUG:
                        print "Received:", messagetype, dict
                return messagetype, dict

        def getranduniqueid(self):
                """ Generate 64 bit hex id number """
                result = ""
                if dev_random:
                        s = dev_random.read(8)
                        for char in s:
                                a, b = divmod(ord(char), 16)
                                result = "%s%01x%01x" % (result, a, b)
                else:
                        max = pow(2, 16) - 1               
                        for i in range(0, 4):
                                result = result + ("%04x" % whrandom.randint(0, 
max))
                # the server strip leading 0 from uniqueids, so we do the same
                i = 0
                while result[i] == '0': i = i + 1
                return result[i:]

        def getranddepth(self):
                """ Returns a random number between self.min_depth 
                        and self.max_depth.
                """
                return whrandom.randint(self.min_depth, self.max_depth)

        def connect(self):
                """ Connect to server """
                self.sock = socket.socket(socket.AF_INET,
                        socket.SOCK_STREAM)
                self.sock.connect(self.host, self.port)
                self.response_closed = 0
                self.file = self.sock.makefile('rb+')                   
                self._handshake()

        def close(self):
                """ close from server """
                self.response_closed = 1
                self.sock.close()

        def _handshake(self):
                """ Handshake with server.  """

                uid = self.getranduniqueid()
                if self.keep_alive:
                        keep_alive = 'true'
                else:
                        keep_alive = 'false'
                self.sendmesg('HandshakeRequest',
                        {'UniqueID': uid,
                        'Depth': 1,
                        'HopsToLive': self.search_depth,
                        'KeepAlive': keep_alive})
                type, items = self.recvmesg()
                if type == 'HandshakeRequest':
                        try:
                                self.sendmesg('HandshakeReply',
                                        {'UniqueID': items['UniqueID'],
                                        'Depth': items['Depth'],
                                        'HopsToLive': items['HopsToLive'],
                                        'KeepAlive': keep_alive,
                                        'Version': FREENET_VERSION})
                        except KeyError:
                                self.close()
                                raise error_reply, \
                                        'Necessary request fields are missing'
                elif type == 'HandshakeReply':
                        try:
                                if items['Version'] != FREENET_VERSION:
                                        self.close()
                                        raise error_version, \
                                                'Incompatible FreeNet version'
                                if items['UniqueID'] != uid:
                                        self.close()
                                        raise error_uniqueid, \
                                                'Incorrect unique ID'
                        except KeyError:

                                raise error_version, \
                                        'Necessary reply fields are missing'
                else:
                        self.close()
                        raise error_reply, 'Unexpected message type'


        def insert(self, key, datafile, datalength = None, search_depth = None):
                """ Insert file under key
                        key: key to insert file under (unencrypted)
                        data: file data
                """
                if not self.keep_alive and self.response_closed:
                        self.connect()
                search_depth = search_depth or self.search_depth
                uid = self.getranduniqueid()
                if self.keep_alive:
                        keep_alive = 'true'
                else:
                        keep_alive = 'false'
                self.sendmesg('InsertRequest',
                        {'UniqueID': uid,
                        'Depth': self.getranddepth(),
                        'HopsToLive': search_depth,
                        'KeepAlive': keep_alive,
                        'Source': 'tcp/127.0.0.1:19114',
                        'SearchKey': quick_sha1(key)})
                done = 0
                while not done:
                        done = 1
                        type, items = self.recvmesg()
                        if type == 'RequestFailed':
                                if not self.keep_alive:
                                        self.close()
                                raise error_reply, 'Request failed'
                        elif type == 'DataReply':
                                if not self.keep_alive:
                                        self.close()
                                        raise error_keyexists, 'Key already 
exists'
                        elif type == 'TimedOut':
                                if not self.keep_alive:
                                        self.close()
                                raise error_nodata, \
                                        'Key already exists but no data was 
found'
                        elif type == 'QueryRestarted':
                                done = 0
                        elif type == 'InsertReply':
                                if items['UniqueID'] != uid:
                                        if not self.keep_alive:
                                                self.close()
                                        raise error_uniqueid, \
                                                'Unique ID mismatch'
                                self.sendmesg('DataInsert',
                                        {'UniqueID': uid,
                                        'Depth': self.getranddepth(),
                                        'HopsToLive': search_depth,
                                        'KeepAlive': keep_alive,
                                        'Source': 'tcp/127.0.0.1:19114',
                                        'DataSource': 'tcp/127.0.0.1:19114'},
                                        datafile, datalength)
                        else:
                                if not self.keep_alive:
                                        self.close()
                                raise error_reply, 'Unexpected message type'
                        if not self.keep_alive:
                                self.close()

        def request(self, key, savefile=sys.stdout, search_depth=None):
                """ Request file under key
                        key: key to request file from (unencrypted)
                        returns: file data
                """
                if not self.keep_alive and self.response_closed:
                        self.connect()
                search_depth = search_depth or self.search_depth
                uid = self.getranduniqueid()
                if self.keep_alive:
                        keep_alive = 'true'
                else:
                        keep_alive = 'false'
                self.sendmesg('DataRequest',
                        {'UniqueID': uid,
                        'Depth': self.getranddepth(),
                        'HopsToLive': search_depth,
                        'KeepAlive': keep_alive,
                        'Source': 'tcp/127.0.0.1:19114',
                        'SearchKey': quick_sha1(key)})
                done = 0
                while not done:
                        done = 1
                        type, items = self.recvmesg(savefile)
                        if type == 'RequestFailed':
                                if not self.keep_alive:
                                        self.close()
                                raise error_reply, 'Request failed'
                        elif type == 'TimedOut':
                                if not self.keep_alive:
                                        self.close()
                                raise error_keyexists, \
                                        'Key exists but no data was found'
                        elif type == 'QueryRestarted':
                                done = 0
                        elif type == 'DataReply':
                                if items['UniqueID'] != uid:
                                        if not self.keep_alive:
                                                self.close()
                                        raise error_uniqueid, \
                                                'Unique ID mismatch'
                                return None
                        else:
                                if not self.keep_alive:
                                        self.close()
                                raise error_reply, 'Unexpected message type'
                return None # shouldn't happen(?)

if __name__ == '__main__':
        fn = Freenet(search_depth=11)
        fn.request("README", search_depth=5)

Reply via email to