123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398 |
- # -*- test-case-name: twisted.spread.test.test_banana -*-
- # Copyright (c) Twisted Matrix Laboratories.
- # See LICENSE for details.
- """
- Banana -- s-exp based protocol.
- Future Plans: This module is almost entirely stable. The same caveat applies
- to it as applies to L{twisted.spread.jelly}, however. Read its future plans
- for more details.
- @author: Glyph Lefkowitz
- """
- from __future__ import absolute_import, division
- import copy, struct
- from io import BytesIO
- from twisted.internet import protocol
- from twisted.persisted import styles
- from twisted.python import log
- from twisted.python.compat import iterbytes, long, _bytesChr as chr
- from twisted.python.reflect import fullyQualifiedName
- class BananaError(Exception):
- pass
- def int2b128(integer, stream):
- if integer == 0:
- stream(chr(0))
- return
- assert integer > 0, "can only encode positive integers"
- while integer:
- stream(chr(integer & 0x7f))
- integer = integer >> 7
- def b1282int(st):
- """
- Convert an integer represented as a base 128 string into an L{int} or
- L{long}.
- @param st: The integer encoded in a byte string.
- @type st: L{bytes}
- @return: The integer value extracted from the byte string.
- @rtype: L{int} or L{long}
- """
- e = 1
- i = 0
- for char in iterbytes(st):
- n = ord(char)
- i += (n * e)
- e <<= 7
- return i
- # delimiter characters.
- LIST = chr(0x80)
- INT = chr(0x81)
- STRING = chr(0x82)
- NEG = chr(0x83)
- FLOAT = chr(0x84)
- # "optional" -- these might be refused by a low-level implementation.
- LONGINT = chr(0x85)
- LONGNEG = chr(0x86)
- # really optional; this is part of the 'pb' vocabulary
- VOCAB = chr(0x87)
- HIGH_BIT_SET = chr(0x80)
- def setPrefixLimit(limit):
- """
- Set the limit on the prefix length for all Banana connections
- established after this call.
- The prefix length limit determines how many bytes of prefix a banana
- decoder will allow before rejecting a potential object as too large.
- @type limit: L{int}
- @param limit: The number of bytes of prefix for banana to allow when
- decoding.
- """
- global _PREFIX_LIMIT
- _PREFIX_LIMIT = limit
- _PREFIX_LIMIT = None
- setPrefixLimit(64)
- SIZE_LIMIT = 640 * 1024 # 640k is all you'll ever need :-)
- class Banana(protocol.Protocol, styles.Ephemeral):
- """
- L{Banana} implements the I{Banana} s-expression protocol, client and
- server.
- @ivar knownDialects: These are the profiles supported by this Banana
- implementation.
- @type knownDialects: L{list} of L{bytes}
- """
- # The specification calls these profiles but this implementation calls them
- # dialects instead.
- knownDialects = [b"pb", b"none"]
- prefixLimit = None
- sizeLimit = SIZE_LIMIT
- def setPrefixLimit(self, limit):
- """
- Set the prefix limit for decoding done by this protocol instance.
- @see: L{setPrefixLimit}
- """
- self.prefixLimit = limit
- self._smallestLongInt = -2 ** (limit * 7) + 1
- self._smallestInt = -2 ** 31
- self._largestInt = 2 ** 31 - 1
- self._largestLongInt = 2 ** (limit * 7) - 1
- def connectionReady(self):
- """Surrogate for connectionMade
- Called after protocol negotiation.
- """
- def _selectDialect(self, dialect):
- self.currentDialect = dialect
- self.connectionReady()
- def callExpressionReceived(self, obj):
- if self.currentDialect:
- self.expressionReceived(obj)
- else:
- # this is the first message we've received
- if self.isClient:
- # if I'm a client I have to respond
- for serverVer in obj:
- if serverVer in self.knownDialects:
- self.sendEncoded(serverVer)
- self._selectDialect(serverVer)
- break
- else:
- # I can't speak any of those dialects.
- log.msg("The client doesn't speak any of the protocols "
- "offered by the server: disconnecting.")
- self.transport.loseConnection()
- else:
- if obj in self.knownDialects:
- self._selectDialect(obj)
- else:
- # the client just selected a protocol that I did not suggest.
- log.msg("The client selected a protocol the server didn't "
- "suggest and doesn't know: disconnecting.")
- self.transport.loseConnection()
- def connectionMade(self):
- self.setPrefixLimit(_PREFIX_LIMIT)
- self.currentDialect = None
- if not self.isClient:
- self.sendEncoded(self.knownDialects)
- def gotItem(self, item):
- l = self.listStack
- if l:
- l[-1][1].append(item)
- else:
- self.callExpressionReceived(item)
- buffer = b''
- def dataReceived(self, chunk):
- buffer = self.buffer + chunk
- listStack = self.listStack
- gotItem = self.gotItem
- while buffer:
- assert self.buffer != buffer, "This ain't right: %s %s" % (repr(self.buffer), repr(buffer))
- self.buffer = buffer
- pos = 0
- for ch in iterbytes(buffer):
- if ch >= HIGH_BIT_SET:
- break
- pos = pos + 1
- else:
- if pos > self.prefixLimit:
- raise BananaError("Security precaution: more than %d bytes of prefix" % (self.prefixLimit,))
- return
- num = buffer[:pos]
- typebyte = buffer[pos:pos+1]
- rest = buffer[pos+1:]
- if len(num) > self.prefixLimit:
- raise BananaError("Security precaution: longer than %d bytes worth of prefix" % (self.prefixLimit,))
- if typebyte == LIST:
- num = b1282int(num)
- if num > SIZE_LIMIT:
- raise BananaError("Security precaution: List too long.")
- listStack.append((num, []))
- buffer = rest
- elif typebyte == STRING:
- num = b1282int(num)
- if num > SIZE_LIMIT:
- raise BananaError("Security precaution: String too long.")
- if len(rest) >= num:
- buffer = rest[num:]
- gotItem(rest[:num])
- else:
- return
- elif typebyte == INT:
- buffer = rest
- num = b1282int(num)
- gotItem(num)
- elif typebyte == LONGINT:
- buffer = rest
- num = b1282int(num)
- gotItem(num)
- elif typebyte == LONGNEG:
- buffer = rest
- num = b1282int(num)
- gotItem(-num)
- elif typebyte == NEG:
- buffer = rest
- num = -b1282int(num)
- gotItem(num)
- elif typebyte == VOCAB:
- buffer = rest
- num = b1282int(num)
- item = self.incomingVocabulary[num]
- if self.currentDialect == b'pb':
- # the sender issues VOCAB only for dialect pb
- gotItem(item)
- else:
- raise NotImplementedError(
- "Invalid item for pb protocol {0!r}".format(item))
- elif typebyte == FLOAT:
- if len(rest) >= 8:
- buffer = rest[8:]
- gotItem(struct.unpack("!d", rest[:8])[0])
- else:
- return
- else:
- raise NotImplementedError(("Invalid Type Byte %r" % (typebyte,)))
- while listStack and (len(listStack[-1][1]) == listStack[-1][0]):
- item = listStack.pop()[1]
- gotItem(item)
- self.buffer = b''
- def expressionReceived(self, lst):
- """Called when an expression (list, string, or int) is received.
- """
- raise NotImplementedError()
- outgoingVocabulary = {
- # Jelly Data Types
- b'None' : 1,
- b'class' : 2,
- b'dereference' : 3,
- b'reference' : 4,
- b'dictionary' : 5,
- b'function' : 6,
- b'instance' : 7,
- b'list' : 8,
- b'module' : 9,
- b'persistent' : 10,
- b'tuple' : 11,
- b'unpersistable' : 12,
- # PB Data Types
- b'copy' : 13,
- b'cache' : 14,
- b'cached' : 15,
- b'remote' : 16,
- b'local' : 17,
- b'lcache' : 18,
- # PB Protocol Messages
- b'version' : 19,
- b'login' : 20,
- b'password' : 21,
- b'challenge' : 22,
- b'logged_in' : 23,
- b'not_logged_in' : 24,
- b'cachemessage' : 25,
- b'message' : 26,
- b'answer' : 27,
- b'error' : 28,
- b'decref' : 29,
- b'decache' : 30,
- b'uncache' : 31,
- }
- incomingVocabulary = {}
- for k, v in outgoingVocabulary.items():
- incomingVocabulary[v] = k
- def __init__(self, isClient=1):
- self.listStack = []
- self.outgoingSymbols = copy.copy(self.outgoingVocabulary)
- self.outgoingSymbolCount = 0
- self.isClient = isClient
- def sendEncoded(self, obj):
- """
- Send the encoded representation of the given object:
- @param obj: An object to encode and send.
- @raise BananaError: If the given object is not an instance of one of
- the types supported by Banana.
- @return: L{None}
- """
- encodeStream = BytesIO()
- self._encode(obj, encodeStream.write)
- value = encodeStream.getvalue()
- self.transport.write(value)
- def _encode(self, obj, write):
- if isinstance(obj, (list, tuple)):
- if len(obj) > SIZE_LIMIT:
- raise BananaError(
- "list/tuple is too long to send (%d)" % (len(obj),))
- int2b128(len(obj), write)
- write(LIST)
- for elem in obj:
- self._encode(elem, write)
- elif isinstance(obj, (int, long)):
- if obj < self._smallestLongInt or obj > self._largestLongInt:
- raise BananaError(
- "int/long is too large to send (%d)" % (obj,))
- if obj < self._smallestInt:
- int2b128(-obj, write)
- write(LONGNEG)
- elif obj < 0:
- int2b128(-obj, write)
- write(NEG)
- elif obj <= self._largestInt:
- int2b128(obj, write)
- write(INT)
- else:
- int2b128(obj, write)
- write(LONGINT)
- elif isinstance(obj, float):
- write(FLOAT)
- write(struct.pack("!d", obj))
- elif isinstance(obj, bytes):
- # TODO: an API for extending banana...
- if self.currentDialect == b"pb" and obj in self.outgoingSymbols:
- symbolID = self.outgoingSymbols[obj]
- int2b128(symbolID, write)
- write(VOCAB)
- else:
- if len(obj) > SIZE_LIMIT:
- raise BananaError(
- "byte string is too long to send (%d)" % (len(obj),))
- int2b128(len(obj), write)
- write(STRING)
- write(obj)
- else:
- raise BananaError("Banana cannot send {0} objects: {1!r}".format(
- fullyQualifiedName(type(obj)), obj))
- # For use from the interactive interpreter
- _i = Banana()
- _i.connectionMade()
- _i._selectDialect(b"none")
- def encode(lst):
- """Encode a list s-expression."""
- encodeStream = BytesIO()
- _i.transport = encodeStream
- _i.sendEncoded(lst)
- return encodeStream.getvalue()
- def decode(st):
- """
- Decode a banana-encoded string.
- """
- l = []
- _i.expressionReceived = l.append
- try:
- _i.dataReceived(st)
- finally:
- _i.buffer = b''
- del _i.expressionReceived
- return l[0]
|