Skip to content

Commit

Permalink
Add API method to generate an I2P Destination
Browse files Browse the repository at this point in the history
  • Loading branch information
str4d committed Sep 16, 2015
1 parent 039e605 commit 0bde564
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 32 deletions.
15 changes: 15 additions & 0 deletions txi2p/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from twisted.internet.endpoints import clientFromString

from txi2p.helpers import getApi
from txi2p.sam import api as samApi


_apiGenerators = {
'SAM': samApi.generateDestination,
}

def generateDestination(reactor, keyfile, api=None, apiEndpoint=None):
api, apiEndpoint = getApi(api, apiEndpoint, _apiGenerators)
if isinstance(apiEndpoint, str):
apiEndpoint = clientFromString(reactor, apiEndpoint)
return _apiGenerators[api](keyfile, apiEndpoint)
22 changes: 22 additions & 0 deletions txi2p/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
DEFAULT_ENDPOINT = {
'BOB': 'tcp:127.0.0.1:2827',
'SAM': 'tcp:127.0.0.1:7656',
}

DEFAULT_API = 'BOB'


def getApi(api, apiEndpoint, apiDict):
if not api:
if apiEndpoint:
raise ValueError('api must be specified if apiEndpoint is given')
else:
api = DEFAULT_API

if api not in apiDict:
raise ValueError('Specified I2P API is invalid or unsupported')

if not apiEndpoint:
apiEndpoint = DEFAULT_ENDPOINT[api]

return (api, apiEndpoint)
34 changes: 3 additions & 31 deletions txi2p/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,12 @@
from zope.interface import implementer

from txi2p.bob.endpoints import BOBI2PClientEndpoint, BOBI2PServerEndpoint
from txi2p.helpers import getApi
from txi2p.sam.endpoints import (
SAMI2PStreamClientEndpoint,
SAMI2PStreamServerEndpoint,
)

DEFAULT_ENDPOINT = {
'BOB': 'tcp:127.0.0.1:2827',
'SAM': 'tcp:127.0.0.1:7656',
}

DEFAULT_API = 'BOB'

if not _PY3:
from twisted.plugin import IPlugin
else:
Expand Down Expand Up @@ -56,18 +50,7 @@ def _parseSAMClient(self, reactor, host, port, samEndpoint,

def _parseClient(self, reactor, host, port=None,
api=None, apiEndpoint=None, **kwargs):
if not api:
if apiEndpoint:
raise ValueError('api must be specified if apiEndpoint is given')
else:
api = DEFAULT_API

if api not in self._apiParsers:
raise ValueError('Specified I2P API is invalid or unsupported')

if not apiEndpoint:
apiEndpoint = DEFAULT_ENDPOINT[api]

api, apiEndpoint = getApi(api, apiEndpoint, self._apiParsers)
return self._apiParsers[api](self, reactor, host,
port and int(port) or None,
apiEndpoint, **kwargs)
Expand Down Expand Up @@ -107,18 +90,7 @@ def _parseSAMServer(self, reactor, keypairPath, port, samEndpoint,

def _parseServer(self, reactor, keypairPath, port=None,
api=None, apiEndpoint=None, **kwargs):
if not api:
if apiEndpoint:
raise ValueError('api must be specified if apiEndpoint is given')
else:
api = DEFAULT_API

if api not in self._apiParsers:
raise ValueError('Specified I2P API is invalid or unsupported')

if not apiEndpoint:
apiEndpoint = DEFAULT_ENDPOINT[api]

api, apiEndpoint = getApi(api, apiEndpoint, self._apiParsers)
return self._apiParsers[api](self, reactor, keypairPath,
port and int(port) or None,
apiEndpoint, **kwargs)
Expand Down
8 changes: 8 additions & 0 deletions txi2p/sam/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from txi2p.sam.session import DestGenerateFactory


def generateDestination(keyfile, samEndpoint):
destFac = DestGenerateFactory(keyfile)
d = samEndpoint.connect(destFac)
d.addCallback(lambda proto: destFac.deferred)
return d
41 changes: 40 additions & 1 deletion txi2p/sam/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import os
from parsley import makeProtocol
from twisted.internet import defer, error
from twisted.python import log
from twisted.python import failure, log

from txi2p import grammar
from txi2p.address import I2PAddress
Expand Down Expand Up @@ -155,3 +155,42 @@ def createSession((samVersion, style, id, proto, pubKey)):
d.addCallback(lambda proto: sessionFac.deferred)
d.addCallback(createSession)
return d


class DestGenerateSender(SAMSender):
def sendDestGenerate(self):
self.transport.write('DEST GENERATE\n')


class DestGenerateReceiver(SAMReceiver):
def command(self):
self.sender.sendDestGenerate()
self.currentRule = 'State_dest'

def destGenerated(self, pub, priv):
self.factory.destGenerated(pub, priv)
self.sender.transport.loseConnection()


# A Protocol for generating an I2P Destination via SAM
DestGenerateProtocol = makeProtocol(
grammar.samGrammarSource,
DestGenerateSender,
DestGenerateReceiver)


class DestGenerateFactory(SAMFactory):
protocol = DestGenerateProtocol

def __init__(self, keyfile):
self._keyfile = keyfile
self.deferred = defer.Deferred(self._cancel)

def destGenerated(self, pubKey, privKey):
try:
f = open(self._keyfile, 'w')
f.write(privKey)
f.close()
self.deferred.callback(I2PAddress(pubKey))
except IOError as e:
self.deferred.errback(failure.Failure(e))
52 changes: 52 additions & 0 deletions txi2p/sam/test/test_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from twisted.test import proto_helpers
from twisted.trial import unittest

from txi2p.address import I2PAddress
from txi2p.sam import session
from txi2p.test.util import TEST_B64
from .util import SAMProtocolTestMixin, SAMFactoryTestMixin
Expand Down Expand Up @@ -240,3 +241,54 @@ def test_getSession_existingNickname_withoutEndpoint(self):
self.assertEqual(1, samEndpoint.called)
self.assertEqual(s, s2)
test_getSession_existingNickname_withoutEndpoint.skip = skipSRO


class TestDestGenerateProtocol(SAMProtocolTestMixin, unittest.TestCase):
protocol = session.DestGenerateProtocol

def test_destGenerateAfterHello(self):
fac, proto = self.makeProto()
proto.transport.clear()
proto.dataReceived('HELLO REPLY RESULT=OK VERSION=3.1\n')
self.assertEquals('DEST GENERATE\n', proto.transport.value())

def test_destGenerated(self):
fac, proto = self.makeProto()
fac.destGenerated = Mock()
proto.transport.clear()
proto.dataReceived('HELLO REPLY RESULT=OK VERSION=3.1\n')
proto.transport.clear()
proto.dataReceived('DEST REPLY PUB=%s PRIV=%s\n' % (TEST_B64, 'TEST_PRIV'))
fac.destGenerated.assert_called_with(TEST_B64, 'TEST_PRIV')


class TestDestGenerateFactory(SAMFactoryTestMixin, unittest.TestCase):
factory = session.DestGenerateFactory
blankFactoryArgs = ('',)

def test_destGenerated(self):
tmp = '/tmp/TestDestGenerateFactory.privKey'
mreactor = proto_helpers.MemoryReactor()
fac, proto = self.makeProto(tmp)
# Shortcut to end of SAM dest generate protocol
proto.receiver.currentRule = 'State_dest'
proto._parser._setupInterp()
proto.dataReceived('DEST REPLY PUB=%s PRIV=%s\n' % (TEST_B64, 'TEST_PRIV'))
s = self.successResultOf(fac.deferred)
self.assertEqual(I2PAddress(TEST_B64), s)
os.remove(tmp)
test_destGenerated.skip = skipSRO

def test_destGenerated_privKeySaved(self):
tmp = '/tmp/TestDestGenerateFactory.privKey'
mreactor = proto_helpers.MemoryReactor()
fac, proto = self.makeProto(tmp)
# Shortcut to end of SAM dest generate protocol
proto.receiver.currentRule = 'State_dest'
proto._parser._setupInterp()
proto.dataReceived('DEST REPLY PUB=%s PRIV=%s\n' % (TEST_B64, 'TEST_PRIV'))
f = open(tmp, 'r')
privKey = f.read()
f.close()
self.assertEqual('TEST_PRIV', privKey)
os.remove(tmp)

0 comments on commit 0bde564

Please sign in to comment.