NoUserOrg

Reto Spoerri : Game Design : Zürich

ProgrammingCodeSnippletsRencodeWithPandaTypes

Filed in: Resources.ProgrammingCodeSnippletsRencodeWithPandaTypes · Modified on : Thu, 26 Nov 09

a secure network picking method, not sure if my additions are secure... but i added the panda3d vector and point types



"""
rencode -- Web safe object pickling/unpickling.

The rencode module is a modified version of bencode from the
BitTorrent project.  For complex, heterogeneous data structures with
many small elements, r-encodings take up significantly less space than
b-encodings:

 >>> len(rencode.dumps({'a':0, 'b':[1,2], 'c':99}))
 13
 >>> len(bencode.bencode({'a':0, 'b':[1,2], 'c':99}))
 26

The rencode format is not standardized, and may change with different
rencode module versions, so you should check that you are using the
same rencode version throughout your project.
"""

__version__ = '1.0.0'
__all__ = ['dumps', 'loads']

# Original bencode module by Petru Paler, et al.
#
# Modifications by Reto Spoerri (26.11.2009)
#  - Added some comments
#  - Added Panda3d Vector and Point types
#
# Modifications by Connelly Barnes:
#
#  - Added support for floats (sent as 32-bit or 64-bit in network
#    order), bools, None.
#  - Allowed dict keys to be of any serializable type.
#  - Lists/tuples are always decoded as tuples (thus, tuples can be
#    used as dict keys).
#  - Embedded extra information in the 'typecodes' to save some space.
#  - Added a restriction on integer length, so that malicious hosts
#    cannot pass us large integers which take a long time to decode.
#
# Licensed by Bram Cohen under the "MIT license":
#
#  "Copyright (C) 2001-2002 Bram Cohen
#
#  Permission is hereby granted, free of charge, to any person
#  obtaining a copy of this software and associated documentation files
#  (the "Software"), to deal in the Software without restriction,
#  including without limitation the rights to use, copy, modify, merge,
#  publish, distribute, sublicense, and/or sell copies of the Software,
#  and to permit persons to whom the Software is furnished to do so,
#  subject to the following conditions:
#
#  The above copyright notice and this permission notice shall be
#  included in all copies or substantial portions of the Software.
#
#  The Software is provided "AS IS", without warranty of any kind,
#  express or implied, including but not limited to the warranties of
#  merchantability,  fitness for a particular purpose and
#  noninfringement. In no event shall the  authors or copyright holders
#  be liable for any claim, damages or other liability, whether in an
#  action of contract, tort or otherwise, arising from, out of or in
#  connection with the Software or the use or other dealings in the
#  Software."
#
# (The rencode module is licensed under the above license as well).
#

import struct
import string
import traceback

from pandac.PandaModules import *

# Number of bits for serialized floats, either 32 or 64.
FLOAT_BITS = 32

# Maximum length of integer when written as base 10 string.
MAX_INT_LENGTH = 64

# The bencode 'typecodes' such as i, d, etc have been extended and
# relocated on the base-256 character set.
CHR_LIST  = chr(59)
CHR_DICT  = chr(60)
CHR_INT   = chr(61)
CHR_INT1  = chr(62)
CHR_INT2  = chr(63)
CHR_INT4  = chr(64)
CHR_INT8  = chr(65)
CHR_FLOAT = chr(66)
CHR_TRUE  = chr(67)
CHR_FALSE = chr(68)
CHR_NONE  = chr(69)
CHR_TERM  = chr(127)
# panda3d values
CHR_VEC   = chr(33)
CHR_POINT = chr(34)
CHR_VEC_D   = chr(35)
CHR_POINT_D = chr(36)

# Positive integers with value embedded in typecode.
INT_POS_FIXED_START = 0
INT_POS_FIXED_COUNT = 32


# NOTE: i've made the start 4 higher and the count 4 smaller, dunno what that will change...
# Negative integers with value embedded in typecode.
INT_NEG_FIXED_START = 70 #74     # ord(CHR_POINT_D) + 1    # last of the lower char's
INT_NEG_FIXED_COUNT = 32 #28     # ord(CHR_TERM) - 1 - INT_NEG_FIXED_START

# Dictionaries with length embedded in typecode.
DICT_FIXED_START = 102       # INT_NEG_FIXED_START + INT_NEG_FIXED_COUNT
DICT_FIXED_COUNT = 25        # ord(CHR_TERM) - 1 - DICT_FIXED_START

# Strings with length embedded in typecode.
STR_FIXED_START = 128        # ord(CHR_TERM) + 1
STR_FIXED_COUNT = 64

# Lists with length embedded in typecode.
LIST_FIXED_START = STR_FIXED_START+STR_FIXED_COUNT
LIST_FIXED_COUNT = 64

def decode_int(x, f):
    f += 1
    newf = x.index(CHR_TERM, f)
    if newf - f >= MAX_INT_LENGTH:
        raise ValueError('overflow')
    try:
        n = int(x[f:newf])
    except (OverflowError, ValueError):
        n = long(x[f:newf])
    if x[f] == '-':
        if x[f + 1] == '0':
            raise ValueError
    elif x[f] == '0' and newf != f+1:
        raise ValueError
    return (n, newf+1)

def decode_intb(x, f):
    f += 1
    return (struct.unpack('!b', x[f:f+1])[0], f+1)

def decode_inth(x, f):
    f += 1
    return (struct.unpack('!h', x[f:f+2])[0], f+2)

def decode_intl(x, f):
    f += 1
    return (struct.unpack('!l', x[f:f+4])[0], f+4)

def decode_intq(x, f):
    f += 1
    return (struct.unpack('!q', x[f:f+8])[0], f+8)

def decode_float(x, f):
    f += 1
    if FLOAT_BITS == 32:
        n = struct.unpack('!f', x[f:f+4])[0]
        return (n, f+4)
    elif FLOAT_BITS == 64:
        n = struct.unpack('!d', x[f:f+8])[0]
        return (n, f+8)
    else:
        raise ValueError

def decode_string(x, f):
    colon = x.index(':', f)
    try:
        n = int(x[f:colon])
    except (OverflowError, ValueError):
        n = long(x[f:colon])
    if x[f] == '0' and colon != f+1:
        raise ValueError
    colon += 1
    return (x[colon:colon+n], colon+n)

def decode_list(x, f):
    r, f = [], f+1
    while x[f] != CHR_TERM:
        v, f = decode_func[x[f]](x, f)
        r.append(v)
    return (tuple(r), f + 1)

def decode_dict(x, f):
    r, f = {}, f+1
    while x[f] != CHR_TERM:
        k, f = decode_func[x[f]](x, f)
        r[k], f = decode_func[x[f]](x, f)
    return (r, f + 1)

def decode_true(x, f):
  return (True, f+1)

def decode_false(x, f):
  return (False, f+1)

def decode_none(x, f):
  return (None, f+1)

def decode_vec(x, f):
  r, f = [], f+1
  while x[f] != CHR_TERM:
      v, f = decode_func[x[f]](x, f)
      r.append(v)
  if len(r) == 2:
    r = Vec2(*r)
  if len(r) == 3:
    r = Vec3(*r)
  if len(r) == 4:
    r = Vec4(*r)
  return (r, f+1)

def decode_vec_double(x, f):
  r, f = [], f+1
  while x[f] != CHR_TERM:
      v, f = decode_func[x[f]](x, f)
      r.append(v)
  if len(r) == 2:
    r = Vec2D(*r)
  if len(r) == 3:
    r = Vec3D(*r)
  if len(r) == 4:
    r = Vec4D(*r)
  return (r, f+1)

def decode_point(x, f):
  r, f = [], f+1
  while x[f] != CHR_TERM:
      v, f = decode_func[x[f]](x, f)
      r.append(v)
  if len(r) == 2:
    r = Point2(*r)
  if len(r) == 3:
    r = Point3(*r)
  if len(r) == 4:
    r = Point4(*r)
  return (r, f+1)

def decode_point_double(x, f):
  r, f = [], f+1
  while x[f] != CHR_TERM:
      v, f = decode_func[x[f]](x, f)
      r.append(v)
  if len(r) == 2:
    r = Point2D(*r)
  if len(r) == 3:
    r = Point3D(*r)
  if len(r) == 4:
    r = Point4D(*r)
  return (r, f+1)

decode_func = {}
decode_func['0'] = decode_string
decode_func['1'] = decode_string
decode_func['2'] = decode_string
decode_func['3'] = decode_string
decode_func['4'] = decode_string
decode_func['5'] = decode_string
decode_func['6'] = decode_string
decode_func['7'] = decode_string
decode_func['8'] = decode_string
decode_func['9'] = decode_string
decode_func[CHR_LIST ] = decode_list
decode_func[CHR_DICT ] = decode_dict
decode_func[CHR_INT  ] = decode_int
decode_func[CHR_INT1 ] = decode_intb
decode_func[CHR_INT2 ] = decode_inth
decode_func[CHR_INT4 ] = decode_intl
decode_func[CHR_INT8 ] = decode_intq
decode_func[CHR_FLOAT] = decode_float
decode_func[CHR_TRUE ] = decode_true
decode_func[CHR_FALSE] = decode_false
decode_func[CHR_NONE ] = decode_none
decode_func[CHR_VEC  ] = decode_vec
decode_func[CHR_POINT] = decode_point
decode_func[CHR_VEC_D] = decode_vec_double
decode_func[CHR_POINT_D] = decode_point_double

def make_fixed_length_string_decoders():
    def make_decoder(slen):
        def f(x, f):
            return (x[f+1:f+1+slen], f+1+slen)
        return f
    for i in range(STR_FIXED_COUNT):
        decode_func[chr(STR_FIXED_START+i)] = make_decoder(i)

make_fixed_length_string_decoders()

def make_fixed_length_list_decoders():
    def make_decoder(slen):
        def f(x, f):
            r, f = [], f+1
            for i in range(slen):
                v, f = decode_func[x[f]](x, f)
                r.append(v)
            return (tuple(r), f)
        return f
    for i in range(LIST_FIXED_COUNT):
        decode_func[chr(LIST_FIXED_START+i)] = make_decoder(i)

make_fixed_length_list_decoders()

def make_fixed_length_int_decoders():
    def make_decoder(j):
        def f(x, f):
            return (j, f+1)
        return f
    for i in range(INT_POS_FIXED_COUNT):
        decode_func[chr(INT_POS_FIXED_START+i)] = make_decoder(i)
    for i in range(INT_NEG_FIXED_COUNT):
        decode_func[chr(INT_NEG_FIXED_START+i)] = make_decoder(-1-i)

make_fixed_length_int_decoders()

def make_fixed_length_dict_decoders():
    def make_decoder(slen):
        def f(x, f):
            r, f = {}, f+1
            for j in range(slen):
                k, f = decode_func[x[f]](x, f)
                r[k], f = decode_func[x[f]](x, f)
            return (r, f)
        return f
    for i in range(DICT_FIXED_COUNT):
        decode_func[chr(DICT_FIXED_START+i)] = make_decoder(i)

make_fixed_length_dict_decoders()

def encode_dict(x,r):
    r.append(CHR_DICT)
    for k, v in x.items():
        encode_func[type(k)](k, r)
        encode_func[type(v)](v, r)
    r.append(CHR_TERM)


def loads(x):
    try:
        r, l = decode_func[x[0]](x, 0)
    except (IndexError, KeyError):
        traceback.print_exc()
        raise ValueError
    #if l != len(x):
    #    raise ValueError
    return r

from types import StringType, IntType, LongType, DictType, ListType, TupleType, FloatType, NoneType

def encode_int(x, r):
    if 0 <= x < INT_POS_FIXED_COUNT:
        r.append(chr(INT_POS_FIXED_START+x))
    elif -INT_NEG_FIXED_COUNT <= x < 0:
        r.append(chr(INT_NEG_FIXED_START-1-x))
    elif -128 <= x < 128:
        r.extend((CHR_INT1, struct.pack('!b', x)))
    elif -32768 <= x < 32768:
        r.extend((CHR_INT2, struct.pack('!h', x)))
    elif -2147483648 <= x < 2147483648:
        r.extend((CHR_INT4, struct.pack('!l', x)))
    elif -9223372036854775808 <= x < 9223372036854775808:
        r.extend((CHR_INT8, struct.pack('!q', x)))
    else:
        s = str(x)
        if len(s) >= MAX_INT_LENGTH:
            raise ValueError('overflow')
        r.extend((CHR_INT, s, CHR_TERM))

def encode_float(x, r):
    if FLOAT_BITS == 32:
        r.extend((CHR_FLOAT, struct.pack('!f', x)))
    elif FLOAT_BITS == 64:
        r.extend((CHR_FLOAT, struct.pack('!d', x)))
    else:
        raise ValueError

def encode_bool(x, r):
    r.extend({False: CHR_FALSE, True: CHR_TRUE}[bool(x)])

def encode_none(x, r):
    r.extend(CHR_NONE)

def encode_string(x, r):
    if len(x) < STR_FIXED_COUNT:
        r.extend((chr(STR_FIXED_START + len(x)), x))
    else:
        r.extend((str(len(x)), ':', x))

def encode_list(x, r):
    if len(x) < LIST_FIXED_COUNT:
        r.append(chr(LIST_FIXED_START + len(x)))
        for i in x:
            encode_func[type(i)](i, r)
    else:
        r.append(CHR_LIST)
        for i in x:
            encode_func[type(i)](i, r)
        r.append(CHR_TERM)

def encode_dict(x,r):
    if len(x) < DICT_FIXED_COUNT:
        r.append(chr(DICT_FIXED_START + len(x)))
        for k, v in x.items():
            encode_func[type(k)](k, r)
            encode_func[type(v)](v, r)
    else:
        r.append(CHR_DICT)
        for k, v in x.items():
            encode_func[type(k)](k, r)
            encode_func[type(v)](v, r)
        r.append(CHR_TERM)

def encode_vec(x,r):
  r.append(CHR_VEC)
  for f in x:
    encode_float(f,r)
  r.append(CHR_TERM)

def encode_vec_double(x,r):
  r.append(CHR_VEC_D)
  for f in x:
    encode_float(f,r)
  r.append(CHR_TERM)

def encode_point(x,r):
  r.append(CHR_POINT)
  for f in x:
    encode_float(f,r)
  r.append(CHR_TERM)

def encode_point_double(x,r):
  r.append(CHR_POINT_D)
  for f in x:
    encode_float(f,r)
  r.append(CHR_TERM)

encode_func = {}
encode_func[IntType] = encode_int
encode_func[LongType] = encode_int
encode_func[FloatType] = encode_float
encode_func[StringType] = encode_string
encode_func[ListType] = encode_list
encode_func[TupleType] = encode_list
encode_func[DictType] = encode_dict
encode_func[NoneType] = encode_none
encode_func[Vec2] = encode_vec
encode_func[Vec3] = encode_vec
encode_func[Vec4] = encode_vec
encode_func[Vec2D] = encode_vec_double
encode_func[Vec3D] = encode_vec_double
encode_func[Vec4D] = encode_vec_double
encode_func[Point2] = encode_point
encode_func[Point3] = encode_point
encode_func[Point4] = encode_point
encode_func[Point2D] = encode_point_double
encode_func[Point3D] = encode_point_double
encode_func[Point4D] = encode_point_double

try:
    from types import BooleanType
    encode_func[BooleanType] = encode_bool
except ImportError:
    pass

try:
    from types import UnicodeType
    encode_func[UnicodeType] = encode_string
except ImportError:
    pass

def dumps(x):
    r = []
    encode_func[type(x)](x, r)
    return ''.join(r)


def test():

    f1 = struct.unpack('!f', struct.pack('!f', 25.5))[0]
    f2 = struct.unpack('!f', struct.pack('!f', 29.3))[0]
    f3 = struct.unpack('!f', struct.pack('!f', -0.6))[0]
    L = (({'a':15, 'bb':f1, 'ccc':f2, '':(f3,(),False,True,'')},('a',10**20),tuple(range(-100000,100000)),'b'*31,'b'*62,'b'*64,2**30,2**33,2**62,2**64,2**30,2**33,2**62,2**64,False,False, True, -1, 2, 0),)
    assert loads(dumps(L)) == L
    assert loads(dumps(-2**31)) == -2**31
    ''' # this fails... , but that's probably because of the detail
    if str(3.3492934923942394) == '3.3492934923942394':
      print "yes"
    L = {192: [3.3492934923942394, 1.230030343242342, 9.4929349232349243,
               9.4939349349394931, 4.342992349295949, 0.4309953949239393],
         298: [6.3249093409490905, 2.439009234090990, 0.9139904909090434,
               5.8795437823407908, 3.529098080909880, 4.5899843289893433]}
    print loads(dumps(L))
    assert loads(dumps(L)) == L'''
    d = dict(zip(range(-100000,100000),range(-100000,100000)))
    d.update({'a':20, 20:40, 40:41, f1:f2, f2:f3, f3:False, False:True, True:False})
    L = (d, {}, {5:6}, {7:7,True:8}, {9:10, 22:39, 49:50, 44: ''})
    assert loads(dumps(L)) == L
    L = ('', 'a'*10, 'a'*100, 'a'*1000, 'a'*10000, 'a'*100000, 'a'*1000000, 'a'*10000000)
    assert loads(dumps(L)) == L
    L = tuple([dict(zip(range(n),range(n))) for n in range(100)]) + ('b',)
    assert loads(dumps(L)) == L
    L = tuple([dict(zip(range(n),range(-n,0))) for n in range(100)]) + ('b',)
    assert loads(dumps(L)) == L
    L = tuple([tuple(range(n)) for n in range(100)]) + ('b',)
    assert loads(dumps(L)) == L
    L = tuple(['a'*n for n in range(1000)]) + ('b',)
    assert loads(dumps(L)) == L
    L = tuple(['a'*n for n in range(1000)]) + (None,True,None)
    assert loads(dumps(L)) == L
    assert loads(dumps(None)) == None
    assert loads(dumps({None:None})) == {None:None}
    L = [
        Vec2(1,2), Vec3(1,2,3), Vec4(1,2,3,4),
        Vec2D(1,2), Vec3D(1,2,3), Vec4D(1,2,3,4),
        Point2(1,2), Point3(1,2,3), Point4(1,2,3,4),
        Point2D(1,2), Point3D(1,2,3), Point4D(1,2,3,4),
      ]
    assert loads(dumps(L))

try:
    import psyco
    psyco.bind(dumps)
    psyco.bind(loads)
except ImportError:
    pass


if __name__ == '__main__':
  test()