implement ssl connection
This commit is contained in:
parent
1607d08f1c
commit
5fb4cd84b3
@ -2,8 +2,8 @@
|
|||||||
"status_led_pin": 5,
|
"status_led_pin": 5,
|
||||||
"battery_coeff": 2.25,
|
"battery_coeff": 2.25,
|
||||||
"sumo_id": "xxxxxxxx",
|
"sumo_id": "xxxxxxxx",
|
||||||
"firmware_timestamp": "2019.05.30 00:42:00",
|
"firmware_timestamp": "2019.06.09 21:51:00",
|
||||||
"firmware_version": "0.6.0",
|
"firmware_version": "0.7.0",
|
||||||
"left_servo_tuning": 33,
|
"left_servo_tuning": 33,
|
||||||
"right_servo_tuning": 33,
|
"right_servo_tuning": 33,
|
||||||
"ultrasonic_threshold": 40,
|
"ultrasonic_threshold": 40,
|
||||||
@ -12,7 +12,7 @@
|
|||||||
"right_line_value": 1000,
|
"right_line_value": 1000,
|
||||||
"left_line_threshold": 1000,
|
"left_line_threshold": 1000,
|
||||||
"right_line_threshold": 1000,
|
"right_line_threshold": 1000,
|
||||||
"sumo_server": "165.227.140.64:80",
|
"sumo_server": "165.227.140.64:443",
|
||||||
"wifis": {
|
"wifis": {
|
||||||
"RoboKoding": "salakala"
|
"RoboKoding": "salakala"
|
||||||
}
|
}
|
||||||
|
132
uwebsockets.py
132
uwebsockets.py
@ -1,12 +1,12 @@
|
|||||||
"""
|
"""
|
||||||
Websockets client for micropython
|
Websockets client for micropython
|
||||||
|
|
||||||
Based very heavily on
|
Based very heavily off
|
||||||
https://github.com/aaugustin/websockets/blob/master/websockets/client.py
|
https://github.com/aaugustin/websockets/blob/master/websockets/client.py
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#import usocket as socket
|
#import libraries
|
||||||
import os
|
import ussl
|
||||||
import ure as re
|
import ure as re
|
||||||
import urandom as random
|
import urandom as random
|
||||||
import ustruct as struct
|
import ustruct as struct
|
||||||
@ -33,21 +33,44 @@ CLOSE_TOO_BIG = const(1009)
|
|||||||
CLOSE_MISSING_EXTN = const(1010)
|
CLOSE_MISSING_EXTN = const(1010)
|
||||||
CLOSE_BAD_CONDITION = const(1011)
|
CLOSE_BAD_CONDITION = const(1011)
|
||||||
|
|
||||||
URL_RE = re.compile(r'ws://([A-Za-z0-9\-\.]+)(?:\:([0-9]+))?(/.+)?')
|
URL_RE = re.compile(r'(wss|ws)://([A-Za-z0-9-\.]+)(?:\:([0-9]+))?(/.+)?')
|
||||||
URI = namedtuple('URI', ('hostname', 'port', 'path'))
|
URI = namedtuple('URI', ('protocol', 'hostname', 'port', 'path'))
|
||||||
|
|
||||||
|
class NoDataException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
def urlparse(uri):
|
def urlparse(uri):
|
||||||
|
"""Parse ws:// URLs"""
|
||||||
match = URL_RE.match(uri)
|
match = URL_RE.match(uri)
|
||||||
if match:
|
if match:
|
||||||
return URI(match.group(1), int(match.group(2)), match.group(3))
|
protocol = match.group(1)
|
||||||
else:
|
host = match.group(2)
|
||||||
raise ValueError("Invalid URL: %s" % uri)
|
port = match.group(3)
|
||||||
|
path = match.group(4)
|
||||||
|
|
||||||
|
if protocol == 'wss':
|
||||||
|
if port is None:
|
||||||
|
port = 443
|
||||||
|
elif protocol == 'ws':
|
||||||
|
if port is None:
|
||||||
|
port = 80
|
||||||
|
else:
|
||||||
|
raise ValueError('Scheme {} is invalid'.format(protocol))
|
||||||
|
|
||||||
|
return URI(protocol, host, int(port), path)
|
||||||
|
|
||||||
|
|
||||||
class Websocket:
|
class Websocket:
|
||||||
|
"""
|
||||||
|
Basis of the Websocket protocol.
|
||||||
|
|
||||||
|
This can probably be replaced with the C-based websocket module, but
|
||||||
|
this one currently supports more options.
|
||||||
|
"""
|
||||||
is_client = False
|
is_client = False
|
||||||
|
|
||||||
def __init__(self, sock):
|
def __init__(self, sock):
|
||||||
self._sock = sock
|
self.sock = sock
|
||||||
self.open = True
|
self.open = True
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
@ -57,11 +80,21 @@ class Websocket:
|
|||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
def settimeout(self, timeout):
|
def settimeout(self, timeout):
|
||||||
self._sock.settimeout(timeout)
|
self.sock.settimeout(timeout)
|
||||||
|
|
||||||
def read_frame(self, max_size=None):
|
def read_frame(self, max_size=None):
|
||||||
|
"""
|
||||||
|
Read a frame from the socket.
|
||||||
|
See https://tools.ietf.org/html/rfc6455#section-5.2 for the details.
|
||||||
|
"""
|
||||||
|
|
||||||
# Frame header
|
# Frame header
|
||||||
byte1, byte2 = struct.unpack('!BB', self._sock.read(2))
|
two_bytes = self.sock.read(2)
|
||||||
|
|
||||||
|
if not two_bytes:
|
||||||
|
raise NoDataException
|
||||||
|
|
||||||
|
byte1, byte2 = struct.unpack('!BB', two_bytes)
|
||||||
|
|
||||||
# Byte 1: FIN(1) _(1) _(1) _(1) OPCODE(4)
|
# Byte 1: FIN(1) _(1) _(1) _(1) OPCODE(4)
|
||||||
fin = bool(byte1 & 0x80)
|
fin = bool(byte1 & 0x80)
|
||||||
@ -72,15 +105,15 @@ class Websocket:
|
|||||||
length = byte2 & 0x7f
|
length = byte2 & 0x7f
|
||||||
|
|
||||||
if length == 126: # Magic number, length header is 2 bytes
|
if length == 126: # Magic number, length header is 2 bytes
|
||||||
length, = struct.unpack('!H', self._sock.read(2))
|
length, = struct.unpack('!H', self.sock.read(2))
|
||||||
elif length == 127: # Magic number, length header is 8 bytes
|
elif length == 127: # Magic number, length header is 8 bytes
|
||||||
length, = struct.unpack('!Q', self._sock.read(8))
|
length, = struct.unpack('!Q', self.sock.read(8))
|
||||||
|
|
||||||
if mask: # Mask is 4 bytes
|
if mask: # Mask is 4 bytes
|
||||||
mask_bits = self._sock.read(4)
|
mask_bits = self.sock.read(4)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = self._sock.read(length)
|
data = self.sock.read(length)
|
||||||
except MemoryError:
|
except MemoryError:
|
||||||
# We can't receive this many bytes, close the socket
|
# We can't receive this many bytes, close the socket
|
||||||
self.close(code=CLOSE_TOO_BIG)
|
self.close(code=CLOSE_TOO_BIG)
|
||||||
@ -93,6 +126,10 @@ class Websocket:
|
|||||||
return fin, opcode, data
|
return fin, opcode, data
|
||||||
|
|
||||||
def write_frame(self, opcode, data=b''):
|
def write_frame(self, opcode, data=b''):
|
||||||
|
"""
|
||||||
|
Write a frame to the socket.
|
||||||
|
See https://tools.ietf.org/html/rfc6455#section-5.2 for the details.
|
||||||
|
"""
|
||||||
fin = True
|
fin = True
|
||||||
mask = self.is_client # messages sent by client are masked
|
mask = self.is_client # messages sent by client are masked
|
||||||
|
|
||||||
@ -108,34 +145,44 @@ class Websocket:
|
|||||||
|
|
||||||
if length < 126: # 126 is magic value to use 2-byte length header
|
if length < 126: # 126 is magic value to use 2-byte length header
|
||||||
byte2 |= length
|
byte2 |= length
|
||||||
self._sock.write(struct.pack('!BB', byte1, byte2))
|
self.sock.write(struct.pack('!BB', byte1, byte2))
|
||||||
|
|
||||||
elif length < (1 << 16): # Length fits in 2-bytes
|
elif length < (1 << 16): # Length fits in 2-bytes
|
||||||
byte2 |= 126 # Magic code
|
byte2 |= 126 # Magic code
|
||||||
self._sock.write(struct.pack('!BBH', byte1, byte2, length))
|
self.sock.write(struct.pack('!BBH', byte1, byte2, length))
|
||||||
|
|
||||||
elif length < (1 << 64):
|
elif length < (1 << 64):
|
||||||
byte2 |= 127 # Magic code
|
byte2 |= 127 # Magic code
|
||||||
self._sock.write(struct.pack('!BBQ', byte1, byte2, length))
|
self.sock.write(struct.pack('!BBQ', byte1, byte2, length))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise ValueError()
|
raise ValueError()
|
||||||
|
|
||||||
if mask: # Mask is 4 bytes
|
if mask: # Mask is 4 bytes
|
||||||
mask_bits = struct.pack('!I', random.getrandbits(32))
|
mask_bits = struct.pack('!I', random.getrandbits(32))
|
||||||
self._sock.write(mask_bits)
|
self.sock.write(mask_bits)
|
||||||
|
|
||||||
data = bytes(b ^ mask_bits[i % 4]
|
data = bytes(b ^ mask_bits[i % 4]
|
||||||
for i, b in enumerate(data))
|
for i, b in enumerate(data))
|
||||||
|
|
||||||
self._sock.write(data)
|
self.sock.write(data)
|
||||||
|
|
||||||
def recv(self):
|
def recv(self):
|
||||||
|
"""
|
||||||
|
Receive data from the websocket.
|
||||||
|
|
||||||
|
This is slightly different from 'websockets' in that it doesn't
|
||||||
|
fire off a routine to process frames and put the data in a queue.
|
||||||
|
If you don't call recv() sufficiently often you won't process control
|
||||||
|
frames.
|
||||||
|
"""
|
||||||
assert self.open
|
assert self.open
|
||||||
|
|
||||||
while self.open:
|
while self.open:
|
||||||
try:
|
try:
|
||||||
fin, opcode, data = self.read_frame()
|
fin, opcode, data = self.read_frame()
|
||||||
|
except NoDataException:
|
||||||
|
return ''
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self._close()
|
self._close()
|
||||||
return
|
return
|
||||||
@ -165,6 +212,8 @@ class Websocket:
|
|||||||
raise ValueError(opcode)
|
raise ValueError(opcode)
|
||||||
|
|
||||||
def send(self, buf):
|
def send(self, buf):
|
||||||
|
"""Send data to the websocket."""
|
||||||
|
|
||||||
assert self.open
|
assert self.open
|
||||||
|
|
||||||
if isinstance(buf, str):
|
if isinstance(buf, str):
|
||||||
@ -178,6 +227,7 @@ class Websocket:
|
|||||||
self.write_frame(opcode, buf)
|
self.write_frame(opcode, buf)
|
||||||
|
|
||||||
def close(self, code=CLOSE_OK, reason=''):
|
def close(self, code=CLOSE_OK, reason=''):
|
||||||
|
"""Close the websocket."""
|
||||||
if not self.open:
|
if not self.open:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -188,7 +238,7 @@ class Websocket:
|
|||||||
|
|
||||||
def _close(self):
|
def _close(self):
|
||||||
self.open = False
|
self.open = False
|
||||||
self._sock.close()
|
self.sock.close()
|
||||||
|
|
||||||
class WebsocketClient(Websocket):
|
class WebsocketClient(Websocket):
|
||||||
is_client = True
|
is_client = True
|
||||||
@ -198,41 +248,33 @@ def connect(uri):
|
|||||||
Connect a websocket.
|
Connect a websocket.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Parse the given WebSocket URI
|
|
||||||
uri = urlparse(uri)
|
uri = urlparse(uri)
|
||||||
assert uri
|
assert uri
|
||||||
|
|
||||||
# Connect the socket
|
|
||||||
sock = socket.socket()
|
sock = socket.socket()
|
||||||
sock.settimeout(1)
|
|
||||||
addr = socket.getaddrinfo(uri.hostname, uri.port)
|
addr = socket.getaddrinfo(uri.hostname, uri.port)
|
||||||
sock.connect(addr[0][4])
|
sock.connect(addr[0][4])
|
||||||
|
if uri.protocol == 'wss':
|
||||||
|
sock = ussl.wrap_socket(sock)
|
||||||
|
|
||||||
|
def send_header(header, *args):
|
||||||
|
sock.write(header % args + '\r\n')
|
||||||
|
|
||||||
# Sec-WebSocket-Key is 16 bytes of random base64 encoded
|
# Sec-WebSocket-Key is 16 bytes of random base64 encoded
|
||||||
key = binascii.b2a_base64(os.urandom(16))[:-1]
|
key = binascii.b2a_base64(bytes(random.getrandbits(8)
|
||||||
|
for _ in range(16)))[:-1]
|
||||||
|
|
||||||
# WebSocket initiation headers
|
send_header(b'GET %s HTTP/1.1', uri.path or '/')
|
||||||
headers = [
|
send_header(b'Host: %s:%s', uri.hostname, uri.port)
|
||||||
b'GET %s HTTP/1.1' % uri.path or '/',
|
send_header(b'Connection: Upgrade')
|
||||||
b'Upgrade: websocket',
|
send_header(b'Upgrade: websocket')
|
||||||
b'Connection: Upgrade',
|
send_header(b'Sec-WebSocket-Key: %s', key)
|
||||||
b'Host: %s:%s' % (uri.hostname, uri.port),
|
send_header(b'Sec-WebSocket-Version: 13')
|
||||||
b'Origin: http://%s:%s' % (uri.hostname, uri.port),
|
send_header(b'Origin: http://localhost')
|
||||||
b'Sec-WebSocket-Key: ' + key,
|
send_header(b'')
|
||||||
b'Sec-WebSocket-Version: 13',
|
|
||||||
b'',
|
|
||||||
b''
|
|
||||||
]
|
|
||||||
|
|
||||||
# Concatenate the headers and add new lines
|
|
||||||
data = b'\r\n'.join(headers)
|
|
||||||
|
|
||||||
# Send the WebSocket initiation packet
|
|
||||||
sock.send(data)
|
|
||||||
|
|
||||||
# Check for the WebSocket response header
|
|
||||||
header = sock.readline()[:-2]
|
header = sock.readline()[:-2]
|
||||||
assert header == b'HTTP/1.1 101 Switching Protocols', header
|
assert header.startswith(b'HTTP/1.1 101 '), header
|
||||||
|
|
||||||
# We don't (currently) need these headers
|
# We don't (currently) need these headers
|
||||||
# FIXME: should we check the return key?
|
# FIXME: should we check the return key?
|
||||||
|
Loading…
Reference in New Issue
Block a user