This repository has been archived on 2024-05-09. You can view files and clone it, but cannot push or open issues/pull-requests.
ipodderx-core/khashmir/krpc.py

244 lines
8.8 KiB
Python

# The contents of this file are subject to the BitTorrent Open Source License
# Version 1.1 (the License). You may not copy or use this file, in either
# source code or executable form, except in compliance with the License. You
# may obtain a copy of the License at http://www.bittorrent.com/license/.
#
# Software distributed under the License is distributed on an AS IS basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
from BitTorrent.defer import Deferred
from BitTorrent.bencode import bencode, bdecode
import socket
from BitTorrent.RawServer_magic import Handler
from BitTorrent.platform import bttime
import time
from math import log10
import sys
from traceback import print_exc
from khash import distance
from cache import Cache
from KRateLimiter import KRateLimiter
from hammerlock import Hammerlock
from const import *
# commands
TID = 't'
REQ = 'q'
RSP = 'r'
TYP = 'y'
ARG = 'a'
ERR = 'e'
class KRPCFailSilently(Exception):
pass
class KRPCProtocolError(Exception):
pass
class KRPCServerError(Exception):
pass
class KRPCSelfNodeError(Exception):
pass
class hostbroker(Handler):
def __init__(self, server, addr, transport, call_later, max_ul_rate, config, rlcount):
self.server = server
self.addr = addr
self.transport = transport
self.rltransport = KRateLimiter(transport, max_ul_rate, call_later, rlcount, config['max_rate_period'])
self.call_later = call_later
self.connections = Cache(touch_on_access=True)
self.hammerlock = Hammerlock(100, call_later)
self.expire_connections(loop=True)
self.config = config
if not self.config.has_key('pause'):
self.config['pause'] = False
def expire_connections(self, loop=False):
self.connections.expire(bttime() - KRPC_CONNECTION_CACHE_TIME)
if loop:
self.call_later(self.expire_connections, KRPC_CONNECTION_CACHE_TIME, (True,))
def data_came_in(self, addr, datagram):
#if addr != self.addr:
if not self.config['pause'] and self.hammerlock.check(addr):
c = self.connectionForAddr(addr)
c.datagramReceived(datagram, addr)
def connection_lost(self, socket):
## this is like, bad
print ">>> connection lost!", socket
def connectionForAddr(self, addr):
if addr == self.addr:
raise KRPCSelfNodeError()
if not self.connections.has_key(addr):
conn = KRPC(addr, self.server, self.transport, self.rltransport, self.call_later)
self.connections[addr] = conn
else:
conn = self.connections[addr]
return conn
## connection
class KRPC:
noisy = 0
def __init__(self, addr, server, transport, rltransport, call_later):
self.call_later = call_later
self.transport = transport
self.rltransport = rltransport
self.factory = server
self.addr = addr
self.tids = {}
self.mtid = 0
self.pinging = False
def sendErr(self, addr, tid, msg):
## send error
out = bencode({TID:tid, TYP:ERR, ERR :msg})
olen = len(out)
self.rltransport.sendto(out, 0, addr)
return olen
def datagramReceived(self, str, addr):
# bdecode
try:
msg = bdecode(str)
except Exception, e:
if self.noisy:
print "response decode error: " + `e`, `str`
else:
#if self.noisy:
# print msg
# look at msg type
if msg[TYP] == REQ:
ilen = len(str)
# if request
# tell factory to handle
f = getattr(self.factory ,"krpc_" + msg[REQ], None)
msg[ARG]['_krpc_sender'] = self.addr
if f and callable(f):
try:
ret = apply(f, (), msg[ARG])
except KRPCFailSilently:
pass
except KRPCServerError, e:
olen = self.sendErr(addr, msg[TID], "Server Error: %s" % e.args[0])
except KRPCProtocolError, e:
olen = self.sendErr(addr, msg[TID], "Protocol Error: %s" % e.args[0])
except Exception, e:
print_exc(20)
olen = self.sendErr(addr, msg[TID], "Server Error")
else:
if ret:
# make response
out = bencode({TID : msg[TID], TYP : RSP, RSP : ret})
else:
out = bencode({TID : msg[TID], TYP : RSP, RSP : {}})
# send response
olen = len(out)
self.rltransport.sendto(out, 0, addr)
else:
if self.noisy:
#print "don't know about method %s" % msg[REQ]
pass
# unknown method
out = bencode({TID:msg[TID], TYP:ERR, ERR : KRPC_ERROR_METHOD_UNKNOWN})
olen = len(out)
self.rltransport.sendto(out, 0, addr)
if self.noisy:
try:
ndist = 10 * log10(2**160 * 1.0 / distance(self.factory.node.id, msg[ARG]['id']))
ndist = int(ndist)
except OverflowError:
ndist = 999
h = None
if msg[ARG].has_key('target'):
h = msg[ARG]['target']
elif msg[ARG].has_key('info_hash'):
h = msg[ARG]['info_hash']
else:
tdist = '-'
if h != None:
try:
tdist = 10 * log10(2**160 * 1.0 / distance(self.factory.node.id, h))
tdist = int(tdist)
except OverflowError:
tdist = 999
t = time.localtime()
t = "%2d-%2d-%2d %2d:%2d:%2d" % (t[0], t[1], t[2], t[3], t[4], t[5])
print "%s %s %s >>> %s - %s %s %s - %s %s" % (t,
msg[ARG]['id'].encode('base64')[:4],
addr,
self.factory.node.port,
ilen,
msg[REQ],
olen,
ndist,
tdist)
elif msg[TYP] == RSP:
# if response
# lookup tid
if self.tids.has_key(msg[TID]):
df = self.tids[msg[TID]]
# callback
del(self.tids[msg[TID]])
df.callback({'rsp' : msg[RSP], '_krpc_sender': addr})
else:
# no tid, this transaction timed out already...
pass
elif msg[TYP] == ERR:
# if error
# lookup tid
if self.tids.has_key(msg[TID]):
df = self.tids[msg[TID]]
# callback
df.errback(msg[ERR])
del(self.tids[msg[TID]])
else:
# day late and dollar short
pass
else:
print "unknown message type " + `msg`
# unknown message type
df = self.tids[msg[TID]]
# callback
df.errback(KRPC_ERROR_RECEIVED_UNKNOWN)
del(self.tids[msg[TID]])
def sendRequest(self, method, args):
# make message
# send it
msg = {TID : chr(self.mtid), TYP : REQ, REQ : method, ARG : args}
self.mtid = (self.mtid + 1) % 256
s = bencode(msg)
d = Deferred()
self.tids[msg[TID]] = d
self.call_later(self.timeOut, KRPC_TIMEOUT, (msg[TID],))
self.call_later(self._send, 0, (s, d))
return d
def timeOut(self, id):
if self.tids.has_key(id):
df = self.tids[id]
del(self.tids[id])
df.errback(KRPC_ERROR_TIMEOUT)
def _send(self, s, d):
try:
self.transport.sendto(s, 0, self.addr)
except socket.error:
d.errback(KRPC_SOCKET_ERROR)