summaryrefslogtreecommitdiff
path: root/sunshine/lqsoft/cstruct/constraints.py
blob: 0cd056ad558f03e1afbe3e483bae4f40ed6fe643 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# -*- coding: utf-8

import types

# default priorities
PRIO_OFFSET = 100
PRIO_PREFIX = 500
PRIO_TYPE = 600
PRIO_LENGTH = 700
PRIO_MAXLENGTH = 750
PRIO_NBOUNDS = 800

class IConstraint(object):
    def __init__(self, priority):
        self.priority = priority

    def __str__(self):
        return self.__class__.__name__

    def before_unpack(self, opts):
        return True

    def pack(self, opts):
        return True

    def before_pack(self, opts):
        return True

    def on_value_set(self, opts):
        pass
    
class PrefixConstraint(IConstraint):
    def __init__(self, param, priority=PRIO_PREFIX):
        IConstraint.__init__(self, priority)

        if not isinstance(param, str):
            raise ValueError("Prefix constraints takes a byte array as an argument")
        self.prefix = param

    def match(self, data, pos):
        l = len(data)
        for char in self.prefix:
            if pos >= l: # prefix exceeds the data
                return False

            if data[pos] != char: # characters don't match
                return False
            pos += 1
        return True

    def before_unpack(self, opts):
        return self.match(opts['data'], opts['offset'])

class OffsetConstraint(IConstraint):

    def __init__(self, param, priority=PRIO_OFFSET):
        IConstraint.__init__(self, priority)

        if isinstance(param, str):
            self.before_upack = self.before_upack_field
        elif isinstance(param, int):
            self.before_upack = self.before_upack_number
        else:
            raise ValueError("Offset constraint must contain a number or a valid field name.")
        
        self.__offset = param

    def before_upack_number(self, options):
        if self.__offset != options['offset']:
            return False
        return True

    def before_upack_field(self, options):
        off_field = getattr(options['obj'], self.__offset)
        if not isinstance(off_field, NumericField):
            raise ValueError("Field offset can only be attached \
                    to a numeric field.")
        if getattr(options['obj'], self.__offset) != options['offset']:
            return False
        return True      

    def before_pack(self, options):       
        if isinstance(self.__offset, str):           
            setattr(options['obj'], self.__offset, options['offset'])
            
    def pack(self, options):
        if isinstance(self.__offset, int) and (options['offset'] != self.__offset):
            raise PackingException("Explicit offset of field %s was set, but position doesn't match" % \
                options['field'].name )
            
class ValueTypeConstraint(IConstraint):

    def __init__(self, typeklass, priority=PRIO_TYPE):
        IConstraint.__init__(self, priority)

        if not isinstance(typeklass, type):
            raise ValueError("This constraint must contain a type class.")
        self._klass = typeklass

    def on_value_set(self, opts):
        if not isinstance(opts['value'], self._klass):
            raise ValueError("Field %s accepts only instances of %s as value."\
                % (opts['field'].name, self._klass.__name__) )

class NumericBounds(IConstraint):
    BOUND_FOR_CTYPE = {
        'int':      (-(2**31)+1 , 2**31),
        'uint':     (0          , 2**32-1),
        'short':    (-(2**15)+1 , 2**15),
        'ushort':   (0          , 2**16-1),
        'byte':     (-127, 128),
        'ubyte':    (0, 255),
    }

    def __init__(self, lower_bound = None, upper_bound = None, ctype=None, \
      priority=PRIO_NBOUNDS):
        IConstraint.__init__(self, priority)

        if ctype != None:
            self._lbound, self._ubound = self.BOUND_FOR_CTYPE[ctype]
        elif lower_bound == None or upper_bound == None:
            raise ValueError("You need to specify bounds or a ctype.")
        else:
            self._lbound = lower_bound
            self._ubound = upper_bound

    def on_value_set(self, opts):
        if not (self._lbound <= opts['value'] <= self._ubound):
            raise ValueError("Field %s - value %s out of bounds."\
                % (opts['field'].name, opts['value']) )

class LengthConstraint(IConstraint):
    def __init__(self, length, padding_func, priority=PRIO_LENGTH, opt_name='length'):
        IConstraint.__init__(self, priority)

        if isinstance(length, property):
            self.before_unpack = self.before_unpack_prop
        elif isinstance(length, str):
            self.before_unpack = self.before_unpack_field
        elif isinstance(length, int):
            self.before_unpack = self.before_unpack_number
        else:
            raise ValueError("Length constraint must contain a number or a field name.")

        self._opt_name = opt_name
        self.__length = length
        self.__padding_func = padding_func

    def on_value_set(self, opts):
        L = len(opts['value'])
        if isinstance(self.__length, property):
            return self.__length.__set__(opts, L)            
        if isinstance(self.__length, str):
            return setattr(opts['obj'], self.__length, L)
        if self.__length < 0:
            return # do nothing

        if L > self.__length:
            raise ValueError("Field %s has limited length of %d." % (opts['field'].name, self.__length) )

        if self.__padding_func: 
            opts['padding'] = (self.__length - L)
            self.__padding_func(opts)

    def before_pack(self, opts):
        # the value is about to be packed
        # nothing to do here, 'cause we ensure proper length in the trigger
        opts[self._opt_name] = len(opts['value'])

    def pack(self, opts):
        # value is being packed - add our property
        opts[self._opt_name] = len(opts['value'])

    def before_unpack_prop(self, opts):
        opts[self._opt_name] = self.__length.__get__(opts)
        return True

    def before_unpack_number(self, opts):
        opts[self._opt_name] = self.__length
        return True

    def before_unpack_field(self, opts):
        opts[self._opt_name] = getattr(opts['obj'], self.__length)
        return True

class MaxLengthConstraint(LengthConstraint):
    def __init__(self, length, priority=PRIO_MAXLENGTH):
        LengthConstraint.__init__(self, length, None, priority, opt_name='max_length')