Merge pull request #6584 from tannewt/websocket_serial
Add WebSocket at /cp/serial/
This commit is contained in:
commit
2f9de1cb07
|
@ -5,6 +5,7 @@
|
|||
|
||||
.. module:: hashlib
|
||||
:synopsis: hashing algorithms
|
||||
:noindex:
|
||||
|
||||
|see_cpython_module| :mod:`cpython:hashlib`.
|
||||
|
||||
|
|
|
@ -286,6 +286,44 @@ not protected by basic auth in case the device is someone elses.
|
|||
|
||||
Only `GET` requests are supported and will return `405 Method Not Allowed` otherwise.
|
||||
|
||||
#### `/cp/devices.json`
|
||||
|
||||
Returns information about other devices found on the network using MDNS.
|
||||
|
||||
* `total`: Total MDNS response count. May be more than in `devices` if internal limits were hit.
|
||||
* `devices`: List of discovered devices.
|
||||
* `hostname`: MDNS hostname
|
||||
* `instance_name`: MDNS instance name. Defaults to human readable board name.
|
||||
* `port`: Port of CircuitPython Web API
|
||||
* `ip`: IP address
|
||||
|
||||
Example:
|
||||
```sh
|
||||
curl -v -L http://circuitpython.local/cp/devices.json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"total": 1,
|
||||
"devices": [
|
||||
{
|
||||
"hostname": "cpy-951032",
|
||||
"instance_name": "Adafruit Feather ESP32-S2 TFT",
|
||||
"port": 80,
|
||||
"ip": "192.168.1.235"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### `/cp/serial/`
|
||||
|
||||
|
||||
Serves a basic serial terminal program when a `GET` request is received without the
|
||||
`Upgrade: websocket` header. Otherwise the socket is upgraded to a WebSocket. See WebSockets below for more detail.
|
||||
|
||||
This is an authenticated endpoint in both modes.
|
||||
|
||||
#### `/cp/version.json`
|
||||
|
||||
Returns information about the device.
|
||||
|
@ -323,36 +361,6 @@ curl -v -L http://circuitpython.local/cp/version.json
|
|||
}
|
||||
```
|
||||
|
||||
#### `/cp/devices.json`
|
||||
|
||||
Returns information about other devices found on the network using MDNS.
|
||||
|
||||
* `total`: Total MDNS response count. May be more than in `devices` if internal limits were hit.
|
||||
* `devices`: List of discovered devices.
|
||||
* `hostname`: MDNS hostname
|
||||
* `instance_name`: MDNS instance name. Defaults to human readable board name.
|
||||
* `port`: Port of CircuitPython Web API
|
||||
* `ip`: IP address
|
||||
|
||||
Example:
|
||||
```sh
|
||||
curl -v -L http://circuitpython.local/cp/devices.json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"total": 1,
|
||||
"devices": [
|
||||
{
|
||||
"hostname": "cpy-951032",
|
||||
"instance_name": "Adafruit Feather ESP32-S2 TFT",
|
||||
"port": 80,
|
||||
"ip": "192.168.1.235"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Static files
|
||||
|
||||
* `/favicon.ico` - Blinka
|
||||
|
@ -361,4 +369,12 @@ curl -v -L http://circuitpython.local/cp/devices.json
|
|||
|
||||
### WebSocket
|
||||
|
||||
Coming soon!
|
||||
The CircuitPython serial interactions are available over a WebSocket. A WebSocket begins as a
|
||||
special HTTP request that gets upgraded to a WebSocket. Authentication happens before upgrading.
|
||||
|
||||
WebSockets are *not* bare sockets once upgraded. Instead they have their own framing format for data.
|
||||
CircuitPython can handle PING and CLOSE opcodes. All others are treated as TEXT. Data to
|
||||
CircuitPython is expected to be masked UTF-8, as the spec requires. Data from CircuitPython to the
|
||||
client is unmasked. It is also unbuffered so the client will get a variety of frame sizes.
|
||||
|
||||
Only one WebSocket at a time is supported.
|
||||
|
|
|
@ -2199,6 +2199,10 @@ msgstr ""
|
|||
msgid "Unsupported format"
|
||||
msgstr ""
|
||||
|
||||
#: shared-bindings/hashlib/__init__.c
|
||||
msgid "Unsupported hash algorithm"
|
||||
msgstr ""
|
||||
|
||||
#: ports/espressif/common-hal/dualbank/__init__.c
|
||||
msgid "Update Failed"
|
||||
msgstr ""
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2022 Scott Shawcroft for Adafruit Industries
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "shared-bindings/hashlib/Hash.h"
|
||||
|
||||
#include "components/mbedtls/mbedtls/include/mbedtls/ssl.h"
|
||||
|
||||
void common_hal_hashlib_hash_update(hashlib_hash_obj_t *self, const uint8_t *data, size_t datalen) {
|
||||
if (self->hash_type == MBEDTLS_SSL_HASH_SHA1) {
|
||||
mbedtls_sha1_update_ret(&self->sha1, data, datalen);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void common_hal_hashlib_hash_digest(hashlib_hash_obj_t *self, uint8_t *data, size_t datalen) {
|
||||
if (datalen < common_hal_hashlib_hash_get_digest_size(self)) {
|
||||
return;
|
||||
}
|
||||
if (self->hash_type == MBEDTLS_SSL_HASH_SHA1) {
|
||||
// We copy the sha1 state so we can continue to update if needed or get
|
||||
// the digest a second time.
|
||||
mbedtls_sha1_context copy;
|
||||
mbedtls_sha1_clone(©, &self->sha1);
|
||||
mbedtls_sha1_finish_ret(&self->sha1, data);
|
||||
mbedtls_sha1_clone(&self->sha1, ©);
|
||||
}
|
||||
}
|
||||
|
||||
size_t common_hal_hashlib_hash_get_digest_size(hashlib_hash_obj_t *self) {
|
||||
if (self->hash_type == MBEDTLS_SSL_HASH_SHA1) {
|
||||
return 20;
|
||||
}
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2022 Scott Shawcroft for Adafruit Industries
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef MICROPY_INCLUDED_ESPRESSIF_COMMON_HAL_HASHLIB_HASH_H
|
||||
#define MICROPY_INCLUDED_ESPRESSIF_COMMON_HAL_HASHLIB_HASH_H
|
||||
|
||||
#include "components/mbedtls/mbedtls/include/mbedtls/sha1.h"
|
||||
|
||||
typedef struct {
|
||||
mp_obj_base_t base;
|
||||
union {
|
||||
mbedtls_sha1_context sha1;
|
||||
};
|
||||
// Of MBEDTLS_SSL_HASH_*
|
||||
uint8_t hash_type;
|
||||
} hashlib_hash_obj_t;
|
||||
|
||||
#endif // MICROPY_INCLUDED_ESPRESSIF_COMMON_HAL_HASHLIB_HASH_H
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2022 Scott Shawcroft for Adafruit Industries
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "shared-bindings/hashlib/__init__.h"
|
||||
|
||||
#include "components/mbedtls/mbedtls/include/mbedtls/ssl.h"
|
||||
|
||||
|
||||
bool common_hal_hashlib_new(hashlib_hash_obj_t *self, const char *algorithm) {
|
||||
if (strcmp(algorithm, "sha1") == 0) {
|
||||
self->hash_type = MBEDTLS_SSL_HASH_SHA1;
|
||||
mbedtls_sha1_init(&self->sha1);
|
||||
mbedtls_sha1_starts_ret(&self->sha1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
|
@ -19,6 +19,7 @@ CIRCUITPY_COUNTIO ?= 1
|
|||
CIRCUITPY_DUALBANK ?= 1
|
||||
CIRCUITPY_FRAMEBUFFERIO ?= 1
|
||||
CIRCUITPY_FREQUENCYIO ?= 1
|
||||
CIRCUITPY_HASHLIB ?= 1
|
||||
CIRCUITPY_IMAGECAPTURE ?= 1
|
||||
CIRCUITPY_I2CPERIPHERAL ?= 1
|
||||
CIRCUITPY_RGBMATRIX ?= 1
|
||||
|
|
|
@ -207,6 +207,9 @@ endif
|
|||
ifeq ($(CIRCUITPY_GNSS),1)
|
||||
SRC_PATTERNS += gnss/%
|
||||
endif
|
||||
ifeq ($(CIRCUITPY_HASHLIB),1)
|
||||
SRC_PATTERNS += hashlib/%
|
||||
endif
|
||||
ifeq ($(CIRCUITPY_I2CPERIPHERAL),1)
|
||||
SRC_PATTERNS += i2cperipheral/%
|
||||
endif
|
||||
|
@ -419,6 +422,8 @@ SRC_COMMON_HAL_ALL = \
|
|||
gnss/GNSS.c \
|
||||
gnss/PositionFix.c \
|
||||
gnss/SatelliteSystem.c \
|
||||
hashlib/__init__.c \
|
||||
hashlib/Hash.c \
|
||||
i2cperipheral/I2CPeripheral.c \
|
||||
i2cperipheral/__init__.c \
|
||||
microcontroller/Pin.c \
|
||||
|
|
|
@ -233,7 +233,7 @@ CIRCUITPY_GETPASS ?= $(CIRCUITPY_FULL_BUILD)
|
|||
CFLAGS += -DCIRCUITPY_GETPASS=$(CIRCUITPY_GETPASS)
|
||||
|
||||
ifeq ($(CIRCUITPY_DISPLAYIO),1)
|
||||
CIRCUITPY_GIFIO ?= $(CIRCUITPY_FULL_BUILD)
|
||||
CIRCUITPY_GIFIO ?= $(CIRCUITPY_CAMERA)
|
||||
else
|
||||
CIRCUITPY_GIFIO ?= 0
|
||||
endif
|
||||
|
@ -242,6 +242,9 @@ CFLAGS += -DCIRCUITPY_GIFIO=$(CIRCUITPY_GIFIO)
|
|||
CIRCUITPY_GNSS ?= 0
|
||||
CFLAGS += -DCIRCUITPY_GNSS=$(CIRCUITPY_GNSS)
|
||||
|
||||
CIRCUITPY_HASHLIB ?= $(CIRCUITPY_WEB_WORKFLOW)
|
||||
CFLAGS += -DCIRCUITPY_HASHLIB=$(CIRCUITPY_HASHLIB)
|
||||
|
||||
CIRCUITPY_I2CPERIPHERAL ?= $(CIRCUITPY_FULL_BUILD)
|
||||
CFLAGS += -DCIRCUITPY_I2CPERIPHERAL=$(CIRCUITPY_I2CPERIPHERAL)
|
||||
|
||||
|
|
|
@ -26,3 +26,7 @@ pyelftools
|
|||
|
||||
# for stubs and annotations
|
||||
adafruit-circuitpython-typing
|
||||
|
||||
# for web workflow minify
|
||||
minify_html
|
||||
jsmin
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2022 Scott Shawcroft for Adafruit Industries
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "shared-bindings/hashlib/Hash.h"
|
||||
|
||||
#include "py/obj.h"
|
||||
#include "py/objproperty.h"
|
||||
#include "py/objstr.h"
|
||||
#include "py/runtime.h"
|
||||
|
||||
//| class Hash:
|
||||
//| """In progress hash algorithm. This object is always created by a `hashlib.new()`. It has no
|
||||
//| user-visible constructor."""
|
||||
//|
|
||||
|
||||
//| digest_size: int
|
||||
//| """Digest size in bytes"""
|
||||
//|
|
||||
STATIC mp_obj_t hashlib_hash_get_digest_size(mp_obj_t self_in) {
|
||||
mp_check_self(mp_obj_is_type(self_in, &hashlib_hash_type));
|
||||
hashlib_hash_obj_t *self = MP_OBJ_TO_PTR(self_in);
|
||||
return MP_OBJ_NEW_SMALL_INT(common_hal_hashlib_hash_get_digest_size(self));
|
||||
}
|
||||
MP_PROPERTY_GETTER(hashlib_hash_digest_size_obj, hashlib_hash_get_digest_size);
|
||||
|
||||
//| def update(self, data: ReadableBuffer) -> None:
|
||||
//| """Update the hash with the given bytes.
|
||||
//|
|
||||
//| :param ~circuitpython_typing.ReadableBuffer data: Update the hash from data in this buffer"""
|
||||
//| ...
|
||||
//|
|
||||
mp_obj_t hashlib_hash_update(mp_obj_t self_in, mp_obj_t buf_in) {
|
||||
mp_check_self(mp_obj_is_type(self_in, &hashlib_hash_type));
|
||||
hashlib_hash_obj_t *self = MP_OBJ_TO_PTR(self_in);
|
||||
|
||||
mp_buffer_info_t bufinfo;
|
||||
mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_READ);
|
||||
|
||||
common_hal_hashlib_hash_update(self, bufinfo.buf, bufinfo.len);
|
||||
return mp_const_none;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_2(hashlib_hash_update_obj, hashlib_hash_update);
|
||||
|
||||
//| def digest(self) -> bytes:
|
||||
//| """Returns the current digest as bytes() with a length of `hashlib.Hash.digest_size`."""
|
||||
//| ...
|
||||
//|
|
||||
STATIC mp_obj_t hashlib_hash_digest(mp_obj_t self_in) {
|
||||
mp_check_self(mp_obj_is_type(self_in, &hashlib_hash_type));
|
||||
hashlib_hash_obj_t *self = MP_OBJ_TO_PTR(self_in);
|
||||
|
||||
size_t size = common_hal_hashlib_hash_get_digest_size(self);
|
||||
mp_obj_t obj = mp_obj_new_bytes_of_zeros(size);
|
||||
mp_obj_str_t *o = MP_OBJ_TO_PTR(obj);
|
||||
|
||||
common_hal_hashlib_hash_digest(self, (uint8_t *)o->data, size);
|
||||
return obj;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_1(hashlib_hash_digest_obj, hashlib_hash_digest);
|
||||
|
||||
STATIC const mp_rom_map_elem_t hashlib_hash_locals_dict_table[] = {
|
||||
{ MP_ROM_QSTR(MP_QSTR_digest_size), MP_ROM_PTR(&hashlib_hash_digest_size_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_update), MP_ROM_PTR(&hashlib_hash_update_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_digest), MP_ROM_PTR(&hashlib_hash_digest_obj) },
|
||||
};
|
||||
|
||||
STATIC MP_DEFINE_CONST_DICT(hashlib_hash_locals_dict, hashlib_hash_locals_dict_table);
|
||||
|
||||
const mp_obj_type_t hashlib_hash_type = {
|
||||
{ &mp_type_type },
|
||||
.name = MP_QSTR_Hash,
|
||||
.locals_dict = (mp_obj_dict_t *)&hashlib_hash_locals_dict,
|
||||
};
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2022 Scott Shawcroft for Adafruit Industries
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef MICROPY_INCLUDED_SHARED_BINDINGS_HASHLIB_HASH_H
|
||||
#define MICROPY_INCLUDED_SHARED_BINDINGS_HASHLIB_HASH_H
|
||||
|
||||
#include "py/obj.h"
|
||||
|
||||
#include "common-hal/hashlib/Hash.h"
|
||||
|
||||
extern const mp_obj_type_t hashlib_hash_type;
|
||||
|
||||
// So that new can call it when given data.
|
||||
mp_obj_t hashlib_hash_update(mp_obj_t self_in, mp_obj_t buf_in);
|
||||
|
||||
void common_hal_hashlib_hash_update(hashlib_hash_obj_t *self, const uint8_t *data, size_t datalen);
|
||||
void common_hal_hashlib_hash_digest(hashlib_hash_obj_t *self, uint8_t *data, size_t datalen);
|
||||
size_t common_hal_hashlib_hash_get_digest_size(hashlib_hash_obj_t *self);
|
||||
|
||||
#endif // MICROPY_INCLUDED_SHARED_BINDINGS_HASHLIB_HASH_H
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* This file is part of the Micro Python project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2022 Scott Shawcroft for Adafruit Industries
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "py/obj.h"
|
||||
#include "py/mpconfig.h"
|
||||
#include "py/runtime.h"
|
||||
#include "shared-bindings/hashlib/__init__.h"
|
||||
#include "shared-bindings/hashlib/Hash.h"
|
||||
#include "supervisor/shared/translate/translate.h"
|
||||
|
||||
//| """Hashing related functions
|
||||
//|
|
||||
//| |see_cpython_module| :mod:`cpython:hashlib`.
|
||||
//| """
|
||||
//|
|
||||
//| def new(name, data=b"") -> hashlib.Hash:
|
||||
//| """Returns a Hash object setup for the named algorithm. Raises ValueError when the named
|
||||
//| algorithm is unsupported.
|
||||
//|
|
||||
//| :return: a hash object for the given algorithm
|
||||
//| :rtype: hashlib.Hash"""
|
||||
//| ...
|
||||
//|
|
||||
STATIC mp_obj_t hashlib_new(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
|
||||
enum { ARG_name, ARG_data };
|
||||
static const mp_arg_t allowed_args[] = {
|
||||
{ MP_QSTR_name, MP_ARG_REQUIRED | MP_ARG_OBJ },
|
||||
{ MP_QSTR_data, MP_ARG_OBJ, {.u_obj = mp_const_none} },
|
||||
};
|
||||
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
|
||||
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
|
||||
|
||||
const char *algorithm = mp_obj_str_get_str(args[ARG_name].u_obj);
|
||||
|
||||
hashlib_hash_obj_t *self = m_new_obj(hashlib_hash_obj_t);
|
||||
self->base.type = &hashlib_hash_type;
|
||||
|
||||
if (!common_hal_hashlib_new(self, algorithm)) {
|
||||
mp_raise_ValueError(translate("Unsupported hash algorithm"));
|
||||
}
|
||||
|
||||
if (args[ARG_data].u_obj != mp_const_none) {
|
||||
hashlib_hash_update(self, args[ARG_data].u_obj);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(hashlib_new_obj, 1, hashlib_new);
|
||||
|
||||
STATIC const mp_rom_map_elem_t hashlib_module_globals_table[] = {
|
||||
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_hashlib) },
|
||||
|
||||
{ MP_ROM_QSTR(MP_QSTR_new), MP_ROM_PTR(&hashlib_new_obj) },
|
||||
|
||||
// Hash is deliberately omitted here because CPython doesn't expose the
|
||||
// object on `hashlib` only the internal `_hashlib`.
|
||||
};
|
||||
|
||||
STATIC MP_DEFINE_CONST_DICT(hashlib_module_globals, hashlib_module_globals_table);
|
||||
|
||||
const mp_obj_module_t hashlib_module = {
|
||||
.base = { &mp_type_module },
|
||||
.globals = (mp_obj_dict_t *)&hashlib_module_globals,
|
||||
};
|
||||
|
||||
MP_REGISTER_MODULE(MP_QSTR_hashlib, hashlib_module, CIRCUITPY_HASHLIB);
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2022 Scott Shawcroft for Adafruit Industries
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef MICROPY_INCLUDED_SHARED_BINDINGS_HASHLIB___INIT___H
|
||||
#define MICROPY_INCLUDED_SHARED_BINDINGS_HASHLIB___INIT___H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "shared-bindings/hashlib/Hash.h"
|
||||
|
||||
bool common_hal_hashlib_new(hashlib_hash_obj_t *self, const char *algorithm);
|
||||
|
||||
#endif // MICROPY_INCLUDED_SHARED_BINDINGS_HASHLIB___INIT___H
|
|
@ -45,6 +45,10 @@
|
|||
#include "tusb.h"
|
||||
#endif
|
||||
|
||||
#if CIRCUITPY_WEB_WORKFLOW
|
||||
#include "supervisor/shared/web_workflow/websocket.h"
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Note: DEBUG_UART currently only works on STM32 and nRF.
|
||||
* Enabling on another platform will cause a crash.
|
||||
|
@ -165,6 +169,13 @@ bool serial_connected(void) {
|
|||
}
|
||||
#endif
|
||||
|
||||
#if CIRCUITPY_WEB_WORKFLOW
|
||||
if (websocket_connected()) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
if (port_serial_connected()) {
|
||||
return true;
|
||||
}
|
||||
|
@ -195,6 +206,12 @@ char serial_read(void) {
|
|||
}
|
||||
#endif
|
||||
|
||||
#if CIRCUITPY_WEB_WORKFLOW
|
||||
if (websocket_available()) {
|
||||
return websocket_read_char();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CIRCUITPY_USB_CDC
|
||||
if (!usb_cdc_console_enabled()) {
|
||||
return -1;
|
||||
|
@ -229,6 +246,12 @@ bool serial_bytes_available(void) {
|
|||
}
|
||||
#endif
|
||||
|
||||
#if CIRCUITPY_WEB_WORKFLOW
|
||||
if (websocket_available()) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CIRCUITPY_USB_CDC
|
||||
if (usb_cdc_console_enabled() && tud_cdc_available() > 0) {
|
||||
return true;
|
||||
|
@ -271,6 +294,10 @@ void serial_write_substring(const char *text, uint32_t length) {
|
|||
ble_serial_write(text, length);
|
||||
#endif
|
||||
|
||||
#if CIRCUITPY_WEB_WORKFLOW
|
||||
websocket_write(text, length);
|
||||
#endif
|
||||
|
||||
#if CIRCUITPY_USB_CDC
|
||||
if (!usb_cdc_console_enabled()) {
|
||||
return;
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<!DOCTYPE html>
|
||||
<html style="height: 100%;">
|
||||
<head>
|
||||
<title>Simple client</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script src="/serial.js" defer=true></script>
|
||||
</head>
|
||||
<body style="flex-direction: column; display: flex; height: 100%; width: 100%; margin: 0; font-size: 1rem;">
|
||||
<div style="flex: auto; display: flex; overflow: auto; flex-direction: column;">
|
||||
<pre id="log" style="margin:0; margin-top: auto;"></pre>
|
||||
<span style="height: 1px;"></span>
|
||||
</div>
|
||||
<div id="controls" style="flex: none; display: flex;">
|
||||
<fieldset style="display: inline-block; padding: 0;">
|
||||
<legend>Ctrl</legend>
|
||||
<button id="c">C</button>
|
||||
<button id="d">D</button>
|
||||
</fieldset>
|
||||
<textarea id="input" rows="1" spellcheck="false" wrap="off" style="resize: none; flex: auto; font-size: 1rem;" autocapitalize="none" autocomplete="off" autocorrect="off"></textarea>
|
||||
<button onclick="onSubmit(); return false;">Send</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,81 @@
|
|||
|
||||
var ws;
|
||||
var input = document.getElementById("input");
|
||||
input.value = "";
|
||||
var title = document.querySelector("title");
|
||||
var log = document.getElementById("log");
|
||||
|
||||
function set_enabled(enabled) {
|
||||
input.disabled = !enabled;
|
||||
var buttons = document.querySelectorAll("button");
|
||||
for (button of buttons) {
|
||||
button.disabled = !enabled;
|
||||
}
|
||||
}
|
||||
|
||||
set_enabled(false);
|
||||
|
||||
function onSubmit() {
|
||||
ws.send("\r");
|
||||
input.value = "";
|
||||
input.focus();
|
||||
}
|
||||
|
||||
ws = new WebSocket("ws://" + window.location.host + "/cp/serial/");
|
||||
|
||||
ws.onopen = function() {
|
||||
set_enabled(true);
|
||||
};
|
||||
|
||||
var setting_title = false;
|
||||
var encoder = new TextEncoder();
|
||||
var left_count = 0;
|
||||
ws.onmessage = function(e) {
|
||||
if (e.data == "\x1b]0;") {
|
||||
setting_title = true;
|
||||
title.textContent = "";
|
||||
} else if (e.data == "\x1b\\") {
|
||||
setting_title = false;
|
||||
} else if (setting_title) {
|
||||
title.textContent += e.data;
|
||||
} else if (e.data == "\b") {
|
||||
left_count += 1;
|
||||
} else if (e.data == "\x1b[K") { // Clear line
|
||||
log.textContent = log.textContent.slice(0, -left_count);
|
||||
left_count = 0;
|
||||
} else {
|
||||
log.textContent += e.data;
|
||||
}
|
||||
document.querySelector("span").scrollIntoView();
|
||||
};
|
||||
|
||||
ws.onclose = function() {
|
||||
set_enabled(false);
|
||||
};
|
||||
|
||||
ws.onerror = function(e) {
|
||||
set_enabled(false);
|
||||
};
|
||||
|
||||
input.addEventListener("beforeinput", function(e) {
|
||||
if (e.inputType == "insertLineBreak") {
|
||||
ws.send("\r");
|
||||
input.value = "";
|
||||
input.focus();
|
||||
e.preventDefault();
|
||||
} else if (e.inputType == "insertText") {
|
||||
ws.send(e.data);
|
||||
} else if (e.inputType == "deleteContentBackward") {
|
||||
ws.send("\b");
|
||||
}
|
||||
});
|
||||
|
||||
let ctrl_c = document.querySelector("#c");
|
||||
ctrl_c.onclick = function() {
|
||||
ws.send("\x03");
|
||||
}
|
||||
|
||||
let ctrl_d = document.querySelector("#d");
|
||||
ctrl_d.onclick = function() {
|
||||
ws.send("\x04");
|
||||
}
|
|
@ -3,11 +3,12 @@
|
|||
<head>
|
||||
<title>CircuitPython</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script src="/welcome.js" defer=true></script>
|
||||
</head>
|
||||
<script src="/welcome.js" defer=true></script>
|
||||
<body>
|
||||
<h1><a href="/"><img src="/favicon.ico"/></a> Welcome!</h1>
|
||||
Welcome to CircuitPython's Web API. Go to the <a href="/fs/">file browser</a> to work with files in the CIRCUITPY drive. Make sure you've set <code>CIRCUITPY_WEB_API_PASSWORD='somepassword'</code> in <code>/.env</code>. Provide the password when the browser prompts for it. Leave the username blank.
|
||||
Welcome to CircuitPython's Web API. Go to the <a href="/fs/">file browser</a> to work with files in the CIRCUITPY drive. Go to the <a href="/cp/serial/">serial terminal</a> to see code output and interact with the REPL. Make sure you've set <code>CIRCUITPY_WEB_API_PASSWORD='somepassword'</code> in <code>/.env</code>. Provide the password when the browser prompts for it. Leave the username blank.
|
||||
<h2>Device Info</h2>
|
||||
Board: <a id="board"></a><br>
|
||||
Version: <span id="version"></span><br>
|
||||
|
|
|
@ -39,8 +39,11 @@
|
|||
#include "supervisor/shared/reload.h"
|
||||
#include "supervisor/shared/translate/translate.h"
|
||||
#include "supervisor/shared/web_workflow/web_workflow.h"
|
||||
#include "supervisor/shared/web_workflow/websocket.h"
|
||||
#include "supervisor/usb.h"
|
||||
|
||||
#include "shared-bindings/hashlib/__init__.h"
|
||||
#include "shared-bindings/hashlib/Hash.h"
|
||||
#include "shared-bindings/mdns/RemoteService.h"
|
||||
#include "shared-bindings/mdns/Server.h"
|
||||
#include "shared-bindings/socketpool/__init__.h"
|
||||
|
@ -86,6 +89,10 @@ typedef struct {
|
|||
bool authenticated;
|
||||
bool expect;
|
||||
bool json;
|
||||
bool websocket;
|
||||
uint32_t websocket_version;
|
||||
// RFC6455 for websockets says this header should be 24 base64 characters long.
|
||||
char websocket_key[24 + 1];
|
||||
} _request;
|
||||
|
||||
static wifi_radio_error_t wifi_status = WIFI_RADIO_ERROR_NONE;
|
||||
|
@ -256,20 +263,19 @@ void supervisor_start_web_workflow(void) {
|
|||
active.num = -1;
|
||||
active.connected = false;
|
||||
|
||||
websocket_init();
|
||||
|
||||
// TODO:
|
||||
// GET /cp/serial.txt
|
||||
// - Most recent 1k of serial output.
|
||||
// GET /edit/
|
||||
// - Super basic editor
|
||||
// GET /ws/circuitpython
|
||||
// GET /ws/user
|
||||
// - WebSockets
|
||||
#endif
|
||||
}
|
||||
|
||||
static void _send_raw(socketpool_socket_obj_t *socket, const uint8_t *buf, int len) {
|
||||
int sent = -EAGAIN;
|
||||
while (sent == -EAGAIN) {
|
||||
while (sent == -EAGAIN && common_hal_socketpool_socket_get_connected(socket)) {
|
||||
sent = socketpool_socket_send(socket, buf, len);
|
||||
}
|
||||
if (sent < len) {
|
||||
|
@ -516,7 +522,14 @@ static void _reply_redirect(socketpool_socket_obj_t *socket, _request *request,
|
|||
"HTTP/1.1 301 Moved Permanently\r\n",
|
||||
"Connection: close\r\n",
|
||||
"Content-Length: 0\r\n",
|
||||
"Location: http://", hostname, ".local", path, "\r\n", NULL);
|
||||
"Location: ", NULL);
|
||||
if (request->websocket) {
|
||||
_send_str(socket, "ws");
|
||||
} else {
|
||||
_send_str(socket, "http");
|
||||
}
|
||||
|
||||
_send_strs(socket, "://", hostname, ".local", path, "\r\n", NULL);
|
||||
_cors_header(socket, request);
|
||||
_send_str(socket, "\r\n");
|
||||
}
|
||||
|
@ -826,6 +839,8 @@ STATIC_FILE(directory_html);
|
|||
STATIC_FILE(directory_js);
|
||||
STATIC_FILE(welcome_html);
|
||||
STATIC_FILE(welcome_js);
|
||||
STATIC_FILE(serial_html);
|
||||
STATIC_FILE(serial_js);
|
||||
STATIC_FILE(blinka_16x16_ico);
|
||||
|
||||
static void _reply_static(socketpool_socket_obj_t *socket, _request *request, const uint8_t *response, size_t response_len, const char *content_type) {
|
||||
|
@ -844,10 +859,34 @@ static void _reply_static(socketpool_socket_obj_t *socket, _request *request, co
|
|||
|
||||
#define _REPLY_STATIC(socket, request, filename) _reply_static(socket, request, filename, filename##_length, filename##_content_type)
|
||||
|
||||
static void _reply_websocket_upgrade(socketpool_socket_obj_t *socket, _request *request) {
|
||||
// Compute accept key
|
||||
hashlib_hash_obj_t hash;
|
||||
common_hal_hashlib_new(&hash, "sha1");
|
||||
common_hal_hashlib_hash_update(&hash, (const uint8_t *)request->websocket_key, strlen(request->websocket_key));
|
||||
const char *magic_string = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
common_hal_hashlib_hash_update(&hash, (const uint8_t *)magic_string, strlen(magic_string));
|
||||
size_t digest_size = common_hal_hashlib_hash_get_digest_size(&hash);
|
||||
size_t encoded_size = (digest_size + 1) * 4 / 3 + 1;
|
||||
uint8_t encoded_accept[encoded_size];
|
||||
common_hal_hashlib_hash_digest(&hash, encoded_accept, sizeof(encoded_accept));
|
||||
_base64_in_place((char *)encoded_accept, digest_size, encoded_size);
|
||||
|
||||
// Reply with upgrade
|
||||
_send_strs(socket, "HTTP/1.1 101 Switching Protocols\r\n",
|
||||
"Upgrade: websocket\r\n",
|
||||
"Connection: Upgrade\r\n",
|
||||
"Sec-WebSocket-Accept: ", encoded_accept, "\r\n",
|
||||
"\r\n", NULL);
|
||||
websocket_handoff(socket);
|
||||
// socket is now closed and "disconnected".
|
||||
}
|
||||
|
||||
static bool _reply(socketpool_socket_obj_t *socket, _request *request) {
|
||||
if (request->redirect) {
|
||||
_reply_redirect(socket, request, request->path);
|
||||
} else if (strlen(request->origin) > 0 && !_origin_ok(request->origin)) {
|
||||
ESP_LOGE(TAG, "bad origin %s", request->origin);
|
||||
_reply_forbidden(socket, request);
|
||||
} else if (memcmp(request->path, "/fs/", 4) == 0) {
|
||||
if (!request->authenticated) {
|
||||
|
@ -980,6 +1019,20 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) {
|
|||
_reply_with_devices_json(socket, request);
|
||||
} else if (strcmp(path, "/version.json") == 0) {
|
||||
_reply_with_version_json(socket, request);
|
||||
} else if (strcmp(path, "/serial/") == 0) {
|
||||
if (!request->authenticated) {
|
||||
if (_api_password[0] != '\0') {
|
||||
_reply_unauthorized(socket, request);
|
||||
} else {
|
||||
_reply_forbidden(socket, request);
|
||||
}
|
||||
} else if (request->websocket) {
|
||||
_reply_websocket_upgrade(socket, request);
|
||||
} else {
|
||||
_REPLY_STATIC(socket, request, serial_html);
|
||||
}
|
||||
} else {
|
||||
_reply_missing(socket, request);
|
||||
}
|
||||
} else if (strcmp(request->method, "GET") != 0) {
|
||||
_reply_method_not_allowed(socket, request);
|
||||
|
@ -992,6 +1045,8 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) {
|
|||
_REPLY_STATIC(socket, request, directory_js);
|
||||
} else if (strcmp(request->path, "/welcome.js") == 0) {
|
||||
_REPLY_STATIC(socket, request, welcome_js);
|
||||
} else if (strcmp(request->path, "/serial.js") == 0) {
|
||||
_REPLY_STATIC(socket, request, serial_js);
|
||||
} else if (strcmp(request->path, "/favicon.ico") == 0) {
|
||||
// TODO: Autogenerate this based on the blinka bitmap and change the
|
||||
// palette based on MAC address.
|
||||
|
@ -1015,6 +1070,7 @@ static void _reset_request(_request *request) {
|
|||
request->authenticated = false;
|
||||
request->expect = false;
|
||||
request->json = false;
|
||||
request->websocket = false;
|
||||
}
|
||||
|
||||
static void _process_request(socketpool_socket_obj_t *socket, _request *request) {
|
||||
|
@ -1111,6 +1167,13 @@ static void _process_request(socketpool_socket_obj_t *socket, _request *request)
|
|||
strcpy(request->origin, request->header_value);
|
||||
} else if (strcmp(request->header_key, "X-Timestamp") == 0) {
|
||||
request->timestamp_ms = strtoull(request->header_value, NULL, 10);
|
||||
} else if (strcmp(request->header_key, "Upgrade") == 0) {
|
||||
request->websocket = strcmp(request->header_value, "websocket") == 0;
|
||||
} else if (strcmp(request->header_key, "Sec-WebSocket-Version") == 0) {
|
||||
request->websocket_version = strtoul(request->header_value, NULL, 10);
|
||||
} else if (strcmp(request->header_key, "Sec-WebSocket-Key") == 0 &&
|
||||
strlen(request->header_value) == 24) {
|
||||
strcpy(request->websocket_key, request->header_value);
|
||||
}
|
||||
ESP_LOGI(TAG, "Header %s %s", request->header_key, request->header_value);
|
||||
} else if (request->offset > sizeof(request->header_value) - 1) {
|
||||
|
|
|
@ -0,0 +1,234 @@
|
|||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2022 Scott Shawcroft for Adafruit Industries
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "supervisor/shared/web_workflow/websocket.h"
|
||||
|
||||
// TODO: Remove ESP specific stuff. For now, it is useful as we refine the server.
|
||||
#include "esp_log.h"
|
||||
|
||||
typedef struct {
|
||||
socketpool_socket_obj_t socket;
|
||||
uint8_t opcode;
|
||||
uint8_t frame_len;
|
||||
uint8_t payload_len_size;
|
||||
bool masked;
|
||||
bool closed;
|
||||
uint8_t mask[4];
|
||||
int frame_index;
|
||||
size_t payload_remaining;
|
||||
} _websocket;
|
||||
|
||||
static _websocket cp_serial;
|
||||
|
||||
static const char *TAG = "CP websocket";
|
||||
|
||||
void websocket_init(void) {
|
||||
cp_serial.socket.num = -1;
|
||||
cp_serial.socket.connected = false;
|
||||
}
|
||||
|
||||
void websocket_handoff(socketpool_socket_obj_t *socket) {
|
||||
cp_serial.socket = *socket;
|
||||
cp_serial.closed = false;
|
||||
cp_serial.opcode = 0;
|
||||
cp_serial.frame_index = 0;
|
||||
cp_serial.frame_len = 2;
|
||||
// Mark the original socket object as closed without telling the lower level.
|
||||
socket->connected = false;
|
||||
socket->num = -1;
|
||||
}
|
||||
|
||||
bool websocket_connected(void) {
|
||||
return !cp_serial.closed && common_hal_socketpool_socket_get_connected(&cp_serial.socket);
|
||||
}
|
||||
|
||||
static bool _read_byte(uint8_t *c) {
|
||||
int len = socketpool_socket_recv_into(&cp_serial.socket, c, 1);
|
||||
if (len != 1) {
|
||||
if (len != -EAGAIN) {
|
||||
ESP_LOGE(TAG, "recv error %d", len);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void _send_raw(socketpool_socket_obj_t *socket, const uint8_t *buf, int len) {
|
||||
int sent = -EAGAIN;
|
||||
while (sent == -EAGAIN) {
|
||||
sent = socketpool_socket_send(socket, buf, len);
|
||||
}
|
||||
if (sent < len) {
|
||||
ESP_LOGE(TAG, "short send on %d err %d len %d", socket->num, sent, len);
|
||||
}
|
||||
}
|
||||
|
||||
static void _read_next_frame_header(void) {
|
||||
uint8_t h;
|
||||
if (cp_serial.frame_index == 0 && _read_byte(&h)) {
|
||||
cp_serial.frame_index++;
|
||||
cp_serial.opcode = h & 0xf;
|
||||
}
|
||||
if (cp_serial.frame_index == 1 && _read_byte(&h)) {
|
||||
cp_serial.frame_index++;
|
||||
uint8_t len = h & 0xf;
|
||||
cp_serial.masked = (h >> 7) == 1;
|
||||
if (len <= 125) {
|
||||
cp_serial.payload_remaining = len;
|
||||
cp_serial.payload_len_size = 0;
|
||||
} else if (len == 126) { // 16 bit length
|
||||
cp_serial.payload_len_size = 2;
|
||||
} else if (len == 127) { // 64 bit length
|
||||
cp_serial.payload_len_size = 8;
|
||||
}
|
||||
cp_serial.frame_len = 2 + cp_serial.payload_len_size;
|
||||
if (cp_serial.masked) {
|
||||
cp_serial.frame_len += 4;
|
||||
}
|
||||
}
|
||||
while (cp_serial.frame_index >= 2 &&
|
||||
cp_serial.frame_index < (cp_serial.payload_len_size + 2) &&
|
||||
_read_byte(&h)) {
|
||||
cp_serial.frame_index++;
|
||||
cp_serial.payload_remaining = cp_serial.payload_remaining << 8 | h;
|
||||
}
|
||||
int mask_start = cp_serial.payload_len_size + 2;
|
||||
while (cp_serial.frame_index >= mask_start &&
|
||||
cp_serial.frame_index < cp_serial.frame_len &&
|
||||
_read_byte(&h)) {
|
||||
size_t mask_offset = cp_serial.frame_index - mask_start;
|
||||
cp_serial.mask[mask_offset] = h;
|
||||
cp_serial.frame_index++;
|
||||
}
|
||||
// Reply to PINGs and CLOSE.
|
||||
while ((cp_serial.opcode == 0x8 ||
|
||||
cp_serial.opcode == 0x9) &&
|
||||
cp_serial.frame_index >= cp_serial.frame_len) {
|
||||
|
||||
if (cp_serial.frame_index == cp_serial.frame_len) {
|
||||
uint8_t opcode = 0x8; // CLOSE
|
||||
if (cp_serial.opcode == 0x9) {
|
||||
opcode = 0xA; // PONG
|
||||
} else {
|
||||
// Set the TCP socket to send immediately so that we send the payload back before
|
||||
// closing the connection.
|
||||
int nodelay = 1;
|
||||
lwip_setsockopt(cp_serial.socket.num, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay));
|
||||
}
|
||||
uint8_t frame_header[2];
|
||||
frame_header[0] = 1 << 7 | opcode;
|
||||
if (cp_serial.payload_remaining > 125) {
|
||||
ESP_LOGE(TAG, "CLOSE or PING has long payload");
|
||||
}
|
||||
frame_header[1] = cp_serial.payload_remaining;
|
||||
_send_raw(&cp_serial.socket, (const uint8_t *)frame_header, 2);
|
||||
}
|
||||
|
||||
if (cp_serial.payload_remaining > 0 && _read_byte(&h)) {
|
||||
// Send the payload back to the client.
|
||||
cp_serial.frame_index++;
|
||||
cp_serial.payload_remaining--;
|
||||
_send_raw(&cp_serial.socket, &h, 1);
|
||||
}
|
||||
|
||||
if (cp_serial.payload_remaining == 0) {
|
||||
cp_serial.frame_index = 0;
|
||||
if (cp_serial.opcode == 0x8) {
|
||||
cp_serial.closed = true;
|
||||
|
||||
common_hal_socketpool_socket_close(&cp_serial.socket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool _read_next_payload_byte(uint8_t *c) {
|
||||
_read_next_frame_header();
|
||||
if (cp_serial.opcode == 0x1 &&
|
||||
cp_serial.frame_index >= cp_serial.frame_len &&
|
||||
cp_serial.payload_remaining > 0) {
|
||||
if (_read_byte(c)) {
|
||||
uint8_t mask_offset = (cp_serial.frame_index - cp_serial.frame_len) % 4;
|
||||
*c ^= cp_serial.mask[mask_offset];
|
||||
cp_serial.frame_index++;
|
||||
cp_serial.payload_remaining--;
|
||||
if (cp_serial.payload_remaining == 0) {
|
||||
cp_serial.frame_index = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool websocket_available(void) {
|
||||
if (!websocket_connected()) {
|
||||
return false;
|
||||
}
|
||||
_read_next_frame_header();
|
||||
return cp_serial.payload_remaining > 0 && cp_serial.frame_index >= cp_serial.frame_len;
|
||||
}
|
||||
|
||||
char websocket_read_char(void) {
|
||||
uint8_t c;
|
||||
_read_next_payload_byte(&c);
|
||||
return c;
|
||||
}
|
||||
|
||||
static void _websocket_send(_websocket *ws, const char *text, size_t len) {
|
||||
if (!websocket_connected()) {
|
||||
return;
|
||||
}
|
||||
uint32_t opcode = 1;
|
||||
uint8_t frame_header[2];
|
||||
frame_header[0] = 1 << 7 | opcode;
|
||||
uint8_t payload_len;
|
||||
if (len <= 125) {
|
||||
payload_len = len;
|
||||
} else if (len < (1 << 16)) {
|
||||
payload_len = 126;
|
||||
} else {
|
||||
payload_len = 127;
|
||||
}
|
||||
frame_header[1] = payload_len;
|
||||
_send_raw(&ws->socket, (const uint8_t *)frame_header, 2);
|
||||
if (payload_len == 126) {
|
||||
_send_raw(&ws->socket, (const uint8_t *)&len, 2);
|
||||
} else if (payload_len == 127) {
|
||||
uint32_t zero = 0;
|
||||
// 64 bits where top four bytes are zero.
|
||||
_send_raw(&ws->socket, (const uint8_t *)&zero, 4);
|
||||
_send_raw(&ws->socket, (const uint8_t *)&len, 4);
|
||||
}
|
||||
_send_raw(&ws->socket, (const uint8_t *)text, len);
|
||||
char copy[len];
|
||||
memcpy(copy, text, len);
|
||||
copy[len] = '\0';
|
||||
}
|
||||
|
||||
void websocket_write(const char *text, size_t len) {
|
||||
_websocket_send(&cp_serial, text, len);
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2022 Scott Shawcroft for Adafruit Industries
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "shared-bindings/socketpool/Socket.h"
|
||||
|
||||
void websocket_init(void);
|
||||
void websocket_handoff(socketpool_socket_obj_t *socket);
|
||||
bool websocket_connected(void);
|
||||
bool websocket_available(void);
|
||||
char websocket_read_char(void);
|
||||
void websocket_write(const char *text, size_t len);
|
|
@ -168,8 +168,8 @@ $(BUILD)/autogen_web_workflow_static.c: ../../tools/gen_web_workflow_static.py $
|
|||
$(STATIC_RESOURCES)
|
||||
|
||||
ifeq ($(CIRCUITPY_WEB_WORKFLOW),1)
|
||||
SRC_SUPERVISOR += supervisor/shared/web_workflow/web_workflow.c
|
||||
|
||||
SRC_SUPERVISOR += supervisor/shared/web_workflow/web_workflow.c \
|
||||
supervisor/shared/web_workflow/websocket.c
|
||||
SRC_SUPERVISOR += $(BUILD)/autogen_web_workflow_static.c
|
||||
endif
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
import argparse
|
||||
import gzip
|
||||
import minify_html
|
||||
import jsmin
|
||||
import mimetypes
|
||||
import pathlib
|
||||
|
||||
|
@ -24,6 +26,10 @@ for f in args.files:
|
|||
variable = path.name.replace(".", "_")
|
||||
uncompressed = f.read()
|
||||
ulen = len(uncompressed)
|
||||
if f.name.endswith(".html"):
|
||||
uncompressed = minify_html.minify(uncompressed.decode("utf-8")).encode("utf-8")
|
||||
elif f.name.endswith(".js"):
|
||||
uncompressed = jsmin.jsmin(uncompressed.decode("utf-8")).encode("utf-8")
|
||||
compressed = gzip.compress(uncompressed)
|
||||
clen = len(compressed)
|
||||
compressed = ", ".join([hex(x) for x in compressed])
|
||||
|
|
Loading…
Reference in New Issue