def startListening(self, addOld): assert self.i2pListeningSocket is None,'there is already one?!' if addOld: existingConns = [samId for samId in self.samIdToConnId.itervalues() if samId < 0] else: existingConns = None connId = self.i2pSockStatus.addConn(self.destId, 'tcpListen', 'out') self.i2pListeningSocket = SamTcpListeningSocket(self.i2pSockStatus, connId, existingConns) return connId
class SamTcpDestination(SamExtendedDestination): def __init__(self, destId, asyncSocketManager, realSockStatus, log, ip, port, sessionName, sessionDirection, sessionOptions, i2pSockStatus, defaultOutMaxQueueSize, defaultInMaxQueueSize, defaultInRecvLimitThreshold): #i2p socket self.i2pSockStatus = i2pSockStatus self.i2pSockets = {} self.i2pListeningSocket = None self.defaultOutMaxQueueSize = defaultOutMaxQueueSize self.defaultInMaxQueueSize = defaultInMaxQueueSize self.defaultInRecvLimitThreshold = defaultInRecvLimitThreshold #sam self.nextSamOutId = 1 self.nextSamInId = -1 #mapper self.connIdToSamId = {} #mapper from conn id to sam id for all active conns self.samIdToConnId = {} #mapper from sam id to conn id for all active conns #other self.failedSockets = set() #set of conn ids which are not active, meaning they already failed but were not removed self.waitingSockets = set() #set of conn ids of all outgoing conns which wait for session establishement to connect SamExtendedDestination.__init__(self, destId, asyncSocketManager, realSockStatus, log, ip, port, sessionName, 'tcp', sessionDirection, sessionOptions) ##internal functions - destinations def _establishedDestination(self): #send connect messages for all sockets which were created in the meantime for connId in self.waitingSockets: self._connectI2PSocket(connId, self.i2pSockets[connId]) self.waitingSockets.clear() SamExtendedDestination._establishedDestination(self) def _failDestination(self, reason): #fail sockets for connId in self.i2pSockets.keys(): if connId not in self.failedSockets: self.i2pSockets[connId].errorEvent('SESSION_FAILED') assert len(self.connIdToSamId)==0 and len(self.samIdToConnId)==0, 'failed all conns but active conns left?!' assert len(self.waitingSockets)==0, 'failed but still waiting?!' #clear listening socket if any if self.i2pListeningSocket is not None: self.i2pListeningSocket.clear() #reset status self.nextSamOutId = 1 self.nextSamInId = -1 SamExtendedDestination._failDestination(self, reason) def _removeDestination(self): #fail sockets for connId in self.i2pSockets.keys(): if not connId in self.failedSockets: self.i2pSockets[connId].errorEvent('SESSION_CLOSED') assert len(self.connIdToSamId)==0 and len(self.samIdToConnId)==0, 'failed all conns but active conns left?!' assert len(self.waitingSockets)==0, 'failed but still waiting?!' #remove sockets for i2pSocket in self.i2pSockets.values(): i2pSocket.close(force=True) #listening socket if self.i2pListeningSocket is not None: self.i2pSockStatus.removeConn(self.i2pListeningSocket.fileno()) self.i2pListeningSocket = None SamExtendedDestination._removeDestination(self) ##internal functions - sockets def _addOutgoingI2PSocket(self, remoteDest, inMaxQueueSize=None, outMaxQueueSize=None, inRecvLimitThreshold=None): #set defaults if inMaxQueueSize is None: inMaxQueueSize = self.defaultInMaxQueueSize if outMaxQueueSize is None: outMaxQueueSize = self.defaultOutMaxQueueSize if inRecvLimitThreshold is None: inRecvLimitThreshold = self.defaultInRecvLimitThreshold #add to status obj connId = self.i2pSockStatus.addConn(self.destId, 'tcpOut', 'out') #create socket object conn = SamTcpSocket(self._sendOverRealSocket, self._failI2PSocket, self._removeI2PSocket, self.i2pSockStatus, connId, None, 'out', remoteDest, inMaxQueueSize, outMaxQueueSize, inRecvLimitThreshold) self.i2pSockets[connId] = conn #connect if self.sessionEstablished: self._connectI2PSocket(connId, conn) else: self.waitingSockets.add(connId) return connId def _addIncommingI2PSocket(self, samId, remoteDest): #add to status obj connId = self.i2pSockStatus.addConn(self.destId, 'tcpIn', 'in') #increment samId counter assert self.nextSamInId >= samId , 'Wrong assumption about sam id?!' self.nextSamInId = samId - 1 #add to mapper self.connIdToSamId[connId] = samId self.samIdToConnId[samId] = connId #create socket object conn = SamTcpSocket(self._sendOverRealSocket, self._failI2PSocket, self._removeI2PSocket, self.i2pSockStatus, connId, samId, 'in', remoteDest, self.defaultInMaxQueueSize, self.defaultOutMaxQueueSize, self.defaultInRecvLimitThreshold) self.i2pSockets[connId] = conn #connect event conn.connectEvent() #add to listening socket if self.i2pListeningSocket is not None: self.i2pListeningSocket.acceptEvent(connId, remoteDest) def _allocateSamId(self, connId): samId = self.nextSamOutId self.nextSamOutId += 1 #add to mapper self.connIdToSamId[connId] = samId self.samIdToConnId[samId] = connId return samId def _connectI2PSocket(self, connId, i2pSocket): remoteDest = i2pSocket.getRemoteDestination() if len(remoteDest) == 516 and remoteDest.endswith('AAAA'): #full i2p destination samId = self._allocateSamId(connId) i2pSocket.connect(remoteDest, samId) else: #not a real destination but some kind of name self._addNameLookupQuery(connId, remoteDest, self._finishedNameLookup, funcArgs=[connId]) def _finishedNameLookup(self, connId, success, msg): if success: samId = self._allocateSamId(connId) self.i2pSockets[connId].connect(msg, samId) else: self.i2pSockets[connId].errorEvent('FAILED_NAME_LOOKUP') def _failI2PSocket(self, connId): "called when a conn failed, either an already connected one or a connecting one" i2pSocket = self.i2pSockets[connId] samId = self.connIdToSamId.get(connId) if samId is None: #outgoing conn with name query, remove self._removeNameLookupQuery(connId) else: #active conn, remove from id mapper del self.connIdToSamId[connId] del self.samIdToConnId[samId] #set changes self.waitingSockets.discard(connId) self.failedSockets.add(connId) def _removeI2PSocket(self, connId): "called when close() was called for a socket" #remove from set self.failedSockets.remove(connId) #remove from socket dict del self.i2pSockets[connId] #remove from status obj self.i2pSockStatus.removeConn(connId) ##internal functions - messages def _handleCustomMessage(self, message): messageType = message['msgType'] messageParas = message['msgParas'] if messageType=='STREAM STATUS': #a outgoing sam tcp-socket connected ... or failed samId = int(messageParas['ID']) connId = self.samIdToConnId.get(samId) if connId is not None: #i2p socket still exists i2pSocket = self.i2pSockets[connId] if messageParas['RESULT'].upper() == 'OK': #socket connected i2pSocket.connectEvent() else: #failed i2pSocket.errorEvent(messageParas['RESULT']) elif messageType=='STREAM CONNECTED': #got a new incomming tcp-like connection samId = int(messageParas['ID']) self._addIncommingI2PSocket(samId, messageParas['DESTINATION']) elif messageType=='STREAM SEND': #status info about a previous STREAM SEND command samId = int(messageParas['ID']) connId = self.samIdToConnId.get(samId) if connId is not None: #socket still exists i2pSocket = self.i2pSockets[connId] if messageParas['RESULT'].upper() == 'OK': #data was send i2pSocket.sendSucceededEvent() else: #for some reason, the send failed - probably a bug, but a bug in this lib or in sam? i2pSocket.sendFailedEvent() if messageParas['STATE'].upper() == 'READY': #the buffer of the sam bridge is not full, allowed to send more i2pSocket.sendEvent() elif messageType=='STREAM READY_TO_SEND': samId = int(messageParas['ID']) connId = self.samIdToConnId.get(samId) if connId is not None: #socket still exists self.i2pSockets[connId].sendEvent() elif messageType=='STREAM CLOSED': samId = int(messageParas['ID']) connId = self.samIdToConnId.get(samId) if connId is not None: #socket still exists if messageParas['RESULT'].upper() == 'OK': #OK isn't a good error reason ... messageParas['RESULT'] = 'CLOSED_BY_PEER' self.i2pSockets[connId].errorEvent(messageParas['RESULT']) elif messageType=='STREAM RECEIVED': samId = int(messageParas['ID']) connId = self.samIdToConnId.get(samId, None) if connId is not None: #socket still exists self.i2pSockets[connId].recvEvent(''.join(message['Data'])) else: if self.log is not None: self.log.error('Got unknown message "%s" with the following paras:\n%s', messageType, '\n'.join(tup[0]+': '+tup[1] for tup in messageParas.iteritems())) ##external functions - sockets - normal def connect(self, remoteDest, inMaxQueueSize=None, outMaxQueueSize=None, inRecvLimitThreshold=None): return self._addOutgoingI2PSocket(remoteDest, inMaxQueueSize, outMaxQueueSize, inRecvLimitThreshold) def send(self, connId, data): return self.i2pSockets[connId].send(data) def recv(self, connId, max, peekOnly): return self.i2pSockets[connId].recv(max, peekOnly) def close(self, connId, force): if connId in self.i2pSockets: self.i2pSockets[connId].close(force) ##external functions - sockets - listening def startListening(self, addOld): assert self.i2pListeningSocket is None,'there is already one?!' if addOld: existingConns = [samId for samId in self.samIdToConnId.itervalues() if samId < 0] else: existingConns = None connId = self.i2pSockStatus.addConn(self.destId, 'tcpListen', 'out') self.i2pListeningSocket = SamTcpListeningSocket(self.i2pSockStatus, connId, existingConns) return connId def accept(self, max): return self.i2pListeningSocket.accept(max) def stopListening(self, connId): assert self.i2pListeningSocket.fileno() == connId, 'connId doesn\'t match id of listening socket!' self.i2pSockStatus.removeConn(connId) self.i2pListeningSocket = None ##external functions - change settings def changeDefaultQueueSize(self, defaultInMaxQueueSize=None, defaultOutMaxQueueSize=None): if defaultInMaxQueueSize is not None: self.defaultInMaxQueueSize = defaultInMaxQueueSize if defaultOutMaxQueueSize is not None: self.defaultOutMaxQueueSize = defaultOutMaxQueueSize def changeDefaultInRecvLimitThreshold(self, defaultInRecvLimitThreshold): self.defaultInRecvLimitThreshold = self.defaultInRecvLimitThreshold ##external functions - get info def getI2PSocketRemoteDestination(self, connId): return self.i2pSockets[connId].getRemoteDestination() def getI2PSocketUsedInBufferSpace(self, connId): return self.i2pSockets[connId].getUsedInBufferSpace() def getI2PSocketFreeOutBufferSpace(self, connId): return self.i2pSockets[connId].getFreeOutBufferSpace() def getI2PSocketErrorReason(self, connId): return self.i2pSockets[connId].getErrorReason()