def validate_arg(arg, argdef):
        """
        Validate an incoming (unicode) string argument according the UPnP spec. Raises UPNPError.
        """
        datatype = argdef['datatype']
        reasons = set()
        ranges = {
            'ui1': (int, 0, 255),
            'ui2': (int, 0, 65535),
            'ui4': (int, 0, 4294967295),
            'i1': (int, -128, 127),
            'i2': (int, -32768, 32767),
            'i4': (int, -2147483648, 2147483647),
            'r4': (Decimal, Decimal('3.40282347E+38'), Decimal('1.17549435E-38'))
        }
        try:
            if datatype in set(ranges.keys()):
                v_type, v_min, v_max = ranges[datatype]
                if not v_min <= v_type(arg) <= v_max:
                    reasons.add('%r datatype must be a number in the range %s to %s' % (
                        datatype, v_min, v_max))

            elif datatype in {'r8', 'number', 'float', 'fixed.14.4'}:
                v = Decimal(arg)
                if v < 0:
                    assert Decimal('-1.79769313486232E308') <= v <= Decimal('4.94065645841247E-324')
                else:
                    assert Decimal('4.94065645841247E-324') <= v <= Decimal('1.79769313486232E308')

            elif datatype == 'char':
                v = arg.decode('utf8') if six.PY2 or isinstance(arg, bytes) else arg
                assert len(v) == 1

            elif datatype == 'string':
                v = arg.decode("utf8") if six.PY2 or isinstance(arg, bytes) else arg
                if argdef['allowed_values'] and v not in argdef['allowed_values']:
                    reasons.add('Value %r not in allowed values list' % arg)

            elif datatype == 'date':
                v = parse_date(arg)
                if any((v.hour, v.minute, v.second)):
                    reasons.add("'date' datatype must not contain a time")

            elif datatype in ('dateTime', 'dateTime.tz'):
                v = parse_date(arg)
                if datatype == 'dateTime' and v.tzinfo is not None:
                    reasons.add("'dateTime' datatype must not contain a timezone")

            elif datatype in ('time', 'time.tz'):
                now = datetime.datetime.utcnow()
                v = parse_date(arg, default=now)
                if v.tzinfo is not None:
                    now += v.utcoffset()
                if not all((
                        v.day == now.day,
                        v.month == now.month,
                        v.year == now.year)):
                    reasons.add('%r datatype must not contain a date' % datatype)
                if datatype == 'time' and v.tzinfo is not None:
                    reasons.add('%r datatype must not have timezone information' % datatype)

            elif datatype == 'boolean':
                valid = {'true', 'yes', '1', 'false', 'no', '0'}
                if arg.lower() not in valid:
                    reasons.add('%r datatype must be one of %s' % (datatype, ','.join(valid)))

            elif datatype == 'bin.base64':
                b64decode(arg)

            elif datatype == 'bin.hex':
                unhexlify(arg)

            elif datatype == 'uri':
                urlparse(arg)

            elif datatype == 'uuid':
                if not re.match(
                        r'^[0-9a-f]{8}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{12}$',
                        arg, re.I):
                    reasons.add('%r datatype must contain a valid UUID')

            else:
                reasons.add("%r datatype is unrecognised." % datatype)

        except ValueError as exc:
            reasons.add(str(exc))

        return not bool(len(reasons)), reasons
Validate an incoming (unicode) string argument according the UPnP spec. Raises UPNPError.