espruino / Espruino

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

nRF52 writable NFC #1935

Open gfwilliams opened 3 years ago

gfwilliams commented 3 years ago

Right now you can make your own NFC tag with Puck/Pixl.js with the NFCtag module or going bare metal like http://forum.espruino.com/conversations/354213/

Thing is we already support reading NFC tags for nfcURL/etc - there's no reason that code (in the handler for BLEP_NFC_RX) couldn't optionally handle writing too, since the data is in RAM. It'd make doing this sort of thing much easier.

DanTheMan827 commented 2 years ago

It depends on the polling device, but I found is that depending on the device it may or may not work within the timing of the event loop.

I made a writable tag mostly conforming to the NTAG215 protocol by putting the hal_nfc function pointers inside of an array and exposing the pointer to that array as a javascript variable.

That method works and the tag is readable and writable by devices expecting an NTAG215 (although the protocol isn't 100% implemented yet)

Perhaps it would be a good idea to add a signature like NRF.nfcStart([native function]) so people could implement their own tag protocols? It would however require exposing at minimum hal_nfc_send and hal_nfc_send_rsp

The idea is that you'd have the user give a function conforming to hal_nfc_callback_t which would be used as the NFC callback bypassing the event loop completely

Just a thought.

Here's the code I wrote making use of the hal functions directly, and aside from signal issues due to the antenna size it works fine.

// void init(int)
// void setTagPointer(int)
// void setBufferPointer(int)
// bool getTagWritten()
// int startNfc()
// int stopNfc()
// int restartNfc()
typedef unsigned int size_t;
typedef unsigned int ret_code_t;
typedef unsigned char uint8_t;
typedef enum {
    HAL_NFC_EVENT_FIELD_ON,
    HAL_NFC_EVENT_FIELD_OFF,
    HAL_NFC_EVENT_DATA_RECEIVED,
    HAL_NFC_EVENT_DATA_TRANSMITTED
} hal_nfc_event_t;
typedef enum {
    HAL_NFC_PARAM_ID_TESTING,
    HAL_NFC_PARAM_ID_NFCID1,
    HAL_NFC_PARAM_ID_INTERNAL,
    HAL_NFC_PARAM_ID_UNKNOWN
} hal_nfc_param_id_t;
typedef void (* hal_nfc_callback_t)(void          * p_context,
                                    hal_nfc_event_t event,
                                    const uint8_t * p_data,
                                    size_t          data_length);
unsigned int *native;
ret_code_t hal_nfc_setup(hal_nfc_callback_t callback, void * p_context){
  return ( (ret_code_t(*)(hal_nfc_callback_t, void *))native[0])(callback, p_context);
}
ret_code_t hal_nfc_parameter_set(hal_nfc_param_id_t id, const void * p_data, size_t data_length){
  return ( (ret_code_t(*)(hal_nfc_param_id_t, const void *, size_t))native[1])(id, p_data, data_length);
}
ret_code_t hal_nfc_parameter_get(hal_nfc_param_id_t id, void * p_data, size_t * p_max_data_length){
  return ( (ret_code_t(*)(hal_nfc_param_id_t, void *, size_t *))native[2])(id, p_data, p_max_data_length);
}
ret_code_t hal_nfc_start(void){
  return ( (ret_code_t(*)())native[3])();
}
ret_code_t hal_nfc_send(const uint8_t * p_data, size_t data_length){
  return ( (ret_code_t(*)(const uint8_t *, size_t))native[4])(p_data, data_length);
}
ret_code_t hal_nfc_send_rsp(const uint8_t p_data, size_t data_length){
  return ( (ret_code_t(*)(const uint8_t, size_t))native[5])(p_data, data_length);
}
ret_code_t hal_nfc_stop(void){
  return ( (ret_code_t(*)())native[6])();
}
ret_code_t hal_nfc_done(void){
  return ( (ret_code_t(*)())native[7])();
}
ret_code_t startNfc(void);
ret_code_t stopNfc(void);
ret_code_t restartNfc(void);
void print(const char *text){
  JsVar *p = jspGetNamedVariable("print");
  JsVar *s = jsvNewFromString(text);
  jsvUnLock(jspeFunctionCall(p,0,0,false,1,&s));
  jsvUnLock(s);
  jsvUnLock(p);
}
void printInt(int number){
  JsVar *p = jspGetNamedVariable("print");
  JsVar *s = jsvNewFromInteger(number);
  jsvUnLock(jspeFunctionCall(p,0,0,false,1,&s));
  jsvUnLock(s);
  jsvUnLock(p);
}
void fieldState(int number){
  return;
  JsVar *p = jspGetNamedVariable("fieldState");
  JsVar *s = jsvNewFromInteger(number);
  jsvUnLock(jspeFunctionCall(p,0,0,false,1,&s));
  jsvUnLock(s);
  jsvUnLock(p);
}
void init(void* pointer){
  native = pointer;
}
unsigned char *tag = 0;
void setTagPointer(unsigned char *pointer){
  tag = pointer;
}
unsigned char *tagUid = 0;
unsigned char *tx = 0;
void setBufferPointer(unsigned char *pointer){
  tagUid = &pointer[0];
  tx = &pointer[7];
}
unsigned char version[] = {0x00, 0x04, 0x04, 0x02, 0x01, 0x00, 0x11, 0x03};
unsigned char password_success[] = {0x80, 0x80};
unsigned char puck_success[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
unsigned char zero_page[] = {0x00, 0x00, 0x00, 0x00};
bool authenticated = false;
void setAuthenticated(bool value){ authenticated = value; }
bool getAuthenticated(){ return authenticated; }
bool backdoor = false;
void setBackdoor(bool value){ backdoor = value; }
bool getBackdoor() { return backdoor; }
bool tagWritten = false;
void setTagWritten(bool value){ tagWritten = value; }
bool getTagWritten(){ return tagWritten; }
bool fixUid(){
  unsigned char bcc0 = tag[0] ^ tag[1] ^ tag[2] ^ 0x88;
  unsigned char bcc1 = tag[4] ^ tag[5] ^ tag[6] ^ tag[7];
  if (tag[3] != bcc0 || tag[8] != bcc1){
    tag[3] = bcc0;
    tag[8] = bcc1;
    return true;
  }
  return false;
}
bool isLocked(int page){
  if (page == 0 || page == 1) return true;
  // Static Lock Bytes
  int bit;
  for (bit = 0; bit < 8; bit++){
    if (tag[11] & (1 << bit)){
      if ((bit + 8) == page){
        return true;
      }
    }
    if (tag[10] & (1 << bit)){
      switch (bit){
        case 0: //BL-CC
        case 1: //BL-9-4
        case 2: //BL-15-10
        case 3: //L-CC
          break;
        default: {
          if ((bit + 4) == page){
            return true;
          }
        } break;
      }
    }
  }
  if (authenticated == false){
    if (tag[520] & 0b00000001 > 0 && (page >= 16 && page <= 31))
      return true;
    if (tag[520] & 0b00000010 > 0 && (page >= 32 && page <= 47))
      return true;
    if (tag[520] & 0b00000100 > 0 && (page >= 48 && page <= 63))
      return true;
    if (tag[520] & 0b00001000 > 0 && (page >= 64 && page <= 79))
      return true;
    if (tag[520] & 0b00010000 > 0 && (page >= 80 && page <= 95))
      return true;
    if (tag[520] & 0b00100000 > 0 && (page >= 96 && page <= 111))
      return true;
    if (tag[520] & 0b01000000 > 0 && (page >= 112 && page <= 127))
      return true;
    if (tag[520] & 0b10000000 > 0 && (page >= 128 && page <= 129))
      return true;
  }
  return false;
}
unsigned char *memcpy(unsigned char *dest_str, unsigned char *src_str, int number){
  int i;
  for (i = 0; i < number; i++)
    dest_str[i] = src_str[i];
  return dest_str;
}
unsigned char *readBlock(uint8_t block){

}
void processRx(unsigned char *rx, int rxLen){
  if (rx == 0){
    print("NFC RX pointer is 0");
  }
  if (rxLen == 0){
    hal_nfc_send_rsp(0, 0);
    print("NFC RX length is 0");
    return;
  }
  switch (rx[0]) {
    case 0x30: { // 48 - Read
      if (rxLen < 2){
        print("READ: bad rxlen");
        hal_nfc_send_rsp(0, 0);
        return;
      }
      hal_nfc_send(&tag[rx[1] * 4], 16);
      return;
    }
    case 0xA2: { //  162 - Write
      if (backdoor == false && (rx[1] < 0 || rx[1] > 134 || isLocked(rx[1]))) {
        hal_nfc_send_rsp(0, 4);
        return;
      }
      if (backdoor == false) {
        if (rx[1] == 2) {
          tag[10] = tag[10] | rx[4];
          tag[11] = tag[11] | rx[5];
          tagWritten = true;
          hal_nfc_send_rsp(0x0a, 4);
          return;
        }
        if (rx[1] == 3) {
          tag[16] = tag[16] | rx[2];
          tag[17] = tag[17] | rx[3];
          tag[18] = tag[18] | rx[4];
          tag[19] = tag[19] | rx[5];
          tagWritten = true;
          hal_nfc_send_rsp(0x0a, 4);
          return;
        }
        if (rx[1] == 130) {
          // TODO: Dynamic lock bits
        }
      }
      int index = rx[1] * 4;
      if ((index > 568) || (backdoor == false && index > 536)) {
        hal_nfc_send_rsp(0, 4);
        print("page write oob");
        return;
      } else {
        memcpy(&tag[index], &rx[2], 4);
        tagWritten = true;
        hal_nfc_send_rsp(0xA, 4);
        return;
      }
    }
    case 0x60: { // 96 - Version
      hal_nfc_send(version, 8);
      return;
    }
    case 0x3A: { // 58 - Fast Read
      if (rxLen < 3){
        hal_nfc_send_rsp(0, 4);
        print("Invalid fast read rx length");
        return;
      }
      if (rx[1] > rx[2] || rx[1] < 0 || rx[2] > 134) {
        hal_nfc_send_rsp(0, 4);
        print("Invalid fast read address");
        return;
      }
      if (rx[1] == 133 && rx[2] == 134) {
        backdoor = true;
        print("Backdoor enabled");
        hal_nfc_send(puck_success, 8);
        return;
      }
      unsigned int tag_location = rx[1] * 4;
      unsigned int tx_len = (rx[2] - rx[1] + 1) * 4;
      hal_nfc_send(&tag[tag_location], tx_len);
      return;
    }
    case 0x1B: { // 27 - Password Auth
      hal_nfc_send(password_success, 2);
      authenticated = true;
      return;
    }
    case 0x3C: { // 60 - Read Signature
      hal_nfc_send(&tag[540], 32);
      return;
    }
    case 0x88: { // 136 - CUSTOM: Restart NFC
      hal_nfc_send_rsp(0xA, 4);
      restartNfc();
      return;
    }
    default: { // Unknown command
      hal_nfc_send_rsp(0, 0);
      print("Unknown Command");
      printInt(rx[0]);
      return;
    }
  }
}
void nfc_callback(void * p_context, hal_nfc_event_t event, const uint8_t * p_data, size_t data_length) {
  switch (event){
    case HAL_NFC_EVENT_FIELD_ON: 
      fieldState(1);
      break;
    case HAL_NFC_EVENT_FIELD_OFF:
      fieldState(0);
      authenticated = false;
      backdoor = false;
      if (fixUid()){
        restartNfc();
      }
      break;
    case HAL_NFC_EVENT_DATA_RECEIVED:
      processRx(p_data, data_length);
      break;
    case HAL_NFC_EVENT_DATA_TRANSMITTED: break;
    default:
      print("Unknown nfc event");
      printInt(event);
  }
}
ret_code_t stopNfc(){
  if (native == 0) {
    print("stopNfc: No method pointer set");
    return 99;
  }
  hal_nfc_stop();
  hal_nfc_done();
}
ret_code_t startNfc(){
  ret_code_t ret_val = 0;
  if (native == 0) {
    print("startNfc: No method pointer set");
    return 99;
  }
  if (tag == 0){
    print("No tag pointer set");
    return 100;
  }
  if (tagUid == 0){
    print("No buffer pointer set");
    return 101;
  }
  stopNfc();
  if (fixUid()){
    print("Fixed UID");
  }
  tagUid[0] = tag[0];
  tagUid[1] = tag[1];
  tagUid[2] = tag[2];
  tagUid[3] = tag[4];
  tagUid[4] = tag[5];
  tagUid[5] = tag[6];
  tagUid[6] = tag[7];
  ret_val = hal_nfc_parameter_set(HAL_NFC_PARAM_ID_NFCID1, tagUid, 7);
  ret_val = hal_nfc_setup(nfc_callback, 0);
  ret_val = hal_nfc_start();
  return ret_val;
}
ret_code_t restartNfc(){
  stopNfc();
  startNfc();
}

Then in the javascript side of things...

var buffer = new Uint8Array(24);
var tag = new Uint8Array(572);
var native = E.compiledC(...);
function onInit(){
  tag[0] = 0x04;
  tag[1] = Math.round(Math.random() * 255);
  tag[2] = Math.round(Math.random() * 255);
  tag[3] = tag[0] ^ tag[1] ^ tag[2] ^ 0x88;
  tag[4] = Math.round(Math.random() * 255);
  tag[5] = Math.round(Math.random() * 255);
  tag[6] = Math.round(Math.random() * 255);
  tag[7] = Math.round(Math.random() * 255);
  tag[8] = tag[4] ^ tag[5] ^ tag[6] ^ tag[7];
  tag.set([0x48, 0x00, 0x00, 0xE1, 0x10, 0x3E, 0x00, 0x03, 0x00, 0xFE], 0x09);
  tag.set([0xBD, 0x04, 0x00, 0x00, 0xFF, 0x00, 0x05], 0x20B);

  native.init(process.env.NFCPTR);
  native.setBufferPointer(E.getAddressOf(buffer, true));
  native.setTagPointer(E.getAddressOf(tag, true));
  native.startNfc();
}
DanTheMan827 commented 2 years ago

@gfwilliams Would this be a change that would maybe be worthwhile to make? or perhaps something similar to it...

Could expose these methods to the online compiler service and in the case of a device without them, just set the pointer to an empty function.

diff --git a/src/jswrap_process.c b/src/jswrap_process.c
index f6c738c3..b8bd9cbe 100644
--- a/src/jswrap_process.c
+++ b/src/jswrap_process.c
@@ -23,6 +23,9 @@
 #ifdef PUCKJS
 #include "jswrap_puck.h" // process.env
 #endif
+#ifdef USE_NFC
+#include "hal_t2t/hal_nfc_t2t.h"
+#endif

 /*JSON{
   "type" : "class",
@@ -93,6 +96,18 @@ const void *exportPtrs[] = {
 };
 #endif

+#ifdef USE_NFC
+const void *nfcPtrs[] = {
+    hal_nfc_setup,
+    hal_nfc_parameter_set,
+    hal_nfc_parameter_get,
+    hal_nfc_start,
+    hal_nfc_send,
+    hal_nfc_send_rsp,
+    hal_nfc_stop,
+    hal_nfc_done,
+    0
+};
+#endif
+
 /*JSON{
   "type" : "staticproperty",
   "class" : "process",
@@ -127,6 +142,7 @@ JsVar *jswrap_process_env() {
   // Pointer to a list of predefined exports - eventually we'll get rid of the array above
   jsvObjectSetChildAndUnLock(obj, "EXPTR", jsvNewFromInteger((JsVarInt)(size_t)exportPtrs));
 #endif
+#ifdef USE_NFC
+  jsvObjectSetChildAndUnLock(obj, "NFCPTR", jsvNewFromInteger((JsVarInt)(size_t)nfcPtrs));
+#endif
   return obj;
 }
gfwilliams commented 2 years ago

Honestly, I don't see this as something that's going to benefit many users. I could be wrong (please post up if you read this and you want it too!), but it feels like it's literally just you. And even then it's not useful by itself.

As discussed on the forum threads we'd really have to have some kind of shortcut built into the NFC library that called compiled code without hitting the event loop as well - and then if that happens, and someone had previously used JsVars in their code (or the compiled keyword in JS) it'd likely make their code completely unstable.

I spent pretty much a whole day last week trying to get the code size of the Puck.js build down enough that it'll still fit in available flash, so I'm afraid right now I'm not really feeling like pulling in things that negate all that work.

However I'm happy to pull this code and other changes for NFC into a branch so it's easy for others to compile, and we can see how it works?

DanTheMan827 commented 2 years ago

I won't deny that being able to access NFC at this low of a level is a niche, but it does seem that calling it in this fashion from compiled C allows the NFC callback to completely bypass the JS event loop since it's called from an interrupt (correct me if I'm wrong)

The only change I've made to the firmware in my build is exposing the pointers to the nfc hal functions, from there I call them from native C code compiled through the web ide.

I posted my sample script in two segments for readability, but everything above is done through the web ide and the firmware modified to expose the pointers.

When testing, I did tweak the NFC code to remove the increased max delay and set it to the NFC spec of 302us and my native code through compiledC was able to run within those constraints, even the simplest javascript making use of the rx callback failed to run quickly enough.

mmarquez76 commented 2 years ago

Chiming in to say this would be pretty useful for me as well, and would make the Puck a much more powerful NFC emulator.