1
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:
Methodius
2024-02-14 00:36:02 +09:00
166 changed files with 18674 additions and 1076 deletions

View File

@@ -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/*

View File

@@ -61,7 +61,7 @@
- **Hold right in received signal list to delete selected signal** - **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 - **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 - `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 mode counter increase settings (+1 -> +5, +10, default: +1)
- Debug PIN output settings for protocol development - Debug PIN output settings for protocol development

View File

@@ -71,7 +71,10 @@ Small applications providing configuration for basic firmware and its services.
## system ## 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 - `storage_move_to_sd` - Data migration tool for internal storage
- `updater` - Update service & application - `updater` - Update service & application

View File

@@ -2,6 +2,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h>
void test_furi_memmgr() { void test_furi_memmgr() {
void* ptr; void* ptr;

View File

@@ -32,6 +32,7 @@ static const char* known_ext[] = {
[ArchiveFileTypeBadUsb] = ".txt", [ArchiveFileTypeBadUsb] = ".txt",
[ArchiveFileTypeU2f] = "?", [ArchiveFileTypeU2f] = "?",
[ArchiveFileTypeApplication] = ".fap", [ArchiveFileTypeApplication] = ".fap",
[ArchiveFileTypeJS] = ".js",
[ArchiveFileTypeUpdateManifest] = ".fuf", [ArchiveFileTypeUpdateManifest] = ".fuf",
[ArchiveFileTypeFolder] = "?", [ArchiveFileTypeFolder] = "?",
[ArchiveFileTypeUnknown] = "*", [ArchiveFileTypeUnknown] = "*",

View File

@@ -15,7 +15,7 @@ void archive_set_file_type(ArchiveFile_t* file, const char* path, bool is_folder
} else { } else {
for(size_t i = 0; i < COUNT_OF(known_ext); i++) { for(size_t i = 0; i < COUNT_OF(known_ext); i++) {
if((known_ext[i][0] == '?') || (known_ext[i][0] == '*')) continue; 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(i == ArchiveFileTypeBadUsb) {
if(furi_string_search( if(furi_string_search(
file->path, archive_get_default_path(ArchiveTabBadUsb)) == 0) { file->path, archive_get_default_path(ArchiveTabBadUsb)) == 0) {

View File

@@ -19,6 +19,7 @@ typedef enum {
ArchiveFileTypeU2f, ArchiveFileTypeU2f,
ArchiveFileTypeApplication, ArchiveFileTypeApplication,
ArchiveFileTypeUpdateManifest, ArchiveFileTypeUpdateManifest,
ArchiveFileTypeJS,
ArchiveFileTypeFolder, ArchiveFileTypeFolder,
ArchiveFileTypeUnknown, ArchiveFileTypeUnknown,
ArchiveFileTypeLoading, ArchiveFileTypeLoading,

View File

@@ -32,6 +32,8 @@ static const char* archive_get_flipper_app_name(ArchiveFileTypeEnum file_type) {
return "U2F"; return "U2F";
case ArchiveFileTypeUpdateManifest: case ArchiveFileTypeUpdateManifest:
return "UpdaterApp"; return "UpdaterApp";
case ArchiveFileTypeJS:
return "JS Runner";
default: default:
return NULL; return NULL;
} }

View File

@@ -38,6 +38,7 @@ static const Icon* ArchiveItemIcons[] = {
[ArchiveFileTypeFolder] = &I_dir_10px, [ArchiveFileTypeFolder] = &I_dir_10px,
[ArchiveFileTypeUnknown] = &I_unknown_10px, [ArchiveFileTypeUnknown] = &I_unknown_10px,
[ArchiveFileTypeLoading] = &I_loading_10px, [ArchiveFileTypeLoading] = &I_loading_10px,
[ArchiveFileTypeJS] = &I_js_script_10px,
}; };
void archive_browser_set_callback( void archive_browser_set_callback(

View File

@@ -123,7 +123,7 @@ struct InfraredApp {
InfraredProgressView* progress; /**< Custom view for showing brute force progress. */ InfraredProgressView* progress; /**< Custom view for showing brute force progress. */
FuriString* file_path; /**< Full path to the currently loaded file. */ 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. */ /** Arbitrary text storage for various inputs. */
char text_store[INFRARED_TEXT_STORE_NUM][INFRARED_TEXT_STORE_SIZE + 1]; char text_store[INFRARED_TEXT_STORE_NUM][INFRARED_TEXT_STORE_SIZE + 1];
InfraredAppState app_state; /**< Application state. */ InfraredAppState app_state; /**< Application state. */

View File

@@ -57,20 +57,31 @@ bool infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force) {
Storage* storage = furi_record_open(RECORD_STORAGE); Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* ff = flipper_format_buffered_file_alloc(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 = InfraredBruteForceRecord* record =
InfraredBruteForceRecordDict_get(brute_force->records, signal_name); InfraredBruteForceRecordDict_get(brute_force->records, signal_name);
if(record) { //-V547 if(record) { //-V547
++(record->count); ++(record->count);
} }
} }
furi_string_free(signal_name);
} if(!signals_valid) break;
success = true;
} while(false);
infrared_signal_free(signal);
furi_string_free(signal_name);
flipper_format_free(ff); flipper_format_free(ff);
furi_record_close(RECORD_STORAGE); furi_record_close(RECORD_STORAGE);

View File

@@ -8,7 +8,23 @@
#define TAG "InfraredSignal" #define TAG "InfraredSignal"
// Common keys
#define INFRARED_SIGNAL_NAME_KEY "name" #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 { struct InfraredSignal {
bool is_raw; bool is_raw;
@@ -88,18 +104,23 @@ static bool infrared_signal_is_raw_valid(const InfraredRawSignal* raw) {
static inline bool static inline bool
infrared_signal_save_message(const InfraredMessage* message, FlipperFormat* ff) { infrared_signal_save_message(const InfraredMessage* message, FlipperFormat* ff) {
const char* protocol_name = infrared_get_protocol_name(message->protocol); const char* protocol_name = infrared_get_protocol_name(message->protocol);
return flipper_format_write_string_cstr(ff, "type", "parsed") && return flipper_format_write_string_cstr(
flipper_format_write_string_cstr(ff, "protocol", protocol_name) && ff, INFRARED_SIGNAL_TYPE_KEY, INFRARED_SIGNAL_TYPE_PARSED) &&
flipper_format_write_hex(ff, "address", (uint8_t*)&message->address, 4) && flipper_format_write_string_cstr(ff, INFRARED_SIGNAL_PROTOCOL_KEY, protocol_name) &&
flipper_format_write_hex(ff, "command", (uint8_t*)&message->command, 4); 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) { static inline bool infrared_signal_save_raw(const InfraredRawSignal* raw, FlipperFormat* ff) {
furi_assert(raw->timings_size <= MAX_TIMINGS_AMOUNT); furi_assert(raw->timings_size <= MAX_TIMINGS_AMOUNT);
return flipper_format_write_string_cstr(ff, "type", "raw") && return flipper_format_write_string_cstr(
flipper_format_write_uint32(ff, "frequency", &raw->frequency, 1) && ff, INFRARED_SIGNAL_TYPE_KEY, INFRARED_SIGNAL_TYPE_RAW) &&
flipper_format_write_float(ff, "duty_cycle", &raw->duty_cycle, 1) && flipper_format_write_uint32(ff, INFRARED_SIGNAL_FREQUENCY_KEY, &raw->frequency, 1) &&
flipper_format_write_uint32(ff, "data", raw->timings, raw->timings_size); 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) { 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; bool success = false;
do { do {
if(!flipper_format_read_string(ff, "protocol", buf)) break; if(!flipper_format_read_string(ff, INFRARED_SIGNAL_PROTOCOL_KEY, buf)) break;
InfraredMessage message; InfraredMessage message;
message.protocol = infrared_get_protocol_by_name(furi_string_get_cstr(buf)); message.protocol = infrared_get_protocol_by_name(furi_string_get_cstr(buf));
success = flipper_format_read_hex(ff, "address", (uint8_t*)&message.address, 4) && if(!flipper_format_read_hex(ff, INFRARED_SIGNAL_ADDRESS_KEY, (uint8_t*)&message.address, 4))
flipper_format_read_hex(ff, "command", (uint8_t*)&message.command, 4) && break;
infrared_signal_is_message_valid(&message); if(!flipper_format_read_hex(ff, INFRARED_SIGNAL_COMMAND_KEY, (uint8_t*)&message.command, 4))
break;
if(!success) break; if(!infrared_signal_is_message_valid(&message)) break;
infrared_signal_set_message(signal, &message); infrared_signal_set_message(signal, &message);
} while(0); success = true;
} while(false);
furi_string_free(buf); furi_string_free(buf);
return success; return success;
} }
static inline bool infrared_signal_read_raw(InfraredSignal* signal, FlipperFormat* ff) { static inline bool infrared_signal_read_raw(InfraredSignal* signal, FlipperFormat* ff) {
uint32_t timings_size, frequency; bool success = false;
float duty_cycle;
bool success = flipper_format_read_uint32(ff, "frequency", &frequency, 1) && do {
flipper_format_read_float(ff, "duty_cycle", &duty_cycle, 1) && uint32_t frequency;
flipper_format_get_value_count(ff, "data", &timings_size); if(!flipper_format_read_uint32(ff, INFRARED_SIGNAL_FREQUENCY_KEY, &frequency, 1)) break;
if(!success || timings_size > MAX_TIMINGS_AMOUNT) { float duty_cycle;
return false; if(!flipper_format_read_float(ff, INFRARED_SIGNAL_DUTY_CYCLE_KEY, &duty_cycle, 1)) break;
}
uint32_t* timings = malloc(sizeof(uint32_t) * timings_size); uint32_t timings_size;
success = flipper_format_read_uint32(ff, "data", timings, timings_size); if(!flipper_format_get_value_count(ff, INFRARED_SIGNAL_DATA_KEY, &timings_size)) break;
if(success) { if(timings_size > MAX_TIMINGS_AMOUNT) break;
uint32_t* timings = malloc(sizeof(uint32_t) * timings_size);
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); infrared_signal_set_raw_signal(signal, timings, timings_size, frequency, duty_cycle);
} free(timings);
success = true;
} while(false);
free(timings);
return success; 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(); FuriString* tmp = furi_string_alloc();
bool success = false; bool success = false;
do { do {
if(!flipper_format_read_string(ff, "type", tmp)) break; if(!flipper_format_read_string(ff, INFRARED_SIGNAL_TYPE_KEY, tmp)) break;
if(furi_string_equal(tmp, "raw")) {
success = infrared_signal_read_raw(signal, ff); if(furi_string_equal(tmp, INFRARED_SIGNAL_TYPE_RAW)) {
} else if(furi_string_equal(tmp, "parsed")) { if(!infrared_signal_read_raw(signal, ff)) break;
success = infrared_signal_read_message(signal, ff); } else if(furi_string_equal(tmp, INFRARED_SIGNAL_TYPE_PARSED)) {
if(!infrared_signal_read_message(signal, ff)) break;
} else { } 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); } while(false);
furi_string_free(tmp); furi_string_free(tmp);

View File

@@ -127,7 +127,7 @@ void infrared_signal_set_message(InfraredSignal* signal, const InfraredMessage*
const InfraredMessage* infrared_signal_get_message(const InfraredSignal* signal); 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 * 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 * 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); 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. * @brief Read a signal with a particular name from a FlipperFormat file into an InfraredSignal instance.
* *

View File

@@ -671,7 +671,7 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) {
FuriHalRtcDateTime card_start_trip_minutes_s = {0}; FuriHalRtcDateTime card_start_trip_minutes_s = {0};
from_minutes_to_datetime( from_minutes_to_datetime(
(card_start_trip_date)*24 * 60 + card_start_trip_time, (card_start_trip_date) * 24 * 60 + card_start_trip_time,
&card_start_trip_minutes_s, &card_start_trip_minutes_s,
1992); 1992);
furi_string_printf( furi_string_printf(

View File

@@ -620,7 +620,7 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) {
FuriHalRtcDateTime card_start_trip_minutes_s = {0}; FuriHalRtcDateTime card_start_trip_minutes_s = {0};
from_minutes_to_datetime( from_minutes_to_datetime(
(card_start_trip_date)*24 * 60 + card_start_trip_time, (card_start_trip_date) * 24 * 60 + card_start_trip_time,
&card_start_trip_minutes_s, &card_start_trip_minutes_s,
1992); 1992);
furi_string_printf( furi_string_printf(
@@ -697,7 +697,7 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) {
FuriHalRtcDateTime card_start_trip_minutes_s = {0}; FuriHalRtcDateTime card_start_trip_minutes_s = {0};
from_minutes_to_datetime( from_minutes_to_datetime(
(card_start_trip_date)*24 * 60 + card_start_trip_time, (card_start_trip_date) * 24 * 60 + card_start_trip_time,
&card_start_trip_minutes_s, &card_start_trip_minutes_s,
1992); 1992);
furi_string_printf( furi_string_printf(
@@ -871,7 +871,7 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) {
from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 1992); from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 1992);
FuriHalRtcDateTime card_start_trip_minutes_s = {0}; FuriHalRtcDateTime card_start_trip_minutes_s = {0};
from_minutes_to_datetime( from_minutes_to_datetime(
(card_start_trip_date)*24 * 60 + card_start_trip_time, (card_start_trip_date) * 24 * 60 + card_start_trip_time,
&card_start_trip_minutes_s, &card_start_trip_minutes_s,
1992); 1992);
furi_string_printf( furi_string_printf(
@@ -952,7 +952,7 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) {
from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 1992); from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 1992);
FuriHalRtcDateTime card_start_trip_minutes_s = {0}; FuriHalRtcDateTime card_start_trip_minutes_s = {0};
from_minutes_to_datetime( from_minutes_to_datetime(
(card_start_trip_date)*24 * 60 + card_start_trip_time, (card_start_trip_date) * 24 * 60 + card_start_trip_time,
&card_start_trip_minutes_s, &card_start_trip_minutes_s,
1992); 1992);
furi_string_printf( furi_string_printf(
@@ -1092,7 +1092,7 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) {
FuriHalRtcDateTime card_start_trip_minutes_s = {0}; FuriHalRtcDateTime card_start_trip_minutes_s = {0};
from_minutes_to_datetime( from_minutes_to_datetime(
(card_valid_to_date)*24 * 60 + card_valid_for_minutes - card_start_trip_neg_minutes, (card_valid_to_date) * 24 * 60 + card_valid_for_minutes - card_start_trip_neg_minutes,
&card_start_trip_minutes_s, &card_start_trip_minutes_s,
2016); //-time 2016); //-time
furi_string_printf( furi_string_printf(

View File

@@ -10,7 +10,7 @@ enum SubmenuIndex {
static void nfc_scene_set_type_init_edit_data(Iso14443_3aData* data, size_t uid_len) { 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 // 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); iso14443_3a_set_uid(data, uid, uid_len);
free(uid); free(uid);
} }

View File

@@ -5,68 +5,6 @@ typedef enum {
SubGhzCustomEventManagerSet, SubGhzCustomEventManagerSet,
SubGhzCustomEventManagerSetRAW, 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 //SubGhzCustomEvent
SubGhzCustomEventSceneDeleteSuccess = 100, SubGhzCustomEventSceneDeleteSuccess = 100,
SubGhzCustomEventSceneDelete, SubGhzCustomEventSceneDelete,
@@ -123,3 +61,68 @@ typedef enum {
SubGhzCustomEventByteInputDone, SubGhzCustomEventByteInputDone,
} SubGhzCustomEvent; } 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;

View File

@@ -17,7 +17,7 @@ void subghz_scene_set_cnt_on_enter(void* context) {
scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneSetType); scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneSetType);
switch(state) { switch(state) {
case SubmenuIndexBFTClone: case SetTypeBFTClone:
byte_input_set_header_text(byte_input, "Enter COUNTER in Hex"); byte_input_set_header_text(byte_input, "Enter COUNTER in Hex");
byte_input_set_result_callback( byte_input_set_result_callback(
byte_input, byte_input,
@@ -27,8 +27,8 @@ void subghz_scene_set_cnt_on_enter(void* context) {
subghz->secure_data->cnt, subghz->secure_data->cnt,
2); 2);
break; break;
case SubmenuIndexFaacSLH_Manual_433: case SetTypeFaacSLH_Manual_433:
case SubmenuIndexFaacSLH_Manual_868: case SetTypeFaacSLH_Manual_868:
byte_input_set_header_text(byte_input, "Enter COUNTER in Hex 20 bits"); byte_input_set_header_text(byte_input, "Enter COUNTER in Hex 20 bits");
byte_input_set_result_callback( byte_input_set_result_callback(
byte_input, byte_input,

View File

@@ -32,11 +32,11 @@ bool subghz_scene_set_seed_on_event(void* context, SceneManagerEvent event) {
uint32_t fix_part, cnt, seed; uint32_t fix_part, cnt, seed;
if(event.type == SceneManagerEventTypeCustom) { if(event.type == SceneManagerEventTypeCustom) {
if(event.event == SubGhzCustomEventByteInputDone) { if(event.event == SubGhzCustomEventByteInputDone) {
SubGhzCustomEvent state = SetType state =
scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneSetType); scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneSetType);
switch(state) { switch(state) {
case SubmenuIndexBFTClone: case SetTypeBFTClone:
fix_part = subghz->secure_data->fix[0] << 24 | subghz->secure_data->fix[1] << 16 | 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]; 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; consumed = true;
break; break;
case SubmenuIndexFaacSLH_Manual_433: case SetTypeFaacSLH_Manual_433:
case SubmenuIndexFaacSLH_Manual_868: case SetTypeFaacSLH_Manual_868:
fix_part = subghz->secure_data->fix[0] << 24 | subghz->secure_data->fix[1] << 16 | 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]; 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 | seed = subghz->secure_data->seed[0] << 24 | subghz->secure_data->seed[1] << 16 |
subghz->secure_data->seed[2] << 8 | subghz->secure_data->seed[3]; 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( generated_protocol = subghz_txrx_gen_faac_slh_protocol(
subghz->txrx, subghz->txrx,
"AM650", "AM650",
@@ -83,7 +83,7 @@ bool subghz_scene_set_seed_on_event(void* context, SceneManagerEvent event) {
(cnt & 0xFFFFF), (cnt & 0xFFFFF),
seed, seed,
"FAAC_SLH"); "FAAC_SLH");
} else if(state == SubmenuIndexFaacSLH_Manual_868) { } else if(state == SetTypeFaacSLH_Manual_868) {
generated_protocol = subghz_txrx_gen_faac_slh_protocol( generated_protocol = subghz_txrx_gen_faac_slh_protocol(
subghz->txrx, subghz->txrx,
"AM650", "AM650",

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@
#define DOLPHIN_LOCK_EVENT_FLAG (0x1) #define DOLPHIN_LOCK_EVENT_FLAG (0x1)
#define TAG "Dolphin" #define TAG "Dolphin"
#define HOURS_IN_TICKS(x) ((x)*60 * 60 * 1000) #define HOURS_IN_TICKS(x) ((x) * 60 * 60 * 1000)
static void dolphin_update_clear_limits_timer_period(Dolphin* dolphin); static void dolphin_update_clear_limits_timer_period(Dolphin* dolphin);

View File

@@ -1,5 +1,4 @@
#include "canvas_i.h" #include "canvas_i.h"
#include "icon_i.h"
#include "icon_animation_i.h" #include "icon_animation_i.h"
#include <furi.h> #include <furi.h>

View File

@@ -122,7 +122,7 @@ void canvas_draw_u8g2_bitmap(
uint8_t width, uint8_t width,
uint8_t height, uint8_t height,
const uint8_t* bitmap, const uint8_t* bitmap,
uint8_t rotation); IconRotation rotation);
/** Add canvas commit callback. /** Add canvas commit callback.
* *
@@ -147,4 +147,4 @@ void canvas_remove_framebuffer_callback(
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@@ -32,12 +32,12 @@ typedef enum {
(WorkerEvtStop | WorkerEvtLoad | WorkerEvtFolderEnter | WorkerEvtFolderExit | \ (WorkerEvtStop | WorkerEvtLoad | WorkerEvtFolderEnter | WorkerEvtFolderExit | \
WorkerEvtFolderRefresh | WorkerEvtConfigChange) WorkerEvtFolderRefresh | WorkerEvtConfigChange)
ARRAY_DEF(idx_last_array, int32_t) ARRAY_DEF(IdxLastArray, int32_t)
ARRAY_DEF(ExtFilterArray, FuriString*, FURI_STRING_OPLIST)
struct BrowserWorker { struct BrowserWorker {
FuriThread* thread; FuriThread* thread;
FuriString* filter_extension;
FuriString* path_start; FuriString* path_start;
FuriString* path_current; FuriString* path_current;
FuriString* path_next; FuriString* path_next;
@@ -46,7 +46,8 @@ struct BrowserWorker {
uint32_t load_count; uint32_t load_count;
bool skip_assets; bool skip_assets;
bool hide_dot_files; bool hide_dot_files;
idx_last_array_t idx_last; IdxLastArray_t idx_last;
ExtFilterArray_t ext_filter;
void* cb_ctx; void* cb_ctx;
BrowserWorkerFolderOpenCallback folder_cb; BrowserWorkerFolderOpenCallback folder_cb;
@@ -78,6 +79,32 @@ static bool browser_path_trim(FuriString* path) {
} }
return is_root; 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) { static bool browser_filter_by_name(BrowserWorker* browser, FuriString* name, bool is_folder) {
// Skip dot files if enabled // Skip dot files if enabled
@@ -96,12 +123,20 @@ static bool browser_filter_by_name(BrowserWorker* browser, FuriString* name, boo
} }
} else { } else {
// Filter files by extension // Filter files by extension
if((furi_string_empty(browser->filter_extension)) || if(ExtFilterArray_size(browser->ext_filter) == 0) {
(furi_string_cmp_str(browser->filter_extension, "*") == 0)) {
return true; return true;
} }
if(furi_string_end_with(name, browser->filter_extension)) {
return true; 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; return false;
@@ -345,7 +380,7 @@ static int32_t browser_worker(void* context) {
if(browser_path_is_file(browser->path_next)) { if(browser_path_is_file(browser->path_next)) {
path_extract_filename(browser->path_next, filename, false); 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); 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); bool is_root = browser_folder_check_and_switch(path);
// Push previous selected item index to history array // 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; int32_t file_idx = 0;
browser_folder_init(browser, path, filename, &items_cnt, &file_idx); 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; int32_t file_idx = 0;
browser_folder_init(browser, path, filename, &items_cnt, &file_idx); 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 // 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_string_set(browser->path_current, path);
FURI_LOG_D( FURI_LOG_D(
@@ -437,14 +472,15 @@ static int32_t browser_worker(void* context) {
BrowserWorker* file_browser_worker_alloc( BrowserWorker* file_browser_worker_alloc(
FuriString* path, FuriString* path,
const char* base_path, const char* base_path,
const char* filter_ext, const char* ext_filter,
bool skip_assets, bool skip_assets,
bool hide_dot_files) { bool hide_dot_files) {
BrowserWorker* browser = malloc(sizeof(BrowserWorker)); 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->skip_assets = skip_assets;
browser->hide_dot_files = hide_dot_files; 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_join(browser->thread);
furi_thread_free(browser->thread); furi_thread_free(browser->thread);
furi_string_free(browser->filter_extension);
furi_string_free(browser->path_next); furi_string_free(browser->path_next);
furi_string_free(browser->path_current); furi_string_free(browser->path_current);
furi_string_free(browser->path_start); 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); free(browser);
} }
@@ -515,12 +551,12 @@ void file_browser_worker_set_long_load_callback(
void file_browser_worker_set_config( void file_browser_worker_set_config(
BrowserWorker* browser, BrowserWorker* browser,
FuriString* path, FuriString* path,
const char* filter_ext, const char* ext_filter,
bool skip_assets, bool skip_assets,
bool hide_dot_files) { bool hide_dot_files) {
furi_assert(browser); furi_assert(browser);
furi_string_set(browser->path_next, path); 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->skip_assets = skip_assets;
browser->hide_dot_files = hide_dot_files; browser->hide_dot_files = hide_dot_files;
furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtConfigChange); furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtConfigChange);

View File

@@ -27,7 +27,7 @@ typedef void (*BrowserWorkerLongLoadCallback)(void* context);
BrowserWorker* file_browser_worker_alloc( BrowserWorker* file_browser_worker_alloc(
FuriString* path, FuriString* path,
const char* base_path, const char* base_path,
const char* filter_ext, const char* ext_filter,
bool skip_assets, bool skip_assets,
bool hide_dot_files); bool hide_dot_files);
@@ -54,7 +54,7 @@ void file_browser_worker_set_long_load_callback(
void file_browser_worker_set_config( void file_browser_worker_set_config(
BrowserWorker* browser, BrowserWorker* browser,
FuriString* path, FuriString* path,
const char* filter_ext, const char* ext_filter,
bool skip_assets, bool skip_assets,
bool hide_dot_files); bool hide_dot_files);

View File

@@ -4,6 +4,9 @@
#include <furi.h> #include <furi.h>
#include <stdint.h> #include <stdint.h>
#define TEXT_BOX_MAX_SYMBOL_WIDTH (10)
#define TEXT_BOX_LINE_WIDTH (120)
struct TextBox { struct TextBox {
View* view; View* view;
@@ -78,13 +81,11 @@ static void text_box_insert_endline(Canvas* canvas, TextBoxModel* model) {
const char* str = model->text; const char* str = model->text;
size_t line_num = 0; size_t line_num = 0;
const size_t text_width = 120;
while(str[i] != '\0') { while(str[i] != '\0') {
char symb = str[i++]; char symb = str[i++];
if(symb != '\n') { if(symb != '\n') {
size_t glyph_width = canvas_glyph_width(canvas, symb); 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_num++;
line_width = 0; line_width = 0;
furi_string_push_back(model->text_formatted, '\n'); 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, ""); furi_string_set(model->text_formatted, "");
model->font = TextBoxFontText; model->font = TextBoxFontText;
model->focus = TextBoxFocusStart; model->focus = TextBoxFocusStart;
model->formatted = false;
}, },
true); true);
} }
@@ -218,6 +220,8 @@ void text_box_reset(TextBox* text_box) {
void text_box_set_text(TextBox* text_box, const char* text) { void text_box_set_text(TextBox* text_box, const char* text) {
furi_assert(text_box); furi_assert(text_box);
furi_assert(text); 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( with_view_model(
text_box->view, text_box->view,
@@ -225,7 +229,7 @@ void text_box_set_text(TextBox* text_box, const char* text) {
{ {
model->text = text; model->text = text;
furi_string_reset(model->text_formatted); 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; model->formatted = false;
}, },
true); true);

View File

@@ -7,9 +7,12 @@
#include <gui/view_holder.h> #include <gui/view_holder.h>
#include <gui/modules/loading.h> #include <gui/modules/loading.h>
#include <dolphin/dolphin.h> #include <dolphin/dolphin.h>
#include <lib/toolbox/path.h>
#define TAG "LoaderApplications" #define TAG "LoaderApplications"
#define JS_RUNNER_APP "JS Runner"
struct LoaderApplications { struct LoaderApplications {
FuriThread* thread; FuriThread* thread;
void (*closed_cb)(void*); void (*closed_cb)(void*);
@@ -36,7 +39,7 @@ void loader_applications_free(LoaderApplications* loader_applications) {
} }
typedef struct { typedef struct {
FuriString* fap_path; FuriString* file_path;
DialogsApp* dialogs; DialogsApp* dialogs;
Storage* storage; Storage* storage;
Loader* loader; Loader* loader;
@@ -48,7 +51,7 @@ typedef struct {
static LoaderApplicationsApp* loader_applications_app_alloc() { static LoaderApplicationsApp* loader_applications_app_alloc() {
LoaderApplicationsApp* app = malloc(sizeof(LoaderApplicationsApp)); //-V799 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->dialogs = furi_record_open(RECORD_DIALOGS);
app->storage = furi_record_open(RECORD_STORAGE); app->storage = furi_record_open(RECORD_STORAGE);
app->loader = furi_record_open(RECORD_LOADER); 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_LOADER);
furi_record_close(RECORD_DIALOGS); furi_record_close(RECORD_DIALOGS);
furi_record_close(RECORD_STORAGE); furi_record_close(RECORD_STORAGE);
furi_string_free(app->fap_path); furi_string_free(app->file_path);
free(app); free(app);
} }
@@ -84,13 +87,19 @@ static bool loader_applications_item_callback(
FuriString* item_name) { FuriString* item_name) {
LoaderApplicationsApp* loader_applications_app = context; LoaderApplicationsApp* loader_applications_app = context;
furi_assert(loader_applications_app); furi_assert(loader_applications_app);
return flipper_application_load_name_and_icon( if(furi_string_end_with(path, ".fap")) {
path, loader_applications_app->storage, icon_ptr, item_name); 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) { static bool loader_applications_select_app(LoaderApplicationsApp* loader_applications_app) {
const DialogsFileBrowserOptions browser_options = { const DialogsFileBrowserOptions browser_options = {
.extension = ".fap", .extension = ".fap|.js",
.skip_assets = true, .skip_assets = true,
.icon = &I_unknown_10px, .icon = &I_unknown_10px,
.hide_ext = true, .hide_ext = true,
@@ -101,8 +110,8 @@ static bool loader_applications_select_app(LoaderApplicationsApp* loader_applica
return dialog_file_browser_show( return dialog_file_browser_show(
loader_applications_app->dialogs, loader_applications_app->dialogs,
loader_applications_app->fap_path, loader_applications_app->file_path,
loader_applications_app->fap_path, loader_applications_app->file_path,
&browser_options); &browser_options);
} }
@@ -117,9 +126,8 @@ static void loader_pubsub_callback(const void* message, void* context) {
} }
} }
static void loader_applications_start_app(LoaderApplicationsApp* app) { static void
const char* name = furi_string_get_cstr(app->fap_path); loader_applications_start_app(LoaderApplicationsApp* app, const char* name, const char* args) {
dolphin_deed(DolphinDeedPluginStart); dolphin_deed(DolphinDeedPluginStart);
// load app // load app
@@ -127,7 +135,7 @@ static void loader_applications_start_app(LoaderApplicationsApp* app) {
FuriPubSubSubscription* subscription = FuriPubSubSubscription* subscription =
furi_pubsub_subscribe(loader_get_pubsub(app->loader), loader_pubsub_callback, thread_id); 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) { if(status == LoaderStatusOk) {
furi_thread_flags_wait(APPLICATION_STOP_EVENT, FuriFlagWaitAny, FuriWaitForever); 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); view_holder_start(app->view_holder);
while(loader_applications_select_app(app)) { 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 // stop loading animation

View File

@@ -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); response->has_next = fs_operation_success && (size_left > 0);
} else { } else {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
response->content.storage_read_response.file.data = response->content.storage_read_response.file.data =
malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(0)); malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(0));
response->content.storage_read_response.file.data->size = 0; response->content.storage_read_response.file.data->size = 0;
#pragma GCC diagnostic pop
response->content.storage_read_response.has_file = true; response->content.storage_read_response.has_file = true;
response->has_next = false; response->has_next = false;
fs_operation_success = true; fs_operation_success = true;

View File

@@ -5,6 +5,7 @@ App(
provides=[ provides=[
"updater_app", "updater_app",
"storage_move_to_sd", "storage_move_to_sd",
"js_app",
# "archive", # "archive",
], ],
) )

View 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"],
)

View File

@@ -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]);
}

View 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");
}
}

View File

@@ -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();
}

View File

@@ -0,0 +1,5 @@
print("print", 1);
console.log("log", 2);
console.warn("warn", 3);
console.error("error", 4);
console.debug("debug", 5);

View File

@@ -0,0 +1,9 @@
print("start");
delay(1000)
print("1");
delay(1000)
print("2");
delay(1000)
print("3");
delay(1000)
print("end");

View 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");
}

View File

@@ -0,0 +1,3 @@
let math = load("/ext/apps/Scripts/load_api.js");
let result = math.add(5, 10);
print(result);

View File

@@ -0,0 +1,3 @@
({
add: function (a, b) { return a + b; },
})

View File

@@ -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);
}

View File

@@ -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]));
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View 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

View 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;

View 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;
}

View 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);

View 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);
}

View 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);

View 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);

View 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;
}

View 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;
}

View 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;
}

View File

@@ -0,0 +1,4 @@
#pragma once
#include "../js_thread_i.h"
void* js_flipper_create(struct mjs* mjs, mjs_val_t* object);

View 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;
}

View 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;
}

View File

@@ -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;

View 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;

View 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))));

View 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

View 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";

View 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;
}

View 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);

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -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 8. Flipper will act as new remote, press Send button couple times near the receiver to register new remote
9. Done! 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 ## AN-Motors AT4
**This instruction for older boards, if your has no** `Learn` **button but has buttons** `F`, `CL`, `+`, `-` **read instruction from Alutech AT4N** **This instruction for older boards, if your has no** `Learn` **button but has buttons** `F`, `CL`, `+`, `-` **read instruction from Alutech AT4N**

View File

@@ -38,7 +38,8 @@ COPRO_STACK_ADDR = "0x0"
COPRO_STACK_BIN_DIR = posixpath.join(COPRO_CUBE_DIR, "firmware") COPRO_STACK_BIN_DIR = posixpath.join(COPRO_CUBE_DIR, "firmware")
# Supported toolchain versions # Supported toolchain versions
FBT_TOOLCHAIN_VERSIONS = (" 10.3.",) # Also specify in scripts/ufbt/SConstruct
FBT_TOOLCHAIN_VERSIONS = (" 12.3.", " 13.2.")
OPENOCD_OPTS = [ OPENOCD_OPTS = [
"-f", "-f",

View File

@@ -13,6 +13,10 @@ extern "C" {
#define FURI_WARN_UNUSED __attribute__((warn_unused_result)) #define FURI_WARN_UNUSED __attribute__((warn_unused_result))
#endif #endif
#ifndef FURI_DEPRECATED
#define FURI_DEPRECATED __attribute__((deprecated))
#endif
#ifndef FURI_WEAK #ifndef FURI_WEAK
#define FURI_WEAK __attribute__((weak)) #define FURI_WEAK __attribute__((weak))
#endif #endif

View File

@@ -84,9 +84,9 @@ extern "C" {
#endif #endif
#ifndef REVERSE_BYTES_U32 #ifndef REVERSE_BYTES_U32
#define REVERSE_BYTES_U32(x) \ #define REVERSE_BYTES_U32(x) \
((((x)&0x000000FF) << 24) | (((x)&0x0000FF00) << 8) | (((x)&0x00FF0000) >> 8) | \ ((((x) & 0x000000FF) << 24) | (((x) & 0x0000FF00) << 8) | (((x) & 0x00FF0000) >> 8) | \
(((x)&0xFF000000) >> 24)) (((x) & 0xFF000000) >> 24))
#endif #endif
#ifndef FURI_BIT #ifndef FURI_BIT

View File

@@ -26,19 +26,20 @@
*tmp_x = y; \ *tmp_x = y; \
*tmp_x; \ *tmp_x; \
}) })
#define FURI_CONST_ASSIGN(x, y) \ #define FURI_CONST_ASSIGN(x, y) \
_Generic((x), signed char \ _Generic( \
: FURI_CONST_ASSIGN_(signed char, x, y), unsigned char \ (x), \
: FURI_CONST_ASSIGN_(unsigned char, x, y), short \ signed char: FURI_CONST_ASSIGN_(signed char, x, y), \
: FURI_CONST_ASSIGN_(short, x, y), unsigned short \ unsigned char: FURI_CONST_ASSIGN_(unsigned char, x, y), \
: FURI_CONST_ASSIGN_(unsigned short, x, y), int \ short: FURI_CONST_ASSIGN_(short, x, y), \
: FURI_CONST_ASSIGN_(int, x, y), unsigned \ unsigned short: FURI_CONST_ASSIGN_(unsigned short, x, y), \
: FURI_CONST_ASSIGN_(unsigned, x, y), long \ int: FURI_CONST_ASSIGN_(int, x, y), \
: FURI_CONST_ASSIGN_(long, x, y), unsigned long \ unsigned: FURI_CONST_ASSIGN_(unsigned, x, y), \
: FURI_CONST_ASSIGN_(unsigned long, x, y), long long \ long: FURI_CONST_ASSIGN_(long, x, y), \
: FURI_CONST_ASSIGN_(long long, x, y), unsigned long long \ unsigned long: FURI_CONST_ASSIGN_(unsigned long, x, y), \
: FURI_CONST_ASSIGN_(unsigned long long, x, y), float \ long long: FURI_CONST_ASSIGN_(long long, x, y), \
: FURI_CONST_ASSIGN_(float, x, y), double \ unsigned long long: FURI_CONST_ASSIGN_(unsigned long long, x, y), \
: FURI_CONST_ASSIGN_(double, x, y), long double \ float: FURI_CONST_ASSIGN_(float, x, y), \
: FURI_CONST_ASSIGN_(long double, x, y)) double: FURI_CONST_ASSIGN_(double, x, y), \
long double: FURI_CONST_ASSIGN_(long double, x, y))
#endif #endif

View File

@@ -568,26 +568,30 @@ void furi_string_utf8_decode(char c, FuriStringUTF8State* state, FuriStringUnico
/** /**
* @brief Select for 1 argument * @brief Select for 1 argument
*/ */
#define FURI_STRING_SELECT1(func1, func2, a) \ #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 * @brief Select for 2 arguments
*/ */
#define FURI_STRING_SELECT2(func1, func2, a, b) \ #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 * @brief Select for 3 arguments
*/ */
#define FURI_STRING_SELECT3(func1, func2, a, b, c) \ #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 * @brief Select for 4 arguments
*/ */
#define FURI_STRING_SELECT4(func1, func2, a, b, c, d) \ #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). * @brief Allocate new FuriString and set it content to string (or C string).

View File

@@ -38,6 +38,7 @@ libs = env.BuildModules(
"lfrfid", "lfrfid",
"flipper_application", "flipper_application",
"music_worker", "music_worker",
"mjs",
"nanopb", "nanopb",
"update_util", "update_util",
"heatshrink", "heatshrink",

View File

@@ -26,9 +26,9 @@ extern "C" {
// DigitalSignal uses 10 picosecond time units (1 tick = 10 ps). // DigitalSignal uses 10 picosecond time units (1 tick = 10 ps).
// Use the macros below to convert the time from other units. // Use the macros below to convert the time from other units.
#define DIGITAL_SIGNAL_MS(x) ((x)*100000000UL) #define DIGITAL_SIGNAL_MS(x) ((x) * 100000000UL)
#define DIGITAL_SIGNAL_US(x) ((x)*100000UL) #define DIGITAL_SIGNAL_US(x) ((x) * 100000UL)
#define DIGITAL_SIGNAL_NS(x) ((x)*100UL) #define DIGITAL_SIGNAL_NS(x) ((x) * 100UL)
#define DIGITAL_SIGNAL_PS(x) ((x) / 10UL) #define DIGITAL_SIGNAL_PS(x) ((x) / 10UL)
typedef struct DigitalSignal DigitalSignal; typedef struct DigitalSignal DigitalSignal;

33
lib/mjs/SConscript Normal file
View 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
View 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
View 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
View 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;

View 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
View 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
View 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
View 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
View 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_ */

View 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;
}

View 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_ */

File diff suppressed because it is too large Load Diff

View 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
View 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
View 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
View 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
View 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
View 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
View 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_ */

View 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;
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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 */

View 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 */

Some files were not shown because too many files have changed in this diff Show More