1import json
2from collections import namedtuple
3
4from .symmetries import (
5    decode_string, encode_string, get_byte, get_character, parse_url)
6
7
8EngineIOSession = namedtuple('EngineIOSession', [
9    'id', 'ping_interval', 'ping_timeout', 'transport_upgrades'])
10SocketIOData = namedtuple('SocketIOData', ['path', 'ack_id', 'args'])
11
12
13def parse_host(host, port, resource):
14    if not host.startswith('http'):
15        host = 'http://' + host
16    url_pack = parse_url(host)
17    is_secure = url_pack.scheme == 'https'
18    port = port or url_pack.port or (443 if is_secure else 80)
19    url = '%s:%s%s/%s' % (url_pack.hostname, port, url_pack.path, resource)
20    return is_secure, url
21
22
23def parse_engineIO_session(engineIO_packet_data):
24    d = json.loads(decode_string(engineIO_packet_data))
25    return EngineIOSession(
26        id=d['sid'],
27        ping_interval=d['pingInterval'] / float(1000),
28        ping_timeout=d['pingTimeout'] / float(1000),
29        transport_upgrades=d['upgrades'])
30
31
32def encode_engineIO_content(engineIO_packets):
33    content = bytearray()
34    for packet_type, packet_data in engineIO_packets:
35        packet_text = format_packet_text(packet_type, packet_data)
36        content.extend(_make_packet_prefix(packet_text) + packet_text)
37    return content
38
39
40def decode_engineIO_content(content):
41    content_index = 0
42    content_length = len(content)
43    while content_index < content_length:
44        try:
45            content_index, packet_length = _read_packet_length(
46                content, content_index)
47        except IndexError:
48            break
49        content_index, packet_text = _read_packet_text(
50            content, content_index, packet_length)
51        engineIO_packet_type, engineIO_packet_data = parse_packet_text(
52            packet_text)
53        yield engineIO_packet_type, engineIO_packet_data
54
55
56def format_socketIO_packet_data(path=None, ack_id=None, args=None):
57    socketIO_packet_data = json.dumps(args, ensure_ascii=False) if args else ''
58    if ack_id is not None:
59        socketIO_packet_data = str(ack_id) + socketIO_packet_data
60    if path:
61        socketIO_packet_data = path + ',' + socketIO_packet_data
62    return socketIO_packet_data
63
64
65def parse_socketIO_packet_data(socketIO_packet_data):
66    data = decode_string(socketIO_packet_data)
67    if data.startswith('/'):
68        try:
69            path, data = data.split(',', 1)
70        except ValueError:
71            path = data
72            data = ''
73    else:
74        path = ''
75    try:
76        ack_id_string, data = data.split('[', 1)
77        data = '[' + data
78        ack_id = int(ack_id_string)
79    except (ValueError, IndexError):
80        ack_id = None
81    try:
82        args = json.loads(data)
83    except ValueError:
84        args = []
85    return SocketIOData(path=path, ack_id=ack_id, args=args)
86
87
88def format_packet_text(packet_type, packet_data):
89    return encode_string(str(packet_type) + packet_data)
90
91
92def parse_packet_text(packet_text):
93    packet_type = int(get_character(packet_text, 0))
94    packet_data = packet_text[1:]
95    return packet_type, packet_data
96
97
98def get_namespace_path(socketIO_packet_data):
99    if not socketIO_packet_data.startswith(b'/'):
100        return ''
101    # Loop incrementally in case there is binary data
102    parts = []
103    for i in range(len(socketIO_packet_data)):
104        character = get_character(socketIO_packet_data, i)
105        if ',' == character:
106            break
107        parts.append(character)
108    return ''.join(parts)
109
110
111def _make_packet_prefix(packet):
112    length_string = str(len(packet))
113    header_digits = bytearray([0])
114    for i in range(len(length_string)):
115        header_digits.append(ord(length_string[i]) - 48)
116    header_digits.append(255)
117    return header_digits
118
119
120def _read_packet_length(content, content_index):
121    while get_byte(content, content_index) != 0:
122        content_index += 1
123    content_index += 1
124    packet_length_string = ''
125    byte = get_byte(content, content_index)
126    while byte != 255:
127        packet_length_string += str(byte)
128        content_index += 1
129        byte = get_byte(content, content_index)
130    return content_index, int(packet_length_string)
131
132
133def _read_packet_text(content, content_index, packet_length):
134    while get_byte(content, content_index) == 255:
135        content_index += 1
136    packet_text = content[content_index:content_index + packet_length]
137    return content_index + packet_length, packet_text
138