1 | /* |
2 | * This file is part of the MicroPython project, http://micropython.org/ |
3 | * |
4 | * The MIT License (MIT) |
5 | * |
6 | * Copyright (c) 2013, 2014 Damien P. George |
7 | * Copyright (c) 2014 Paul Sokolovsky |
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/runtime.h" |
32 | #include "py/builtin.h" |
33 | #include "py/objtuple.h" |
34 | #include "py/binary.h" |
35 | #include "py/parsenum.h" |
36 | |
37 | #if MICROPY_PY_STRUCT |
38 | |
39 | /* |
40 | This module implements most of character typecodes from CPython, with |
41 | some extensions: |
42 | |
43 | O - (Pointer to) an arbitrary Python object. This is useful for callback |
44 | data, etc. Note that you must keep reference to passed object in |
45 | your Python application, otherwise it may be garbage-collected, |
46 | and then when you get back this value from callback it may be |
47 | invalid (and lead to crash). |
48 | S - Pointer to a string (returned as a Python string). Note the |
49 | difference from "Ns", - the latter says "in this place of structure |
50 | is character data of up to N bytes length", while "S" means |
51 | "in this place of a structure is a pointer to zero-terminated |
52 | character data". |
53 | */ |
54 | |
55 | STATIC char get_fmt_type(const char **fmt) { |
56 | char t = **fmt; |
57 | switch (t) { |
58 | case '!': |
59 | t = '>'; |
60 | break; |
61 | case '@': |
62 | case '=': |
63 | case '<': |
64 | case '>': |
65 | break; |
66 | default: |
67 | return '@'; |
68 | } |
69 | // Skip type char |
70 | (*fmt)++; |
71 | return t; |
72 | } |
73 | |
74 | STATIC mp_uint_t get_fmt_num(const char **p) { |
75 | const char *num = *p; |
76 | uint len = 1; |
77 | while (unichar_isdigit(*++num)) { |
78 | len++; |
79 | } |
80 | mp_uint_t val = (mp_uint_t)MP_OBJ_SMALL_INT_VALUE(mp_parse_num_integer(*p, len, 10, NULL)); |
81 | *p = num; |
82 | return val; |
83 | } |
84 | |
85 | STATIC size_t calc_size_items(const char *fmt, size_t *total_sz) { |
86 | char fmt_type = get_fmt_type(&fmt); |
87 | size_t total_cnt = 0; |
88 | size_t size; |
89 | for (size = 0; *fmt; fmt++) { |
90 | mp_uint_t cnt = 1; |
91 | if (unichar_isdigit(*fmt)) { |
92 | cnt = get_fmt_num(&fmt); |
93 | } |
94 | |
95 | if (*fmt == 's') { |
96 | total_cnt += 1; |
97 | size += cnt; |
98 | } else { |
99 | total_cnt += cnt; |
100 | size_t align; |
101 | size_t sz = mp_binary_get_size(fmt_type, *fmt, &align); |
102 | while (cnt--) { |
103 | // Apply alignment |
104 | size = (size + align - 1) & ~(align - 1); |
105 | size += sz; |
106 | } |
107 | } |
108 | } |
109 | *total_sz = size; |
110 | return total_cnt; |
111 | } |
112 | |
113 | STATIC mp_obj_t struct_calcsize(mp_obj_t fmt_in) { |
114 | const char *fmt = mp_obj_str_get_str(fmt_in); |
115 | size_t size; |
116 | calc_size_items(fmt, &size); |
117 | return MP_OBJ_NEW_SMALL_INT(size); |
118 | } |
119 | MP_DEFINE_CONST_FUN_OBJ_1(struct_calcsize_obj, struct_calcsize); |
120 | |
121 | STATIC mp_obj_t struct_unpack_from(size_t n_args, const mp_obj_t *args) { |
122 | // unpack requires that the buffer be exactly the right size. |
123 | // unpack_from requires that the buffer be "big enough". |
124 | // Since we implement unpack and unpack_from using the same function |
125 | // we relax the "exact" requirement, and only implement "big enough". |
126 | const char *fmt = mp_obj_str_get_str(args[0]); |
127 | size_t total_sz; |
128 | size_t num_items = calc_size_items(fmt, &total_sz); |
129 | char fmt_type = get_fmt_type(&fmt); |
130 | mp_obj_tuple_t *res = MP_OBJ_TO_PTR(mp_obj_new_tuple(num_items, NULL)); |
131 | mp_buffer_info_t bufinfo; |
132 | mp_get_buffer_raise(args[1], &bufinfo, MP_BUFFER_READ); |
133 | byte *p = bufinfo.buf; |
134 | byte *end_p = &p[bufinfo.len]; |
135 | mp_int_t offset = 0; |
136 | |
137 | if (n_args > 2) { |
138 | // offset arg provided |
139 | offset = mp_obj_get_int(args[2]); |
140 | if (offset < 0) { |
141 | // negative offsets are relative to the end of the buffer |
142 | offset = bufinfo.len + offset; |
143 | if (offset < 0) { |
144 | mp_raise_ValueError(MP_ERROR_TEXT("buffer too small" )); |
145 | } |
146 | } |
147 | p += offset; |
148 | } |
149 | byte *p_base = p; |
150 | |
151 | // Check that the input buffer is big enough to unpack all the values |
152 | if (p + total_sz > end_p) { |
153 | mp_raise_ValueError(MP_ERROR_TEXT("buffer too small" )); |
154 | } |
155 | |
156 | for (size_t i = 0; i < num_items;) { |
157 | mp_uint_t cnt = 1; |
158 | if (unichar_isdigit(*fmt)) { |
159 | cnt = get_fmt_num(&fmt); |
160 | } |
161 | mp_obj_t item; |
162 | if (*fmt == 's') { |
163 | item = mp_obj_new_bytes(p, cnt); |
164 | p += cnt; |
165 | res->items[i++] = item; |
166 | } else { |
167 | while (cnt--) { |
168 | item = mp_binary_get_val(fmt_type, *fmt, p_base, &p); |
169 | res->items[i++] = item; |
170 | } |
171 | } |
172 | fmt++; |
173 | } |
174 | return MP_OBJ_FROM_PTR(res); |
175 | } |
176 | MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(struct_unpack_from_obj, 2, 3, struct_unpack_from); |
177 | |
178 | // This function assumes there is enough room in p to store all the values |
179 | STATIC void struct_pack_into_internal(mp_obj_t fmt_in, byte *p, size_t n_args, const mp_obj_t *args) { |
180 | const char *fmt = mp_obj_str_get_str(fmt_in); |
181 | char fmt_type = get_fmt_type(&fmt); |
182 | |
183 | byte *p_base = p; |
184 | size_t i; |
185 | for (i = 0; i < n_args;) { |
186 | mp_uint_t cnt = 1; |
187 | if (*fmt == '\0') { |
188 | // more arguments given than used by format string; CPython raises struct.error here |
189 | break; |
190 | } |
191 | if (unichar_isdigit(*fmt)) { |
192 | cnt = get_fmt_num(&fmt); |
193 | } |
194 | |
195 | if (*fmt == 's') { |
196 | mp_buffer_info_t bufinfo; |
197 | mp_get_buffer_raise(args[i++], &bufinfo, MP_BUFFER_READ); |
198 | mp_uint_t to_copy = cnt; |
199 | if (bufinfo.len < to_copy) { |
200 | to_copy = bufinfo.len; |
201 | } |
202 | memcpy(p, bufinfo.buf, to_copy); |
203 | memset(p + to_copy, 0, cnt - to_copy); |
204 | p += cnt; |
205 | } else { |
206 | // If we run out of args then we just finish; CPython would raise struct.error |
207 | while (cnt-- && i < n_args) { |
208 | mp_binary_set_val(fmt_type, *fmt, args[i++], p_base, &p); |
209 | } |
210 | } |
211 | fmt++; |
212 | } |
213 | } |
214 | |
215 | STATIC mp_obj_t struct_pack(size_t n_args, const mp_obj_t *args) { |
216 | // TODO: "The arguments must match the values required by the format exactly." |
217 | mp_int_t size = MP_OBJ_SMALL_INT_VALUE(struct_calcsize(args[0])); |
218 | vstr_t vstr; |
219 | vstr_init_len(&vstr, size); |
220 | byte *p = (byte *)vstr.buf; |
221 | memset(p, 0, size); |
222 | struct_pack_into_internal(args[0], p, n_args - 1, &args[1]); |
223 | return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr); |
224 | } |
225 | MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(struct_pack_obj, 1, MP_OBJ_FUN_ARGS_MAX, struct_pack); |
226 | |
227 | STATIC mp_obj_t struct_pack_into(size_t n_args, const mp_obj_t *args) { |
228 | mp_buffer_info_t bufinfo; |
229 | mp_get_buffer_raise(args[1], &bufinfo, MP_BUFFER_WRITE); |
230 | mp_int_t offset = mp_obj_get_int(args[2]); |
231 | if (offset < 0) { |
232 | // negative offsets are relative to the end of the buffer |
233 | offset = (mp_int_t)bufinfo.len + offset; |
234 | if (offset < 0) { |
235 | mp_raise_ValueError(MP_ERROR_TEXT("buffer too small" )); |
236 | } |
237 | } |
238 | byte *p = (byte *)bufinfo.buf; |
239 | byte *end_p = &p[bufinfo.len]; |
240 | p += offset; |
241 | |
242 | // Check that the output buffer is big enough to hold all the values |
243 | mp_int_t sz = MP_OBJ_SMALL_INT_VALUE(struct_calcsize(args[0])); |
244 | if (p + sz > end_p) { |
245 | mp_raise_ValueError(MP_ERROR_TEXT("buffer too small" )); |
246 | } |
247 | |
248 | struct_pack_into_internal(args[0], p, n_args - 3, &args[3]); |
249 | return mp_const_none; |
250 | } |
251 | MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(struct_pack_into_obj, 3, MP_OBJ_FUN_ARGS_MAX, struct_pack_into); |
252 | |
253 | STATIC const mp_rom_map_elem_t mp_module_struct_globals_table[] = { |
254 | { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ustruct) }, |
255 | { MP_ROM_QSTR(MP_QSTR_calcsize), MP_ROM_PTR(&struct_calcsize_obj) }, |
256 | { MP_ROM_QSTR(MP_QSTR_pack), MP_ROM_PTR(&struct_pack_obj) }, |
257 | { MP_ROM_QSTR(MP_QSTR_pack_into), MP_ROM_PTR(&struct_pack_into_obj) }, |
258 | { MP_ROM_QSTR(MP_QSTR_unpack), MP_ROM_PTR(&struct_unpack_from_obj) }, |
259 | { MP_ROM_QSTR(MP_QSTR_unpack_from), MP_ROM_PTR(&struct_unpack_from_obj) }, |
260 | }; |
261 | |
262 | STATIC MP_DEFINE_CONST_DICT(mp_module_struct_globals, mp_module_struct_globals_table); |
263 | |
264 | const mp_obj_module_t mp_module_ustruct = { |
265 | .base = { &mp_type_module }, |
266 | .globals = (mp_obj_dict_t *)&mp_module_struct_globals, |
267 | }; |
268 | |
269 | #endif |
270 | |