|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403 |
- # -*- 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
- """
-
-
- import copy
- import 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
- from twisted.python.reflect import fullyQualifiedName
-
-
- class BananaError(Exception):
- pass
-
-
- def int2b128(integer, stream):
- if integer == 0:
- stream(b"\0")
- return
- assert integer > 0, "can only encode positive integers"
- while integer:
- stream(bytes((integer & 0x7F,)))
- integer = integer >> 7
-
-
- def b1282int(st):
- """
- Convert an integer represented as a base 128 string into an L{int}.
-
- @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}
- """
- e = 1
- i = 0
- for char in iterbytes(st):
- n = ord(char)
- i += n * e
- e <<= 7
- return i
-
-
- # delimiter characters.
- LIST = b"\x80"
- INT = b"\x81"
- STRING = b"\x82"
- NEG = b"\x83"
- FLOAT = b"\x84"
- # "optional" -- these might be refused by a low-level implementation.
- LONGINT = b"\x85"
- LONGNEG = b"\x86"
- # really optional; this is part of the 'pb' vocabulary
- VOCAB = b"\x87"
-
- HIGH_BIT_SET = b"\x80"
-
-
- 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: {} {}".format(
- 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(f"Invalid item for pb protocol {item!r}")
- elif typebyte == FLOAT:
- if len(rest) >= 8:
- buffer = rest[8:]
- gotItem(struct.unpack("!d", rest[:8])[0])
- else:
- return
- else:
- raise NotImplementedError(f"Invalid Type Byte {typebyte!r}")
- 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):
- if obj < self._smallestLongInt or obj > self._largestLongInt:
- raise BananaError("int 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 {} objects: {!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]
|