Skip to content

Commit a0058e6

Browse files
committed
Introduce a random module that is a subset of CPython's random. It
also initializes in the same way where it takes from a true random source when available through os.urandom(). After initializing, it produces deterministic results until the seed is set. This replaces urandom! Fixes adafruit#139.
1 parent 265d5da commit a0058e6

File tree

12 files changed

+416
-11
lines changed

12 files changed

+416
-11
lines changed

atmel-samd/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ SRC_SHARED_MODULE = \
272272
bitbangio/SPI.c \
273273
busio/OneWire.c \
274274
os/__init__.c \
275+
random/__init__.c \
275276
storage/__init__.c \
276277
uheap/__init__.c \
277278

atmel-samd/common-hal/os/__init__.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,7 @@ STATIC MP_DEFINE_ATTRTUPLE(
5555
mp_obj_t common_hal_os_uname(void) {
5656
return (mp_obj_t)&os_uname_info_obj;
5757
}
58+
59+
bool common_hal_os_urandom(uint8_t* buffer, uint32_t length) {
60+
return false;
61+
}

atmel-samd/mpconfigport.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@
5757
#define MICROPY_PY_MATH (1)
5858
#define MICROPY_PY_CMATH (0)
5959
#define MICROPY_PY_IO (0)
60-
#define MICROPY_PY_URANDOM (1)
61-
#define MICROPY_PY_URANDOM_EXTRA_FUNCS (1)
60+
#define MICROPY_PY_URANDOM (0)
61+
#define MICROPY_PY_URANDOM_EXTRA_FUNCS (0)
6262
#define MICROPY_PY_STRUCT (1)
6363
#define MICROPY_PY_SYS (1)
6464
#define MICROPY_CPYTHON_COMPAT (0)
@@ -138,6 +138,7 @@ extern const struct _mp_obj_module_t pulseio_module;
138138
extern const struct _mp_obj_module_t busio_module;
139139
extern const struct _mp_obj_module_t board_module;
140140
extern const struct _mp_obj_module_t os_module;
141+
extern const struct _mp_obj_module_t random_module;
141142
extern const struct _mp_obj_module_t storage_module;
142143
extern const struct _mp_obj_module_t time_module;
143144
extern const struct _mp_obj_module_t neopixel_write_module;
@@ -176,6 +177,7 @@ extern const struct _mp_obj_module_t usb_hid_module;
176177
{ MP_OBJ_NEW_QSTR(MP_QSTR_busio), (mp_obj_t)&busio_module }, \
177178
{ MP_OBJ_NEW_QSTR(MP_QSTR_board), (mp_obj_t)&board_module }, \
178179
{ MP_OBJ_NEW_QSTR(MP_QSTR_os), (mp_obj_t)&os_module }, \
180+
{ MP_OBJ_NEW_QSTR(MP_QSTR_random), (mp_obj_t)&random_module }, \
179181
{ MP_OBJ_NEW_QSTR(MP_QSTR_storage), (mp_obj_t)&storage_module }, \
180182
{ MP_OBJ_NEW_QSTR(MP_QSTR_time), (mp_obj_t)&time_module }, \
181183
{ MP_OBJ_NEW_QSTR(MP_QSTR_neopixel_write),(mp_obj_t)&neopixel_write_module }, \

esp8266/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ SRC_SHARED_MODULE = \
139139
busio/I2C.c \
140140
busio/OneWire.c \
141141
os/__init__.c \
142+
random/__init__.c \
142143
storage/__init__.c \
143144

144145
SRC_SHARED_MODULE_EXPANDED = $(addprefix shared-bindings/, $(SRC_SHARED_MODULE)) \

esp8266/common-hal/os/__init__.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
#include <string.h>
2929

30+
#include "etshal.h"
3031
#include "py/objtuple.h"
3132
#include "py/objstr.h"
3233
#include "genhdr/mpversion.h"
@@ -60,3 +61,20 @@ mp_obj_t common_hal_os_uname(void) {
6061
os_uname_info_obj.items[2] = mp_obj_new_str(ver, strlen(ver), false);
6162
return (mp_obj_t)&os_uname_info_obj;
6263
}
64+
65+
static uint32_t last_random;
66+
bool common_hal_os_urandom(uint8_t* buffer, uint32_t length) {
67+
uint32_t i = 0;
68+
while (i < length) {
69+
uint32_t new_random = last_random;
70+
while (new_random == last_random) {
71+
new_random = *WDEV_HWRNG;
72+
}
73+
for (int j = 0; j < 4 && i < length; j++) {
74+
buffer[i] = new_random & 0xff;
75+
i++;
76+
new_random >>= 8;
77+
}
78+
}
79+
return true;
80+
}

esp8266/mpconfigport.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
#define MICROPY_PY_UHEAPQ (1)
6969
#define MICROPY_PY_UTIMEQ (1)
7070
#define MICROPY_PY_UJSON (1)
71-
#define MICROPY_PY_URANDOM (1)
71+
#define MICROPY_PY_URANDOM (0)
7272
#define MICROPY_PY_URE (1)
7373
#define MICROPY_PY_USELECT (1)
7474
#define MICROPY_PY_UTIME_MP_HAL (1)
@@ -155,8 +155,8 @@ void *esp_native_code_commit(void*, size_t);
155155
// extra built in modules to add to the list of known ones
156156
extern const struct _mp_obj_module_t esp_module;
157157
extern const struct _mp_obj_module_t network_module;
158-
extern const struct _mp_obj_module_t utime_module;
159158
extern const struct _mp_obj_module_t os_module;
159+
extern const struct _mp_obj_module_t random_module;
160160
extern const struct _mp_obj_module_t storage_module;
161161
extern const struct _mp_obj_module_t mp_module_lwip;
162162
extern const struct _mp_obj_module_t mp_module_machine;
@@ -186,6 +186,7 @@ extern const struct _mp_obj_module_t time_module;
186186
{ MP_OBJ_NEW_QSTR(MP_QSTR_busio), (mp_obj_t)&busio_module }, \
187187
{ MP_OBJ_NEW_QSTR(MP_QSTR_bitbangio), (mp_obj_t)&bitbangio_module }, \
188188
{ MP_OBJ_NEW_QSTR(MP_QSTR_storage), (mp_obj_t)&storage_module }, \
189+
{ MP_OBJ_NEW_QSTR(MP_QSTR_random), (mp_obj_t)&random_module }, \
189190
{ MP_OBJ_NEW_QSTR(MP_QSTR_time), (mp_obj_t)&time_module }, \
190191

191192
#define MICROPY_PORT_BUILTIN_MODULE_WEAK_LINKS \

shared-bindings/index.rst

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ limited. For example, a microcontroller without analog features will not have
1212
Support Matrix
1313
---------------
1414

15-
=============== ========== ========= =========== ======= ======= =========== ================= ================ ======= ========= ========= ======== ========= ======= =========
16-
Port `analogio` `audioio` `bitbangio` `board` `busio` `digitalio` `microcontroller` `neopixel_write` `os` `pulseio` `storage` `time` `touchio` `uheap` `usb_hid`
17-
=============== ========== ========= =========== ======= ======= =========== ================= ================ ======= ========= ========= ======== ========= ======= =========
18-
SAMD21 **Yes** No No **Yes** **Yes** **Yes** **Yes** **Yes** **Yes** No **Yes** **Yes** **Yes** Debug **Yes**
19-
SAMD21 Express **Yes** **Yes** **Yes** **Yes** **Yes** **Yes** **Yes** **Yes** **Yes** **Yes** **Yes** **Yes** **Yes** Debug **Yes**
20-
ESP8266 **Yes** No **Yes** **Yes** **Yes** **Yes** **Yes** **Yes** **Yes** No **Yes** **Yes** No Debug No
21-
=============== ========== ========= =========== ======= ======= =========== ================= ================ ======= ========= ========= ======== ========= ======= =========
15+
=============== ========== ========= =========== ======= ======= =========== ================= ================ ======= ========= ======== ========= ======== ========= ======= =========
16+
Port `analogio` `audioio` `bitbangio` `board` `busio` `digitalio` `microcontroller` `neopixel_write` `os` `pulseio` `random` `storage` `time` `touchio` `uheap` `usb_hid`
17+
=============== ========== ========= =========== ======= ======= =========== ================= ================ ======= ========= ======== ========= ======== ========= ======= =========
18+
SAMD21 **Yes** No No **Yes** **Yes** **Yes** **Yes** **Yes** **Yes** No **Yes** **Yes** **Yes** **Yes** Debug **Yes**
19+
SAMD21 Express **Yes** **Yes** **Yes** **Yes** **Yes** **Yes** **Yes** **Yes** **Yes** **Yes** **Yes** **Yes** **Yes** **Yes** Debug **Yes**
20+
ESP8266 **Yes** No **Yes** **Yes** **Yes** **Yes** **Yes** **Yes** **Yes** No **Yes** **Yes** **Yes** No Debug No
21+
=============== ========== ========= =========== ======= ======= =========== ================= ================ ======= ========= ======== ========= ======== ========= ======= =========
2222

2323
Modules
2424
---------

shared-bindings/os/__init__.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "lib/oofatfs/diskio.h"
3434
#include "py/mpstate.h"
3535
#include "py/obj.h"
36+
#include "py/runtime.h"
3637
#include "shared-bindings/os/__init__.h"
3738

3839
//| :mod:`os` --- functions that an OS normally provides
@@ -187,6 +188,20 @@ STATIC mp_obj_t os_sync(void) {
187188
}
188189
MP_DEFINE_CONST_FUN_OBJ_0(os_sync_obj, os_sync);
189190

191+
//| .. function:: urandom(size)
192+
//|
193+
//| Returns a string of *size* random bytes based on a hardware True Random
194+
//| Number Generator. When not available, it will raise a NotImplementedError.
195+
//|
196+
STATIC mp_obj_t os_urandom(mp_obj_t size_in) {
197+
mp_int_t size = mp_obj_get_int(size_in);
198+
uint8_t tmp[size];
199+
if (!common_hal_os_urandom(tmp, size)) {
200+
mp_raise_NotImplementedError("");
201+
}
202+
return mp_obj_new_bytes(tmp, size);
203+
}
204+
MP_DEFINE_CONST_FUN_OBJ_1(os_urandom_obj, os_urandom);
190205

191206
STATIC const mp_rom_map_elem_t os_module_globals_table[] = {
192207
{ MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_os) },
@@ -206,6 +221,8 @@ STATIC const mp_rom_map_elem_t os_module_globals_table[] = {
206221

207222
{ MP_OBJ_NEW_QSTR(MP_QSTR_sync), MP_ROM_PTR(&os_sync_obj) },
208223

224+
{ MP_OBJ_NEW_QSTR(MP_QSTR_urandom), MP_ROM_PTR(&os_urandom_obj) },
225+
209226
//| .. data:: sep
210227
//|
211228
//| Separator used to dileneate path components such as folder and file names.

shared-bindings/os/__init__.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,7 @@ void common_hal_os_rmdir(const char* path);
4545
mp_obj_t common_hal_os_stat(const char* path);
4646
mp_obj_t common_hal_os_statvfs(const char* path);
4747

48+
// Returns true if data was correctly sourced from a true random number generator.
49+
bool common_hal_os_urandom(uint8_t* buffer, mp_uint_t length);
50+
4851
#endif // __MICROPY_INCLUDED_SHARED_BINDINGS_OS___INIT___H__

shared-bindings/random/__init__.c

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
/*
2+
* This file is part of the Micro Python project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2016 Paul Sokolovsky
7+
* Copyright (c) 2016 Scott Shawcroft for Adafruit Industries
8+
*
9+
* Permission is hereby granted, free of charge, to any person obtaining a copy
10+
* of this software and associated documentation files (the "Software"), to deal
11+
* in the Software without restriction, including without limitation the rights
12+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
* copies of the Software, and to permit persons to whom the Software is
14+
* furnished to do so, subject to the following conditions:
15+
*
16+
* The above copyright notice and this permission notice shall be included in
17+
* all copies or substantial portions of the Software.
18+
*
19+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
* THE SOFTWARE.
26+
*/
27+
28+
#include <assert.h>
29+
#include <string.h>
30+
31+
#include "py/obj.h"
32+
#include "py/runtime.h"
33+
#include "shared-bindings/random/__init__.h"
34+
35+
//| :mod:`random` --- psuedo-random numbers and choices
36+
//| ========================================================
37+
//|
38+
//| .. module:: random
39+
//| :synopsis: psuedo-random numbers and choices
40+
//| :platform: SAMD21, ESP8266
41+
//|
42+
//| The `random` module is a strict subset of the CPython `cpython:random`
43+
//| module. So, code written in CircuitPython will work in CPython but not
44+
//| necessarily the other way around.
45+
//|
46+
//| Like its CPython cousin, CircuitPython's random seeds itself on first use
47+
//| with a true random from os.urandom() when available or the uptime otherwise.
48+
//| Once seeded, it will be deterministic, which is why its bad for cryptography.
49+
//|
50+
//| .. warning:: Numbers from this module are not cryptographically strong! Use
51+
//| bytes from `os.urandom` directly for true randomness.
52+
//|
53+
54+
//| .. function:: seed(seed)
55+
//|
56+
//| Sets the starting seed of the random number generation. Further calls to
57+
//| `random` will return deterministic results afterwards.
58+
//|
59+
STATIC mp_obj_t random_seed(mp_obj_t seed_in) {
60+
mp_uint_t seed = mp_obj_get_int_truncated(seed_in);
61+
shared_modules_random_seed(seed);
62+
return mp_const_none;
63+
}
64+
STATIC MP_DEFINE_CONST_FUN_OBJ_1(random_seed_obj, random_seed);
65+
66+
//| .. function:: getrandbits(k)
67+
//|
68+
//| Returns an integer with *k* random bits.
69+
//|
70+
STATIC mp_obj_t random_getrandbits(mp_obj_t num_in) {
71+
int n = mp_obj_get_int(num_in);
72+
if (n > 32 || n == 0) {
73+
mp_raise_ValueError(NULL);
74+
}
75+
return mp_obj_new_int_from_uint(shared_modules_random_getrandbits((uint8_t) n));
76+
}
77+
STATIC MP_DEFINE_CONST_FUN_OBJ_1(random_getrandbits_obj, random_getrandbits);
78+
79+
//| .. function:: randrange(stop)
80+
//| randrange(start, stop, step=1)
81+
//|
82+
//| Returns a randomly selected integer from `range(start, stop, step)`.
83+
//|
84+
STATIC mp_obj_t random_randrange(size_t n_args, const mp_obj_t *args) {
85+
mp_int_t start = 0;
86+
mp_int_t stop = mp_obj_get_int(args[0]);
87+
mp_int_t step = 1;
88+
if (n_args == 1) {
89+
// range(stop)
90+
if (stop <= 0) {
91+
mp_raise_ValueError("stop not reachable from start");
92+
}
93+
} else {
94+
start = stop;
95+
stop = mp_obj_get_int(args[1]);
96+
if (n_args == 2) {
97+
// range(start, stop)
98+
if (start >= stop) {
99+
mp_raise_ValueError("stop not reachable from start");
100+
}
101+
} else {
102+
// range(start, stop, step)
103+
step = mp_obj_get_int(args[2]);
104+
mp_int_t n;
105+
if (step > 0) {
106+
n = (stop - start + step - 1) / step;
107+
} else if (step < 0) {
108+
n = (stop - start + step + 1) / step;
109+
} else {
110+
mp_raise_ValueError("step must be non-zero");
111+
}
112+
if (n <= 0) {
113+
mp_raise_ValueError("invalid step");
114+
}
115+
}
116+
}
117+
118+
return mp_obj_new_int(shared_modules_random_randrange(start, stop, step));
119+
}
120+
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(random_randrange_obj, 1, 3, random_randrange);
121+
122+
//| .. function:: randint(a, b)
123+
//|
124+
//| Returns a randomly selected integer between a and b inclusive. Equivalent
125+
//| to `randrange(a, b + 1, 1)`
126+
//|
127+
STATIC mp_obj_t random_randint(mp_obj_t a_in, mp_obj_t b_in) {
128+
mp_int_t a = mp_obj_get_int(a_in);
129+
mp_int_t b = mp_obj_get_int(b_in);
130+
if (a > b) {
131+
mp_raise_ValueError(NULL);
132+
}
133+
return mp_obj_new_int(shared_modules_random_randrange(a, b + 1, 1));
134+
}
135+
STATIC MP_DEFINE_CONST_FUN_OBJ_2(random_randint_obj, random_randint);
136+
137+
//| .. function:: choice(seq)
138+
//|
139+
//| Returns a randomly selected element from the given sequence. Raises
140+
//| IndexError when the sequence is empty.
141+
//|
142+
STATIC mp_obj_t random_choice(mp_obj_t seq) {
143+
mp_int_t len = mp_obj_get_int(mp_obj_len(seq));
144+
if (len == 0) {
145+
mp_raise_IndexError("empty sequence");
146+
}
147+
return mp_obj_subscr(seq, mp_obj_new_int(shared_modules_random_randrange(0, len, 1)), MP_OBJ_SENTINEL);
148+
}
149+
STATIC MP_DEFINE_CONST_FUN_OBJ_1(random_choice_obj, random_choice);
150+
151+
//| .. function:: random()
152+
//|
153+
//| Returns a random float between 0 and 1.0.
154+
//|
155+
STATIC mp_obj_t random_random(void) {
156+
return mp_obj_new_float(shared_modules_random_random());
157+
}
158+
STATIC MP_DEFINE_CONST_FUN_OBJ_0(random_random_obj, random_random);
159+
160+
//| .. function:: uniform(a, b)
161+
//|
162+
//| Returns a random float between a and b. It may or may not be inclusive
163+
//| depending on float rounding.
164+
//|
165+
STATIC mp_obj_t random_uniform(mp_obj_t a_in, mp_obj_t b_in) {
166+
mp_float_t a = mp_obj_get_float(a_in);
167+
mp_float_t b = mp_obj_get_float(b_in);
168+
return mp_obj_new_float(shared_modules_random_uniform(a, b));
169+
}
170+
STATIC MP_DEFINE_CONST_FUN_OBJ_2(random_uniform_obj, random_uniform);
171+
172+
STATIC const mp_rom_map_elem_t mp_module_random_globals_table[] = {
173+
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_random) },
174+
{ MP_ROM_QSTR(MP_QSTR_seed), MP_ROM_PTR(&random_seed_obj) },
175+
{ MP_ROM_QSTR(MP_QSTR_getrandbits), MP_ROM_PTR(&random_getrandbits_obj) },
176+
{ MP_ROM_QSTR(MP_QSTR_randrange), MP_ROM_PTR(&random_randrange_obj) },
177+
{ MP_ROM_QSTR(MP_QSTR_randint), MP_ROM_PTR(&random_randint_obj) },
178+
{ MP_ROM_QSTR(MP_QSTR_choice), MP_ROM_PTR(&random_choice_obj) },
179+
{ MP_ROM_QSTR(MP_QSTR_random), MP_ROM_PTR(&random_random_obj) },
180+
{ MP_ROM_QSTR(MP_QSTR_uniform), MP_ROM_PTR(&random_uniform_obj) },
181+
};
182+
183+
STATIC MP_DEFINE_CONST_DICT(mp_module_random_globals, mp_module_random_globals_table);
184+
185+
const mp_obj_module_t random_module = {
186+
.base = { &mp_type_module },
187+
.globals = (mp_obj_dict_t*)&mp_module_random_globals,
188+
};

0 commit comments

Comments
 (0)