mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2025-12-12 04:34:43 +04:00
Merge remote-tracking branch 'upstream/dev' into emv-fixes
This commit is contained in:
@@ -1 +1 @@
|
||||
--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/microtar -e lib/mlib -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/*
|
||||
--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/microtar -e lib/mlib -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e lib/mjs -e */arm-none-eabi/*
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
- **Hold right in received signal list to delete selected signal**
|
||||
- **Custom buttons for Keeloq / Alutech AT4N / Nice Flor S / Somfy Telis / Security+ 2.0 / CAME Atomo** - now you can use arrow buttons to send signal with different button code
|
||||
- `Add manually` menu extended with new protocols
|
||||
- FAAC SLH, BFT Mitto / Somfy Telis / Nice Flor S / CAME Atomo, etc.. manual creation with programming new remote into receiver (use button 0xF for BFT Mitto, 0x8 (Prog) on Somfy Telis)
|
||||
- FAAC SLH, BFT Mitto / Somfy Telis / Nice Flor S / CAME Atomo, etc.. manual creation with programming new remote into receiver (use button 0xF for BFT Mitto, 0x8 (Prog) on Somfy Telis, (right arrow button for other protocols))
|
||||
- Debug mode counter increase settings (+1 -> +5, +10, default: +1)
|
||||
- Debug PIN output settings for protocol development
|
||||
|
||||
|
||||
@@ -71,7 +71,10 @@ Small applications providing configuration for basic firmware and its services.
|
||||
|
||||
## system
|
||||
|
||||
Utility apps not visible in other menus.
|
||||
Utility apps not visible in other menus, plus few external apps pre-packaged with the firmware.
|
||||
|
||||
- `hid_app` - BLE & USB HID remote
|
||||
- `js_app` - JS engine runner
|
||||
- `snake_game` - Snake game
|
||||
- `storage_move_to_sd` - Data migration tool for internal storage
|
||||
- `updater` - Update service & application
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
void test_furi_memmgr() {
|
||||
void* ptr;
|
||||
|
||||
@@ -32,6 +32,7 @@ static const char* known_ext[] = {
|
||||
[ArchiveFileTypeBadUsb] = ".txt",
|
||||
[ArchiveFileTypeU2f] = "?",
|
||||
[ArchiveFileTypeApplication] = ".fap",
|
||||
[ArchiveFileTypeJS] = ".js",
|
||||
[ArchiveFileTypeUpdateManifest] = ".fuf",
|
||||
[ArchiveFileTypeFolder] = "?",
|
||||
[ArchiveFileTypeUnknown] = "*",
|
||||
|
||||
@@ -15,7 +15,7 @@ void archive_set_file_type(ArchiveFile_t* file, const char* path, bool is_folder
|
||||
} else {
|
||||
for(size_t i = 0; i < COUNT_OF(known_ext); i++) {
|
||||
if((known_ext[i][0] == '?') || (known_ext[i][0] == '*')) continue;
|
||||
if(furi_string_search(file->path, known_ext[i], 0) != FURI_STRING_FAILURE) {
|
||||
if(furi_string_end_with(file->path, known_ext[i])) {
|
||||
if(i == ArchiveFileTypeBadUsb) {
|
||||
if(furi_string_search(
|
||||
file->path, archive_get_default_path(ArchiveTabBadUsb)) == 0) {
|
||||
|
||||
@@ -19,6 +19,7 @@ typedef enum {
|
||||
ArchiveFileTypeU2f,
|
||||
ArchiveFileTypeApplication,
|
||||
ArchiveFileTypeUpdateManifest,
|
||||
ArchiveFileTypeJS,
|
||||
ArchiveFileTypeFolder,
|
||||
ArchiveFileTypeUnknown,
|
||||
ArchiveFileTypeLoading,
|
||||
|
||||
@@ -32,6 +32,8 @@ static const char* archive_get_flipper_app_name(ArchiveFileTypeEnum file_type) {
|
||||
return "U2F";
|
||||
case ArchiveFileTypeUpdateManifest:
|
||||
return "UpdaterApp";
|
||||
case ArchiveFileTypeJS:
|
||||
return "JS Runner";
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ static const Icon* ArchiveItemIcons[] = {
|
||||
[ArchiveFileTypeFolder] = &I_dir_10px,
|
||||
[ArchiveFileTypeUnknown] = &I_unknown_10px,
|
||||
[ArchiveFileTypeLoading] = &I_loading_10px,
|
||||
[ArchiveFileTypeJS] = &I_js_script_10px,
|
||||
};
|
||||
|
||||
void archive_browser_set_callback(
|
||||
|
||||
@@ -123,7 +123,7 @@ struct InfraredApp {
|
||||
InfraredProgressView* progress; /**< Custom view for showing brute force progress. */
|
||||
|
||||
FuriString* file_path; /**< Full path to the currently loaded file. */
|
||||
FuriString* button_name; /** Name of the button requested in RPC mode. */
|
||||
FuriString* button_name; /**< Name of the button requested in RPC mode. */
|
||||
/** Arbitrary text storage for various inputs. */
|
||||
char text_store[INFRARED_TEXT_STORE_NUM][INFRARED_TEXT_STORE_SIZE + 1];
|
||||
InfraredAppState app_state; /**< Application state. */
|
||||
|
||||
@@ -57,20 +57,31 @@ bool infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force) {
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
|
||||
FuriString* signal_name = furi_string_alloc();
|
||||
InfraredSignal* signal = infrared_signal_alloc();
|
||||
|
||||
do {
|
||||
if(!flipper_format_buffered_file_open_existing(ff, brute_force->db_filename)) break;
|
||||
|
||||
bool signals_valid = false;
|
||||
while(infrared_signal_read_name(ff, signal_name)) {
|
||||
signals_valid = infrared_signal_read_body(signal, ff) &&
|
||||
infrared_signal_is_valid(signal);
|
||||
if(!signals_valid) break;
|
||||
|
||||
success = flipper_format_buffered_file_open_existing(ff, brute_force->db_filename);
|
||||
if(success) {
|
||||
FuriString* signal_name;
|
||||
signal_name = furi_string_alloc();
|
||||
while(flipper_format_read_string(ff, "name", signal_name)) {
|
||||
InfraredBruteForceRecord* record =
|
||||
InfraredBruteForceRecordDict_get(brute_force->records, signal_name);
|
||||
if(record) { //-V547
|
||||
++(record->count);
|
||||
}
|
||||
}
|
||||
|
||||
if(!signals_valid) break;
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
infrared_signal_free(signal);
|
||||
furi_string_free(signal_name);
|
||||
}
|
||||
|
||||
flipper_format_free(ff);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
@@ -8,7 +8,23 @@
|
||||
|
||||
#define TAG "InfraredSignal"
|
||||
|
||||
// Common keys
|
||||
#define INFRARED_SIGNAL_NAME_KEY "name"
|
||||
#define INFRARED_SIGNAL_TYPE_KEY "type"
|
||||
|
||||
// Type key values
|
||||
#define INFRARED_SIGNAL_TYPE_RAW "raw"
|
||||
#define INFRARED_SIGNAL_TYPE_PARSED "parsed"
|
||||
|
||||
// Raw signal keys
|
||||
#define INFRARED_SIGNAL_DATA_KEY "data"
|
||||
#define INFRARED_SIGNAL_FREQUENCY_KEY "frequency"
|
||||
#define INFRARED_SIGNAL_DUTY_CYCLE_KEY "duty_cycle"
|
||||
|
||||
// Parsed signal keys
|
||||
#define INFRARED_SIGNAL_PROTOCOL_KEY "protocol"
|
||||
#define INFRARED_SIGNAL_ADDRESS_KEY "address"
|
||||
#define INFRARED_SIGNAL_COMMAND_KEY "command"
|
||||
|
||||
struct InfraredSignal {
|
||||
bool is_raw;
|
||||
@@ -88,18 +104,23 @@ static bool infrared_signal_is_raw_valid(const InfraredRawSignal* raw) {
|
||||
static inline bool
|
||||
infrared_signal_save_message(const InfraredMessage* message, FlipperFormat* ff) {
|
||||
const char* protocol_name = infrared_get_protocol_name(message->protocol);
|
||||
return flipper_format_write_string_cstr(ff, "type", "parsed") &&
|
||||
flipper_format_write_string_cstr(ff, "protocol", protocol_name) &&
|
||||
flipper_format_write_hex(ff, "address", (uint8_t*)&message->address, 4) &&
|
||||
flipper_format_write_hex(ff, "command", (uint8_t*)&message->command, 4);
|
||||
return flipper_format_write_string_cstr(
|
||||
ff, INFRARED_SIGNAL_TYPE_KEY, INFRARED_SIGNAL_TYPE_PARSED) &&
|
||||
flipper_format_write_string_cstr(ff, INFRARED_SIGNAL_PROTOCOL_KEY, protocol_name) &&
|
||||
flipper_format_write_hex(
|
||||
ff, INFRARED_SIGNAL_ADDRESS_KEY, (uint8_t*)&message->address, 4) &&
|
||||
flipper_format_write_hex(
|
||||
ff, INFRARED_SIGNAL_COMMAND_KEY, (uint8_t*)&message->command, 4);
|
||||
}
|
||||
|
||||
static inline bool infrared_signal_save_raw(const InfraredRawSignal* raw, FlipperFormat* ff) {
|
||||
furi_assert(raw->timings_size <= MAX_TIMINGS_AMOUNT);
|
||||
return flipper_format_write_string_cstr(ff, "type", "raw") &&
|
||||
flipper_format_write_uint32(ff, "frequency", &raw->frequency, 1) &&
|
||||
flipper_format_write_float(ff, "duty_cycle", &raw->duty_cycle, 1) &&
|
||||
flipper_format_write_uint32(ff, "data", raw->timings, raw->timings_size);
|
||||
return flipper_format_write_string_cstr(
|
||||
ff, INFRARED_SIGNAL_TYPE_KEY, INFRARED_SIGNAL_TYPE_RAW) &&
|
||||
flipper_format_write_uint32(ff, INFRARED_SIGNAL_FREQUENCY_KEY, &raw->frequency, 1) &&
|
||||
flipper_format_write_float(ff, INFRARED_SIGNAL_DUTY_CYCLE_KEY, &raw->duty_cycle, 1) &&
|
||||
flipper_format_write_uint32(
|
||||
ff, INFRARED_SIGNAL_DATA_KEY, raw->timings, raw->timings_size);
|
||||
}
|
||||
|
||||
static inline bool infrared_signal_read_message(InfraredSignal* signal, FlipperFormat* ff) {
|
||||
@@ -108,61 +129,72 @@ static inline bool infrared_signal_read_message(InfraredSignal* signal, FlipperF
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
if(!flipper_format_read_string(ff, "protocol", buf)) break;
|
||||
if(!flipper_format_read_string(ff, INFRARED_SIGNAL_PROTOCOL_KEY, buf)) break;
|
||||
|
||||
InfraredMessage message;
|
||||
message.protocol = infrared_get_protocol_by_name(furi_string_get_cstr(buf));
|
||||
|
||||
success = flipper_format_read_hex(ff, "address", (uint8_t*)&message.address, 4) &&
|
||||
flipper_format_read_hex(ff, "command", (uint8_t*)&message.command, 4) &&
|
||||
infrared_signal_is_message_valid(&message);
|
||||
|
||||
if(!success) break;
|
||||
if(!flipper_format_read_hex(ff, INFRARED_SIGNAL_ADDRESS_KEY, (uint8_t*)&message.address, 4))
|
||||
break;
|
||||
if(!flipper_format_read_hex(ff, INFRARED_SIGNAL_COMMAND_KEY, (uint8_t*)&message.command, 4))
|
||||
break;
|
||||
if(!infrared_signal_is_message_valid(&message)) break;
|
||||
|
||||
infrared_signal_set_message(signal, &message);
|
||||
} while(0);
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
furi_string_free(buf);
|
||||
return success;
|
||||
}
|
||||
|
||||
static inline bool infrared_signal_read_raw(InfraredSignal* signal, FlipperFormat* ff) {
|
||||
uint32_t timings_size, frequency;
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
uint32_t frequency;
|
||||
if(!flipper_format_read_uint32(ff, INFRARED_SIGNAL_FREQUENCY_KEY, &frequency, 1)) break;
|
||||
|
||||
float duty_cycle;
|
||||
if(!flipper_format_read_float(ff, INFRARED_SIGNAL_DUTY_CYCLE_KEY, &duty_cycle, 1)) break;
|
||||
|
||||
bool success = flipper_format_read_uint32(ff, "frequency", &frequency, 1) &&
|
||||
flipper_format_read_float(ff, "duty_cycle", &duty_cycle, 1) &&
|
||||
flipper_format_get_value_count(ff, "data", &timings_size);
|
||||
uint32_t timings_size;
|
||||
if(!flipper_format_get_value_count(ff, INFRARED_SIGNAL_DATA_KEY, &timings_size)) break;
|
||||
|
||||
if(!success || timings_size > MAX_TIMINGS_AMOUNT) {
|
||||
return false;
|
||||
}
|
||||
if(timings_size > MAX_TIMINGS_AMOUNT) break;
|
||||
|
||||
uint32_t* timings = malloc(sizeof(uint32_t) * timings_size);
|
||||
success = flipper_format_read_uint32(ff, "data", timings, timings_size);
|
||||
|
||||
if(success) {
|
||||
infrared_signal_set_raw_signal(signal, timings, timings_size, frequency, duty_cycle);
|
||||
}
|
||||
|
||||
if(!flipper_format_read_uint32(ff, INFRARED_SIGNAL_DATA_KEY, timings, timings_size)) {
|
||||
free(timings);
|
||||
break;
|
||||
}
|
||||
infrared_signal_set_raw_signal(signal, timings, timings_size, frequency, duty_cycle);
|
||||
free(timings);
|
||||
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool infrared_signal_read_body(InfraredSignal* signal, FlipperFormat* ff) {
|
||||
bool infrared_signal_read_body(InfraredSignal* signal, FlipperFormat* ff) {
|
||||
FuriString* tmp = furi_string_alloc();
|
||||
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
if(!flipper_format_read_string(ff, "type", tmp)) break;
|
||||
if(furi_string_equal(tmp, "raw")) {
|
||||
success = infrared_signal_read_raw(signal, ff);
|
||||
} else if(furi_string_equal(tmp, "parsed")) {
|
||||
success = infrared_signal_read_message(signal, ff);
|
||||
if(!flipper_format_read_string(ff, INFRARED_SIGNAL_TYPE_KEY, tmp)) break;
|
||||
|
||||
if(furi_string_equal(tmp, INFRARED_SIGNAL_TYPE_RAW)) {
|
||||
if(!infrared_signal_read_raw(signal, ff)) break;
|
||||
} else if(furi_string_equal(tmp, INFRARED_SIGNAL_TYPE_PARSED)) {
|
||||
if(!infrared_signal_read_message(signal, ff)) break;
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Unknown signal type");
|
||||
FURI_LOG_E(TAG, "Unknown signal type: %s", furi_string_get_cstr(tmp));
|
||||
break;
|
||||
}
|
||||
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
furi_string_free(tmp);
|
||||
|
||||
@@ -127,7 +127,7 @@ void infrared_signal_set_message(InfraredSignal* signal, const InfraredMessage*
|
||||
const InfraredMessage* infrared_signal_get_message(const InfraredSignal* signal);
|
||||
|
||||
/**
|
||||
* @brief Read a signal from a FlipperFormat file into an InfraredSignal instance.
|
||||
* @brief Read a signal and its name from a FlipperFormat file into an InfraredSignal instance.
|
||||
*
|
||||
* The file must be allocated and open prior to this call. The seek position determines
|
||||
* which signal will be read (if there is more than one in the file). Calling this function
|
||||
@@ -151,6 +151,17 @@ bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, FuriString*
|
||||
*/
|
||||
bool infrared_signal_read_name(FlipperFormat* ff, FuriString* name);
|
||||
|
||||
/**
|
||||
* @brief Read a signal from a FlipperFormat file.
|
||||
*
|
||||
* Same behaviour as infrared_signal_read(), but only the body is read.
|
||||
*
|
||||
* @param[in,out] ff pointer to the FlipperFormat file instance to read from.
|
||||
* @param[out] body pointer to the InfraredSignal instance to hold the signal body. Must be properly allocated.
|
||||
* @returns true if a signal body was successfully read, false otherwise (e.g. syntax error).
|
||||
*/
|
||||
bool infrared_signal_read_body(InfraredSignal* signal, FlipperFormat* ff);
|
||||
|
||||
/**
|
||||
* @brief Read a signal with a particular name from a FlipperFormat file into an InfraredSignal instance.
|
||||
*
|
||||
|
||||
@@ -10,7 +10,7 @@ enum SubmenuIndex {
|
||||
|
||||
static void nfc_scene_set_type_init_edit_data(Iso14443_3aData* data, size_t uid_len) {
|
||||
// Easiest way to create a zero'd buffer of given length
|
||||
uint8_t* uid = malloc(uid_len);
|
||||
uint8_t* uid = calloc(1, uid_len);
|
||||
iso14443_3a_set_uid(data, uid, uid_len);
|
||||
free(uid);
|
||||
}
|
||||
|
||||
@@ -5,68 +5,6 @@ typedef enum {
|
||||
SubGhzCustomEventManagerSet,
|
||||
SubGhzCustomEventManagerSetRAW,
|
||||
|
||||
//SubmenuIndex
|
||||
SubmenuIndexFaacSLH_Manual_433,
|
||||
SubmenuIndexFaacSLH_Manual_868,
|
||||
SubmenuIndexFaacSLH_433,
|
||||
SubmenuIndexFaacSLH_868,
|
||||
SubmenuIndexBFTClone,
|
||||
SubmenuIndexBFTMitto,
|
||||
SubmenuIndexSomfyTelis,
|
||||
SubmenuIndexBeninca433,
|
||||
SubmenuIndexBeninca868,
|
||||
SubmenuIndexAllmatic433,
|
||||
SubmenuIndexAllmatic868,
|
||||
SubmenuIndexCenturion433,
|
||||
SubmenuIndexIronLogic,
|
||||
SubmenuIndexElmesElectronic,
|
||||
SubmenuIndexSommer_FM_434,
|
||||
SubmenuIndexSommer_FM_868,
|
||||
SubmenuIndexStilmatic,
|
||||
SubmenuIndexDTMNeo433,
|
||||
SubmenuIndexDeaMio433,
|
||||
SubmenuIndexGibidi433,
|
||||
SubmenuIndexNiceMHouse_433_92,
|
||||
SubmenuIndexJCM_433_92,
|
||||
SubmenuIndexFAACRCXT_433_92,
|
||||
SubmenuIndexFAACRCXT_868,
|
||||
SubmenuIndexNormstahl_433_92,
|
||||
SubmenuIndexGeniusBravo433,
|
||||
SubmenuIndexGSN,
|
||||
SubmenuIndexAprimatic,
|
||||
SubmenuIndexHCS101_433_92,
|
||||
SubmenuIndexANMotorsAT4,
|
||||
SubmenuIndexAlutechAT4N,
|
||||
SubmenuIndexNiceFlo12bit,
|
||||
SubmenuIndexNiceFlo24bit,
|
||||
SubmenuIndexNiceFlorS_433_92,
|
||||
SubmenuIndexNiceOne_433_92,
|
||||
SubmenuIndexNiceSmilo_433_92,
|
||||
SubmenuIndexCAME12bit,
|
||||
SubmenuIndexCAME24bit,
|
||||
SubmenuIndexCAME12bit868,
|
||||
SubmenuIndexCAME24bit868,
|
||||
SubmenuIndexCAMETwee,
|
||||
SubmenuIndexCAMESpace,
|
||||
SubmenuIndexCameAtomo433,
|
||||
SubmenuIndexCameAtomo868,
|
||||
SubmenuIndexPricenton433,
|
||||
SubmenuIndexPricenton315,
|
||||
SubmenuIndexBETT_433,
|
||||
SubmenuIndexLinear_300_00,
|
||||
SubmenuIndexNeroSketch, //Deleted in OFW
|
||||
SubmenuIndexNeroRadio, //Deleted in OFW
|
||||
SubmenuIndexGateTX,
|
||||
SubmenuIndexDoorHan_315_00,
|
||||
SubmenuIndexDoorHan_433_92,
|
||||
SubmenuIndexSecPlus_v1_315_00,
|
||||
SubmenuIndexSecPlus_v1_390_00,
|
||||
SubmenuIndexSecPlus_v1_433_00,
|
||||
SubmenuIndexSecPlus_v2_310_00,
|
||||
SubmenuIndexSecPlus_v2_315_00,
|
||||
SubmenuIndexSecPlus_v2_390_00,
|
||||
SubmenuIndexSecPlus_v2_433_00,
|
||||
|
||||
//SubGhzCustomEvent
|
||||
SubGhzCustomEventSceneDeleteSuccess = 100,
|
||||
SubGhzCustomEventSceneDelete,
|
||||
@@ -123,3 +61,68 @@ typedef enum {
|
||||
|
||||
SubGhzCustomEventByteInputDone,
|
||||
} SubGhzCustomEvent;
|
||||
|
||||
typedef enum {
|
||||
SetTypeFaacSLH_Manual_868,
|
||||
SetTypeFaacSLH_Manual_433,
|
||||
SetTypeBFTClone,
|
||||
SetTypeFaacSLH_868,
|
||||
SetTypeFaacSLH_433,
|
||||
SetTypeBFTMitto,
|
||||
SetTypeSomfyTelis,
|
||||
SetTypeANMotorsAT4,
|
||||
SetTypeAlutechAT4N,
|
||||
SetTypeHCS101_433_92,
|
||||
SetTypeDoorHan_315_00,
|
||||
SetTypeDoorHan_433_92,
|
||||
SetTypeBeninca433,
|
||||
SetTypeBeninca868,
|
||||
SetTypeAllmatic433,
|
||||
SetTypeAllmatic868,
|
||||
SetTypeCenturion433,
|
||||
SetTypeSommer_FM_434,
|
||||
SetTypeSommer_FM_868,
|
||||
SetTypeStilmatic,
|
||||
SetTypeIronLogic,
|
||||
SetTypeDeaMio433,
|
||||
SetTypeDTMNeo433,
|
||||
SetTypeGibidi433,
|
||||
SetTypeGSN,
|
||||
SetTypeAprimatic,
|
||||
SetTypeElmesElectronic,
|
||||
SetTypeNormstahl_433_92,
|
||||
SetTypeJCM_433_92,
|
||||
SetTypeFAACRCXT_433_92,
|
||||
SetTypeFAACRCXT_868,
|
||||
SetTypeGeniusBravo433,
|
||||
SetTypeNiceMHouse_433_92,
|
||||
SetTypeNiceSmilo_433_92,
|
||||
SetTypeNiceFlorS_433_92,
|
||||
SetTypeNiceOne_433_92,
|
||||
SetTypeNiceFlo12bit,
|
||||
SetTypeNiceFlo24bit,
|
||||
SetTypeCAME12bit,
|
||||
SetTypeCAME24bit,
|
||||
SetTypeCAME12bit868,
|
||||
SetTypeCAME24bit868,
|
||||
SetTypeCAMETwee,
|
||||
SetTypeCameAtomo433,
|
||||
SetTypeCameAtomo868,
|
||||
SetTypeCAMESpace,
|
||||
SetTypePricenton315,
|
||||
SetTypePricenton433,
|
||||
SetTypeBETT_433,
|
||||
SetTypeLinear_300_00,
|
||||
// SetTypeNeroSketch, //Deleted in OFW
|
||||
// SetTypeNeroRadio, //Deleted in OFW
|
||||
SetTypeGateTX,
|
||||
SetTypeSecPlus_v1_315_00,
|
||||
SetTypeSecPlus_v1_390_00,
|
||||
SetTypeSecPlus_v1_433_00,
|
||||
SetTypeSecPlus_v2_310_00,
|
||||
SetTypeSecPlus_v2_315_00,
|
||||
SetTypeSecPlus_v2_390_00,
|
||||
SetTypeSecPlus_v2_433_00,
|
||||
|
||||
SetTypeMAX,
|
||||
} SetType;
|
||||
|
||||
@@ -17,7 +17,7 @@ void subghz_scene_set_cnt_on_enter(void* context) {
|
||||
scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneSetType);
|
||||
|
||||
switch(state) {
|
||||
case SubmenuIndexBFTClone:
|
||||
case SetTypeBFTClone:
|
||||
byte_input_set_header_text(byte_input, "Enter COUNTER in Hex");
|
||||
byte_input_set_result_callback(
|
||||
byte_input,
|
||||
@@ -27,8 +27,8 @@ void subghz_scene_set_cnt_on_enter(void* context) {
|
||||
subghz->secure_data->cnt,
|
||||
2);
|
||||
break;
|
||||
case SubmenuIndexFaacSLH_Manual_433:
|
||||
case SubmenuIndexFaacSLH_Manual_868:
|
||||
case SetTypeFaacSLH_Manual_433:
|
||||
case SetTypeFaacSLH_Manual_868:
|
||||
byte_input_set_header_text(byte_input, "Enter COUNTER in Hex 20 bits");
|
||||
byte_input_set_result_callback(
|
||||
byte_input,
|
||||
|
||||
@@ -32,11 +32,11 @@ bool subghz_scene_set_seed_on_event(void* context, SceneManagerEvent event) {
|
||||
uint32_t fix_part, cnt, seed;
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == SubGhzCustomEventByteInputDone) {
|
||||
SubGhzCustomEvent state =
|
||||
SetType state =
|
||||
scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneSetType);
|
||||
|
||||
switch(state) {
|
||||
case SubmenuIndexBFTClone:
|
||||
case SetTypeBFTClone:
|
||||
fix_part = subghz->secure_data->fix[0] << 24 | subghz->secure_data->fix[1] << 16 |
|
||||
subghz->secure_data->fix[2] << 8 | subghz->secure_data->fix[3];
|
||||
|
||||
@@ -62,8 +62,8 @@ bool subghz_scene_set_seed_on_event(void* context, SceneManagerEvent event) {
|
||||
}
|
||||
consumed = true;
|
||||
break;
|
||||
case SubmenuIndexFaacSLH_Manual_433:
|
||||
case SubmenuIndexFaacSLH_Manual_868:
|
||||
case SetTypeFaacSLH_Manual_433:
|
||||
case SetTypeFaacSLH_Manual_868:
|
||||
fix_part = subghz->secure_data->fix[0] << 24 | subghz->secure_data->fix[1] << 16 |
|
||||
subghz->secure_data->fix[2] << 8 | subghz->secure_data->fix[3];
|
||||
|
||||
@@ -73,7 +73,7 @@ bool subghz_scene_set_seed_on_event(void* context, SceneManagerEvent event) {
|
||||
seed = subghz->secure_data->seed[0] << 24 | subghz->secure_data->seed[1] << 16 |
|
||||
subghz->secure_data->seed[2] << 8 | subghz->secure_data->seed[3];
|
||||
|
||||
if(state == SubmenuIndexFaacSLH_Manual_433) {
|
||||
if(state == SetTypeFaacSLH_Manual_433) {
|
||||
generated_protocol = subghz_txrx_gen_faac_slh_protocol(
|
||||
subghz->txrx,
|
||||
"AM650",
|
||||
@@ -83,7 +83,7 @@ bool subghz_scene_set_seed_on_event(void* context, SceneManagerEvent event) {
|
||||
(cnt & 0xFFFFF),
|
||||
seed,
|
||||
"FAAC_SLH");
|
||||
} else if(state == SubmenuIndexFaacSLH_Manual_868) {
|
||||
} else if(state == SetTypeFaacSLH_Manual_868) {
|
||||
generated_protocol = subghz_txrx_gen_faac_slh_protocol(
|
||||
subghz->txrx,
|
||||
"AM650",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,4 @@
|
||||
#include "canvas_i.h"
|
||||
#include "icon_i.h"
|
||||
#include "icon_animation_i.h"
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
@@ -122,7 +122,7 @@ void canvas_draw_u8g2_bitmap(
|
||||
uint8_t width,
|
||||
uint8_t height,
|
||||
const uint8_t* bitmap,
|
||||
uint8_t rotation);
|
||||
IconRotation rotation);
|
||||
|
||||
/** Add canvas commit callback.
|
||||
*
|
||||
|
||||
@@ -32,12 +32,12 @@ typedef enum {
|
||||
(WorkerEvtStop | WorkerEvtLoad | WorkerEvtFolderEnter | WorkerEvtFolderExit | \
|
||||
WorkerEvtFolderRefresh | WorkerEvtConfigChange)
|
||||
|
||||
ARRAY_DEF(idx_last_array, int32_t)
|
||||
ARRAY_DEF(IdxLastArray, int32_t)
|
||||
ARRAY_DEF(ExtFilterArray, FuriString*, FURI_STRING_OPLIST)
|
||||
|
||||
struct BrowserWorker {
|
||||
FuriThread* thread;
|
||||
|
||||
FuriString* filter_extension;
|
||||
FuriString* path_start;
|
||||
FuriString* path_current;
|
||||
FuriString* path_next;
|
||||
@@ -46,7 +46,8 @@ struct BrowserWorker {
|
||||
uint32_t load_count;
|
||||
bool skip_assets;
|
||||
bool hide_dot_files;
|
||||
idx_last_array_t idx_last;
|
||||
IdxLastArray_t idx_last;
|
||||
ExtFilterArray_t ext_filter;
|
||||
|
||||
void* cb_ctx;
|
||||
BrowserWorkerFolderOpenCallback folder_cb;
|
||||
@@ -78,6 +79,32 @@ static bool browser_path_trim(FuriString* path) {
|
||||
}
|
||||
return is_root;
|
||||
}
|
||||
static void browser_parse_ext_filter(ExtFilterArray_t ext_filter, const char* filter_str) {
|
||||
ExtFilterArray_reset(ext_filter);
|
||||
if(!filter_str) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t len = strlen(filter_str);
|
||||
if(len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t str_offset = 0;
|
||||
FuriString* ext_temp = furi_string_alloc();
|
||||
while(1) {
|
||||
size_t ext_len = strcspn(&filter_str[str_offset], "|");
|
||||
|
||||
furi_string_set_strn(ext_temp, &filter_str[str_offset], ext_len);
|
||||
ExtFilterArray_push_back(ext_filter, ext_temp);
|
||||
|
||||
str_offset += ext_len + 1;
|
||||
if(str_offset >= len) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
furi_string_free(ext_temp);
|
||||
}
|
||||
|
||||
static bool browser_filter_by_name(BrowserWorker* browser, FuriString* name, bool is_folder) {
|
||||
// Skip dot files if enabled
|
||||
@@ -96,13 +123,21 @@ static bool browser_filter_by_name(BrowserWorker* browser, FuriString* name, boo
|
||||
}
|
||||
} else {
|
||||
// Filter files by extension
|
||||
if((furi_string_empty(browser->filter_extension)) ||
|
||||
(furi_string_cmp_str(browser->filter_extension, "*") == 0)) {
|
||||
if(ExtFilterArray_size(browser->ext_filter) == 0) {
|
||||
return true;
|
||||
}
|
||||
if(furi_string_end_with(name, browser->filter_extension)) {
|
||||
|
||||
ExtFilterArray_it_t it;
|
||||
for(ExtFilterArray_it(it, browser->ext_filter); !ExtFilterArray_end_p(it);
|
||||
ExtFilterArray_next(it)) {
|
||||
FuriString* ext = *ExtFilterArray_cref(it);
|
||||
if((furi_string_empty(ext)) || (furi_string_cmp_str(ext, "*") == 0)) {
|
||||
return true;
|
||||
}
|
||||
if(furi_string_end_with(name, ext)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -345,7 +380,7 @@ static int32_t browser_worker(void* context) {
|
||||
if(browser_path_is_file(browser->path_next)) {
|
||||
path_extract_filename(browser->path_next, filename, false);
|
||||
}
|
||||
idx_last_array_reset(browser->idx_last);
|
||||
IdxLastArray_reset(browser->idx_last);
|
||||
|
||||
furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtFolderEnter);
|
||||
}
|
||||
@@ -355,7 +390,7 @@ static int32_t browser_worker(void* context) {
|
||||
bool is_root = browser_folder_check_and_switch(path);
|
||||
|
||||
// Push previous selected item index to history array
|
||||
idx_last_array_push_back(browser->idx_last, browser->item_sel_idx);
|
||||
IdxLastArray_push_back(browser->idx_last, browser->item_sel_idx);
|
||||
|
||||
int32_t file_idx = 0;
|
||||
browser_folder_init(browser, path, filename, &items_cnt, &file_idx);
|
||||
@@ -378,9 +413,9 @@ static int32_t browser_worker(void* context) {
|
||||
|
||||
int32_t file_idx = 0;
|
||||
browser_folder_init(browser, path, filename, &items_cnt, &file_idx);
|
||||
if(idx_last_array_size(browser->idx_last) > 0) {
|
||||
if(IdxLastArray_size(browser->idx_last) > 0) {
|
||||
// Pop previous selected item index from history array
|
||||
idx_last_array_pop_back(&file_idx, browser->idx_last);
|
||||
IdxLastArray_pop_back(&file_idx, browser->idx_last);
|
||||
}
|
||||
furi_string_set(browser->path_current, path);
|
||||
FURI_LOG_D(
|
||||
@@ -437,14 +472,15 @@ static int32_t browser_worker(void* context) {
|
||||
BrowserWorker* file_browser_worker_alloc(
|
||||
FuriString* path,
|
||||
const char* base_path,
|
||||
const char* filter_ext,
|
||||
const char* ext_filter,
|
||||
bool skip_assets,
|
||||
bool hide_dot_files) {
|
||||
BrowserWorker* browser = malloc(sizeof(BrowserWorker));
|
||||
|
||||
idx_last_array_init(browser->idx_last);
|
||||
IdxLastArray_init(browser->idx_last);
|
||||
ExtFilterArray_init(browser->ext_filter);
|
||||
|
||||
browser->filter_extension = furi_string_alloc_set(filter_ext);
|
||||
browser_parse_ext_filter(browser->ext_filter, ext_filter);
|
||||
browser->skip_assets = skip_assets;
|
||||
browser->hide_dot_files = hide_dot_files;
|
||||
|
||||
@@ -469,12 +505,12 @@ void file_browser_worker_free(BrowserWorker* browser) {
|
||||
furi_thread_join(browser->thread);
|
||||
furi_thread_free(browser->thread);
|
||||
|
||||
furi_string_free(browser->filter_extension);
|
||||
furi_string_free(browser->path_next);
|
||||
furi_string_free(browser->path_current);
|
||||
furi_string_free(browser->path_start);
|
||||
|
||||
idx_last_array_clear(browser->idx_last);
|
||||
IdxLastArray_clear(browser->idx_last);
|
||||
ExtFilterArray_clear(browser->ext_filter);
|
||||
|
||||
free(browser);
|
||||
}
|
||||
@@ -515,12 +551,12 @@ void file_browser_worker_set_long_load_callback(
|
||||
void file_browser_worker_set_config(
|
||||
BrowserWorker* browser,
|
||||
FuriString* path,
|
||||
const char* filter_ext,
|
||||
const char* ext_filter,
|
||||
bool skip_assets,
|
||||
bool hide_dot_files) {
|
||||
furi_assert(browser);
|
||||
furi_string_set(browser->path_next, path);
|
||||
furi_string_set(browser->filter_extension, filter_ext);
|
||||
browser_parse_ext_filter(browser->ext_filter, ext_filter);
|
||||
browser->skip_assets = skip_assets;
|
||||
browser->hide_dot_files = hide_dot_files;
|
||||
furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtConfigChange);
|
||||
|
||||
@@ -27,7 +27,7 @@ typedef void (*BrowserWorkerLongLoadCallback)(void* context);
|
||||
BrowserWorker* file_browser_worker_alloc(
|
||||
FuriString* path,
|
||||
const char* base_path,
|
||||
const char* filter_ext,
|
||||
const char* ext_filter,
|
||||
bool skip_assets,
|
||||
bool hide_dot_files);
|
||||
|
||||
@@ -54,7 +54,7 @@ void file_browser_worker_set_long_load_callback(
|
||||
void file_browser_worker_set_config(
|
||||
BrowserWorker* browser,
|
||||
FuriString* path,
|
||||
const char* filter_ext,
|
||||
const char* ext_filter,
|
||||
bool skip_assets,
|
||||
bool hide_dot_files);
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
#include <furi.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define TEXT_BOX_MAX_SYMBOL_WIDTH (10)
|
||||
#define TEXT_BOX_LINE_WIDTH (120)
|
||||
|
||||
struct TextBox {
|
||||
View* view;
|
||||
|
||||
@@ -78,13 +81,11 @@ static void text_box_insert_endline(Canvas* canvas, TextBoxModel* model) {
|
||||
const char* str = model->text;
|
||||
size_t line_num = 0;
|
||||
|
||||
const size_t text_width = 120;
|
||||
|
||||
while(str[i] != '\0') {
|
||||
char symb = str[i++];
|
||||
if(symb != '\n') {
|
||||
size_t glyph_width = canvas_glyph_width(canvas, symb);
|
||||
if(line_width + glyph_width > text_width) {
|
||||
if(line_width + glyph_width > TEXT_BOX_LINE_WIDTH) {
|
||||
line_num++;
|
||||
line_width = 0;
|
||||
furi_string_push_back(model->text_formatted, '\n');
|
||||
@@ -211,6 +212,7 @@ void text_box_reset(TextBox* text_box) {
|
||||
furi_string_set(model->text_formatted, "");
|
||||
model->font = TextBoxFontText;
|
||||
model->focus = TextBoxFocusStart;
|
||||
model->formatted = false;
|
||||
},
|
||||
true);
|
||||
}
|
||||
@@ -218,6 +220,8 @@ void text_box_reset(TextBox* text_box) {
|
||||
void text_box_set_text(TextBox* text_box, const char* text) {
|
||||
furi_assert(text_box);
|
||||
furi_assert(text);
|
||||
size_t str_length = strlen(text);
|
||||
size_t formating_margin = str_length * TEXT_BOX_MAX_SYMBOL_WIDTH / TEXT_BOX_LINE_WIDTH;
|
||||
|
||||
with_view_model(
|
||||
text_box->view,
|
||||
@@ -225,7 +229,7 @@ void text_box_set_text(TextBox* text_box, const char* text) {
|
||||
{
|
||||
model->text = text;
|
||||
furi_string_reset(model->text_formatted);
|
||||
furi_string_reserve(model->text_formatted, strlen(text));
|
||||
furi_string_reserve(model->text_formatted, str_length + formating_margin);
|
||||
model->formatted = false;
|
||||
},
|
||||
true);
|
||||
|
||||
@@ -7,9 +7,12 @@
|
||||
#include <gui/view_holder.h>
|
||||
#include <gui/modules/loading.h>
|
||||
#include <dolphin/dolphin.h>
|
||||
#include <lib/toolbox/path.h>
|
||||
|
||||
#define TAG "LoaderApplications"
|
||||
|
||||
#define JS_RUNNER_APP "JS Runner"
|
||||
|
||||
struct LoaderApplications {
|
||||
FuriThread* thread;
|
||||
void (*closed_cb)(void*);
|
||||
@@ -36,7 +39,7 @@ void loader_applications_free(LoaderApplications* loader_applications) {
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
FuriString* fap_path;
|
||||
FuriString* file_path;
|
||||
DialogsApp* dialogs;
|
||||
Storage* storage;
|
||||
Loader* loader;
|
||||
@@ -48,7 +51,7 @@ typedef struct {
|
||||
|
||||
static LoaderApplicationsApp* loader_applications_app_alloc() {
|
||||
LoaderApplicationsApp* app = malloc(sizeof(LoaderApplicationsApp)); //-V799
|
||||
app->fap_path = furi_string_alloc_set(EXT_PATH("apps"));
|
||||
app->file_path = furi_string_alloc_set(EXT_PATH("apps"));
|
||||
app->dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
app->storage = furi_record_open(RECORD_STORAGE);
|
||||
app->loader = furi_record_open(RECORD_LOADER);
|
||||
@@ -73,7 +76,7 @@ static void loader_applications_app_free(LoaderApplicationsApp* app) {
|
||||
furi_record_close(RECORD_LOADER);
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
furi_string_free(app->fap_path);
|
||||
furi_string_free(app->file_path);
|
||||
free(app);
|
||||
}
|
||||
|
||||
@@ -84,13 +87,19 @@ static bool loader_applications_item_callback(
|
||||
FuriString* item_name) {
|
||||
LoaderApplicationsApp* loader_applications_app = context;
|
||||
furi_assert(loader_applications_app);
|
||||
if(furi_string_end_with(path, ".fap")) {
|
||||
return flipper_application_load_name_and_icon(
|
||||
path, loader_applications_app->storage, icon_ptr, item_name);
|
||||
} else {
|
||||
path_extract_filename(path, item_name, false);
|
||||
memcpy(*icon_ptr, icon_get_data(&I_js_script_10px), FAP_MANIFEST_MAX_ICON_SIZE);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static bool loader_applications_select_app(LoaderApplicationsApp* loader_applications_app) {
|
||||
const DialogsFileBrowserOptions browser_options = {
|
||||
.extension = ".fap",
|
||||
.extension = ".fap|.js",
|
||||
.skip_assets = true,
|
||||
.icon = &I_unknown_10px,
|
||||
.hide_ext = true,
|
||||
@@ -101,8 +110,8 @@ static bool loader_applications_select_app(LoaderApplicationsApp* loader_applica
|
||||
|
||||
return dialog_file_browser_show(
|
||||
loader_applications_app->dialogs,
|
||||
loader_applications_app->fap_path,
|
||||
loader_applications_app->fap_path,
|
||||
loader_applications_app->file_path,
|
||||
loader_applications_app->file_path,
|
||||
&browser_options);
|
||||
}
|
||||
|
||||
@@ -117,9 +126,8 @@ static void loader_pubsub_callback(const void* message, void* context) {
|
||||
}
|
||||
}
|
||||
|
||||
static void loader_applications_start_app(LoaderApplicationsApp* app) {
|
||||
const char* name = furi_string_get_cstr(app->fap_path);
|
||||
|
||||
static void
|
||||
loader_applications_start_app(LoaderApplicationsApp* app, const char* name, const char* args) {
|
||||
dolphin_deed(DolphinDeedPluginStart);
|
||||
|
||||
// load app
|
||||
@@ -127,7 +135,7 @@ static void loader_applications_start_app(LoaderApplicationsApp* app) {
|
||||
FuriPubSubSubscription* subscription =
|
||||
furi_pubsub_subscribe(loader_get_pubsub(app->loader), loader_pubsub_callback, thread_id);
|
||||
|
||||
LoaderStatus status = loader_start_with_gui_error(app->loader, name, NULL);
|
||||
LoaderStatus status = loader_start_with_gui_error(app->loader, name, args);
|
||||
|
||||
if(status == LoaderStatusOk) {
|
||||
furi_thread_flags_wait(APPLICATION_STOP_EVENT, FuriFlagWaitAny, FuriWaitForever);
|
||||
@@ -144,7 +152,12 @@ static int32_t loader_applications_thread(void* p) {
|
||||
view_holder_start(app->view_holder);
|
||||
|
||||
while(loader_applications_select_app(app)) {
|
||||
loader_applications_start_app(app);
|
||||
if(!furi_string_end_with(app->file_path, ".js")) {
|
||||
loader_applications_start_app(app, furi_string_get_cstr(app->file_path), NULL);
|
||||
} else {
|
||||
loader_applications_start_app(
|
||||
app, JS_RUNNER_APP, furi_string_get_cstr(app->file_path));
|
||||
}
|
||||
}
|
||||
|
||||
// stop loading animation
|
||||
|
||||
@@ -395,9 +395,12 @@ static void rpc_system_storage_read_process(const PB_Main* request, void* contex
|
||||
|
||||
response->has_next = fs_operation_success && (size_left > 0);
|
||||
} else {
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Warray-bounds"
|
||||
response->content.storage_read_response.file.data =
|
||||
malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(0));
|
||||
response->content.storage_read_response.file.data->size = 0;
|
||||
#pragma GCC diagnostic pop
|
||||
response->content.storage_read_response.has_file = true;
|
||||
response->has_next = false;
|
||||
fs_operation_success = true;
|
||||
|
||||
@@ -5,6 +5,7 @@ App(
|
||||
provides=[
|
||||
"updater_app",
|
||||
"storage_move_to_sd",
|
||||
"js_app",
|
||||
# "archive",
|
||||
],
|
||||
)
|
||||
|
||||
41
applications/system/js_app/application.fam
Normal file
41
applications/system/js_app/application.fam
Normal file
@@ -0,0 +1,41 @@
|
||||
App(
|
||||
appid="js_app",
|
||||
name="JS Runner",
|
||||
apptype=FlipperAppType.SYSTEM,
|
||||
entry_point="js_app",
|
||||
stack_size=2 * 1024,
|
||||
resources="examples",
|
||||
order=0,
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_dialog",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_dialog_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_dialog.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_notification",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_notification_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_notification.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_badusb",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_badusb_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_badusb.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_serial",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_serial_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_serial.c"],
|
||||
)
|
||||
@@ -0,0 +1,8 @@
|
||||
let arr_1 = Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
print("len =", arr_1.buffer.byteLength);
|
||||
|
||||
let arr_2 = Uint8Array(arr_1.buffer.slice(2, 6));
|
||||
print("slice len =", arr_2.buffer.byteLength);
|
||||
for (let i = 0; i < arr_2.buffer.byteLength; i++) {
|
||||
print(arr_2[i]);
|
||||
}
|
||||
20
applications/system/js_app/examples/apps/Scripts/bad_uart.js
Normal file
20
applications/system/js_app/examples/apps/Scripts/bad_uart.js
Normal file
@@ -0,0 +1,20 @@
|
||||
let serial = require("serial");
|
||||
serial.setup("lpuart", 115200);
|
||||
|
||||
// serial.write("\n");
|
||||
serial.write([0x0a]);
|
||||
let console_resp = serial.expect("# ", 1000);
|
||||
if (console_resp === undefined) {
|
||||
print("No CLI response");
|
||||
} else {
|
||||
serial.write("uci\n");
|
||||
let uci_state = serial.expect([": not found", "Usage: "]);
|
||||
if (uci_state === 1) {
|
||||
serial.expect("# ");
|
||||
serial.write("uci show wireless\n");
|
||||
serial.expect(".key=");
|
||||
print("key:", serial.readln());
|
||||
} else {
|
||||
print("uci cmd not found");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
let badusb = require("badusb");
|
||||
let notify = require("notification");
|
||||
let flipper = require("flipper");
|
||||
let dialog = require("dialog");
|
||||
|
||||
badusb.setup({ vid: 0xAAAA, pid: 0xBBBB, mfr_name: "Flipper", prod_name: "Zero" });
|
||||
dialog.message("BadUSB demo", "Press OK to start");
|
||||
|
||||
if (badusb.isConnected()) {
|
||||
notify.blink("green", "short");
|
||||
print("USB is connected");
|
||||
|
||||
badusb.println("Hello, world!");
|
||||
|
||||
badusb.press("CTRL", "a");
|
||||
badusb.press("CTRL", "c");
|
||||
badusb.press("DOWN");
|
||||
delay(1000);
|
||||
badusb.press("CTRL", "v");
|
||||
delay(1000);
|
||||
badusb.press("CTRL", "v");
|
||||
|
||||
badusb.println("1234", 200);
|
||||
|
||||
badusb.println("Flipper Model: " + flipper.getModel());
|
||||
badusb.println("Flipper Name: " + flipper.getName());
|
||||
badusb.println("Battery level: " + to_string(flipper.getBatteryCharge()) + "%");
|
||||
|
||||
notify.success();
|
||||
} else {
|
||||
print("USB not connected");
|
||||
notify.error();
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
print("print", 1);
|
||||
console.log("log", 2);
|
||||
console.warn("warn", 3);
|
||||
console.error("error", 4);
|
||||
console.debug("debug", 5);
|
||||
@@ -0,0 +1,9 @@
|
||||
print("start");
|
||||
delay(1000)
|
||||
print("1");
|
||||
delay(1000)
|
||||
print("2");
|
||||
delay(1000)
|
||||
print("3");
|
||||
delay(1000)
|
||||
print("end");
|
||||
19
applications/system/js_app/examples/apps/Scripts/dialog.js
Normal file
19
applications/system/js_app/examples/apps/Scripts/dialog.js
Normal file
@@ -0,0 +1,19 @@
|
||||
let dialog = require("dialog");
|
||||
|
||||
let result1 = dialog.message("Dialog demo", "Press OK to start");
|
||||
print(result1);
|
||||
|
||||
let dialog_params = ({
|
||||
header: "Test_header",
|
||||
text: "Test_text",
|
||||
button_left: "Left",
|
||||
button_right: "Right",
|
||||
button_center: "OK"
|
||||
});
|
||||
|
||||
let result2 = dialog.custom(dialog_params);
|
||||
if (result2 === "") {
|
||||
print("Back is pressed");
|
||||
} else {
|
||||
print(result2, "is pressed");
|
||||
}
|
||||
3
applications/system/js_app/examples/apps/Scripts/load.js
Normal file
3
applications/system/js_app/examples/apps/Scripts/load.js
Normal file
@@ -0,0 +1,3 @@
|
||||
let math = load("/ext/apps/Scripts/load_api.js");
|
||||
let result = math.add(5, 10);
|
||||
print(result);
|
||||
@@ -0,0 +1,3 @@
|
||||
({
|
||||
add: function (a, b) { return a + b; },
|
||||
})
|
||||
@@ -0,0 +1,9 @@
|
||||
let notify = require("notification");
|
||||
notify.error();
|
||||
delay(1000);
|
||||
notify.success();
|
||||
delay(1000);
|
||||
for (let i = 0; i < 10; i++) {
|
||||
notify.blink("red", "short");
|
||||
delay(500);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
let serial = require("serial");
|
||||
serial.setup("usart", 230400);
|
||||
|
||||
while (1) {
|
||||
let rx_data = serial.readBytes(1, 0);
|
||||
if (rx_data !== undefined) {
|
||||
serial.write(rx_data);
|
||||
let data_view = Uint8Array(rx_data);
|
||||
print("0x" + to_hex_string(data_view[0]));
|
||||
}
|
||||
}
|
||||
BIN
applications/system/js_app/icon.png
Normal file
BIN
applications/system/js_app/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
131
applications/system/js_app/js_app.c
Normal file
131
applications/system/js_app/js_app.c
Normal file
@@ -0,0 +1,131 @@
|
||||
#include <dialogs/dialogs.h>
|
||||
#include "js_thread.h"
|
||||
#include <storage/storage.h>
|
||||
#include "js_app_i.h"
|
||||
#include <toolbox/path.h>
|
||||
#include <assets_icons.h>
|
||||
|
||||
#define TAG "JS app"
|
||||
|
||||
typedef struct {
|
||||
JsThread* js_thread;
|
||||
Gui* gui;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
Loading* loading;
|
||||
JsConsoleView* console_view;
|
||||
} JsApp;
|
||||
|
||||
static uint32_t js_view_exit(void* context) {
|
||||
UNUSED(context);
|
||||
return VIEW_NONE;
|
||||
}
|
||||
|
||||
static void js_app_compact_trace(FuriString* trace_str) {
|
||||
// Keep only first line
|
||||
size_t line_end = furi_string_search_char(trace_str, '\n');
|
||||
if(line_end > 0) {
|
||||
furi_string_left(trace_str, line_end);
|
||||
}
|
||||
|
||||
// Remove full path
|
||||
FuriString* file_name = furi_string_alloc();
|
||||
size_t filename_start = furi_string_search_rchar(trace_str, '/');
|
||||
if(filename_start > 0) {
|
||||
filename_start++;
|
||||
furi_string_set_n(
|
||||
file_name, trace_str, filename_start, furi_string_size(trace_str) - filename_start);
|
||||
furi_string_printf(trace_str, "at %s", furi_string_get_cstr(file_name));
|
||||
}
|
||||
|
||||
furi_string_free(file_name);
|
||||
}
|
||||
|
||||
static void js_callback(JsThreadEvent event, const char* msg, void* context) {
|
||||
JsApp* app = context;
|
||||
furi_assert(app);
|
||||
|
||||
if(event == JsThreadEventDone) {
|
||||
FURI_LOG_I(TAG, "Script done");
|
||||
console_view_print(app->console_view, "--- DONE ---");
|
||||
} else if(event == JsThreadEventPrint) {
|
||||
console_view_print(app->console_view, msg);
|
||||
} else if(event == JsThreadEventError) {
|
||||
console_view_print(app->console_view, "--- ERROR ---");
|
||||
console_view_print(app->console_view, msg);
|
||||
} else if(event == JsThreadEventErrorTrace) {
|
||||
FuriString* compact_trace = furi_string_alloc_set_str(msg);
|
||||
js_app_compact_trace(compact_trace);
|
||||
console_view_print(app->console_view, furi_string_get_cstr(compact_trace));
|
||||
furi_string_free(compact_trace);
|
||||
console_view_print(app->console_view, "See logs for full trace");
|
||||
}
|
||||
}
|
||||
|
||||
static JsApp* js_app_alloc(void) {
|
||||
JsApp* app = malloc(sizeof(JsApp));
|
||||
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
app->loading = loading_alloc();
|
||||
|
||||
app->gui = furi_record_open("gui");
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, JsAppViewLoading, loading_get_view(app->loading));
|
||||
|
||||
app->console_view = console_view_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, JsAppViewConsole, console_view_get_view(app->console_view));
|
||||
view_set_previous_callback(console_view_get_view(app->console_view), js_view_exit);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, JsAppViewConsole);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
static void js_app_free(JsApp* app) {
|
||||
console_view_free(app->console_view);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, JsAppViewConsole);
|
||||
loading_free(app->loading);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, JsAppViewLoading);
|
||||
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
furi_record_close("gui");
|
||||
|
||||
free(app);
|
||||
}
|
||||
|
||||
int32_t js_app(void* arg) {
|
||||
JsApp* app = js_app_alloc();
|
||||
|
||||
FuriString* script_path = furi_string_alloc_set(APP_ASSETS_PATH());
|
||||
do {
|
||||
if(arg != NULL && strlen(arg) > 0) {
|
||||
furi_string_set(script_path, (const char*)arg);
|
||||
} else {
|
||||
DialogsFileBrowserOptions browser_options;
|
||||
dialog_file_browser_set_basic_options(&browser_options, ".js", &I_js_script_10px);
|
||||
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
if(!dialog_file_browser_show(dialogs, script_path, script_path, &browser_options))
|
||||
break;
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
}
|
||||
FuriString* name = furi_string_alloc();
|
||||
path_extract_filename(script_path, name, false);
|
||||
FuriString* start_text =
|
||||
furi_string_alloc_printf("Running %s", furi_string_get_cstr(name));
|
||||
console_view_print(app->console_view, furi_string_get_cstr(start_text));
|
||||
console_view_print(app->console_view, "------------");
|
||||
furi_string_free(name);
|
||||
furi_string_free(start_text);
|
||||
|
||||
app->js_thread = js_thread_run(furi_string_get_cstr(script_path), js_callback, app);
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
|
||||
js_thread_stop(app->js_thread);
|
||||
} while(0);
|
||||
|
||||
furi_string_free(script_path);
|
||||
|
||||
js_app_free(app);
|
||||
return 0;
|
||||
} //-V773
|
||||
10
applications/system/js_app/js_app_i.h
Normal file
10
applications/system/js_app/js_app_i.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/modules/loading.h>
|
||||
#include "views/console_view.h"
|
||||
|
||||
typedef enum {
|
||||
JsAppViewConsole,
|
||||
JsAppViewLoading,
|
||||
} JsAppView;
|
||||
126
applications/system/js_app/js_modules.c
Normal file
126
applications/system/js_app/js_modules.c
Normal file
@@ -0,0 +1,126 @@
|
||||
#include <core/common_defines.h>
|
||||
#include "js_modules.h"
|
||||
#include <m-dict.h>
|
||||
#include "modules/js_flipper.h"
|
||||
|
||||
#define TAG "JS modules"
|
||||
|
||||
typedef struct {
|
||||
JsModeConstructor create;
|
||||
JsModeDestructor destroy;
|
||||
void* context;
|
||||
} JsModuleData;
|
||||
|
||||
DICT_DEF2(JsModuleDict, FuriString*, FURI_STRING_OPLIST, JsModuleData, M_POD_OPLIST);
|
||||
|
||||
static const JsModuleDescriptor modules_builtin[] = {
|
||||
{"flipper", js_flipper_create, NULL},
|
||||
};
|
||||
|
||||
struct JsModules {
|
||||
struct mjs* mjs;
|
||||
JsModuleDict_t module_dict;
|
||||
PluginManager* plugin_manager;
|
||||
};
|
||||
|
||||
JsModules* js_modules_create(struct mjs* mjs, CompositeApiResolver* resolver) {
|
||||
JsModules* modules = malloc(sizeof(JsModules));
|
||||
modules->mjs = mjs;
|
||||
JsModuleDict_init(modules->module_dict);
|
||||
|
||||
modules->plugin_manager = plugin_manager_alloc(
|
||||
PLUGIN_APP_ID, PLUGIN_API_VERSION, composite_api_resolver_get(resolver));
|
||||
|
||||
return modules;
|
||||
}
|
||||
|
||||
void js_modules_destroy(JsModules* modules) {
|
||||
JsModuleDict_it_t it;
|
||||
for(JsModuleDict_it(it, modules->module_dict); !JsModuleDict_end_p(it);
|
||||
JsModuleDict_next(it)) {
|
||||
const JsModuleDict_itref_t* module_itref = JsModuleDict_cref(it);
|
||||
if(module_itref->value.destroy) {
|
||||
module_itref->value.destroy(module_itref->value.context);
|
||||
}
|
||||
}
|
||||
plugin_manager_free(modules->plugin_manager);
|
||||
JsModuleDict_clear(modules->module_dict);
|
||||
free(modules);
|
||||
}
|
||||
|
||||
mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_len) {
|
||||
FuriString* module_name = furi_string_alloc_set_str(name);
|
||||
// Check if module is already installed
|
||||
JsModuleData* module_inst = JsModuleDict_get(modules->module_dict, module_name);
|
||||
if(module_inst) { //-V547
|
||||
furi_string_free(module_name);
|
||||
mjs_prepend_errorf(
|
||||
modules->mjs, MJS_BAD_ARGS_ERROR, "\"%s\" module is already installed", name);
|
||||
return MJS_UNDEFINED;
|
||||
}
|
||||
|
||||
bool module_found = false;
|
||||
// Check built-in modules
|
||||
for(size_t i = 0; i < COUNT_OF(modules_builtin); i++) { //-V1008
|
||||
size_t name_compare_len = strlen(modules_builtin[i].name);
|
||||
|
||||
if(name_compare_len != name_len) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(strncmp(name, modules_builtin[i].name, name_compare_len) == 0) {
|
||||
JsModuleData module = {
|
||||
.create = modules_builtin[i].create, .destroy = modules_builtin[i].destroy};
|
||||
JsModuleDict_set_at(modules->module_dict, module_name, module);
|
||||
module_found = true;
|
||||
FURI_LOG_I(TAG, "Using built-in module %s", name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// External module load
|
||||
if(!module_found) {
|
||||
FuriString* module_path = furi_string_alloc();
|
||||
furi_string_printf(module_path, "%s/js_%s.fal", APP_DATA_PATH("plugins"), name);
|
||||
FURI_LOG_I(TAG, "Loading external module %s", furi_string_get_cstr(module_path));
|
||||
do {
|
||||
uint32_t plugin_cnt_last = plugin_manager_get_count(modules->plugin_manager);
|
||||
PluginManagerError load_error = plugin_manager_load_single(
|
||||
modules->plugin_manager, furi_string_get_cstr(module_path));
|
||||
if(load_error != PluginManagerErrorNone) {
|
||||
break;
|
||||
}
|
||||
const JsModuleDescriptor* plugin =
|
||||
plugin_manager_get_ep(modules->plugin_manager, plugin_cnt_last);
|
||||
furi_assert(plugin);
|
||||
|
||||
if(strncmp(name, plugin->name, name_len) != 0) {
|
||||
FURI_LOG_E(TAG, "Module name missmatch %s", plugin->name);
|
||||
break;
|
||||
}
|
||||
JsModuleData module = {.create = plugin->create, .destroy = plugin->destroy};
|
||||
JsModuleDict_set_at(modules->module_dict, module_name, module);
|
||||
|
||||
module_found = true;
|
||||
} while(0);
|
||||
furi_string_free(module_path);
|
||||
}
|
||||
|
||||
// Run module constructor
|
||||
mjs_val_t module_object = MJS_UNDEFINED;
|
||||
if(module_found) {
|
||||
module_inst = JsModuleDict_get(modules->module_dict, module_name);
|
||||
furi_assert(module_inst);
|
||||
if(module_inst->create) { //-V779
|
||||
module_inst->context = module_inst->create(modules->mjs, &module_object);
|
||||
}
|
||||
}
|
||||
|
||||
if(module_object == MJS_UNDEFINED) { //-V547
|
||||
mjs_prepend_errorf(modules->mjs, MJS_BAD_ARGS_ERROR, "\"%s\" module load fail", name);
|
||||
}
|
||||
|
||||
furi_string_free(module_name);
|
||||
|
||||
return module_object;
|
||||
}
|
||||
25
applications/system/js_app/js_modules.h
Normal file
25
applications/system/js_app/js_modules.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include "js_thread_i.h"
|
||||
#include <flipper_application/flipper_application.h>
|
||||
#include <flipper_application/plugins/plugin_manager.h>
|
||||
#include <flipper_application/plugins/composite_resolver.h>
|
||||
|
||||
#define PLUGIN_APP_ID "js"
|
||||
#define PLUGIN_API_VERSION 1
|
||||
|
||||
typedef void* (*JsModeConstructor)(struct mjs* mjs, mjs_val_t* object);
|
||||
typedef void (*JsModeDestructor)(void* inst);
|
||||
|
||||
typedef struct {
|
||||
char* name;
|
||||
JsModeConstructor create;
|
||||
JsModeDestructor destroy;
|
||||
} JsModuleDescriptor;
|
||||
|
||||
typedef struct JsModules JsModules;
|
||||
|
||||
JsModules* js_modules_create(struct mjs* mjs, CompositeApiResolver* resolver);
|
||||
|
||||
void js_modules_destroy(JsModules* modules);
|
||||
|
||||
mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_len);
|
||||
323
applications/system/js_app/js_thread.c
Normal file
323
applications/system/js_app/js_thread.c
Normal file
@@ -0,0 +1,323 @@
|
||||
#include <common/cs_dbg.h>
|
||||
#include <toolbox/stream/file_stream.h>
|
||||
#include <loader/firmware_api/firmware_api.h>
|
||||
#include <flipper_application/api_hashtable/api_hashtable.h>
|
||||
#include <flipper_application/plugins/composite_resolver.h>
|
||||
#include <furi_hal.h>
|
||||
#include "plugin_api/app_api_interface.h"
|
||||
#include "js_thread.h"
|
||||
#include "js_thread_i.h"
|
||||
#include "js_modules.h"
|
||||
|
||||
#define TAG "JS"
|
||||
|
||||
struct JsThread {
|
||||
FuriThread* thread;
|
||||
FuriString* path;
|
||||
CompositeApiResolver* resolver;
|
||||
JsThreadCallback app_callback;
|
||||
void* context;
|
||||
JsModules* modules;
|
||||
};
|
||||
|
||||
static void js_str_print(FuriString* msg_str, struct mjs* mjs) {
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
for(size_t i = 0; i < num_args; i++) {
|
||||
char* name = NULL;
|
||||
size_t name_len = 0;
|
||||
int need_free = 0;
|
||||
mjs_val_t arg = mjs_arg(mjs, i);
|
||||
mjs_err_t err = mjs_to_string(mjs, &arg, &name, &name_len, &need_free);
|
||||
if(err != MJS_OK) {
|
||||
furi_string_cat_printf(msg_str, "err %s ", mjs_strerror(mjs, err));
|
||||
} else {
|
||||
furi_string_cat_printf(msg_str, "%s ", name);
|
||||
}
|
||||
if(need_free) {
|
||||
free(name);
|
||||
name = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void js_print(struct mjs* mjs) {
|
||||
FuriString* msg_str = furi_string_alloc();
|
||||
js_str_print(msg_str, mjs);
|
||||
|
||||
printf("%s\r\n", furi_string_get_cstr(msg_str));
|
||||
|
||||
JsThread* worker = mjs_get_context(mjs);
|
||||
furi_assert(worker);
|
||||
if(worker->app_callback) {
|
||||
worker->app_callback(JsThreadEventPrint, furi_string_get_cstr(msg_str), worker->context);
|
||||
}
|
||||
|
||||
furi_string_free(msg_str);
|
||||
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void js_console_log(struct mjs* mjs) {
|
||||
FuriString* msg_str = furi_string_alloc();
|
||||
js_str_print(msg_str, mjs);
|
||||
FURI_LOG_I(TAG, "%s", furi_string_get_cstr(msg_str));
|
||||
furi_string_free(msg_str);
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void js_console_warn(struct mjs* mjs) {
|
||||
FuriString* msg_str = furi_string_alloc();
|
||||
js_str_print(msg_str, mjs);
|
||||
FURI_LOG_W(TAG, "%s", furi_string_get_cstr(msg_str));
|
||||
furi_string_free(msg_str);
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void js_console_error(struct mjs* mjs) {
|
||||
FuriString* msg_str = furi_string_alloc();
|
||||
js_str_print(msg_str, mjs);
|
||||
FURI_LOG_E(TAG, "%s", furi_string_get_cstr(msg_str));
|
||||
furi_string_free(msg_str);
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void js_console_debug(struct mjs* mjs) {
|
||||
FuriString* msg_str = furi_string_alloc();
|
||||
js_str_print(msg_str, mjs);
|
||||
FURI_LOG_D(TAG, "%s", furi_string_get_cstr(msg_str));
|
||||
furi_string_free(msg_str);
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void js_exit_flag_poll(struct mjs* mjs) {
|
||||
uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny, 0);
|
||||
if(flags & FuriFlagError) {
|
||||
return;
|
||||
}
|
||||
if(flags & ThreadEventStop) {
|
||||
mjs_exit(mjs);
|
||||
}
|
||||
}
|
||||
|
||||
bool js_delay_with_flags(struct mjs* mjs, uint32_t time) {
|
||||
uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny, time);
|
||||
if(flags & FuriFlagError) {
|
||||
return false;
|
||||
}
|
||||
if(flags & ThreadEventStop) {
|
||||
mjs_exit(mjs);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void js_flags_set(struct mjs* mjs, uint32_t flags) {
|
||||
JsThread* worker = mjs_get_context(mjs);
|
||||
furi_assert(worker);
|
||||
furi_thread_flags_set(furi_thread_get_id(worker->thread), flags);
|
||||
}
|
||||
|
||||
uint32_t js_flags_wait(struct mjs* mjs, uint32_t flags_mask, uint32_t timeout) {
|
||||
flags_mask |= ThreadEventStop;
|
||||
uint32_t flags = furi_thread_flags_get();
|
||||
furi_check((flags & FuriFlagError) == 0);
|
||||
if(flags == 0) {
|
||||
flags = furi_thread_flags_wait(flags_mask, FuriFlagWaitAny, timeout);
|
||||
} else {
|
||||
uint32_t state = furi_thread_flags_clear(flags & flags_mask);
|
||||
furi_check((state & FuriFlagError) == 0);
|
||||
}
|
||||
|
||||
if(flags & FuriFlagError) {
|
||||
return 0;
|
||||
}
|
||||
if(flags & ThreadEventStop) {
|
||||
mjs_exit(mjs);
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
static void js_delay(struct mjs* mjs) {
|
||||
bool args_correct = false;
|
||||
int ms = 0;
|
||||
|
||||
if(mjs_nargs(mjs) == 1) {
|
||||
mjs_val_t arg = mjs_arg(mjs, 0);
|
||||
if(mjs_is_number(arg)) {
|
||||
ms = mjs_get_int(mjs, arg);
|
||||
args_correct = true;
|
||||
}
|
||||
}
|
||||
if(!args_correct) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
js_delay_with_flags(mjs, ms);
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void* js_dlsym(void* handle, const char* name) {
|
||||
CompositeApiResolver* resolver = handle;
|
||||
Elf32_Addr addr = 0;
|
||||
uint32_t hash = elf_symbolname_hash(name);
|
||||
const ElfApiInterface* api = composite_api_resolver_get(resolver);
|
||||
|
||||
if(!api->resolver_callback(api, hash, &addr)) {
|
||||
FURI_LOG_E(TAG, "FFI: cannot find \"%s\"", name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return (void*)addr;
|
||||
}
|
||||
|
||||
static void js_ffi_address(struct mjs* mjs) {
|
||||
mjs_val_t name_v = mjs_arg(mjs, 0);
|
||||
size_t len;
|
||||
const char* name = mjs_get_string(mjs, &name_v, &len);
|
||||
void* addr = mjs_ffi_resolve(mjs, name);
|
||||
mjs_return(mjs, mjs_mk_foreign(mjs, addr));
|
||||
}
|
||||
|
||||
static void js_require(struct mjs* mjs) {
|
||||
mjs_val_t name_v = mjs_arg(mjs, 0);
|
||||
size_t len;
|
||||
const char* name = mjs_get_string(mjs, &name_v, &len);
|
||||
mjs_val_t req_object = MJS_UNDEFINED;
|
||||
if((len == 0) || (name == NULL)) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "String argument is expected");
|
||||
} else {
|
||||
JsThread* worker = mjs_get_context(mjs);
|
||||
furi_assert(worker);
|
||||
req_object = js_module_require(worker->modules, name, len);
|
||||
}
|
||||
mjs_return(mjs, req_object);
|
||||
}
|
||||
|
||||
static void js_global_to_string(struct mjs* mjs) {
|
||||
double num = mjs_get_int(mjs, mjs_arg(mjs, 0));
|
||||
char tmp_str[] = "-2147483648";
|
||||
itoa(num, tmp_str, 10);
|
||||
mjs_val_t ret = mjs_mk_string(mjs, tmp_str, ~0, true);
|
||||
mjs_return(mjs, ret);
|
||||
}
|
||||
|
||||
static void js_global_to_hex_string(struct mjs* mjs) {
|
||||
double num = mjs_get_int(mjs, mjs_arg(mjs, 0));
|
||||
char tmp_str[] = "-FFFFFFFF";
|
||||
itoa(num, tmp_str, 16);
|
||||
mjs_val_t ret = mjs_mk_string(mjs, tmp_str, ~0, true);
|
||||
mjs_return(mjs, ret);
|
||||
}
|
||||
|
||||
#ifdef JS_DEBUG
|
||||
static void js_dump_write_callback(void* ctx, const char* format, ...) {
|
||||
File* file = ctx;
|
||||
furi_assert(ctx);
|
||||
|
||||
FuriString* str = furi_string_alloc();
|
||||
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
furi_string_vprintf(str, format, args);
|
||||
furi_string_cat(str, "\n");
|
||||
va_end(args);
|
||||
|
||||
storage_file_write(file, furi_string_get_cstr(str), furi_string_size(str));
|
||||
furi_string_free(str);
|
||||
}
|
||||
#endif
|
||||
|
||||
static int32_t js_thread(void* arg) {
|
||||
JsThread* worker = arg;
|
||||
worker->resolver = composite_api_resolver_alloc();
|
||||
composite_api_resolver_add(worker->resolver, firmware_api_interface);
|
||||
composite_api_resolver_add(worker->resolver, application_api_interface);
|
||||
|
||||
struct mjs* mjs = mjs_create(worker);
|
||||
worker->modules = js_modules_create(mjs, worker->resolver);
|
||||
mjs_val_t global = mjs_get_global(mjs);
|
||||
mjs_set(mjs, global, "print", ~0, MJS_MK_FN(js_print));
|
||||
mjs_set(mjs, global, "delay", ~0, MJS_MK_FN(js_delay));
|
||||
mjs_set(mjs, global, "to_string", ~0, MJS_MK_FN(js_global_to_string));
|
||||
mjs_set(mjs, global, "to_hex_string", ~0, MJS_MK_FN(js_global_to_hex_string));
|
||||
mjs_set(mjs, global, "ffi_address", ~0, MJS_MK_FN(js_ffi_address));
|
||||
mjs_set(mjs, global, "require", ~0, MJS_MK_FN(js_require));
|
||||
|
||||
mjs_val_t console_obj = mjs_mk_object(mjs);
|
||||
mjs_set(mjs, console_obj, "log", ~0, MJS_MK_FN(js_console_log));
|
||||
mjs_set(mjs, console_obj, "warn", ~0, MJS_MK_FN(js_console_warn));
|
||||
mjs_set(mjs, console_obj, "error", ~0, MJS_MK_FN(js_console_error));
|
||||
mjs_set(mjs, console_obj, "debug", ~0, MJS_MK_FN(js_console_debug));
|
||||
mjs_set(mjs, global, "console", ~0, console_obj);
|
||||
|
||||
mjs_set_ffi_resolver(mjs, js_dlsym, worker->resolver);
|
||||
|
||||
mjs_set_exec_flags_poller(mjs, js_exit_flag_poll);
|
||||
|
||||
mjs_err_t err = mjs_exec_file(mjs, furi_string_get_cstr(worker->path), NULL);
|
||||
|
||||
#ifdef JS_DEBUG
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
|
||||
FuriString* dump_path = furi_string_alloc_set(worker->path);
|
||||
furi_string_cat(dump_path, ".lst");
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
File* file = storage_file_alloc(storage);
|
||||
|
||||
if(storage_file_open(
|
||||
file, furi_string_get_cstr(dump_path), FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
|
||||
mjs_disasm_all(mjs, js_dump_write_callback, file);
|
||||
}
|
||||
|
||||
storage_file_close(file);
|
||||
storage_file_free(file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
furi_string_free(dump_path);
|
||||
}
|
||||
#endif
|
||||
|
||||
if(err != MJS_OK) {
|
||||
FURI_LOG_E(TAG, "Exec error: %s", mjs_strerror(mjs, err));
|
||||
if(worker->app_callback) {
|
||||
worker->app_callback(JsThreadEventError, mjs_strerror(mjs, err), worker->context);
|
||||
}
|
||||
const char* stack_trace = mjs_get_stack_trace(mjs);
|
||||
if(stack_trace != NULL) {
|
||||
FURI_LOG_E(TAG, "Stack trace:\n%s", stack_trace);
|
||||
if(worker->app_callback) {
|
||||
worker->app_callback(JsThreadEventErrorTrace, stack_trace, worker->context);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(worker->app_callback) {
|
||||
worker->app_callback(JsThreadEventDone, NULL, worker->context);
|
||||
}
|
||||
}
|
||||
|
||||
js_modules_destroy(worker->modules);
|
||||
mjs_destroy(mjs);
|
||||
|
||||
composite_api_resolver_free(worker->resolver);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
JsThread* js_thread_run(const char* script_path, JsThreadCallback callback, void* context) {
|
||||
JsThread* worker = malloc(sizeof(JsThread)); //-V799
|
||||
worker->path = furi_string_alloc_set(script_path);
|
||||
worker->thread = furi_thread_alloc_ex("JsThread", 8 * 1024, js_thread, worker);
|
||||
worker->app_callback = callback;
|
||||
worker->context = context;
|
||||
furi_thread_start(worker->thread);
|
||||
return worker;
|
||||
}
|
||||
|
||||
void js_thread_stop(JsThread* worker) {
|
||||
furi_thread_flags_set(furi_thread_get_id(worker->thread), ThreadEventStop);
|
||||
furi_thread_join(worker->thread);
|
||||
furi_thread_free(worker->thread);
|
||||
furi_string_free(worker->path);
|
||||
free(worker);
|
||||
}
|
||||
16
applications/system/js_app/js_thread.h
Normal file
16
applications/system/js_app/js_thread.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
typedef struct JsThread JsThread;
|
||||
|
||||
typedef enum {
|
||||
JsThreadEventDone,
|
||||
JsThreadEventError,
|
||||
JsThreadEventPrint,
|
||||
JsThreadEventErrorTrace,
|
||||
} JsThreadEvent;
|
||||
|
||||
typedef void (*JsThreadCallback)(JsThreadEvent event, const char* msg, void* context);
|
||||
|
||||
JsThread* js_thread_run(const char* script_path, JsThreadCallback callback, void* context);
|
||||
|
||||
void js_thread_stop(JsThread* worker);
|
||||
25
applications/system/js_app/js_thread_i.h
Normal file
25
applications/system/js_app/js_thread_i.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <mjs_core_public.h>
|
||||
#include <mjs_ffi_public.h>
|
||||
#include <mjs_exec_public.h>
|
||||
#include <mjs_object_public.h>
|
||||
#include <mjs_string_public.h>
|
||||
#include <mjs_array_public.h>
|
||||
#include <mjs_util_public.h>
|
||||
#include <mjs_primitive_public.h>
|
||||
#include <mjs_array_buf_public.h>
|
||||
|
||||
#define INST_PROP_NAME "_"
|
||||
|
||||
typedef enum {
|
||||
ThreadEventStop = (1 << 0),
|
||||
ThreadEventCustomDataRx = (1 << 1),
|
||||
} WorkerEventFlags;
|
||||
|
||||
bool js_delay_with_flags(struct mjs* mjs, uint32_t time);
|
||||
|
||||
void js_flags_set(struct mjs* mjs, uint32_t flags);
|
||||
|
||||
uint32_t js_flags_wait(struct mjs* mjs, uint32_t flags, uint32_t timeout);
|
||||
410
applications/system/js_app/modules/js_badusb.c
Normal file
410
applications/system/js_app/modules/js_badusb.c
Normal file
@@ -0,0 +1,410 @@
|
||||
#include <core/common_defines.h>
|
||||
#include "../js_modules.h"
|
||||
#include <furi_hal.h>
|
||||
|
||||
typedef struct {
|
||||
FuriHalUsbHidConfig* hid_cfg;
|
||||
FuriHalUsbInterface* usb_if_prev;
|
||||
uint8_t key_hold_cnt;
|
||||
} JsBadusbInst;
|
||||
|
||||
static const struct {
|
||||
char* name;
|
||||
uint16_t code;
|
||||
} key_codes[] = {
|
||||
{"CTRL", KEY_MOD_LEFT_CTRL},
|
||||
{"SHIFT", KEY_MOD_LEFT_SHIFT},
|
||||
{"ALT", KEY_MOD_LEFT_ALT},
|
||||
{"GUI", KEY_MOD_LEFT_GUI},
|
||||
|
||||
{"DOWN", HID_KEYBOARD_DOWN_ARROW},
|
||||
{"LEFT", HID_KEYBOARD_LEFT_ARROW},
|
||||
{"RIGHT", HID_KEYBOARD_RIGHT_ARROW},
|
||||
{"UP", HID_KEYBOARD_UP_ARROW},
|
||||
|
||||
{"ENTER", HID_KEYBOARD_RETURN},
|
||||
{"PAUSE", HID_KEYBOARD_PAUSE},
|
||||
{"CAPSLOCK", HID_KEYBOARD_CAPS_LOCK},
|
||||
{"DELETE", HID_KEYBOARD_DELETE_FORWARD},
|
||||
{"BACKSPACE", HID_KEYBOARD_DELETE},
|
||||
{"END", HID_KEYBOARD_END},
|
||||
{"ESC", HID_KEYBOARD_ESCAPE},
|
||||
{"HOME", HID_KEYBOARD_HOME},
|
||||
{"INSERT", HID_KEYBOARD_INSERT},
|
||||
{"NUMLOCK", HID_KEYPAD_NUMLOCK},
|
||||
{"PAGEUP", HID_KEYBOARD_PAGE_UP},
|
||||
{"PAGEDOWN", HID_KEYBOARD_PAGE_DOWN},
|
||||
{"PRINTSCREEN", HID_KEYBOARD_PRINT_SCREEN},
|
||||
{"SCROLLLOCK", HID_KEYBOARD_SCROLL_LOCK},
|
||||
{"SPACE", HID_KEYBOARD_SPACEBAR},
|
||||
{"TAB", HID_KEYBOARD_TAB},
|
||||
{"MENU", HID_KEYBOARD_APPLICATION},
|
||||
|
||||
{"F1", HID_KEYBOARD_F1},
|
||||
{"F2", HID_KEYBOARD_F2},
|
||||
{"F3", HID_KEYBOARD_F3},
|
||||
{"F4", HID_KEYBOARD_F4},
|
||||
{"F5", HID_KEYBOARD_F5},
|
||||
{"F6", HID_KEYBOARD_F6},
|
||||
{"F7", HID_KEYBOARD_F7},
|
||||
{"F8", HID_KEYBOARD_F8},
|
||||
{"F9", HID_KEYBOARD_F9},
|
||||
{"F10", HID_KEYBOARD_F10},
|
||||
{"F11", HID_KEYBOARD_F11},
|
||||
{"F12", HID_KEYBOARD_F12},
|
||||
};
|
||||
|
||||
static bool setup_parse_params(struct mjs* mjs, mjs_val_t arg, FuriHalUsbHidConfig* hid_cfg) {
|
||||
if(!mjs_is_object(arg)) {
|
||||
return false;
|
||||
}
|
||||
mjs_val_t vid_obj = mjs_get(mjs, arg, "vid", ~0);
|
||||
mjs_val_t pid_obj = mjs_get(mjs, arg, "pid", ~0);
|
||||
mjs_val_t mfr_obj = mjs_get(mjs, arg, "mfr_name", ~0);
|
||||
mjs_val_t prod_obj = mjs_get(mjs, arg, "prod_name", ~0);
|
||||
|
||||
if(mjs_is_number(vid_obj) && mjs_is_number(pid_obj)) {
|
||||
hid_cfg->vid = mjs_get_int32(mjs, vid_obj);
|
||||
hid_cfg->pid = mjs_get_int32(mjs, pid_obj);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(mjs_is_string(mfr_obj)) {
|
||||
size_t str_len = 0;
|
||||
const char* str_temp = mjs_get_string(mjs, &mfr_obj, &str_len);
|
||||
if((str_len == 0) || (str_temp == NULL)) {
|
||||
return false;
|
||||
}
|
||||
strlcpy(hid_cfg->manuf, str_temp, sizeof(hid_cfg->manuf));
|
||||
}
|
||||
|
||||
if(mjs_is_string(prod_obj)) {
|
||||
size_t str_len = 0;
|
||||
const char* str_temp = mjs_get_string(mjs, &prod_obj, &str_len);
|
||||
if((str_len == 0) || (str_temp == NULL)) {
|
||||
return false;
|
||||
}
|
||||
strlcpy(hid_cfg->product, str_temp, sizeof(hid_cfg->product));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void js_badusb_setup(struct mjs* mjs) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
JsBadusbInst* badusb = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(badusb);
|
||||
|
||||
if(badusb->usb_if_prev) {
|
||||
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "HID is already started");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
bool args_correct = false;
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
if(num_args == 0) {
|
||||
// No arguments: start USB HID with default settings
|
||||
args_correct = true;
|
||||
} else if(num_args == 1) {
|
||||
badusb->hid_cfg = malloc(sizeof(FuriHalUsbHidConfig));
|
||||
// Parse argument object
|
||||
args_correct = setup_parse_params(mjs, mjs_arg(mjs, 0), badusb->hid_cfg);
|
||||
}
|
||||
if(!args_correct) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
badusb->usb_if_prev = furi_hal_usb_get_config();
|
||||
|
||||
if(!furi_hal_usb_set_config(&usb_hid, badusb->hid_cfg)) {
|
||||
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "USB is locked, close companion app first");
|
||||
badusb->usb_if_prev = NULL;
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void js_badusb_is_connected(struct mjs* mjs) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
JsBadusbInst* badusb = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(badusb);
|
||||
|
||||
if(badusb->usb_if_prev == NULL) {
|
||||
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "HID is not started");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
bool is_connected = furi_hal_hid_is_connected();
|
||||
mjs_return(mjs, mjs_mk_boolean(mjs, is_connected));
|
||||
}
|
||||
|
||||
uint16_t get_keycode_by_name(const char* key_name, size_t name_len) {
|
||||
if(name_len == 1) { // Single char
|
||||
return (HID_ASCII_TO_KEY(key_name[0]));
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < COUNT_OF(key_codes); i++) {
|
||||
size_t key_cmd_len = strlen(key_codes[i].name);
|
||||
if(key_cmd_len != name_len) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(strncmp(key_name, key_codes[i].name, name_len) == 0) {
|
||||
return key_codes[i].code;
|
||||
}
|
||||
}
|
||||
|
||||
return HID_KEYBOARD_NONE;
|
||||
}
|
||||
|
||||
static bool parse_keycode(struct mjs* mjs, size_t nargs, uint16_t* keycode) {
|
||||
uint16_t key_tmp = 0;
|
||||
for(size_t i = 0; i < nargs; i++) {
|
||||
mjs_val_t arg = mjs_arg(mjs, i);
|
||||
if(mjs_is_string(arg)) {
|
||||
size_t name_len = 0;
|
||||
const char* key_name = mjs_get_string(mjs, &arg, &name_len);
|
||||
if((key_name == NULL) || (name_len == 0)) {
|
||||
// String error
|
||||
return false;
|
||||
}
|
||||
uint16_t str_key = get_keycode_by_name(key_name, name_len);
|
||||
if(str_key == HID_KEYBOARD_NONE) {
|
||||
// Unknown key code
|
||||
return false;
|
||||
}
|
||||
if((str_key & 0xFF) && (key_tmp & 0xFF)) {
|
||||
// Main key is already defined
|
||||
return false;
|
||||
}
|
||||
key_tmp |= str_key;
|
||||
} else if(mjs_is_number(arg)) {
|
||||
uint32_t keycode_number = (uint32_t)mjs_get_int32(mjs, arg);
|
||||
if(((key_tmp & 0xFF) != 0) || (keycode_number > 0xFF)) {
|
||||
return false;
|
||||
}
|
||||
key_tmp |= keycode_number & 0xFF;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
*keycode = key_tmp;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void js_badusb_press(struct mjs* mjs) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
JsBadusbInst* badusb = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(badusb);
|
||||
if(badusb->usb_if_prev == NULL) {
|
||||
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "HID is not started");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
bool args_correct = false;
|
||||
uint16_t keycode = HID_KEYBOARD_NONE;
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
if(num_args > 0) {
|
||||
args_correct = parse_keycode(mjs, num_args, &keycode);
|
||||
}
|
||||
if(!args_correct) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
furi_hal_hid_kb_press(keycode);
|
||||
furi_hal_hid_kb_release(keycode);
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void js_badusb_hold(struct mjs* mjs) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
JsBadusbInst* badusb = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(badusb);
|
||||
if(badusb->usb_if_prev == NULL) {
|
||||
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "HID is not started");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
bool args_correct = false;
|
||||
uint16_t keycode = HID_KEYBOARD_NONE;
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
if(num_args > 0) {
|
||||
args_correct = parse_keycode(mjs, num_args, &keycode);
|
||||
}
|
||||
if(!args_correct) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
if(keycode & 0xFF) {
|
||||
badusb->key_hold_cnt++;
|
||||
if(badusb->key_hold_cnt > (HID_KB_MAX_KEYS - 1)) {
|
||||
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Too many keys are hold");
|
||||
furi_hal_hid_kb_release_all();
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
}
|
||||
furi_hal_hid_kb_press(keycode);
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void js_badusb_release(struct mjs* mjs) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
JsBadusbInst* badusb = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(badusb);
|
||||
if(badusb->usb_if_prev == NULL) {
|
||||
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "HID is not started");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
bool args_correct = false;
|
||||
uint16_t keycode = HID_KEYBOARD_NONE;
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
if(num_args == 0) {
|
||||
furi_hal_hid_kb_release_all();
|
||||
badusb->key_hold_cnt = 0;
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
} else {
|
||||
args_correct = parse_keycode(mjs, num_args, &keycode);
|
||||
}
|
||||
if(!args_correct) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
if((keycode & 0xFF) && (badusb->key_hold_cnt > 0)) {
|
||||
badusb->key_hold_cnt--;
|
||||
}
|
||||
furi_hal_hid_kb_release(keycode);
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void badusb_print(struct mjs* mjs, bool ln) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
JsBadusbInst* badusb = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(badusb);
|
||||
if(badusb->usb_if_prev == NULL) {
|
||||
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "HID is not started");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
bool args_correct = false;
|
||||
const char* text_str = NULL;
|
||||
size_t text_len = 0;
|
||||
uint32_t delay_val = 0;
|
||||
do {
|
||||
mjs_val_t obj_string = MJS_UNDEFINED;
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
if(num_args == 1) {
|
||||
obj_string = mjs_arg(mjs, 0);
|
||||
} else if(num_args == 2) {
|
||||
obj_string = mjs_arg(mjs, 0);
|
||||
mjs_val_t obj_delay = mjs_arg(mjs, 1);
|
||||
if(!mjs_is_number(obj_delay)) {
|
||||
break;
|
||||
}
|
||||
delay_val = (uint32_t)mjs_get_int32(mjs, obj_delay);
|
||||
if(delay_val > 60000) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!mjs_is_string(obj_string)) {
|
||||
break;
|
||||
}
|
||||
text_str = mjs_get_string(mjs, &obj_string, &text_len);
|
||||
if((text_str == NULL) || (text_len == 0)) {
|
||||
break;
|
||||
}
|
||||
args_correct = true;
|
||||
} while(0);
|
||||
|
||||
if(!args_correct) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < text_len; i++) {
|
||||
uint16_t keycode = HID_ASCII_TO_KEY(text_str[i]);
|
||||
furi_hal_hid_kb_press(keycode);
|
||||
furi_hal_hid_kb_release(keycode);
|
||||
if(delay_val > 0) {
|
||||
bool need_exit = js_delay_with_flags(mjs, delay_val);
|
||||
if(need_exit) {
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(ln) {
|
||||
furi_hal_hid_kb_press(HID_KEYBOARD_RETURN);
|
||||
furi_hal_hid_kb_release(HID_KEYBOARD_RETURN);
|
||||
}
|
||||
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void js_badusb_print(struct mjs* mjs) {
|
||||
badusb_print(mjs, false);
|
||||
}
|
||||
|
||||
static void js_badusb_println(struct mjs* mjs) {
|
||||
badusb_print(mjs, true);
|
||||
}
|
||||
|
||||
static void* js_badusb_create(struct mjs* mjs, mjs_val_t* object) {
|
||||
JsBadusbInst* badusb = malloc(sizeof(JsBadusbInst));
|
||||
mjs_val_t badusb_obj = mjs_mk_object(mjs);
|
||||
mjs_set(mjs, badusb_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, badusb));
|
||||
mjs_set(mjs, badusb_obj, "setup", ~0, MJS_MK_FN(js_badusb_setup));
|
||||
mjs_set(mjs, badusb_obj, "isConnected", ~0, MJS_MK_FN(js_badusb_is_connected));
|
||||
mjs_set(mjs, badusb_obj, "press", ~0, MJS_MK_FN(js_badusb_press));
|
||||
mjs_set(mjs, badusb_obj, "hold", ~0, MJS_MK_FN(js_badusb_hold));
|
||||
mjs_set(mjs, badusb_obj, "release", ~0, MJS_MK_FN(js_badusb_release));
|
||||
mjs_set(mjs, badusb_obj, "print", ~0, MJS_MK_FN(js_badusb_print));
|
||||
mjs_set(mjs, badusb_obj, "println", ~0, MJS_MK_FN(js_badusb_println));
|
||||
*object = badusb_obj;
|
||||
return badusb;
|
||||
}
|
||||
|
||||
static void js_badusb_destroy(void* inst) {
|
||||
JsBadusbInst* badusb = inst;
|
||||
if(badusb->usb_if_prev) {
|
||||
furi_hal_hid_kb_release_all();
|
||||
furi_check(furi_hal_usb_set_config(badusb->usb_if_prev, NULL));
|
||||
}
|
||||
if(badusb->hid_cfg) {
|
||||
free(badusb->hid_cfg);
|
||||
}
|
||||
free(badusb);
|
||||
}
|
||||
|
||||
static const JsModuleDescriptor js_badusb_desc = {
|
||||
"badusb",
|
||||
js_badusb_create,
|
||||
js_badusb_destroy,
|
||||
};
|
||||
|
||||
static const FlipperAppPluginDescriptor plugin_descriptor = {
|
||||
.appid = PLUGIN_APP_ID,
|
||||
.ep_api_version = PLUGIN_API_VERSION,
|
||||
.entry_point = &js_badusb_desc,
|
||||
};
|
||||
|
||||
const FlipperAppPluginDescriptor* js_badusb_ep(void) {
|
||||
return &plugin_descriptor;
|
||||
}
|
||||
154
applications/system/js_app/modules/js_dialog.c
Normal file
154
applications/system/js_app/modules/js_dialog.c
Normal file
@@ -0,0 +1,154 @@
|
||||
#include <core/common_defines.h>
|
||||
#include "../js_modules.h"
|
||||
#include <dialogs/dialogs.h>
|
||||
|
||||
static bool js_dialog_msg_parse_params(struct mjs* mjs, const char** hdr, const char** msg) {
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
if(num_args != 2) {
|
||||
return false;
|
||||
}
|
||||
mjs_val_t header_obj = mjs_arg(mjs, 0);
|
||||
mjs_val_t msg_obj = mjs_arg(mjs, 1);
|
||||
if((!mjs_is_string(header_obj)) || (!mjs_is_string(msg_obj))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t arg_len = 0;
|
||||
*hdr = mjs_get_string(mjs, &header_obj, &arg_len);
|
||||
if(arg_len == 0) {
|
||||
*hdr = NULL;
|
||||
}
|
||||
|
||||
*msg = mjs_get_string(mjs, &msg_obj, &arg_len);
|
||||
if(arg_len == 0) {
|
||||
*msg = NULL;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void js_dialog_message(struct mjs* mjs) {
|
||||
const char* dialog_header = NULL;
|
||||
const char* dialog_msg = NULL;
|
||||
if(!js_dialog_msg_parse_params(mjs, &dialog_header, &dialog_msg)) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
DialogMessage* message = dialog_message_alloc();
|
||||
dialog_message_set_buttons(message, NULL, "OK", NULL);
|
||||
if(dialog_header) {
|
||||
dialog_message_set_header(message, dialog_header, 64, 3, AlignCenter, AlignTop);
|
||||
}
|
||||
if(dialog_msg) {
|
||||
dialog_message_set_text(message, dialog_msg, 64, 26, AlignCenter, AlignTop);
|
||||
}
|
||||
DialogMessageButton result = dialog_message_show(dialogs, message);
|
||||
dialog_message_free(message);
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
mjs_return(mjs, mjs_mk_boolean(mjs, result == DialogMessageButtonCenter));
|
||||
}
|
||||
|
||||
static void js_dialog_custom(struct mjs* mjs) {
|
||||
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
DialogMessage* message = dialog_message_alloc();
|
||||
|
||||
bool params_correct = false;
|
||||
|
||||
do {
|
||||
if(mjs_nargs(mjs) != 1) {
|
||||
break;
|
||||
}
|
||||
mjs_val_t params_obj = mjs_arg(mjs, 0);
|
||||
if(!mjs_is_object(params_obj)) {
|
||||
break;
|
||||
}
|
||||
|
||||
mjs_val_t text_obj = mjs_get(mjs, params_obj, "header", ~0);
|
||||
size_t arg_len = 0;
|
||||
const char* text_str = mjs_get_string(mjs, &text_obj, &arg_len);
|
||||
if(arg_len == 0) {
|
||||
text_str = NULL;
|
||||
}
|
||||
if(text_str) {
|
||||
dialog_message_set_header(message, text_str, 64, 3, AlignCenter, AlignTop);
|
||||
}
|
||||
|
||||
text_obj = mjs_get(mjs, params_obj, "text", ~0);
|
||||
text_str = mjs_get_string(mjs, &text_obj, &arg_len);
|
||||
if(arg_len == 0) {
|
||||
text_str = NULL;
|
||||
}
|
||||
if(text_str) {
|
||||
dialog_message_set_text(message, text_str, 64, 26, AlignCenter, AlignTop);
|
||||
}
|
||||
|
||||
mjs_val_t btn_obj[3] = {
|
||||
mjs_get(mjs, params_obj, "button_left", ~0),
|
||||
mjs_get(mjs, params_obj, "button_center", ~0),
|
||||
mjs_get(mjs, params_obj, "button_right", ~0),
|
||||
};
|
||||
const char* btn_text[3] = {NULL, NULL, NULL};
|
||||
|
||||
for(uint8_t i = 0; i < 3; i++) {
|
||||
if(!mjs_is_string(btn_obj[i])) {
|
||||
continue;
|
||||
}
|
||||
btn_text[i] = mjs_get_string(mjs, &btn_obj[i], &arg_len);
|
||||
if(arg_len == 0) {
|
||||
btn_text[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
dialog_message_set_buttons(message, btn_text[0], btn_text[1], btn_text[2]);
|
||||
|
||||
DialogMessageButton result = dialog_message_show(dialogs, message);
|
||||
mjs_val_t return_obj = MJS_UNDEFINED;
|
||||
if(result == DialogMessageButtonLeft) {
|
||||
return_obj = mjs_mk_string(mjs, btn_text[0], ~0, true);
|
||||
} else if(result == DialogMessageButtonCenter) {
|
||||
return_obj = mjs_mk_string(mjs, btn_text[1], ~0, true);
|
||||
} else if(result == DialogMessageButtonRight) {
|
||||
return_obj = mjs_mk_string(mjs, btn_text[2], ~0, true);
|
||||
} else {
|
||||
return_obj = mjs_mk_string(mjs, "", ~0, true);
|
||||
}
|
||||
|
||||
mjs_return(mjs, return_obj);
|
||||
params_correct = true;
|
||||
} while(0);
|
||||
|
||||
dialog_message_free(message);
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
|
||||
if(!params_correct) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
}
|
||||
|
||||
static void* js_dialog_create(struct mjs* mjs, mjs_val_t* object) {
|
||||
mjs_val_t dialog_obj = mjs_mk_object(mjs);
|
||||
mjs_set(mjs, dialog_obj, "message", ~0, MJS_MK_FN(js_dialog_message));
|
||||
mjs_set(mjs, dialog_obj, "custom", ~0, MJS_MK_FN(js_dialog_custom));
|
||||
*object = dialog_obj;
|
||||
|
||||
return (void*)1;
|
||||
}
|
||||
|
||||
static const JsModuleDescriptor js_dialog_desc = {
|
||||
"dialog",
|
||||
js_dialog_create,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const FlipperAppPluginDescriptor plugin_descriptor = {
|
||||
.appid = PLUGIN_APP_ID,
|
||||
.ep_api_version = PLUGIN_API_VERSION,
|
||||
.entry_point = &js_dialog_desc,
|
||||
};
|
||||
|
||||
const FlipperAppPluginDescriptor* js_dialog_ep(void) {
|
||||
return &plugin_descriptor;
|
||||
}
|
||||
36
applications/system/js_app/modules/js_flipper.c
Normal file
36
applications/system/js_app/modules/js_flipper.c
Normal file
@@ -0,0 +1,36 @@
|
||||
#include <core/common_defines.h>
|
||||
#include "../js_modules.h"
|
||||
#include <furi_hal_version.h>
|
||||
#include <power/power_service/power.h>
|
||||
|
||||
static void js_flipper_get_model(struct mjs* mjs) {
|
||||
mjs_val_t ret = mjs_mk_string(mjs, furi_hal_version_get_model_name(), ~0, true);
|
||||
mjs_return(mjs, ret);
|
||||
}
|
||||
|
||||
static void js_flipper_get_name(struct mjs* mjs) {
|
||||
const char* name_str = furi_hal_version_get_name_ptr();
|
||||
if(name_str == NULL) {
|
||||
name_str = "Unknown";
|
||||
}
|
||||
mjs_val_t ret = mjs_mk_string(mjs, name_str, ~0, true);
|
||||
mjs_return(mjs, ret);
|
||||
}
|
||||
|
||||
static void js_flipper_get_battery(struct mjs* mjs) {
|
||||
Power* power = furi_record_open(RECORD_POWER);
|
||||
PowerInfo info;
|
||||
power_get_info(power, &info);
|
||||
furi_record_close(RECORD_POWER);
|
||||
mjs_return(mjs, mjs_mk_number(mjs, info.charge));
|
||||
}
|
||||
|
||||
void* js_flipper_create(struct mjs* mjs, mjs_val_t* object) {
|
||||
mjs_val_t flipper_obj = mjs_mk_object(mjs);
|
||||
mjs_set(mjs, flipper_obj, "getModel", ~0, MJS_MK_FN(js_flipper_get_model));
|
||||
mjs_set(mjs, flipper_obj, "getName", ~0, MJS_MK_FN(js_flipper_get_name));
|
||||
mjs_set(mjs, flipper_obj, "getBatteryCharge", ~0, MJS_MK_FN(js_flipper_get_battery));
|
||||
*object = flipper_obj;
|
||||
|
||||
return (void*)1;
|
||||
}
|
||||
4
applications/system/js_app/modules/js_flipper.h
Normal file
4
applications/system/js_app/modules/js_flipper.h
Normal file
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
#include "../js_thread_i.h"
|
||||
|
||||
void* js_flipper_create(struct mjs* mjs, mjs_val_t* object);
|
||||
109
applications/system/js_app/modules/js_notification.c
Normal file
109
applications/system/js_app/modules/js_notification.c
Normal file
@@ -0,0 +1,109 @@
|
||||
#include <core/common_defines.h>
|
||||
#include "../js_modules.h"
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
static void js_notify(struct mjs* mjs, const NotificationSequence* sequence) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
NotificationApp* notification = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(notification);
|
||||
notification_message(notification, sequence);
|
||||
}
|
||||
|
||||
static void js_notify_success(struct mjs* mjs) {
|
||||
js_notify(mjs, &sequence_success);
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void js_notify_error(struct mjs* mjs) {
|
||||
js_notify(mjs, &sequence_error);
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static const struct {
|
||||
const char* color_name;
|
||||
const NotificationSequence* sequence_short;
|
||||
const NotificationSequence* sequence_long;
|
||||
} led_sequences[] = {
|
||||
{"blue", &sequence_blink_blue_10, &sequence_blink_blue_100},
|
||||
{"red", &sequence_blink_red_10, &sequence_blink_red_100},
|
||||
{"green", &sequence_blink_green_10, &sequence_blink_green_100},
|
||||
{"yellow", &sequence_blink_yellow_10, &sequence_blink_yellow_100},
|
||||
{"cyan", &sequence_blink_cyan_10, &sequence_blink_cyan_100},
|
||||
{"magenta", &sequence_blink_magenta_10, &sequence_blink_magenta_100},
|
||||
};
|
||||
|
||||
static void js_notify_blink(struct mjs* mjs) {
|
||||
const NotificationSequence* sequence = NULL;
|
||||
do {
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
if(num_args != 2) {
|
||||
break;
|
||||
}
|
||||
mjs_val_t color_obj = mjs_arg(mjs, 0);
|
||||
mjs_val_t type_obj = mjs_arg(mjs, 1);
|
||||
if((!mjs_is_string(color_obj)) || (!mjs_is_string(type_obj))) break;
|
||||
|
||||
size_t arg_len = 0;
|
||||
const char* arg_str = mjs_get_string(mjs, &color_obj, &arg_len);
|
||||
if((arg_len == 0) || (arg_str == NULL)) break;
|
||||
|
||||
int32_t color_id = -1;
|
||||
for(size_t i = 0; i < COUNT_OF(led_sequences); i++) {
|
||||
size_t name_len = strlen(led_sequences[i].color_name);
|
||||
if(arg_len != name_len) continue;
|
||||
if(strncmp(arg_str, led_sequences[i].color_name, arg_len) == 0) {
|
||||
color_id = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(color_id == -1) break;
|
||||
|
||||
arg_str = mjs_get_string(mjs, &type_obj, &arg_len);
|
||||
if((arg_len == 0) || (arg_str == NULL)) break;
|
||||
if(strncmp(arg_str, "short", arg_len) == 0) {
|
||||
sequence = led_sequences[color_id].sequence_short;
|
||||
} else if(strncmp(arg_str, "long", arg_len) == 0) {
|
||||
sequence = led_sequences[color_id].sequence_long;
|
||||
}
|
||||
} while(0);
|
||||
|
||||
if(sequence == NULL) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
} else {
|
||||
js_notify(mjs, sequence);
|
||||
}
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void* js_notification_create(struct mjs* mjs, mjs_val_t* object) {
|
||||
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
mjs_val_t notify_obj = mjs_mk_object(mjs);
|
||||
mjs_set(mjs, notify_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, notification));
|
||||
mjs_set(mjs, notify_obj, "success", ~0, MJS_MK_FN(js_notify_success));
|
||||
mjs_set(mjs, notify_obj, "error", ~0, MJS_MK_FN(js_notify_error));
|
||||
mjs_set(mjs, notify_obj, "blink", ~0, MJS_MK_FN(js_notify_blink));
|
||||
*object = notify_obj;
|
||||
|
||||
return notification;
|
||||
}
|
||||
|
||||
static void js_notification_destroy(void* inst) {
|
||||
UNUSED(inst);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
}
|
||||
|
||||
static const JsModuleDescriptor js_notification_desc = {
|
||||
"notification",
|
||||
js_notification_create,
|
||||
js_notification_destroy,
|
||||
};
|
||||
|
||||
static const FlipperAppPluginDescriptor plugin_descriptor = {
|
||||
.appid = PLUGIN_APP_ID,
|
||||
.ep_api_version = PLUGIN_API_VERSION,
|
||||
.entry_point = &js_notification_desc,
|
||||
};
|
||||
|
||||
const FlipperAppPluginDescriptor* js_notification_ep(void) {
|
||||
return &plugin_descriptor;
|
||||
}
|
||||
618
applications/system/js_app/modules/js_serial.c
Normal file
618
applications/system/js_app/modules/js_serial.c
Normal file
@@ -0,0 +1,618 @@
|
||||
#include <core/common_defines.h>
|
||||
#include <furi_hal.h>
|
||||
#include "../js_modules.h"
|
||||
#include <m-array.h>
|
||||
|
||||
#define TAG "js_serial"
|
||||
#define RX_BUF_LEN 2048
|
||||
|
||||
typedef struct {
|
||||
bool setup_done;
|
||||
FuriStreamBuffer* rx_stream;
|
||||
FuriHalSerialHandle* serial_handle;
|
||||
struct mjs* mjs;
|
||||
} JsSerialInst;
|
||||
|
||||
typedef struct {
|
||||
size_t len;
|
||||
char* data;
|
||||
} PatternArrayItem;
|
||||
|
||||
static const struct {
|
||||
const char* name;
|
||||
const FuriHalSerialId value;
|
||||
} serial_channels[] = {
|
||||
{"usart", FuriHalSerialIdUsart},
|
||||
{"lpuart", FuriHalSerialIdLpuart},
|
||||
};
|
||||
|
||||
ARRAY_DEF(PatternArray, PatternArrayItem, M_POD_OPLIST);
|
||||
|
||||
static void
|
||||
js_serial_on_async_rx(FuriHalSerialHandle* handle, FuriHalSerialRxEvent event, void* context) {
|
||||
JsSerialInst* serial = context;
|
||||
furi_assert(serial);
|
||||
|
||||
if(event & FuriHalSerialRxEventData) {
|
||||
uint8_t data = furi_hal_serial_async_rx(handle);
|
||||
furi_stream_buffer_send(serial->rx_stream, &data, 1, 0);
|
||||
js_flags_set(serial->mjs, ThreadEventCustomDataRx);
|
||||
}
|
||||
}
|
||||
|
||||
static void js_serial_setup(struct mjs* mjs) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(serial);
|
||||
|
||||
if(serial->setup_done) {
|
||||
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is already configured");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
bool args_correct = false;
|
||||
FuriHalSerialId serial_id = FuriHalSerialIdMax;
|
||||
uint32_t baudrate = 0;
|
||||
|
||||
do {
|
||||
if(mjs_nargs(mjs) != 2) break;
|
||||
|
||||
mjs_val_t arg = mjs_arg(mjs, 0);
|
||||
if(!mjs_is_string(arg)) break;
|
||||
|
||||
size_t str_len = 0;
|
||||
const char* arg_str = mjs_get_string(mjs, &arg, &str_len);
|
||||
for(size_t i = 0; i < COUNT_OF(serial_channels); i++) {
|
||||
size_t name_len = strlen(serial_channels[i].name);
|
||||
if(str_len != name_len) continue;
|
||||
if(strncmp(arg_str, serial_channels[i].name, str_len) == 0) {
|
||||
serial_id = serial_channels[i].value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(serial_id == FuriHalSerialIdMax) {
|
||||
break;
|
||||
}
|
||||
|
||||
arg = mjs_arg(mjs, 1);
|
||||
if(!mjs_is_number(arg)) break;
|
||||
baudrate = mjs_get_int32(mjs, arg);
|
||||
|
||||
args_correct = true;
|
||||
} while(0);
|
||||
|
||||
if(!args_correct) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
serial->rx_stream = furi_stream_buffer_alloc(RX_BUF_LEN, 1);
|
||||
serial->serial_handle = furi_hal_serial_control_acquire(serial_id);
|
||||
if(serial->serial_handle) {
|
||||
furi_hal_serial_init(serial->serial_handle, baudrate);
|
||||
furi_hal_serial_async_rx_start(
|
||||
serial->serial_handle, js_serial_on_async_rx, serial, false);
|
||||
serial->setup_done = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void js_serial_write(struct mjs* mjs) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(serial);
|
||||
if(!serial->setup_done) {
|
||||
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
bool args_correct = true;
|
||||
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
for(size_t i = 0; i < num_args; i++) {
|
||||
mjs_val_t arg = mjs_arg(mjs, i);
|
||||
if(mjs_is_string(arg)) {
|
||||
size_t str_len = 0;
|
||||
const char* arg_str = mjs_get_string(mjs, &arg, &str_len);
|
||||
if((str_len == 0) || (arg_str == NULL)) {
|
||||
args_correct = false;
|
||||
break;
|
||||
}
|
||||
furi_hal_serial_tx(serial->serial_handle, (uint8_t*)arg_str, str_len);
|
||||
} else if(mjs_is_number(arg)) {
|
||||
uint32_t byte_val = mjs_get_int32(mjs, arg);
|
||||
if(byte_val > 0xFF) {
|
||||
args_correct = false;
|
||||
break;
|
||||
}
|
||||
furi_hal_serial_tx(serial->serial_handle, (uint8_t*)&byte_val, 1);
|
||||
} else if(mjs_is_array(arg)) {
|
||||
size_t array_len = mjs_array_length(mjs, arg);
|
||||
for(size_t i = 0; i < array_len; i++) {
|
||||
mjs_val_t array_arg = mjs_array_get(mjs, arg, i);
|
||||
if(!mjs_is_number(array_arg)) {
|
||||
args_correct = false;
|
||||
break;
|
||||
}
|
||||
uint32_t byte_val = mjs_get_int32(mjs, array_arg);
|
||||
if(byte_val > 0xFF) {
|
||||
args_correct = false;
|
||||
break;
|
||||
}
|
||||
furi_hal_serial_tx(serial->serial_handle, (uint8_t*)&byte_val, 1);
|
||||
}
|
||||
if(!args_correct) {
|
||||
break;
|
||||
}
|
||||
} else if(mjs_is_typed_array(arg)) {
|
||||
mjs_val_t array_buf = arg;
|
||||
if(mjs_is_data_view(arg)) {
|
||||
array_buf = mjs_dataview_get_buf(mjs, arg);
|
||||
}
|
||||
size_t len = 0;
|
||||
char* buf = mjs_array_buf_get_ptr(mjs, array_buf, &len);
|
||||
furi_hal_serial_tx(serial->serial_handle, (uint8_t*)buf, len);
|
||||
} else {
|
||||
args_correct = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!args_correct) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
}
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static size_t js_serial_receive(JsSerialInst* serial, char* buf, size_t len, uint32_t timeout) {
|
||||
size_t bytes_read = 0;
|
||||
while(1) {
|
||||
uint32_t flags = ThreadEventCustomDataRx;
|
||||
if(furi_stream_buffer_is_empty(serial->rx_stream)) {
|
||||
flags = js_flags_wait(serial->mjs, ThreadEventCustomDataRx, timeout);
|
||||
}
|
||||
if(flags == 0) { // Timeout
|
||||
break;
|
||||
} else if(flags & ThreadEventStop) { // Exit flag
|
||||
bytes_read = 0;
|
||||
break;
|
||||
} else if(flags & ThreadEventCustomDataRx) { // New data received
|
||||
size_t rx_len = furi_stream_buffer_receive(
|
||||
serial->rx_stream, &buf[bytes_read], len - bytes_read, 0);
|
||||
bytes_read += rx_len;
|
||||
if(bytes_read == len) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
static void js_serial_read(struct mjs* mjs) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(serial);
|
||||
if(!serial->setup_done) {
|
||||
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t read_len = 0;
|
||||
uint32_t timeout = FuriWaitForever;
|
||||
|
||||
do {
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
if(num_args == 1) {
|
||||
mjs_val_t arg = mjs_arg(mjs, 0);
|
||||
if(!mjs_is_number(arg)) {
|
||||
break;
|
||||
}
|
||||
read_len = mjs_get_int32(mjs, arg);
|
||||
} else if(num_args == 2) {
|
||||
mjs_val_t len_arg = mjs_arg(mjs, 0);
|
||||
mjs_val_t timeout_arg = mjs_arg(mjs, 1);
|
||||
if((!mjs_is_number(len_arg)) || (!mjs_is_number(timeout_arg))) {
|
||||
break;
|
||||
}
|
||||
read_len = mjs_get_int32(mjs, len_arg);
|
||||
timeout = mjs_get_int32(mjs, timeout_arg);
|
||||
}
|
||||
} while(0);
|
||||
|
||||
if(read_len == 0) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
char* read_buf = malloc(read_len);
|
||||
size_t bytes_read = js_serial_receive(serial, read_buf, read_len, timeout);
|
||||
|
||||
mjs_val_t return_obj = MJS_UNDEFINED;
|
||||
if(bytes_read > 0) {
|
||||
return_obj = mjs_mk_string(mjs, read_buf, bytes_read, true);
|
||||
}
|
||||
mjs_return(mjs, return_obj);
|
||||
free(read_buf);
|
||||
}
|
||||
|
||||
static void js_serial_readln(struct mjs* mjs) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(serial);
|
||||
if(!serial->setup_done) {
|
||||
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
bool args_correct = false;
|
||||
uint32_t timeout = FuriWaitForever;
|
||||
|
||||
do {
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
if(num_args > 1) {
|
||||
break;
|
||||
} else if(num_args == 1) {
|
||||
mjs_val_t arg = mjs_arg(mjs, 0);
|
||||
if(!mjs_is_number(arg)) {
|
||||
break;
|
||||
}
|
||||
timeout = mjs_get_int32(mjs, arg);
|
||||
}
|
||||
args_correct = true;
|
||||
} while(0);
|
||||
|
||||
if(!args_correct) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
FuriString* rx_buf = furi_string_alloc();
|
||||
size_t bytes_read = 0;
|
||||
char read_char = 0;
|
||||
|
||||
while(1) {
|
||||
size_t read_len = js_serial_receive(serial, &read_char, 1, timeout);
|
||||
if(read_len != 1) {
|
||||
break;
|
||||
}
|
||||
if((read_char == '\r') || (read_char == '\n')) {
|
||||
break;
|
||||
} else {
|
||||
furi_string_push_back(rx_buf, read_char);
|
||||
bytes_read++;
|
||||
}
|
||||
}
|
||||
|
||||
mjs_val_t return_obj = MJS_UNDEFINED;
|
||||
if(bytes_read > 0) {
|
||||
return_obj = mjs_mk_string(mjs, furi_string_get_cstr(rx_buf), bytes_read, true);
|
||||
}
|
||||
mjs_return(mjs, return_obj);
|
||||
furi_string_free(rx_buf);
|
||||
}
|
||||
|
||||
static void js_serial_read_bytes(struct mjs* mjs) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(serial);
|
||||
if(!serial->setup_done) {
|
||||
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t read_len = 0;
|
||||
uint32_t timeout = FuriWaitForever;
|
||||
|
||||
do {
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
if(num_args == 1) {
|
||||
mjs_val_t arg = mjs_arg(mjs, 0);
|
||||
if(!mjs_is_number(arg)) {
|
||||
break;
|
||||
}
|
||||
read_len = mjs_get_int32(mjs, arg);
|
||||
} else if(num_args == 2) {
|
||||
mjs_val_t len_arg = mjs_arg(mjs, 0);
|
||||
mjs_val_t timeout_arg = mjs_arg(mjs, 1);
|
||||
if((!mjs_is_number(len_arg)) || (!mjs_is_number(timeout_arg))) {
|
||||
break;
|
||||
}
|
||||
read_len = mjs_get_int32(mjs, len_arg);
|
||||
timeout = mjs_get_int32(mjs, timeout_arg);
|
||||
}
|
||||
} while(0);
|
||||
|
||||
if(read_len == 0) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
char* read_buf = malloc(read_len);
|
||||
size_t bytes_read = js_serial_receive(serial, read_buf, read_len, timeout);
|
||||
|
||||
mjs_val_t return_obj = MJS_UNDEFINED;
|
||||
if(bytes_read > 0) {
|
||||
return_obj = mjs_mk_array_buf(mjs, read_buf, bytes_read);
|
||||
}
|
||||
mjs_return(mjs, return_obj);
|
||||
free(read_buf);
|
||||
}
|
||||
|
||||
static bool
|
||||
js_serial_expect_parse_string(struct mjs* mjs, mjs_val_t arg, PatternArray_t patterns) {
|
||||
size_t str_len = 0;
|
||||
const char* arg_str = mjs_get_string(mjs, &arg, &str_len);
|
||||
if((str_len == 0) || (arg_str == NULL)) {
|
||||
return false;
|
||||
}
|
||||
PatternArrayItem* item = PatternArray_push_new(patterns);
|
||||
item->data = malloc(str_len + 1);
|
||||
memcpy(item->data, arg_str, str_len);
|
||||
item->len = str_len;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool js_serial_expect_parse_array(struct mjs* mjs, mjs_val_t arg, PatternArray_t patterns) {
|
||||
size_t array_len = mjs_array_length(mjs, arg);
|
||||
if(array_len == 0) {
|
||||
return false;
|
||||
}
|
||||
char* array_data = malloc(array_len + 1);
|
||||
|
||||
for(size_t i = 0; i < array_len; i++) {
|
||||
mjs_val_t array_arg = mjs_array_get(mjs, arg, i);
|
||||
if(!mjs_is_number(array_arg)) {
|
||||
free(array_data);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t byte_val = mjs_get_int32(mjs, array_arg);
|
||||
if(byte_val > 0xFF) {
|
||||
free(array_data);
|
||||
return false;
|
||||
}
|
||||
array_data[i] = byte_val;
|
||||
}
|
||||
|
||||
PatternArrayItem* item = PatternArray_push_new(patterns);
|
||||
item->data = array_data;
|
||||
item->len = array_len;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
js_serial_expect_parse_args(struct mjs* mjs, PatternArray_t patterns, uint32_t* timeout) {
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
if(num_args == 2) {
|
||||
mjs_val_t timeout_arg = mjs_arg(mjs, 1);
|
||||
if(!mjs_is_number(timeout_arg)) {
|
||||
return false;
|
||||
}
|
||||
*timeout = mjs_get_int32(mjs, timeout_arg);
|
||||
} else if(num_args != 1) {
|
||||
return false;
|
||||
}
|
||||
mjs_val_t patterns_arg = mjs_arg(mjs, 0);
|
||||
if(mjs_is_string(patterns_arg)) { // Single string pattern
|
||||
if(!js_serial_expect_parse_string(mjs, patterns_arg, patterns)) {
|
||||
return false;
|
||||
}
|
||||
} else if(mjs_is_array(patterns_arg)) {
|
||||
size_t array_len = mjs_array_length(mjs, patterns_arg);
|
||||
if(array_len == 0) {
|
||||
return false;
|
||||
}
|
||||
mjs_val_t array_arg = mjs_array_get(mjs, patterns_arg, 0);
|
||||
|
||||
if(mjs_is_number(array_arg)) { // Binary array pattern
|
||||
if(!js_serial_expect_parse_array(mjs, patterns_arg, patterns)) {
|
||||
return false;
|
||||
}
|
||||
} else if((mjs_is_string(array_arg)) || (mjs_is_array(array_arg))) { // Multiple patterns
|
||||
for(size_t i = 0; i < array_len; i++) {
|
||||
mjs_val_t arg = mjs_array_get(mjs, patterns_arg, i);
|
||||
|
||||
if(mjs_is_string(arg)) {
|
||||
if(!js_serial_expect_parse_string(mjs, arg, patterns)) {
|
||||
return false;
|
||||
}
|
||||
} else if(mjs_is_array(arg)) {
|
||||
if(!js_serial_expect_parse_array(mjs, arg, patterns)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int32_t js_serial_expect_check_pattern_start(
|
||||
PatternArray_t patterns,
|
||||
char value,
|
||||
int32_t pattern_last) {
|
||||
size_t array_len = PatternArray_size(patterns);
|
||||
if((pattern_last + 1) >= (int32_t)array_len) {
|
||||
return (-1);
|
||||
}
|
||||
for(size_t i = pattern_last + 1; i < array_len; i++) {
|
||||
if(PatternArray_get(patterns, i)->data[0] == value) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return (-1);
|
||||
}
|
||||
|
||||
static void js_serial_expect(struct mjs* mjs) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(serial);
|
||||
if(!serial->setup_done) {
|
||||
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t timeout = FuriWaitForever;
|
||||
PatternArray_t patterns;
|
||||
PatternArray_it_t it;
|
||||
PatternArray_init(patterns);
|
||||
|
||||
if(!js_serial_expect_parse_args(mjs, patterns, &timeout)) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
for(PatternArray_it(it, patterns); !PatternArray_end_p(it); PatternArray_next(it)) {
|
||||
const PatternArrayItem* item = PatternArray_cref(it);
|
||||
free(item->data);
|
||||
}
|
||||
PatternArray_clear(patterns);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t pattern_len_max = 0;
|
||||
for(PatternArray_it(it, patterns); !PatternArray_end_p(it); PatternArray_next(it)) {
|
||||
const PatternArrayItem* item = PatternArray_cref(it);
|
||||
if(item->len > pattern_len_max) {
|
||||
pattern_len_max = item->len;
|
||||
}
|
||||
}
|
||||
|
||||
char* compare_buf = malloc(pattern_len_max);
|
||||
int32_t pattern_found = -1;
|
||||
int32_t pattern_candidate = -1;
|
||||
size_t buf_len = 0;
|
||||
bool is_timeout = false;
|
||||
|
||||
while(1) {
|
||||
if(buf_len == 0) {
|
||||
// Empty buffer - read by 1 byte to find pattern start
|
||||
size_t bytes_read = js_serial_receive(serial, &compare_buf[0], 1, timeout);
|
||||
if(bytes_read != 1) {
|
||||
is_timeout = true;
|
||||
break;
|
||||
}
|
||||
pattern_candidate = js_serial_expect_check_pattern_start(patterns, compare_buf[0], -1);
|
||||
if(pattern_candidate == -1) {
|
||||
continue;
|
||||
}
|
||||
buf_len = 1;
|
||||
}
|
||||
assert(pattern_candidate >= 0);
|
||||
|
||||
// Read next and try to find pattern match
|
||||
PatternArrayItem* pattern_cur = PatternArray_get(patterns, pattern_candidate);
|
||||
pattern_found = pattern_candidate;
|
||||
for(size_t i = 0; i < pattern_cur->len; i++) {
|
||||
if(i >= buf_len) {
|
||||
size_t bytes_read = js_serial_receive(serial, &compare_buf[i], 1, timeout);
|
||||
if(bytes_read != 1) {
|
||||
is_timeout = true;
|
||||
break;
|
||||
}
|
||||
buf_len++;
|
||||
}
|
||||
if(compare_buf[i] != pattern_cur->data[i]) {
|
||||
pattern_found = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if((is_timeout) || (pattern_found >= 0)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Search other patterns with the same start char
|
||||
pattern_candidate =
|
||||
js_serial_expect_check_pattern_start(patterns, compare_buf[0], pattern_candidate);
|
||||
if(pattern_candidate >= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Look for another pattern start
|
||||
for(size_t i = 1; i < buf_len; i++) {
|
||||
pattern_candidate = js_serial_expect_check_pattern_start(patterns, compare_buf[i], -1);
|
||||
if(pattern_candidate >= 0) {
|
||||
memmove(&compare_buf[0], &compare_buf[i], buf_len - i);
|
||||
buf_len -= i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(pattern_candidate >= 0) {
|
||||
continue;
|
||||
}
|
||||
// Nothing found - reset buffer
|
||||
buf_len = 0;
|
||||
}
|
||||
|
||||
if(is_timeout) {
|
||||
FURI_LOG_W(TAG, "Expect: timeout");
|
||||
}
|
||||
|
||||
for(PatternArray_it(it, patterns); !PatternArray_end_p(it); PatternArray_next(it)) {
|
||||
const PatternArrayItem* item = PatternArray_cref(it);
|
||||
free(item->data);
|
||||
}
|
||||
PatternArray_clear(patterns);
|
||||
free(compare_buf);
|
||||
|
||||
if(pattern_found >= 0) {
|
||||
mjs_return(mjs, mjs_mk_number(mjs, pattern_found));
|
||||
} else {
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
}
|
||||
|
||||
static void* js_serial_create(struct mjs* mjs, mjs_val_t* object) {
|
||||
JsSerialInst* js_serial = malloc(sizeof(JsSerialInst));
|
||||
js_serial->mjs = mjs;
|
||||
mjs_val_t serial_obj = mjs_mk_object(mjs);
|
||||
mjs_set(mjs, serial_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, js_serial));
|
||||
mjs_set(mjs, serial_obj, "setup", ~0, MJS_MK_FN(js_serial_setup));
|
||||
mjs_set(mjs, serial_obj, "write", ~0, MJS_MK_FN(js_serial_write));
|
||||
mjs_set(mjs, serial_obj, "read", ~0, MJS_MK_FN(js_serial_read));
|
||||
mjs_set(mjs, serial_obj, "readln", ~0, MJS_MK_FN(js_serial_readln));
|
||||
mjs_set(mjs, serial_obj, "readBytes", ~0, MJS_MK_FN(js_serial_read_bytes));
|
||||
mjs_set(mjs, serial_obj, "expect", ~0, MJS_MK_FN(js_serial_expect));
|
||||
*object = serial_obj;
|
||||
|
||||
return js_serial;
|
||||
}
|
||||
|
||||
static void js_serial_destroy(void* inst) {
|
||||
JsSerialInst* js_serial = inst;
|
||||
if(js_serial->setup_done) {
|
||||
furi_hal_serial_async_rx_stop(js_serial->serial_handle);
|
||||
furi_hal_serial_deinit(js_serial->serial_handle);
|
||||
furi_hal_serial_control_release(js_serial->serial_handle);
|
||||
js_serial->serial_handle = NULL;
|
||||
}
|
||||
|
||||
furi_stream_buffer_free(js_serial->rx_stream);
|
||||
free(js_serial);
|
||||
}
|
||||
|
||||
static const JsModuleDescriptor js_serial_desc = {
|
||||
"serial",
|
||||
js_serial_create,
|
||||
js_serial_destroy,
|
||||
};
|
||||
|
||||
static const FlipperAppPluginDescriptor plugin_descriptor = {
|
||||
.appid = PLUGIN_APP_ID,
|
||||
.ep_api_version = PLUGIN_API_VERSION,
|
||||
.entry_point = &js_serial_desc,
|
||||
};
|
||||
|
||||
const FlipperAppPluginDescriptor* js_serial_ep(void) {
|
||||
return &plugin_descriptor;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <flipper_application/api_hashtable/api_hashtable.h>
|
||||
|
||||
/*
|
||||
* Resolver interface with private application's symbols.
|
||||
* Implementation is contained in app_api_table.c
|
||||
*/
|
||||
extern const ElfApiInterface* const application_api_interface;
|
||||
27
applications/system/js_app/plugin_api/app_api_table.cpp
Normal file
27
applications/system/js_app/plugin_api/app_api_table.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#include <flipper_application/api_hashtable/api_hashtable.h>
|
||||
#include <flipper_application/api_hashtable/compilesort.hpp>
|
||||
|
||||
/*
|
||||
* This file contains an implementation of a symbol table
|
||||
* with private app's symbols. It is used by composite API resolver
|
||||
* to load plugins that use internal application's APIs.
|
||||
*/
|
||||
#include "app_api_table_i.h"
|
||||
|
||||
static_assert(!has_hash_collisions(app_api_table), "Detected API method hash collision!");
|
||||
|
||||
constexpr HashtableApiInterface applicaton_hashtable_api_interface{
|
||||
{
|
||||
.api_version_major = 0,
|
||||
.api_version_minor = 0,
|
||||
/* generic resolver using pre-sorted array */
|
||||
.resolver_callback = &elf_resolve_from_hashtable,
|
||||
},
|
||||
/* pointers to application's API table boundaries */
|
||||
.table_cbegin = app_api_table.cbegin(),
|
||||
.table_cend = app_api_table.cend(),
|
||||
};
|
||||
|
||||
/* Casting to generic resolver to use in Composite API resolver */
|
||||
extern "C" const ElfApiInterface* const application_api_interface =
|
||||
&applicaton_hashtable_api_interface;
|
||||
11
applications/system/js_app/plugin_api/app_api_table_i.h
Normal file
11
applications/system/js_app/plugin_api/app_api_table_i.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#include <assets_icons.h>
|
||||
#include "js_plugin_api.h"
|
||||
/*
|
||||
* A list of app's private functions and objects to expose for plugins.
|
||||
* It is used to generate a table of symbols for import resolver to use.
|
||||
* TBD: automatically generate this table from app's header files
|
||||
*/
|
||||
static constexpr auto app_api_table = sort(create_array_t<sym_entry>(
|
||||
API_METHOD(js_delay_with_flags, bool, (struct mjs*, uint32_t)),
|
||||
API_METHOD(js_flags_set, void, (struct mjs*, uint32_t)),
|
||||
API_METHOD(js_flags_wait, uint32_t, (struct mjs*, uint32_t, uint32_t))));
|
||||
18
applications/system/js_app/plugin_api/js_plugin_api.h
Normal file
18
applications/system/js_app/plugin_api/js_plugin_api.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <mjs_core_public.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
bool js_delay_with_flags(struct mjs* mjs, uint32_t time);
|
||||
|
||||
void js_flags_set(struct mjs* mjs, uint32_t flags);
|
||||
|
||||
uint32_t js_flags_wait(struct mjs* mjs, uint32_t flags, uint32_t timeout);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
43
applications/system/js_app/views/console_font.h
Normal file
43
applications/system/js_app/views/console_font.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/*
|
||||
Fontname: -misc-spleen-medium-r-normal--8-80-72-72-C-50-ISO10646-1
|
||||
Copyright: Copyright (c) 2018-2022, Frederic Cambus
|
||||
Glyphs: 96/472
|
||||
BBX Build Mode: 2
|
||||
*/
|
||||
static const uint8_t u8g2_font_spleen5x8_mr[] =
|
||||
"`\2\3\2\3\4\1\1\4\5\10\0\377\6\377\7\377\1\77\2\217\3\325 \6\305\372\274\2!\10\305"
|
||||
"Zaw(\7\42\12\305:\245$JrV\0#\15\305\332I\62(\245$\31\224\62\0$\13\305Z"
|
||||
"\331R\23\65e\214\0%\15\305zI\224\24\263\60)%!\0&\16\305ZY\22%\221\224$R\244"
|
||||
"\244\0'\7\305Za\235\31(\10\305z\215\255\25\0)\10\305:i\261\255\6*\13\305\372X\24I"
|
||||
"C$\225\1+\12\305\372h\30\15R\230\3,\10\305\372\314a\226\1-\10\305\372\344!'\1.\7"
|
||||
"\305\372\34s\0/\13\305za\26fa\26\206\0\60\12\305\332R%\261\224\42\35\61\10\305\372\231\330"
|
||||
"\66\3\62\12\305\332R\61\222\302!\6\63\12\305\332R-M\242H\7\64\14\305\272a\22%\321\220\205"
|
||||
"\71\0\65\12\305\272C\22\256a\262\3\66\12\305\332R\70U\242H\7\67\13\305\272C\22\205Y\61G"
|
||||
"\0\70\12\305\332RI\252D\221\16\71\12\305\332R%\212\306H\7:\10\305\372\264\34\317\1;\11\305"
|
||||
"\372\264\34\12\263\14<\11\305\372HVL\313\0=\11\305\372\224!\36r\20>\11\305\332i\61\253#"
|
||||
"\0\77\12\305:R\61\253C\71\2@\13\305\332R%Q\22%\235\1A\14\305\332R%J\206$J"
|
||||
"\242\30B\12\305\272Se\252D\311\16C\10\305\332K\330:\3D\14\305\272S%J\242$Jv\0"
|
||||
"E\11\305\332K\70\205\351\14F\12\305\332K\30Na\16\1G\14\305\332K\230(Q\22E\63\0H"
|
||||
"\16\305\272Q\22%C\22%Q\22\305\0I\10\305\332[\330\66\3J\11\305\332[\330\244#\0K\14"
|
||||
"\305\272Q\22%S%J\242\30L\7\305\272a\327\31M\16\305\272Q\62$C\22%Q\22\305\0N"
|
||||
"\15\305\272Q\242$JEI\224(\6O\14\305\332R%J\242$\212t\0P\13\305\272S%J\246"
|
||||
"\60\207\0Q\14\305\332R%J\242$\212D\5R\13\305\272S%J\246J\24\3S\11\305\332K\252"
|
||||
"\206\311\16T\10\305\272\203\24v\7U\15\305\272Q\22%Q\22%Q\64\3V\14\305\272Q\22%Q"
|
||||
"\22E\232\16W\16\305\272Q\22%Q\62$C\22\305\0X\14\305\272Q\22E\232T\211b\0Y\14"
|
||||
"\305\272Q\22%Q\64&;\0Z\12\305\272C\230\65\16\61\0[\10\305:S\330\343\2\134\13\305\32"
|
||||
"a\32\246a\32&\0]\10\305:c\237\26\0^\11\305\372YR\313\311\0_\7\305\372\334\207\4`"
|
||||
"\7\305:i\316\21a\12\305\372\240\32-Q\64\3b\14\305\32a\70U\242$Jv\0c\11\305\372"
|
||||
"\340\22Vg\0d\14\305za\264DI\224D\321\14e\13\305\372\340\22%C\222\316\0f\12\305Z"
|
||||
"R\230ma\35\1g\14\305\372\340\22%Q\244&\23\0h\14\305\32a\70U\242$J\242\30i\11"
|
||||
"\305\372\71\42\26e\0j\11\305\372\71\24\66i\0k\13\305\32a))iIT\6l\10\305:a"
|
||||
"\257\62\0m\15\305\372X\224\14\311\220DI\24\3n\14\305\372\330T\211\222(\211b\0o\13\305\372"
|
||||
"\240T\211\222(\322\1p\13\305\372\330T\211\222)\14\1q\13\305\372\340\22%Q\64V\0r\12\305"
|
||||
"\372\340\22%a\35\2s\11\305\372\340\222\252\311\16t\11\305:a\266\205U\31u\14\305\372X\224D"
|
||||
"I\224D\321\14v\14\305\372X\224DI\24i:\0w\15\305\372X\224D\311\220\14I\24\3x\13"
|
||||
"\305\372X\24iR%\212\1y\14\305\372X\224DI\24\215\311\4z\12\305\372\330\20f\265!\6{"
|
||||
"\12\305ZR\230\31\253\12\0|\7\305Za\77\1}\13\305\32j\30jZ\30i\0~\11\305\372\244"
|
||||
"H\321I\0\177\6\305\372\274\2\0\0\0\4\377\377\0";
|
||||
164
applications/system/js_app/views/console_view.c
Normal file
164
applications/system/js_app/views/console_view.c
Normal file
@@ -0,0 +1,164 @@
|
||||
#include "../js_app_i.h"
|
||||
#include "console_font.h"
|
||||
|
||||
#define CONSOLE_LINES 8
|
||||
#define CONSOLE_CHAR_W 5
|
||||
#define LINE_BREAKS_MAX 3
|
||||
#define LINE_LEN_MAX (128 / CONSOLE_CHAR_W)
|
||||
|
||||
struct JsConsoleView {
|
||||
View* view;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
FuriString* text[CONSOLE_LINES];
|
||||
} JsConsoleViewModel;
|
||||
|
||||
static void console_view_draw_callback(Canvas* canvas, void* _model) {
|
||||
JsConsoleViewModel* model = _model;
|
||||
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_set_custom_u8g2_font(canvas, u8g2_font_spleen5x8_mr);
|
||||
uint8_t line_h = canvas_current_font_height(canvas);
|
||||
|
||||
for(size_t i = 0; i < CONSOLE_LINES; i++) {
|
||||
canvas_draw_str(canvas, 0, (i + 1) * line_h - 1, furi_string_get_cstr(model->text[i]));
|
||||
if(furi_string_size(model->text[i]) > LINE_LEN_MAX) {
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str(canvas, 128 - 7, (i + 1) * line_h - 1, "...");
|
||||
canvas_set_custom_u8g2_font(canvas, u8g2_font_spleen5x8_mr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool console_view_input_callback(InputEvent* event, void* context) {
|
||||
UNUSED(event);
|
||||
UNUSED(context);
|
||||
return false;
|
||||
}
|
||||
|
||||
void console_view_push_line(JsConsoleView* console_view, const char* text, bool line_trimmed) {
|
||||
with_view_model(
|
||||
console_view->view,
|
||||
JsConsoleViewModel * model,
|
||||
{
|
||||
FuriString* str_temp = model->text[0];
|
||||
for(size_t i = 0; i < CONSOLE_LINES - 1; i++) {
|
||||
model->text[i] = model->text[i + 1];
|
||||
}
|
||||
if(!line_trimmed) {
|
||||
furi_string_printf(str_temp, "%.*s", LINE_LEN_MAX, text);
|
||||
} else {
|
||||
// Leave some space for dots
|
||||
furi_string_printf(str_temp, "%.*s ", LINE_LEN_MAX - 1, text);
|
||||
}
|
||||
model->text[CONSOLE_LINES - 1] = str_temp;
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
void console_view_print(JsConsoleView* console_view, const char* text) {
|
||||
char line_buf[LINE_LEN_MAX + 1];
|
||||
uint8_t line_buf_cnt = 0;
|
||||
uint8_t utf8_bytes_left = 0;
|
||||
uint8_t line_break_cnt = 0;
|
||||
bool line_trim = false;
|
||||
|
||||
for(size_t i = 0; i < strlen(text); i++) {
|
||||
if(text[i] & 0x80) { // UTF8 or another non-ascii character byte
|
||||
if(utf8_bytes_left > 0) {
|
||||
utf8_bytes_left--;
|
||||
if(utf8_bytes_left == 0) {
|
||||
line_buf[line_buf_cnt++] = '?';
|
||||
}
|
||||
} else {
|
||||
if((text[i] & 0xE0) == 0xC0) {
|
||||
utf8_bytes_left = 1;
|
||||
} else if((text[i] & 0xF0) == 0xE0) {
|
||||
utf8_bytes_left = 2;
|
||||
} else if((text[i] & 0xF8) == 0xF0) {
|
||||
utf8_bytes_left = 3;
|
||||
} else {
|
||||
line_buf[line_buf_cnt++] = '?';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(utf8_bytes_left > 0) {
|
||||
utf8_bytes_left = 0;
|
||||
line_buf[line_buf_cnt++] = '?';
|
||||
if(line_buf_cnt >= LINE_LEN_MAX) {
|
||||
line_break_cnt++;
|
||||
if(line_break_cnt >= LINE_BREAKS_MAX) {
|
||||
line_trim = true;
|
||||
break;
|
||||
}
|
||||
line_buf[line_buf_cnt] = '\0';
|
||||
console_view_push_line(console_view, line_buf, false);
|
||||
line_buf_cnt = 1;
|
||||
line_buf[0] = ' ';
|
||||
}
|
||||
}
|
||||
|
||||
if(text[i] == '\n') {
|
||||
line_buf[line_buf_cnt] = '\0';
|
||||
line_buf_cnt = 0;
|
||||
console_view_push_line(console_view, line_buf, false);
|
||||
} else {
|
||||
line_buf[line_buf_cnt++] = text[i];
|
||||
}
|
||||
|
||||
if(line_buf_cnt >= LINE_LEN_MAX) {
|
||||
line_break_cnt++;
|
||||
if(line_break_cnt >= LINE_BREAKS_MAX) {
|
||||
line_trim = true;
|
||||
break;
|
||||
}
|
||||
line_buf[line_buf_cnt] = '\0';
|
||||
console_view_push_line(console_view, line_buf, false);
|
||||
line_buf_cnt = 1;
|
||||
line_buf[0] = ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
if(line_buf_cnt > 0) {
|
||||
line_buf[line_buf_cnt] = '\0';
|
||||
console_view_push_line(console_view, line_buf, line_trim);
|
||||
}
|
||||
}
|
||||
|
||||
JsConsoleView* console_view_alloc(void) {
|
||||
JsConsoleView* console_view = malloc(sizeof(JsConsoleView));
|
||||
console_view->view = view_alloc();
|
||||
view_set_draw_callback(console_view->view, console_view_draw_callback);
|
||||
view_set_input_callback(console_view->view, console_view_input_callback);
|
||||
view_allocate_model(console_view->view, ViewModelTypeLocking, sizeof(JsConsoleViewModel));
|
||||
|
||||
with_view_model(
|
||||
console_view->view,
|
||||
JsConsoleViewModel * model,
|
||||
{
|
||||
for(size_t i = 0; i < CONSOLE_LINES; i++) {
|
||||
model->text[i] = furi_string_alloc();
|
||||
}
|
||||
},
|
||||
true);
|
||||
return console_view;
|
||||
}
|
||||
|
||||
void console_view_free(JsConsoleView* console_view) {
|
||||
with_view_model(
|
||||
console_view->view,
|
||||
JsConsoleViewModel * model,
|
||||
{
|
||||
for(size_t i = 0; i < CONSOLE_LINES; i++) {
|
||||
furi_string_free(model->text[i]);
|
||||
}
|
||||
},
|
||||
false);
|
||||
view_free(console_view->view);
|
||||
free(console_view);
|
||||
}
|
||||
|
||||
View* console_view_get_view(JsConsoleView* console_view) {
|
||||
return console_view->view;
|
||||
}
|
||||
13
applications/system/js_app/views/console_view.h
Normal file
13
applications/system/js_app/views/console_view.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
typedef struct JsConsoleView JsConsoleView;
|
||||
|
||||
JsConsoleView* console_view_alloc(void);
|
||||
|
||||
void console_view_free(JsConsoleView* console_view);
|
||||
|
||||
View* console_view_get_view(JsConsoleView* console_view);
|
||||
|
||||
void console_view_print(JsConsoleView* console_view, const char* text);
|
||||
BIN
assets/icons/Archive/js_script_10px.png
Normal file
BIN
assets/icons/Archive/js_script_10px.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
@@ -29,6 +29,13 @@ SEED -> Your seed from the remote button you got earlier
|
||||
8. Flipper will act as new remote, press Send button couple times near the receiver to register new remote
|
||||
9. Done!
|
||||
|
||||
## Dea Mio
|
||||
1. Create new remote with randomly generated serial: Go to SubGHz -> Add Manually -> Dea Mio 433Mhz
|
||||
2. Open your new remote file
|
||||
3. Right arrow button on the flipper simulates press of hidden button in original remote
|
||||
4. Send button simulates one of basic buttons of the remote, can be programmed into the receiver
|
||||
5. Follow manufacturer instructions on new remotes programming
|
||||
|
||||
## AN-Motors AT4
|
||||
|
||||
**This instruction for older boards, if your has no** `Learn` **button but has buttons** `F`, `CL`, `+`, `-` **read instruction from Alutech AT4N**
|
||||
|
||||
@@ -38,7 +38,8 @@ COPRO_STACK_ADDR = "0x0"
|
||||
COPRO_STACK_BIN_DIR = posixpath.join(COPRO_CUBE_DIR, "firmware")
|
||||
|
||||
# Supported toolchain versions
|
||||
FBT_TOOLCHAIN_VERSIONS = (" 10.3.",)
|
||||
# Also specify in scripts/ufbt/SConstruct
|
||||
FBT_TOOLCHAIN_VERSIONS = (" 12.3.", " 13.2.")
|
||||
|
||||
OPENOCD_OPTS = [
|
||||
"-f",
|
||||
|
||||
@@ -13,6 +13,10 @@ extern "C" {
|
||||
#define FURI_WARN_UNUSED __attribute__((warn_unused_result))
|
||||
#endif
|
||||
|
||||
#ifndef FURI_DEPRECATED
|
||||
#define FURI_DEPRECATED __attribute__((deprecated))
|
||||
#endif
|
||||
|
||||
#ifndef FURI_WEAK
|
||||
#define FURI_WEAK __attribute__((weak))
|
||||
#endif
|
||||
|
||||
@@ -27,18 +27,19 @@
|
||||
*tmp_x; \
|
||||
})
|
||||
#define FURI_CONST_ASSIGN(x, y) \
|
||||
_Generic((x), signed char \
|
||||
: FURI_CONST_ASSIGN_(signed char, x, y), unsigned char \
|
||||
: FURI_CONST_ASSIGN_(unsigned char, x, y), short \
|
||||
: FURI_CONST_ASSIGN_(short, x, y), unsigned short \
|
||||
: FURI_CONST_ASSIGN_(unsigned short, x, y), int \
|
||||
: FURI_CONST_ASSIGN_(int, x, y), unsigned \
|
||||
: FURI_CONST_ASSIGN_(unsigned, x, y), long \
|
||||
: FURI_CONST_ASSIGN_(long, x, y), unsigned long \
|
||||
: FURI_CONST_ASSIGN_(unsigned long, x, y), long long \
|
||||
: FURI_CONST_ASSIGN_(long long, x, y), unsigned long long \
|
||||
: FURI_CONST_ASSIGN_(unsigned long long, x, y), float \
|
||||
: FURI_CONST_ASSIGN_(float, x, y), double \
|
||||
: FURI_CONST_ASSIGN_(double, x, y), long double \
|
||||
: FURI_CONST_ASSIGN_(long double, x, y))
|
||||
_Generic( \
|
||||
(x), \
|
||||
signed char: FURI_CONST_ASSIGN_(signed char, x, y), \
|
||||
unsigned char: FURI_CONST_ASSIGN_(unsigned char, x, y), \
|
||||
short: FURI_CONST_ASSIGN_(short, x, y), \
|
||||
unsigned short: FURI_CONST_ASSIGN_(unsigned short, x, y), \
|
||||
int: FURI_CONST_ASSIGN_(int, x, y), \
|
||||
unsigned: FURI_CONST_ASSIGN_(unsigned, x, y), \
|
||||
long: FURI_CONST_ASSIGN_(long, x, y), \
|
||||
unsigned long: FURI_CONST_ASSIGN_(unsigned long, x, y), \
|
||||
long long: FURI_CONST_ASSIGN_(long long, x, y), \
|
||||
unsigned long long: FURI_CONST_ASSIGN_(unsigned long long, x, y), \
|
||||
float: FURI_CONST_ASSIGN_(float, x, y), \
|
||||
double: FURI_CONST_ASSIGN_(double, x, y), \
|
||||
long double: FURI_CONST_ASSIGN_(long double, x, y))
|
||||
#endif
|
||||
|
||||
@@ -569,25 +569,29 @@ void furi_string_utf8_decode(char c, FuriStringUTF8State* state, FuriStringUnico
|
||||
* @brief Select for 1 argument
|
||||
*/
|
||||
#define FURI_STRING_SELECT1(func1, func2, a) \
|
||||
_Generic((a), char* : func2, const char* : func2, FuriString* : func1, const FuriString* : func1)(a)
|
||||
_Generic((a), char*: func2, const char*: func2, FuriString*: func1, const FuriString*: func1)( \
|
||||
a)
|
||||
|
||||
/**
|
||||
* @brief Select for 2 arguments
|
||||
*/
|
||||
#define FURI_STRING_SELECT2(func1, func2, a, b) \
|
||||
_Generic((b), char* : func2, const char* : func2, FuriString* : func1, const FuriString* : func1)(a, b)
|
||||
_Generic((b), char*: func2, const char*: func2, FuriString*: func1, const FuriString*: func1)( \
|
||||
a, b)
|
||||
|
||||
/**
|
||||
* @brief Select for 3 arguments
|
||||
*/
|
||||
#define FURI_STRING_SELECT3(func1, func2, a, b, c) \
|
||||
_Generic((b), char* : func2, const char* : func2, FuriString* : func1, const FuriString* : func1)(a, b, c)
|
||||
_Generic((b), char*: func2, const char*: func2, FuriString*: func1, const FuriString*: func1)( \
|
||||
a, b, c)
|
||||
|
||||
/**
|
||||
* @brief Select for 4 arguments
|
||||
*/
|
||||
#define FURI_STRING_SELECT4(func1, func2, a, b, c, d) \
|
||||
_Generic((b), char* : func2, const char* : func2, FuriString* : func1, const FuriString* : func1)(a, b, c, d)
|
||||
_Generic((b), char*: func2, const char*: func2, FuriString*: func1, const FuriString*: func1)( \
|
||||
a, b, c, d)
|
||||
|
||||
/**
|
||||
* @brief Allocate new FuriString and set it content to string (or C string).
|
||||
|
||||
@@ -38,6 +38,7 @@ libs = env.BuildModules(
|
||||
"lfrfid",
|
||||
"flipper_application",
|
||||
"music_worker",
|
||||
"mjs",
|
||||
"nanopb",
|
||||
"update_util",
|
||||
"heatshrink",
|
||||
|
||||
33
lib/mjs/SConscript
Normal file
33
lib/mjs/SConscript
Normal file
@@ -0,0 +1,33 @@
|
||||
Import("env")
|
||||
|
||||
env.Append(
|
||||
CPPPATH=[
|
||||
"#/lib/mjs",
|
||||
],
|
||||
SDK_HEADERS=[
|
||||
File("mjs_core_public.h"),
|
||||
File("mjs_exec_public.h"),
|
||||
File("mjs_object_public.h"),
|
||||
File("mjs_string_public.h"),
|
||||
File("mjs_array_public.h"),
|
||||
File("mjs_primitive_public.h"),
|
||||
File("mjs_util_public.h"),
|
||||
File("mjs_array_buf_public.h"),
|
||||
],
|
||||
)
|
||||
|
||||
libenv = env.Clone(FW_LIB_NAME="mjs")
|
||||
libenv.ApplyLibFlags()
|
||||
|
||||
libenv.AppendUnique(
|
||||
CCFLAGS=[
|
||||
"-Wno-redundant-decls",
|
||||
"-Wno-unused-function",
|
||||
],
|
||||
)
|
||||
|
||||
sources = libenv.GlobRecursive("*.c*")
|
||||
|
||||
lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources)
|
||||
libenv.Install("${LIB_DIST_DIR}", lib)
|
||||
Return("lib")
|
||||
157
lib/mjs/common/cs_dbg.c
Normal file
157
lib/mjs/common/cs_dbg.c
Normal file
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "cs_dbg.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "cs_time.h"
|
||||
#include "str_util.h"
|
||||
|
||||
enum cs_log_level cs_log_level WEAK =
|
||||
#if CS_ENABLE_DEBUG
|
||||
LL_VERBOSE_DEBUG;
|
||||
#else
|
||||
LL_ERROR;
|
||||
#endif
|
||||
|
||||
#if CS_ENABLE_STDIO
|
||||
static char* s_file_level = NULL;
|
||||
|
||||
void cs_log_set_file_level(const char* file_level) WEAK;
|
||||
|
||||
FILE* cs_log_file WEAK = NULL;
|
||||
|
||||
#if CS_LOG_ENABLE_TS_DIFF
|
||||
double cs_log_ts WEAK;
|
||||
#endif
|
||||
|
||||
enum cs_log_level cs_log_cur_msg_level WEAK = LL_NONE;
|
||||
|
||||
void cs_log_set_file_level(const char* file_level) {
|
||||
char* fl = s_file_level;
|
||||
if(file_level != NULL) {
|
||||
s_file_level = strdup(file_level);
|
||||
} else {
|
||||
s_file_level = NULL;
|
||||
}
|
||||
free(fl);
|
||||
}
|
||||
|
||||
int cs_log_print_prefix(enum cs_log_level level, const char* file, int ln) WEAK;
|
||||
int cs_log_print_prefix(enum cs_log_level level, const char* file, int ln) {
|
||||
char prefix[CS_LOG_PREFIX_LEN], *q;
|
||||
const char* p;
|
||||
size_t fl = 0, ll = 0, pl = 0;
|
||||
|
||||
if(level > cs_log_level && s_file_level == NULL) return 0;
|
||||
|
||||
p = file + strlen(file);
|
||||
|
||||
while(p != file) {
|
||||
const char c = *(p - 1);
|
||||
if(c == '/' || c == '\\') break;
|
||||
p--;
|
||||
fl++;
|
||||
}
|
||||
|
||||
ll = (ln < 10000 ? (ln < 1000 ? (ln < 100 ? (ln < 10 ? 1 : 2) : 3) : 4) : 5);
|
||||
if(fl > (sizeof(prefix) - ll - 2)) fl = (sizeof(prefix) - ll - 2);
|
||||
|
||||
pl = fl + 1 + ll;
|
||||
memcpy(prefix, p, fl);
|
||||
q = prefix + pl;
|
||||
memset(q, ' ', sizeof(prefix) - pl);
|
||||
do {
|
||||
*(--q) = '0' + (ln % 10);
|
||||
ln /= 10;
|
||||
} while(ln > 0);
|
||||
*(--q) = ':';
|
||||
|
||||
if(s_file_level != NULL) {
|
||||
enum cs_log_level pll = cs_log_level;
|
||||
struct mg_str fl = mg_mk_str(s_file_level), ps = MG_MK_STR_N(prefix, pl);
|
||||
struct mg_str k, v;
|
||||
while((fl = mg_next_comma_list_entry_n(fl, &k, &v)).p != NULL) {
|
||||
bool yes = !(!mg_str_starts_with(ps, k) || v.len == 0);
|
||||
if(!yes) continue;
|
||||
pll = (enum cs_log_level)(*v.p - '0');
|
||||
break;
|
||||
}
|
||||
if(level > pll) return 0;
|
||||
}
|
||||
|
||||
if(cs_log_file == NULL) cs_log_file = stderr;
|
||||
cs_log_cur_msg_level = level;
|
||||
fwrite(prefix, 1, sizeof(prefix), cs_log_file);
|
||||
#if CS_LOG_ENABLE_TS_DIFF
|
||||
{
|
||||
double now = cs_time();
|
||||
fprintf(cs_log_file, "%7u ", (unsigned int)((now - cs_log_ts) * 1000000));
|
||||
cs_log_ts = now;
|
||||
}
|
||||
#endif
|
||||
return 1;
|
||||
}
|
||||
|
||||
void cs_log_printf(const char* fmt, ...) WEAK;
|
||||
void cs_log_printf(const char* fmt, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vfprintf(cs_log_file, fmt, ap);
|
||||
va_end(ap);
|
||||
fputc('\n', cs_log_file);
|
||||
fflush(cs_log_file);
|
||||
cs_log_cur_msg_level = LL_NONE;
|
||||
}
|
||||
|
||||
void cs_log_set_file(FILE* file) WEAK;
|
||||
void cs_log_set_file(FILE* file) {
|
||||
cs_log_file = file;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int cs_log_print_prefix(enum cs_log_level level, const char* file, int ln) WEAK;
|
||||
int cs_log_print_prefix(enum cs_log_level level, const char* file, int ln) {
|
||||
(void)level;
|
||||
(void)file;
|
||||
(void)ln;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void cs_log_printf(const char* fmt, ...) WEAK;
|
||||
void cs_log_printf(const char* fmt, ...) {
|
||||
(void)fmt;
|
||||
}
|
||||
|
||||
void cs_log_set_file_level(const char* file_level) {
|
||||
(void)file_level;
|
||||
}
|
||||
|
||||
#endif /* CS_ENABLE_STDIO */
|
||||
|
||||
void cs_log_set_level(enum cs_log_level level) WEAK;
|
||||
void cs_log_set_level(enum cs_log_level level) {
|
||||
cs_log_level = level;
|
||||
#if CS_LOG_ENABLE_TS_DIFF && CS_ENABLE_STDIO
|
||||
cs_log_ts = cs_time();
|
||||
#endif
|
||||
}
|
||||
148
lib/mjs/common/cs_dbg.h
Normal file
148
lib/mjs/common/cs_dbg.h
Normal file
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef CS_COMMON_CS_DBG_H_
|
||||
#define CS_COMMON_CS_DBG_H_
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
#if CS_ENABLE_STDIO
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
#ifndef CS_ENABLE_DEBUG
|
||||
#define CS_ENABLE_DEBUG 0
|
||||
#endif
|
||||
|
||||
#ifndef CS_LOG_PREFIX_LEN
|
||||
#define CS_LOG_PREFIX_LEN 24
|
||||
#endif
|
||||
|
||||
#ifndef CS_LOG_ENABLE_TS_DIFF
|
||||
#define CS_LOG_ENABLE_TS_DIFF 0
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/*
|
||||
* Log level; `LL_INFO` is the default. Use `cs_log_set_level()` to change it.
|
||||
*/
|
||||
enum cs_log_level {
|
||||
LL_NONE = -1,
|
||||
LL_ERROR = 0,
|
||||
LL_WARN = 1,
|
||||
LL_INFO = 2,
|
||||
LL_DEBUG = 3,
|
||||
LL_VERBOSE_DEBUG = 4,
|
||||
|
||||
_LL_MIN = -2,
|
||||
_LL_MAX = 5,
|
||||
};
|
||||
|
||||
/*
|
||||
* Set max log level to print; messages with the level above the given one will
|
||||
* not be printed.
|
||||
*/
|
||||
void cs_log_set_level(enum cs_log_level level);
|
||||
|
||||
/*
|
||||
* A comma-separated set of prefix=level.
|
||||
* prefix is matched against the log prefix exactly as printed, including line
|
||||
* number, but partial match is ok. Check stops on first matching entry.
|
||||
* If nothing matches, default level is used.
|
||||
*
|
||||
* Examples:
|
||||
* main.c:=4 - everything from main C at verbose debug level.
|
||||
* mongoose.c=1,mjs.c=1,=4 - everything at verbose debug except mg_* and mjs_*
|
||||
*
|
||||
*/
|
||||
void cs_log_set_file_level(const char* file_level);
|
||||
|
||||
/*
|
||||
* Helper function which prints message prefix with the given `level`.
|
||||
* If message should be printed (according to the current log level
|
||||
* and filter), prints the prefix and returns 1, otherwise returns 0.
|
||||
*
|
||||
* Clients should typically just use `LOG()` macro.
|
||||
*/
|
||||
int cs_log_print_prefix(enum cs_log_level level, const char* fname, int line);
|
||||
|
||||
extern enum cs_log_level cs_log_level;
|
||||
|
||||
#if CS_ENABLE_STDIO
|
||||
|
||||
/*
|
||||
* Set file to write logs into. If `NULL`, logs go to `stderr`.
|
||||
*/
|
||||
void cs_log_set_file(FILE* file);
|
||||
|
||||
/*
|
||||
* Prints log to the current log file, appends "\n" in the end and flushes the
|
||||
* stream.
|
||||
*/
|
||||
void cs_log_printf(const char* fmt, ...) PRINTF_LIKE(1, 2);
|
||||
|
||||
#if CS_ENABLE_STDIO
|
||||
|
||||
/*
|
||||
* Format and print message `x` with the given level `l`. Example:
|
||||
*
|
||||
* ```c
|
||||
* LOG(LL_INFO, ("my info message: %d", 123));
|
||||
* LOG(LL_DEBUG, ("my debug message: %d", 123));
|
||||
* ```
|
||||
*/
|
||||
#define LOG(l, x) \
|
||||
do { \
|
||||
if(cs_log_print_prefix(l, __FILE__, __LINE__)) { \
|
||||
cs_log_printf x; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#else
|
||||
|
||||
#define LOG(l, x) ((void)l)
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef CS_NDEBUG
|
||||
|
||||
/*
|
||||
* Shortcut for `LOG(LL_VERBOSE_DEBUG, (...))`
|
||||
*/
|
||||
#define DBG(x) LOG(LL_VERBOSE_DEBUG, x)
|
||||
|
||||
#else /* NDEBUG */
|
||||
|
||||
#define DBG(x)
|
||||
|
||||
#endif
|
||||
|
||||
#else /* CS_ENABLE_STDIO */
|
||||
|
||||
#define LOG(l, x)
|
||||
#define DBG(x)
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* CS_COMMON_CS_DBG_H_ */
|
||||
108
lib/mjs/common/cs_dirent.c
Normal file
108
lib/mjs/common/cs_dirent.c
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef EXCLUDE_COMMON
|
||||
|
||||
#include "mg_mem.h"
|
||||
#include "cs_dirent.h"
|
||||
|
||||
/*
|
||||
* This file contains POSIX opendir/closedir/readdir API implementation
|
||||
* for systems which do not natively support it (e.g. Windows).
|
||||
*/
|
||||
|
||||
#ifdef _WIN32
|
||||
struct win32_dir {
|
||||
DIR d;
|
||||
HANDLE handle;
|
||||
WIN32_FIND_DATAW info;
|
||||
struct dirent result;
|
||||
};
|
||||
|
||||
DIR *opendir(const char *name) {
|
||||
struct win32_dir *dir = NULL;
|
||||
wchar_t wpath[MAX_PATH];
|
||||
DWORD attrs;
|
||||
|
||||
if (name == NULL) {
|
||||
SetLastError(ERROR_BAD_ARGUMENTS);
|
||||
} else if ((dir = (struct win32_dir *) MG_MALLOC(sizeof(*dir))) == NULL) {
|
||||
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
|
||||
} else {
|
||||
to_wchar(name, wpath, ARRAY_SIZE(wpath));
|
||||
attrs = GetFileAttributesW(wpath);
|
||||
if (attrs != 0xFFFFFFFF && (attrs & FILE_ATTRIBUTE_DIRECTORY)) {
|
||||
(void) wcscat(wpath, L"\\*");
|
||||
dir->handle = FindFirstFileW(wpath, &dir->info);
|
||||
dir->result.d_name[0] = '\0';
|
||||
} else {
|
||||
MG_FREE(dir);
|
||||
dir = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return (DIR *) dir;
|
||||
}
|
||||
|
||||
int closedir(DIR *d) {
|
||||
struct win32_dir *dir = (struct win32_dir *) d;
|
||||
int result = 0;
|
||||
|
||||
if (dir != NULL) {
|
||||
if (dir->handle != INVALID_HANDLE_VALUE)
|
||||
result = FindClose(dir->handle) ? 0 : -1;
|
||||
MG_FREE(dir);
|
||||
} else {
|
||||
result = -1;
|
||||
SetLastError(ERROR_BAD_ARGUMENTS);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
struct dirent *readdir(DIR *d) {
|
||||
struct win32_dir *dir = (struct win32_dir *) d;
|
||||
struct dirent *result = NULL;
|
||||
|
||||
if (dir) {
|
||||
memset(&dir->result, 0, sizeof(dir->result));
|
||||
if (dir->handle != INVALID_HANDLE_VALUE) {
|
||||
result = &dir->result;
|
||||
(void) WideCharToMultiByte(CP_UTF8, 0, dir->info.cFileName, -1,
|
||||
result->d_name, sizeof(result->d_name), NULL,
|
||||
NULL);
|
||||
|
||||
if (!FindNextFileW(dir->handle, &dir->info)) {
|
||||
(void) FindClose(dir->handle);
|
||||
dir->handle = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
} else {
|
||||
SetLastError(ERROR_FILE_NOT_FOUND);
|
||||
}
|
||||
} else {
|
||||
SetLastError(ERROR_BAD_ARGUMENTS);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* EXCLUDE_COMMON */
|
||||
|
||||
/* ISO C requires a translation unit to contain at least one declaration */
|
||||
typedef int cs_dirent_dummy;
|
||||
51
lib/mjs/common/cs_dirent.h
Normal file
51
lib/mjs/common/cs_dirent.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef CS_COMMON_CS_DIRENT_H_
|
||||
#define CS_COMMON_CS_DIRENT_H_
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#ifdef CS_DEFINE_DIRENT
|
||||
typedef struct { int dummy; } DIR;
|
||||
|
||||
struct dirent {
|
||||
int d_ino;
|
||||
#ifdef _WIN32
|
||||
char d_name[MAX_PATH];
|
||||
#else
|
||||
/* TODO(rojer): Use PATH_MAX but make sure it's sane on every platform */
|
||||
char d_name[256];
|
||||
#endif
|
||||
};
|
||||
|
||||
DIR *opendir(const char *dir_name);
|
||||
int closedir(DIR *dir);
|
||||
struct dirent *readdir(DIR *dir);
|
||||
#endif /* CS_DEFINE_DIRENT */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* CS_COMMON_CS_DIRENT_H_ */
|
||||
65
lib/mjs/common/cs_file.c
Normal file
65
lib/mjs/common/cs_file.c
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "cs_file.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef CS_MMAP
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#endif
|
||||
|
||||
#ifdef CS_MMAP
|
||||
char* cs_read_file(const char* path, size_t* size) WEAK;
|
||||
char* cs_read_file(const char* path, size_t* size) {
|
||||
FILE* fp;
|
||||
char* data = NULL;
|
||||
if((fp = fopen(path, "rb")) == NULL) {
|
||||
} else if(fseek(fp, 0, SEEK_END) != 0) {
|
||||
fclose(fp);
|
||||
} else {
|
||||
*size = ftell(fp);
|
||||
data = (char*)malloc(*size + 1);
|
||||
if(data != NULL) {
|
||||
fseek(fp, 0, SEEK_SET); /* Some platforms might not have rewind(), Oo */
|
||||
if(fread(data, 1, *size, fp) != *size) {
|
||||
free(data);
|
||||
return NULL;
|
||||
}
|
||||
data[*size] = '\0';
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
char* cs_mmap_file(const char* path, size_t* size) WEAK;
|
||||
char* cs_mmap_file(const char* path, size_t* size) {
|
||||
char* r;
|
||||
int fd = open(path, O_RDONLY, 0);
|
||||
struct stat st;
|
||||
if(fd < 0) return NULL;
|
||||
fstat(fd, &st);
|
||||
*size = (size_t)st.st_size;
|
||||
r = (char*)mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
if(r == MAP_FAILED) return NULL;
|
||||
return r;
|
||||
}
|
||||
#endif
|
||||
48
lib/mjs/common/cs_file.h
Normal file
48
lib/mjs/common/cs_file.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef CS_COMMON_CS_FILE_H_
|
||||
#define CS_COMMON_CS_FILE_H_
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/*
|
||||
* Read whole file `path` in memory. It is responsibility of the caller
|
||||
* to `free()` allocated memory. File content is guaranteed to be
|
||||
* '\0'-terminated. File size is returned in `size` variable, which does not
|
||||
* count terminating `\0`.
|
||||
* Return: allocated memory, or NULL on error.
|
||||
*/
|
||||
char *cs_read_file(const char *path, size_t *size);
|
||||
|
||||
#ifdef CS_MMAP
|
||||
/*
|
||||
* Only on platforms which support mmapping: mmap file `path` to the returned
|
||||
* address. File size is written to `*size`.
|
||||
*/
|
||||
char *cs_mmap_file(const char *path, size_t *size);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* CS_COMMON_CS_FILE_H_ */
|
||||
91
lib/mjs/common/cs_time.c
Normal file
91
lib/mjs/common/cs_time.c
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "cs_time.h"
|
||||
|
||||
#if CS_ENABLE_STDIO
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <stddef.h>
|
||||
/*
|
||||
* There is no sys/time.h on ARMCC.
|
||||
*/
|
||||
#if !(defined(__ARMCC_VERSION) || defined(__ICCARM__)) && !defined(__TI_COMPILER_VERSION__) && \
|
||||
(!defined(CS_PLATFORM) || CS_PLATFORM != CS_P_NXP_LPC)
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
#else
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
double cs_time(void) WEAK;
|
||||
double cs_time(void) {
|
||||
double now;
|
||||
#ifndef _WIN32
|
||||
struct timeval tv;
|
||||
if(gettimeofday(&tv, NULL /* tz */) != 0) return 0;
|
||||
now = (double)tv.tv_sec + (((double)tv.tv_usec) / (double)1000000.0);
|
||||
#else
|
||||
SYSTEMTIME sysnow;
|
||||
FILETIME ftime;
|
||||
GetLocalTime(&sysnow);
|
||||
SystemTimeToFileTime(&sysnow, &ftime);
|
||||
/*
|
||||
* 1. VC 6.0 doesn't support conversion uint64 -> double, so, using int64
|
||||
* This should not cause a problems in this (21th) century
|
||||
* 2. Windows FILETIME is a number of 100-nanosecond intervals since January
|
||||
* 1, 1601 while time_t is a number of _seconds_ since January 1, 1970 UTC,
|
||||
* thus, we need to convert to seconds and adjust amount (subtract 11644473600
|
||||
* seconds)
|
||||
*/
|
||||
now = (double)(((int64_t)ftime.dwLowDateTime + ((int64_t)ftime.dwHighDateTime << 32)) /
|
||||
10000000.0) -
|
||||
11644473600;
|
||||
#endif /* _WIN32 */
|
||||
return now;
|
||||
}
|
||||
|
||||
double cs_timegm(const struct tm* tm) {
|
||||
/* Month-to-day offset for non-leap-years. */
|
||||
static const int month_day[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
|
||||
|
||||
/* Most of the calculation is easy; leap years are the main difficulty. */
|
||||
int month = tm->tm_mon % 12;
|
||||
int year = tm->tm_year + tm->tm_mon / 12;
|
||||
int year_for_leap;
|
||||
int64_t rt;
|
||||
|
||||
if(month < 0) { /* Negative values % 12 are still negative. */
|
||||
month += 12;
|
||||
--year;
|
||||
}
|
||||
|
||||
/* This is the number of Februaries since 1900. */
|
||||
year_for_leap = (month > 1) ? year + 1 : year;
|
||||
|
||||
rt = tm->tm_sec /* Seconds */
|
||||
+ 60 * (tm->tm_min /* Minute = 60 seconds */
|
||||
+ 60 * (tm->tm_hour /* Hour = 60 minutes */
|
||||
+ 24 * (month_day[month] + tm->tm_mday - 1 /* Day = 24 hours */
|
||||
+ 365 * (year - 70) /* Year = 365 days */
|
||||
+ (year_for_leap - 69) / 4 /* Every 4 years is leap... */
|
||||
- (year_for_leap - 1) / 100 /* Except centuries... */
|
||||
+ (year_for_leap + 299) / 400))); /* Except 400s. */
|
||||
return rt < 0 ? -1 : (double)rt;
|
||||
}
|
||||
|
||||
#endif
|
||||
42
lib/mjs/common/cs_time.h
Normal file
42
lib/mjs/common/cs_time.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef CS_COMMON_CS_TIME_H_
|
||||
#define CS_COMMON_CS_TIME_H_
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/* Sub-second granularity time(). */
|
||||
double cs_time(void);
|
||||
|
||||
/*
|
||||
* Similar to (non-standard) timegm, converts broken-down time into the number
|
||||
* of seconds since Unix Epoch.
|
||||
*/
|
||||
double cs_timegm(const struct tm* tm);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* CS_COMMON_CS_TIME_H_ */
|
||||
76
lib/mjs/common/cs_varint.c
Normal file
76
lib/mjs/common/cs_varint.c
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "cs_varint.h"
|
||||
|
||||
size_t cs_varint_llen(uint64_t num) {
|
||||
size_t llen = 0;
|
||||
|
||||
do {
|
||||
llen++;
|
||||
} while (num >>= 7);
|
||||
|
||||
return llen;
|
||||
}
|
||||
|
||||
size_t cs_varint_encode(uint64_t num, uint8_t *buf, size_t buf_size) {
|
||||
size_t llen = 0;
|
||||
|
||||
do {
|
||||
uint8_t byte = num & 0x7f;
|
||||
num >>= 7;
|
||||
if (num != 0) byte |= 0x80;
|
||||
if (llen < buf_size) *buf++ = byte;
|
||||
llen++;
|
||||
} while (num != 0);
|
||||
|
||||
return llen;
|
||||
}
|
||||
|
||||
bool cs_varint_decode(const uint8_t *buf, size_t buf_size, uint64_t *num,
|
||||
size_t *llen) {
|
||||
size_t i = 0, shift = 0;
|
||||
uint64_t n = 0;
|
||||
|
||||
do {
|
||||
if (i == buf_size || i == (8 * sizeof(*num) / 7 + 1)) return false;
|
||||
/*
|
||||
* Each byte of varint contains 7 bits, in little endian order.
|
||||
* MSB is a continuation bit: it tells whether next byte is used.
|
||||
*/
|
||||
n |= ((uint64_t)(buf[i] & 0x7f)) << shift;
|
||||
/*
|
||||
* First we increment i, then check whether it is within boundary and
|
||||
* whether decoded byte had continuation bit set.
|
||||
*/
|
||||
i++;
|
||||
shift += 7;
|
||||
} while (shift < sizeof(uint64_t) * 8 && (buf[i - 1] & 0x80));
|
||||
|
||||
*num = n;
|
||||
*llen = i;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t cs_varint_decode_unsafe(const uint8_t *buf, int *llen) {
|
||||
uint64_t v;
|
||||
size_t l;
|
||||
cs_varint_decode(buf, ~0, &v, &l);
|
||||
*llen = l;
|
||||
return v;
|
||||
}
|
||||
59
lib/mjs/common/cs_varint.h
Normal file
59
lib/mjs/common/cs_varint.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef CS_COMMON_CS_VARINT_H_
|
||||
#define CS_COMMON_CS_VARINT_H_
|
||||
|
||||
#if defined(_WIN32) && _MSC_VER < 1700
|
||||
typedef unsigned char uint8_t;
|
||||
typedef unsigned __int64 uint64_t;
|
||||
#else
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Returns number of bytes required to encode `num`. */
|
||||
size_t cs_varint_llen(uint64_t num);
|
||||
|
||||
/*
|
||||
* Encodes `num` into `buf`.
|
||||
* Returns number of bytes required to encode `num`.
|
||||
* Note: return value may be greater than `buf_size` but the function will only
|
||||
* write `buf_size` bytes.
|
||||
*/
|
||||
size_t cs_varint_encode(uint64_t num, uint8_t *buf, size_t buf_size);
|
||||
|
||||
/*
|
||||
* Decodes varint stored in `buf`.
|
||||
* Stores the number of bytes consumed into `llen`.
|
||||
* If there aren't enough bytes in `buf` to decode a number, returns false.
|
||||
*/
|
||||
bool cs_varint_decode(const uint8_t *buf, size_t buf_size, uint64_t *num,
|
||||
size_t *llen);
|
||||
|
||||
uint64_t cs_varint_decode_unsafe(const uint8_t *buf, int *llen);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CS_COMMON_CS_VARINT_H_ */
|
||||
1528
lib/mjs/common/frozen/frozen.c
Normal file
1528
lib/mjs/common/frozen/frozen.c
Normal file
File diff suppressed because it is too large
Load Diff
359
lib/mjs/common/frozen/frozen.h
Normal file
359
lib/mjs/common/frozen/frozen.h
Normal file
@@ -0,0 +1,359 @@
|
||||
/*
|
||||
* Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com>
|
||||
* Copyright (c) 2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef CS_FROZEN_FROZEN_H_
|
||||
#define CS_FROZEN_FROZEN_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#if defined(_WIN32) && _MSC_VER < 1700
|
||||
typedef int bool;
|
||||
enum { false = 0, true = 1 };
|
||||
#else
|
||||
#include <stdbool.h>
|
||||
#endif
|
||||
|
||||
/* JSON token type */
|
||||
enum json_token_type {
|
||||
JSON_TYPE_INVALID = 0, /* memsetting to 0 should create INVALID value */
|
||||
JSON_TYPE_STRING,
|
||||
JSON_TYPE_NUMBER,
|
||||
JSON_TYPE_TRUE,
|
||||
JSON_TYPE_FALSE,
|
||||
JSON_TYPE_NULL,
|
||||
JSON_TYPE_OBJECT_START,
|
||||
JSON_TYPE_OBJECT_END,
|
||||
JSON_TYPE_ARRAY_START,
|
||||
JSON_TYPE_ARRAY_END,
|
||||
|
||||
JSON_TYPES_CNT
|
||||
};
|
||||
|
||||
/*
|
||||
* Structure containing token type and value. Used in `json_walk()` and
|
||||
* `json_scanf()` with the format specifier `%T`.
|
||||
*/
|
||||
struct json_token {
|
||||
const char* ptr; /* Points to the beginning of the value */
|
||||
int len; /* Value length */
|
||||
enum json_token_type type; /* Type of the token, possible values are above */
|
||||
};
|
||||
|
||||
#define JSON_INVALID_TOKEN \
|
||||
{ 0, 0, JSON_TYPE_INVALID }
|
||||
|
||||
/* Error codes */
|
||||
#define JSON_STRING_INVALID -1
|
||||
#define JSON_STRING_INCOMPLETE -2
|
||||
|
||||
/*
|
||||
* Callback-based SAX-like API.
|
||||
*
|
||||
* Property name and length is given only if it's available: i.e. if current
|
||||
* event is an object's property. In other cases, `name` is `NULL`. For
|
||||
* example, name is never given:
|
||||
* - For the first value in the JSON string;
|
||||
* - For events JSON_TYPE_OBJECT_END and JSON_TYPE_ARRAY_END
|
||||
*
|
||||
* E.g. for the input `{ "foo": 123, "bar": [ 1, 2, { "baz": true } ] }`,
|
||||
* the sequence of callback invocations will be as follows:
|
||||
*
|
||||
* - type: JSON_TYPE_OBJECT_START, name: NULL, path: "", value: NULL
|
||||
* - type: JSON_TYPE_NUMBER, name: "foo", path: ".foo", value: "123"
|
||||
* - type: JSON_TYPE_ARRAY_START, name: "bar", path: ".bar", value: NULL
|
||||
* - type: JSON_TYPE_NUMBER, name: "0", path: ".bar[0]", value: "1"
|
||||
* - type: JSON_TYPE_NUMBER, name: "1", path: ".bar[1]", value: "2"
|
||||
* - type: JSON_TYPE_OBJECT_START, name: "2", path: ".bar[2]", value: NULL
|
||||
* - type: JSON_TYPE_TRUE, name: "baz", path: ".bar[2].baz", value: "true"
|
||||
* - type: JSON_TYPE_OBJECT_END, name: NULL, path: ".bar[2]", value: "{ \"baz\":
|
||||
*true }"
|
||||
* - type: JSON_TYPE_ARRAY_END, name: NULL, path: ".bar", value: "[ 1, 2, {
|
||||
*\"baz\": true } ]"
|
||||
* - type: JSON_TYPE_OBJECT_END, name: NULL, path: "", value: "{ \"foo\": 123,
|
||||
*\"bar\": [ 1, 2, { \"baz\": true } ] }"
|
||||
*/
|
||||
typedef void (*json_walk_callback_t)(
|
||||
void* callback_data,
|
||||
const char* name,
|
||||
size_t name_len,
|
||||
const char* path,
|
||||
const struct json_token* token);
|
||||
|
||||
/*
|
||||
* Parse `json_string`, invoking `callback` in a way similar to SAX parsers;
|
||||
* see `json_walk_callback_t`.
|
||||
* Return number of processed bytes, or a negative error code.
|
||||
*/
|
||||
int json_walk(
|
||||
const char* json_string,
|
||||
int json_string_length,
|
||||
json_walk_callback_t callback,
|
||||
void* callback_data);
|
||||
|
||||
/*
|
||||
* JSON generation API.
|
||||
* struct json_out abstracts output, allowing alternative printing plugins.
|
||||
*/
|
||||
struct json_out {
|
||||
int (*printer)(struct json_out*, const char* str, size_t len);
|
||||
union {
|
||||
struct {
|
||||
char* buf;
|
||||
size_t size;
|
||||
size_t len;
|
||||
} buf;
|
||||
void* data;
|
||||
FILE* fp;
|
||||
} u;
|
||||
};
|
||||
|
||||
extern int json_printer_buf(struct json_out*, const char*, size_t);
|
||||
extern int json_printer_file(struct json_out*, const char*, size_t);
|
||||
|
||||
#define JSON_OUT_BUF(buf, len) \
|
||||
{ \
|
||||
json_printer_buf, { \
|
||||
{ buf, len, 0 } \
|
||||
} \
|
||||
}
|
||||
#define JSON_OUT_FILE(fp) \
|
||||
{ \
|
||||
json_printer_file, { \
|
||||
{ (char*)fp, 0, 0 } \
|
||||
} \
|
||||
}
|
||||
|
||||
typedef int (*json_printf_callback_t)(struct json_out*, va_list* ap);
|
||||
|
||||
/*
|
||||
* Generate formatted output into a given sting buffer.
|
||||
* This is a superset of printf() function, with extra format specifiers:
|
||||
* - `%B` print json boolean, `true` or `false`. Accepts an `int`.
|
||||
* - `%Q` print quoted escaped string or `null`. Accepts a `const char *`.
|
||||
* - `%.*Q` same as `%Q`, but with length. Accepts `int`, `const char *`
|
||||
* - `%V` print quoted base64-encoded string. Accepts a `const char *`, `int`.
|
||||
* - `%H` print quoted hex-encoded string. Accepts a `int`, `const char *`.
|
||||
* - `%M` invokes a json_printf_callback_t function. That callback function
|
||||
* can consume more parameters.
|
||||
*
|
||||
* Return number of bytes printed. If the return value is bigger than the
|
||||
* supplied buffer, that is an indicator of overflow. In the overflow case,
|
||||
* overflown bytes are not printed.
|
||||
*/
|
||||
int json_printf(struct json_out*, const char* fmt, ...);
|
||||
int json_vprintf(struct json_out*, const char* fmt, va_list ap);
|
||||
|
||||
/*
|
||||
* Same as json_printf, but prints to a file.
|
||||
* File is created if does not exist. File is truncated if already exists.
|
||||
*/
|
||||
int json_fprintf(const char* file_name, const char* fmt, ...);
|
||||
int json_vfprintf(const char* file_name, const char* fmt, va_list ap);
|
||||
|
||||
/*
|
||||
* Print JSON into an allocated 0-terminated string.
|
||||
* Return allocated string, or NULL on error.
|
||||
* Example:
|
||||
*
|
||||
* ```c
|
||||
* char *str = json_asprintf("{a:%H}", 3, "abc");
|
||||
* printf("%s\n", str); // Prints "616263"
|
||||
* free(str);
|
||||
* ```
|
||||
*/
|
||||
char* json_asprintf(const char* fmt, ...);
|
||||
char* json_vasprintf(const char* fmt, va_list ap);
|
||||
|
||||
/*
|
||||
* Helper %M callback that prints contiguous C arrays.
|
||||
* Consumes void *array_ptr, size_t array_size, size_t elem_size, char *fmt
|
||||
* Return number of bytes printed.
|
||||
*/
|
||||
int json_printf_array(struct json_out*, va_list* ap);
|
||||
|
||||
/*
|
||||
* Scan JSON string `str`, performing scanf-like conversions according to `fmt`.
|
||||
* This is a `scanf()` - like function, with following differences:
|
||||
*
|
||||
* 1. Object keys in the format string may be not quoted, e.g. "{key: %d}"
|
||||
* 2. Order of keys in an object is irrelevant.
|
||||
* 3. Several extra format specifiers are supported:
|
||||
* - %B: consumes `int *` (or `char *`, if `sizeof(bool) == sizeof(char)`),
|
||||
* expects boolean `true` or `false`.
|
||||
* - %Q: consumes `char **`, expects quoted, JSON-encoded string. Scanned
|
||||
* string is malloc-ed, caller must free() the string.
|
||||
* - %V: consumes `char **`, `int *`. Expects base64-encoded string.
|
||||
* Result string is base64-decoded, malloced and NUL-terminated.
|
||||
* The length of result string is stored in `int *` placeholder.
|
||||
* Caller must free() the result.
|
||||
* - %H: consumes `int *`, `char **`.
|
||||
* Expects a hex-encoded string, e.g. "fa014f".
|
||||
* Result string is hex-decoded, malloced and NUL-terminated.
|
||||
* The length of the result string is stored in `int *` placeholder.
|
||||
* Caller must free() the result.
|
||||
* - %M: consumes custom scanning function pointer and
|
||||
* `void *user_data` parameter - see json_scanner_t definition.
|
||||
* - %T: consumes `struct json_token *`, fills it out with matched token.
|
||||
*
|
||||
* Return number of elements successfully scanned & converted.
|
||||
* Negative number means scan error.
|
||||
*/
|
||||
int json_scanf(const char* str, int str_len, const char* fmt, ...);
|
||||
int json_vscanf(const char* str, int str_len, const char* fmt, va_list ap);
|
||||
|
||||
/* json_scanf's %M handler */
|
||||
typedef void (*json_scanner_t)(const char* str, int len, void* user_data);
|
||||
|
||||
/*
|
||||
* Helper function to scan array item with given path and index.
|
||||
* Fills `token` with the matched JSON token.
|
||||
* Return -1 if no array element found, otherwise non-negative token length.
|
||||
*/
|
||||
int json_scanf_array_elem(
|
||||
const char* s,
|
||||
int len,
|
||||
const char* path,
|
||||
int index,
|
||||
struct json_token* token);
|
||||
|
||||
/*
|
||||
* Unescape JSON-encoded string src,slen into dst, dlen.
|
||||
* src and dst may overlap.
|
||||
* If destination buffer is too small (or zero-length), result string is not
|
||||
* written but the length is counted nevertheless (similar to snprintf).
|
||||
* Return the length of unescaped string in bytes.
|
||||
*/
|
||||
int json_unescape(const char* src, int slen, char* dst, int dlen);
|
||||
|
||||
/*
|
||||
* Escape a string `str`, `str_len` into the printer `out`.
|
||||
* Return the number of bytes printed.
|
||||
*/
|
||||
int json_escape(struct json_out* out, const char* str, size_t str_len);
|
||||
|
||||
/*
|
||||
* Read the whole file in memory.
|
||||
* Return malloc-ed file content, or NULL on error. The caller must free().
|
||||
*/
|
||||
char* json_fread(const char* file_name);
|
||||
|
||||
/*
|
||||
* Update given JSON string `s,len` by changing the value at given `json_path`.
|
||||
* The result is saved to `out`. If `json_fmt` == NULL, that deletes the key.
|
||||
* If path is not present, missing keys are added. Array path without an
|
||||
* index pushes a value to the end of an array.
|
||||
* Return 1 if the string was changed, 0 otherwise.
|
||||
*
|
||||
* Example: s is a JSON string { "a": 1, "b": [ 2 ] }
|
||||
* json_setf(s, len, out, ".a", "7"); // { "a": 7, "b": [ 2 ] }
|
||||
* json_setf(s, len, out, ".b", "7"); // { "a": 1, "b": 7 }
|
||||
* json_setf(s, len, out, ".b[]", "7"); // { "a": 1, "b": [ 2,7 ] }
|
||||
* json_setf(s, len, out, ".b", NULL); // { "a": 1 }
|
||||
*/
|
||||
int json_setf(
|
||||
const char* s,
|
||||
int len,
|
||||
struct json_out* out,
|
||||
const char* json_path,
|
||||
const char* json_fmt,
|
||||
...);
|
||||
|
||||
int json_vsetf(
|
||||
const char* s,
|
||||
int len,
|
||||
struct json_out* out,
|
||||
const char* json_path,
|
||||
const char* json_fmt,
|
||||
va_list ap);
|
||||
|
||||
/*
|
||||
* Pretty-print JSON string `s,len` into `out`.
|
||||
* Return number of processed bytes in `s`.
|
||||
*/
|
||||
int json_prettify(const char* s, int len, struct json_out* out);
|
||||
|
||||
/*
|
||||
* Prettify JSON file `file_name`.
|
||||
* Return number of processed bytes, or negative number of error.
|
||||
* On error, file content is not modified.
|
||||
*/
|
||||
int json_prettify_file(const char* file_name);
|
||||
|
||||
/*
|
||||
* Iterate over an object at given JSON `path`.
|
||||
* On each iteration, fill the `key` and `val` tokens. It is OK to pass NULL
|
||||
* for `key`, or `val`, in which case they won't be populated.
|
||||
* Return an opaque value suitable for the next iteration, or NULL when done.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```c
|
||||
* void *h = NULL;
|
||||
* struct json_token key, val;
|
||||
* while ((h = json_next_key(s, len, h, ".foo", &key, &val)) != NULL) {
|
||||
* printf("[%.*s] -> [%.*s]\n", key.len, key.ptr, val.len, val.ptr);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
void* json_next_key(
|
||||
const char* s,
|
||||
int len,
|
||||
void* handle,
|
||||
const char* path,
|
||||
struct json_token* key,
|
||||
struct json_token* val);
|
||||
|
||||
/*
|
||||
* Iterate over an array at given JSON `path`.
|
||||
* Similar to `json_next_key`, but fills array index `idx` instead of `key`.
|
||||
*/
|
||||
void* json_next_elem(
|
||||
const char* s,
|
||||
int len,
|
||||
void* handle,
|
||||
const char* path,
|
||||
int* idx,
|
||||
struct json_token* val);
|
||||
|
||||
#ifndef JSON_MAX_PATH_LEN
|
||||
#define JSON_MAX_PATH_LEN 256
|
||||
#endif
|
||||
|
||||
#ifndef JSON_MINIMAL
|
||||
#define JSON_MINIMAL 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_ENABLE_BASE64
|
||||
#define JSON_ENABLE_BASE64 !JSON_MINIMAL
|
||||
#endif
|
||||
|
||||
#ifndef JSON_ENABLE_HEX
|
||||
#define JSON_ENABLE_HEX !JSON_MINIMAL
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* CS_FROZEN_FROZEN_H_ */
|
||||
151
lib/mjs/common/mbuf.c
Normal file
151
lib/mjs/common/mbuf.c
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef EXCLUDE_COMMON
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "mbuf.h"
|
||||
|
||||
#ifndef MBUF_REALLOC
|
||||
#define MBUF_REALLOC realloc
|
||||
#endif
|
||||
|
||||
#ifndef MBUF_FREE
|
||||
#define MBUF_FREE free
|
||||
#endif
|
||||
|
||||
void mbuf_init(struct mbuf *mbuf, size_t initial_size) WEAK;
|
||||
void mbuf_init(struct mbuf *mbuf, size_t initial_size) {
|
||||
mbuf->len = mbuf->size = 0;
|
||||
mbuf->buf = NULL;
|
||||
mbuf_resize(mbuf, initial_size);
|
||||
}
|
||||
|
||||
void mbuf_free(struct mbuf *mbuf) WEAK;
|
||||
void mbuf_free(struct mbuf *mbuf) {
|
||||
if (mbuf->buf != NULL) {
|
||||
MBUF_FREE(mbuf->buf);
|
||||
mbuf_init(mbuf, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void mbuf_resize(struct mbuf *a, size_t new_size) WEAK;
|
||||
void mbuf_resize(struct mbuf *a, size_t new_size) {
|
||||
if (new_size > a->size || (new_size < a->size && new_size >= a->len)) {
|
||||
char *buf = (char *) MBUF_REALLOC(a->buf, new_size);
|
||||
/*
|
||||
* In case realloc fails, there's not much we can do, except keep things as
|
||||
* they are. Note that NULL is a valid return value from realloc when
|
||||
* size == 0, but that is covered too.
|
||||
*/
|
||||
if (buf == NULL && new_size != 0) return;
|
||||
a->buf = buf;
|
||||
a->size = new_size;
|
||||
}
|
||||
}
|
||||
|
||||
void mbuf_trim(struct mbuf *mbuf) WEAK;
|
||||
void mbuf_trim(struct mbuf *mbuf) {
|
||||
mbuf_resize(mbuf, mbuf->len);
|
||||
}
|
||||
|
||||
size_t mbuf_insert(struct mbuf *a, size_t off, const void *buf, size_t) WEAK;
|
||||
size_t mbuf_insert(struct mbuf *a, size_t off, const void *buf, size_t len) {
|
||||
char *p = NULL;
|
||||
|
||||
assert(a != NULL);
|
||||
assert(a->len <= a->size);
|
||||
assert(off <= a->len);
|
||||
|
||||
/* check overflow */
|
||||
if (~(size_t) 0 - (size_t) a->buf < len) return 0;
|
||||
|
||||
if (a->len + len <= a->size) {
|
||||
memmove(a->buf + off + len, a->buf + off, a->len - off);
|
||||
if (buf != NULL) {
|
||||
memcpy(a->buf + off, buf, len);
|
||||
}
|
||||
a->len += len;
|
||||
} else {
|
||||
size_t min_size = (a->len + len);
|
||||
size_t new_size = (size_t)(min_size * MBUF_SIZE_MULTIPLIER);
|
||||
if (new_size - min_size > MBUF_SIZE_MAX_HEADROOM) {
|
||||
new_size = min_size + MBUF_SIZE_MAX_HEADROOM;
|
||||
}
|
||||
p = (char *) MBUF_REALLOC(a->buf, new_size);
|
||||
if (p == NULL && new_size != min_size) {
|
||||
new_size = min_size;
|
||||
p = (char *) MBUF_REALLOC(a->buf, new_size);
|
||||
}
|
||||
if (p != NULL) {
|
||||
a->buf = p;
|
||||
if (off != a->len) {
|
||||
memmove(a->buf + off + len, a->buf + off, a->len - off);
|
||||
}
|
||||
if (buf != NULL) memcpy(a->buf + off, buf, len);
|
||||
a->len += len;
|
||||
a->size = new_size;
|
||||
} else {
|
||||
len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
size_t mbuf_append(struct mbuf *a, const void *buf, size_t len) WEAK;
|
||||
size_t mbuf_append(struct mbuf *a, const void *buf, size_t len) {
|
||||
return mbuf_insert(a, a->len, buf, len);
|
||||
}
|
||||
|
||||
size_t mbuf_append_and_free(struct mbuf *a, void *buf, size_t len) WEAK;
|
||||
size_t mbuf_append_and_free(struct mbuf *a, void *data, size_t len) {
|
||||
size_t ret;
|
||||
/* Optimization: if the buffer is currently empty,
|
||||
* take over the user-provided buffer. */
|
||||
if (a->len == 0) {
|
||||
if (a->buf != NULL) free(a->buf);
|
||||
a->buf = (char *) data;
|
||||
a->len = a->size = len;
|
||||
return len;
|
||||
}
|
||||
ret = mbuf_insert(a, a->len, data, len);
|
||||
free(data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void mbuf_remove(struct mbuf *mb, size_t n) WEAK;
|
||||
void mbuf_remove(struct mbuf *mb, size_t n) {
|
||||
if (n > 0 && n <= mb->len) {
|
||||
memmove(mb->buf, mb->buf + n, mb->len - n);
|
||||
mb->len -= n;
|
||||
}
|
||||
}
|
||||
|
||||
void mbuf_clear(struct mbuf *mb) WEAK;
|
||||
void mbuf_clear(struct mbuf *mb) {
|
||||
mb->len = 0;
|
||||
}
|
||||
|
||||
void mbuf_move(struct mbuf *from, struct mbuf *to) WEAK;
|
||||
void mbuf_move(struct mbuf *from, struct mbuf *to) {
|
||||
memcpy(to, from, sizeof(*to));
|
||||
memset(from, 0, sizeof(*from));
|
||||
}
|
||||
|
||||
#endif /* EXCLUDE_COMMON */
|
||||
111
lib/mjs/common/mbuf.h
Normal file
111
lib/mjs/common/mbuf.h
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Mbufs are mutable/growing memory buffers, like C++ strings.
|
||||
* Mbuf can append data to the end of a buffer or insert data into arbitrary
|
||||
* position in the middle of a buffer. The buffer grows automatically when
|
||||
* needed.
|
||||
*/
|
||||
|
||||
#ifndef CS_COMMON_MBUF_H_
|
||||
#define CS_COMMON_MBUF_H_
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "platform.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef MBUF_SIZE_MULTIPLIER
|
||||
#define MBUF_SIZE_MULTIPLIER 1.5
|
||||
#endif
|
||||
|
||||
#ifndef MBUF_SIZE_MAX_HEADROOM
|
||||
#ifdef BUFSIZ
|
||||
#define MBUF_SIZE_MAX_HEADROOM BUFSIZ
|
||||
#else
|
||||
#define MBUF_SIZE_MAX_HEADROOM 1024
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Memory buffer descriptor */
|
||||
struct mbuf {
|
||||
char *buf; /* Buffer pointer */
|
||||
size_t len; /* Data length. Data is located between offset 0 and len. */
|
||||
size_t size; /* Buffer size allocated by realloc(1). Must be >= len */
|
||||
};
|
||||
|
||||
/*
|
||||
* Initialises an Mbuf.
|
||||
* `initial_capacity` specifies the initial capacity of the mbuf.
|
||||
*/
|
||||
void mbuf_init(struct mbuf *, size_t initial_capacity);
|
||||
|
||||
/* Frees the space allocated for the mbuffer and resets the mbuf structure. */
|
||||
void mbuf_free(struct mbuf *);
|
||||
|
||||
/*
|
||||
* Appends data to the Mbuf.
|
||||
*
|
||||
* Returns the number of bytes appended or 0 if out of memory.
|
||||
*/
|
||||
size_t mbuf_append(struct mbuf *, const void *data, size_t data_size);
|
||||
|
||||
/*
|
||||
* Appends data to the Mbuf and frees it (data must be heap-allocated).
|
||||
*
|
||||
* Returns the number of bytes appended or 0 if out of memory.
|
||||
* data is freed irrespective of return value.
|
||||
*/
|
||||
size_t mbuf_append_and_free(struct mbuf *, void *data, size_t data_size);
|
||||
|
||||
/*
|
||||
* Inserts data at a specified offset in the Mbuf.
|
||||
*
|
||||
* Existing data will be shifted forwards and the buffer will
|
||||
* be grown if necessary.
|
||||
* Returns the number of bytes inserted.
|
||||
*/
|
||||
size_t mbuf_insert(struct mbuf *, size_t, const void *, size_t);
|
||||
|
||||
/* Removes `data_size` bytes from the beginning of the buffer. */
|
||||
void mbuf_remove(struct mbuf *, size_t data_size);
|
||||
|
||||
/*
|
||||
* Resizes an Mbuf.
|
||||
*
|
||||
* If `new_size` is smaller than buffer's `len`, the
|
||||
* resize is not performed.
|
||||
*/
|
||||
void mbuf_resize(struct mbuf *, size_t new_size);
|
||||
|
||||
/* Moves the state from one mbuf to the other. */
|
||||
void mbuf_move(struct mbuf *from, struct mbuf *to);
|
||||
|
||||
/* Removes all the data from mbuf (if any). */
|
||||
void mbuf_clear(struct mbuf *);
|
||||
|
||||
/* Shrinks an Mbuf by resizing its `size` to `len`. */
|
||||
void mbuf_trim(struct mbuf *);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* CS_COMMON_MBUF_H_ */
|
||||
45
lib/mjs/common/mg_mem.h
Normal file
45
lib/mjs/common/mg_mem.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef CS_COMMON_MG_MEM_H_
|
||||
#define CS_COMMON_MG_MEM_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef MG_MALLOC
|
||||
#define MG_MALLOC malloc
|
||||
#endif
|
||||
|
||||
#ifndef MG_CALLOC
|
||||
#define MG_CALLOC calloc
|
||||
#endif
|
||||
|
||||
#ifndef MG_REALLOC
|
||||
#define MG_REALLOC realloc
|
||||
#endif
|
||||
|
||||
#ifndef MG_FREE
|
||||
#define MG_FREE free
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CS_COMMON_MG_MEM_H_ */
|
||||
175
lib/mjs/common/mg_str.c
Normal file
175
lib/mjs/common/mg_str.c
Normal file
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "mg_mem.h"
|
||||
#include "mg_str.h"
|
||||
#include "platform.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
int mg_ncasecmp(const char* s1, const char* s2, size_t len) WEAK;
|
||||
|
||||
struct mg_str mg_mk_str(const char* s) WEAK;
|
||||
struct mg_str mg_mk_str(const char* s) {
|
||||
struct mg_str ret = {s, 0};
|
||||
if(s != NULL) ret.len = strlen(s);
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct mg_str mg_mk_str_n(const char* s, size_t len) WEAK;
|
||||
struct mg_str mg_mk_str_n(const char* s, size_t len) {
|
||||
struct mg_str ret = {s, len};
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mg_vcmp(const struct mg_str* str1, const char* str2) WEAK;
|
||||
int mg_vcmp(const struct mg_str* str1, const char* str2) {
|
||||
size_t n2 = strlen(str2), n1 = str1->len;
|
||||
int r = strncmp(str1->p, str2, (n1 < n2) ? n1 : n2);
|
||||
if(r == 0) {
|
||||
return n1 - n2;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
int mg_vcasecmp(const struct mg_str* str1, const char* str2) WEAK;
|
||||
int mg_vcasecmp(const struct mg_str* str1, const char* str2) {
|
||||
size_t n2 = strlen(str2), n1 = str1->len;
|
||||
int r = mg_ncasecmp(str1->p, str2, (n1 < n2) ? n1 : n2);
|
||||
if(r == 0) {
|
||||
return n1 - n2;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static struct mg_str mg_strdup_common(const struct mg_str s, int nul_terminate) {
|
||||
struct mg_str r = {NULL, 0};
|
||||
if(s.len > 0 && s.p != NULL) {
|
||||
char* sc = (char*)MG_MALLOC(s.len + (nul_terminate ? 1 : 0));
|
||||
if(sc != NULL) {
|
||||
memcpy(sc, s.p, s.len);
|
||||
if(nul_terminate) sc[s.len] = '\0';
|
||||
r.p = sc;
|
||||
r.len = s.len;
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
struct mg_str mg_strdup(const struct mg_str s) WEAK;
|
||||
struct mg_str mg_strdup(const struct mg_str s) {
|
||||
return mg_strdup_common(s, 0 /* NUL-terminate */);
|
||||
}
|
||||
|
||||
struct mg_str mg_strdup_nul(const struct mg_str s) WEAK;
|
||||
struct mg_str mg_strdup_nul(const struct mg_str s) {
|
||||
return mg_strdup_common(s, 1 /* NUL-terminate */);
|
||||
}
|
||||
|
||||
const char* mg_strchr(const struct mg_str s, int c) WEAK;
|
||||
const char* mg_strchr(const struct mg_str s, int c) {
|
||||
size_t i;
|
||||
for(i = 0; i < s.len; i++) {
|
||||
if(s.p[i] == c) return &s.p[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int mg_strcmp(const struct mg_str str1, const struct mg_str str2) WEAK;
|
||||
int mg_strcmp(const struct mg_str str1, const struct mg_str str2) {
|
||||
size_t i = 0;
|
||||
while(i < str1.len && i < str2.len) {
|
||||
int c1 = str1.p[i];
|
||||
int c2 = str2.p[i];
|
||||
if(c1 < c2) return -1;
|
||||
if(c1 > c2) return 1;
|
||||
i++;
|
||||
}
|
||||
if(i < str1.len) return 1;
|
||||
if(i < str2.len) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mg_strncmp(const struct mg_str, const struct mg_str, size_t n) WEAK;
|
||||
int mg_strncmp(const struct mg_str str1, const struct mg_str str2, size_t n) {
|
||||
struct mg_str s1 = str1;
|
||||
struct mg_str s2 = str2;
|
||||
|
||||
if(s1.len > n) {
|
||||
s1.len = n;
|
||||
}
|
||||
if(s2.len > n) {
|
||||
s2.len = n;
|
||||
}
|
||||
return mg_strcmp(s1, s2);
|
||||
}
|
||||
|
||||
int mg_strcasecmp(const struct mg_str str1, const struct mg_str str2) WEAK;
|
||||
int mg_strcasecmp(const struct mg_str str1, const struct mg_str str2) {
|
||||
size_t i = 0;
|
||||
while(i < str1.len && i < str2.len) {
|
||||
int c1 = tolower((int)str1.p[i]);
|
||||
int c2 = tolower((int)str2.p[i]);
|
||||
if(c1 < c2) return -1;
|
||||
if(c1 > c2) return 1;
|
||||
i++;
|
||||
}
|
||||
if(i < str1.len) return 1;
|
||||
if(i < str2.len) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void mg_strfree(struct mg_str* s) WEAK;
|
||||
void mg_strfree(struct mg_str* s) {
|
||||
char* sp = (char*)s->p;
|
||||
s->p = NULL;
|
||||
s->len = 0;
|
||||
if(sp != NULL) free(sp);
|
||||
}
|
||||
|
||||
const char* mg_strstr(const struct mg_str haystack, const struct mg_str needle) WEAK;
|
||||
const char* mg_strstr(const struct mg_str haystack, const struct mg_str needle) {
|
||||
size_t i;
|
||||
if(needle.len > haystack.len) return NULL;
|
||||
for(i = 0; i <= haystack.len - needle.len; i++) {
|
||||
if(memcmp(haystack.p + i, needle.p, needle.len) == 0) {
|
||||
return haystack.p + i;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct mg_str mg_strstrip(struct mg_str s) WEAK;
|
||||
struct mg_str mg_strstrip(struct mg_str s) {
|
||||
while(s.len > 0 && isspace((int)*s.p)) {
|
||||
s.p++;
|
||||
s.len--;
|
||||
}
|
||||
while(s.len > 0 && isspace((int)*(s.p + s.len - 1))) {
|
||||
s.len--;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
int mg_str_starts_with(struct mg_str s, struct mg_str prefix) WEAK;
|
||||
int mg_str_starts_with(struct mg_str s, struct mg_str prefix) {
|
||||
const struct mg_str sp = MG_MK_STR_N(s.p, prefix.len);
|
||||
if(s.len < prefix.len) return 0;
|
||||
return (mg_strcmp(sp, prefix) == 0);
|
||||
}
|
||||
113
lib/mjs/common/mg_str.h
Normal file
113
lib/mjs/common/mg_str.h
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef CS_COMMON_MG_STR_H_
|
||||
#define CS_COMMON_MG_STR_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Describes chunk of memory */
|
||||
struct mg_str {
|
||||
const char *p; /* Memory chunk pointer */
|
||||
size_t len; /* Memory chunk length */
|
||||
};
|
||||
|
||||
/*
|
||||
* Helper function for creating mg_str struct from plain C string.
|
||||
* `NULL` is allowed and becomes `{NULL, 0}`.
|
||||
*/
|
||||
struct mg_str mg_mk_str(const char *s);
|
||||
|
||||
/*
|
||||
* Like `mg_mk_str`, but takes string length explicitly.
|
||||
*/
|
||||
struct mg_str mg_mk_str_n(const char *s, size_t len);
|
||||
|
||||
/* Macro for initializing mg_str. */
|
||||
#define MG_MK_STR(str_literal) \
|
||||
{ str_literal, sizeof(str_literal) - 1 }
|
||||
#define MG_MK_STR_N(str_literal, len) \
|
||||
{ str_literal, len }
|
||||
#define MG_NULL_STR \
|
||||
{ NULL, 0 }
|
||||
|
||||
/*
|
||||
* Cross-platform version of `strcmp()` where where first string is
|
||||
* specified by `struct mg_str`.
|
||||
*/
|
||||
int mg_vcmp(const struct mg_str *str2, const char *str1);
|
||||
|
||||
/*
|
||||
* Cross-platform version of `strncasecmp()` where first string is
|
||||
* specified by `struct mg_str`.
|
||||
*/
|
||||
int mg_vcasecmp(const struct mg_str *str2, const char *str1);
|
||||
|
||||
/* Creates a copy of s (heap-allocated). */
|
||||
struct mg_str mg_strdup(const struct mg_str s);
|
||||
|
||||
/*
|
||||
* Creates a copy of s (heap-allocated).
|
||||
* Resulting string is NUL-terminated (but NUL is not included in len).
|
||||
*/
|
||||
struct mg_str mg_strdup_nul(const struct mg_str s);
|
||||
|
||||
/*
|
||||
* Locates character in a string.
|
||||
*/
|
||||
const char *mg_strchr(const struct mg_str s, int c);
|
||||
|
||||
/*
|
||||
* Compare two `mg_str`s; return value is the same as `strcmp`.
|
||||
*/
|
||||
int mg_strcmp(const struct mg_str str1, const struct mg_str str2);
|
||||
|
||||
/*
|
||||
* Like `mg_strcmp`, but compares at most `n` characters.
|
||||
*/
|
||||
int mg_strncmp(const struct mg_str str1, const struct mg_str str2, size_t n);
|
||||
|
||||
/*
|
||||
* Compare two `mg_str`s ignoreing case; return value is the same as `strcmp`.
|
||||
*/
|
||||
int mg_strcasecmp(const struct mg_str str1, const struct mg_str str2);
|
||||
|
||||
/*
|
||||
* Free the string (assuming it was heap allocated).
|
||||
*/
|
||||
void mg_strfree(struct mg_str *s);
|
||||
|
||||
/*
|
||||
* Finds the first occurrence of a substring `needle` in the `haystack`.
|
||||
*/
|
||||
const char *mg_strstr(const struct mg_str haystack, const struct mg_str needle);
|
||||
|
||||
/* Strip whitespace at the start and the end of s */
|
||||
struct mg_str mg_strstrip(struct mg_str s);
|
||||
|
||||
/* Returns 1 if s starts with the given prefix. */
|
||||
int mg_str_starts_with(struct mg_str s, struct mg_str prefix);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CS_COMMON_MG_STR_H_ */
|
||||
89
lib/mjs/common/platform.h
Normal file
89
lib/mjs/common/platform.h
Normal file
@@ -0,0 +1,89 @@
|
||||
#ifndef CS_COMMON_PLATFORM_H_
|
||||
#define CS_COMMON_PLATFORM_H_
|
||||
|
||||
/*
|
||||
* For the "custom" platform, includes and dependencies can be
|
||||
* provided through mg_locals.h.
|
||||
*/
|
||||
#define CS_P_CUSTOM 0
|
||||
#define CS_P_UNIX 1
|
||||
#define CS_P_WINDOWS 2
|
||||
#define CS_P_ESP32 15
|
||||
#define CS_P_ESP8266 3
|
||||
#define CS_P_CC3100 6
|
||||
#define CS_P_CC3200 4
|
||||
#define CS_P_CC3220 17
|
||||
#define CS_P_MSP432 5
|
||||
#define CS_P_TM4C129 14
|
||||
#define CS_P_MBED 7
|
||||
#define CS_P_WINCE 8
|
||||
#define CS_P_NXP_LPC 13
|
||||
#define CS_P_NXP_KINETIS 9
|
||||
#define CS_P_NRF51 12
|
||||
#define CS_P_NRF52 10
|
||||
#define CS_P_PIC32 11
|
||||
#define CS_P_RS14100 18
|
||||
#define CS_P_STM32 16
|
||||
#define CS_P_FLIPPER 19
|
||||
/* Next id: 20 */
|
||||
|
||||
#ifndef CS_PLATFORM
|
||||
#define CS_PLATFORM CS_P_FLIPPER
|
||||
#endif
|
||||
|
||||
#ifndef CS_PLATFORM
|
||||
#error "CS_PLATFORM is not specified and we couldn't guess it."
|
||||
#endif
|
||||
|
||||
#define MG_NET_IF_SOCKET 1
|
||||
#define MG_NET_IF_SIMPLELINK 2
|
||||
#define MG_NET_IF_LWIP_LOW_LEVEL 3
|
||||
#define MG_NET_IF_PIC32 4
|
||||
#define MG_NET_IF_NULL 5
|
||||
|
||||
#define MG_SSL_IF_OPENSSL 1
|
||||
#define MG_SSL_IF_MBEDTLS 2
|
||||
#define MG_SSL_IF_SIMPLELINK 3
|
||||
|
||||
#if CS_PLATFORM == CS_P_FLIPPER
|
||||
#include "platforms/platform_flipper.h"
|
||||
#endif
|
||||
|
||||
/* Common stuff */
|
||||
|
||||
#if !defined(PRINTF_LIKE)
|
||||
#if defined(__GNUC__) || defined(__clang__) || defined(__TI_COMPILER_VERSION__)
|
||||
#define PRINTF_LIKE(f, a) __attribute__((format(printf, f, a)))
|
||||
#else
|
||||
#define PRINTF_LIKE(f, a)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !defined(WEAK)
|
||||
#if(defined(__GNUC__) || defined(__clang__) || defined(__TI_COMPILER_VERSION__)) && \
|
||||
!defined(_WIN32)
|
||||
#define WEAK __attribute__((weak))
|
||||
#else
|
||||
#define WEAK
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define NORETURN __attribute__((noreturn))
|
||||
#define NOINLINE __attribute__((noinline))
|
||||
#define WARN_UNUSED_RESULT __attribute__((warn_unused_result))
|
||||
#define NOINSTR __attribute__((no_instrument_function))
|
||||
#define DO_NOT_WARN_UNUSED __attribute__((unused))
|
||||
#else
|
||||
#define NORETURN
|
||||
#define NOINLINE
|
||||
#define WARN_UNUSED_RESULT
|
||||
#define NOINSTR
|
||||
#define DO_NOT_WARN_UNUSED
|
||||
#endif /* __GNUC__ */
|
||||
|
||||
#ifndef ARRAY_SIZE
|
||||
#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
|
||||
#endif
|
||||
|
||||
#endif /* CS_COMMON_PLATFORM_H_ */
|
||||
65
lib/mjs/common/platforms/platform_flipper.c
Normal file
65
lib/mjs/common/platforms/platform_flipper.c
Normal file
@@ -0,0 +1,65 @@
|
||||
#include <furi.h>
|
||||
#include <toolbox/stream/file_stream.h>
|
||||
#include "../cs_dbg.h"
|
||||
#include "../frozen/frozen.h"
|
||||
|
||||
char* cs_read_file(const char* path, size_t* size) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
Stream* stream = file_stream_alloc(storage);
|
||||
char* data = NULL;
|
||||
if(!file_stream_open(stream, path, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
} else {
|
||||
*size = stream_size(stream);
|
||||
data = (char*)malloc(*size + 1);
|
||||
if(data != NULL) {
|
||||
stream_rewind(stream);
|
||||
if(stream_read(stream, (uint8_t*)data, *size) != *size) {
|
||||
file_stream_close(stream);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
stream_free(stream);
|
||||
free(data);
|
||||
return NULL;
|
||||
}
|
||||
data[*size] = '\0';
|
||||
}
|
||||
}
|
||||
file_stream_close(stream);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
stream_free(stream);
|
||||
return data;
|
||||
}
|
||||
|
||||
char* json_fread(const char* path) {
|
||||
UNUSED(path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int json_vfprintf(const char* file_name, const char* fmt, va_list ap) {
|
||||
UNUSED(file_name);
|
||||
UNUSED(fmt);
|
||||
UNUSED(ap);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int json_prettify_file(const char* file_name) {
|
||||
UNUSED(file_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int json_printer_file(struct json_out* out, const char* buf, size_t len) {
|
||||
UNUSED(out);
|
||||
UNUSED(buf);
|
||||
UNUSED(len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cs_log_print_prefix(enum cs_log_level level, const char* file, int ln) {
|
||||
(void)level;
|
||||
(void)file;
|
||||
(void)ln;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void cs_log_printf(const char* fmt, ...) {
|
||||
(void)fmt;
|
||||
}
|
||||
48
lib/mjs/common/platforms/platform_flipper.h
Normal file
48
lib/mjs/common/platforms/platform_flipper.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#if CS_PLATFORM == CS_P_FLIPPER
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define to64(x) strtoll(x, NULL, 10)
|
||||
#define INT64_FMT "lld"
|
||||
#define SIZE_T_FMT "u"
|
||||
typedef struct stat cs_stat_t;
|
||||
#define DIRSEP '/'
|
||||
|
||||
#ifndef CS_ENABLE_STDIO
|
||||
#define CS_ENABLE_STDIO 0
|
||||
#endif
|
||||
|
||||
#ifndef MG_ENABLE_FILESYSTEM
|
||||
#define MG_ENABLE_FILESYSTEM 0
|
||||
#endif
|
||||
|
||||
#endif /* CS_PLATFORM == CS_P_FLIPPER */
|
||||
537
lib/mjs/common/str_util.c
Normal file
537
lib/mjs/common/str_util.c
Normal file
@@ -0,0 +1,537 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef EXCLUDE_COMMON
|
||||
|
||||
#include "str_util.h"
|
||||
#include "mg_mem.h"
|
||||
#include "platform.h"
|
||||
|
||||
#ifndef C_DISABLE_BUILTIN_SNPRINTF
|
||||
#define C_DISABLE_BUILTIN_SNPRINTF 1
|
||||
#endif
|
||||
|
||||
#include "mg_mem.h"
|
||||
|
||||
size_t c_strnlen(const char* s, size_t maxlen) WEAK;
|
||||
size_t c_strnlen(const char* s, size_t maxlen) {
|
||||
size_t l = 0;
|
||||
for(; l < maxlen && s[l] != '\0'; l++) {
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
#define C_SNPRINTF_APPEND_CHAR(ch) \
|
||||
do { \
|
||||
if(i < (int)buf_size) buf[i] = ch; \
|
||||
i++; \
|
||||
} while(0)
|
||||
|
||||
#define C_SNPRINTF_FLAG_ZERO 1
|
||||
|
||||
#if C_DISABLE_BUILTIN_SNPRINTF
|
||||
int c_vsnprintf(char* buf, size_t buf_size, const char* fmt, va_list ap) WEAK;
|
||||
int c_vsnprintf(char* buf, size_t buf_size, const char* fmt, va_list ap) {
|
||||
return vsnprintf(buf, buf_size, fmt, ap);
|
||||
}
|
||||
#else
|
||||
static int c_itoa(char* buf, size_t buf_size, int64_t num, int base, int flags, int field_width) {
|
||||
char tmp[40];
|
||||
int i = 0, k = 0, neg = 0;
|
||||
|
||||
if(num < 0) {
|
||||
neg++;
|
||||
num = -num;
|
||||
}
|
||||
|
||||
/* Print into temporary buffer - in reverse order */
|
||||
do {
|
||||
int rem = num % base;
|
||||
if(rem < 10) {
|
||||
tmp[k++] = '0' + rem;
|
||||
} else {
|
||||
tmp[k++] = 'a' + (rem - 10);
|
||||
}
|
||||
num /= base;
|
||||
} while(num > 0);
|
||||
|
||||
/* Zero padding */
|
||||
if(flags && C_SNPRINTF_FLAG_ZERO) {
|
||||
while(k < field_width && k < (int)sizeof(tmp) - 1) {
|
||||
tmp[k++] = '0';
|
||||
}
|
||||
}
|
||||
|
||||
/* And sign */
|
||||
if(neg) {
|
||||
tmp[k++] = '-';
|
||||
}
|
||||
|
||||
/* Now output */
|
||||
while(--k >= 0) {
|
||||
C_SNPRINTF_APPEND_CHAR(tmp[k]);
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
int c_vsnprintf(char* buf, size_t buf_size, const char* fmt, va_list ap) WEAK;
|
||||
int c_vsnprintf(char* buf, size_t buf_size, const char* fmt, va_list ap) {
|
||||
int ch, i = 0, len_mod, flags, precision, field_width;
|
||||
|
||||
while((ch = *fmt++) != '\0') {
|
||||
if(ch != '%') {
|
||||
C_SNPRINTF_APPEND_CHAR(ch);
|
||||
} else {
|
||||
/*
|
||||
* Conversion specification:
|
||||
* zero or more flags (one of: # 0 - <space> + ')
|
||||
* an optional minimum field width (digits)
|
||||
* an optional precision (. followed by digits, or *)
|
||||
* an optional length modifier (one of: hh h l ll L q j z t)
|
||||
* conversion specifier (one of: d i o u x X e E f F g G a A c s p n)
|
||||
*/
|
||||
flags = field_width = precision = len_mod = 0;
|
||||
|
||||
/* Flags. only zero-pad flag is supported. */
|
||||
if(*fmt == '0') {
|
||||
flags |= C_SNPRINTF_FLAG_ZERO;
|
||||
}
|
||||
|
||||
/* Field width */
|
||||
while(*fmt >= '0' && *fmt <= '9') {
|
||||
field_width *= 10;
|
||||
field_width += *fmt++ - '0';
|
||||
}
|
||||
/* Dynamic field width */
|
||||
if(*fmt == '*') {
|
||||
field_width = va_arg(ap, int);
|
||||
fmt++;
|
||||
}
|
||||
|
||||
/* Precision */
|
||||
if(*fmt == '.') {
|
||||
fmt++;
|
||||
if(*fmt == '*') {
|
||||
precision = va_arg(ap, int);
|
||||
fmt++;
|
||||
} else {
|
||||
while(*fmt >= '0' && *fmt <= '9') {
|
||||
precision *= 10;
|
||||
precision += *fmt++ - '0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Length modifier */
|
||||
switch(*fmt) {
|
||||
case 'h':
|
||||
case 'l':
|
||||
case 'L':
|
||||
case 'I':
|
||||
case 'q':
|
||||
case 'j':
|
||||
case 'z':
|
||||
case 't':
|
||||
len_mod = *fmt++;
|
||||
if(*fmt == 'h') {
|
||||
len_mod = 'H';
|
||||
fmt++;
|
||||
}
|
||||
if(*fmt == 'l') {
|
||||
len_mod = 'q';
|
||||
fmt++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
ch = *fmt++;
|
||||
if(ch == 's') {
|
||||
const char* s = va_arg(ap, const char*); /* Always fetch parameter */
|
||||
int j;
|
||||
int pad = field_width - (precision >= 0 ? c_strnlen(s, precision) : 0);
|
||||
for(j = 0; j < pad; j++) {
|
||||
C_SNPRINTF_APPEND_CHAR(' ');
|
||||
}
|
||||
|
||||
/* `s` may be NULL in case of %.*s */
|
||||
if(s != NULL) {
|
||||
/* Ignore negative and 0 precisions */
|
||||
for(j = 0; (precision <= 0 || j < precision) && s[j] != '\0'; j++) {
|
||||
C_SNPRINTF_APPEND_CHAR(s[j]);
|
||||
}
|
||||
}
|
||||
} else if(ch == 'c') {
|
||||
ch = va_arg(ap, int); /* Always fetch parameter */
|
||||
C_SNPRINTF_APPEND_CHAR(ch);
|
||||
} else if(ch == 'd' && len_mod == 0) {
|
||||
i += c_itoa(buf + i, buf_size - i, va_arg(ap, int), 10, flags, field_width);
|
||||
} else if(ch == 'd' && len_mod == 'l') {
|
||||
i += c_itoa(buf + i, buf_size - i, va_arg(ap, long), 10, flags, field_width);
|
||||
#ifdef SSIZE_MAX
|
||||
} else if(ch == 'd' && len_mod == 'z') {
|
||||
i += c_itoa(buf + i, buf_size - i, va_arg(ap, ssize_t), 10, flags, field_width);
|
||||
#endif
|
||||
} else if(ch == 'd' && len_mod == 'q') {
|
||||
i += c_itoa(buf + i, buf_size - i, va_arg(ap, int64_t), 10, flags, field_width);
|
||||
} else if((ch == 'x' || ch == 'u') && len_mod == 0) {
|
||||
i += c_itoa(
|
||||
buf + i,
|
||||
buf_size - i,
|
||||
va_arg(ap, unsigned),
|
||||
ch == 'x' ? 16 : 10,
|
||||
flags,
|
||||
field_width);
|
||||
} else if((ch == 'x' || ch == 'u') && len_mod == 'l') {
|
||||
i += c_itoa(
|
||||
buf + i,
|
||||
buf_size - i,
|
||||
va_arg(ap, unsigned long),
|
||||
ch == 'x' ? 16 : 10,
|
||||
flags,
|
||||
field_width);
|
||||
} else if((ch == 'x' || ch == 'u') && len_mod == 'z') {
|
||||
i += c_itoa(
|
||||
buf + i,
|
||||
buf_size - i,
|
||||
va_arg(ap, size_t),
|
||||
ch == 'x' ? 16 : 10,
|
||||
flags,
|
||||
field_width);
|
||||
} else if(ch == 'p') {
|
||||
unsigned long num = (unsigned long)(uintptr_t)va_arg(ap, void*);
|
||||
C_SNPRINTF_APPEND_CHAR('0');
|
||||
C_SNPRINTF_APPEND_CHAR('x');
|
||||
i += c_itoa(buf + i, buf_size - i, num, 16, flags, 0);
|
||||
} else {
|
||||
#ifndef NO_LIBC
|
||||
/*
|
||||
* TODO(lsm): abort is not nice in a library, remove it
|
||||
* Also, ESP8266 SDK doesn't have it
|
||||
*/
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Zero-terminate the result */
|
||||
if(buf_size > 0) {
|
||||
buf[i < (int)buf_size ? i : (int)buf_size - 1] = '\0';
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
#endif
|
||||
|
||||
int c_snprintf(char* buf, size_t buf_size, const char* fmt, ...) WEAK;
|
||||
int c_snprintf(char* buf, size_t buf_size, const char* fmt, ...) {
|
||||
int result;
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
result = c_vsnprintf(buf, buf_size, fmt, ap);
|
||||
va_end(ap);
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
int to_wchar(const char* path, wchar_t* wbuf, size_t wbuf_len) {
|
||||
int ret;
|
||||
char buf[MAX_PATH * 2], buf2[MAX_PATH * 2], *p;
|
||||
|
||||
strncpy(buf, path, sizeof(buf));
|
||||
buf[sizeof(buf) - 1] = '\0';
|
||||
|
||||
/* Trim trailing slashes. Leave backslash for paths like "X:\" */
|
||||
p = buf + strlen(buf) - 1;
|
||||
while(p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] == '/')) *p-- = '\0';
|
||||
|
||||
memset(wbuf, 0, wbuf_len * sizeof(wchar_t));
|
||||
ret = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int)wbuf_len);
|
||||
|
||||
/*
|
||||
* Convert back to Unicode. If doubly-converted string does not match the
|
||||
* original, something is fishy, reject.
|
||||
*/
|
||||
WideCharToMultiByte(CP_UTF8, 0, wbuf, (int)wbuf_len, buf2, sizeof(buf2), NULL, NULL);
|
||||
if(strcmp(buf, buf2) != 0) {
|
||||
wbuf[0] = L'\0';
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif /* _WIN32 */
|
||||
|
||||
/* The simplest O(mn) algorithm. Better implementation are GPLed */
|
||||
const char* c_strnstr(const char* s, const char* find, size_t slen) WEAK;
|
||||
const char* c_strnstr(const char* s, const char* find, size_t slen) {
|
||||
size_t find_length = strlen(find);
|
||||
size_t i;
|
||||
|
||||
for(i = 0; i < slen; i++) {
|
||||
if(i + find_length > slen) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if(strncmp(&s[i], find, find_length) == 0) {
|
||||
return &s[i];
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#if CS_ENABLE_STRDUP
|
||||
char* strdup(const char* src) WEAK;
|
||||
char* strdup(const char* src) {
|
||||
size_t len = strlen(src) + 1;
|
||||
char* ret = MG_MALLOC(len);
|
||||
if(ret != NULL) {
|
||||
strcpy(ret, src);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
void cs_to_hex(char* to, const unsigned char* p, size_t len) WEAK;
|
||||
void cs_to_hex(char* to, const unsigned char* p, size_t len) {
|
||||
static const char* hex = "0123456789abcdef";
|
||||
|
||||
for(; len--; p++) {
|
||||
*to++ = hex[p[0] >> 4];
|
||||
*to++ = hex[p[0] & 0x0f];
|
||||
}
|
||||
*to = '\0';
|
||||
}
|
||||
|
||||
static int fourbit(int ch) {
|
||||
if(ch >= '0' && ch <= '9') {
|
||||
return ch - '0';
|
||||
} else if(ch >= 'a' && ch <= 'f') {
|
||||
return ch - 'a' + 10;
|
||||
} else if(ch >= 'A' && ch <= 'F') {
|
||||
return ch - 'A' + 10;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void cs_from_hex(char* to, const char* p, size_t len) WEAK;
|
||||
void cs_from_hex(char* to, const char* p, size_t len) {
|
||||
size_t i;
|
||||
|
||||
for(i = 0; i < len; i += 2) {
|
||||
*to++ = (fourbit(p[i]) << 4) + fourbit(p[i + 1]);
|
||||
}
|
||||
*to = '\0';
|
||||
}
|
||||
|
||||
#if CS_ENABLE_TO64
|
||||
int64_t cs_to64(const char* s) WEAK;
|
||||
int64_t cs_to64(const char* s) {
|
||||
int64_t result = 0;
|
||||
int64_t neg = 1;
|
||||
while(*s && isspace((unsigned char)*s)) s++;
|
||||
if(*s == '-') {
|
||||
neg = -1;
|
||||
s++;
|
||||
}
|
||||
while(isdigit((unsigned char)*s)) {
|
||||
result *= 10;
|
||||
result += (*s - '0');
|
||||
s++;
|
||||
}
|
||||
return result * neg;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int str_util_lowercase(const char* s) {
|
||||
return tolower(*(const unsigned char*)s);
|
||||
}
|
||||
|
||||
int mg_ncasecmp(const char* s1, const char* s2, size_t len) WEAK;
|
||||
int mg_ncasecmp(const char* s1, const char* s2, size_t len) {
|
||||
int diff = 0;
|
||||
|
||||
if(len > 0) do {
|
||||
diff = str_util_lowercase(s1++) - str_util_lowercase(s2++);
|
||||
} while(diff == 0 && s1[-1] != '\0' && --len > 0);
|
||||
|
||||
return diff;
|
||||
}
|
||||
|
||||
int mg_casecmp(const char* s1, const char* s2) WEAK;
|
||||
int mg_casecmp(const char* s1, const char* s2) {
|
||||
return mg_ncasecmp(s1, s2, (size_t)~0);
|
||||
}
|
||||
|
||||
int mg_asprintf(char** buf, size_t size, const char* fmt, ...) WEAK;
|
||||
int mg_asprintf(char** buf, size_t size, const char* fmt, ...) {
|
||||
int ret;
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
ret = mg_avprintf(buf, size, fmt, ap);
|
||||
va_end(ap);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mg_avprintf(char** buf, size_t size, const char* fmt, va_list ap) WEAK;
|
||||
int mg_avprintf(char** buf, size_t size, const char* fmt, va_list ap) {
|
||||
va_list ap_copy;
|
||||
int len;
|
||||
|
||||
va_copy(ap_copy, ap);
|
||||
len = vsnprintf(*buf, size, fmt, ap_copy);
|
||||
va_end(ap_copy);
|
||||
|
||||
if(len < 0) {
|
||||
/* eCos and Windows are not standard-compliant and return -1 when
|
||||
* the buffer is too small. Keep allocating larger buffers until we
|
||||
* succeed or out of memory. */
|
||||
*buf = NULL; /* LCOV_EXCL_START */
|
||||
while(len < 0) {
|
||||
MG_FREE(*buf);
|
||||
if(size == 0) {
|
||||
size = 5;
|
||||
}
|
||||
size *= 2;
|
||||
if((*buf = (char*)MG_MALLOC(size)) == NULL) {
|
||||
len = -1;
|
||||
break;
|
||||
}
|
||||
va_copy(ap_copy, ap);
|
||||
len = vsnprintf(*buf, size - 1, fmt, ap_copy);
|
||||
va_end(ap_copy);
|
||||
}
|
||||
|
||||
/*
|
||||
* Microsoft version of vsnprintf() is not always null-terminated, so put
|
||||
* the terminator manually
|
||||
*/
|
||||
(*buf)[len] = 0;
|
||||
/* LCOV_EXCL_STOP */
|
||||
} else if(len >= (int)size) {
|
||||
/* Standard-compliant code path. Allocate a buffer that is large enough. */
|
||||
if((*buf = (char*)MG_MALLOC(len + 1)) == NULL) {
|
||||
len = -1; /* LCOV_EXCL_LINE */
|
||||
} else { /* LCOV_EXCL_LINE */
|
||||
va_copy(ap_copy, ap);
|
||||
len = vsnprintf(*buf, len + 1, fmt, ap_copy);
|
||||
va_end(ap_copy);
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
const char* mg_next_comma_list_entry(const char*, struct mg_str*, struct mg_str*) WEAK;
|
||||
const char* mg_next_comma_list_entry(const char* list, struct mg_str* val, struct mg_str* eq_val) {
|
||||
struct mg_str ret = mg_next_comma_list_entry_n(mg_mk_str(list), val, eq_val);
|
||||
return ret.p;
|
||||
}
|
||||
|
||||
struct mg_str
|
||||
mg_next_comma_list_entry_n(struct mg_str list, struct mg_str* val, struct mg_str* eq_val) WEAK;
|
||||
struct mg_str
|
||||
mg_next_comma_list_entry_n(struct mg_str list, struct mg_str* val, struct mg_str* eq_val) {
|
||||
if(list.len == 0) {
|
||||
/* End of the list */
|
||||
list = mg_mk_str(NULL);
|
||||
} else {
|
||||
const char* chr = NULL;
|
||||
*val = list;
|
||||
|
||||
if((chr = mg_strchr(*val, ',')) != NULL) {
|
||||
/* Comma found. Store length and shift the list ptr */
|
||||
val->len = chr - val->p;
|
||||
chr++;
|
||||
list.len -= (chr - list.p);
|
||||
list.p = chr;
|
||||
} else {
|
||||
/* This value is the last one */
|
||||
list = mg_mk_str_n(list.p + list.len, 0);
|
||||
}
|
||||
|
||||
if(eq_val != NULL) {
|
||||
/* Value has form "x=y", adjust pointers and lengths */
|
||||
/* so that val points to "x", and eq_val points to "y". */
|
||||
eq_val->len = 0;
|
||||
eq_val->p = (const char*)memchr(val->p, '=', val->len);
|
||||
if(eq_val->p != NULL) {
|
||||
eq_val->p++; /* Skip over '=' character */
|
||||
eq_val->len = val->p + val->len - eq_val->p;
|
||||
val->len = (eq_val->p - val->p) - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
size_t mg_match_prefix_n(const struct mg_str, const struct mg_str) WEAK;
|
||||
size_t mg_match_prefix_n(const struct mg_str pattern, const struct mg_str str) {
|
||||
const char* or_str;
|
||||
size_t res = 0, len = 0, i = 0, j = 0;
|
||||
|
||||
if((or_str = (const char*)memchr(pattern.p, '|', pattern.len)) != NULL ||
|
||||
(or_str = (const char*)memchr(pattern.p, ',', pattern.len)) != NULL) {
|
||||
struct mg_str pstr = {pattern.p, (size_t)(or_str - pattern.p)};
|
||||
res = mg_match_prefix_n(pstr, str);
|
||||
if(res > 0) return res;
|
||||
pstr.p = or_str + 1;
|
||||
pstr.len = (pattern.p + pattern.len) - (or_str + 1);
|
||||
return mg_match_prefix_n(pstr, str);
|
||||
}
|
||||
|
||||
for(; i < pattern.len && j < str.len; i++, j++) {
|
||||
if(pattern.p[i] == '?') {
|
||||
continue;
|
||||
} else if(pattern.p[i] == '*') {
|
||||
i++;
|
||||
if(i < pattern.len && pattern.p[i] == '*') {
|
||||
i++;
|
||||
len = str.len - j;
|
||||
} else {
|
||||
len = 0;
|
||||
while(j + len < str.len && str.p[j + len] != '/') len++;
|
||||
}
|
||||
if(i == pattern.len || (pattern.p[i] == '$' && i == pattern.len - 1)) return j + len;
|
||||
do {
|
||||
const struct mg_str pstr = {pattern.p + i, pattern.len - i};
|
||||
const struct mg_str sstr = {str.p + j + len, str.len - j - len};
|
||||
res = mg_match_prefix_n(pstr, sstr);
|
||||
} while(res == 0 && len != 0 && len-- > 0);
|
||||
return res == 0 ? 0 : j + res + len;
|
||||
} else if(str_util_lowercase(&pattern.p[i]) != str_util_lowercase(&str.p[j])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(i < pattern.len && pattern.p[i] == '$') {
|
||||
return j == str.len ? str.len : 0;
|
||||
}
|
||||
return i == pattern.len ? j : 0;
|
||||
}
|
||||
|
||||
size_t mg_match_prefix(const char*, int, const char*) WEAK;
|
||||
size_t mg_match_prefix(const char* pattern, int pattern_len, const char* str) {
|
||||
const struct mg_str pstr = {pattern, (size_t)pattern_len};
|
||||
struct mg_str s = {str, 0};
|
||||
if(str != NULL) s.len = strlen(str);
|
||||
return mg_match_prefix_n(pstr, s);
|
||||
}
|
||||
|
||||
#endif /* EXCLUDE_COMMON */
|
||||
195
lib/mjs/common/str_util.h
Normal file
195
lib/mjs/common/str_util.h
Normal file
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef CS_COMMON_STR_UTIL_H_
|
||||
#define CS_COMMON_STR_UTIL_H_
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "mg_str.h"
|
||||
#include "platform.h"
|
||||
|
||||
#ifndef CS_ENABLE_STRDUP
|
||||
#define CS_ENABLE_STRDUP 0
|
||||
#endif
|
||||
|
||||
#ifndef CS_ENABLE_TO64
|
||||
#define CS_ENABLE_TO64 0
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Expands to a string representation of its argument: e.g.
|
||||
* `CS_STRINGIFY_LIT(5) expands to "5"`
|
||||
*/
|
||||
#if !defined(_MSC_VER) || _MSC_VER >= 1900
|
||||
#define CS_STRINGIFY_LIT(...) #__VA_ARGS__
|
||||
#else
|
||||
#define CS_STRINGIFY_LIT(x) #x
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Expands to a string representation of its argument, which is allowed
|
||||
* to be a macro: e.g.
|
||||
*
|
||||
* #define FOO 123
|
||||
* CS_STRINGIFY_MACRO(FOO)
|
||||
*
|
||||
* expands to 123.
|
||||
*/
|
||||
#define CS_STRINGIFY_MACRO(x) CS_STRINGIFY_LIT(x)
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Equivalent of standard `strnlen()`.
|
||||
*/
|
||||
size_t c_strnlen(const char* s, size_t maxlen);
|
||||
|
||||
/*
|
||||
* Equivalent of standard `snprintf()`.
|
||||
*/
|
||||
int c_snprintf(char* buf, size_t buf_size, const char* format, ...) PRINTF_LIKE(3, 4);
|
||||
|
||||
/*
|
||||
* Equivalent of standard `vsnprintf()`.
|
||||
*/
|
||||
int c_vsnprintf(char* buf, size_t buf_size, const char* format, va_list ap);
|
||||
|
||||
/*
|
||||
* Find the first occurrence of find in s, where the search is limited to the
|
||||
* first slen characters of s.
|
||||
*/
|
||||
const char* c_strnstr(const char* s, const char* find, size_t slen);
|
||||
|
||||
/*
|
||||
* Stringify binary data. Output buffer size must be 2 * size_of_input + 1
|
||||
* because each byte of input takes 2 bytes in string representation
|
||||
* plus 1 byte for the terminating \0 character.
|
||||
*/
|
||||
void cs_to_hex(char* to, const unsigned char* p, size_t len);
|
||||
|
||||
/*
|
||||
* Convert stringified binary data back to binary.
|
||||
* Does the reverse of `cs_to_hex()`.
|
||||
*/
|
||||
void cs_from_hex(char* to, const char* p, size_t len);
|
||||
|
||||
#if CS_ENABLE_STRDUP
|
||||
/*
|
||||
* Equivalent of standard `strdup()`, defined if only `CS_ENABLE_STRDUP` is 1.
|
||||
*/
|
||||
char* strdup(const char* src);
|
||||
#endif
|
||||
|
||||
#if CS_ENABLE_TO64
|
||||
#include <stdint.h>
|
||||
/*
|
||||
* Simple string -> int64 conversion routine.
|
||||
*/
|
||||
int64_t cs_to64(const char* s);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Cross-platform version of `strncasecmp()`.
|
||||
*/
|
||||
int mg_ncasecmp(const char* s1, const char* s2, size_t len);
|
||||
|
||||
/*
|
||||
* Cross-platform version of `strcasecmp()`.
|
||||
*/
|
||||
int mg_casecmp(const char* s1, const char* s2);
|
||||
|
||||
/*
|
||||
* Prints message to the buffer. If the buffer is large enough to hold the
|
||||
* message, it returns buffer. If buffer is to small, it allocates a large
|
||||
* enough buffer on heap and returns allocated buffer.
|
||||
* This is a supposed use case:
|
||||
*
|
||||
* ```c
|
||||
* char buf[5], *p = buf;
|
||||
* mg_avprintf(&p, sizeof(buf), "%s", "hi there");
|
||||
* use_p_somehow(p);
|
||||
* if (p != buf) {
|
||||
* free(p);
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* The purpose of this is to avoid malloc-ing if generated strings are small.
|
||||
*/
|
||||
int mg_asprintf(char** buf, size_t size, const char* fmt, ...) PRINTF_LIKE(3, 4);
|
||||
|
||||
/* Same as mg_asprintf, but takes varargs list. */
|
||||
int mg_avprintf(char** buf, size_t size, const char* fmt, va_list ap);
|
||||
|
||||
/*
|
||||
* A helper function for traversing a comma separated list of values.
|
||||
* It returns a list pointer shifted to the next value or NULL if the end
|
||||
* of the list found.
|
||||
* The value is stored in a val vector. If the value has a form "x=y", then
|
||||
* eq_val vector is initialised to point to the "y" part, and val vector length
|
||||
* is adjusted to point only to "x".
|
||||
* If the list is just a comma separated list of entries, like "aa,bb,cc" then
|
||||
* `eq_val` will contain zero-length string.
|
||||
*
|
||||
* The purpose of this function is to parse comma separated string without
|
||||
* any copying/memory allocation.
|
||||
*/
|
||||
const char* mg_next_comma_list_entry(const char* list, struct mg_str* val, struct mg_str* eq_val);
|
||||
|
||||
/*
|
||||
* Like `mg_next_comma_list_entry()`, but takes `list` as `struct mg_str`.
|
||||
* NB: Test return value's .p, not .len. On last itreation that yields result
|
||||
* .len will be 0 but .p will not. When finished, .p will be NULL.
|
||||
*/
|
||||
struct mg_str
|
||||
mg_next_comma_list_entry_n(struct mg_str list, struct mg_str* val, struct mg_str* eq_val);
|
||||
|
||||
/*
|
||||
* Matches 0-terminated string (mg_match_prefix) or string with given length
|
||||
* mg_match_prefix_n against a glob pattern. Glob syntax:
|
||||
* ```
|
||||
* - * matches zero or more characters until a slash character /
|
||||
* - ** matches zero or more characters
|
||||
* - ? Matches exactly one character which is not a slash /
|
||||
* - | or , divides alternative patterns
|
||||
* - any other character matches itself
|
||||
* ```
|
||||
* Match is case-insensitive. Return number of bytes matched.
|
||||
* Examples:
|
||||
* ```
|
||||
* mg_match_prefix("a*f", len, "abcdefgh") == 6
|
||||
* mg_match_prefix("a*f", len, "abcdexgh") == 0
|
||||
* mg_match_prefix("a*f|de*,xy", len, "defgh") == 5
|
||||
* mg_match_prefix("?*", len, "abc") == 3
|
||||
* mg_match_prefix("?*", len, "") == 0
|
||||
* ```
|
||||
*/
|
||||
size_t mg_match_prefix(const char* pattern, int pattern_len, const char* str);
|
||||
|
||||
/*
|
||||
* Like `mg_match_prefix()`, but takes `pattern` and `str` as `struct mg_str`.
|
||||
*/
|
||||
size_t mg_match_prefix_n(const struct mg_str pattern, const struct mg_str str);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CS_COMMON_STR_UTIL_H_ */
|
||||
553
lib/mjs/ffi/ffi.c
Normal file
553
lib/mjs/ffi/ffi.c
Normal file
@@ -0,0 +1,553 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#include "ffi.h"
|
||||
|
||||
#define IS_W(arg) ((arg).ctype == FFI_CTYPE_WORD)
|
||||
#define IS_D(arg) ((arg).ctype == FFI_CTYPE_DOUBLE)
|
||||
#define IS_F(arg) ((arg).ctype == FFI_CTYPE_FLOAT)
|
||||
|
||||
#define W(arg) ((ffi_word_t)(arg).v.i)
|
||||
#define D(arg) ((arg).v.d)
|
||||
#define F(arg) ((arg).v.f)
|
||||
|
||||
void ffi_set_word(struct ffi_arg* arg, ffi_word_t v) {
|
||||
arg->ctype = FFI_CTYPE_WORD;
|
||||
arg->v.i = v;
|
||||
}
|
||||
|
||||
void ffi_set_bool(struct ffi_arg* arg, bool v) {
|
||||
arg->ctype = FFI_CTYPE_BOOL;
|
||||
arg->v.i = v;
|
||||
}
|
||||
|
||||
void ffi_set_ptr(struct ffi_arg* arg, void* v) {
|
||||
ffi_set_word(arg, (ffi_word_t)v);
|
||||
}
|
||||
|
||||
void ffi_set_double(struct ffi_arg* arg, double v) {
|
||||
arg->ctype = FFI_CTYPE_DOUBLE;
|
||||
arg->v.d = v;
|
||||
}
|
||||
|
||||
void ffi_set_float(struct ffi_arg* arg, float v) {
|
||||
arg->ctype = FFI_CTYPE_FLOAT;
|
||||
arg->v.f = v;
|
||||
}
|
||||
|
||||
/*
|
||||
* The ARM ABI uses only 4 32-bit registers for paramter passing.
|
||||
* Xtensa call0 calling-convention (as used by Espressif) has 6.
|
||||
*
|
||||
* Focusing only on implementing FFI with registers means we can simplify a lot.
|
||||
*
|
||||
* ARM has some quasi-alignment rules when mixing double and integers as
|
||||
* arguments. Only:
|
||||
* a) double, int32_t, int32_t
|
||||
* b) int32_t, double
|
||||
* would fit in 4 registers. (the same goes for uint64_t).
|
||||
*
|
||||
* In order to simplify further, when a double-width argument is present, we
|
||||
* allow only two arguments.
|
||||
*/
|
||||
|
||||
/*
|
||||
* We need to support x86_64 in order to support local tests.
|
||||
* x86_64 has more and wider registers, but unlike the two main
|
||||
* embedded platforms we target it has a separate register file for
|
||||
* integer values and for floating point values (both for passing args and
|
||||
* return values). E.g. if a double value is passed as a second argument
|
||||
* it gets passed in the first available floating point register.
|
||||
*
|
||||
* I.e, the compiler generates exactly the same code for:
|
||||
*
|
||||
* void foo(int a, double b) {...}
|
||||
*
|
||||
* and
|
||||
*
|
||||
* void foo(double b, int a) {...}
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
typedef ffi_word_t (*w4w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t);
|
||||
typedef ffi_word_t (*w5w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t);
|
||||
typedef ffi_word_t (*w6w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t);
|
||||
|
||||
typedef ffi_word_t (*wdw_t)(double, ffi_word_t);
|
||||
typedef ffi_word_t (*wwd_t)(ffi_word_t, double);
|
||||
typedef ffi_word_t (*wdd_t)(double, double);
|
||||
|
||||
typedef ffi_word_t (*wwwd_t)(ffi_word_t, ffi_word_t, double);
|
||||
typedef ffi_word_t (*wwdw_t)(ffi_word_t, double, ffi_word_t);
|
||||
typedef ffi_word_t (*wwdd_t)(ffi_word_t, double, double);
|
||||
typedef ffi_word_t (*wdww_t)(double, ffi_word_t, ffi_word_t);
|
||||
typedef ffi_word_t (*wdwd_t)(double, ffi_word_t, double);
|
||||
typedef ffi_word_t (*wddw_t)(double, double, ffi_word_t);
|
||||
typedef ffi_word_t (*wddd_t)(double, double, double);
|
||||
|
||||
typedef ffi_word_t (*wfw_t)(float, ffi_word_t);
|
||||
typedef ffi_word_t (*wwf_t)(ffi_word_t, float);
|
||||
typedef ffi_word_t (*wff_t)(float, float);
|
||||
|
||||
typedef ffi_word_t (*wwwf_t)(ffi_word_t, ffi_word_t, float);
|
||||
typedef ffi_word_t (*wwfw_t)(ffi_word_t, float, ffi_word_t);
|
||||
typedef ffi_word_t (*wwff_t)(ffi_word_t, float, float);
|
||||
typedef ffi_word_t (*wfww_t)(float, ffi_word_t, ffi_word_t);
|
||||
typedef ffi_word_t (*wfwf_t)(float, ffi_word_t, float);
|
||||
typedef ffi_word_t (*wffw_t)(float, float, ffi_word_t);
|
||||
typedef ffi_word_t (*wfff_t)(float, float, float);
|
||||
|
||||
typedef bool (*b4w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t);
|
||||
typedef bool (*b5w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t);
|
||||
typedef bool (*b6w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t);
|
||||
typedef bool (*bdw_t)(double, ffi_word_t);
|
||||
typedef bool (*bwd_t)(ffi_word_t, double);
|
||||
typedef bool (*bdd_t)(double, double);
|
||||
|
||||
typedef bool (*bwwd_t)(ffi_word_t, ffi_word_t, double);
|
||||
typedef bool (*bwdw_t)(ffi_word_t, double, ffi_word_t);
|
||||
typedef bool (*bwdd_t)(ffi_word_t, double, double);
|
||||
typedef bool (*bdww_t)(double, ffi_word_t, ffi_word_t);
|
||||
typedef bool (*bdwd_t)(double, ffi_word_t, double);
|
||||
typedef bool (*bddw_t)(double, double, ffi_word_t);
|
||||
typedef bool (*bddd_t)(double, double, double);
|
||||
|
||||
typedef bool (*bfw_t)(float, ffi_word_t);
|
||||
typedef bool (*bwf_t)(ffi_word_t, float);
|
||||
typedef bool (*bff_t)(float, float);
|
||||
|
||||
typedef bool (*bwwf_t)(ffi_word_t, ffi_word_t, float);
|
||||
typedef bool (*bwfw_t)(ffi_word_t, float, ffi_word_t);
|
||||
typedef bool (*bwff_t)(ffi_word_t, float, float);
|
||||
typedef bool (*bfww_t)(float, ffi_word_t, ffi_word_t);
|
||||
typedef bool (*bfwf_t)(float, ffi_word_t, float);
|
||||
typedef bool (*bffw_t)(float, float, ffi_word_t);
|
||||
typedef bool (*bfff_t)(float, float, float);
|
||||
|
||||
typedef double (*d4w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t);
|
||||
typedef double (*d5w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t);
|
||||
typedef double (*d6w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t);
|
||||
typedef double (*ddw_t)(double, ffi_word_t);
|
||||
typedef double (*dwd_t)(ffi_word_t, double);
|
||||
typedef double (*ddd_t)(double, double);
|
||||
|
||||
typedef double (*dwwd_t)(ffi_word_t, ffi_word_t, double);
|
||||
typedef double (*dwdw_t)(ffi_word_t, double, ffi_word_t);
|
||||
typedef double (*dwdd_t)(ffi_word_t, double, double);
|
||||
typedef double (*ddww_t)(double, ffi_word_t, ffi_word_t);
|
||||
typedef double (*ddwd_t)(double, ffi_word_t, double);
|
||||
typedef double (*dddw_t)(double, double, ffi_word_t);
|
||||
typedef double (*dddd_t)(double, double, double);
|
||||
|
||||
typedef float (*f4w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t);
|
||||
typedef float (*f5w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t);
|
||||
typedef float (*f6w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t);
|
||||
typedef float (*ffw_t)(float, ffi_word_t);
|
||||
typedef float (*fwf_t)(ffi_word_t, float);
|
||||
typedef float (*fff_t)(float, float);
|
||||
|
||||
typedef float (*fwwf_t)(ffi_word_t, ffi_word_t, float);
|
||||
typedef float (*fwfw_t)(ffi_word_t, float, ffi_word_t);
|
||||
typedef float (*fwff_t)(ffi_word_t, float, float);
|
||||
typedef float (*ffww_t)(float, ffi_word_t, ffi_word_t);
|
||||
typedef float (*ffwf_t)(float, ffi_word_t, float);
|
||||
typedef float (*fffw_t)(float, float, ffi_word_t);
|
||||
typedef float (*ffff_t)(float, float, float);
|
||||
|
||||
int ffi_call_mjs(ffi_fn_t* func, int nargs, struct ffi_arg* res, struct ffi_arg* args) {
|
||||
int i, doubles = 0, floats = 0;
|
||||
|
||||
if(nargs > 6) return -1;
|
||||
for(i = 0; i < nargs; i++) {
|
||||
doubles += (IS_D(args[i]));
|
||||
floats += (IS_F(args[i]));
|
||||
}
|
||||
|
||||
/* Doubles and floats are not supported together atm */
|
||||
if(doubles > 0 && floats > 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch(res->ctype) {
|
||||
case FFI_CTYPE_WORD: { /* {{{ */
|
||||
ffi_word_t r;
|
||||
if(doubles == 0) {
|
||||
if(floats == 0) {
|
||||
/*
|
||||
* No double and no float args: we currently support up to 6
|
||||
* word-sized arguments
|
||||
*/
|
||||
if(nargs <= 4) {
|
||||
w4w_t f = (w4w_t)func;
|
||||
r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]));
|
||||
} else if(nargs == 5) {
|
||||
w5w_t f = (w5w_t)func;
|
||||
r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]), W(args[4]));
|
||||
} else if(nargs == 6) {
|
||||
w6w_t f = (w6w_t)func;
|
||||
r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]), W(args[4]), W(args[5]));
|
||||
} else {
|
||||
abort();
|
||||
}
|
||||
} else {
|
||||
/* There are some floats */
|
||||
switch(nargs) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
if(IS_F(args[0]) && IS_F(args[1])) {
|
||||
wff_t f = (wff_t)func;
|
||||
r = f(F(args[0]), F(args[1]));
|
||||
} else if(IS_F(args[0])) {
|
||||
wfw_t f = (wfw_t)func;
|
||||
r = f(F(args[0]), W(args[1]));
|
||||
} else {
|
||||
wwf_t f = (wwf_t)func;
|
||||
r = f(W(args[0]), F(args[1]));
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
if(IS_W(args[0]) && IS_W(args[1]) && IS_F(args[2])) {
|
||||
wwwf_t f = (wwwf_t)func;
|
||||
r = f(W(args[0]), W(args[1]), F(args[2]));
|
||||
} else if(IS_W(args[0]) && IS_F(args[1]) && IS_W(args[2])) {
|
||||
wwfw_t f = (wwfw_t)func;
|
||||
r = f(W(args[0]), F(args[1]), W(args[2]));
|
||||
} else if(IS_W(args[0]) && IS_F(args[1]) && IS_F(args[2])) {
|
||||
wwff_t f = (wwff_t)func;
|
||||
r = f(W(args[0]), F(args[1]), F(args[2]));
|
||||
} else if(IS_F(args[0]) && IS_W(args[1]) && IS_W(args[2])) {
|
||||
wfww_t f = (wfww_t)func;
|
||||
r = f(F(args[0]), W(args[1]), W(args[2]));
|
||||
} else if(IS_F(args[0]) && IS_W(args[1]) && IS_F(args[2])) {
|
||||
wfwf_t f = (wfwf_t)func;
|
||||
r = f(F(args[0]), W(args[1]), F(args[2]));
|
||||
} else if(IS_F(args[0]) && IS_F(args[1]) && IS_W(args[2])) {
|
||||
wffw_t f = (wffw_t)func;
|
||||
r = f(F(args[0]), F(args[1]), W(args[2]));
|
||||
} else if(IS_F(args[0]) && IS_F(args[1]) && IS_F(args[2])) {
|
||||
wfff_t f = (wfff_t)func;
|
||||
r = f(F(args[0]), F(args[1]), F(args[2]));
|
||||
} else {
|
||||
// The above checks should be exhaustive
|
||||
abort();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* There are some doubles */
|
||||
switch(nargs) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
if(IS_D(args[0]) && IS_D(args[1])) {
|
||||
wdd_t f = (wdd_t)func;
|
||||
r = f(D(args[0]), D(args[1]));
|
||||
} else if(IS_D(args[0])) {
|
||||
wdw_t f = (wdw_t)func;
|
||||
r = f(D(args[0]), W(args[1]));
|
||||
} else {
|
||||
wwd_t f = (wwd_t)func;
|
||||
r = f(W(args[0]), D(args[1]));
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
if(IS_W(args[0]) && IS_W(args[1]) && IS_D(args[2])) {
|
||||
wwwd_t f = (wwwd_t)func;
|
||||
r = f(W(args[0]), W(args[1]), D(args[2]));
|
||||
} else if(IS_W(args[0]) && IS_D(args[1]) && IS_W(args[2])) {
|
||||
wwdw_t f = (wwdw_t)func;
|
||||
r = f(W(args[0]), D(args[1]), W(args[2]));
|
||||
} else if(IS_W(args[0]) && IS_D(args[1]) && IS_D(args[2])) {
|
||||
wwdd_t f = (wwdd_t)func;
|
||||
r = f(W(args[0]), D(args[1]), D(args[2]));
|
||||
} else if(IS_D(args[0]) && IS_W(args[1]) && IS_W(args[2])) {
|
||||
wdww_t f = (wdww_t)func;
|
||||
r = f(D(args[0]), W(args[1]), W(args[2]));
|
||||
} else if(IS_D(args[0]) && IS_W(args[1]) && IS_D(args[2])) {
|
||||
wdwd_t f = (wdwd_t)func;
|
||||
r = f(D(args[0]), W(args[1]), D(args[2]));
|
||||
} else if(IS_D(args[0]) && IS_D(args[1]) && IS_W(args[2])) {
|
||||
wddw_t f = (wddw_t)func;
|
||||
r = f(D(args[0]), D(args[1]), W(args[2]));
|
||||
} else if(IS_D(args[0]) && IS_D(args[1]) && IS_D(args[2])) {
|
||||
wddd_t f = (wddd_t)func;
|
||||
r = f(D(args[0]), D(args[1]), D(args[2]));
|
||||
} else {
|
||||
// The above checks should be exhaustive
|
||||
abort();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
res->v.i = (uint64_t)r;
|
||||
} break; /* }}} */
|
||||
case FFI_CTYPE_BOOL: { /* {{{ */
|
||||
ffi_word_t r;
|
||||
if(doubles == 0) {
|
||||
if(floats == 0) {
|
||||
/*
|
||||
* No double and no float args: we currently support up to 6
|
||||
* word-sized arguments
|
||||
*/
|
||||
if(nargs <= 4) {
|
||||
b4w_t f = (b4w_t)func;
|
||||
r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]));
|
||||
} else if(nargs == 5) {
|
||||
b5w_t f = (b5w_t)func;
|
||||
r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]), W(args[4]));
|
||||
} else if(nargs == 6) {
|
||||
b6w_t f = (b6w_t)func;
|
||||
r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]), W(args[4]), W(args[5]));
|
||||
} else {
|
||||
abort();
|
||||
}
|
||||
} else {
|
||||
/* There are some floats */
|
||||
switch(nargs) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
if(IS_F(args[0]) && IS_F(args[1])) {
|
||||
bff_t f = (bff_t)func;
|
||||
r = f(F(args[0]), F(args[1]));
|
||||
} else if(IS_F(args[0])) {
|
||||
bfw_t f = (bfw_t)func;
|
||||
r = f(F(args[0]), W(args[1]));
|
||||
} else {
|
||||
bwf_t f = (bwf_t)func;
|
||||
r = f(W(args[0]), F(args[1]));
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
if(IS_W(args[0]) && IS_W(args[1]) && IS_F(args[2])) {
|
||||
bwwf_t f = (bwwf_t)func;
|
||||
r = f(W(args[0]), W(args[1]), F(args[2]));
|
||||
} else if(IS_W(args[0]) && IS_F(args[1]) && IS_W(args[2])) {
|
||||
bwfw_t f = (bwfw_t)func;
|
||||
r = f(W(args[0]), F(args[1]), W(args[2]));
|
||||
} else if(IS_W(args[0]) && IS_F(args[1]) && IS_F(args[2])) {
|
||||
bwff_t f = (bwff_t)func;
|
||||
r = f(W(args[0]), F(args[1]), F(args[2]));
|
||||
} else if(IS_F(args[0]) && IS_W(args[1]) && IS_W(args[2])) {
|
||||
bfww_t f = (bfww_t)func;
|
||||
r = f(F(args[0]), W(args[1]), W(args[2]));
|
||||
} else if(IS_F(args[0]) && IS_W(args[1]) && IS_F(args[2])) {
|
||||
bfwf_t f = (bfwf_t)func;
|
||||
r = f(F(args[0]), W(args[1]), F(args[2]));
|
||||
} else if(IS_F(args[0]) && IS_F(args[1]) && IS_W(args[2])) {
|
||||
bffw_t f = (bffw_t)func;
|
||||
r = f(F(args[0]), F(args[1]), W(args[2]));
|
||||
} else if(IS_F(args[0]) && IS_F(args[1]) && IS_F(args[2])) {
|
||||
bfff_t f = (bfff_t)func;
|
||||
r = f(F(args[0]), F(args[1]), F(args[2]));
|
||||
} else {
|
||||
// The above checks should be exhaustive
|
||||
abort();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* There are some doubles */
|
||||
switch(nargs) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
if(IS_D(args[0]) && IS_D(args[1])) {
|
||||
bdd_t f = (bdd_t)func;
|
||||
r = f(D(args[0]), D(args[1]));
|
||||
} else if(IS_D(args[0])) {
|
||||
bdw_t f = (bdw_t)func;
|
||||
r = f(D(args[0]), W(args[1]));
|
||||
} else {
|
||||
bwd_t f = (bwd_t)func;
|
||||
r = f(W(args[0]), D(args[1]));
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
if(IS_W(args[0]) && IS_W(args[1]) && IS_D(args[2])) {
|
||||
bwwd_t f = (bwwd_t)func;
|
||||
r = f(W(args[0]), W(args[1]), D(args[2]));
|
||||
} else if(IS_W(args[0]) && IS_D(args[1]) && IS_W(args[2])) {
|
||||
bwdw_t f = (bwdw_t)func;
|
||||
r = f(W(args[0]), D(args[1]), W(args[2]));
|
||||
} else if(IS_W(args[0]) && IS_D(args[1]) && IS_D(args[2])) {
|
||||
bwdd_t f = (bwdd_t)func;
|
||||
r = f(W(args[0]), D(args[1]), D(args[2]));
|
||||
} else if(IS_D(args[0]) && IS_W(args[1]) && IS_W(args[2])) {
|
||||
bdww_t f = (bdww_t)func;
|
||||
r = f(D(args[0]), W(args[1]), W(args[2]));
|
||||
} else if(IS_D(args[0]) && IS_W(args[1]) && IS_D(args[2])) {
|
||||
bdwd_t f = (bdwd_t)func;
|
||||
r = f(D(args[0]), W(args[1]), D(args[2]));
|
||||
} else if(IS_D(args[0]) && IS_D(args[1]) && IS_W(args[2])) {
|
||||
bddw_t f = (bddw_t)func;
|
||||
r = f(D(args[0]), D(args[1]), W(args[2]));
|
||||
} else if(IS_D(args[0]) && IS_D(args[1]) && IS_D(args[2])) {
|
||||
bddd_t f = (bddd_t)func;
|
||||
r = f(D(args[0]), D(args[1]), D(args[2]));
|
||||
} else {
|
||||
// The above checks should be exhaustive
|
||||
abort();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
res->v.i = (uint64_t)r;
|
||||
} break; /* }}} */
|
||||
case FFI_CTYPE_DOUBLE: { /* {{{ */
|
||||
double r;
|
||||
if(doubles == 0) {
|
||||
/* No double args: we currently support up to 6 word-sized arguments
|
||||
*/
|
||||
if(nargs <= 4) {
|
||||
d4w_t f = (d4w_t)func;
|
||||
r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]));
|
||||
} else if(nargs == 5) {
|
||||
d5w_t f = (d5w_t)func;
|
||||
r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]), W(args[4]));
|
||||
} else if(nargs == 6) {
|
||||
d6w_t f = (d6w_t)func;
|
||||
r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]), W(args[4]), W(args[5]));
|
||||
} else {
|
||||
abort();
|
||||
}
|
||||
} else {
|
||||
switch(nargs) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
if(IS_D(args[0]) && IS_D(args[1])) {
|
||||
ddd_t f = (ddd_t)func;
|
||||
r = f(D(args[0]), D(args[1]));
|
||||
} else if(IS_D(args[0])) {
|
||||
ddw_t f = (ddw_t)func;
|
||||
r = f(D(args[0]), W(args[1]));
|
||||
} else {
|
||||
dwd_t f = (dwd_t)func;
|
||||
r = f(W(args[0]), D(args[1]));
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
if(IS_W(args[0]) && IS_W(args[1]) && IS_D(args[2])) {
|
||||
dwwd_t f = (dwwd_t)func;
|
||||
r = f(W(args[0]), W(args[1]), D(args[2]));
|
||||
} else if(IS_W(args[0]) && IS_D(args[1]) && IS_W(args[2])) {
|
||||
dwdw_t f = (dwdw_t)func;
|
||||
r = f(W(args[0]), D(args[1]), W(args[2]));
|
||||
} else if(IS_W(args[0]) && IS_D(args[1]) && IS_D(args[2])) {
|
||||
dwdd_t f = (dwdd_t)func;
|
||||
r = f(W(args[0]), D(args[1]), D(args[2]));
|
||||
} else if(IS_D(args[0]) && IS_W(args[1]) && IS_W(args[2])) {
|
||||
ddww_t f = (ddww_t)func;
|
||||
r = f(D(args[0]), W(args[1]), W(args[2]));
|
||||
} else if(IS_D(args[0]) && IS_W(args[1]) && IS_D(args[2])) {
|
||||
ddwd_t f = (ddwd_t)func;
|
||||
r = f(D(args[0]), W(args[1]), D(args[2]));
|
||||
} else if(IS_D(args[0]) && IS_D(args[1]) && IS_W(args[2])) {
|
||||
dddw_t f = (dddw_t)func;
|
||||
r = f(D(args[0]), D(args[1]), W(args[2]));
|
||||
} else if(IS_D(args[0]) && IS_D(args[1]) && IS_D(args[2])) {
|
||||
dddd_t f = (dddd_t)func;
|
||||
r = f(D(args[0]), D(args[1]), D(args[2]));
|
||||
} else {
|
||||
// The above checks should be exhaustive
|
||||
abort();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
res->v.d = r;
|
||||
} break; /* }}} */
|
||||
case FFI_CTYPE_FLOAT: { /* {{{ */
|
||||
double r;
|
||||
if(floats == 0) {
|
||||
/* No float args: we currently support up to 6 word-sized arguments
|
||||
*/
|
||||
if(nargs <= 4) {
|
||||
f4w_t f = (f4w_t)func;
|
||||
r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]));
|
||||
} else if(nargs == 5) {
|
||||
f5w_t f = (f5w_t)func;
|
||||
r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]), W(args[4]));
|
||||
} else if(nargs == 6) {
|
||||
f6w_t f = (f6w_t)func;
|
||||
r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]), W(args[4]), W(args[5]));
|
||||
} else {
|
||||
abort();
|
||||
}
|
||||
} else {
|
||||
/* There are some float args */
|
||||
switch(nargs) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
if(IS_F(args[0]) && IS_F(args[1])) {
|
||||
fff_t f = (fff_t)func;
|
||||
r = f(F(args[0]), F(args[1]));
|
||||
} else if(IS_F(args[0])) {
|
||||
ffw_t f = (ffw_t)func;
|
||||
r = f(F(args[0]), W(args[1]));
|
||||
} else {
|
||||
fwf_t f = (fwf_t)func;
|
||||
r = f(W(args[0]), F(args[1]));
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
if(IS_W(args[0]) && IS_W(args[1]) && IS_F(args[2])) {
|
||||
fwwf_t f = (fwwf_t)func;
|
||||
r = f(W(args[0]), W(args[1]), F(args[2]));
|
||||
} else if(IS_W(args[0]) && IS_F(args[1]) && IS_W(args[2])) {
|
||||
fwfw_t f = (fwfw_t)func;
|
||||
r = f(W(args[0]), F(args[1]), W(args[2]));
|
||||
} else if(IS_W(args[0]) && IS_F(args[1]) && IS_F(args[2])) {
|
||||
fwff_t f = (fwff_t)func;
|
||||
r = f(W(args[0]), F(args[1]), F(args[2]));
|
||||
} else if(IS_F(args[0]) && IS_W(args[1]) && IS_W(args[2])) {
|
||||
ffww_t f = (ffww_t)func;
|
||||
r = f(F(args[0]), W(args[1]), W(args[2]));
|
||||
} else if(IS_F(args[0]) && IS_W(args[1]) && IS_F(args[2])) {
|
||||
ffwf_t f = (ffwf_t)func;
|
||||
r = f(F(args[0]), W(args[1]), F(args[2]));
|
||||
} else if(IS_F(args[0]) && IS_F(args[1]) && IS_W(args[2])) {
|
||||
fffw_t f = (fffw_t)func;
|
||||
r = f(F(args[0]), F(args[1]), W(args[2]));
|
||||
} else if(IS_F(args[0]) && IS_F(args[1]) && IS_F(args[2])) {
|
||||
ffff_t f = (ffff_t)func;
|
||||
r = f(F(args[0]), F(args[1]), F(args[2]));
|
||||
} else {
|
||||
// The above checks should be exhaustive
|
||||
abort();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
res->v.f = r;
|
||||
} break; /* }}} */
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
53
lib/mjs/ffi/ffi.h
Normal file
53
lib/mjs/ffi/ffi.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef MJS_FFI_FFI_H_
|
||||
#define MJS_FFI_FFI_H_
|
||||
|
||||
#include "../common/platform.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/*
|
||||
* Maximum number of word-sized args to ffi-ed function. If at least one
|
||||
* of the args is double, only 2 args are allowed.
|
||||
*/
|
||||
#define FFI_MAX_ARGS_CNT 6
|
||||
|
||||
typedef void(ffi_fn_t)(void);
|
||||
|
||||
typedef intptr_t ffi_word_t;
|
||||
|
||||
enum ffi_ctype {
|
||||
FFI_CTYPE_WORD,
|
||||
FFI_CTYPE_BOOL,
|
||||
FFI_CTYPE_FLOAT,
|
||||
FFI_CTYPE_DOUBLE,
|
||||
};
|
||||
|
||||
struct ffi_arg {
|
||||
enum ffi_ctype ctype;
|
||||
union {
|
||||
uint64_t i;
|
||||
double d;
|
||||
float f;
|
||||
} v;
|
||||
};
|
||||
|
||||
int ffi_call_mjs(ffi_fn_t* func, int nargs, struct ffi_arg* res, struct ffi_arg* args);
|
||||
|
||||
void ffi_set_word(struct ffi_arg* arg, ffi_word_t v);
|
||||
void ffi_set_bool(struct ffi_arg* arg, bool v);
|
||||
void ffi_set_ptr(struct ffi_arg* arg, void* v);
|
||||
void ffi_set_double(struct ffi_arg* arg, double v);
|
||||
void ffi_set_float(struct ffi_arg* arg, float v);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MJS_FFI_FFI_H_ */
|
||||
232
lib/mjs/mjs_array.c
Normal file
232
lib/mjs/mjs_array.c
Normal file
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* Copyright (c) 2017 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "common/str_util.h"
|
||||
#include "mjs_array.h"
|
||||
#include "mjs_core.h"
|
||||
#include "mjs_internal.h"
|
||||
#include "mjs_object.h"
|
||||
#include "mjs_primitive.h"
|
||||
#include "mjs_string.h"
|
||||
#include "mjs_util.h"
|
||||
|
||||
#define SPLICE_NEW_ITEM_IDX 2
|
||||
|
||||
/* like c_snprintf but returns `size` if write is truncated */
|
||||
static int v_sprintf_s(char* buf, size_t size, const char* fmt, ...) {
|
||||
size_t n;
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
n = c_vsnprintf(buf, size, fmt, ap);
|
||||
if(n > size) {
|
||||
return size;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
mjs_val_t mjs_mk_array(struct mjs* mjs) {
|
||||
mjs_val_t ret = mjs_mk_object(mjs);
|
||||
/* change the tag to MJS_TAG_ARRAY */
|
||||
ret &= ~MJS_TAG_MASK;
|
||||
ret |= MJS_TAG_ARRAY;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mjs_is_array(mjs_val_t v) {
|
||||
return (v & MJS_TAG_MASK) == MJS_TAG_ARRAY;
|
||||
}
|
||||
|
||||
mjs_val_t mjs_array_get(struct mjs* mjs, mjs_val_t arr, unsigned long index) {
|
||||
return mjs_array_get2(mjs, arr, index, NULL);
|
||||
}
|
||||
|
||||
mjs_val_t mjs_array_get2(struct mjs* mjs, mjs_val_t arr, unsigned long index, int* has) {
|
||||
mjs_val_t res = MJS_UNDEFINED;
|
||||
|
||||
if(has != NULL) {
|
||||
*has = 0;
|
||||
}
|
||||
|
||||
if(mjs_is_object(arr)) {
|
||||
struct mjs_property* p;
|
||||
char buf[20];
|
||||
int n = v_sprintf_s(buf, sizeof(buf), "%lu", index);
|
||||
p = mjs_get_own_property(mjs, arr, buf, n);
|
||||
if(p != NULL) {
|
||||
if(has != NULL) {
|
||||
*has = 1;
|
||||
}
|
||||
res = p->value;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
unsigned long mjs_array_length(struct mjs* mjs, mjs_val_t v) {
|
||||
struct mjs_property* p;
|
||||
unsigned long len = 0;
|
||||
|
||||
if(!mjs_is_object(v)) {
|
||||
len = 0;
|
||||
goto clean;
|
||||
}
|
||||
|
||||
for(p = get_object_struct(v)->properties; p != NULL; p = p->next) {
|
||||
int ok = 0;
|
||||
unsigned long n = 0;
|
||||
str_to_ulong(mjs, p->name, &ok, &n);
|
||||
if(ok && n >= len && n < 0xffffffff) {
|
||||
len = n + 1;
|
||||
}
|
||||
}
|
||||
|
||||
clean:
|
||||
return len;
|
||||
}
|
||||
|
||||
mjs_err_t mjs_array_set(struct mjs* mjs, mjs_val_t arr, unsigned long index, mjs_val_t v) {
|
||||
mjs_err_t ret = MJS_OK;
|
||||
|
||||
if(mjs_is_object(arr)) {
|
||||
char buf[20];
|
||||
int n = v_sprintf_s(buf, sizeof(buf), "%lu", index);
|
||||
ret = mjs_set(mjs, arr, buf, n, v);
|
||||
} else {
|
||||
ret = MJS_TYPE_ERROR;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void mjs_array_del(struct mjs* mjs, mjs_val_t arr, unsigned long index) {
|
||||
char buf[20];
|
||||
int n = v_sprintf_s(buf, sizeof(buf), "%lu", index);
|
||||
mjs_del(mjs, arr, buf, n);
|
||||
}
|
||||
|
||||
mjs_err_t mjs_array_push(struct mjs* mjs, mjs_val_t arr, mjs_val_t v) {
|
||||
return mjs_array_set(mjs, arr, mjs_array_length(mjs, arr), v);
|
||||
}
|
||||
|
||||
MJS_PRIVATE void mjs_array_push_internal(struct mjs* mjs) {
|
||||
mjs_err_t rcode = MJS_OK;
|
||||
mjs_val_t ret = MJS_UNDEFINED;
|
||||
int nargs = mjs_nargs(mjs);
|
||||
int i;
|
||||
|
||||
/* Make sure that `this` is an array */
|
||||
if(!mjs_check_arg(mjs, -1 /*this*/, "this", MJS_TYPE_OBJECT_ARRAY, NULL)) {
|
||||
goto clean;
|
||||
}
|
||||
|
||||
/* Push all args */
|
||||
for(i = 0; i < nargs; i++) {
|
||||
rcode = mjs_array_push(mjs, mjs->vals.this_obj, mjs_arg(mjs, i));
|
||||
if(rcode != MJS_OK) {
|
||||
mjs_prepend_errorf(mjs, rcode, "");
|
||||
goto clean;
|
||||
}
|
||||
}
|
||||
|
||||
/* Return the new array length */
|
||||
ret = mjs_mk_number(mjs, mjs_array_length(mjs, mjs->vals.this_obj));
|
||||
|
||||
clean:
|
||||
mjs_return(mjs, ret);
|
||||
return;
|
||||
}
|
||||
|
||||
static void move_item(struct mjs* mjs, mjs_val_t arr, unsigned long from, unsigned long to) {
|
||||
mjs_val_t cur = mjs_array_get(mjs, arr, from);
|
||||
mjs_array_set(mjs, arr, to, cur);
|
||||
mjs_array_del(mjs, arr, from);
|
||||
}
|
||||
|
||||
MJS_PRIVATE void mjs_array_splice(struct mjs* mjs) {
|
||||
int nargs = mjs_nargs(mjs);
|
||||
mjs_err_t rcode = MJS_OK;
|
||||
mjs_val_t ret = mjs_mk_array(mjs);
|
||||
mjs_val_t start_v = MJS_UNDEFINED;
|
||||
mjs_val_t deleteCount_v = MJS_UNDEFINED;
|
||||
int start = 0;
|
||||
int arr_len;
|
||||
int delete_cnt = 0;
|
||||
int new_items_cnt = 0;
|
||||
int delta = 0;
|
||||
int i;
|
||||
|
||||
/* Make sure that `this` is an array */
|
||||
if(!mjs_check_arg(mjs, -1 /*this*/, "this", MJS_TYPE_OBJECT_ARRAY, NULL)) {
|
||||
goto clean;
|
||||
}
|
||||
|
||||
/* Get array length */
|
||||
arr_len = mjs_array_length(mjs, mjs->vals.this_obj);
|
||||
|
||||
/* get start from arg 0 */
|
||||
if(!mjs_check_arg(mjs, 0, "start", MJS_TYPE_NUMBER, &start_v)) {
|
||||
goto clean;
|
||||
}
|
||||
start = mjs_normalize_idx(mjs_get_int(mjs, start_v), arr_len);
|
||||
|
||||
/* Handle deleteCount */
|
||||
if(nargs >= SPLICE_NEW_ITEM_IDX) {
|
||||
/* deleteCount is given; use it */
|
||||
if(!mjs_check_arg(mjs, 1, "deleteCount", MJS_TYPE_NUMBER, &deleteCount_v)) {
|
||||
goto clean;
|
||||
}
|
||||
delete_cnt = mjs_get_int(mjs, deleteCount_v);
|
||||
new_items_cnt = nargs - SPLICE_NEW_ITEM_IDX;
|
||||
} else {
|
||||
/* deleteCount is not given; assume the end of the array */
|
||||
delete_cnt = arr_len - start;
|
||||
}
|
||||
if(delete_cnt > arr_len - start) {
|
||||
delete_cnt = arr_len - start;
|
||||
} else if(delete_cnt < 0) {
|
||||
delete_cnt = 0;
|
||||
}
|
||||
|
||||
/* delta at which subsequent array items should be moved */
|
||||
delta = new_items_cnt - delete_cnt;
|
||||
|
||||
/*
|
||||
* copy items which are going to be deleted to the separate array (will be
|
||||
* returned)
|
||||
*/
|
||||
for(i = 0; i < delete_cnt; i++) {
|
||||
mjs_val_t cur = mjs_array_get(mjs, mjs->vals.this_obj, start + i);
|
||||
rcode = mjs_array_push(mjs, ret, cur);
|
||||
if(rcode != MJS_OK) {
|
||||
mjs_prepend_errorf(mjs, rcode, "");
|
||||
goto clean;
|
||||
}
|
||||
}
|
||||
|
||||
/* If needed, move subsequent items */
|
||||
if(delta < 0) {
|
||||
for(i = start; i < arr_len; i++) {
|
||||
if(i >= start - delta) {
|
||||
move_item(mjs, mjs->vals.this_obj, i, i + delta);
|
||||
} else {
|
||||
mjs_array_del(mjs, mjs->vals.this_obj, i);
|
||||
}
|
||||
}
|
||||
} else if(delta > 0) {
|
||||
for(i = arr_len - 1; i >= start; i--) {
|
||||
move_item(mjs, mjs->vals.this_obj, i, i + delta);
|
||||
}
|
||||
}
|
||||
|
||||
/* Set new items to the array */
|
||||
for(i = 0; i < nargs - SPLICE_NEW_ITEM_IDX; i++) {
|
||||
mjs_array_set(mjs, mjs->vals.this_obj, start + i, mjs_arg(mjs, SPLICE_NEW_ITEM_IDX + i));
|
||||
}
|
||||
|
||||
clean:
|
||||
mjs_return(mjs, ret);
|
||||
}
|
||||
26
lib/mjs/mjs_array.h
Normal file
26
lib/mjs/mjs_array.h
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2014 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef MJS_ARRAY_H_
|
||||
#define MJS_ARRAY_H_
|
||||
|
||||
#include "mjs_internal.h"
|
||||
#include "mjs_array_public.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
MJS_PRIVATE mjs_val_t mjs_array_get2(struct mjs* mjs, mjs_val_t arr, unsigned long index, int* has);
|
||||
|
||||
MJS_PRIVATE void mjs_array_splice(struct mjs* mjs);
|
||||
|
||||
MJS_PRIVATE void mjs_array_push_internal(struct mjs* mjs);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MJS_ARRAY_H_ */
|
||||
385
lib/mjs/mjs_array_buf.c
Normal file
385
lib/mjs/mjs_array_buf.c
Normal file
@@ -0,0 +1,385 @@
|
||||
#include "mjs_array_buf.h"
|
||||
#include "common/cs_varint.h"
|
||||
#include "common/mg_str.h"
|
||||
#include "mjs_core.h"
|
||||
#include "mjs_internal.h"
|
||||
#include "mjs_primitive.h"
|
||||
#include "mjs_object.h"
|
||||
#include "mjs_array.h"
|
||||
#include "mjs_util.h"
|
||||
#include "mjs_exec_public.h"
|
||||
|
||||
#ifndef MJS_ARRAY_BUF_RESERVE
|
||||
#define MJS_ARRAY_BUF_RESERVE 100
|
||||
#endif
|
||||
|
||||
#define IS_SIGNED(type) \
|
||||
(type == MJS_DATAVIEW_I8 || type == MJS_DATAVIEW_I16 || type == MJS_DATAVIEW_I32)
|
||||
|
||||
int mjs_is_array_buf(mjs_val_t v) {
|
||||
return (v & MJS_TAG_MASK) == MJS_TAG_ARRAY_BUF;
|
||||
}
|
||||
|
||||
int mjs_is_data_view(mjs_val_t v) {
|
||||
return (v & MJS_TAG_MASK) == MJS_TAG_ARRAY_BUF_VIEW;
|
||||
}
|
||||
|
||||
int mjs_is_typed_array(mjs_val_t v) {
|
||||
return ((v & MJS_TAG_MASK) == MJS_TAG_ARRAY_BUF) ||
|
||||
((v & MJS_TAG_MASK) == MJS_TAG_ARRAY_BUF_VIEW);
|
||||
}
|
||||
|
||||
char* mjs_array_buf_get_ptr(struct mjs* mjs, mjs_val_t buf, size_t* bytelen) {
|
||||
struct mbuf* m = &mjs->array_buffers;
|
||||
size_t offset = buf & ~MJS_TAG_MASK;
|
||||
char* ptr = m->buf + offset;
|
||||
|
||||
uint64_t len = 0;
|
||||
size_t header_len = 0;
|
||||
if(offset < m->len && cs_varint_decode((uint8_t*)ptr, m->len - offset, &len, &header_len)) {
|
||||
if(bytelen) {
|
||||
*bytelen = len;
|
||||
}
|
||||
return ptr + header_len;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static size_t mjs_dataview_get_element_len(mjs_dataview_type_t type) {
|
||||
size_t len = 1;
|
||||
switch(type) {
|
||||
case MJS_DATAVIEW_U8:
|
||||
case MJS_DATAVIEW_I8:
|
||||
len = 1;
|
||||
break;
|
||||
case MJS_DATAVIEW_U16:
|
||||
case MJS_DATAVIEW_I16:
|
||||
len = 2;
|
||||
break;
|
||||
case MJS_DATAVIEW_U32:
|
||||
case MJS_DATAVIEW_I32:
|
||||
len = 4;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static int64_t get_value(char* buf, mjs_dataview_type_t type) {
|
||||
int64_t value = 0;
|
||||
switch(type) {
|
||||
case MJS_DATAVIEW_U8:
|
||||
value = *(uint8_t*)buf;
|
||||
break;
|
||||
case MJS_DATAVIEW_I8:
|
||||
value = *(int8_t*)buf;
|
||||
break;
|
||||
case MJS_DATAVIEW_U16:
|
||||
value = *(uint16_t*)buf;
|
||||
break;
|
||||
case MJS_DATAVIEW_I16:
|
||||
value = *(int16_t*)buf;
|
||||
break;
|
||||
case MJS_DATAVIEW_U32:
|
||||
value = *(uint32_t*)buf;
|
||||
break;
|
||||
case MJS_DATAVIEW_I32:
|
||||
value = *(int32_t*)buf;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static void set_value(char* buf, int64_t value, mjs_dataview_type_t type) {
|
||||
switch(type) {
|
||||
case MJS_DATAVIEW_U8:
|
||||
*(uint8_t*)buf = (uint8_t)value;
|
||||
break;
|
||||
case MJS_DATAVIEW_I8:
|
||||
*(int8_t*)buf = (int8_t)value;
|
||||
break;
|
||||
case MJS_DATAVIEW_U16:
|
||||
*(uint16_t*)buf = (uint16_t)value;
|
||||
break;
|
||||
case MJS_DATAVIEW_I16:
|
||||
*(int16_t*)buf = (int16_t)value;
|
||||
break;
|
||||
case MJS_DATAVIEW_U32:
|
||||
*(uint32_t*)buf = (uint32_t)value;
|
||||
break;
|
||||
case MJS_DATAVIEW_I32:
|
||||
*(int32_t*)buf = (int32_t)value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static mjs_val_t mjs_dataview_get(struct mjs* mjs, mjs_val_t obj, size_t index) {
|
||||
mjs_val_t buf_obj = mjs_get(mjs, obj, "buffer", -1);
|
||||
|
||||
size_t byte_len = 0;
|
||||
char* buf = mjs_array_buf_get_ptr(mjs, buf_obj, &byte_len);
|
||||
mjs_dataview_type_t type = mjs_get_int(mjs, mjs_get(mjs, obj, "_t", -1));
|
||||
if((mjs_dataview_get_element_len(type) * (index + 1)) > byte_len) {
|
||||
return MJS_UNDEFINED;
|
||||
}
|
||||
|
||||
buf += mjs_dataview_get_element_len(type) * index;
|
||||
int64_t value = get_value(buf, type);
|
||||
|
||||
return mjs_mk_number(mjs, value);
|
||||
}
|
||||
|
||||
static mjs_err_t mjs_dataview_set(struct mjs* mjs, mjs_val_t obj, size_t index, int64_t value) {
|
||||
mjs_val_t buf_obj = mjs_get(mjs, obj, "buffer", -1);
|
||||
|
||||
size_t byte_len = 0;
|
||||
char* buf = mjs_array_buf_get_ptr(mjs, buf_obj, &byte_len);
|
||||
mjs_dataview_type_t type = mjs_get_int(mjs, mjs_get(mjs, obj, "_t", -1));
|
||||
if((mjs_dataview_get_element_len(type) * (index + 1)) > byte_len) {
|
||||
return MJS_TYPE_ERROR;
|
||||
}
|
||||
|
||||
buf += mjs_dataview_get_element_len(type) * index;
|
||||
set_value(buf, value, type);
|
||||
|
||||
return MJS_OK;
|
||||
}
|
||||
|
||||
mjs_val_t mjs_dataview_get_prop(struct mjs* mjs, mjs_val_t obj, mjs_val_t key) {
|
||||
if(!mjs_is_number(key)) {
|
||||
return MJS_UNDEFINED;
|
||||
}
|
||||
int index = mjs_get_int(mjs, key);
|
||||
return mjs_dataview_get(mjs, obj, index);
|
||||
}
|
||||
|
||||
mjs_err_t mjs_dataview_set_prop(struct mjs* mjs, mjs_val_t obj, mjs_val_t key, mjs_val_t val) {
|
||||
if(!mjs_is_number(key)) {
|
||||
return MJS_TYPE_ERROR;
|
||||
}
|
||||
int index = mjs_get_int(mjs, key);
|
||||
int64_t value = 0;
|
||||
|
||||
if(mjs_is_number(val)) {
|
||||
value = mjs_get_double(mjs, val);
|
||||
} else if(mjs_is_boolean(val)) {
|
||||
value = mjs_get_bool(mjs, val) ? (1) : (0);
|
||||
}
|
||||
return mjs_dataview_set(mjs, obj, index, value);
|
||||
}
|
||||
|
||||
mjs_val_t mjs_dataview_get_buf(struct mjs* mjs, mjs_val_t obj) {
|
||||
return mjs_get(mjs, obj, "buffer", -1);
|
||||
}
|
||||
|
||||
mjs_val_t mjs_dataview_get_len(struct mjs* mjs, mjs_val_t obj) {
|
||||
size_t bytelen = 0;
|
||||
mjs_array_buf_get_ptr(mjs, mjs_dataview_get_buf(mjs, obj), &bytelen);
|
||||
mjs_dataview_type_t type = mjs_get_int(mjs, mjs_get(mjs, obj, "_t", -1));
|
||||
size_t element_len = mjs_dataview_get_element_len(type);
|
||||
|
||||
return mjs_mk_number(mjs, bytelen / element_len);
|
||||
}
|
||||
|
||||
mjs_val_t mjs_mk_array_buf(struct mjs* mjs, char* data, size_t buf_len) {
|
||||
struct mbuf* m = &mjs->array_buffers;
|
||||
|
||||
if((m->len + buf_len) > m->size) {
|
||||
char* prev_buf = m->buf;
|
||||
mbuf_resize(m, m->len + buf_len + MJS_ARRAY_BUF_RESERVE);
|
||||
|
||||
if(data >= prev_buf && data < (prev_buf + m->len)) {
|
||||
data += m->buf - prev_buf;
|
||||
}
|
||||
}
|
||||
|
||||
size_t offset = m->len;
|
||||
char* prev_buf = m->buf;
|
||||
|
||||
size_t header_len = cs_varint_llen(buf_len);
|
||||
mbuf_insert(m, offset, NULL, header_len + buf_len);
|
||||
if(data >= prev_buf && data < (prev_buf + m->len)) {
|
||||
data += m->buf - prev_buf;
|
||||
}
|
||||
|
||||
cs_varint_encode(buf_len, (unsigned char*)m->buf + offset, header_len);
|
||||
|
||||
if(data != NULL) {
|
||||
memcpy(m->buf + offset + header_len, data, buf_len);
|
||||
} else {
|
||||
memset(m->buf + offset + header_len, 0, buf_len);
|
||||
}
|
||||
|
||||
return (offset & ~MJS_TAG_MASK) | MJS_TAG_ARRAY_BUF;
|
||||
}
|
||||
|
||||
void mjs_array_buf_slice(struct mjs* mjs) {
|
||||
size_t nargs = mjs_nargs(mjs);
|
||||
mjs_val_t src = mjs_get_this(mjs);
|
||||
|
||||
size_t start = 0;
|
||||
size_t end = 0;
|
||||
char* src_buf = NULL;
|
||||
size_t src_len = 0;
|
||||
|
||||
bool args_correct = false;
|
||||
do {
|
||||
if(!mjs_is_array_buf(src)) {
|
||||
break;
|
||||
}
|
||||
src_buf = mjs_array_buf_get_ptr(mjs, src, &src_len);
|
||||
|
||||
if((nargs == 0) || (nargs > 2)) {
|
||||
break;
|
||||
}
|
||||
|
||||
mjs_val_t start_obj = mjs_arg(mjs, 0);
|
||||
if(!mjs_is_number(start_obj)) {
|
||||
break;
|
||||
}
|
||||
start = mjs_get_int32(mjs, start_obj);
|
||||
|
||||
if(nargs == 2) {
|
||||
mjs_val_t end_obj = mjs_arg(mjs, 1);
|
||||
if(!mjs_is_number(end_obj)) {
|
||||
break;
|
||||
}
|
||||
end = mjs_get_int32(mjs, end_obj);
|
||||
} else {
|
||||
end = src_len - 1;
|
||||
}
|
||||
|
||||
if((start >= src_len) || (end >= src_len) || (start >= end)) {
|
||||
break;
|
||||
}
|
||||
|
||||
args_correct = true;
|
||||
} while(0);
|
||||
|
||||
if(!args_correct) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
src_buf += start;
|
||||
mjs_return(mjs, mjs_mk_array_buf(mjs, src_buf, end - start));
|
||||
}
|
||||
|
||||
static mjs_val_t
|
||||
mjs_mk_dataview_from_buf(struct mjs* mjs, mjs_val_t buf, mjs_dataview_type_t type) {
|
||||
size_t len = 0;
|
||||
mjs_array_buf_get_ptr(mjs, buf, &len);
|
||||
if(len % mjs_dataview_get_element_len(type) != 0) {
|
||||
mjs_prepend_errorf(
|
||||
mjs, MJS_BAD_ARGS_ERROR, "Buffer len is not a multiple of element size");
|
||||
return MJS_UNDEFINED;
|
||||
}
|
||||
mjs_val_t view_obj = mjs_mk_object(mjs);
|
||||
mjs_set(mjs, view_obj, "_t", ~0, mjs_mk_number(mjs, (double)type));
|
||||
mjs_set(mjs, view_obj, "buffer", ~0, buf);
|
||||
|
||||
view_obj &= ~MJS_TAG_MASK;
|
||||
view_obj |= MJS_TAG_ARRAY_BUF_VIEW;
|
||||
|
||||
mjs_dataview_get(mjs, view_obj, 0);
|
||||
|
||||
return view_obj;
|
||||
}
|
||||
|
||||
static mjs_val_t
|
||||
mjs_mk_dataview(struct mjs* mjs, size_t len, mjs_val_t arr, mjs_dataview_type_t type) {
|
||||
size_t elements_nb = 0;
|
||||
if(mjs_is_array(arr)) {
|
||||
if(!mjs_is_number(mjs_array_get(mjs, arr, 0))) {
|
||||
return MJS_UNDEFINED;
|
||||
}
|
||||
elements_nb = mjs_array_length(mjs, arr);
|
||||
} else {
|
||||
elements_nb = len;
|
||||
}
|
||||
|
||||
size_t element_len = mjs_dataview_get_element_len(type);
|
||||
mjs_val_t buf_obj = mjs_mk_array_buf(mjs, NULL, element_len * elements_nb);
|
||||
|
||||
if(mjs_is_array(arr)) {
|
||||
char* buf_ptr = mjs_array_buf_get_ptr(mjs, buf_obj, NULL);
|
||||
for(uint8_t i = 0; i < elements_nb; i++) {
|
||||
int64_t value = mjs_get_double(mjs, mjs_array_get(mjs, arr, i));
|
||||
set_value(buf_ptr, value, type);
|
||||
buf_ptr += element_len;
|
||||
}
|
||||
}
|
||||
|
||||
return mjs_mk_dataview_from_buf(mjs, buf_obj, type);
|
||||
}
|
||||
|
||||
static void mjs_array_buf_new(struct mjs* mjs) {
|
||||
mjs_val_t len_arg = mjs_arg(mjs, 0);
|
||||
mjs_val_t buf_obj = MJS_UNDEFINED;
|
||||
if(!mjs_is_number(len_arg)) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
} else {
|
||||
int len = mjs_get_int(mjs, len_arg);
|
||||
buf_obj = mjs_mk_array_buf(mjs, NULL, len);
|
||||
}
|
||||
mjs_return(mjs, buf_obj);
|
||||
}
|
||||
|
||||
static void mjs_dataview_new(struct mjs* mjs, mjs_dataview_type_t type) {
|
||||
mjs_val_t view_arg = mjs_arg(mjs, 0);
|
||||
mjs_val_t view_obj = MJS_UNDEFINED;
|
||||
|
||||
if(mjs_is_array_buf(view_arg)) { // Create a view of existing ArrayBuf
|
||||
view_obj = mjs_mk_dataview_from_buf(mjs, view_arg, type);
|
||||
} else if(mjs_is_number(view_arg)) { // Create new typed array
|
||||
int len = mjs_get_int(mjs, view_arg);
|
||||
view_obj = mjs_mk_dataview(mjs, len, MJS_UNDEFINED, type);
|
||||
} else if(mjs_is_array(view_arg)) { // Create new typed array from array
|
||||
view_obj = mjs_mk_dataview(mjs, 0, view_arg, type);
|
||||
} else {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
}
|
||||
|
||||
mjs_return(mjs, view_obj);
|
||||
}
|
||||
|
||||
static void mjs_new_u8_array(struct mjs* mjs) {
|
||||
mjs_dataview_new(mjs, MJS_DATAVIEW_U8);
|
||||
}
|
||||
|
||||
static void mjs_new_i8_array(struct mjs* mjs) {
|
||||
mjs_dataview_new(mjs, MJS_DATAVIEW_I8);
|
||||
}
|
||||
|
||||
static void mjs_new_u16_array(struct mjs* mjs) {
|
||||
mjs_dataview_new(mjs, MJS_DATAVIEW_U16);
|
||||
}
|
||||
|
||||
static void mjs_new_i16_array(struct mjs* mjs) {
|
||||
mjs_dataview_new(mjs, MJS_DATAVIEW_I16);
|
||||
}
|
||||
|
||||
static void mjs_new_u32_array(struct mjs* mjs) {
|
||||
mjs_dataview_new(mjs, MJS_DATAVIEW_U32);
|
||||
}
|
||||
|
||||
static void mjs_new_i32_array(struct mjs* mjs) {
|
||||
mjs_dataview_new(mjs, MJS_DATAVIEW_I32);
|
||||
}
|
||||
|
||||
void mjs_init_builtin_array_buf(struct mjs* mjs, mjs_val_t obj) {
|
||||
mjs_set(mjs, obj, "ArrayBuffer", ~0, MJS_MK_FN(mjs_array_buf_new));
|
||||
mjs_set(mjs, obj, "Uint8Array", ~0, MJS_MK_FN(mjs_new_u8_array));
|
||||
mjs_set(mjs, obj, "Int8Array", ~0, MJS_MK_FN(mjs_new_i8_array));
|
||||
mjs_set(mjs, obj, "Uint16Array", ~0, MJS_MK_FN(mjs_new_u16_array));
|
||||
mjs_set(mjs, obj, "Int16Array", ~0, MJS_MK_FN(mjs_new_i16_array));
|
||||
mjs_set(mjs, obj, "Uint32Array", ~0, MJS_MK_FN(mjs_new_u32_array));
|
||||
mjs_set(mjs, obj, "Int32Array", ~0, MJS_MK_FN(mjs_new_i32_array));
|
||||
}
|
||||
27
lib/mjs/mjs_array_buf.h
Normal file
27
lib/mjs/mjs_array_buf.h
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "mjs_internal.h"
|
||||
#include "mjs_array_buf_public.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
mjs_val_t mjs_dataview_get_prop(struct mjs* mjs, mjs_val_t obj, mjs_val_t key);
|
||||
|
||||
mjs_err_t mjs_dataview_set_prop(struct mjs* mjs, mjs_val_t obj, mjs_val_t key, mjs_val_t val);
|
||||
|
||||
void mjs_init_builtin_array_buf(struct mjs* mjs, mjs_val_t obj);
|
||||
|
||||
mjs_val_t mjs_dataview_get_len(struct mjs* mjs, mjs_val_t obj);
|
||||
|
||||
void mjs_array_buf_slice(struct mjs* mjs);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
37
lib/mjs/mjs_array_buf_public.h
Normal file
37
lib/mjs/mjs_array_buf_public.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "mjs_core_public.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
typedef enum {
|
||||
MJS_DATAVIEW_U8,
|
||||
MJS_DATAVIEW_I8,
|
||||
MJS_DATAVIEW_U16,
|
||||
MJS_DATAVIEW_I16,
|
||||
MJS_DATAVIEW_U32,
|
||||
MJS_DATAVIEW_I32,
|
||||
} mjs_dataview_type_t;
|
||||
|
||||
int mjs_is_array_buf(mjs_val_t v);
|
||||
|
||||
int mjs_is_data_view(mjs_val_t v);
|
||||
|
||||
int mjs_is_typed_array(mjs_val_t v);
|
||||
|
||||
mjs_val_t mjs_mk_array_buf(struct mjs* mjs, char* data, size_t buf_len);
|
||||
|
||||
char* mjs_array_buf_get_ptr(struct mjs* mjs, mjs_val_t buf, size_t* bytelen);
|
||||
|
||||
mjs_val_t mjs_dataview_get_buf(struct mjs* mjs, mjs_val_t obj);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
47
lib/mjs/mjs_array_public.h
Normal file
47
lib/mjs/mjs_array_public.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (c) 2017 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
/*
|
||||
* === Arrays
|
||||
*/
|
||||
|
||||
#ifndef MJS_ARRAY_PUBLIC_H_
|
||||
#define MJS_ARRAY_PUBLIC_H_
|
||||
|
||||
#include "mjs_core_public.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/* Make an empty array object */
|
||||
mjs_val_t mjs_mk_array(struct mjs* mjs);
|
||||
|
||||
/* Returns length on an array. If `arr` is not an array, 0 is returned. */
|
||||
unsigned long mjs_array_length(struct mjs* mjs, mjs_val_t arr);
|
||||
|
||||
/* Insert value `v` in array `arr` at the end of the array. */
|
||||
mjs_err_t mjs_array_push(struct mjs* mjs, mjs_val_t arr, mjs_val_t v);
|
||||
|
||||
/*
|
||||
* Return array member at index `index`. If `index` is out of bounds, undefined
|
||||
* is returned.
|
||||
*/
|
||||
mjs_val_t mjs_array_get(struct mjs*, mjs_val_t arr, unsigned long index);
|
||||
|
||||
/* Insert value `v` into `arr` at index `index`. */
|
||||
mjs_err_t mjs_array_set(struct mjs* mjs, mjs_val_t arr, unsigned long index, mjs_val_t v);
|
||||
|
||||
/* Returns true if the given value is an array */
|
||||
int mjs_is_array(mjs_val_t v);
|
||||
|
||||
/* Delete value in array `arr` at index `index`, if it exists. */
|
||||
void mjs_array_del(struct mjs* mjs, mjs_val_t arr, unsigned long index);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MJS_ARRAY_PUBLIC_H_ */
|
||||
147
lib/mjs/mjs_bcode.c
Normal file
147
lib/mjs/mjs_bcode.c
Normal file
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright (c) 2017 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#include "common/cs_varint.h"
|
||||
|
||||
#include "mjs_internal.h"
|
||||
#include "mjs_bcode.h"
|
||||
#include "mjs_core.h"
|
||||
#include "mjs_tok.h"
|
||||
|
||||
static void add_lineno_map_item(struct pstate* pstate) {
|
||||
if(pstate->last_emitted_line_no < pstate->line_no) {
|
||||
int offset = pstate->cur_idx - pstate->start_bcode_idx;
|
||||
size_t offset_llen = cs_varint_llen(offset);
|
||||
size_t lineno_llen = cs_varint_llen(pstate->line_no);
|
||||
mbuf_resize(
|
||||
&pstate->offset_lineno_map,
|
||||
pstate->offset_lineno_map.size + offset_llen + lineno_llen);
|
||||
|
||||
/* put offset */
|
||||
cs_varint_encode(
|
||||
offset,
|
||||
(uint8_t*)pstate->offset_lineno_map.buf + pstate->offset_lineno_map.len,
|
||||
offset_llen);
|
||||
pstate->offset_lineno_map.len += offset_llen;
|
||||
|
||||
/* put line_no */
|
||||
cs_varint_encode(
|
||||
pstate->line_no,
|
||||
(uint8_t*)pstate->offset_lineno_map.buf + pstate->offset_lineno_map.len,
|
||||
lineno_llen);
|
||||
pstate->offset_lineno_map.len += lineno_llen;
|
||||
|
||||
pstate->last_emitted_line_no = pstate->line_no;
|
||||
}
|
||||
}
|
||||
|
||||
MJS_PRIVATE void emit_byte(struct pstate* pstate, uint8_t byte) {
|
||||
add_lineno_map_item(pstate);
|
||||
mbuf_insert(&pstate->mjs->bcode_gen, pstate->cur_idx, &byte, sizeof(byte));
|
||||
pstate->cur_idx += sizeof(byte);
|
||||
}
|
||||
|
||||
MJS_PRIVATE void emit_int(struct pstate* pstate, int64_t n) {
|
||||
struct mbuf* b = &pstate->mjs->bcode_gen;
|
||||
size_t llen = cs_varint_llen(n);
|
||||
add_lineno_map_item(pstate);
|
||||
mbuf_insert(b, pstate->cur_idx, NULL, llen);
|
||||
cs_varint_encode(n, (uint8_t*)b->buf + pstate->cur_idx, llen);
|
||||
pstate->cur_idx += llen;
|
||||
}
|
||||
|
||||
MJS_PRIVATE void emit_str(struct pstate* pstate, const char* ptr, size_t len) {
|
||||
struct mbuf* b = &pstate->mjs->bcode_gen;
|
||||
size_t llen = cs_varint_llen(len);
|
||||
add_lineno_map_item(pstate);
|
||||
mbuf_insert(b, pstate->cur_idx, NULL, llen + len);
|
||||
cs_varint_encode(len, (uint8_t*)b->buf + pstate->cur_idx, llen);
|
||||
memcpy(b->buf + pstate->cur_idx + llen, ptr, len);
|
||||
pstate->cur_idx += llen + len;
|
||||
}
|
||||
|
||||
MJS_PRIVATE int
|
||||
mjs_bcode_insert_offset(struct pstate* p, struct mjs* mjs, size_t offset, size_t v) {
|
||||
int llen = (int)cs_varint_llen(v);
|
||||
int diff = llen - MJS_INIT_OFFSET_SIZE;
|
||||
assert(offset < mjs->bcode_gen.len);
|
||||
if(diff > 0) {
|
||||
mbuf_resize(&mjs->bcode_gen, mjs->bcode_gen.size + diff);
|
||||
}
|
||||
/*
|
||||
* Offset is going to take more than one was reserved, so, move the data
|
||||
* forward
|
||||
*/
|
||||
memmove(
|
||||
mjs->bcode_gen.buf + offset + llen,
|
||||
mjs->bcode_gen.buf + offset + MJS_INIT_OFFSET_SIZE,
|
||||
mjs->bcode_gen.len - offset - MJS_INIT_OFFSET_SIZE);
|
||||
mjs->bcode_gen.len += diff;
|
||||
cs_varint_encode(v, (uint8_t*)mjs->bcode_gen.buf + offset, llen);
|
||||
|
||||
/*
|
||||
* If current parsing index is after the offset at which we've inserted new
|
||||
* varint, the index might need to be adjusted
|
||||
*/
|
||||
if(p->cur_idx >= (int)offset) {
|
||||
p->cur_idx += diff;
|
||||
}
|
||||
return diff;
|
||||
}
|
||||
|
||||
MJS_PRIVATE void mjs_bcode_part_add(struct mjs* mjs, const struct mjs_bcode_part* bp) {
|
||||
mbuf_append(&mjs->bcode_parts, bp, sizeof(*bp));
|
||||
}
|
||||
|
||||
MJS_PRIVATE struct mjs_bcode_part* mjs_bcode_part_get(struct mjs* mjs, int num) {
|
||||
assert(num < mjs_bcode_parts_cnt(mjs));
|
||||
return (struct mjs_bcode_part*)(mjs->bcode_parts.buf + num * sizeof(struct mjs_bcode_part));
|
||||
}
|
||||
|
||||
MJS_PRIVATE struct mjs_bcode_part* mjs_bcode_part_get_by_offset(struct mjs* mjs, size_t offset) {
|
||||
int i;
|
||||
int parts_cnt = mjs_bcode_parts_cnt(mjs);
|
||||
struct mjs_bcode_part* bp = NULL;
|
||||
|
||||
if(offset >= mjs->bcode_len) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for(i = 0; i < parts_cnt; i++) {
|
||||
bp = mjs_bcode_part_get(mjs, i);
|
||||
if(offset < bp->start_idx + bp->data.len) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* given the non-corrupted data, the needed part must be found */
|
||||
assert(i < parts_cnt);
|
||||
|
||||
return bp;
|
||||
}
|
||||
|
||||
MJS_PRIVATE int mjs_bcode_parts_cnt(struct mjs* mjs) {
|
||||
return mjs->bcode_parts.len / sizeof(struct mjs_bcode_part);
|
||||
}
|
||||
|
||||
MJS_PRIVATE void mjs_bcode_commit(struct mjs* mjs) {
|
||||
struct mjs_bcode_part bp;
|
||||
memset(&bp, 0, sizeof(bp));
|
||||
|
||||
/* Make sure the bcode doesn't occupy any extra space */
|
||||
mbuf_trim(&mjs->bcode_gen);
|
||||
|
||||
/* Transfer the ownership of the bcode data */
|
||||
bp.data.p = mjs->bcode_gen.buf;
|
||||
bp.data.len = mjs->bcode_gen.len;
|
||||
mbuf_init(&mjs->bcode_gen, 0);
|
||||
|
||||
bp.start_idx = mjs->bcode_len;
|
||||
bp.exec_res = MJS_ERRS_CNT;
|
||||
|
||||
mjs_bcode_part_add(mjs, &bp);
|
||||
|
||||
mjs->bcode_len += bp.data.len;
|
||||
}
|
||||
105
lib/mjs/mjs_bcode.h
Normal file
105
lib/mjs/mjs_bcode.h
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (c) 2017 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef MJS_BCODE_H_
|
||||
#define MJS_BCODE_H_
|
||||
|
||||
#include "mjs_internal.h"
|
||||
|
||||
#include "mjs_core.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
enum mjs_opcode {
|
||||
OP_NOP, /* ( -- ) */
|
||||
OP_DROP, /* ( a -- ) */
|
||||
OP_DUP, /* ( a -- a a ) */
|
||||
OP_SWAP, /* ( a b -- b a ) */
|
||||
OP_JMP, /* ( -- ) */
|
||||
OP_JMP_TRUE, /* ( -- ) */
|
||||
OP_JMP_NEUTRAL_TRUE, /* ( -- ) */
|
||||
OP_JMP_FALSE, /* ( -- ) */
|
||||
OP_JMP_NEUTRAL_FALSE, /* ( -- ) */
|
||||
OP_FIND_SCOPE, /* ( a -- a b ) */
|
||||
OP_PUSH_SCOPE, /* ( -- a ) */
|
||||
OP_PUSH_STR, /* ( -- a ) */
|
||||
OP_PUSH_TRUE, /* ( -- a ) */
|
||||
OP_PUSH_FALSE, /* ( -- a ) */
|
||||
OP_PUSH_INT, /* ( -- a ) */
|
||||
OP_PUSH_DBL, /* ( -- a ) */
|
||||
OP_PUSH_NULL, /* ( -- a ) */
|
||||
OP_PUSH_UNDEF, /* ( -- a ) */
|
||||
OP_PUSH_OBJ, /* ( -- a ) */
|
||||
OP_PUSH_ARRAY, /* ( -- a ) */
|
||||
OP_PUSH_FUNC, /* ( -- a ) */
|
||||
OP_PUSH_THIS, /* ( -- a ) */
|
||||
OP_GET, /* ( key obj -- obj[key] ) */
|
||||
OP_CREATE, /* ( key obj -- ) */
|
||||
OP_EXPR, /* ( ... -- a ) */
|
||||
OP_APPEND, /* ( a b -- ) */
|
||||
OP_SET_ARG, /* ( a -- a ) */
|
||||
OP_NEW_SCOPE, /* ( -- ) */
|
||||
OP_DEL_SCOPE, /* ( -- ) */
|
||||
OP_CALL, /* ( func param1 param2 ... num_params -- result ) */
|
||||
OP_RETURN, /* ( -- ) */
|
||||
OP_LOOP, /* ( -- ) Push break & continue addresses to loop_labels */
|
||||
OP_BREAK, /* ( -- ) */
|
||||
OP_CONTINUE, /* ( -- ) */
|
||||
OP_SETRETVAL, /* ( a -- ) */
|
||||
OP_EXIT, /* ( -- ) */
|
||||
OP_BCODE_HEADER, /* ( -- ) */
|
||||
OP_ARGS, /* ( -- ) Mark the beginning of function call arguments */
|
||||
OP_FOR_IN_NEXT, /* ( name obj iter_ptr -- name obj iter_ptr_next ) */
|
||||
OP_MAX
|
||||
};
|
||||
|
||||
struct pstate;
|
||||
struct mjs;
|
||||
|
||||
MJS_PRIVATE void emit_byte(struct pstate* pstate, uint8_t byte);
|
||||
MJS_PRIVATE void emit_int(struct pstate* pstate, int64_t n);
|
||||
MJS_PRIVATE void emit_str(struct pstate* pstate, const char* ptr, size_t len);
|
||||
|
||||
/*
|
||||
* Inserts provided offset `v` at the offset `offset`.
|
||||
*
|
||||
* Returns delta at which the code was moved; the delta can be any: 0 or
|
||||
* positive or negative.
|
||||
*/
|
||||
MJS_PRIVATE int
|
||||
mjs_bcode_insert_offset(struct pstate* p, struct mjs* mjs, size_t offset, size_t v);
|
||||
|
||||
/*
|
||||
* Adds a new bcode part; does not retain `bp`.
|
||||
*/
|
||||
MJS_PRIVATE void mjs_bcode_part_add(struct mjs* mjs, const struct mjs_bcode_part* bp);
|
||||
|
||||
/*
|
||||
* Returns bcode part by the bcode number
|
||||
*/
|
||||
MJS_PRIVATE struct mjs_bcode_part* mjs_bcode_part_get(struct mjs* mjs, int num);
|
||||
|
||||
/*
|
||||
* Returns bcode part by the global bcode offset
|
||||
*/
|
||||
MJS_PRIVATE struct mjs_bcode_part* mjs_bcode_part_get_by_offset(struct mjs* mjs, size_t offset);
|
||||
|
||||
/*
|
||||
* Returns a number of bcode parts
|
||||
*/
|
||||
MJS_PRIVATE int mjs_bcode_parts_cnt(struct mjs* mjs);
|
||||
|
||||
/*
|
||||
* Adds the bcode being generated (mjs->bcode_gen) as a next bcode part
|
||||
*/
|
||||
MJS_PRIVATE void mjs_bcode_commit(struct mjs* mjs);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MJS_BCODE_H_ */
|
||||
162
lib/mjs/mjs_builtin.c
Normal file
162
lib/mjs/mjs_builtin.c
Normal file
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright (c) 2017 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#include "mjs_bcode.h"
|
||||
#include "mjs_core.h"
|
||||
#include "mjs_dataview.h"
|
||||
#include "mjs_exec.h"
|
||||
#include "mjs_gc.h"
|
||||
#include "mjs_internal.h"
|
||||
#include "mjs_json.h"
|
||||
#include "mjs_object.h"
|
||||
#include "mjs_primitive.h"
|
||||
#include "mjs_string.h"
|
||||
#include "mjs_util.h"
|
||||
#include "mjs_array_buf.h"
|
||||
|
||||
/*
|
||||
* If the file with the given filename was already loaded, returns the
|
||||
* corresponding bcode part; otherwise returns NULL.
|
||||
*/
|
||||
static struct mjs_bcode_part* mjs_get_loaded_file_bcode(struct mjs* mjs, const char* filename) {
|
||||
int parts_cnt = mjs_bcode_parts_cnt(mjs);
|
||||
int i;
|
||||
|
||||
if(filename == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for(i = 0; i < parts_cnt; i++) {
|
||||
struct mjs_bcode_part* bp = mjs_bcode_part_get(mjs, i);
|
||||
const char* cur_fn = mjs_get_bcode_filename(mjs, bp);
|
||||
if(strcmp(filename, cur_fn) == 0) {
|
||||
return bp;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void mjs_load(struct mjs* mjs) {
|
||||
mjs_val_t res = MJS_UNDEFINED;
|
||||
mjs_val_t arg0 = mjs_arg(mjs, 0);
|
||||
mjs_val_t arg1 = mjs_arg(mjs, 1);
|
||||
int custom_global = 0; /* whether the custom global object was provided */
|
||||
|
||||
if(mjs_is_string(arg0)) {
|
||||
const char* path = mjs_get_cstring(mjs, &arg0);
|
||||
struct mjs_bcode_part* bp = NULL;
|
||||
mjs_err_t ret;
|
||||
|
||||
if(mjs_is_object(arg1)) {
|
||||
custom_global = 1;
|
||||
push_mjs_val(&mjs->scopes, arg1);
|
||||
}
|
||||
bp = mjs_get_loaded_file_bcode(mjs, path);
|
||||
if(bp == NULL) {
|
||||
/* File was not loaded before, so, load */
|
||||
ret = mjs_exec_file(mjs, path, &res);
|
||||
} else {
|
||||
/*
|
||||
* File was already loaded before, so if it was evaluated successfully,
|
||||
* then skip the evaluation at all (and assume MJS_OK); otherwise
|
||||
* re-evaluate it again.
|
||||
*
|
||||
* However, if the custom global object was provided, then reevaluate
|
||||
* the file in any case.
|
||||
*/
|
||||
if(bp->exec_res != MJS_OK || custom_global) {
|
||||
ret = mjs_execute(mjs, bp->start_idx, &res);
|
||||
} else {
|
||||
ret = MJS_OK;
|
||||
}
|
||||
}
|
||||
if(ret != MJS_OK) {
|
||||
/*
|
||||
* arg0 and path might be invalidated by executing a file, so refresh
|
||||
* them
|
||||
*/
|
||||
arg0 = mjs_arg(mjs, 0);
|
||||
path = mjs_get_cstring(mjs, &arg0);
|
||||
mjs_prepend_errorf(mjs, ret, "failed to exec file \"%s\"", path);
|
||||
goto clean;
|
||||
}
|
||||
|
||||
clean:
|
||||
if(custom_global) {
|
||||
mjs_pop_val(&mjs->scopes);
|
||||
}
|
||||
}
|
||||
mjs_return(mjs, res);
|
||||
}
|
||||
|
||||
static void mjs_get_mjs(struct mjs* mjs) {
|
||||
mjs_return(mjs, mjs_mk_foreign(mjs, mjs));
|
||||
}
|
||||
|
||||
static void mjs_chr(struct mjs* mjs) {
|
||||
mjs_val_t arg0 = mjs_arg(mjs, 0), res = MJS_NULL;
|
||||
int n = mjs_get_int(mjs, arg0);
|
||||
if(mjs_is_number(arg0) && n >= 0 && n <= 255) {
|
||||
uint8_t s = n;
|
||||
res = mjs_mk_string(mjs, (const char*)&s, sizeof(s), 1);
|
||||
}
|
||||
mjs_return(mjs, res);
|
||||
}
|
||||
|
||||
static void mjs_do_gc(struct mjs* mjs) {
|
||||
mjs_val_t arg0 = mjs_arg(mjs, 0);
|
||||
mjs_gc(mjs, mjs_is_boolean(arg0) ? mjs_get_bool(mjs, arg0) : 0);
|
||||
mjs_return(mjs, arg0);
|
||||
}
|
||||
|
||||
static void mjs_s2o(struct mjs* mjs) {
|
||||
mjs_return(
|
||||
mjs,
|
||||
mjs_struct_to_obj(
|
||||
mjs,
|
||||
mjs_get_ptr(mjs, mjs_arg(mjs, 0)),
|
||||
(const struct mjs_c_struct_member*)mjs_get_ptr(mjs, mjs_arg(mjs, 1))));
|
||||
}
|
||||
|
||||
void mjs_init_builtin(struct mjs* mjs, mjs_val_t obj) {
|
||||
mjs_val_t v;
|
||||
|
||||
mjs_set(mjs, obj, "global", ~0, obj);
|
||||
|
||||
mjs_set(mjs, obj, "load", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_load));
|
||||
mjs_set(mjs, obj, "ffi", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_ffi_call));
|
||||
mjs_set(
|
||||
mjs, obj, "ffi_cb_free", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_ffi_cb_free));
|
||||
mjs_set(mjs, obj, "mkstr", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_mkstr));
|
||||
mjs_set(mjs, obj, "getMJS", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_get_mjs));
|
||||
mjs_set(mjs, obj, "die", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_die));
|
||||
mjs_set(mjs, obj, "gc", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_do_gc));
|
||||
mjs_set(mjs, obj, "chr", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_chr));
|
||||
mjs_set(mjs, obj, "s2o", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_s2o));
|
||||
|
||||
/*
|
||||
* Populate JSON.parse() and JSON.stringify()
|
||||
*/
|
||||
// v = mjs_mk_object(mjs);
|
||||
// mjs_set(
|
||||
// mjs, v, "stringify", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_op_json_stringify));
|
||||
// mjs_set(mjs, v, "parse", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_op_json_parse));
|
||||
// mjs_set(mjs, obj, "JSON", ~0, v);
|
||||
|
||||
/*
|
||||
* Populate Object.create()
|
||||
*/
|
||||
v = mjs_mk_object(mjs);
|
||||
mjs_set(mjs, v, "create", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_op_create_object));
|
||||
mjs_set(mjs, obj, "Object", ~0, v);
|
||||
|
||||
/*
|
||||
* Populate numeric stuff
|
||||
*/
|
||||
mjs_set(mjs, obj, "NaN", ~0, MJS_TAG_NAN);
|
||||
mjs_set(mjs, obj, "isNaN", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_op_isnan));
|
||||
|
||||
mjs_init_builtin_array_buf(mjs, obj);
|
||||
}
|
||||
22
lib/mjs/mjs_builtin.h
Normal file
22
lib/mjs/mjs_builtin.h
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef MJS_BUILTIN_H_
|
||||
#define MJS_BUILTIN_H_
|
||||
|
||||
#include "mjs_core_public.h"
|
||||
#include "mjs_internal.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
void mjs_init_builtin(struct mjs* mjs, mjs_val_t obj);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MJS_BUILTIN_H_ */
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user