espruino / Espruino

The Espruino JavaScript interpreter - Official Repo
http://www.espruino.com/
Other
2.76k stars 741 forks source link

Building Crypto on ESP8266 - compile errors #807

Closed wilberforce closed 8 years ago

wilberforce commented 8 years ago

Adding the the Makefile:

else ifdef ESP8266_BOARD
EMBEDDED=1
USE_NET=1
USE_TELNET=1
USE_CRYPTO=1
BOARD=ESP8266_BOARD
CC libs/crypto/jswrap_crypto.o
libs/crypto/jswrap_crypto.c: In function 'jswrap_crypto_error':
libs/crypto/jswrap_crypto.c:91:3: error: invalid initializer
   if (e) jsError(e);
   ^
libs/crypto/jswrap_crypto.c: At top level:JsVar
void jswrap_crypto_error(int err) {
  const char *e = jswrap_crypto_error_to_str(err);
>>>  if (e) jsError(e);
  else jsError("Unknown error: -0x%x", -err);
}

The issue appears to be the flash_str #define:

[#define](http://forum.espruino.com/sear­ch/?q=%23define) jsError(fmt, ...) do { \
    FLASH_STR(flash_str, fmt); \
    jsError_flash(flash_str, ##__VA_ARGS__); \
  } while(0)
root@esp8266-VirtualBox:~/Espruino# grep '#define FLASH_STR' src/*.h
src/jsutils.h:#define FLASH_STR(name, x) static char name[] __attribute__((section(".irom.literal"))­) __attribute__((aligned(4))) = x

I tried changing:

#define FLASH_STR(name, x) static char name[] __attribute__((section(".irom.literal"))) __attribute__((aligned(4))) = x

to #define FLASH_STR(name, x) static char * name __attribute__((section(".irom.literal"))) __attribute__((aligned(4))) = x

libs/network/esp8266/jswrap_esp8266_network.c:151:3: error: initializer element is not constant wr0, wr1, wr2, wr3, wr4, wr5, wr6, wr7, wr8, wr9, __wr10, ^ libs/network/esp8266/jswrap_esp8266_network.c:151:3: error: (near initialization for 'wifiReasons[0]')

However then it fails here:

FLASH_STR(__ev0, "#onassociated");
FLASH_STR(__ev1, "#ondisconnected");
FLASH_STR(__ev2, "#onauth_change");
FLASH_STR(__ev3, "#onconnected");
FLASH_STR(__ev4, "#ondhcp_timeout");
FLASH_STR(__ev5, "#onsta_joined");
FLASH_STR(__ev6, "#onsta_left");
FLASH_STR(__ev7, "#onprobe_recv");
static char *wifi_events[] = { __ev0, __ev1, __ev2, __ev3, __ev4, __ev5, __ev6, __ev7 };

What is the best way to solve this?

urish commented 8 years ago

http - same result, -2

seems like these codes come from the network stack. If I put a non-existent hostname (e.g. blaafdiafi.com, it returns -6. If I put a host which has doesn't have the TCP port open (e.g. face.com), we get -0xb.

wilberforce commented 8 years ago

Perhaps it's worth increasing the buffer size - you have it 2K right? MBEDTLS_SSL_MAX_CONTENT_LEN to 2048

BTW - how did you get further so that it actually creates the socket?

urish commented 8 years ago

I removed the IF statement in https://github.com/espruino/Espruino/blob/dc5cd0df3cc7e72c806980fe3d27298f8bd118e8/libs/network/network.c#L554

checking now with 2K

wilberforce commented 8 years ago

ahhh - its a decimal to hex hiding the details: https://github.com/espruino/Espruino/blob/4f40dcceba401e46c296b524990b6e98dabfd884/libs/network/socketerrors.h

typedef enum {
  SOCKET_ERR_CLOSED       = -1,
  SOCKET_ERR_MEM          = -2,
  SOCKET_ERR_TIMEOUT      = -3,
  SOCKET_ERR_NO_ROUTE     = -4,
  SOCKET_ERR_BUSY         = -5,
  SOCKET_ERR_NOT_FOUND    = -6,
  SOCKET_ERR_MAX_SOCK     = -7,
  SOCKET_ERR_UNSENT_DATA  = -8,
  SOCKET_ERR_RESET        = -9,
  SOCKET_ERR_UNKNOWN      = -10,
  SOCKET_ERR_NO_CONN      = -11,
  SOCKET_ERR_BAD_ARG      = -12,
  SOCKET_ERR_SSL_HAND     = -13,
  SOCKET_ERR_SSL_INVALID  = -14,
  SOCKET_ERR_NO_RESP      = -15,
  SOCKET_ERR_LAST         = -15, // not an error, just value of last error
} SocketError;

SOCKET_ERR_MEM = -2,

urish commented 8 years ago

good work :)

urish commented 8 years ago
>[tls.connect({host:'www.google.com',port:443}),process.memory(),esp.getState().freeHeap]
Connecting with TLS...
Performing the SSL/TLS handshake...
=[
  { "type": 4,
    "opt": {
      "host": "www.google.com",
      "port": 443 },
    "sckt": 15 },
  { "free": 133, "usage": 667, "total": 800, "history": 59 },
  2240 ]
ERROR: Failed! mbedtls_ssl_handshake returned -0x2

Now need to guess which one is missing - heap or js vars

wilberforce commented 8 years ago

I removed the IF statement in

if (jsvIsObject(options)) {
    if (!ssl_load_cacert(sd, options) ||
        !ssl_load_owncert(sd, options) ||
        !ssl_load_key(sd, options)) {
      ssl_freeSocketData(sckt);
      return false;
    }
  }

Looks like at least one is required in the or.. I think the forum post mentions these options...

tls.connect(options, callback) Description Create a socket connection using TLS Options can have ca, key and cert fields, which should be the decoded content of the certificate.

To use https:// you should not need to use the cert options... did you try:

require("http").get("https://www.google.­com", function(res) {
res.on('data', function(data) { console.log(data); });
});
wilberforce commented 8 years ago

Now need to guess which one is missing - heap or js vars

Looks like heap:

https://github.com/espruino/Espruino/search?utf8=%E2%9C%93&q=SOCKET_ERR_MEM

https://github.com/espruino/Espruino/blob/a9f4524670426b5856a6aaf14426fec2d325a486/libs/network/esp8266/network_esp8266.c#L509

507 int err = code; 508 switch (code) { 509 case ESPCONN_MEM: err = SOCKET_ERR_MEM; break; 510 case ESPCONN_ABRT: err = SOCKET_ERR_RESET; break; … 1001 releaseSocket(pSocketData); 1002 return SOCKET_ERR_MEM; 1003 }

urish commented 8 years ago

Yup, seems like it is heap - less js vars, and now we are getting -0x7200 nearly all the time.

urish commented 8 years ago

Well, after playing with esp-open-rtos for a while, it seems like MBEDTLS_SSL_MAX_CONTENT_LEN has to be somewhere around 3900 bytes (or larger), otherwise the handshake fails.

urish commented 8 years ago

@gfwilliams is there a way to allocate a big buffer off the js-vars space?

gfwilliams commented 8 years ago

@urish all the memory needed for TLS comes straight out of the JsVars space already - so you need to increase the amount of variables as much as possible.

If you want to change that, you'll have to modify config.h again to replace calls to Espruino's jsvar malloc implementation with malloc and free.

urish commented 8 years ago

@gfwilliams thanks for the response! I will look into it further, some more questions that come into my head:

  1. How much bytes does a single JsVars contribute to the JsVars space size?
  2. I see that two of the mbedTls modules have strings that consume great amounts of RAM (nearly 4k), which can make a huge difference on the ESP:
root@dd700b1d591e:/c/Users/Uri/Documents/esp-ssl# cat topstrings
10989 ./espruino_esp8266_partial.o
2493 ./libs/crypto/mbedtls/library/oid.o
1717 ./libs/crypto/mbedtls/library/x509_crt.o

Where can I find information how to move those strings into the Flash so they won't consume any RAM?

Thanks!

gfwilliams commented 8 years ago
  1. It's worth looking at http://www.espruino.com/Internals - 12 or 16 bytes, depending on if you have >1023 vars or not.
  2. This has quite a lot of info where it's been attempted with Graphics: https://github.com/espruino/Espruino/issues/770

But actually without much success, so I'm not 100% sure what's going on there. There should be a bunch of stuff on the net about moving ESP8266 stuff from RAM into flash though

wilberforce commented 8 years ago
  1. Here is the reference to work that was done to move some constants to flash https://github.com/espruino/Espruino/issues/697

@tve already did a whole bunch here: 294f3ff

It involves #define to wrap the strings.

wilberforce commented 8 years ago

Yikes . The oid.c is full of strings! Are you sure you need it for tls v 1.2? Might be worth looking into the switches you need for a minimum build, I reference a minimum config.h above that might be worth a shot..

gfwilliams commented 8 years ago

Actually you should note that some of this stuff works by first copying the string from flash into RAM - but you don't need to do that (and probably shouldn't in the case of the crypto). It'd be better to read directly from flash

wilberforce commented 8 years ago

following on what @gfwilliams is saying, here are the macro's that pack into iRom, and then read from it: https://github.com/espruino/Espruino/issues/770#issuecomment-181502608

urish commented 8 years ago

Progress report - oid.c is indeed needed for TLS, and I believe that so is x509_crt.c.

So far, the only module that I was able to disable without impacting the ability to establish TLS connection is MBEDTLS_SSL_SRV_C.

I tried to move the big tables into the ROM by adding __attribute__((section(".irom.literal"))):

static const oid_x520_attr_t oid_x520_attr_type[] __attribute__((section(".irom.literal")) =
{
    {
        { ADD_LEN( MBEDTLS_OID_AT_CN ),          "id-at-commonName",               "Common Name" },
        "CN",
    },
...
}

But this seems to have no affect on the size in topstrings (whereas commenting the entire struct content definitely reduces the size of the module in topstrings). Am I doing it right?

MaBecker commented 8 years ago

check object file with

xtensa-lx106-elf-objdump -h path/name_of_object_file.o

for a line with .irom.literal to make sure that it will be stored in flash

wilberforce commented 8 years ago

here is the definition of the macro: https://github.com/espruino/Espruino/blob/b61d388fabfdc916892f66bef5b0029badd4a6ec/src/jsutils.h#L49 #define FLASH_STR(name, x) static char name[] __attribute__((section(".irom.literal"))) __attribute__((aligned(4))) = x It also aligns of 4 byte boundarys which is required to read from flash.

and here is example use in the simple case: https://github.com/espruino/Espruino/blob/d7bfdaf0394ed9e3b242e9301cce59ebb045a0e3/libs/network/esp8266/jswrap_esp8266_network.c#L71

FLASH_STR(expect_cb, "Expecting callback function but got %v");
#define EXPECT_CB_EXCEPTION(jsCB) jsExceptionHere_flash(JSET_ERROR, expect_cb, jsCB)

I think in this case you are declaring arrays of strings, so this would need to be modified.... I don't see why your addition (with the aligned(4) would not work now for proof of concept...

Also here is using an array of strings, but there has got to be a better way! https://github.com/espruino/Espruino/blob/d7bfdaf0394ed9e3b242e9301cce59ebb045a0e3/libs/network/esp8266/jswrap_esp8266_network.c#L119

wilberforce commented 8 years ago

Looks like this is done a cunning way with arduino build: http://www.esp8266.com/viewtopic.php?f=9&t=5446

lfaustini wrote:how can I push the WString.cpp.o into the .irom0.text section? I believe that ESP8266-Arduino IDE uses a special linker script that puts many functions that are marked for the .text section into .irom0.text. The excerpt below comes from esp8266/Arduino on GitHub: CODE: SELECT ALL .irom0.text : ALIGN(4) { _irom0_text_start = ABSOLUTE(.); core_esp8266_.o(.literal, .text_) spiffs.o(.literal, .text) .cpp.o(.literal, .text) libm.a:(.literal .text .literal. .text.) libsmartconfig.a:(.literal .text .literal._ .text.) (.irom0.literal .irom.literal .irom.text.literal .irom0.text .irom.text) _irom0_text_end = ABSOLUTE(.); _flash_code_end = ABSOLUTE(.); } >irom0_0_seg :irom0_0_phdr

So this ld file moves stuff into flash, without having to use macros.

I think it works on functions too.... Looks promising..

https://github.com/esp8266/Arduino/blob/55e5bdfc6cb28fa79f94953f292f66b38804fc67/tools/sdk/ld/eagle.app.v6.common.ld

gfwilliams commented 8 years ago

You could try it. I wonder if that's program code only though - He's talking about WString.o - not WStrings in general :)

The issue is that accesses to flash need to word-only and word-aligned. Just dumping a string into ROM will be fine until you go to access a character from it. Hence the macros.

Until the compiler is smart enough to auto-generate only word accesses for stuff in ROM, you will have no choice but to manually modify code.

wilberforce commented 8 years ago

@tve @gfwilliams

This technique installs an exception handler that catches the mis-aligned reads, and corrects them.

https://github.com/cesanta/smart.js/blob/master/smartjs/platforms/esp8266/user/esp_flash_bytes.c

It looks a lot simpler that having to copy all the ROM strings from iRom to ram before use...

tve commented 8 years ago

What is the performance impact of this, though?

wilberforce commented 8 years ago

I have no idea - convenience vs performance?

tve commented 8 years ago

well, the performance already isn't all that great. At some point it just sucks. You're gonna have to use some macro to put the strings into flash anyhow, is there that much to be gained by not having a macro that does the copy? How many bytes of strings are left? How many of those could possibly be moved to flash?

wilberforce commented 8 years ago

Top 20 from ./topstrings: 7250 ./espruino_esp8266_partial.o 765 ./src/jsinteractive.o 542 ./libs/network/socketserver.o 512 ./gen/jswrapper.o 438 ./libs/network/esp8266/jswrap_esp8266_network.o 387 ./libs/network/esp8266/ota.o 295 ./src/jslex.o 245 ./src/jsvar.o 225 ./src/jsparse.o 216 ./libs/network/telnet/jswrap_telnet.o 214 ./src/jswrap_process.o 207 ./targets/esp8266/esp8266_board_utils.o 207 ./src/jswrap_json.o 196 ./src/jswrap_io.o 196 ./libs/network/socketerrors.o 176 ./libs/network/esp8266/network_esp8266.o 151 ./targets/esp8266/user_main.o 146 ./src/jswrap_object.o 137 ./src/jswrap_espruino.o 132 ./src/jswrap_pipe.o

If the constants in jswrapper were aligned on 4 byte boundaries we could keep the reference tables in flash... That would save a heap of space".

The non- string tables might be any easy target, as they are generated - these are not showing in the top strings that you have kindly written.

urish commented 8 years ago

we will probably also be able to get mbedTLS working on the ESP :-)

gfwilliams commented 8 years ago

Assuming we could catch unaligned accesses, we could just copy all the const data to flash and remove the irom defines completely. It sounds very tempting.

the performance already isn't all that great

I guess it depends how you look at it. It's not great, so would people notice if it got a bit worse? :) The main complaint at the moment actually seems to be lack of RAM, which this might help with.

If someone has some free time/inclination, maybe they could try this just dump everything into ROM, removing all the ESP8266-specific flash copying/etc, and using that exception handler. The only way to know for sure is to do some benchmarks and see how much it hits performance.

If it's loads then I think it'd be a bad idea, but if it's a few percent I think it's a hit worth taking.

From my point of view, it'd be nice to remove the slightly iffy preprocessor stuff around print statements, and I think if you're printing/making error messages then you don't really care about performance either.

The one thing I think could hit performance is the symbol table, but then a few very simple ESP8266-specific tweaks in jswrapper.c could fix that.

Perhaps some of the mbedTLS stuff could end up being quite slow, but then if it's a choice of having it slow or not fitting it in at all, I think it's worth having it in.

wilberforce commented 8 years ago

@gfwilliams has merged the build with sha crypto for the esp8266 - so now the standard build will support web sockets:

https://github.com/espruino/Espruino/pull/824

wilberforce commented 8 years ago

In aes.c there is a save-to-rom macro, however in the esp8266 case the tables still end up in ram.

There is still not enough heap space for the buffers, so unless more code is moved from ram to irom, TLS support for https can't work without reducing jsvars.

My initial aim of supporting web sockets via SHA1 is done, so closing issue.