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)
