mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-07-15 00:08:11 -07:00
Merge branch 'dev' into nfcf
This commit is contained in:
+23
-12
@@ -8,7 +8,6 @@ env.Append(
|
||||
Dir("flipper_format"),
|
||||
Dir("infrared"),
|
||||
Dir("nfc"),
|
||||
Dir("one_wire"),
|
||||
Dir("ST25RFAL002"),
|
||||
Dir("subghz"),
|
||||
Dir("toolbox"),
|
||||
@@ -16,16 +15,9 @@ env.Append(
|
||||
Dir("update_util"),
|
||||
Dir("print"),
|
||||
],
|
||||
SDK_HEADERS=[
|
||||
File("one_wire/one_wire_host_timing.h"),
|
||||
File("one_wire/one_wire_host.h"),
|
||||
File("one_wire/one_wire_slave.h"),
|
||||
File("one_wire/one_wire_device.h"),
|
||||
File("one_wire/ibutton/ibutton_worker.h"),
|
||||
File("one_wire/maxim_crc.h"),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
env.Append(
|
||||
CPPPATH=[
|
||||
"#/",
|
||||
@@ -34,6 +26,23 @@ env.Append(
|
||||
# Ugly hack
|
||||
Dir("../assets/compiled"),
|
||||
],
|
||||
SDK_HEADERS=[
|
||||
*(
|
||||
File(f"#/lib/mlib/m-{name}.h")
|
||||
for name in (
|
||||
"algo",
|
||||
"array",
|
||||
"bptree",
|
||||
"core",
|
||||
"deque",
|
||||
"dict",
|
||||
"list",
|
||||
"rbtree",
|
||||
"tuple",
|
||||
"variant",
|
||||
)
|
||||
),
|
||||
],
|
||||
CPPDEFINES=[
|
||||
'"M_MEMORY_FULL(x)=abort()"',
|
||||
],
|
||||
@@ -47,13 +56,13 @@ env.Append(
|
||||
# littlefs
|
||||
# subghz
|
||||
# toolbox
|
||||
# one_wire
|
||||
# micro-ecc
|
||||
# misc
|
||||
# digital_signal
|
||||
# fnv1a-hash
|
||||
# micro-ecc
|
||||
# fnv1a_hash
|
||||
# microtar
|
||||
# nfc
|
||||
# one_wire
|
||||
# qrcode
|
||||
# u8g2
|
||||
# update_util
|
||||
@@ -77,6 +86,8 @@ libs = env.BuildModules(
|
||||
"drivers",
|
||||
"fatfs",
|
||||
"flipper_format",
|
||||
"one_wire",
|
||||
"ibutton",
|
||||
"infrared",
|
||||
"littlefs",
|
||||
"mbedtls",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "popup_vm.h"
|
||||
#include "gui/modules/popup.h"
|
||||
#include <gui/modules/popup.h>
|
||||
|
||||
PopupVM::PopupVM() {
|
||||
popup = popup_alloc();
|
||||
}
|
||||
|
||||
+8
-11
@@ -140,19 +140,16 @@ uint16_t bq25896_get_vreg_voltage(FuriHalI2cBusHandle* handle) {
|
||||
|
||||
void bq25896_set_vreg_voltage(FuriHalI2cBusHandle* handle, uint16_t vreg_voltage) {
|
||||
if(vreg_voltage < 3840) {
|
||||
// Minimum value is 3840 mV
|
||||
bq25896_regs.r06.VREG = 0;
|
||||
} else {
|
||||
// Find the nearest voltage value (subtract offset, divide into sections)
|
||||
// Values are truncated downward as needed (e.g. 4200mV -> 4192 mV)
|
||||
bq25896_regs.r06.VREG = (uint8_t)((vreg_voltage - 3840) / 16);
|
||||
// Minimum valid value is 3840 mV
|
||||
vreg_voltage = 3840;
|
||||
} else if(vreg_voltage > 4208) {
|
||||
// Maximum safe value is 4208 mV
|
||||
vreg_voltage = 4208;
|
||||
}
|
||||
|
||||
// Do not allow values above 23 (0x17, 4208mV)
|
||||
// Exceeding 4.2v will overcharge the battery!
|
||||
if(bq25896_regs.r06.VREG > 23) {
|
||||
bq25896_regs.r06.VREG = 23;
|
||||
}
|
||||
// Find the nearest voltage value (subtract offset, divide into sections)
|
||||
// Values are truncated downward as needed (e.g. 4200mV -> 4192 mV)
|
||||
bq25896_regs.r06.VREG = (uint8_t)((vreg_voltage - 3840) / 16);
|
||||
|
||||
// Apply changes
|
||||
furi_hal_i2c_write_reg_8(
|
||||
|
||||
@@ -36,10 +36,10 @@ void bq25896_disable_otg(FuriHalI2cBusHandle* handle);
|
||||
/** Is otg enabled */
|
||||
bool bq25896_is_otg_enabled(FuriHalI2cBusHandle* handle);
|
||||
|
||||
/** Get VREG (charging) voltage in mV */
|
||||
/** Get VREG (charging limit) voltage in mV */
|
||||
uint16_t bq25896_get_vreg_voltage(FuriHalI2cBusHandle* handle);
|
||||
|
||||
/** Set VREG (charging) voltage in mV
|
||||
/** Set VREG (charging limit) voltage in mV
|
||||
*
|
||||
* Valid range: 3840mV - 4208mV, in steps of 16mV
|
||||
*/
|
||||
|
||||
@@ -150,9 +150,8 @@ uint8_t cc1101_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* data, uint
|
||||
}
|
||||
|
||||
uint8_t cc1101_read_fifo(FuriHalSpiBusHandle* handle, uint8_t* data, uint8_t* size) {
|
||||
uint8_t buff_tx[64];
|
||||
buff_tx[0] = CC1101_FIFO | CC1101_READ | CC1101_BURST;
|
||||
uint8_t buff_rx[2];
|
||||
uint8_t buff_trx[2];
|
||||
buff_trx[0] = CC1101_FIFO | CC1101_READ | CC1101_BURST;
|
||||
|
||||
// Start transaction
|
||||
// Wait IC to become ready
|
||||
@@ -160,15 +159,15 @@ uint8_t cc1101_read_fifo(FuriHalSpiBusHandle* handle, uint8_t* data, uint8_t* si
|
||||
;
|
||||
|
||||
// First byte - packet length
|
||||
furi_hal_spi_bus_trx(handle, buff_tx, buff_rx, 2, CC1101_TIMEOUT);
|
||||
furi_hal_spi_bus_trx(handle, buff_trx, buff_trx, 2, CC1101_TIMEOUT);
|
||||
|
||||
// Check that the packet is placed in the receive buffer
|
||||
if(buff_rx[1] > 64) {
|
||||
if(buff_trx[1] > 64) {
|
||||
*size = 64;
|
||||
} else {
|
||||
*size = buff_rx[1];
|
||||
*size = buff_trx[1];
|
||||
}
|
||||
furi_hal_spi_bus_trx(handle, &buff_tx[1], data, *size, CC1101_TIMEOUT);
|
||||
furi_hal_spi_bus_trx(handle, NULL, data, *size, CC1101_TIMEOUT);
|
||||
|
||||
return *size;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
#include <furi.h>
|
||||
|
||||
#define err(...) FURI_LOG_E("Heatshrink", "Error: %d-%s", __VA_ARGS__)
|
||||
@@ -6,6 +6,10 @@ env.Append(
|
||||
],
|
||||
SDK_HEADERS=[
|
||||
File("flipper_application.h"),
|
||||
File("plugins/plugin_manager.h"),
|
||||
File("plugins/composite_resolver.h"),
|
||||
File("api_hashtable/api_hashtable.h"),
|
||||
File("api_hashtable/compilesort.hpp"),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -14,6 +18,7 @@ libenv = env.Clone(FW_LIB_NAME="flipper_application")
|
||||
libenv.ApplyLibFlags()
|
||||
|
||||
sources = libenv.GlobRecursive("*.c")
|
||||
sources.append(File("api_hashtable/api_hashtable.cpp"))
|
||||
|
||||
lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources)
|
||||
libenv.Install("${LIB_DIST_DIR}", lib)
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
#include "api_hashtable.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <algorithm>
|
||||
|
||||
#define TAG "hashtable_api"
|
||||
|
||||
bool elf_resolve_from_hashtable(
|
||||
const ElfApiInterface* interface,
|
||||
const char* name,
|
||||
Elf32_Addr* address) {
|
||||
const HashtableApiInterface* hashtable_interface =
|
||||
static_cast<const HashtableApiInterface*>(interface);
|
||||
bool result = false;
|
||||
uint32_t gnu_sym_hash = elf_gnu_hash(name);
|
||||
|
||||
sym_entry key = {
|
||||
.hash = gnu_sym_hash,
|
||||
.address = 0,
|
||||
};
|
||||
|
||||
auto find_res =
|
||||
std::lower_bound(hashtable_interface->table_cbegin, hashtable_interface->table_cend, key);
|
||||
if((find_res == hashtable_interface->table_cend || (find_res->hash != gnu_sym_hash))) {
|
||||
FURI_LOG_W(
|
||||
TAG,
|
||||
"Can't find symbol '%s' (hash %lx) @ %p!",
|
||||
name,
|
||||
gnu_sym_hash,
|
||||
hashtable_interface->table_cbegin);
|
||||
result = false;
|
||||
} else {
|
||||
result = true;
|
||||
*address = find_res->address;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
|
||||
#include <flipper_application/elf/elf_api_interface.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Symbol table entry
|
||||
*/
|
||||
struct sym_entry {
|
||||
uint32_t hash;
|
||||
uint32_t address;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Resolver for API entries using a pre-sorted table with hashes
|
||||
* @param interface pointer to HashtableApiInterface
|
||||
* @param name function name
|
||||
* @param address output for function address
|
||||
* @return true if the table contains a function
|
||||
*/
|
||||
bool elf_resolve_from_hashtable(
|
||||
const ElfApiInterface* interface,
|
||||
const char* name,
|
||||
Elf32_Addr* address);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
|
||||
/**
|
||||
* @brief HashtableApiInterface is an implementation of ElfApiInterface
|
||||
* that uses a hash table to resolve function addresses.
|
||||
* table_cbegin and table_cend must point to a sorted array of sym_entry
|
||||
*/
|
||||
struct HashtableApiInterface : public ElfApiInterface {
|
||||
const sym_entry *table_cbegin, *table_cend;
|
||||
};
|
||||
|
||||
#define API_METHOD(x, ret_type, args_type) \
|
||||
sym_entry { \
|
||||
.hash = elf_gnu_hash(#x), .address = (uint32_t)(static_cast<ret_type(*) args_type>(x)) \
|
||||
}
|
||||
|
||||
#define API_VARIABLE(x, var_type) \
|
||||
sym_entry { .hash = elf_gnu_hash(#x), .address = (uint32_t)(&(x)), }
|
||||
|
||||
constexpr bool operator<(const sym_entry& k1, const sym_entry& k2) {
|
||||
return k1.hash < k2.hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculate hash for a string using the ELF GNU hash algorithm
|
||||
* @param s string to calculate hash for
|
||||
* @return hash value
|
||||
*/
|
||||
constexpr uint32_t elf_gnu_hash(const char* s) {
|
||||
uint32_t h = 0x1505;
|
||||
for(unsigned char c = *s; c != '\0'; c = *++s) {
|
||||
h = (h << 5) + h + c;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
/* Compile-time check for hash collisions in API table.
|
||||
* Usage: static_assert(!has_hash_collisions(api_methods), "Hash collision detected");
|
||||
*/
|
||||
template <std::size_t N>
|
||||
constexpr bool has_hash_collisions(const std::array<sym_entry, N>& api_methods) {
|
||||
for(std::size_t i = 0; i < (N - 1); ++i) {
|
||||
if(api_methods[i].hash == api_methods[i + 1].hash) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* Implementation of compile-time sort for symbol table entries.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
#include <iterator>
|
||||
#include <array>
|
||||
|
||||
namespace cstd {
|
||||
|
||||
template <typename RAIt>
|
||||
constexpr RAIt next(RAIt it, typename std::iterator_traits<RAIt>::difference_type n = 1) {
|
||||
return it + n;
|
||||
}
|
||||
|
||||
template <typename RAIt>
|
||||
constexpr auto distance(RAIt first, RAIt last) {
|
||||
return last - first;
|
||||
}
|
||||
|
||||
template <class ForwardIt1, class ForwardIt2>
|
||||
constexpr void iter_swap(ForwardIt1 a, ForwardIt2 b) {
|
||||
auto temp = std::move(*a);
|
||||
*a = std::move(*b);
|
||||
*b = std::move(temp);
|
||||
}
|
||||
|
||||
template <class InputIt, class UnaryPredicate>
|
||||
constexpr InputIt find_if_not(InputIt first, InputIt last, UnaryPredicate q) {
|
||||
for(; first != last; ++first) {
|
||||
if(!q(*first)) {
|
||||
return first;
|
||||
}
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
template <class ForwardIt, class UnaryPredicate>
|
||||
constexpr ForwardIt partition(ForwardIt first, ForwardIt last, UnaryPredicate p) {
|
||||
first = cstd::find_if_not(first, last, p);
|
||||
if(first == last) return first;
|
||||
|
||||
for(ForwardIt i = cstd::next(first); i != last; ++i) {
|
||||
if(p(*i)) {
|
||||
cstd::iter_swap(i, first);
|
||||
++first;
|
||||
}
|
||||
}
|
||||
return first;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template <class RAIt, class Compare = std::less<> >
|
||||
constexpr void quick_sort(RAIt first, RAIt last, Compare cmp = Compare{}) {
|
||||
auto const N = cstd::distance(first, last);
|
||||
if(N <= 1) return;
|
||||
auto const pivot = *cstd::next(first, N / 2);
|
||||
auto const middle1 =
|
||||
cstd::partition(first, last, [=](auto const& elem) { return cmp(elem, pivot); });
|
||||
auto const middle2 =
|
||||
cstd::partition(middle1, last, [=](auto const& elem) { return !cmp(pivot, elem); });
|
||||
quick_sort(first, middle1, cmp); // assert(std::is_sorted(first, middle1, cmp));
|
||||
quick_sort(middle2, last, cmp); // assert(std::is_sorted(middle2, last, cmp));
|
||||
}
|
||||
|
||||
template <typename Range>
|
||||
constexpr auto sort(Range&& range) {
|
||||
quick_sort(std::begin(range), std::end(range));
|
||||
return range;
|
||||
}
|
||||
|
||||
template <typename V, typename... T>
|
||||
constexpr auto array_of(T&&... t) -> std::array<V, sizeof...(T)> {
|
||||
return {{std::forward<T>(t)...}};
|
||||
}
|
||||
|
||||
template <typename T, typename... N>
|
||||
constexpr auto my_make_array(N&&... args) -> std::array<T, sizeof...(args)> {
|
||||
return {std::forward<N>(args)...};
|
||||
}
|
||||
|
||||
namespace traits {
|
||||
template <typename T, typename... Ts>
|
||||
struct array_type {
|
||||
using type = T;
|
||||
};
|
||||
|
||||
template <typename T, typename... Ts>
|
||||
static constexpr bool are_same_type() {
|
||||
return std::conjunction_v<std::is_same<T, Ts>...>;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
constexpr auto create_array(const T&&... values) {
|
||||
using array_type = typename traits::array_type<T...>::type;
|
||||
static_assert(sizeof...(T) > 0, "an array must have at least one element");
|
||||
static_assert(traits::are_same_type<T...>(), "all elements must have same type");
|
||||
return std::array<array_type, sizeof...(T)>{values...};
|
||||
}
|
||||
|
||||
template <typename T, typename... Ts>
|
||||
constexpr auto create_array_t(const Ts&&... values) {
|
||||
using array_type = T;
|
||||
static_assert(sizeof...(Ts) > 0, "an array must have at least one element");
|
||||
static_assert(traits::are_same_type<Ts...>(), "all elements must have same type");
|
||||
return std::array<array_type, sizeof...(Ts)>{static_cast<T>(values)...};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,361 @@
|
||||
#include "application_assets.h"
|
||||
#include <toolbox/path.h>
|
||||
#include <storage/storage_i.h>
|
||||
|
||||
// #define ELF_ASSETS_DEBUG_LOG 1
|
||||
|
||||
#ifndef ELF_ASSETS_DEBUG_LOG
|
||||
#undef FURI_LOG_D
|
||||
#define FURI_LOG_D(...)
|
||||
#undef FURI_LOG_E
|
||||
#define FURI_LOG_E(...)
|
||||
#endif
|
||||
|
||||
#define FLIPPER_APPLICATION_ASSETS_MAGIC 0x4F4C5A44
|
||||
#define FLIPPER_APPLICATION_ASSETS_VERSION 1
|
||||
#define FLIPPER_APPLICATION_ASSETS_SIGNATURE_FILENAME ".assets.signature"
|
||||
|
||||
#define BUFFER_SIZE 512
|
||||
|
||||
#define TAG "fap_assets"
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
typedef struct {
|
||||
uint32_t magic;
|
||||
uint32_t version;
|
||||
uint32_t dirs_count;
|
||||
uint32_t files_count;
|
||||
} FlipperApplicationAssetsHeader;
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
typedef enum {
|
||||
AssetsSignatureResultEqual,
|
||||
AssetsSignatureResultNotEqual,
|
||||
AssetsSignatureResultError,
|
||||
} AssetsSignatureResult;
|
||||
|
||||
static FuriString* flipper_application_assets_alloc_app_full_path(FuriString* app_name) {
|
||||
furi_assert(app_name);
|
||||
FuriString* full_path = furi_string_alloc_set(APPS_ASSETS_PATH "/");
|
||||
furi_string_cat(full_path, app_name);
|
||||
return full_path;
|
||||
}
|
||||
|
||||
static FuriString* flipper_application_assets_alloc_signature_file_path(FuriString* app_name) {
|
||||
furi_assert(app_name);
|
||||
FuriString* signature_file_path = flipper_application_assets_alloc_app_full_path(app_name);
|
||||
furi_string_cat(signature_file_path, "/" FLIPPER_APPLICATION_ASSETS_SIGNATURE_FILENAME);
|
||||
|
||||
return signature_file_path;
|
||||
}
|
||||
|
||||
static uint8_t* flipper_application_assets_alloc_and_load_data(File* file, size_t* size) {
|
||||
furi_assert(file);
|
||||
|
||||
uint8_t* data = NULL;
|
||||
uint32_t length = 0;
|
||||
|
||||
// read data length
|
||||
if(storage_file_read(file, &length, sizeof(length)) != sizeof(length)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
data = malloc(length);
|
||||
|
||||
// read data
|
||||
if(storage_file_read(file, (void*)data, length) != length) {
|
||||
free((void*)data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if(size != NULL) {
|
||||
*size = length;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static bool flipper_application_assets_process_files(
|
||||
Storage* storage,
|
||||
File* file,
|
||||
FuriString* app_name,
|
||||
uint32_t files_count) {
|
||||
furi_assert(storage);
|
||||
furi_assert(file);
|
||||
furi_assert(app_name);
|
||||
|
||||
UNUSED(storage);
|
||||
|
||||
bool success = false;
|
||||
uint32_t length = 0;
|
||||
char* path = NULL;
|
||||
FuriString* file_path = furi_string_alloc();
|
||||
File* destination = storage_file_alloc(storage);
|
||||
|
||||
FuriString* full_path = flipper_application_assets_alloc_app_full_path(app_name);
|
||||
|
||||
for(uint32_t i = 0; i < files_count; i++) {
|
||||
path = (char*)flipper_application_assets_alloc_and_load_data(file, NULL);
|
||||
|
||||
if(path == NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
// read file size
|
||||
if(storage_file_read(file, &length, sizeof(length)) != sizeof(length)) {
|
||||
break;
|
||||
}
|
||||
|
||||
furi_string_set(file_path, full_path);
|
||||
furi_string_cat(file_path, "/");
|
||||
furi_string_cat(file_path, path);
|
||||
|
||||
if(!storage_file_open(
|
||||
destination, furi_string_get_cstr(file_path), FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
|
||||
FURI_LOG_E(TAG, "Can't create file: %s", furi_string_get_cstr(file_path));
|
||||
break;
|
||||
}
|
||||
|
||||
// copy data to file
|
||||
if(!storage_file_copy_to_file(file, destination, length)) {
|
||||
FURI_LOG_E(TAG, "Can't copy data to file: %s", furi_string_get_cstr(file_path));
|
||||
break;
|
||||
}
|
||||
|
||||
storage_file_close(destination);
|
||||
|
||||
free(path);
|
||||
path = NULL;
|
||||
|
||||
if(i == files_count - 1) {
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(path != NULL) {
|
||||
free(path);
|
||||
}
|
||||
|
||||
storage_file_free(destination);
|
||||
furi_string_free(file_path);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool flipper_application_assets_process_dirs(
|
||||
Storage* storage,
|
||||
File* file,
|
||||
FuriString* app_name,
|
||||
uint32_t dirs_count) {
|
||||
furi_assert(storage);
|
||||
furi_assert(file);
|
||||
furi_assert(app_name);
|
||||
|
||||
bool success = false;
|
||||
FuriString* full_path = flipper_application_assets_alloc_app_full_path(app_name);
|
||||
|
||||
do {
|
||||
if(!storage_simply_mkdir(storage, APPS_ASSETS_PATH)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(!storage_simply_mkdir(storage, furi_string_get_cstr(full_path))) {
|
||||
break;
|
||||
}
|
||||
|
||||
FuriString* dir_path = furi_string_alloc();
|
||||
char* path = NULL;
|
||||
|
||||
for(uint32_t i = 0; i < dirs_count; i++) {
|
||||
path = (char*)flipper_application_assets_alloc_and_load_data(file, NULL);
|
||||
|
||||
if(path == NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
furi_string_set(dir_path, full_path);
|
||||
furi_string_cat(dir_path, "/");
|
||||
furi_string_cat(dir_path, path);
|
||||
|
||||
if(!storage_simply_mkdir(storage, furi_string_get_cstr(dir_path))) {
|
||||
FURI_LOG_E(TAG, "Can't create directory: %s", furi_string_get_cstr(dir_path));
|
||||
break;
|
||||
}
|
||||
|
||||
free(path);
|
||||
path = NULL;
|
||||
|
||||
if(i == dirs_count - 1) {
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(path != NULL) {
|
||||
free(path);
|
||||
}
|
||||
|
||||
furi_string_free(dir_path);
|
||||
} while(false);
|
||||
|
||||
furi_string_free(full_path);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static AssetsSignatureResult flipper_application_assets_process_signature(
|
||||
Storage* storage,
|
||||
File* file,
|
||||
FuriString* app_name,
|
||||
uint8_t** signature_data,
|
||||
size_t* signature_data_size) {
|
||||
furi_assert(storage);
|
||||
furi_assert(file);
|
||||
furi_assert(app_name);
|
||||
furi_assert(signature_data);
|
||||
furi_assert(signature_data_size);
|
||||
|
||||
AssetsSignatureResult result = AssetsSignatureResultError;
|
||||
File* signature_file = storage_file_alloc(storage);
|
||||
FuriString* signature_file_path =
|
||||
flipper_application_assets_alloc_signature_file_path(app_name);
|
||||
|
||||
do {
|
||||
// read signature
|
||||
*signature_data =
|
||||
flipper_application_assets_alloc_and_load_data(file, signature_data_size);
|
||||
|
||||
if(*signature_data == NULL) { //-V547
|
||||
FURI_LOG_E(TAG, "Can't read signature");
|
||||
break;
|
||||
}
|
||||
|
||||
result = AssetsSignatureResultNotEqual;
|
||||
|
||||
if(!storage_file_open(
|
||||
signature_file,
|
||||
furi_string_get_cstr(signature_file_path),
|
||||
FSAM_READ_WRITE,
|
||||
FSOM_OPEN_EXISTING)) {
|
||||
FURI_LOG_E(TAG, "Can't open signature file");
|
||||
break;
|
||||
}
|
||||
|
||||
size_t signature_size = storage_file_size(signature_file);
|
||||
uint8_t* signature_file_data = malloc(signature_size);
|
||||
if(storage_file_read(signature_file, signature_file_data, signature_size) !=
|
||||
signature_size) {
|
||||
FURI_LOG_E(TAG, "Can't read signature file");
|
||||
free(signature_file_data);
|
||||
break;
|
||||
}
|
||||
|
||||
if(memcmp(*signature_data, signature_file_data, signature_size) == 0) {
|
||||
FURI_LOG_D(TAG, "Assets signature is equal");
|
||||
result = AssetsSignatureResultEqual;
|
||||
}
|
||||
|
||||
free(signature_file_data);
|
||||
} while(0);
|
||||
|
||||
storage_file_free(signature_file);
|
||||
furi_string_free(signature_file_path);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool flipper_application_assets_load(File* file, const char* elf_path, size_t offset, size_t size) {
|
||||
UNUSED(size);
|
||||
furi_assert(file);
|
||||
furi_assert(elf_path);
|
||||
FlipperApplicationAssetsHeader header;
|
||||
bool result = false;
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
uint8_t* signature_data = NULL;
|
||||
size_t signature_data_size = 0;
|
||||
FuriString* app_name = furi_string_alloc();
|
||||
path_extract_filename_no_ext(elf_path, app_name);
|
||||
|
||||
FURI_LOG_D(TAG, "Loading assets for %s", furi_string_get_cstr(app_name));
|
||||
|
||||
do {
|
||||
if(!storage_file_seek(file, offset, true)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// read header
|
||||
if(storage_file_read(file, &header, sizeof(header)) != sizeof(header)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(header.magic != FLIPPER_APPLICATION_ASSETS_MAGIC) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(header.version != FLIPPER_APPLICATION_ASSETS_VERSION) {
|
||||
break;
|
||||
}
|
||||
|
||||
// process signature
|
||||
AssetsSignatureResult signature_result = flipper_application_assets_process_signature(
|
||||
storage, file, app_name, &signature_data, &signature_data_size);
|
||||
|
||||
if(signature_result == AssetsSignatureResultError) {
|
||||
FURI_LOG_E(TAG, "Assets signature error");
|
||||
break;
|
||||
} else if(signature_result == AssetsSignatureResultEqual) {
|
||||
FURI_LOG_D(TAG, "Assets signature equal, skip loading");
|
||||
result = true;
|
||||
break;
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Assets signature not equal, loading");
|
||||
|
||||
// remove old assets
|
||||
FuriString* full_path = flipper_application_assets_alloc_app_full_path(app_name);
|
||||
storage_simply_remove_recursive(storage, furi_string_get_cstr(full_path));
|
||||
furi_string_free(full_path);
|
||||
|
||||
FURI_LOG_D(TAG, "Assets removed");
|
||||
}
|
||||
|
||||
// process directories
|
||||
if(!flipper_application_assets_process_dirs(storage, file, app_name, header.dirs_count)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// process files
|
||||
if(!flipper_application_assets_process_files(storage, file, app_name, header.files_count)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// write signature
|
||||
FuriString* signature_file_path =
|
||||
flipper_application_assets_alloc_signature_file_path(app_name);
|
||||
File* signature_file = storage_file_alloc(storage);
|
||||
|
||||
if(storage_file_open(
|
||||
signature_file,
|
||||
furi_string_get_cstr(signature_file_path),
|
||||
FSAM_WRITE,
|
||||
FSOM_CREATE_ALWAYS)) {
|
||||
storage_file_write(signature_file, signature_data, signature_data_size);
|
||||
}
|
||||
|
||||
storage_file_free(signature_file);
|
||||
furi_string_free(signature_file_path);
|
||||
|
||||
result = true;
|
||||
} while(false);
|
||||
|
||||
if(signature_data != NULL) {
|
||||
free(signature_data);
|
||||
}
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
furi_string_free(app_name);
|
||||
|
||||
FURI_LOG_D(TAG, "Assets loading %s", result ? "success" : "failed");
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* @file application_assets.h
|
||||
* Flipper application assets
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <storage/storage.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
bool flipper_application_assets_load(File* file, const char* elf_path, size_t offset, size_t size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "application_manifest.h"
|
||||
|
||||
#include <furi_hal_version.h>
|
||||
|
||||
bool flipper_application_manifest_is_valid(const FlipperApplicationManifest* manifest) {
|
||||
if((manifest->base.manifest_magic != FAP_MANIFEST_MAGIC) ||
|
||||
(manifest->base.manifest_version != FAP_MANIFEST_SUPPORTED_VERSION)) {
|
||||
@@ -19,3 +21,8 @@ bool flipper_application_manifest_is_compatible(
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool flipper_application_manifest_is_target_compatible(const FlipperApplicationManifest* manifest) {
|
||||
const Version* version = furi_hal_version_get_firmware_version();
|
||||
return version_get_target(version) == manifest->base.hardware_target_id;
|
||||
}
|
||||
@@ -65,6 +65,14 @@ bool flipper_application_manifest_is_compatible(
|
||||
const FlipperApplicationManifest* manifest,
|
||||
const ElfApiInterface* api_interface);
|
||||
|
||||
/**
|
||||
* @brief Check if application is compatible with current hardware
|
||||
*
|
||||
* @param manifest
|
||||
* @return bool
|
||||
*/
|
||||
bool flipper_application_manifest_is_target_compatible(const FlipperApplicationManifest* manifest);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -3,10 +3,14 @@
|
||||
#include <elf.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define ELF_INVALID_ADDRESS 0xFFFFFFFF
|
||||
|
||||
typedef struct {
|
||||
/**
|
||||
* @brief Interface for ELF loader to resolve symbols
|
||||
*/
|
||||
typedef struct ElfApiInterface {
|
||||
uint16_t api_version_major;
|
||||
uint16_t api_version_minor;
|
||||
bool (*resolver_callback)(const char* name, Elf32_Addr* address);
|
||||
bool (*resolver_callback)(
|
||||
const struct ElfApiInterface* interface,
|
||||
const char* name,
|
||||
Elf32_Addr* address);
|
||||
} ElfApiInterface;
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
#define FURI_LOG_D(...)
|
||||
#endif
|
||||
|
||||
#define ELF_INVALID_ADDRESS 0xFFFFFFFF
|
||||
|
||||
#define TRAMPOLINE_CODE_SIZE 6
|
||||
|
||||
/**
|
||||
@@ -166,7 +168,7 @@ static ELFSection* elf_section_of(ELFFile* elf, int index) {
|
||||
static Elf32_Addr elf_address_of(ELFFile* elf, Elf32_Sym* sym, const char* sName) {
|
||||
if(sym->st_shndx == SHN_UNDEF) {
|
||||
Elf32_Addr addr = 0;
|
||||
if(elf->api_interface->resolver_callback(sName, &addr)) {
|
||||
if(elf->api_interface->resolver_callback(elf->api_interface, sName, &addr)) {
|
||||
return addr;
|
||||
}
|
||||
} else {
|
||||
@@ -241,7 +243,7 @@ static void elf_relocate_jmp_call(ELFFile* elf, Elf32_Addr relAddr, int type, El
|
||||
if(to_thumb || (symAddr & 2) || (!is_call)) {
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"can't relocate value at %x, %s, doing trampoline",
|
||||
"can't relocate value at %lx, %s, doing trampoline",
|
||||
relAddr,
|
||||
elf_reloc_type_to_str(type));
|
||||
|
||||
@@ -421,29 +423,11 @@ typedef enum {
|
||||
SectionTypeRelData = 1 << 2,
|
||||
SectionTypeSymTab = 1 << 3,
|
||||
SectionTypeStrTab = 1 << 4,
|
||||
SectionTypeManifest = 1 << 5,
|
||||
SectionTypeDebugLink = 1 << 6,
|
||||
SectionTypeDebugLink = 1 << 5,
|
||||
|
||||
SectionTypeValid = SectionTypeSymTab | SectionTypeStrTab | SectionTypeManifest,
|
||||
SectionTypeValid = SectionTypeSymTab | SectionTypeStrTab,
|
||||
} SectionType;
|
||||
|
||||
static bool elf_load_metadata(
|
||||
ELFFile* elf,
|
||||
Elf32_Shdr* section_header,
|
||||
FlipperApplicationManifest* manifest) {
|
||||
if(section_header->sh_size < sizeof(FlipperApplicationManifest)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(manifest == NULL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return storage_file_seek(elf->fd, section_header->sh_offset, true) &&
|
||||
storage_file_read(elf->fd, manifest, section_header->sh_size) ==
|
||||
section_header->sh_size;
|
||||
}
|
||||
|
||||
static bool elf_load_debug_link(ELFFile* elf, Elf32_Shdr* section_header) {
|
||||
elf->debug_link_info.debug_link_size = section_header->sh_size;
|
||||
elf->debug_link_info.debug_link = malloc(section_header->sh_size);
|
||||
@@ -478,7 +462,7 @@ static bool elf_load_section_data(ELFFile* elf, ELFSection* section, Elf32_Shdr*
|
||||
return false;
|
||||
}
|
||||
|
||||
FURI_LOG_D(TAG, "0x%X", section->data);
|
||||
FURI_LOG_D(TAG, "0x%p", section->data);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -486,8 +470,7 @@ static SectionType elf_preload_section(
|
||||
ELFFile* elf,
|
||||
size_t section_idx,
|
||||
Elf32_Shdr* section_header,
|
||||
FuriString* name_string,
|
||||
FlipperApplicationManifest* manifest) {
|
||||
FuriString* name_string) {
|
||||
const char* name = furi_string_get_cstr(name_string);
|
||||
|
||||
#ifdef ELF_DEBUG_LOG
|
||||
@@ -533,10 +516,13 @@ static SectionType elf_preload_section(
|
||||
section_p->sec_idx = section_idx;
|
||||
|
||||
if(section_header->sh_type == SHT_PREINIT_ARRAY) {
|
||||
furi_assert(elf->preinit_array == NULL);
|
||||
elf->preinit_array = section_p;
|
||||
} else if(section_header->sh_type == SHT_INIT_ARRAY) {
|
||||
furi_assert(elf->init_array == NULL);
|
||||
elf->init_array = section_p;
|
||||
} else if(section_header->sh_type == SHT_FINI_ARRAY) {
|
||||
furi_assert(elf->fini_array == NULL);
|
||||
elf->fini_array = section_p;
|
||||
}
|
||||
|
||||
@@ -572,16 +558,6 @@ static SectionType elf_preload_section(
|
||||
return SectionTypeStrTab;
|
||||
}
|
||||
|
||||
// Load manifest section
|
||||
if(strcmp(name, ".fapmeta") == 0) {
|
||||
FURI_LOG_D(TAG, "Found .fapmeta section");
|
||||
if(elf_load_metadata(elf, section_header, manifest)) {
|
||||
return SectionTypeManifest;
|
||||
} else {
|
||||
return SectionTypeERROR;
|
||||
}
|
||||
}
|
||||
|
||||
// Load debug link section
|
||||
if(strcmp(name, ".gnu_debuglink") == 0) {
|
||||
FURI_LOG_D(TAG, "Found .gnu_debuglink section");
|
||||
@@ -634,10 +610,17 @@ ELFFile* elf_file_alloc(Storage* storage, const ElfApiInterface* api_interface)
|
||||
elf->api_interface = api_interface;
|
||||
ELFSectionDict_init(elf->sections);
|
||||
AddressCache_init(elf->trampoline_cache);
|
||||
elf->init_array_called = false;
|
||||
return elf;
|
||||
}
|
||||
|
||||
void elf_file_free(ELFFile* elf) {
|
||||
// furi_check(!elf->init_array_called);
|
||||
if(elf->init_array_called) {
|
||||
FURI_LOG_W(TAG, "Init array was called, but fini array wasn't");
|
||||
elf_file_call_section_list(elf->fini_array, true);
|
||||
}
|
||||
|
||||
// free sections data
|
||||
{
|
||||
ELFSectionDict_it_t it;
|
||||
@@ -692,41 +675,12 @@ bool elf_file_open(ELFFile* elf, const char* path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool elf_file_load_manifest(ELFFile* elf, FlipperApplicationManifest* manifest) {
|
||||
bool result = false;
|
||||
FuriString* name;
|
||||
name = furi_string_alloc();
|
||||
|
||||
FURI_LOG_D(TAG, "Looking for manifest section");
|
||||
for(size_t section_idx = 1; section_idx < elf->sections_count; section_idx++) {
|
||||
Elf32_Shdr section_header;
|
||||
|
||||
furi_string_reset(name);
|
||||
if(!elf_read_section(elf, section_idx, §ion_header, name)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_string_cmp(name, ".fapmeta") == 0) {
|
||||
if(elf_load_metadata(elf, §ion_header, manifest)) {
|
||||
FURI_LOG_D(TAG, "Load manifest done");
|
||||
result = true;
|
||||
break;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
furi_string_free(name);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool elf_file_load_section_table(ELFFile* elf, FlipperApplicationManifest* manifest) {
|
||||
bool elf_file_load_section_table(ELFFile* elf) {
|
||||
SectionType loaded_sections = SectionTypeERROR;
|
||||
FuriString* name;
|
||||
name = furi_string_alloc();
|
||||
FuriString* name = furi_string_alloc();
|
||||
|
||||
FURI_LOG_D(TAG, "Scan ELF indexs...");
|
||||
// TODO: why we start from 1?
|
||||
for(size_t section_idx = 1; section_idx < elf->sections_count; section_idx++) {
|
||||
Elf32_Shdr section_header;
|
||||
|
||||
@@ -738,8 +692,7 @@ bool elf_file_load_section_table(ELFFile* elf, FlipperApplicationManifest* manif
|
||||
|
||||
FURI_LOG_D(
|
||||
TAG, "Preloading data for section #%d %s", section_idx, furi_string_get_cstr(name));
|
||||
SectionType section_type =
|
||||
elf_preload_section(elf, section_idx, §ion_header, name, manifest);
|
||||
SectionType section_type = elf_preload_section(elf, section_idx, §ion_header, name);
|
||||
loaded_sections |= section_type;
|
||||
|
||||
if(section_type == SectionTypeERROR) {
|
||||
@@ -753,14 +706,49 @@ bool elf_file_load_section_table(ELFFile* elf, FlipperApplicationManifest* manif
|
||||
return IS_FLAGS_SET(loaded_sections, SectionTypeValid);
|
||||
}
|
||||
|
||||
ElfProcessSectionResult elf_process_section(
|
||||
ELFFile* elf,
|
||||
const char* name,
|
||||
ElfProcessSection* process_section,
|
||||
void* context) {
|
||||
ElfProcessSectionResult result = ElfProcessSectionResultNotFound;
|
||||
FuriString* section_name = furi_string_alloc();
|
||||
Elf32_Shdr section_header;
|
||||
|
||||
// find section
|
||||
// TODO: why we start from 1?
|
||||
for(size_t section_idx = 1; section_idx < elf->sections_count; section_idx++) {
|
||||
furi_string_reset(section_name);
|
||||
if(!elf_read_section(elf, section_idx, §ion_header, section_name)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_string_cmp(section_name, name) == 0) {
|
||||
result = ElfProcessSectionResultCannotProcess;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(result != ElfProcessSectionResultNotFound) { //-V547
|
||||
if(process_section(elf->fd, section_header.sh_offset, section_header.sh_size, context)) {
|
||||
result = ElfProcessSectionResultSuccess;
|
||||
} else {
|
||||
result = ElfProcessSectionResultCannotProcess;
|
||||
}
|
||||
}
|
||||
|
||||
furi_string_free(section_name);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ELFFileLoadStatus elf_file_load_sections(ELFFile* elf) {
|
||||
ELFFileLoadStatus status = ELFFileLoadStatusSuccess;
|
||||
ELFSectionDict_it_t it;
|
||||
|
||||
AddressCache_init(elf->relocation_cache);
|
||||
|
||||
for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it);
|
||||
ELFSectionDict_next(it)) {
|
||||
for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it); ELFSectionDict_next(it)) {
|
||||
ELFSectionDict_itref_t* itref = ELFSectionDict_ref(it);
|
||||
FURI_LOG_D(TAG, "Relocating section '%s'", itref->key);
|
||||
if(!elf_relocate_section(elf, &itref->value)) {
|
||||
@@ -798,19 +786,26 @@ ELFFileLoadStatus elf_file_load_sections(ELFFile* elf) {
|
||||
return status;
|
||||
}
|
||||
|
||||
void elf_file_pre_run(ELFFile* elf) {
|
||||
void elf_file_call_init(ELFFile* elf) {
|
||||
furi_check(!elf->init_array_called);
|
||||
elf_file_call_section_list(elf->preinit_array, false);
|
||||
elf_file_call_section_list(elf->init_array, false);
|
||||
elf->init_array_called = true;
|
||||
}
|
||||
|
||||
int32_t elf_file_run(ELFFile* elf, void* args) {
|
||||
int32_t result;
|
||||
result = ((int32_t(*)(void*))elf->entry)(args);
|
||||
return result;
|
||||
bool elf_file_is_init_complete(ELFFile* elf) {
|
||||
return elf->init_array_called;
|
||||
}
|
||||
|
||||
void elf_file_post_run(ELFFile* elf) {
|
||||
void* elf_file_get_entry_point(ELFFile* elf) {
|
||||
furi_check(elf->init_array_called);
|
||||
return (void*)elf->entry;
|
||||
}
|
||||
|
||||
void elf_file_call_fini(ELFFile* elf) {
|
||||
furi_check(elf->init_array_called);
|
||||
elf_file_call_section_list(elf->fini_array, true);
|
||||
elf->init_array_called = false;
|
||||
}
|
||||
|
||||
const ElfApiInterface* elf_file_get_api_interface(ELFFile* elf_file) {
|
||||
@@ -835,8 +830,9 @@ void elf_file_init_debug_info(ELFFile* elf, ELFDebugInfo* debug_info) {
|
||||
|
||||
const void* data_ptr = itref->value.data;
|
||||
if(data_ptr) {
|
||||
debug_info->mmap_entries[mmap_entry_idx].address = (uint32_t)data_ptr;
|
||||
debug_info->mmap_entries[mmap_entry_idx].name = itref->key;
|
||||
ELFMemoryMapEntry* entry = &debug_info->mmap_entries[mmap_entry_idx];
|
||||
entry->address = (uint32_t)data_ptr;
|
||||
entry->name = itref->key;
|
||||
mmap_entry_idx++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,14 @@ typedef enum {
|
||||
ELFFileLoadStatusMissingImports,
|
||||
} ELFFileLoadStatus;
|
||||
|
||||
typedef enum {
|
||||
ElfProcessSectionResultNotFound,
|
||||
ElfProcessSectionResultCannotProcess,
|
||||
ElfProcessSectionResultSuccess,
|
||||
} ElfProcessSectionResult;
|
||||
|
||||
typedef bool(ElfProcessSection)(File* file, size_t offset, size_t size, void* context);
|
||||
|
||||
/**
|
||||
* @brief Allocate ELFFile instance
|
||||
* @param storage
|
||||
@@ -59,21 +67,12 @@ void elf_file_free(ELFFile* elf_file);
|
||||
*/
|
||||
bool elf_file_open(ELFFile* elf_file, const char* path);
|
||||
|
||||
/**
|
||||
* @brief Load ELF file manifest
|
||||
* @param elf
|
||||
* @param manifest
|
||||
* @return bool
|
||||
*/
|
||||
bool elf_file_load_manifest(ELFFile* elf, FlipperApplicationManifest* manifest);
|
||||
|
||||
/**
|
||||
* @brief Load ELF file section table (load stage #1)
|
||||
* @param elf_file
|
||||
* @param manifest
|
||||
* @return bool
|
||||
*/
|
||||
bool elf_file_load_section_table(ELFFile* elf_file, FlipperApplicationManifest* manifest);
|
||||
bool elf_file_load_section_table(ELFFile* elf_file);
|
||||
|
||||
/**
|
||||
* @brief Load and relocate ELF file sections (load stage #2)
|
||||
@@ -83,24 +82,34 @@ bool elf_file_load_section_table(ELFFile* elf_file, FlipperApplicationManifest*
|
||||
ELFFileLoadStatus elf_file_load_sections(ELFFile* elf_file);
|
||||
|
||||
/**
|
||||
* @brief Execute ELF file pre-run stage, call static constructors for example (load stage #3)
|
||||
* @brief Execute ELF file pre-run stage,
|
||||
* call static constructors for example (load stage #3)
|
||||
* Must be done before invoking any code from the ELF file
|
||||
* @param elf
|
||||
*/
|
||||
void elf_file_pre_run(ELFFile* elf);
|
||||
void elf_file_call_init(ELFFile* elf);
|
||||
|
||||
/**
|
||||
* @brief Run ELF file (load stage #4)
|
||||
* @brief Check if ELF file pre-run stage was executed and its code is runnable
|
||||
* @param elf
|
||||
*/
|
||||
bool elf_file_is_init_complete(ELFFile* elf);
|
||||
|
||||
/**
|
||||
* @brief Get actual entry point for ELF file
|
||||
* @param elf_file
|
||||
* @param args
|
||||
* @return int32_t
|
||||
*/
|
||||
int32_t elf_file_run(ELFFile* elf_file, void* args);
|
||||
void* elf_file_get_entry_point(ELFFile* elf_file);
|
||||
|
||||
/**
|
||||
* @brief Execute ELF file post-run stage, call static destructors for example (load stage #5)
|
||||
* @brief Execute ELF file post-run stage,
|
||||
* call static destructors for example (load stage #5)
|
||||
* Must be done if any code from the ELF file was executed
|
||||
* @param elf
|
||||
*/
|
||||
void elf_file_post_run(ELFFile* elf);
|
||||
void elf_file_call_fini(ELFFile* elf);
|
||||
|
||||
/**
|
||||
* @brief Get ELF file API interface
|
||||
@@ -122,6 +131,21 @@ void elf_file_init_debug_info(ELFFile* elf_file, ELFDebugInfo* debug_info);
|
||||
*/
|
||||
void elf_file_clear_debug_info(ELFDebugInfo* debug_info);
|
||||
|
||||
/**
|
||||
* @brief Process ELF file section
|
||||
*
|
||||
* @param elf_file
|
||||
* @param name
|
||||
* @param process_section
|
||||
* @param context
|
||||
* @return ElfProcessSectionResult
|
||||
*/
|
||||
ElfProcessSectionResult elf_process_section(
|
||||
ELFFile* elf_file,
|
||||
const char* name,
|
||||
ElfProcessSection* process_section,
|
||||
void* context);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -45,6 +45,8 @@ struct ELFFile {
|
||||
ELFSection* preinit_array;
|
||||
ELFSection* init_array;
|
||||
ELFSection* fini_array;
|
||||
|
||||
bool init_array_called;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -1,27 +1,67 @@
|
||||
#include "flipper_application.h"
|
||||
#include "elf/elf_file.h"
|
||||
#include <notification/notification_messages.h>
|
||||
#include "application_assets.h"
|
||||
|
||||
#define TAG "fapp"
|
||||
#include <m-list.h>
|
||||
|
||||
#define TAG "Fap"
|
||||
|
||||
struct FlipperApplication {
|
||||
ELFDebugInfo state;
|
||||
FlipperApplicationManifest manifest;
|
||||
ELFFile* elf;
|
||||
FuriThread* thread;
|
||||
void* ep_thread_args;
|
||||
};
|
||||
|
||||
/* For debugger access to app state */
|
||||
FlipperApplication* last_loaded_app = NULL;
|
||||
/********************** Debugger access to loader state **********************/
|
||||
|
||||
LIST_DEF(FlipperApplicationList, const FlipperApplication*, M_POD_OPLIST);
|
||||
|
||||
FlipperApplicationList_t flipper_application_loaded_app_list = {0};
|
||||
static bool flipper_application_loaded_app_list_initialized = false;
|
||||
|
||||
static void flipper_application_list_add_app(const FlipperApplication* app) {
|
||||
furi_assert(app);
|
||||
|
||||
if(!flipper_application_loaded_app_list_initialized) {
|
||||
FlipperApplicationList_init(flipper_application_loaded_app_list);
|
||||
flipper_application_loaded_app_list_initialized = true;
|
||||
}
|
||||
FlipperApplicationList_push_back(flipper_application_loaded_app_list, app);
|
||||
}
|
||||
|
||||
static void flipper_application_list_remove_app(const FlipperApplication* app) {
|
||||
furi_assert(flipper_application_loaded_app_list_initialized);
|
||||
furi_assert(app);
|
||||
|
||||
FlipperApplicationList_it_t it;
|
||||
for(FlipperApplicationList_it(it, flipper_application_loaded_app_list);
|
||||
!FlipperApplicationList_end_p(it);
|
||||
FlipperApplicationList_next(it)) {
|
||||
if(*FlipperApplicationList_ref(it) == app) {
|
||||
FlipperApplicationList_remove(flipper_application_loaded_app_list, it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
FlipperApplication*
|
||||
flipper_application_alloc(Storage* storage, const ElfApiInterface* api_interface) {
|
||||
FlipperApplication* app = malloc(sizeof(FlipperApplication));
|
||||
app->elf = elf_file_alloc(storage, api_interface);
|
||||
app->thread = NULL;
|
||||
app->ep_thread_args = NULL;
|
||||
return app;
|
||||
}
|
||||
|
||||
bool flipper_application_is_plugin(FlipperApplication* app) {
|
||||
return app->manifest.stack_size == 0;
|
||||
}
|
||||
|
||||
void flipper_application_free(FlipperApplication* app) {
|
||||
furi_assert(app);
|
||||
|
||||
@@ -30,9 +70,16 @@ void flipper_application_free(FlipperApplication* app) {
|
||||
furi_thread_free(app->thread);
|
||||
}
|
||||
|
||||
last_loaded_app = NULL;
|
||||
if(app->state.entry) {
|
||||
flipper_application_list_remove_app(app);
|
||||
}
|
||||
|
||||
elf_file_clear_debug_info(&app->state);
|
||||
|
||||
if(elf_file_is_init_complete(app->elf)) {
|
||||
elf_file_call_fini(app->elf);
|
||||
}
|
||||
|
||||
elf_file_free(app->elf);
|
||||
free(app);
|
||||
}
|
||||
@@ -43,6 +90,10 @@ static FlipperApplicationPreloadStatus
|
||||
return FlipperApplicationPreloadStatusInvalidManifest;
|
||||
}
|
||||
|
||||
if(!flipper_application_manifest_is_target_compatible(&app->manifest)) {
|
||||
return FlipperApplicationPreloadStatusTargetMismatch;
|
||||
}
|
||||
|
||||
if(!flipper_application_manifest_is_compatible(
|
||||
&app->manifest, elf_file_get_api_interface(app->elf))) {
|
||||
return FlipperApplicationPreloadStatusApiMismatch;
|
||||
@@ -51,24 +102,83 @@ static FlipperApplicationPreloadStatus
|
||||
return FlipperApplicationPreloadStatusSuccess;
|
||||
}
|
||||
|
||||
/* Parse headers, load manifest */
|
||||
FlipperApplicationPreloadStatus
|
||||
flipper_application_preload_manifest(FlipperApplication* app, const char* path) {
|
||||
if(!elf_file_open(app->elf, path) || !elf_file_load_manifest(app->elf, &app->manifest)) {
|
||||
static bool flipper_application_process_manifest_section(
|
||||
File* file,
|
||||
size_t offset,
|
||||
size_t size,
|
||||
void* context) {
|
||||
FlipperApplicationManifest* manifest = context;
|
||||
|
||||
if(size < sizeof(FlipperApplicationManifest)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(manifest == NULL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return storage_file_seek(file, offset, true) &&
|
||||
storage_file_read(file, manifest, size) == size;
|
||||
}
|
||||
|
||||
// we can't use const char* as context because we will lose the const qualifier
|
||||
typedef struct {
|
||||
const char* path;
|
||||
} FlipperApplicationPreloadAssetsContext;
|
||||
|
||||
static bool flipper_application_process_assets_section(
|
||||
File* file,
|
||||
size_t offset,
|
||||
size_t size,
|
||||
void* context) {
|
||||
FlipperApplicationPreloadAssetsContext* preload_context = context;
|
||||
return flipper_application_assets_load(file, preload_context->path, offset, size);
|
||||
}
|
||||
|
||||
static FlipperApplicationPreloadStatus
|
||||
flipper_application_load(FlipperApplication* app, const char* path, bool load_full) {
|
||||
if(!elf_file_open(app->elf, path)) {
|
||||
return FlipperApplicationPreloadStatusInvalidFile;
|
||||
}
|
||||
|
||||
// if we are loading full file
|
||||
if(load_full) {
|
||||
// load section table
|
||||
if(!elf_file_load_section_table(app->elf)) {
|
||||
return FlipperApplicationPreloadStatusInvalidFile;
|
||||
}
|
||||
|
||||
// load assets section
|
||||
FlipperApplicationPreloadAssetsContext preload_context = {.path = path};
|
||||
if(elf_process_section(
|
||||
app->elf,
|
||||
".fapassets",
|
||||
flipper_application_process_assets_section,
|
||||
&preload_context) == ElfProcessSectionResultCannotProcess) {
|
||||
return FlipperApplicationPreloadStatusInvalidFile;
|
||||
}
|
||||
}
|
||||
|
||||
// load manifest section
|
||||
if(elf_process_section(
|
||||
app->elf, ".fapmeta", flipper_application_process_manifest_section, &app->manifest) !=
|
||||
ElfProcessSectionResultSuccess) {
|
||||
return FlipperApplicationPreloadStatusInvalidFile;
|
||||
}
|
||||
|
||||
return flipper_application_validate_manifest(app);
|
||||
}
|
||||
|
||||
/* Parse headers, load manifest */
|
||||
FlipperApplicationPreloadStatus
|
||||
flipper_application_preload_manifest(FlipperApplication* app, const char* path) {
|
||||
return flipper_application_load(app, path, false);
|
||||
}
|
||||
|
||||
/* Parse headers, load full file */
|
||||
FlipperApplicationPreloadStatus
|
||||
flipper_application_preload(FlipperApplication* app, const char* path) {
|
||||
if(!elf_file_open(app->elf, path) || !elf_file_load_section_table(app->elf, &app->manifest)) {
|
||||
return FlipperApplicationPreloadStatusInvalidFile;
|
||||
}
|
||||
|
||||
return flipper_application_validate_manifest(app);
|
||||
return flipper_application_load(app, path, true);
|
||||
}
|
||||
|
||||
const FlipperApplicationManifest* flipper_application_get_manifest(FlipperApplication* app) {
|
||||
@@ -76,12 +186,12 @@ const FlipperApplicationManifest* flipper_application_get_manifest(FlipperApplic
|
||||
}
|
||||
|
||||
FlipperApplicationLoadStatus flipper_application_map_to_memory(FlipperApplication* app) {
|
||||
last_loaded_app = app;
|
||||
ELFFileLoadStatus status = elf_file_load_sections(app->elf);
|
||||
|
||||
switch(status) {
|
||||
case ELFFileLoadStatusSuccess:
|
||||
elf_file_init_debug_info(app->elf, &app->state);
|
||||
flipper_application_list_add_app(app);
|
||||
return FlipperApplicationLoadStatusSuccess;
|
||||
case ELFFileLoadStatusNoFreeMemory:
|
||||
return FlipperApplicationLoadStatusNoFreeMemory;
|
||||
@@ -93,9 +203,15 @@ FlipperApplicationLoadStatus flipper_application_map_to_memory(FlipperApplicatio
|
||||
}
|
||||
|
||||
static int32_t flipper_application_thread(void* context) {
|
||||
elf_file_pre_run(last_loaded_app->elf);
|
||||
int32_t result = elf_file_run(last_loaded_app->elf, context);
|
||||
elf_file_post_run(last_loaded_app->elf);
|
||||
furi_assert(context);
|
||||
FlipperApplication* app = (FlipperApplication*)context;
|
||||
|
||||
elf_file_call_init(app->elf);
|
||||
|
||||
FlipperApplicationEntryPoint entry_point = elf_file_get_entry_point(app->elf);
|
||||
int32_t ret_code = entry_point(app->ep_thread_args);
|
||||
|
||||
elf_file_call_fini(app->elf);
|
||||
|
||||
// wait until all notifications from RAM are completed
|
||||
NotificationApp* notifications = furi_record_open(RECORD_NOTIFICATION);
|
||||
@@ -105,17 +221,17 @@ static int32_t flipper_application_thread(void* context) {
|
||||
notification_message_block(notifications, &sequence_empty);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
|
||||
return result;
|
||||
return ret_code;
|
||||
}
|
||||
|
||||
FuriThread* flipper_application_spawn(FlipperApplication* app, void* args) {
|
||||
furi_check(app->thread == NULL);
|
||||
furi_check(!flipper_application_is_plugin(app));
|
||||
app->ep_thread_args = args;
|
||||
|
||||
const FlipperApplicationManifest* manifest = flipper_application_get_manifest(app);
|
||||
furi_check(manifest->stack_size > 0);
|
||||
|
||||
app->thread = furi_thread_alloc_ex(
|
||||
manifest->name, manifest->stack_size, flipper_application_thread, args);
|
||||
manifest->name, manifest->stack_size, flipper_application_thread, app);
|
||||
|
||||
return app->thread;
|
||||
}
|
||||
@@ -149,3 +265,28 @@ const char* flipper_application_load_status_to_string(FlipperApplicationLoadStat
|
||||
}
|
||||
return load_status_strings[status];
|
||||
}
|
||||
|
||||
const FlipperAppPluginDescriptor*
|
||||
flipper_application_plugin_get_descriptor(FlipperApplication* app) {
|
||||
if(!flipper_application_is_plugin(app)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if(!elf_file_is_init_complete(app->elf)) {
|
||||
elf_file_call_init(app->elf);
|
||||
}
|
||||
|
||||
typedef const FlipperAppPluginDescriptor* (*get_lib_descriptor_t)(void);
|
||||
get_lib_descriptor_t lib_ep = elf_file_get_entry_point(app->elf);
|
||||
furi_check(lib_ep);
|
||||
|
||||
const FlipperAppPluginDescriptor* lib_descriptor = lib_ep();
|
||||
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"Library for %s, API v. %lu loaded",
|
||||
lib_descriptor->appid,
|
||||
lib_descriptor->ep_api_version);
|
||||
|
||||
return lib_descriptor;
|
||||
}
|
||||
@@ -115,6 +115,40 @@ FlipperApplicationLoadStatus flipper_application_map_to_memory(FlipperApplicatio
|
||||
*/
|
||||
FuriThread* flipper_application_spawn(FlipperApplication* app, void* args);
|
||||
|
||||
/**
|
||||
* @brief Check if application is a plugin (not a runnable standalone app)
|
||||
* @param app Application pointer
|
||||
* @return true if application is a plugin, false otherwise
|
||||
*/
|
||||
bool flipper_application_is_plugin(FlipperApplication* app);
|
||||
|
||||
/**
|
||||
* @brief Entry point prototype for standalone applications
|
||||
*/
|
||||
typedef int32_t (*FlipperApplicationEntryPoint)(void*);
|
||||
|
||||
/**
|
||||
* @brief An object that describes a plugin - must be returned by plugin's entry point
|
||||
*/
|
||||
typedef struct {
|
||||
const char* appid;
|
||||
const uint32_t ep_api_version;
|
||||
const void* entry_point;
|
||||
} FlipperAppPluginDescriptor;
|
||||
|
||||
/**
|
||||
* @brief Entry point prototype for plugins
|
||||
*/
|
||||
typedef const FlipperAppPluginDescriptor* (*FlipperApplicationPluginEntryPoint)(void);
|
||||
|
||||
/**
|
||||
* @brief Get plugin descriptor for preloaded plugin
|
||||
* @param app Application pointer
|
||||
* @return Pointer to plugin descriptor
|
||||
*/
|
||||
const FlipperAppPluginDescriptor*
|
||||
flipper_application_plugin_get_descriptor(FlipperApplication* app);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
#include "composite_resolver.h"
|
||||
|
||||
#include <m-list.h>
|
||||
#include <m-algo.h>
|
||||
|
||||
LIST_DEF(ElfApiInterfaceList, const ElfApiInterface*, M_POD_OPLIST)
|
||||
#define M_OPL_ElfApiInterfaceList_t() LIST_OPLIST(ElfApiInterfaceList, M_POD_OPLIST)
|
||||
|
||||
struct CompositeApiResolver {
|
||||
ElfApiInterface api_interface;
|
||||
ElfApiInterfaceList_t interfaces;
|
||||
};
|
||||
|
||||
static bool composite_api_resolver_callback(
|
||||
const ElfApiInterface* interface,
|
||||
const char* name,
|
||||
Elf32_Addr* address) {
|
||||
CompositeApiResolver* resolver = (CompositeApiResolver*)interface;
|
||||
for
|
||||
M_EACH(interface, resolver->interfaces, ElfApiInterfaceList_t) {
|
||||
if((*interface)->resolver_callback(*interface, name, address)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
CompositeApiResolver* composite_api_resolver_alloc() {
|
||||
CompositeApiResolver* resolver = malloc(sizeof(CompositeApiResolver));
|
||||
resolver->api_interface.api_version_major = 0;
|
||||
resolver->api_interface.api_version_minor = 0;
|
||||
resolver->api_interface.resolver_callback = &composite_api_resolver_callback;
|
||||
ElfApiInterfaceList_init(resolver->interfaces);
|
||||
return resolver;
|
||||
}
|
||||
|
||||
void composite_api_resolver_free(CompositeApiResolver* resolver) {
|
||||
ElfApiInterfaceList_clear(resolver->interfaces);
|
||||
free(resolver);
|
||||
}
|
||||
|
||||
void composite_api_resolver_add(CompositeApiResolver* resolver, const ElfApiInterface* interface) {
|
||||
if(ElfApiInterfaceList_empty_p(resolver->interfaces)) {
|
||||
resolver->api_interface.api_version_major = interface->api_version_major;
|
||||
resolver->api_interface.api_version_minor = interface->api_version_minor;
|
||||
}
|
||||
ElfApiInterfaceList_push_back(resolver->interfaces, interface);
|
||||
}
|
||||
|
||||
const ElfApiInterface* composite_api_resolver_get(CompositeApiResolver* resolver) {
|
||||
return &resolver->api_interface;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <flipper_application/elf/elf_api_interface.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Composite API resolver
|
||||
* Resolves API interface by calling all resolvers in order
|
||||
* Uses API version from first resolver
|
||||
* Note: when using hashtable resolvers, collisions between tables are not detected
|
||||
* Can be cast to ElfApiInterface*
|
||||
*/
|
||||
typedef struct CompositeApiResolver CompositeApiResolver;
|
||||
|
||||
/**
|
||||
* @brief Allocate composite API resolver
|
||||
* @return CompositeApiResolver* instance
|
||||
*/
|
||||
CompositeApiResolver* composite_api_resolver_alloc();
|
||||
|
||||
/**
|
||||
* @brief Free composite API resolver
|
||||
* @param resolver Instance
|
||||
*/
|
||||
void composite_api_resolver_free(CompositeApiResolver* resolver);
|
||||
|
||||
/**
|
||||
* @brief Add API resolver to composite resolver
|
||||
* @param resolver Instance
|
||||
* @param interface API resolver
|
||||
*/
|
||||
void composite_api_resolver_add(CompositeApiResolver* resolver, const ElfApiInterface* interface);
|
||||
|
||||
/**
|
||||
* @brief Get API interface from composite resolver
|
||||
* @param resolver Instance
|
||||
* @return API interface
|
||||
*/
|
||||
const ElfApiInterface* composite_api_resolver_get(CompositeApiResolver* resolver);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,153 @@
|
||||
#include "plugin_manager.h"
|
||||
|
||||
#include <loader/firmware_api/firmware_api.h>
|
||||
#include <storage/storage.h>
|
||||
#include <toolbox/path.h>
|
||||
|
||||
#include <m-array.h>
|
||||
#include <m-algo.h>
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "libmgr"
|
||||
|
||||
ARRAY_DEF(FlipperApplicationList, FlipperApplication*, M_PTR_OPLIST)
|
||||
#define M_OPL_FlipperApplicationList_t() ARRAY_OPLIST(FlipperApplicationList, M_PTR_OPLIST)
|
||||
|
||||
struct PluginManager {
|
||||
const char* application_id;
|
||||
uint32_t api_version;
|
||||
Storage* storage;
|
||||
FlipperApplicationList_t libs;
|
||||
const ElfApiInterface* api_interface;
|
||||
};
|
||||
|
||||
PluginManager* plugin_manager_alloc(
|
||||
const char* application_id,
|
||||
uint32_t api_version,
|
||||
const ElfApiInterface* api_interface) {
|
||||
PluginManager* manager = malloc(sizeof(PluginManager));
|
||||
manager->application_id = application_id;
|
||||
manager->api_version = api_version;
|
||||
manager->api_interface = api_interface ? api_interface : firmware_api_interface;
|
||||
manager->storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperApplicationList_init(manager->libs);
|
||||
return manager;
|
||||
}
|
||||
|
||||
void plugin_manager_free(PluginManager* manager) {
|
||||
for
|
||||
M_EACH(loaded_lib, manager->libs, FlipperApplicationList_t) {
|
||||
flipper_application_free(*loaded_lib);
|
||||
}
|
||||
FlipperApplicationList_clear(manager->libs);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
free(manager);
|
||||
}
|
||||
|
||||
PluginManagerError plugin_manager_load_single(PluginManager* manager, const char* path) {
|
||||
FlipperApplication* lib = flipper_application_alloc(manager->storage, manager->api_interface);
|
||||
|
||||
PluginManagerError error = PluginManagerErrorNone;
|
||||
do {
|
||||
FlipperApplicationPreloadStatus preload_res = flipper_application_preload(lib, path);
|
||||
|
||||
if(preload_res != FlipperApplicationPreloadStatusSuccess) {
|
||||
FURI_LOG_E(TAG, "Failed to preload %s", path);
|
||||
error = PluginManagerErrorLoaderError;
|
||||
break;
|
||||
}
|
||||
|
||||
if(!flipper_application_is_plugin(lib)) {
|
||||
FURI_LOG_E(TAG, "Not a plugin %s", path);
|
||||
error = PluginManagerErrorLoaderError;
|
||||
break;
|
||||
}
|
||||
|
||||
FlipperApplicationLoadStatus load_status = flipper_application_map_to_memory(lib);
|
||||
if(load_status != FlipperApplicationLoadStatusSuccess) {
|
||||
FURI_LOG_E(TAG, "Failed to load module_demo_plugin1.fal");
|
||||
break;
|
||||
}
|
||||
|
||||
const FlipperAppPluginDescriptor* app_descriptor =
|
||||
flipper_application_plugin_get_descriptor(lib);
|
||||
|
||||
if(!app_descriptor) {
|
||||
FURI_LOG_E(TAG, "Failed to get descriptor %s", path);
|
||||
error = PluginManagerErrorLoaderError;
|
||||
break;
|
||||
}
|
||||
|
||||
if(strcmp(app_descriptor->appid, manager->application_id) != 0) {
|
||||
FURI_LOG_E(TAG, "Application id mismatch %s", path);
|
||||
error = PluginManagerErrorApplicationIdMismatch;
|
||||
break;
|
||||
}
|
||||
|
||||
if(app_descriptor->ep_api_version != manager->api_version) {
|
||||
FURI_LOG_E(TAG, "API version mismatch %s", path);
|
||||
error = PluginManagerErrorAPIVersionMismatch;
|
||||
break;
|
||||
}
|
||||
|
||||
FlipperApplicationList_push_back(manager->libs, lib);
|
||||
} while(false);
|
||||
|
||||
if(error != PluginManagerErrorNone) {
|
||||
flipper_application_free(lib);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
PluginManagerError plugin_manager_load_all(PluginManager* manager, const char* path) {
|
||||
File* directory = storage_file_alloc(manager->storage);
|
||||
char file_name_buffer[256];
|
||||
FuriString* file_name = furi_string_alloc();
|
||||
do {
|
||||
if(!storage_dir_open(directory, path)) {
|
||||
FURI_LOG_E(TAG, "Failed to open directory %s", path);
|
||||
break;
|
||||
}
|
||||
while(true) {
|
||||
if(!storage_dir_read(directory, NULL, file_name_buffer, sizeof(file_name_buffer))) {
|
||||
break;
|
||||
}
|
||||
|
||||
furi_string_set(file_name, file_name_buffer);
|
||||
if(!furi_string_end_with_str(file_name, ".fal")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
path_concat(path, file_name_buffer, file_name);
|
||||
FURI_LOG_D(TAG, "Loading %s", furi_string_get_cstr(file_name));
|
||||
PluginManagerError error =
|
||||
plugin_manager_load_single(manager, furi_string_get_cstr(file_name));
|
||||
|
||||
if(error != PluginManagerErrorNone) {
|
||||
FURI_LOG_E(TAG, "Failed to load %s", furi_string_get_cstr(file_name));
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while(false);
|
||||
storage_dir_close(directory);
|
||||
storage_file_free(directory);
|
||||
furi_string_free(file_name);
|
||||
return PluginManagerErrorNone;
|
||||
}
|
||||
|
||||
uint32_t plugin_manager_get_count(PluginManager* manager) {
|
||||
return FlipperApplicationList_size(manager->libs);
|
||||
}
|
||||
|
||||
const FlipperAppPluginDescriptor* plugin_manager_get(PluginManager* manager, uint32_t index) {
|
||||
FlipperApplication* app = *FlipperApplicationList_get(manager->libs, index);
|
||||
return flipper_application_plugin_get_descriptor(app);
|
||||
}
|
||||
|
||||
const void* plugin_manager_get_ep(PluginManager* manager, uint32_t index) {
|
||||
const FlipperAppPluginDescriptor* lib_descr = plugin_manager_get(manager, index);
|
||||
furi_check(lib_descr);
|
||||
return lib_descr->entry_point;
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#include <flipper_application/flipper_application.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Object that manages plugins for an application
|
||||
* Implements mass loading of plugins and provides access to their descriptors
|
||||
*/
|
||||
typedef struct PluginManager PluginManager;
|
||||
|
||||
typedef enum {
|
||||
PluginManagerErrorNone = 0,
|
||||
PluginManagerErrorLoaderError,
|
||||
PluginManagerErrorApplicationIdMismatch,
|
||||
PluginManagerErrorAPIVersionMismatch,
|
||||
} PluginManagerError;
|
||||
|
||||
/**
|
||||
* @brief Allocates new PluginManager
|
||||
* @param application_id Application ID filter - only plugins with matching ID will be loaded
|
||||
* @param api_version Application API version filter - only plugins with matching API version
|
||||
* @param api_interface Application API interface - used to resolve plugins' API imports
|
||||
* If plugin uses private application's API, use CompoundApiInterface
|
||||
* @return new PluginManager instance
|
||||
*/
|
||||
PluginManager* plugin_manager_alloc(
|
||||
const char* application_id,
|
||||
uint32_t api_version,
|
||||
const ElfApiInterface* api_interface);
|
||||
|
||||
/**
|
||||
* @brief Frees PluginManager
|
||||
* @param manager PluginManager instance
|
||||
*/
|
||||
void plugin_manager_free(PluginManager* manager);
|
||||
|
||||
/**
|
||||
* @brief Loads single plugin by full path
|
||||
* @param manager PluginManager instance
|
||||
* @param path Path to plugin
|
||||
* @return Error code
|
||||
*/
|
||||
PluginManagerError plugin_manager_load_single(PluginManager* manager, const char* path);
|
||||
|
||||
/**
|
||||
* @brief Loads all plugins from specified directory
|
||||
* @param manager PluginManager instance
|
||||
* @param path Path to directory
|
||||
* @return Error code
|
||||
*/
|
||||
PluginManagerError plugin_manager_load_all(PluginManager* manager, const char* path);
|
||||
|
||||
/**
|
||||
* @brief Returns number of loaded plugins
|
||||
* @param manager PluginManager instance
|
||||
* @return Number of loaded plugins
|
||||
*/
|
||||
uint32_t plugin_manager_get_count(PluginManager* manager);
|
||||
|
||||
/**
|
||||
* @brief Returns plugin descriptor by index
|
||||
* @param manager PluginManager instance
|
||||
* @param index Plugin index
|
||||
* @return Plugin descriptor
|
||||
*/
|
||||
const FlipperAppPluginDescriptor* plugin_manager_get(PluginManager* manager, uint32_t index);
|
||||
|
||||
/**
|
||||
* @brief Returns plugin entry point by index
|
||||
* @param manager PluginManager instance
|
||||
* @param index Plugin index
|
||||
* @return Plugin entry point
|
||||
*/
|
||||
const void* plugin_manager_get_ep(PluginManager* manager, uint32_t index);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -91,6 +91,12 @@ bool flipper_format_file_open_always(FlipperFormat* flipper_format, const char*
|
||||
return file_stream_open(flipper_format->stream, path, FSAM_READ_WRITE, FSOM_CREATE_ALWAYS);
|
||||
}
|
||||
|
||||
bool flipper_format_buffered_file_open_always(FlipperFormat* flipper_format, const char* path) {
|
||||
furi_assert(flipper_format);
|
||||
return buffered_file_stream_open(
|
||||
flipper_format->stream, path, FSAM_READ_WRITE, FSOM_CREATE_ALWAYS);
|
||||
}
|
||||
|
||||
bool flipper_format_file_open_new(FlipperFormat* flipper_format, const char* path) {
|
||||
furi_assert(flipper_format);
|
||||
return file_stream_open(flipper_format->stream, path, FSAM_READ_WRITE, FSOM_CREATE_NEW);
|
||||
|
||||
@@ -131,7 +131,7 @@ bool flipper_format_file_open_existing(FlipperFormat* flipper_format, const char
|
||||
|
||||
/**
|
||||
* Open existing file, buffered mode.
|
||||
* Use only if FlipperFormat allocated as a file.
|
||||
* Use only if FlipperFormat allocated as a buffered file.
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param path File path
|
||||
* @return True on success
|
||||
@@ -156,6 +156,15 @@ bool flipper_format_file_open_append(FlipperFormat* flipper_format, const char*
|
||||
*/
|
||||
bool flipper_format_file_open_always(FlipperFormat* flipper_format, const char* path);
|
||||
|
||||
/**
|
||||
* Open file. Creates a new file, or deletes the contents of the file if it already exists, buffered mode.
|
||||
* Use only if FlipperFormat allocated as a buffered file.
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param path File path
|
||||
* @return True on success
|
||||
*/
|
||||
bool flipper_format_buffered_file_open_always(FlipperFormat* flipper_format, const char* path);
|
||||
|
||||
/**
|
||||
* Open file. Creates a new file, fails if file already exists.
|
||||
* Use only if FlipperFormat allocated as a file.
|
||||
|
||||
Submodule
+1
Submodule lib/heatshrink added at 7398ccc916
@@ -1,20 +0,0 @@
|
||||
#ifndef HEATSHRINK_H
|
||||
#define HEATSHRINK_H
|
||||
|
||||
#define HEATSHRINK_AUTHOR "Scott Vokes <vokes.s@gmail.com>"
|
||||
#define HEATSHRINK_URL "https://github.com/atomicobject/heatshrink"
|
||||
|
||||
/* Version 0.4.1 */
|
||||
#define HEATSHRINK_VERSION_MAJOR 0
|
||||
#define HEATSHRINK_VERSION_MINOR 4
|
||||
#define HEATSHRINK_VERSION_PATCH 1
|
||||
|
||||
#define HEATSHRINK_MIN_WINDOW_BITS 4
|
||||
#define HEATSHRINK_MAX_WINDOW_BITS 15
|
||||
|
||||
#define HEATSHRINK_MIN_LOOKAHEAD_BITS 3
|
||||
|
||||
#define HEATSHRINK_LITERAL_MARKER 0x01
|
||||
#define HEATSHRINK_BACKREF_MARKER 0x00
|
||||
|
||||
#endif
|
||||
@@ -1,28 +0,0 @@
|
||||
#ifndef HEATSHRINK_CONFIG_H
|
||||
#define HEATSHRINK_CONFIG_H
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
/* Should functionality assuming dynamic allocation be used? */
|
||||
#ifndef HEATSHRINK_DYNAMIC_ALLOC
|
||||
#define HEATSHRINK_DYNAMIC_ALLOC 1
|
||||
#endif
|
||||
|
||||
#if HEATSHRINK_DYNAMIC_ALLOC
|
||||
/* Optional replacement of malloc/free */
|
||||
#define HEATSHRINK_MALLOC(SZ) malloc(SZ)
|
||||
#define HEATSHRINK_FREE(P, SZ) free(P)
|
||||
#else
|
||||
/* Required parameters for static configuration */
|
||||
#define HEATSHRINK_STATIC_INPUT_BUFFER_SIZE 1024
|
||||
#define HEATSHRINK_STATIC_WINDOW_BITS 8
|
||||
#define HEATSHRINK_STATIC_LOOKAHEAD_BITS 4
|
||||
#endif
|
||||
|
||||
/* Turn on logging for debugging. */
|
||||
#define HEATSHRINK_DEBUGGING_LOGS 0
|
||||
|
||||
/* Use indexing for faster compression. (This requires additional space.) */
|
||||
#define HEATSHRINK_USE_INDEX 1
|
||||
|
||||
#endif
|
||||
@@ -1,364 +0,0 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "heatshrink_decoder.h"
|
||||
|
||||
/* States for the polling state machine. */
|
||||
typedef enum {
|
||||
HSDS_TAG_BIT, /* tag bit */
|
||||
HSDS_YIELD_LITERAL, /* ready to yield literal byte */
|
||||
HSDS_BACKREF_INDEX_MSB, /* most significant byte of index */
|
||||
HSDS_BACKREF_INDEX_LSB, /* least significant byte of index */
|
||||
HSDS_BACKREF_COUNT_MSB, /* most significant byte of count */
|
||||
HSDS_BACKREF_COUNT_LSB, /* least significant byte of count */
|
||||
HSDS_YIELD_BACKREF, /* ready to yield back-reference */
|
||||
} HSD_state;
|
||||
|
||||
#if HEATSHRINK_DEBUGGING_LOGS
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <assert.h>
|
||||
#define LOG(...) fprintf(stderr, __VA_ARGS__)
|
||||
#define ASSERT(X) assert(X)
|
||||
static const char *state_names[] = {
|
||||
"tag_bit",
|
||||
"yield_literal",
|
||||
"backref_index_msb",
|
||||
"backref_index_lsb",
|
||||
"backref_count_msb",
|
||||
"backref_count_lsb",
|
||||
"yield_backref",
|
||||
};
|
||||
#else
|
||||
#define LOG(...) /* no-op */
|
||||
#define ASSERT(X) /* no-op */
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
uint8_t *buf; /* output buffer */
|
||||
size_t buf_size; /* buffer size */
|
||||
size_t *output_size; /* bytes pushed to buffer, so far */
|
||||
} output_info;
|
||||
|
||||
#define NO_BITS ((uint16_t)-1)
|
||||
|
||||
/* Forward references. */
|
||||
static uint16_t get_bits(heatshrink_decoder *hsd, uint8_t count);
|
||||
static void push_byte(heatshrink_decoder *hsd, output_info *oi, uint8_t byte);
|
||||
|
||||
#if HEATSHRINK_DYNAMIC_ALLOC
|
||||
heatshrink_decoder *heatshrink_decoder_alloc(uint8_t* buffer,
|
||||
uint16_t input_buffer_size,
|
||||
uint8_t window_sz2,
|
||||
uint8_t lookahead_sz2) {
|
||||
if ((window_sz2 < HEATSHRINK_MIN_WINDOW_BITS) ||
|
||||
(window_sz2 > HEATSHRINK_MAX_WINDOW_BITS) ||
|
||||
(input_buffer_size == 0) ||
|
||||
(lookahead_sz2 < HEATSHRINK_MIN_LOOKAHEAD_BITS) ||
|
||||
(lookahead_sz2 >= window_sz2)) {
|
||||
return NULL;
|
||||
}
|
||||
size_t sz = sizeof(heatshrink_decoder);
|
||||
heatshrink_decoder *hsd = HEATSHRINK_MALLOC(sz);
|
||||
if (hsd == NULL) { return NULL; }
|
||||
hsd->input_buffer_size = input_buffer_size;
|
||||
hsd->window_sz2 = window_sz2;
|
||||
hsd->lookahead_sz2 = lookahead_sz2;
|
||||
hsd->buffers = buffer;
|
||||
heatshrink_decoder_reset(hsd);
|
||||
LOG("-- allocated decoder with buffer size of %zu (%zu + %u + %u)\n",
|
||||
sz, sizeof(heatshrink_decoder), (1 << window_sz2), input_buffer_size);
|
||||
return hsd;
|
||||
}
|
||||
|
||||
void heatshrink_decoder_free(heatshrink_decoder *hsd) {
|
||||
size_t sz = sizeof(heatshrink_decoder);
|
||||
HEATSHRINK_FREE(hsd, sz);
|
||||
(void)sz; /* may not be used by free */
|
||||
}
|
||||
#endif
|
||||
|
||||
void heatshrink_decoder_reset(heatshrink_decoder *hsd) {
|
||||
hsd->state = HSDS_TAG_BIT;
|
||||
hsd->input_size = 0;
|
||||
hsd->input_index = 0;
|
||||
hsd->bit_index = 0x00;
|
||||
hsd->current_byte = 0x00;
|
||||
hsd->output_count = 0;
|
||||
hsd->output_index = 0;
|
||||
hsd->head_index = 0;
|
||||
}
|
||||
|
||||
/* Copy SIZE bytes into the decoder's input buffer, if it will fit. */
|
||||
HSD_sink_res heatshrink_decoder_sink(heatshrink_decoder *hsd,
|
||||
uint8_t *in_buf, size_t size, size_t *input_size) {
|
||||
if ((hsd == NULL) || (in_buf == NULL) || (input_size == NULL)) {
|
||||
return HSDR_SINK_ERROR_NULL;
|
||||
}
|
||||
|
||||
size_t rem = HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd) - hsd->input_size;
|
||||
if (rem == 0) {
|
||||
*input_size = 0;
|
||||
return HSDR_SINK_FULL;
|
||||
}
|
||||
|
||||
size = rem < size ? rem : size;
|
||||
LOG("-- sinking %zd bytes\n", size);
|
||||
/* copy into input buffer (at head of buffers) */
|
||||
memcpy(&hsd->buffers[hsd->input_size], in_buf, size);
|
||||
hsd->input_size += size;
|
||||
*input_size = size;
|
||||
return HSDR_SINK_OK;
|
||||
}
|
||||
|
||||
|
||||
/*****************
|
||||
* Decompression *
|
||||
*****************/
|
||||
|
||||
#define BACKREF_COUNT_BITS(HSD) (HEATSHRINK_DECODER_LOOKAHEAD_BITS(HSD))
|
||||
#define BACKREF_INDEX_BITS(HSD) (HEATSHRINK_DECODER_WINDOW_BITS(HSD))
|
||||
|
||||
// States
|
||||
static HSD_state st_tag_bit(heatshrink_decoder *hsd);
|
||||
static HSD_state st_yield_literal(heatshrink_decoder *hsd,
|
||||
output_info *oi);
|
||||
static HSD_state st_backref_index_msb(heatshrink_decoder *hsd);
|
||||
static HSD_state st_backref_index_lsb(heatshrink_decoder *hsd);
|
||||
static HSD_state st_backref_count_msb(heatshrink_decoder *hsd);
|
||||
static HSD_state st_backref_count_lsb(heatshrink_decoder *hsd);
|
||||
static HSD_state st_yield_backref(heatshrink_decoder *hsd,
|
||||
output_info *oi);
|
||||
|
||||
HSD_poll_res heatshrink_decoder_poll(heatshrink_decoder *hsd,
|
||||
uint8_t *out_buf, size_t out_buf_size, size_t *output_size) {
|
||||
if ((hsd == NULL) || (out_buf == NULL) || (output_size == NULL)) {
|
||||
return HSDR_POLL_ERROR_NULL;
|
||||
}
|
||||
*output_size = 0;
|
||||
|
||||
output_info oi;
|
||||
oi.buf = out_buf;
|
||||
oi.buf_size = out_buf_size;
|
||||
oi.output_size = output_size;
|
||||
|
||||
while (1) {
|
||||
LOG("-- poll, state is %d (%s), input_size %d\n",
|
||||
hsd->state, state_names[hsd->state], hsd->input_size);
|
||||
uint8_t in_state = hsd->state;
|
||||
switch (in_state) {
|
||||
case HSDS_TAG_BIT:
|
||||
hsd->state = st_tag_bit(hsd);
|
||||
break;
|
||||
case HSDS_YIELD_LITERAL:
|
||||
hsd->state = st_yield_literal(hsd, &oi);
|
||||
break;
|
||||
case HSDS_BACKREF_INDEX_MSB:
|
||||
hsd->state = st_backref_index_msb(hsd);
|
||||
break;
|
||||
case HSDS_BACKREF_INDEX_LSB:
|
||||
hsd->state = st_backref_index_lsb(hsd);
|
||||
break;
|
||||
case HSDS_BACKREF_COUNT_MSB:
|
||||
hsd->state = st_backref_count_msb(hsd);
|
||||
break;
|
||||
case HSDS_BACKREF_COUNT_LSB:
|
||||
hsd->state = st_backref_count_lsb(hsd);
|
||||
break;
|
||||
case HSDS_YIELD_BACKREF:
|
||||
hsd->state = st_yield_backref(hsd, &oi);
|
||||
break;
|
||||
default:
|
||||
return HSDR_POLL_ERROR_UNKNOWN;
|
||||
}
|
||||
|
||||
/* If the current state cannot advance, check if input or output
|
||||
* buffer are exhausted. */
|
||||
if (hsd->state == in_state) {
|
||||
if (*output_size == out_buf_size) { return HSDR_POLL_MORE; }
|
||||
return HSDR_POLL_EMPTY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static HSD_state st_tag_bit(heatshrink_decoder *hsd) {
|
||||
uint32_t bits = get_bits(hsd, 1); // get tag bit
|
||||
if (bits == NO_BITS) {
|
||||
return HSDS_TAG_BIT;
|
||||
} else if (bits) {
|
||||
return HSDS_YIELD_LITERAL;
|
||||
} else if (HEATSHRINK_DECODER_WINDOW_BITS(hsd) > 8) {
|
||||
return HSDS_BACKREF_INDEX_MSB;
|
||||
} else {
|
||||
hsd->output_index = 0;
|
||||
return HSDS_BACKREF_INDEX_LSB;
|
||||
}
|
||||
}
|
||||
|
||||
static HSD_state st_yield_literal(heatshrink_decoder *hsd,
|
||||
output_info *oi) {
|
||||
/* Emit a repeated section from the window buffer, and add it (again)
|
||||
* to the window buffer. (Note that the repetition can include
|
||||
* itself.)*/
|
||||
if (*oi->output_size < oi->buf_size) {
|
||||
uint16_t byte = get_bits(hsd, 8);
|
||||
if (byte == NO_BITS) { return HSDS_YIELD_LITERAL; } /* out of input */
|
||||
uint8_t *buf = &hsd->buffers[HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd)];
|
||||
uint16_t mask = (1 << HEATSHRINK_DECODER_WINDOW_BITS(hsd)) - 1;
|
||||
uint8_t c = byte & 0xFF;
|
||||
LOG("-- emitting literal byte 0x%02x ('%c')\n", c, isprint(c) ? c : '.');
|
||||
buf[hsd->head_index++ & mask] = c;
|
||||
push_byte(hsd, oi, c);
|
||||
return HSDS_TAG_BIT;
|
||||
} else {
|
||||
return HSDS_YIELD_LITERAL;
|
||||
}
|
||||
}
|
||||
|
||||
static HSD_state st_backref_index_msb(heatshrink_decoder *hsd) {
|
||||
uint8_t bit_ct = BACKREF_INDEX_BITS(hsd);
|
||||
ASSERT(bit_ct > 8);
|
||||
uint16_t bits = get_bits(hsd, bit_ct - 8);
|
||||
LOG("-- backref index (msb), got 0x%04x (+1)\n", bits);
|
||||
if (bits == NO_BITS) { return HSDS_BACKREF_INDEX_MSB; }
|
||||
hsd->output_index = bits << 8;
|
||||
return HSDS_BACKREF_INDEX_LSB;
|
||||
}
|
||||
|
||||
static HSD_state st_backref_index_lsb(heatshrink_decoder *hsd) {
|
||||
uint8_t bit_ct = BACKREF_INDEX_BITS(hsd);
|
||||
uint16_t bits = get_bits(hsd, bit_ct < 8 ? bit_ct : 8);
|
||||
LOG("-- backref index (lsb), got 0x%04x (+1)\n", bits);
|
||||
if (bits == NO_BITS) { return HSDS_BACKREF_INDEX_LSB; }
|
||||
hsd->output_index |= bits;
|
||||
hsd->output_index++;
|
||||
uint8_t br_bit_ct = BACKREF_COUNT_BITS(hsd);
|
||||
hsd->output_count = 0;
|
||||
return (br_bit_ct > 8) ? HSDS_BACKREF_COUNT_MSB : HSDS_BACKREF_COUNT_LSB;
|
||||
}
|
||||
|
||||
static HSD_state st_backref_count_msb(heatshrink_decoder *hsd) {
|
||||
uint8_t br_bit_ct = BACKREF_COUNT_BITS(hsd);
|
||||
ASSERT(br_bit_ct > 8);
|
||||
uint16_t bits = get_bits(hsd, br_bit_ct - 8);
|
||||
LOG("-- backref count (msb), got 0x%04x (+1)\n", bits);
|
||||
if (bits == NO_BITS) { return HSDS_BACKREF_COUNT_MSB; }
|
||||
hsd->output_count = bits << 8;
|
||||
return HSDS_BACKREF_COUNT_LSB;
|
||||
}
|
||||
|
||||
static HSD_state st_backref_count_lsb(heatshrink_decoder *hsd) {
|
||||
uint8_t br_bit_ct = BACKREF_COUNT_BITS(hsd);
|
||||
uint16_t bits = get_bits(hsd, br_bit_ct < 8 ? br_bit_ct : 8);
|
||||
LOG("-- backref count (lsb), got 0x%04x (+1)\n", bits);
|
||||
if (bits == NO_BITS) { return HSDS_BACKREF_COUNT_LSB; }
|
||||
hsd->output_count |= bits;
|
||||
hsd->output_count++;
|
||||
return HSDS_YIELD_BACKREF;
|
||||
}
|
||||
|
||||
static HSD_state st_yield_backref(heatshrink_decoder *hsd,
|
||||
output_info *oi) {
|
||||
size_t count = oi->buf_size - *oi->output_size;
|
||||
if (count > 0) {
|
||||
size_t i = 0;
|
||||
if (hsd->output_count < count) count = hsd->output_count;
|
||||
uint8_t *buf = &hsd->buffers[HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd)];
|
||||
uint16_t mask = (1 << HEATSHRINK_DECODER_WINDOW_BITS(hsd)) - 1;
|
||||
uint16_t neg_offset = hsd->output_index;
|
||||
LOG("-- emitting %zu bytes from -%u bytes back\n", count, neg_offset);
|
||||
ASSERT(neg_offset <= mask + 1);
|
||||
ASSERT(count <= (size_t)(1 << BACKREF_COUNT_BITS(hsd)));
|
||||
|
||||
for (i=0; i<count; i++) {
|
||||
uint8_t c = buf[(hsd->head_index - neg_offset) & mask];
|
||||
push_byte(hsd, oi, c);
|
||||
buf[hsd->head_index & mask] = c;
|
||||
hsd->head_index++;
|
||||
LOG(" -- ++ 0x%02x\n", c);
|
||||
}
|
||||
hsd->output_count -= count;
|
||||
if (hsd->output_count == 0) { return HSDS_TAG_BIT; }
|
||||
}
|
||||
return HSDS_YIELD_BACKREF;
|
||||
}
|
||||
|
||||
/* Get the next COUNT bits from the input buffer, saving incremental progress.
|
||||
* Returns NO_BITS on end of input, or if more than 15 bits are requested. */
|
||||
static uint16_t get_bits(heatshrink_decoder *hsd, uint8_t count) {
|
||||
uint16_t accumulator = 0;
|
||||
int i = 0;
|
||||
if (count > 15) { return NO_BITS; }
|
||||
LOG("-- popping %u bit(s)\n", count);
|
||||
|
||||
/* If we aren't able to get COUNT bits, suspend immediately, because we
|
||||
* don't track how many bits of COUNT we've accumulated before suspend. */
|
||||
if (hsd->input_size == 0) {
|
||||
if (hsd->bit_index < (1 << (count - 1))) { return NO_BITS; }
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
if (hsd->bit_index == 0x00) {
|
||||
if (hsd->input_size == 0) {
|
||||
LOG(" -- out of bits, suspending w/ accumulator of %u (0x%02x)\n",
|
||||
accumulator, accumulator);
|
||||
return NO_BITS;
|
||||
}
|
||||
hsd->current_byte = hsd->buffers[hsd->input_index++];
|
||||
LOG(" -- pulled byte 0x%02x\n", hsd->current_byte);
|
||||
if (hsd->input_index == hsd->input_size) {
|
||||
hsd->input_index = 0; /* input is exhausted */
|
||||
hsd->input_size = 0;
|
||||
}
|
||||
hsd->bit_index = 0x80;
|
||||
}
|
||||
accumulator <<= 1;
|
||||
if (hsd->current_byte & hsd->bit_index) {
|
||||
accumulator |= 0x01;
|
||||
if (0) {
|
||||
LOG(" -- got 1, accumulator 0x%04x, bit_index 0x%02x\n",
|
||||
accumulator, hsd->bit_index);
|
||||
}
|
||||
} else {
|
||||
if (0) {
|
||||
LOG(" -- got 0, accumulator 0x%04x, bit_index 0x%02x\n",
|
||||
accumulator, hsd->bit_index);
|
||||
}
|
||||
}
|
||||
hsd->bit_index >>= 1;
|
||||
}
|
||||
|
||||
if (count > 1) { LOG(" -- accumulated %08x\n", accumulator); }
|
||||
return accumulator;
|
||||
}
|
||||
|
||||
HSD_finish_res heatshrink_decoder_finish(heatshrink_decoder *hsd) {
|
||||
if (hsd == NULL) { return HSDR_FINISH_ERROR_NULL; }
|
||||
switch (hsd->state) {
|
||||
case HSDS_TAG_BIT:
|
||||
return hsd->input_size == 0 ? HSDR_FINISH_DONE : HSDR_FINISH_MORE;
|
||||
|
||||
/* If we want to finish with no input, but are in these states, it's
|
||||
* because the 0-bit padding to the last byte looks like a backref
|
||||
* marker bit followed by all 0s for index and count bits. */
|
||||
case HSDS_BACKREF_INDEX_LSB:
|
||||
case HSDS_BACKREF_INDEX_MSB:
|
||||
case HSDS_BACKREF_COUNT_LSB:
|
||||
case HSDS_BACKREF_COUNT_MSB:
|
||||
return hsd->input_size == 0 ? HSDR_FINISH_DONE : HSDR_FINISH_MORE;
|
||||
|
||||
/* If the output stream is padded with 0xFFs (possibly due to being in
|
||||
* flash memory), also explicitly check the input size rather than
|
||||
* uselessly returning MORE but yielding 0 bytes when polling. */
|
||||
case HSDS_YIELD_LITERAL:
|
||||
return hsd->input_size == 0 ? HSDR_FINISH_DONE : HSDR_FINISH_MORE;
|
||||
|
||||
default:
|
||||
return HSDR_FINISH_MORE;
|
||||
}
|
||||
}
|
||||
|
||||
static void push_byte(heatshrink_decoder *hsd, output_info *oi, uint8_t byte) {
|
||||
LOG(" -- pushing byte: 0x%02x ('%c')\n", byte, isprint(byte) ? byte : '.');
|
||||
oi->buf[(*oi->output_size)++] = byte;
|
||||
(void)hsd;
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
#ifndef HEATSHRINK_DECODER_H
|
||||
#define HEATSHRINK_DECODER_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "heatshrink_common.h"
|
||||
#include "heatshrink_config.h"
|
||||
|
||||
typedef enum {
|
||||
HSDR_SINK_OK, /* data sunk, ready to poll */
|
||||
HSDR_SINK_FULL, /* out of space in internal buffer */
|
||||
HSDR_SINK_ERROR_NULL=-1, /* NULL argument */
|
||||
} HSD_sink_res;
|
||||
|
||||
typedef enum {
|
||||
HSDR_POLL_EMPTY, /* input exhausted */
|
||||
HSDR_POLL_MORE, /* more data remaining, call again w/ fresh output buffer */
|
||||
HSDR_POLL_ERROR_NULL=-1, /* NULL arguments */
|
||||
HSDR_POLL_ERROR_UNKNOWN=-2,
|
||||
} HSD_poll_res;
|
||||
|
||||
typedef enum {
|
||||
HSDR_FINISH_DONE, /* output is done */
|
||||
HSDR_FINISH_MORE, /* more output remains */
|
||||
HSDR_FINISH_ERROR_NULL=-1, /* NULL arguments */
|
||||
} HSD_finish_res;
|
||||
|
||||
#if HEATSHRINK_DYNAMIC_ALLOC
|
||||
#define HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(BUF) \
|
||||
((BUF)->input_buffer_size)
|
||||
#define HEATSHRINK_DECODER_WINDOW_BITS(BUF) \
|
||||
((BUF)->window_sz2)
|
||||
#define HEATSHRINK_DECODER_LOOKAHEAD_BITS(BUF) \
|
||||
((BUF)->lookahead_sz2)
|
||||
#else
|
||||
#define HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(_) \
|
||||
HEATSHRINK_STATIC_INPUT_BUFFER_SIZE
|
||||
#define HEATSHRINK_DECODER_WINDOW_BITS(_) \
|
||||
(HEATSHRINK_STATIC_WINDOW_BITS)
|
||||
#define HEATSHRINK_DECODER_LOOKAHEAD_BITS(BUF) \
|
||||
(HEATSHRINK_STATIC_LOOKAHEAD_BITS)
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
uint16_t input_size; /* bytes in input buffer */
|
||||
uint16_t input_index; /* offset to next unprocessed input byte */
|
||||
uint16_t output_count; /* how many bytes to output */
|
||||
uint16_t output_index; /* index for bytes to output */
|
||||
uint16_t head_index; /* head of window buffer */
|
||||
uint8_t state; /* current state machine node */
|
||||
uint8_t current_byte; /* current byte of input */
|
||||
uint8_t bit_index; /* current bit index */
|
||||
|
||||
#if HEATSHRINK_DYNAMIC_ALLOC
|
||||
/* Fields that are only used if dynamically allocated. */
|
||||
uint8_t window_sz2; /* window buffer bits */
|
||||
uint8_t lookahead_sz2; /* lookahead bits */
|
||||
uint16_t input_buffer_size; /* input buffer size */
|
||||
|
||||
/* Input buffer, then expansion window buffer */
|
||||
uint8_t* buffers;
|
||||
#else
|
||||
/* Input buffer, then expansion window buffer */
|
||||
uint8_t buffers[(1 << HEATSHRINK_DECODER_WINDOW_BITS(_))
|
||||
+ HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(_)];
|
||||
#endif
|
||||
} heatshrink_decoder;
|
||||
|
||||
#if HEATSHRINK_DYNAMIC_ALLOC
|
||||
/* Allocate a decoder with an input buffer of INPUT_BUFFER_SIZE bytes,
|
||||
* an expansion buffer size of 2^WINDOW_SZ2, and a lookahead
|
||||
* size of 2^lookahead_sz2. (The window buffer and lookahead sizes
|
||||
* must match the settings used when the data was compressed.)
|
||||
* Returns NULL on error. */
|
||||
heatshrink_decoder *heatshrink_decoder_alloc(uint8_t* buffer, uint16_t input_buffer_size,
|
||||
uint8_t expansion_buffer_sz2, uint8_t lookahead_sz2);
|
||||
|
||||
/* Free a decoder. */
|
||||
void heatshrink_decoder_free(heatshrink_decoder *hsd);
|
||||
#endif
|
||||
|
||||
/* Reset a decoder. */
|
||||
void heatshrink_decoder_reset(heatshrink_decoder *hsd);
|
||||
|
||||
/* Sink at most SIZE bytes from IN_BUF into the decoder. *INPUT_SIZE is set to
|
||||
* indicate how many bytes were actually sunk (in case a buffer was filled). */
|
||||
HSD_sink_res heatshrink_decoder_sink(heatshrink_decoder *hsd,
|
||||
uint8_t *in_buf, size_t size, size_t *input_size);
|
||||
|
||||
/* Poll for output from the decoder, copying at most OUT_BUF_SIZE bytes into
|
||||
* OUT_BUF (setting *OUTPUT_SIZE to the actual amount copied). */
|
||||
HSD_poll_res heatshrink_decoder_poll(heatshrink_decoder *hsd,
|
||||
uint8_t *out_buf, size_t out_buf_size, size_t *output_size);
|
||||
|
||||
/* Notify the dencoder that the input stream is finished.
|
||||
* If the return value is HSDR_FINISH_MORE, there is still more output, so
|
||||
* call heatshrink_decoder_poll and repeat. */
|
||||
HSD_finish_res heatshrink_decoder_finish(heatshrink_decoder *hsd);
|
||||
|
||||
#endif
|
||||
@@ -1,602 +0,0 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include "heatshrink_encoder.h"
|
||||
|
||||
typedef enum {
|
||||
HSES_NOT_FULL, /* input buffer not full enough */
|
||||
HSES_FILLED, /* buffer is full */
|
||||
HSES_SEARCH, /* searching for patterns */
|
||||
HSES_YIELD_TAG_BIT, /* yield tag bit */
|
||||
HSES_YIELD_LITERAL, /* emit literal byte */
|
||||
HSES_YIELD_BR_INDEX, /* yielding backref index */
|
||||
HSES_YIELD_BR_LENGTH, /* yielding backref length */
|
||||
HSES_SAVE_BACKLOG, /* copying buffer to backlog */
|
||||
HSES_FLUSH_BITS, /* flush bit buffer */
|
||||
HSES_DONE, /* done */
|
||||
} HSE_state;
|
||||
|
||||
#if HEATSHRINK_DEBUGGING_LOGS
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <assert.h>
|
||||
#define LOG(...) fprintf(stderr, __VA_ARGS__)
|
||||
#define ASSERT(X) assert(X)
|
||||
static const char *state_names[] = {
|
||||
"not_full",
|
||||
"filled",
|
||||
"search",
|
||||
"yield_tag_bit",
|
||||
"yield_literal",
|
||||
"yield_br_index",
|
||||
"yield_br_length",
|
||||
"save_backlog",
|
||||
"flush_bits",
|
||||
"done",
|
||||
};
|
||||
#else
|
||||
#define LOG(...) /* no-op */
|
||||
#define ASSERT(X) /* no-op */
|
||||
#endif
|
||||
|
||||
// Encoder flags
|
||||
enum {
|
||||
FLAG_IS_FINISHING = 0x01,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint8_t *buf; /* output buffer */
|
||||
size_t buf_size; /* buffer size */
|
||||
size_t *output_size; /* bytes pushed to buffer, so far */
|
||||
} output_info;
|
||||
|
||||
#define MATCH_NOT_FOUND ((uint16_t)-1)
|
||||
|
||||
static uint16_t get_input_offset(heatshrink_encoder *hse);
|
||||
static uint16_t get_input_buffer_size(heatshrink_encoder *hse);
|
||||
static uint16_t get_lookahead_size(heatshrink_encoder *hse);
|
||||
static void add_tag_bit(heatshrink_encoder *hse, output_info *oi, uint8_t tag);
|
||||
static int can_take_byte(output_info *oi);
|
||||
static int is_finishing(heatshrink_encoder *hse);
|
||||
static void save_backlog(heatshrink_encoder *hse);
|
||||
|
||||
/* Push COUNT (max 8) bits to the output buffer, which has room. */
|
||||
static void push_bits(heatshrink_encoder *hse, uint8_t count, uint8_t bits,
|
||||
output_info *oi);
|
||||
static uint8_t push_outgoing_bits(heatshrink_encoder *hse, output_info *oi);
|
||||
static void push_literal_byte(heatshrink_encoder *hse, output_info *oi);
|
||||
|
||||
#if HEATSHRINK_DYNAMIC_ALLOC
|
||||
heatshrink_encoder *heatshrink_encoder_alloc(uint8_t* buffer, uint8_t window_sz2,
|
||||
uint8_t lookahead_sz2) {
|
||||
if ((window_sz2 < HEATSHRINK_MIN_WINDOW_BITS) ||
|
||||
(window_sz2 > HEATSHRINK_MAX_WINDOW_BITS) ||
|
||||
(lookahead_sz2 < HEATSHRINK_MIN_LOOKAHEAD_BITS) ||
|
||||
(lookahead_sz2 >= window_sz2)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Note: 2 * the window size is used because the buffer needs to fit
|
||||
* (1 << window_sz2) bytes for the current input, and an additional
|
||||
* (1 << window_sz2) bytes for the previous buffer of input, which
|
||||
* will be scanned for useful backreferences. */
|
||||
size_t buf_sz = (2 << window_sz2);
|
||||
|
||||
heatshrink_encoder *hse = HEATSHRINK_MALLOC(sizeof(*hse));
|
||||
if (hse == NULL) { return NULL; }
|
||||
hse->window_sz2 = window_sz2;
|
||||
hse->lookahead_sz2 = lookahead_sz2;
|
||||
hse->buffer = buffer;
|
||||
heatshrink_encoder_reset(hse);
|
||||
|
||||
#if HEATSHRINK_USE_INDEX
|
||||
size_t index_sz = buf_sz*sizeof(uint16_t);
|
||||
hse->search_index = HEATSHRINK_MALLOC(index_sz + sizeof(struct hs_index));
|
||||
if (hse->search_index == NULL) {
|
||||
HEATSHRINK_FREE(hse, sizeof(*hse) + buf_sz);
|
||||
return NULL;
|
||||
}
|
||||
hse->search_index->size = index_sz;
|
||||
#endif
|
||||
|
||||
LOG("-- allocated encoder with buffer size of %zu (%u byte input size)\n",
|
||||
buf_sz, get_input_buffer_size(hse));
|
||||
return hse;
|
||||
}
|
||||
|
||||
void heatshrink_encoder_free(heatshrink_encoder *hse) {
|
||||
#if HEATSHRINK_USE_INDEX
|
||||
size_t index_sz = sizeof(struct hs_index) + hse->search_index->size;
|
||||
HEATSHRINK_FREE(hse->search_index, index_sz);
|
||||
(void)index_sz;
|
||||
#endif
|
||||
HEATSHRINK_FREE(hse, sizeof(heatshrink_encoder));
|
||||
}
|
||||
#endif
|
||||
|
||||
void heatshrink_encoder_reset(heatshrink_encoder *hse) {
|
||||
hse->input_size = 0;
|
||||
hse->state = HSES_NOT_FULL;
|
||||
hse->match_scan_index = 0;
|
||||
hse->flags = 0;
|
||||
hse->bit_index = 0x80;
|
||||
hse->current_byte = 0x00;
|
||||
hse->match_length = 0;
|
||||
|
||||
hse->outgoing_bits = 0x0000;
|
||||
hse->outgoing_bits_count = 0;
|
||||
|
||||
#ifdef LOOP_DETECT
|
||||
hse->loop_detect = (uint32_t)-1;
|
||||
#endif
|
||||
}
|
||||
|
||||
HSE_sink_res heatshrink_encoder_sink(heatshrink_encoder *hse,
|
||||
uint8_t *in_buf, size_t size, size_t *input_size) {
|
||||
if ((hse == NULL) || (in_buf == NULL) || (input_size == NULL)) {
|
||||
return HSER_SINK_ERROR_NULL;
|
||||
}
|
||||
|
||||
/* Sinking more content after saying the content is done, tsk tsk */
|
||||
if (is_finishing(hse)) { return HSER_SINK_ERROR_MISUSE; }
|
||||
|
||||
/* Sinking more content before processing is done */
|
||||
if (hse->state != HSES_NOT_FULL) { return HSER_SINK_ERROR_MISUSE; }
|
||||
|
||||
uint16_t write_offset = get_input_offset(hse) + hse->input_size;
|
||||
uint16_t ibs = get_input_buffer_size(hse);
|
||||
uint16_t rem = ibs - hse->input_size;
|
||||
uint16_t cp_sz = rem < size ? rem : size;
|
||||
|
||||
memcpy(&hse->buffer[write_offset], in_buf, cp_sz);
|
||||
*input_size = cp_sz;
|
||||
hse->input_size += cp_sz;
|
||||
|
||||
LOG("-- sunk %u bytes (of %zu) into encoder at %d, input buffer now has %u\n",
|
||||
cp_sz, size, write_offset, hse->input_size);
|
||||
if (cp_sz == rem) {
|
||||
LOG("-- internal buffer is now full\n");
|
||||
hse->state = HSES_FILLED;
|
||||
}
|
||||
|
||||
return HSER_SINK_OK;
|
||||
}
|
||||
|
||||
|
||||
/***************
|
||||
* Compression *
|
||||
***************/
|
||||
|
||||
static uint16_t find_longest_match(heatshrink_encoder *hse, uint16_t start,
|
||||
uint16_t end, const uint16_t maxlen, uint16_t *match_length);
|
||||
static void do_indexing(heatshrink_encoder *hse);
|
||||
|
||||
static HSE_state st_step_search(heatshrink_encoder *hse);
|
||||
static HSE_state st_yield_tag_bit(heatshrink_encoder *hse,
|
||||
output_info *oi);
|
||||
static HSE_state st_yield_literal(heatshrink_encoder *hse,
|
||||
output_info *oi);
|
||||
static HSE_state st_yield_br_index(heatshrink_encoder *hse,
|
||||
output_info *oi);
|
||||
static HSE_state st_yield_br_length(heatshrink_encoder *hse,
|
||||
output_info *oi);
|
||||
static HSE_state st_save_backlog(heatshrink_encoder *hse);
|
||||
static HSE_state st_flush_bit_buffer(heatshrink_encoder *hse,
|
||||
output_info *oi);
|
||||
|
||||
HSE_poll_res heatshrink_encoder_poll(heatshrink_encoder *hse,
|
||||
uint8_t *out_buf, size_t out_buf_size, size_t *output_size) {
|
||||
if ((hse == NULL) || (out_buf == NULL) || (output_size == NULL)) {
|
||||
return HSER_POLL_ERROR_NULL;
|
||||
}
|
||||
if (out_buf_size == 0) {
|
||||
LOG("-- MISUSE: output buffer size is 0\n");
|
||||
return HSER_POLL_ERROR_MISUSE;
|
||||
}
|
||||
*output_size = 0;
|
||||
|
||||
output_info oi;
|
||||
oi.buf = out_buf;
|
||||
oi.buf_size = out_buf_size;
|
||||
oi.output_size = output_size;
|
||||
|
||||
while (1) {
|
||||
LOG("-- polling, state %u (%s), flags 0x%02x\n",
|
||||
hse->state, state_names[hse->state], hse->flags);
|
||||
|
||||
uint8_t in_state = hse->state;
|
||||
switch (in_state) {
|
||||
case HSES_NOT_FULL:
|
||||
return HSER_POLL_EMPTY;
|
||||
case HSES_FILLED:
|
||||
do_indexing(hse);
|
||||
hse->state = HSES_SEARCH;
|
||||
break;
|
||||
case HSES_SEARCH:
|
||||
hse->state = st_step_search(hse);
|
||||
break;
|
||||
case HSES_YIELD_TAG_BIT:
|
||||
hse->state = st_yield_tag_bit(hse, &oi);
|
||||
break;
|
||||
case HSES_YIELD_LITERAL:
|
||||
hse->state = st_yield_literal(hse, &oi);
|
||||
break;
|
||||
case HSES_YIELD_BR_INDEX:
|
||||
hse->state = st_yield_br_index(hse, &oi);
|
||||
break;
|
||||
case HSES_YIELD_BR_LENGTH:
|
||||
hse->state = st_yield_br_length(hse, &oi);
|
||||
break;
|
||||
case HSES_SAVE_BACKLOG:
|
||||
hse->state = st_save_backlog(hse);
|
||||
break;
|
||||
case HSES_FLUSH_BITS:
|
||||
hse->state = st_flush_bit_buffer(hse, &oi);
|
||||
/* fall through */
|
||||
case HSES_DONE:
|
||||
return HSER_POLL_EMPTY;
|
||||
default:
|
||||
LOG("-- bad state %s\n", state_names[hse->state]);
|
||||
return HSER_POLL_ERROR_MISUSE;
|
||||
}
|
||||
|
||||
if (hse->state == in_state) {
|
||||
/* Check if output buffer is exhausted. */
|
||||
if (*output_size == out_buf_size) return HSER_POLL_MORE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HSE_finish_res heatshrink_encoder_finish(heatshrink_encoder *hse) {
|
||||
if (hse == NULL) { return HSER_FINISH_ERROR_NULL; }
|
||||
LOG("-- setting is_finishing flag\n");
|
||||
hse->flags |= FLAG_IS_FINISHING;
|
||||
if (hse->state == HSES_NOT_FULL) { hse->state = HSES_FILLED; }
|
||||
return hse->state == HSES_DONE ? HSER_FINISH_DONE : HSER_FINISH_MORE;
|
||||
}
|
||||
|
||||
static HSE_state st_step_search(heatshrink_encoder *hse) {
|
||||
uint16_t window_length = get_input_buffer_size(hse);
|
||||
uint16_t lookahead_sz = get_lookahead_size(hse);
|
||||
uint16_t msi = hse->match_scan_index;
|
||||
LOG("## step_search, scan @ +%d (%d/%d), input size %d\n",
|
||||
msi, hse->input_size + msi, 2*window_length, hse->input_size);
|
||||
|
||||
bool fin = is_finishing(hse);
|
||||
if (msi > hse->input_size - (fin ? 1 : lookahead_sz)) {
|
||||
/* Current search buffer is exhausted, copy it into the
|
||||
* backlog and await more input. */
|
||||
LOG("-- end of search @ %d\n", msi);
|
||||
return fin ? HSES_FLUSH_BITS : HSES_SAVE_BACKLOG;
|
||||
}
|
||||
|
||||
uint16_t input_offset = get_input_offset(hse);
|
||||
uint16_t end = input_offset + msi;
|
||||
uint16_t start = end - window_length;
|
||||
|
||||
uint16_t max_possible = lookahead_sz;
|
||||
if (hse->input_size - msi < lookahead_sz) {
|
||||
max_possible = hse->input_size - msi;
|
||||
}
|
||||
|
||||
uint16_t match_length = 0;
|
||||
uint16_t match_pos = find_longest_match(hse,
|
||||
start, end, max_possible, &match_length);
|
||||
|
||||
if (match_pos == MATCH_NOT_FOUND) {
|
||||
LOG("ss Match not found\n");
|
||||
hse->match_scan_index++;
|
||||
hse->match_length = 0;
|
||||
return HSES_YIELD_TAG_BIT;
|
||||
} else {
|
||||
LOG("ss Found match of %d bytes at %d\n", match_length, match_pos);
|
||||
hse->match_pos = match_pos;
|
||||
hse->match_length = match_length;
|
||||
ASSERT(match_pos <= 1 << HEATSHRINK_ENCODER_WINDOW_BITS(hse) /*window_length*/);
|
||||
|
||||
return HSES_YIELD_TAG_BIT;
|
||||
}
|
||||
}
|
||||
|
||||
static HSE_state st_yield_tag_bit(heatshrink_encoder *hse,
|
||||
output_info *oi) {
|
||||
if (can_take_byte(oi)) {
|
||||
if (hse->match_length == 0) {
|
||||
add_tag_bit(hse, oi, HEATSHRINK_LITERAL_MARKER);
|
||||
return HSES_YIELD_LITERAL;
|
||||
} else {
|
||||
add_tag_bit(hse, oi, HEATSHRINK_BACKREF_MARKER);
|
||||
hse->outgoing_bits = hse->match_pos - 1;
|
||||
hse->outgoing_bits_count = HEATSHRINK_ENCODER_WINDOW_BITS(hse);
|
||||
return HSES_YIELD_BR_INDEX;
|
||||
}
|
||||
} else {
|
||||
return HSES_YIELD_TAG_BIT; /* output is full, continue */
|
||||
}
|
||||
}
|
||||
|
||||
static HSE_state st_yield_literal(heatshrink_encoder *hse,
|
||||
output_info *oi) {
|
||||
if (can_take_byte(oi)) {
|
||||
push_literal_byte(hse, oi);
|
||||
return HSES_SEARCH;
|
||||
} else {
|
||||
return HSES_YIELD_LITERAL;
|
||||
}
|
||||
}
|
||||
|
||||
static HSE_state st_yield_br_index(heatshrink_encoder *hse,
|
||||
output_info *oi) {
|
||||
if (can_take_byte(oi)) {
|
||||
LOG("-- yielding backref index %u\n", hse->match_pos);
|
||||
if (push_outgoing_bits(hse, oi) > 0) {
|
||||
return HSES_YIELD_BR_INDEX; /* continue */
|
||||
} else {
|
||||
hse->outgoing_bits = hse->match_length - 1;
|
||||
hse->outgoing_bits_count = HEATSHRINK_ENCODER_LOOKAHEAD_BITS(hse);
|
||||
return HSES_YIELD_BR_LENGTH; /* done */
|
||||
}
|
||||
} else {
|
||||
return HSES_YIELD_BR_INDEX; /* continue */
|
||||
}
|
||||
}
|
||||
|
||||
static HSE_state st_yield_br_length(heatshrink_encoder *hse,
|
||||
output_info *oi) {
|
||||
if (can_take_byte(oi)) {
|
||||
LOG("-- yielding backref length %u\n", hse->match_length);
|
||||
if (push_outgoing_bits(hse, oi) > 0) {
|
||||
return HSES_YIELD_BR_LENGTH;
|
||||
} else {
|
||||
hse->match_scan_index += hse->match_length;
|
||||
hse->match_length = 0;
|
||||
return HSES_SEARCH;
|
||||
}
|
||||
} else {
|
||||
return HSES_YIELD_BR_LENGTH;
|
||||
}
|
||||
}
|
||||
|
||||
static HSE_state st_save_backlog(heatshrink_encoder *hse) {
|
||||
LOG("-- saving backlog\n");
|
||||
save_backlog(hse);
|
||||
return HSES_NOT_FULL;
|
||||
}
|
||||
|
||||
static HSE_state st_flush_bit_buffer(heatshrink_encoder *hse,
|
||||
output_info *oi) {
|
||||
if (hse->bit_index == 0x80) {
|
||||
LOG("-- done!\n");
|
||||
return HSES_DONE;
|
||||
} else if (can_take_byte(oi)) {
|
||||
LOG("-- flushing remaining byte (bit_index == 0x%02x)\n", hse->bit_index);
|
||||
oi->buf[(*oi->output_size)++] = hse->current_byte;
|
||||
LOG("-- done!\n");
|
||||
return HSES_DONE;
|
||||
} else {
|
||||
return HSES_FLUSH_BITS;
|
||||
}
|
||||
}
|
||||
|
||||
static void add_tag_bit(heatshrink_encoder *hse, output_info *oi, uint8_t tag) {
|
||||
LOG("-- adding tag bit: %d\n", tag);
|
||||
push_bits(hse, 1, tag, oi);
|
||||
}
|
||||
|
||||
static uint16_t get_input_offset(heatshrink_encoder *hse) {
|
||||
return get_input_buffer_size(hse);
|
||||
}
|
||||
|
||||
static uint16_t get_input_buffer_size(heatshrink_encoder *hse) {
|
||||
return (1 << HEATSHRINK_ENCODER_WINDOW_BITS(hse));
|
||||
(void)hse;
|
||||
}
|
||||
|
||||
static uint16_t get_lookahead_size(heatshrink_encoder *hse) {
|
||||
return (1 << HEATSHRINK_ENCODER_LOOKAHEAD_BITS(hse));
|
||||
(void)hse;
|
||||
}
|
||||
|
||||
static void do_indexing(heatshrink_encoder *hse) {
|
||||
#if HEATSHRINK_USE_INDEX
|
||||
/* Build an index array I that contains flattened linked lists
|
||||
* for the previous instances of every byte in the buffer.
|
||||
*
|
||||
* For example, if buf[200] == 'x', then index[200] will either
|
||||
* be an offset i such that buf[i] == 'x', or a negative offset
|
||||
* to indicate end-of-list. This significantly speeds up matching,
|
||||
* while only using sizeof(uint16_t)*sizeof(buffer) bytes of RAM.
|
||||
*
|
||||
* Future optimization options:
|
||||
* 1. Since any negative value represents end-of-list, the other
|
||||
* 15 bits could be used to improve the index dynamically.
|
||||
*
|
||||
* 2. Likewise, the last lookahead_sz bytes of the index will
|
||||
* not be usable, so temporary data could be stored there to
|
||||
* dynamically improve the index.
|
||||
* */
|
||||
struct hs_index *hsi = HEATSHRINK_ENCODER_INDEX(hse);
|
||||
int16_t last[256];
|
||||
memset(last, 0xFF, sizeof(last));
|
||||
|
||||
uint8_t * const data = hse->buffer;
|
||||
int16_t * const index = hsi->index;
|
||||
|
||||
const uint16_t input_offset = get_input_offset(hse);
|
||||
const uint16_t end = input_offset + hse->input_size;
|
||||
|
||||
for (uint16_t i=0; i<end; i++) {
|
||||
uint8_t v = data[i];
|
||||
int16_t lv = last[v];
|
||||
index[i] = lv;
|
||||
last[v] = i;
|
||||
}
|
||||
#else
|
||||
(void)hse;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int is_finishing(heatshrink_encoder *hse) {
|
||||
return hse->flags & FLAG_IS_FINISHING;
|
||||
}
|
||||
|
||||
static int can_take_byte(output_info *oi) {
|
||||
return *oi->output_size < oi->buf_size;
|
||||
}
|
||||
|
||||
/* Return the longest match for the bytes at buf[end:end+maxlen] between
|
||||
* buf[start] and buf[end-1]. If no match is found, return -1. */
|
||||
static uint16_t find_longest_match(heatshrink_encoder *hse, uint16_t start,
|
||||
uint16_t end, const uint16_t maxlen, uint16_t *match_length) {
|
||||
LOG("-- scanning for match of buf[%u:%u] between buf[%u:%u] (max %u bytes)\n",
|
||||
end, end + maxlen, start, end + maxlen - 1, maxlen);
|
||||
uint8_t *buf = hse->buffer;
|
||||
|
||||
uint16_t match_maxlen = 0;
|
||||
uint16_t match_index = MATCH_NOT_FOUND;
|
||||
|
||||
uint16_t len = 0;
|
||||
uint8_t * const needlepoint = &buf[end];
|
||||
#if HEATSHRINK_USE_INDEX
|
||||
struct hs_index *hsi = HEATSHRINK_ENCODER_INDEX(hse);
|
||||
int16_t pos = hsi->index[end];
|
||||
|
||||
while (pos - (int16_t)start >= 0) {
|
||||
uint8_t * const pospoint = &buf[pos];
|
||||
len = 0;
|
||||
|
||||
/* Only check matches that will potentially beat the current maxlen.
|
||||
* This is redundant with the index if match_maxlen is 0, but the
|
||||
* added branch overhead to check if it == 0 seems to be worse. */
|
||||
if (pospoint[match_maxlen] != needlepoint[match_maxlen]) {
|
||||
pos = hsi->index[pos];
|
||||
continue;
|
||||
}
|
||||
|
||||
for (len = 1; len < maxlen; len++) {
|
||||
if (pospoint[len] != needlepoint[len]) break;
|
||||
}
|
||||
|
||||
if (len > match_maxlen) {
|
||||
match_maxlen = len;
|
||||
match_index = pos;
|
||||
if (len == maxlen) { break; } /* won't find better */
|
||||
}
|
||||
pos = hsi->index[pos];
|
||||
}
|
||||
#else
|
||||
for (int16_t pos=end - 1; pos - (int16_t)start >= 0; pos--) {
|
||||
uint8_t * const pospoint = &buf[pos];
|
||||
if ((pospoint[match_maxlen] == needlepoint[match_maxlen])
|
||||
&& (*pospoint == *needlepoint)) {
|
||||
for (len=1; len<maxlen; len++) {
|
||||
if (0) {
|
||||
LOG(" --> cmp buf[%d] == 0x%02x against %02x (start %u)\n",
|
||||
pos + len, pospoint[len], needlepoint[len], start);
|
||||
}
|
||||
if (pospoint[len] != needlepoint[len]) { break; }
|
||||
}
|
||||
if (len > match_maxlen) {
|
||||
match_maxlen = len;
|
||||
match_index = pos;
|
||||
if (len == maxlen) { break; } /* don't keep searching */
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
const size_t break_even_point =
|
||||
(1 + HEATSHRINK_ENCODER_WINDOW_BITS(hse) +
|
||||
HEATSHRINK_ENCODER_LOOKAHEAD_BITS(hse));
|
||||
|
||||
/* Instead of comparing break_even_point against 8*match_maxlen,
|
||||
* compare match_maxlen against break_even_point/8 to avoid
|
||||
* overflow. Since MIN_WINDOW_BITS and MIN_LOOKAHEAD_BITS are 4 and
|
||||
* 3, respectively, break_even_point/8 will always be at least 1. */
|
||||
if (match_maxlen > (break_even_point / 8)) {
|
||||
LOG("-- best match: %u bytes at -%u\n",
|
||||
match_maxlen, end - match_index);
|
||||
*match_length = match_maxlen;
|
||||
return end - match_index;
|
||||
}
|
||||
LOG("-- none found\n");
|
||||
return MATCH_NOT_FOUND;
|
||||
}
|
||||
|
||||
static uint8_t push_outgoing_bits(heatshrink_encoder *hse, output_info *oi) {
|
||||
uint8_t count = 0;
|
||||
uint8_t bits = 0;
|
||||
if (hse->outgoing_bits_count > 8) {
|
||||
count = 8;
|
||||
bits = hse->outgoing_bits >> (hse->outgoing_bits_count - 8);
|
||||
} else {
|
||||
count = hse->outgoing_bits_count;
|
||||
bits = hse->outgoing_bits;
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
LOG("-- pushing %d outgoing bits: 0x%02x\n", count, bits);
|
||||
push_bits(hse, count, bits, oi);
|
||||
hse->outgoing_bits_count -= count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Push COUNT (max 8) bits to the output buffer, which has room.
|
||||
* Bytes are set from the lowest bits, up. */
|
||||
static void push_bits(heatshrink_encoder *hse, uint8_t count, uint8_t bits,
|
||||
output_info *oi) {
|
||||
ASSERT(count <= 8);
|
||||
LOG("++ push_bits: %d bits, input of 0x%02x\n", count, bits);
|
||||
|
||||
/* If adding a whole byte and at the start of a new output byte,
|
||||
* just push it through whole and skip the bit IO loop. */
|
||||
if (count == 8 && hse->bit_index == 0x80) {
|
||||
oi->buf[(*oi->output_size)++] = bits;
|
||||
} else {
|
||||
for (int i=count - 1; i>=0; i--) {
|
||||
bool bit = bits & (1 << i);
|
||||
if (bit) { hse->current_byte |= hse->bit_index; }
|
||||
if (0) {
|
||||
LOG(" -- setting bit %d at bit index 0x%02x, byte => 0x%02x\n",
|
||||
bit ? 1 : 0, hse->bit_index, hse->current_byte);
|
||||
}
|
||||
hse->bit_index >>= 1;
|
||||
if (hse->bit_index == 0x00) {
|
||||
hse->bit_index = 0x80;
|
||||
LOG(" > pushing byte 0x%02x\n", hse->current_byte);
|
||||
oi->buf[(*oi->output_size)++] = hse->current_byte;
|
||||
hse->current_byte = 0x00;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void push_literal_byte(heatshrink_encoder *hse, output_info *oi) {
|
||||
uint16_t processed_offset = hse->match_scan_index - 1;
|
||||
uint16_t input_offset = get_input_offset(hse) + processed_offset;
|
||||
uint8_t c = hse->buffer[input_offset];
|
||||
LOG("-- yielded literal byte 0x%02x ('%c') from +%d\n",
|
||||
c, isprint(c) ? c : '.', input_offset);
|
||||
push_bits(hse, 8, c, oi);
|
||||
}
|
||||
|
||||
static void save_backlog(heatshrink_encoder *hse) {
|
||||
size_t input_buf_sz = get_input_buffer_size(hse);
|
||||
|
||||
uint16_t msi = hse->match_scan_index;
|
||||
|
||||
/* Copy processed data to beginning of buffer, so it can be
|
||||
* used for future matches. Don't bother checking whether the
|
||||
* input is less than the maximum size, because if it isn't,
|
||||
* we're done anyway. */
|
||||
uint16_t rem = input_buf_sz - msi; // unprocessed bytes
|
||||
uint16_t shift_sz = input_buf_sz + rem;
|
||||
|
||||
memmove(&hse->buffer[0],
|
||||
&hse->buffer[input_buf_sz - rem],
|
||||
shift_sz);
|
||||
|
||||
hse->match_scan_index = 0;
|
||||
hse->input_size -= input_buf_sz - rem;
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
#ifndef HEATSHRINK_ENCODER_H
|
||||
#define HEATSHRINK_ENCODER_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "heatshrink_common.h"
|
||||
#include "heatshrink_config.h"
|
||||
|
||||
typedef enum {
|
||||
HSER_SINK_OK, /* data sunk into input buffer */
|
||||
HSER_SINK_ERROR_NULL=-1, /* NULL argument */
|
||||
HSER_SINK_ERROR_MISUSE=-2, /* API misuse */
|
||||
} HSE_sink_res;
|
||||
|
||||
typedef enum {
|
||||
HSER_POLL_EMPTY, /* input exhausted */
|
||||
HSER_POLL_MORE, /* poll again for more output */
|
||||
HSER_POLL_ERROR_NULL=-1, /* NULL argument */
|
||||
HSER_POLL_ERROR_MISUSE=-2, /* API misuse */
|
||||
} HSE_poll_res;
|
||||
|
||||
typedef enum {
|
||||
HSER_FINISH_DONE, /* encoding is complete */
|
||||
HSER_FINISH_MORE, /* more output remaining; use poll */
|
||||
HSER_FINISH_ERROR_NULL=-1, /* NULL argument */
|
||||
} HSE_finish_res;
|
||||
|
||||
#if HEATSHRINK_DYNAMIC_ALLOC
|
||||
#define HEATSHRINK_ENCODER_WINDOW_BITS(HSE) \
|
||||
((HSE)->window_sz2)
|
||||
#define HEATSHRINK_ENCODER_LOOKAHEAD_BITS(HSE) \
|
||||
((HSE)->lookahead_sz2)
|
||||
#define HEATSHRINK_ENCODER_INDEX(HSE) \
|
||||
((HSE)->search_index)
|
||||
struct hs_index {
|
||||
uint16_t size;
|
||||
int16_t index[];
|
||||
};
|
||||
#else
|
||||
#define HEATSHRINK_ENCODER_WINDOW_BITS(_) \
|
||||
(HEATSHRINK_STATIC_WINDOW_BITS)
|
||||
#define HEATSHRINK_ENCODER_LOOKAHEAD_BITS(_) \
|
||||
(HEATSHRINK_STATIC_LOOKAHEAD_BITS)
|
||||
#define HEATSHRINK_ENCODER_INDEX(HSE) \
|
||||
(&(HSE)->search_index)
|
||||
struct hs_index {
|
||||
uint16_t size;
|
||||
int16_t index[2 << HEATSHRINK_STATIC_WINDOW_BITS];
|
||||
};
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
uint16_t input_size; /* bytes in input buffer */
|
||||
uint16_t match_scan_index;
|
||||
uint16_t match_length;
|
||||
uint16_t match_pos;
|
||||
uint16_t outgoing_bits; /* enqueued outgoing bits */
|
||||
uint8_t outgoing_bits_count;
|
||||
uint8_t flags;
|
||||
uint8_t state; /* current state machine node */
|
||||
uint8_t current_byte; /* current byte of output */
|
||||
uint8_t bit_index; /* current bit index */
|
||||
#if HEATSHRINK_DYNAMIC_ALLOC
|
||||
uint8_t window_sz2; /* 2^n size of window */
|
||||
uint8_t lookahead_sz2; /* 2^n size of lookahead */
|
||||
#if HEATSHRINK_USE_INDEX
|
||||
struct hs_index *search_index;
|
||||
#endif
|
||||
/* input buffer and / sliding window for expansion */
|
||||
uint8_t* buffer;
|
||||
#else
|
||||
#if HEATSHRINK_USE_INDEX
|
||||
struct hs_index search_index;
|
||||
#endif
|
||||
/* input buffer and / sliding window for expansion */
|
||||
uint8_t buffer[2 << HEATSHRINK_ENCODER_WINDOW_BITS(_)];
|
||||
#endif
|
||||
} heatshrink_encoder;
|
||||
|
||||
#if HEATSHRINK_DYNAMIC_ALLOC
|
||||
/* Allocate a new encoder struct and its buffers.
|
||||
* Returns NULL on error. */
|
||||
heatshrink_encoder *heatshrink_encoder_alloc(uint8_t* buffer, uint8_t window_sz2,
|
||||
uint8_t lookahead_sz2);
|
||||
|
||||
/* Free an encoder. */
|
||||
void heatshrink_encoder_free(heatshrink_encoder *hse);
|
||||
#endif
|
||||
|
||||
/* Reset an encoder. */
|
||||
void heatshrink_encoder_reset(heatshrink_encoder *hse);
|
||||
|
||||
/* Sink up to SIZE bytes from IN_BUF into the encoder.
|
||||
* INPUT_SIZE is set to the number of bytes actually sunk (in case a
|
||||
* buffer was filled.). */
|
||||
HSE_sink_res heatshrink_encoder_sink(heatshrink_encoder *hse,
|
||||
uint8_t *in_buf, size_t size, size_t *input_size);
|
||||
|
||||
/* Poll for output from the encoder, copying at most OUT_BUF_SIZE bytes into
|
||||
* OUT_BUF (setting *OUTPUT_SIZE to the actual amount copied). */
|
||||
HSE_poll_res heatshrink_encoder_poll(heatshrink_encoder *hse,
|
||||
uint8_t *out_buf, size_t out_buf_size, size_t *output_size);
|
||||
|
||||
/* Notify the encoder that the input stream is finished.
|
||||
* If the return value is HSER_FINISH_MORE, there is still more output, so
|
||||
* call heatshrink_encoder_poll and repeat. */
|
||||
HSE_finish_res heatshrink_encoder_finish(heatshrink_encoder *hse);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,24 @@
|
||||
Import("env")
|
||||
|
||||
env.Append(
|
||||
LINT_SOURCES=[
|
||||
Dir("."),
|
||||
],
|
||||
CPPPATH=[
|
||||
"#/lib/ibutton",
|
||||
],
|
||||
SDK_HEADERS=[
|
||||
File("ibutton_key.h"),
|
||||
File("ibutton_worker.h"),
|
||||
File("ibutton_protocols.h"),
|
||||
],
|
||||
)
|
||||
|
||||
libenv = env.Clone(FW_LIB_NAME="ibutton")
|
||||
libenv.ApplyLibFlags()
|
||||
|
||||
sources = libenv.GlobRecursive("*.c*")
|
||||
|
||||
lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources)
|
||||
libenv.Install("${LIB_DIST_DIR}", lib)
|
||||
Return("lib")
|
||||
@@ -0,0 +1,43 @@
|
||||
#include "ibutton_key_i.h"
|
||||
|
||||
struct iButtonKey {
|
||||
iButtonProtocolId protocol_id;
|
||||
iButtonProtocolData* protocol_data;
|
||||
size_t protocol_data_size;
|
||||
};
|
||||
|
||||
iButtonKey* ibutton_key_alloc(size_t data_size) {
|
||||
iButtonKey* key = malloc(sizeof(iButtonKey));
|
||||
|
||||
key->protocol_id = iButtonProtocolIdInvalid;
|
||||
key->protocol_data = malloc(data_size);
|
||||
key->protocol_data_size = data_size;
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
void ibutton_key_free(iButtonKey* key) {
|
||||
free(key->protocol_data);
|
||||
free(key);
|
||||
}
|
||||
|
||||
void ibutton_key_reset(iButtonKey* key) {
|
||||
key->protocol_id = iButtonProtocolIdInvalid;
|
||||
memset(key->protocol_data, 0, key->protocol_data_size);
|
||||
}
|
||||
|
||||
iButtonProtocolId ibutton_key_get_protocol_id(const iButtonKey* key) {
|
||||
return key->protocol_id;
|
||||
}
|
||||
|
||||
void ibutton_key_set_protocol_id(iButtonKey* key, iButtonProtocolId protocol_id) {
|
||||
key->protocol_id = protocol_id;
|
||||
}
|
||||
|
||||
iButtonProtocolData* ibutton_key_get_protocol_data(const iButtonKey* key) {
|
||||
return key->protocol_data;
|
||||
}
|
||||
|
||||
size_t ibutton_key_get_protocol_data_size(const iButtonKey* key) {
|
||||
return key->protocol_data_size;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* @file ibutton_key.h
|
||||
*
|
||||
* iButton key data holder
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <core/string.h>
|
||||
|
||||
#include "protocols/protocol_common.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct iButtonKey iButtonKey;
|
||||
|
||||
/**
|
||||
* Allocate a key object
|
||||
* @param [in] data_size maximum data size held by the key
|
||||
* @return pointer to the key object
|
||||
*/
|
||||
iButtonKey* ibutton_key_alloc(size_t data_size);
|
||||
|
||||
/**
|
||||
* Destroy the key object, free resources
|
||||
* @param [in] key pointer to the key object
|
||||
*/
|
||||
void ibutton_key_free(iButtonKey* key);
|
||||
|
||||
/**
|
||||
* Get the protocol id held by the key
|
||||
* @param [in] key pointer to the key object
|
||||
* @return protocol id held by the key
|
||||
*/
|
||||
iButtonProtocolId ibutton_key_get_protocol_id(const iButtonKey* key);
|
||||
|
||||
/**
|
||||
* Set the protocol id held by the key
|
||||
* @param [in] key pointer to the key object
|
||||
* @param [in] protocol_id new protocol id
|
||||
*/
|
||||
void ibutton_key_set_protocol_id(iButtonKey* key, iButtonProtocolId protocol_id);
|
||||
|
||||
/**
|
||||
* Reset the protocol id and data held by the key
|
||||
* @param [in] key pointer to the key object
|
||||
*/
|
||||
void ibutton_key_reset(iButtonKey* key);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "ibutton_key.h"
|
||||
|
||||
#include "protocols/protocol_common_i.h"
|
||||
|
||||
iButtonProtocolData* ibutton_key_get_protocol_data(const iButtonKey* key);
|
||||
|
||||
size_t ibutton_key_get_protocol_data_size(const iButtonKey* key);
|
||||
@@ -0,0 +1,309 @@
|
||||
#include "ibutton_protocols.h"
|
||||
|
||||
#include <storage/storage.h>
|
||||
|
||||
#include "ibutton_key_i.h"
|
||||
|
||||
#include "protocols/protocol_group_defs.h"
|
||||
|
||||
#define IBUTTON_FILE_TYPE "Flipper iButton key"
|
||||
|
||||
#define IBUTTON_PROTOCOL_KEY_V1 "Key type"
|
||||
#define IBUTTON_PROTOCOL_KEY_V2 "Protocol"
|
||||
|
||||
#define IBUTTON_CURRENT_FORMAT_VERSION 2U
|
||||
|
||||
#define GET_PROTOCOL_GROUP(id) \
|
||||
iButtonProtocolGroupInfo info; \
|
||||
ibutton_protocols_get_group_by_id(protocols, (id), &info);
|
||||
|
||||
#define GROUP_BASE (info.base)
|
||||
#define GROUP_DATA (info.group)
|
||||
#define PROTOCOL_ID (info.id)
|
||||
|
||||
struct iButtonProtocols {
|
||||
iButtonProtocolGroupData** group_datas;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const iButtonProtocolGroupBase* base;
|
||||
iButtonProtocolGroupData* group;
|
||||
iButtonProtocolLocalId id;
|
||||
} iButtonProtocolGroupInfo;
|
||||
|
||||
static void ibutton_protocols_get_group_by_id(
|
||||
iButtonProtocols* protocols,
|
||||
iButtonProtocolId id,
|
||||
iButtonProtocolGroupInfo* info) {
|
||||
iButtonProtocolLocalId local_id = id;
|
||||
|
||||
for(iButtonProtocolGroupId i = 0; i < iButtonProtocolGroupMax; ++i) {
|
||||
if(local_id < (signed)ibutton_protocol_groups[i]->protocol_count) {
|
||||
info->base = ibutton_protocol_groups[i];
|
||||
info->group = protocols->group_datas[i];
|
||||
info->id = local_id;
|
||||
return;
|
||||
|
||||
} else {
|
||||
local_id -= ibutton_protocol_groups[i]->protocol_count;
|
||||
}
|
||||
}
|
||||
furi_crash(NULL);
|
||||
}
|
||||
|
||||
iButtonProtocols* ibutton_protocols_alloc() {
|
||||
iButtonProtocols* protocols = malloc(sizeof(iButtonProtocols*));
|
||||
|
||||
protocols->group_datas = malloc(sizeof(iButtonProtocolGroupData*) * iButtonProtocolGroupMax);
|
||||
|
||||
for(iButtonProtocolGroupId i = 0; i < iButtonProtocolGroupMax; ++i) {
|
||||
protocols->group_datas[i] = ibutton_protocol_groups[i]->alloc();
|
||||
}
|
||||
|
||||
return protocols;
|
||||
}
|
||||
|
||||
void ibutton_protocols_free(iButtonProtocols* protocols) {
|
||||
for(iButtonProtocolGroupId i = 0; i < iButtonProtocolGroupMax; ++i) {
|
||||
ibutton_protocol_groups[i]->free(protocols->group_datas[i]);
|
||||
}
|
||||
|
||||
free(protocols->group_datas);
|
||||
free(protocols);
|
||||
}
|
||||
|
||||
uint32_t ibutton_protocols_get_protocol_count() {
|
||||
uint32_t count = 0;
|
||||
|
||||
for(iButtonProtocolGroupId i = 0; i < iButtonProtocolGroupMax; ++i) {
|
||||
count += ibutton_protocol_groups[i]->protocol_count;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
iButtonProtocolId ibutton_protocols_get_id_by_name(iButtonProtocols* protocols, const char* name) {
|
||||
iButtonProtocolLocalId offset = 0;
|
||||
|
||||
for(iButtonProtocolGroupId i = 0; i < iButtonProtocolGroupMax; ++i) {
|
||||
iButtonProtocolLocalId local_id;
|
||||
if(ibutton_protocol_groups[i]->get_id_by_name(protocols->group_datas[i], &local_id, name)) {
|
||||
return local_id + offset;
|
||||
}
|
||||
offset += ibutton_protocol_groups[i]->protocol_count;
|
||||
}
|
||||
return iButtonProtocolIdInvalid;
|
||||
}
|
||||
|
||||
uint32_t ibutton_protocols_get_features(iButtonProtocols* protocols, iButtonProtocolId id) {
|
||||
GET_PROTOCOL_GROUP(id);
|
||||
return GROUP_BASE->get_features(GROUP_DATA, PROTOCOL_ID);
|
||||
}
|
||||
|
||||
size_t ibutton_protocols_get_max_data_size(iButtonProtocols* protocols) {
|
||||
size_t max_size = 0;
|
||||
|
||||
for(iButtonProtocolGroupId i = 0; i < iButtonProtocolGroupMax; ++i) {
|
||||
const size_t current_max_size =
|
||||
ibutton_protocol_groups[i]->get_max_data_size(protocols->group_datas[i]);
|
||||
if(current_max_size > max_size) {
|
||||
max_size = current_max_size;
|
||||
}
|
||||
}
|
||||
|
||||
return max_size;
|
||||
}
|
||||
|
||||
const char* ibutton_protocols_get_manufacturer(iButtonProtocols* protocols, iButtonProtocolId id) {
|
||||
GET_PROTOCOL_GROUP(id);
|
||||
return GROUP_BASE->get_manufacturer(GROUP_DATA, PROTOCOL_ID);
|
||||
}
|
||||
|
||||
const char* ibutton_protocols_get_name(iButtonProtocols* protocols, iButtonProtocolId id) {
|
||||
GET_PROTOCOL_GROUP(id);
|
||||
return GROUP_BASE->get_name(GROUP_DATA, PROTOCOL_ID);
|
||||
}
|
||||
|
||||
bool ibutton_protocols_read(iButtonProtocols* protocols, iButtonKey* key) {
|
||||
iButtonProtocolLocalId id = iButtonProtocolIdInvalid;
|
||||
iButtonProtocolData* data = ibutton_key_get_protocol_data(key);
|
||||
|
||||
iButtonProtocolLocalId offset = 0;
|
||||
for(iButtonProtocolGroupId i = 0; i < iButtonProtocolGroupMax; ++i) {
|
||||
if(ibutton_protocol_groups[i]->read(protocols->group_datas[i], data, &id)) {
|
||||
id += offset;
|
||||
break;
|
||||
}
|
||||
offset += ibutton_protocol_groups[i]->protocol_count;
|
||||
}
|
||||
|
||||
ibutton_key_set_protocol_id(key, id);
|
||||
return id != iButtonProtocolIdInvalid;
|
||||
}
|
||||
|
||||
bool ibutton_protocols_write_blank(iButtonProtocols* protocols, iButtonKey* key) {
|
||||
const iButtonProtocolId id = ibutton_key_get_protocol_id(key);
|
||||
iButtonProtocolData* data = ibutton_key_get_protocol_data(key);
|
||||
|
||||
GET_PROTOCOL_GROUP(id);
|
||||
return GROUP_BASE->write_blank(GROUP_DATA, data, PROTOCOL_ID);
|
||||
}
|
||||
|
||||
bool ibutton_protocols_write_copy(iButtonProtocols* protocols, iButtonKey* key) {
|
||||
const iButtonProtocolId id = ibutton_key_get_protocol_id(key);
|
||||
iButtonProtocolData* data = ibutton_key_get_protocol_data(key);
|
||||
|
||||
GET_PROTOCOL_GROUP(id);
|
||||
return GROUP_BASE->write_copy(GROUP_DATA, data, PROTOCOL_ID);
|
||||
}
|
||||
|
||||
void ibutton_protocols_emulate_start(iButtonProtocols* protocols, iButtonKey* key) {
|
||||
const iButtonProtocolId id = ibutton_key_get_protocol_id(key);
|
||||
iButtonProtocolData* data = ibutton_key_get_protocol_data(key);
|
||||
|
||||
GET_PROTOCOL_GROUP(id);
|
||||
GROUP_BASE->emulate_start(GROUP_DATA, data, PROTOCOL_ID);
|
||||
}
|
||||
|
||||
void ibutton_protocols_emulate_stop(iButtonProtocols* protocols, iButtonKey* key) {
|
||||
const iButtonProtocolId id = ibutton_key_get_protocol_id(key);
|
||||
iButtonProtocolData* data = ibutton_key_get_protocol_data(key);
|
||||
|
||||
GET_PROTOCOL_GROUP(id);
|
||||
GROUP_BASE->emulate_stop(GROUP_DATA, data, PROTOCOL_ID);
|
||||
}
|
||||
|
||||
bool ibutton_protocols_save(
|
||||
iButtonProtocols* protocols,
|
||||
const iButtonKey* key,
|
||||
const char* file_name) {
|
||||
const iButtonProtocolId id = ibutton_key_get_protocol_id(key);
|
||||
const iButtonProtocolData* data = ibutton_key_get_protocol_data(key);
|
||||
|
||||
bool success = false;
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
|
||||
FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
|
||||
|
||||
do {
|
||||
const char* protocol_name = ibutton_protocols_get_name(protocols, id);
|
||||
|
||||
if(!flipper_format_buffered_file_open_always(ff, file_name)) break;
|
||||
|
||||
if(!flipper_format_write_header_cstr(ff, IBUTTON_FILE_TYPE, IBUTTON_CURRENT_FORMAT_VERSION))
|
||||
break;
|
||||
if(!flipper_format_write_string_cstr(ff, IBUTTON_PROTOCOL_KEY_V2, protocol_name)) break;
|
||||
|
||||
GET_PROTOCOL_GROUP(id);
|
||||
if(!GROUP_BASE->save(GROUP_DATA, data, PROTOCOL_ID, ff)) break;
|
||||
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
flipper_format_free(ff);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ibutton_protocols_load(iButtonProtocols* protocols, iButtonKey* key, const char* file_name) {
|
||||
iButtonProtocolData* data = ibutton_key_get_protocol_data(key);
|
||||
|
||||
bool success = false;
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
|
||||
FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
|
||||
FuriString* tmp = furi_string_alloc();
|
||||
|
||||
do {
|
||||
if(!flipper_format_buffered_file_open_existing(ff, file_name)) break;
|
||||
|
||||
uint32_t version;
|
||||
|
||||
if(!flipper_format_read_header(ff, tmp, &version)) break;
|
||||
if(!furi_string_equal(tmp, IBUTTON_FILE_TYPE)) break;
|
||||
|
||||
if(version == 1) {
|
||||
if(!flipper_format_read_string(ff, IBUTTON_PROTOCOL_KEY_V1, tmp)) break;
|
||||
} else if(version == 2) {
|
||||
if(!flipper_format_read_string(ff, IBUTTON_PROTOCOL_KEY_V2, tmp)) break;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
const iButtonProtocolId id =
|
||||
ibutton_protocols_get_id_by_name(protocols, furi_string_get_cstr(tmp));
|
||||
ibutton_key_set_protocol_id(key, id);
|
||||
|
||||
GET_PROTOCOL_GROUP(id);
|
||||
if(!GROUP_BASE->load(GROUP_DATA, data, PROTOCOL_ID, version, ff)) break;
|
||||
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
flipper_format_free(ff);
|
||||
furi_string_free(tmp);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void ibutton_protocols_render_data(
|
||||
iButtonProtocols* protocols,
|
||||
const iButtonKey* key,
|
||||
FuriString* result) {
|
||||
const iButtonProtocolId id = ibutton_key_get_protocol_id(key);
|
||||
const iButtonProtocolData* data = ibutton_key_get_protocol_data(key);
|
||||
|
||||
GET_PROTOCOL_GROUP(id);
|
||||
GROUP_BASE->render_data(GROUP_DATA, data, PROTOCOL_ID, result);
|
||||
}
|
||||
|
||||
void ibutton_protocols_render_brief_data(
|
||||
iButtonProtocols* protocols,
|
||||
const iButtonKey* key,
|
||||
FuriString* result) {
|
||||
const iButtonProtocolId id = ibutton_key_get_protocol_id(key);
|
||||
const iButtonProtocolData* data = ibutton_key_get_protocol_data(key);
|
||||
|
||||
GET_PROTOCOL_GROUP(id);
|
||||
GROUP_BASE->render_brief_data(GROUP_DATA, data, PROTOCOL_ID, result);
|
||||
}
|
||||
|
||||
void ibutton_protocols_render_error(
|
||||
iButtonProtocols* protocols,
|
||||
const iButtonKey* key,
|
||||
FuriString* result) {
|
||||
const iButtonProtocolId id = ibutton_key_get_protocol_id(key);
|
||||
const iButtonProtocolData* data = ibutton_key_get_protocol_data(key);
|
||||
|
||||
GET_PROTOCOL_GROUP(id);
|
||||
GROUP_BASE->render_error(GROUP_DATA, data, PROTOCOL_ID, result);
|
||||
}
|
||||
|
||||
bool ibutton_protocols_is_valid(iButtonProtocols* protocols, const iButtonKey* key) {
|
||||
const iButtonProtocolId id = ibutton_key_get_protocol_id(key);
|
||||
const iButtonProtocolData* data = ibutton_key_get_protocol_data(key);
|
||||
|
||||
GET_PROTOCOL_GROUP(id);
|
||||
return GROUP_BASE->is_valid(GROUP_DATA, data, PROTOCOL_ID);
|
||||
}
|
||||
|
||||
void ibutton_protocols_get_editable_data(
|
||||
iButtonProtocols* protocols,
|
||||
const iButtonKey* key,
|
||||
iButtonEditableData* editable) {
|
||||
const iButtonProtocolId id = ibutton_key_get_protocol_id(key);
|
||||
iButtonProtocolData* data = ibutton_key_get_protocol_data(key);
|
||||
|
||||
GET_PROTOCOL_GROUP(id);
|
||||
GROUP_BASE->get_editable_data(GROUP_DATA, data, PROTOCOL_ID, editable);
|
||||
}
|
||||
|
||||
void ibutton_protocols_apply_edits(iButtonProtocols* protocols, const iButtonKey* key) {
|
||||
const iButtonProtocolId id = ibutton_key_get_protocol_id(key);
|
||||
iButtonProtocolData* data = ibutton_key_get_protocol_data(key);
|
||||
|
||||
GET_PROTOCOL_GROUP(id);
|
||||
GROUP_BASE->apply_edits(GROUP_DATA, data, PROTOCOL_ID);
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
/**
|
||||
* @file ibutton_protocols.h
|
||||
*
|
||||
* Common interface for accessing various iButton protocols
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "protocols/protocol_common.h"
|
||||
|
||||
#include "ibutton_key.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct iButtonProtocols iButtonProtocols;
|
||||
|
||||
/**
|
||||
* Allocate an iButtonProtocols object
|
||||
* @return pointer to an iButtonProtocols object
|
||||
*/
|
||||
iButtonProtocols* ibutton_protocols_alloc();
|
||||
|
||||
/**
|
||||
* Destroy an iButtonProtocols object, free resources
|
||||
* @param [in] protocols pointer to an iButtonProtocols object
|
||||
*/
|
||||
void ibutton_protocols_free(iButtonProtocols* protocols);
|
||||
|
||||
/**
|
||||
* Get the total number of available protocols
|
||||
*/
|
||||
uint32_t ibutton_protocols_get_protocol_count();
|
||||
|
||||
/**
|
||||
* Get maximum data size out of all protocols available
|
||||
* @param [in] protocols pointer to an iButtonProtocols object
|
||||
* @return maximum data size in bytes
|
||||
*/
|
||||
size_t ibutton_protocols_get_max_data_size(iButtonProtocols* protocols);
|
||||
|
||||
/**
|
||||
* Get the protocol id based on its name
|
||||
* @param [in] protocols pointer to an iButtonProtocols object
|
||||
* @param [in] name pointer to a string containing the name
|
||||
* @return protocol id on success on iButtonProtocolIdInvalid on failure
|
||||
*/
|
||||
iButtonProtocolId ibutton_protocols_get_id_by_name(iButtonProtocols* protocols, const char* name);
|
||||
|
||||
/**
|
||||
* Get the manufacturer name based on the protocol id
|
||||
* @param [in] protocols pointer to an iButtonProtocols object
|
||||
* @param [in] id id of the protocol in question
|
||||
* @return pointer to a statically allocated string with manufacturer name
|
||||
*/
|
||||
const char* ibutton_protocols_get_manufacturer(iButtonProtocols* protocols, iButtonProtocolId id);
|
||||
|
||||
/**
|
||||
* Get the protocol name based on the protocol id
|
||||
* @param [in] protocols pointer to an iButtonProtocols object
|
||||
* @param [in] id id of the protocol in question
|
||||
* @return pointer to a statically allocated string with protocol name
|
||||
*/
|
||||
const char* ibutton_protocols_get_name(iButtonProtocols* protocols, iButtonProtocolId id);
|
||||
|
||||
/**
|
||||
* Get protocol features bitmask by protocol id
|
||||
* @param [in] protocols pointer to an iButtonProtocols object
|
||||
* @param [in] id id of the protocol in question
|
||||
*/
|
||||
uint32_t ibutton_protocols_get_features(iButtonProtocols* protocols, iButtonProtocolId id);
|
||||
|
||||
/**
|
||||
* Read a physical device (a key or an emulator)
|
||||
* @param [in] protocols pointer to an iButtonProtocols object
|
||||
* @param [out] key pointer to the key to read into (must be allocated before)
|
||||
* @return true on success, false on failure
|
||||
*/
|
||||
bool ibutton_protocols_read(iButtonProtocols* protocols, iButtonKey* key);
|
||||
|
||||
/**
|
||||
* Write the key to a blank
|
||||
* @param [in] protocols pointer to an iButtonProtocols object
|
||||
* @param [in] key pointer to the key to be written
|
||||
* @return true on success, false on failure
|
||||
*/
|
||||
bool ibutton_protocols_write_blank(iButtonProtocols* protocols, iButtonKey* key);
|
||||
|
||||
/**
|
||||
* Write the key to another one of the same type
|
||||
* @param [in] protocols pointer to an iButtonProtocols object
|
||||
* @param [in] key pointer to the key to be written
|
||||
* @return true on success, false on failure
|
||||
*/
|
||||
bool ibutton_protocols_write_copy(iButtonProtocols* protocols, iButtonKey* key);
|
||||
|
||||
/**
|
||||
* Start emulating the key
|
||||
* @param [in] protocols pointer to an iButtonProtocols object
|
||||
* @param [in] key pointer to the key to be emulated
|
||||
*/
|
||||
void ibutton_protocols_emulate_start(iButtonProtocols* protocols, iButtonKey* key);
|
||||
|
||||
/**
|
||||
* Stop emulating the key
|
||||
* @param [in] protocols pointer to an iButtonProtocols object
|
||||
* @param [in] key pointer to the key to be emulated
|
||||
*/
|
||||
void ibutton_protocols_emulate_stop(iButtonProtocols* protocols, iButtonKey* key);
|
||||
|
||||
/**
|
||||
* Save the key data to a file.
|
||||
* @param [in] protocols pointer to an iButtonProtocols object
|
||||
* @param [in] key pointer to the key to be saved
|
||||
* @param [in] file_name full absolute path to the file name
|
||||
* @return true on success, false on failure
|
||||
*/
|
||||
bool ibutton_protocols_save(
|
||||
iButtonProtocols* protocols,
|
||||
const iButtonKey* key,
|
||||
const char* file_name);
|
||||
|
||||
/**
|
||||
* Load the key from a file.
|
||||
* @param [in] protocols pointer to an iButtonProtocols object
|
||||
* @param [out] key pointer to the key to load into (must be allocated before)
|
||||
* @param [in] file_name full absolute path to the file name
|
||||
* @return true on success, false on failure
|
||||
*/
|
||||
bool ibutton_protocols_load(iButtonProtocols* protocols, iButtonKey* key, const char* file_name);
|
||||
|
||||
/**
|
||||
* Format a string containing device full data
|
||||
* @param [in] protocols pointer to an iButtonProtocols object
|
||||
* @param [in] key pointer to the key to be rendered
|
||||
* @param [out] result pointer to the FuriString instance (must be initialized)
|
||||
*/
|
||||
void ibutton_protocols_render_data(
|
||||
iButtonProtocols* protocols,
|
||||
const iButtonKey* key,
|
||||
FuriString* result);
|
||||
|
||||
/**
|
||||
* Format a string containing device brief data
|
||||
* @param [in] protocols pointer to an iButtonProtocols object
|
||||
* @param [in] key pointer to the key to be rendered
|
||||
* @param [out] result pointer to the FuriString instance (must be initialized)
|
||||
*/
|
||||
void ibutton_protocols_render_brief_data(
|
||||
iButtonProtocols* protocols,
|
||||
const iButtonKey* key,
|
||||
FuriString* result);
|
||||
|
||||
/**
|
||||
* Format a string containing error message (for invalid keys)
|
||||
* @param [in] protocols pointer to an iButtonProtocols object
|
||||
* @param [in] key pointer to the key to be rendered
|
||||
* @param [out] result pointer to the FuriString instance (must be initialized)
|
||||
*/
|
||||
void ibutton_protocols_render_error(
|
||||
iButtonProtocols* protocols,
|
||||
const iButtonKey* key,
|
||||
FuriString* result);
|
||||
|
||||
/**
|
||||
* Check whether the key data is valid
|
||||
* @param [in] protocols pointer to an iButtonProtocols object
|
||||
* @param [in] key pointer to the key to be checked
|
||||
* @return true if data is valid, false otherwise
|
||||
*/
|
||||
bool ibutton_protocols_is_valid(iButtonProtocols* protocols, const iButtonKey* key);
|
||||
|
||||
/**
|
||||
* Get a pointer to the key's editable data (for in-place editing)
|
||||
* @param [in] protocols pointer to an iButtonProtocols object
|
||||
* @param [in] key pointer to the key to be checked
|
||||
* @param [out] editable pointer to a structure to contain the editable data
|
||||
*/
|
||||
void ibutton_protocols_get_editable_data(
|
||||
iButtonProtocols* protocols,
|
||||
const iButtonKey* key,
|
||||
iButtonEditableData* editable);
|
||||
|
||||
/**
|
||||
* Make all necessary internal adjustments after editing the key
|
||||
* @param [in] protocols pointer to an iButtonProtocols object
|
||||
* @param [in,out] key pointer to the key to be adjusted
|
||||
*/
|
||||
void ibutton_protocols_apply_edits(iButtonProtocols* protocols, const iButtonKey* key);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,13 +1,14 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <atomic.h>
|
||||
#include "ibutton_worker_i.h"
|
||||
#include "ibutton_protocols.h"
|
||||
|
||||
#include <core/check.h>
|
||||
|
||||
typedef enum {
|
||||
iButtonMessageEnd,
|
||||
iButtonMessageStop,
|
||||
iButtonMessageRead,
|
||||
iButtonMessageWrite,
|
||||
iButtonMessageWriteBlank,
|
||||
iButtonMessageWriteCopy,
|
||||
iButtonMessageEmulate,
|
||||
iButtonMessageNotifyEmulate,
|
||||
} iButtonMessageType;
|
||||
@@ -21,26 +22,15 @@ typedef struct {
|
||||
|
||||
static int32_t ibutton_worker_thread(void* thread_context);
|
||||
|
||||
iButtonWorker* ibutton_worker_alloc() {
|
||||
iButtonWorker* ibutton_worker_alloc(iButtonProtocols* protocols) {
|
||||
iButtonWorker* worker = malloc(sizeof(iButtonWorker));
|
||||
worker->key_p = NULL;
|
||||
worker->key_data = malloc(ibutton_key_get_max_size());
|
||||
worker->host = onewire_host_alloc();
|
||||
worker->slave = onewire_slave_alloc();
|
||||
worker->writer = ibutton_writer_alloc(worker->host);
|
||||
worker->device = onewire_device_alloc(0, 0, 0, 0, 0, 0, 0, 0);
|
||||
|
||||
worker->protocols = protocols;
|
||||
worker->messages = furi_message_queue_alloc(1, sizeof(iButtonMessage));
|
||||
|
||||
worker->mode_index = iButtonWorkerIdle;
|
||||
worker->read_cb = NULL;
|
||||
worker->write_cb = NULL;
|
||||
worker->emulate_cb = NULL;
|
||||
worker->cb_ctx = NULL;
|
||||
|
||||
worker->mode_index = iButtonWorkerModeIdle;
|
||||
worker->thread = furi_thread_alloc_ex("iButtonWorker", 2048, ibutton_worker_thread, worker);
|
||||
|
||||
worker->protocols = protocol_dict_alloc(ibutton_protocols, iButtonProtocolMax);
|
||||
|
||||
return worker;
|
||||
}
|
||||
|
||||
@@ -48,7 +38,7 @@ void ibutton_worker_read_set_callback(
|
||||
iButtonWorker* worker,
|
||||
iButtonWorkerReadCallback callback,
|
||||
void* context) {
|
||||
furi_check(worker->mode_index == iButtonWorkerIdle);
|
||||
furi_check(worker->mode_index == iButtonWorkerModeIdle);
|
||||
worker->read_cb = callback;
|
||||
worker->cb_ctx = context;
|
||||
}
|
||||
@@ -57,7 +47,7 @@ void ibutton_worker_write_set_callback(
|
||||
iButtonWorker* worker,
|
||||
iButtonWorkerWriteCallback callback,
|
||||
void* context) {
|
||||
furi_check(worker->mode_index == iButtonWorkerIdle);
|
||||
furi_check(worker->mode_index == iButtonWorkerModeIdle);
|
||||
worker->write_cb = callback;
|
||||
worker->cb_ctx = context;
|
||||
}
|
||||
@@ -66,7 +56,7 @@ void ibutton_worker_emulate_set_callback(
|
||||
iButtonWorker* worker,
|
||||
iButtonWorkerEmulateCallback callback,
|
||||
void* context) {
|
||||
furi_check(worker->mode_index == iButtonWorkerIdle);
|
||||
furi_check(worker->mode_index == iButtonWorkerModeIdle);
|
||||
worker->emulate_cb = callback;
|
||||
worker->cb_ctx = context;
|
||||
}
|
||||
@@ -77,8 +67,14 @@ void ibutton_worker_read_start(iButtonWorker* worker, iButtonKey* key) {
|
||||
furi_message_queue_put(worker->messages, &message, FuriWaitForever) == FuriStatusOk);
|
||||
}
|
||||
|
||||
void ibutton_worker_write_start(iButtonWorker* worker, iButtonKey* key) {
|
||||
iButtonMessage message = {.type = iButtonMessageWrite, .data.key = key};
|
||||
void ibutton_worker_write_blank_start(iButtonWorker* worker, iButtonKey* key) {
|
||||
iButtonMessage message = {.type = iButtonMessageWriteBlank, .data.key = key};
|
||||
furi_check(
|
||||
furi_message_queue_put(worker->messages, &message, FuriWaitForever) == FuriStatusOk);
|
||||
}
|
||||
|
||||
void ibutton_worker_write_copy_start(iButtonWorker* worker, iButtonKey* key) {
|
||||
iButtonMessage message = {.type = iButtonMessageWriteCopy, .data.key = key};
|
||||
furi_check(
|
||||
furi_message_queue_put(worker->messages, &message, FuriWaitForever) == FuriStatusOk);
|
||||
}
|
||||
@@ -96,19 +92,8 @@ void ibutton_worker_stop(iButtonWorker* worker) {
|
||||
}
|
||||
|
||||
void ibutton_worker_free(iButtonWorker* worker) {
|
||||
ibutton_writer_free(worker->writer);
|
||||
|
||||
onewire_slave_free(worker->slave);
|
||||
|
||||
onewire_host_free(worker->host);
|
||||
onewire_device_free(worker->device);
|
||||
|
||||
protocol_dict_free(worker->protocols);
|
||||
|
||||
furi_message_queue_free(worker->messages);
|
||||
|
||||
furi_thread_free(worker->thread);
|
||||
free(worker->key_data);
|
||||
free(worker);
|
||||
}
|
||||
|
||||
@@ -137,7 +122,7 @@ void ibutton_worker_notify_emulate(iButtonWorker* worker) {
|
||||
}
|
||||
|
||||
void ibutton_worker_set_key_p(iButtonWorker* worker, iButtonKey* key) {
|
||||
worker->key_p = key;
|
||||
worker->key = key;
|
||||
}
|
||||
|
||||
static int32_t ibutton_worker_thread(void* thread_context) {
|
||||
@@ -154,25 +139,29 @@ static int32_t ibutton_worker_thread(void* thread_context) {
|
||||
if(status == FuriStatusOk) {
|
||||
switch(message.type) {
|
||||
case iButtonMessageEnd:
|
||||
ibutton_worker_switch_mode(worker, iButtonWorkerIdle);
|
||||
ibutton_worker_switch_mode(worker, iButtonWorkerModeIdle);
|
||||
ibutton_worker_set_key_p(worker, NULL);
|
||||
running = false;
|
||||
break;
|
||||
case iButtonMessageStop:
|
||||
ibutton_worker_switch_mode(worker, iButtonWorkerIdle);
|
||||
ibutton_worker_switch_mode(worker, iButtonWorkerModeIdle);
|
||||
ibutton_worker_set_key_p(worker, NULL);
|
||||
break;
|
||||
case iButtonMessageRead:
|
||||
ibutton_worker_set_key_p(worker, message.data.key);
|
||||
ibutton_worker_switch_mode(worker, iButtonWorkerRead);
|
||||
ibutton_worker_switch_mode(worker, iButtonWorkerModeRead);
|
||||
break;
|
||||
case iButtonMessageWrite:
|
||||
case iButtonMessageWriteBlank:
|
||||
ibutton_worker_set_key_p(worker, message.data.key);
|
||||
ibutton_worker_switch_mode(worker, iButtonWorkerWrite);
|
||||
ibutton_worker_switch_mode(worker, iButtonWorkerModeWriteBlank);
|
||||
break;
|
||||
case iButtonMessageWriteCopy:
|
||||
ibutton_worker_set_key_p(worker, message.data.key);
|
||||
ibutton_worker_switch_mode(worker, iButtonWorkerModeWriteCopy);
|
||||
break;
|
||||
case iButtonMessageEmulate:
|
||||
ibutton_worker_set_key_p(worker, message.data.key);
|
||||
ibutton_worker_switch_mode(worker, iButtonWorkerEmulate);
|
||||
ibutton_worker_switch_mode(worker, iButtonWorkerModeEmulate);
|
||||
break;
|
||||
case iButtonMessageNotifyEmulate:
|
||||
if(worker->emulate_cb) {
|
||||
@@ -5,7 +5,9 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ibutton_key.h"
|
||||
#include "ibutton_protocols.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -28,7 +30,7 @@ typedef struct iButtonWorker iButtonWorker;
|
||||
* Allocate ibutton worker
|
||||
* @return iButtonWorker*
|
||||
*/
|
||||
iButtonWorker* ibutton_worker_alloc();
|
||||
iButtonWorker* ibutton_worker_alloc(iButtonProtocols* protocols);
|
||||
|
||||
/**
|
||||
* Free ibutton worker
|
||||
@@ -78,11 +80,18 @@ void ibutton_worker_write_set_callback(
|
||||
void* context);
|
||||
|
||||
/**
|
||||
* Start write mode
|
||||
* Start write blank mode
|
||||
* @param worker
|
||||
* @param key
|
||||
*/
|
||||
void ibutton_worker_write_start(iButtonWorker* worker, iButtonKey* key);
|
||||
void ibutton_worker_write_blank_start(iButtonWorker* worker, iButtonKey* key);
|
||||
|
||||
/**
|
||||
* Start write copy mode
|
||||
* @param worker
|
||||
* @param key
|
||||
*/
|
||||
void ibutton_worker_write_copy_start(iButtonWorker* worker, iButtonKey* key);
|
||||
|
||||
/**
|
||||
* Set "emulate success" callback
|
||||
@@ -5,13 +5,11 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <core/thread.h>
|
||||
#include <core/message_queue.h>
|
||||
|
||||
#include "ibutton_worker.h"
|
||||
#include "ibutton_writer.h"
|
||||
#include "../one_wire_host.h"
|
||||
#include "../one_wire_slave.h"
|
||||
#include "../one_wire_device.h"
|
||||
#include <toolbox/protocols/protocol_dict.h>
|
||||
#include "protocols/ibutton_protocols.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -25,19 +23,16 @@ typedef struct {
|
||||
} iButtonWorkerModeType;
|
||||
|
||||
typedef enum {
|
||||
iButtonWorkerIdle = 0,
|
||||
iButtonWorkerRead = 1,
|
||||
iButtonWorkerWrite = 2,
|
||||
iButtonWorkerEmulate = 3,
|
||||
iButtonWorkerModeIdle,
|
||||
iButtonWorkerModeRead,
|
||||
iButtonWorkerModeWriteBlank,
|
||||
iButtonWorkerModeWriteCopy,
|
||||
iButtonWorkerModeEmulate,
|
||||
} iButtonWorkerMode;
|
||||
|
||||
struct iButtonWorker {
|
||||
iButtonKey* key_p;
|
||||
uint8_t* key_data;
|
||||
OneWireHost* host;
|
||||
OneWireSlave* slave;
|
||||
OneWireDevice* device;
|
||||
iButtonWriter* writer;
|
||||
iButtonKey* key;
|
||||
iButtonProtocols* protocols;
|
||||
iButtonWorkerMode mode_index;
|
||||
FuriMessageQueue* messages;
|
||||
FuriThread* thread;
|
||||
@@ -45,10 +40,8 @@ struct iButtonWorker {
|
||||
iButtonWorkerReadCallback read_cb;
|
||||
iButtonWorkerWriteCallback write_cb;
|
||||
iButtonWorkerEmulateCallback emulate_cb;
|
||||
void* cb_ctx;
|
||||
|
||||
ProtocolDict* protocols;
|
||||
iButtonProtocol protocol_to_encode;
|
||||
void* cb_ctx;
|
||||
};
|
||||
|
||||
extern const iButtonWorkerModeType ibutton_worker_modes[];
|
||||
@@ -0,0 +1,153 @@
|
||||
#include "ibutton_worker_i.h"
|
||||
|
||||
#include <core/check.h>
|
||||
|
||||
#include <furi_hal_rfid.h>
|
||||
#include <furi_hal_power.h>
|
||||
|
||||
#include "ibutton_protocols.h"
|
||||
|
||||
static void ibutton_worker_mode_idle_start(iButtonWorker* worker);
|
||||
static void ibutton_worker_mode_idle_tick(iButtonWorker* worker);
|
||||
static void ibutton_worker_mode_idle_stop(iButtonWorker* worker);
|
||||
|
||||
static void ibutton_worker_mode_emulate_start(iButtonWorker* worker);
|
||||
static void ibutton_worker_mode_emulate_tick(iButtonWorker* worker);
|
||||
static void ibutton_worker_mode_emulate_stop(iButtonWorker* worker);
|
||||
|
||||
static void ibutton_worker_mode_read_start(iButtonWorker* worker);
|
||||
static void ibutton_worker_mode_read_tick(iButtonWorker* worker);
|
||||
static void ibutton_worker_mode_read_stop(iButtonWorker* worker);
|
||||
|
||||
static void ibutton_worker_mode_write_common_start(iButtonWorker* worker);
|
||||
static void ibutton_worker_mode_write_blank_tick(iButtonWorker* worker);
|
||||
static void ibutton_worker_mode_write_copy_tick(iButtonWorker* worker);
|
||||
static void ibutton_worker_mode_write_common_stop(iButtonWorker* worker);
|
||||
|
||||
const iButtonWorkerModeType ibutton_worker_modes[] = {
|
||||
{
|
||||
.quant = FuriWaitForever,
|
||||
.start = ibutton_worker_mode_idle_start,
|
||||
.tick = ibutton_worker_mode_idle_tick,
|
||||
.stop = ibutton_worker_mode_idle_stop,
|
||||
},
|
||||
{
|
||||
.quant = 100,
|
||||
.start = ibutton_worker_mode_read_start,
|
||||
.tick = ibutton_worker_mode_read_tick,
|
||||
.stop = ibutton_worker_mode_read_stop,
|
||||
},
|
||||
{
|
||||
.quant = 1000,
|
||||
.start = ibutton_worker_mode_write_common_start,
|
||||
.tick = ibutton_worker_mode_write_blank_tick,
|
||||
.stop = ibutton_worker_mode_write_common_stop,
|
||||
},
|
||||
{
|
||||
.quant = 1000,
|
||||
.start = ibutton_worker_mode_write_common_start,
|
||||
.tick = ibutton_worker_mode_write_copy_tick,
|
||||
.stop = ibutton_worker_mode_write_common_stop,
|
||||
},
|
||||
{
|
||||
.quant = 1000,
|
||||
.start = ibutton_worker_mode_emulate_start,
|
||||
.tick = ibutton_worker_mode_emulate_tick,
|
||||
.stop = ibutton_worker_mode_emulate_stop,
|
||||
},
|
||||
};
|
||||
|
||||
/*********************** IDLE ***********************/
|
||||
|
||||
void ibutton_worker_mode_idle_start(iButtonWorker* worker) {
|
||||
UNUSED(worker);
|
||||
}
|
||||
|
||||
void ibutton_worker_mode_idle_tick(iButtonWorker* worker) {
|
||||
UNUSED(worker);
|
||||
}
|
||||
|
||||
void ibutton_worker_mode_idle_stop(iButtonWorker* worker) {
|
||||
UNUSED(worker);
|
||||
}
|
||||
|
||||
/*********************** READ ***********************/
|
||||
|
||||
void ibutton_worker_mode_read_start(iButtonWorker* worker) {
|
||||
UNUSED(worker);
|
||||
furi_hal_power_enable_otg();
|
||||
}
|
||||
|
||||
void ibutton_worker_mode_read_tick(iButtonWorker* worker) {
|
||||
if(ibutton_protocols_read(worker->protocols, worker->key)) {
|
||||
if(worker->read_cb != NULL) {
|
||||
worker->read_cb(worker->cb_ctx);
|
||||
}
|
||||
|
||||
ibutton_worker_switch_mode(worker, iButtonWorkerModeIdle);
|
||||
}
|
||||
}
|
||||
|
||||
void ibutton_worker_mode_read_stop(iButtonWorker* worker) {
|
||||
UNUSED(worker);
|
||||
furi_hal_power_disable_otg();
|
||||
}
|
||||
|
||||
/*********************** EMULATE ***********************/
|
||||
|
||||
void ibutton_worker_mode_emulate_start(iButtonWorker* worker) {
|
||||
furi_assert(worker->key);
|
||||
|
||||
furi_hal_rfid_pins_reset();
|
||||
furi_hal_rfid_pin_pull_pulldown();
|
||||
|
||||
ibutton_protocols_emulate_start(worker->protocols, worker->key);
|
||||
}
|
||||
|
||||
void ibutton_worker_mode_emulate_tick(iButtonWorker* worker) {
|
||||
UNUSED(worker);
|
||||
}
|
||||
|
||||
void ibutton_worker_mode_emulate_stop(iButtonWorker* worker) {
|
||||
furi_assert(worker->key);
|
||||
|
||||
ibutton_protocols_emulate_stop(worker->protocols, worker->key);
|
||||
|
||||
furi_hal_rfid_pins_reset();
|
||||
}
|
||||
|
||||
/*********************** WRITE ***********************/
|
||||
|
||||
void ibutton_worker_mode_write_common_start(iButtonWorker* worker) { //-V524
|
||||
UNUSED(worker);
|
||||
furi_hal_power_enable_otg();
|
||||
}
|
||||
|
||||
void ibutton_worker_mode_write_blank_tick(iButtonWorker* worker) {
|
||||
furi_assert(worker->key);
|
||||
|
||||
const bool success = ibutton_protocols_write_blank(worker->protocols, worker->key);
|
||||
// TODO: pass a proper result to the callback
|
||||
const iButtonWorkerWriteResult result = success ? iButtonWorkerWriteOK :
|
||||
iButtonWorkerWriteNoDetect;
|
||||
if(worker->write_cb != NULL) {
|
||||
worker->write_cb(worker->cb_ctx, result);
|
||||
}
|
||||
}
|
||||
|
||||
void ibutton_worker_mode_write_copy_tick(iButtonWorker* worker) {
|
||||
furi_assert(worker->key);
|
||||
|
||||
const bool success = ibutton_protocols_write_copy(worker->protocols, worker->key);
|
||||
// TODO: pass a proper result to the callback
|
||||
const iButtonWorkerWriteResult result = success ? iButtonWorkerWriteOK :
|
||||
iButtonWorkerWriteNoDetect;
|
||||
if(worker->write_cb != NULL) {
|
||||
worker->write_cb(worker->cb_ctx, result);
|
||||
}
|
||||
}
|
||||
|
||||
void ibutton_worker_mode_write_common_stop(iButtonWorker* worker) { //-V524
|
||||
UNUSED(worker);
|
||||
furi_hal_power_disable_otg();
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
#include "rw1990.h"
|
||||
|
||||
#include <core/kernel.h>
|
||||
|
||||
#define RW1990_1_CMD_WRITE_RECORD_FLAG 0xD1
|
||||
#define RW1990_1_CMD_READ_RECORD_FLAG 0xB5
|
||||
#define RW1990_1_CMD_WRITE_ROM 0xD5
|
||||
|
||||
#define RW1990_2_CMD_WRITE_RECORD_FLAG 0x1D
|
||||
#define RW1990_2_CMD_READ_RECORD_FLAG 0x1E
|
||||
#define RW1990_2_CMD_WRITE_ROM 0xD5
|
||||
|
||||
#define DS1990_CMD_READ_ROM 0x33
|
||||
|
||||
static void rw1990_write_byte(OneWireHost* host, uint8_t value) {
|
||||
for(uint8_t bitMask = 0x01; bitMask; bitMask <<= 1) {
|
||||
onewire_host_write_bit(host, (bool)(bitMask & value));
|
||||
furi_delay_us(5000);
|
||||
}
|
||||
}
|
||||
|
||||
static bool rw1990_read_and_compare(OneWireHost* host, const uint8_t* data, size_t data_size) {
|
||||
bool success = false;
|
||||
|
||||
if(onewire_host_reset(host)) {
|
||||
success = true;
|
||||
onewire_host_write(host, DS1990_CMD_READ_ROM);
|
||||
|
||||
for(size_t i = 0; i < data_size; ++i) {
|
||||
if(data[i] != onewire_host_read(host)) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool rw1990_write_v1(OneWireHost* host, const uint8_t* data, size_t data_size) {
|
||||
// Unlock sequence
|
||||
onewire_host_reset(host);
|
||||
onewire_host_write(host, RW1990_1_CMD_WRITE_RECORD_FLAG);
|
||||
furi_delay_us(10);
|
||||
|
||||
onewire_host_write_bit(host, false);
|
||||
furi_delay_us(5000);
|
||||
|
||||
// Write data
|
||||
onewire_host_reset(host);
|
||||
onewire_host_write(host, RW1990_1_CMD_WRITE_ROM);
|
||||
|
||||
for(size_t i = 0; i < data_size; ++i) {
|
||||
// inverted key for RW1990.1
|
||||
rw1990_write_byte(host, ~(data[i]));
|
||||
furi_delay_us(30000);
|
||||
}
|
||||
|
||||
// Lock sequence
|
||||
onewire_host_write(host, RW1990_1_CMD_WRITE_RECORD_FLAG);
|
||||
|
||||
onewire_host_write_bit(host, true);
|
||||
furi_delay_us(10000);
|
||||
|
||||
// TODO: Better error handling
|
||||
return rw1990_read_and_compare(host, data, data_size);
|
||||
}
|
||||
|
||||
bool rw1990_write_v2(OneWireHost* host, const uint8_t* data, size_t data_size) {
|
||||
// Unlock sequence
|
||||
onewire_host_reset(host);
|
||||
onewire_host_write(host, RW1990_2_CMD_WRITE_RECORD_FLAG);
|
||||
furi_delay_us(10);
|
||||
|
||||
onewire_host_write_bit(host, true);
|
||||
furi_delay_us(5000);
|
||||
|
||||
// Write data
|
||||
onewire_host_reset(host);
|
||||
onewire_host_write(host, RW1990_2_CMD_WRITE_ROM);
|
||||
|
||||
for(size_t i = 0; i < data_size; ++i) {
|
||||
rw1990_write_byte(host, data[i]);
|
||||
furi_delay_us(30000);
|
||||
}
|
||||
|
||||
// Lock sequence
|
||||
onewire_host_write(host, RW1990_2_CMD_WRITE_RECORD_FLAG);
|
||||
|
||||
onewire_host_write_bit(host, false);
|
||||
furi_delay_us(10000);
|
||||
|
||||
// TODO: Better error handling
|
||||
return rw1990_read_and_compare(host, data, data_size);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <one_wire/one_wire_host.h>
|
||||
|
||||
bool rw1990_write_v1(OneWireHost* host, const uint8_t* data, size_t data_size);
|
||||
|
||||
bool rw1990_write_v2(OneWireHost* host, const uint8_t* data, size_t data_size);
|
||||
@@ -0,0 +1,42 @@
|
||||
#include "tm2004.h"
|
||||
|
||||
#include <core/kernel.h>
|
||||
|
||||
#define TM2004_CMD_READ_STATUS 0xAA
|
||||
#define TM2004_CMD_READ_MEMORY 0xF0
|
||||
#define TM2004_CMD_WRITE_ROM 0x3C
|
||||
#define TM2004_CMD_FINALIZATION 0x35
|
||||
#define TM2004_ANSWER_READ_MEMORY 0xF5
|
||||
|
||||
bool tm2004_write(OneWireHost* host, const uint8_t* data, size_t data_size) {
|
||||
onewire_host_reset(host);
|
||||
onewire_host_write(host, TM2004_CMD_WRITE_ROM);
|
||||
// Starting writing from address 0x0000
|
||||
onewire_host_write(host, 0x00);
|
||||
onewire_host_write(host, 0x00);
|
||||
|
||||
size_t i;
|
||||
for(i = 0; i < data_size; ++i) {
|
||||
uint8_t answer;
|
||||
|
||||
onewire_host_write(host, data[i]);
|
||||
answer = onewire_host_read(host);
|
||||
// TODO: check answer CRC
|
||||
|
||||
// pulse indicating that data is correct
|
||||
furi_delay_us(600);
|
||||
onewire_host_write_bit(host, true);
|
||||
furi_delay_us(50000);
|
||||
|
||||
// read written key byte
|
||||
answer = onewire_host_read(host); //-V519
|
||||
|
||||
// check that written and read are same
|
||||
if(data[i] != answer) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Better error handling
|
||||
return i == data_size;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <one_wire/one_wire_host.h>
|
||||
|
||||
bool tm2004_write(OneWireHost* host, const uint8_t* data, size_t data_size);
|
||||
@@ -0,0 +1,261 @@
|
||||
#include "dallas_common.h"
|
||||
|
||||
#include <core/common_defines.h>
|
||||
#include <one_wire/maxim_crc.h>
|
||||
|
||||
#define BITS_IN_BYTE 8U
|
||||
|
||||
#define DALLAS_COMMON_ROM_DATA_KEY_V1 "Data"
|
||||
#define DALLAS_COMMON_ROM_DATA_KEY_V2 "Rom Data"
|
||||
|
||||
#define DALLAS_COMMON_COPY_SCRATCH_MIN_TIMEOUT_US 5U
|
||||
#define DALLAS_COMMON_COPY_SCRATCH_POLL_COUNT 20U
|
||||
|
||||
#define DALLAS_COMMON_END_ADDRESS_MASK 0x01F
|
||||
#define DALLAS_COMMON_STATUS_FLAG_PF (1U << 5)
|
||||
#define DALLAS_COMMON_STATUS_FLAG_OF (1U << 6)
|
||||
#define DALLAS_COMMON_STATUS_FLAG_AA (1U << 7)
|
||||
|
||||
#define DALLAS_COMMON_BRIEF_HEAD_COUNT 4U
|
||||
#define DALLAS_COMMON_BRIEF_TAIL_COUNT 3U
|
||||
|
||||
#define BITS_IN_BYTE 8U
|
||||
#define BITS_IN_KBIT 1024U
|
||||
#define BITS_IN_MBIT (BITS_IN_KBIT * 1024U)
|
||||
|
||||
bool dallas_common_skip_rom(OneWireHost* host) {
|
||||
onewire_host_write(host, DALLAS_COMMON_CMD_SKIP_ROM);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool dallas_common_read_rom(OneWireHost* host, DallasCommonRomData* rom_data) {
|
||||
onewire_host_write(host, DALLAS_COMMON_CMD_READ_ROM);
|
||||
onewire_host_read_bytes(host, rom_data->bytes, sizeof(DallasCommonRomData));
|
||||
|
||||
return dallas_common_is_valid_crc(rom_data);
|
||||
}
|
||||
|
||||
bool dallas_common_write_scratchpad(
|
||||
OneWireHost* host,
|
||||
uint16_t address,
|
||||
const uint8_t* data,
|
||||
size_t data_size) {
|
||||
onewire_host_write(host, DALLAS_COMMON_CMD_WRITE_SCRATCH);
|
||||
onewire_host_write(host, (uint8_t)address);
|
||||
onewire_host_write(host, (uint8_t)(address >> BITS_IN_BYTE));
|
||||
|
||||
onewire_host_write_bytes(host, data, data_size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool dallas_common_read_scratchpad(
|
||||
OneWireHost* host,
|
||||
DallasCommonAddressRegs* regs,
|
||||
uint8_t* data,
|
||||
size_t data_size) {
|
||||
onewire_host_write(host, DALLAS_COMMON_CMD_READ_SCRATCH);
|
||||
onewire_host_read_bytes(host, regs->bytes, sizeof(DallasCommonAddressRegs));
|
||||
onewire_host_read_bytes(host, data, data_size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool dallas_common_copy_scratchpad(
|
||||
OneWireHost* host,
|
||||
const DallasCommonAddressRegs* regs,
|
||||
uint32_t timeout_us) {
|
||||
onewire_host_write(host, DALLAS_COMMON_CMD_COPY_SCRATCH);
|
||||
onewire_host_write_bytes(host, regs->bytes, sizeof(DallasCommonAddressRegs));
|
||||
|
||||
const uint32_t poll_delay =
|
||||
MAX(timeout_us / DALLAS_COMMON_COPY_SCRATCH_POLL_COUNT,
|
||||
DALLAS_COMMON_COPY_SCRATCH_MIN_TIMEOUT_US);
|
||||
|
||||
uint32_t time_elapsed;
|
||||
for(time_elapsed = 0; time_elapsed < timeout_us; time_elapsed += poll_delay) {
|
||||
if(!onewire_host_read_bit(host)) break;
|
||||
furi_delay_us(poll_delay);
|
||||
}
|
||||
|
||||
return time_elapsed < timeout_us;
|
||||
}
|
||||
|
||||
bool dallas_common_read_mem(OneWireHost* host, uint16_t address, uint8_t* data, size_t data_size) {
|
||||
onewire_host_write(host, DALLAS_COMMON_CMD_READ_MEM);
|
||||
|
||||
onewire_host_write(host, (uint8_t)address);
|
||||
onewire_host_write(host, (uint8_t)(address >> BITS_IN_BYTE));
|
||||
|
||||
onewire_host_read_bytes(host, data, (uint16_t)data_size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool dallas_common_write_mem(
|
||||
OneWireHost* host,
|
||||
uint32_t timeout_us,
|
||||
size_t page_size,
|
||||
const uint8_t* data,
|
||||
size_t data_size) {
|
||||
// Data size must be a multiple of page size
|
||||
furi_check(data_size % page_size == 0);
|
||||
|
||||
DallasCommonAddressRegs regs;
|
||||
uint8_t* scratch = malloc(page_size);
|
||||
|
||||
size_t i;
|
||||
for(i = 0; i < data_size; i += page_size) {
|
||||
const uint8_t* data_ptr = data + i;
|
||||
|
||||
// Write scratchpad with the next page value
|
||||
if(!onewire_host_reset(host)) break;
|
||||
if(!dallas_common_skip_rom(host)) break;
|
||||
if(!dallas_common_write_scratchpad(host, i, data_ptr, page_size)) break;
|
||||
|
||||
// Read back the scratchpad contents and address registers
|
||||
if(!onewire_host_reset(host)) break;
|
||||
if(!dallas_common_skip_rom(host)) break;
|
||||
if(!dallas_common_read_scratchpad(host, ®s, scratch, page_size)) break;
|
||||
|
||||
// Verify scratchpad contents
|
||||
if(memcmp(data_ptr, scratch, page_size) != 0) break;
|
||||
|
||||
// Write scratchpad to internal memory
|
||||
if(!onewire_host_reset(host)) break;
|
||||
if(!dallas_common_skip_rom(host)) break;
|
||||
if(!dallas_common_copy_scratchpad(host, ®s, timeout_us)) break;
|
||||
|
||||
// Read back the address registers again
|
||||
if(!onewire_host_reset(host)) break;
|
||||
if(!dallas_common_skip_rom(host)) break;
|
||||
if(!dallas_common_read_scratchpad(host, ®s, scratch, 0)) break;
|
||||
|
||||
// Check if AA flag is set
|
||||
if(!(regs.fields.status & DALLAS_COMMON_STATUS_FLAG_AA)) break;
|
||||
}
|
||||
|
||||
free(scratch);
|
||||
|
||||
return i == data_size;
|
||||
}
|
||||
|
||||
bool dallas_common_emulate_search_rom(OneWireSlave* bus, const DallasCommonRomData* rom_data) {
|
||||
for(size_t i = 0; i < sizeof(DallasCommonRomData); i++) {
|
||||
for(size_t j = 0; j < BITS_IN_BYTE; j++) {
|
||||
bool bit = (rom_data->bytes[i] >> j) & 0x01;
|
||||
|
||||
if(!onewire_slave_send_bit(bus, bit)) return false;
|
||||
if(!onewire_slave_send_bit(bus, !bit)) return false;
|
||||
|
||||
onewire_slave_receive_bit(bus);
|
||||
// TODO: check for errors and return if any
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool dallas_common_emulate_read_rom(OneWireSlave* bus, const DallasCommonRomData* rom_data) {
|
||||
return onewire_slave_send(bus, rom_data->bytes, sizeof(DallasCommonRomData));
|
||||
}
|
||||
|
||||
bool dallas_common_emulate_read_mem(OneWireSlave* bus, const uint8_t* data, size_t data_size) {
|
||||
bool success = false;
|
||||
|
||||
union {
|
||||
uint8_t bytes[sizeof(uint16_t)];
|
||||
uint16_t word;
|
||||
} address;
|
||||
|
||||
do {
|
||||
if(!onewire_slave_receive(bus, address.bytes, sizeof(address))) break;
|
||||
if(address.word >= data_size) break;
|
||||
if(!onewire_slave_send(bus, data + address.word, data_size - address.word)) break;
|
||||
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool dallas_common_save_rom_data(FlipperFormat* ff, const DallasCommonRomData* rom_data) {
|
||||
return flipper_format_write_hex(
|
||||
ff, DALLAS_COMMON_ROM_DATA_KEY_V2, rom_data->bytes, sizeof(DallasCommonRomData));
|
||||
}
|
||||
|
||||
bool dallas_common_load_rom_data(
|
||||
FlipperFormat* ff,
|
||||
uint32_t format_version,
|
||||
DallasCommonRomData* rom_data) {
|
||||
switch(format_version) {
|
||||
case 1:
|
||||
return flipper_format_read_hex(
|
||||
ff, DALLAS_COMMON_ROM_DATA_KEY_V1, rom_data->bytes, sizeof(DallasCommonRomData));
|
||||
case 2:
|
||||
return flipper_format_read_hex(
|
||||
ff, DALLAS_COMMON_ROM_DATA_KEY_V2, rom_data->bytes, sizeof(DallasCommonRomData));
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool dallas_common_is_valid_crc(const DallasCommonRomData* rom_data) {
|
||||
const uint8_t crc_calculated =
|
||||
maxim_crc8(rom_data->bytes, sizeof(DallasCommonRomData) - 1, MAXIM_CRC8_INIT);
|
||||
const uint8_t crc_received = rom_data->fields.checksum;
|
||||
|
||||
return crc_calculated == crc_received;
|
||||
}
|
||||
|
||||
void dallas_common_render_brief_data(
|
||||
FuriString* result,
|
||||
const DallasCommonRomData* rom_data,
|
||||
const uint8_t* mem_data,
|
||||
size_t mem_size,
|
||||
const char* mem_name) {
|
||||
for(size_t i = 0; i < sizeof(rom_data->bytes); ++i) {
|
||||
furi_string_cat_printf(result, "%02X ", rom_data->bytes[i]);
|
||||
}
|
||||
|
||||
const char* size_prefix = "";
|
||||
size_t mem_size_bits = mem_size * BITS_IN_BYTE;
|
||||
|
||||
if(mem_size_bits >= BITS_IN_MBIT) {
|
||||
size_prefix = "M";
|
||||
mem_size_bits /= BITS_IN_MBIT;
|
||||
} else if(mem_size_bits >= BITS_IN_KBIT) {
|
||||
size_prefix = "K";
|
||||
mem_size_bits /= BITS_IN_KBIT;
|
||||
}
|
||||
|
||||
furi_string_cat_printf(
|
||||
result, "\nInternal %s: %zu %sbit\n", mem_name, mem_size_bits, size_prefix);
|
||||
|
||||
for(size_t i = 0; i < DALLAS_COMMON_BRIEF_HEAD_COUNT; ++i) {
|
||||
furi_string_cat_printf(result, "%02X ", mem_data[i]);
|
||||
}
|
||||
|
||||
furi_string_cat_printf(result, "[ . . . ]");
|
||||
|
||||
for(size_t i = mem_size - DALLAS_COMMON_BRIEF_TAIL_COUNT; i < mem_size; ++i) {
|
||||
furi_string_cat_printf(result, " %02X", mem_data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void dallas_common_render_crc_error(FuriString* result, const DallasCommonRomData* rom_data) {
|
||||
furi_string_set(result, "CRC Error\n");
|
||||
|
||||
const size_t data_size = sizeof(DallasCommonRomData);
|
||||
|
||||
for(size_t i = 0; i < data_size; ++i) {
|
||||
furi_string_cat_printf(result, (i < data_size - 1) ? "%02X " : "%02X", rom_data->bytes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void dallas_common_apply_edits(DallasCommonRomData* rom_data, uint8_t family_code) {
|
||||
rom_data->fields.family_code = family_code;
|
||||
const uint8_t crc =
|
||||
maxim_crc8(rom_data->bytes, sizeof(DallasCommonRomData) - 1, MAXIM_CRC8_INIT);
|
||||
rom_data->fields.checksum = crc;
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
#pragma once
|
||||
|
||||
#include <one_wire/one_wire_host.h>
|
||||
#include <one_wire/one_wire_slave.h>
|
||||
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#define DALLAS_COMMON_MANUFACTURER_NAME "Dallas"
|
||||
|
||||
#define DALLAS_COMMON_CMD_READ_ROM 0x33U
|
||||
#define DALLAS_COMMON_CMD_MATCH_ROM 0x55U
|
||||
#define DALLAS_COMMON_CMD_SKIP_ROM 0xCCU
|
||||
#define DALLAS_COMMON_CMD_COND_SEARCH 0xECU
|
||||
#define DALLAS_COMMON_CMD_SEARCH_ROM 0xF0U
|
||||
|
||||
#define DALLAS_COMMON_CMD_READ_SCRATCH 0xAAU
|
||||
#define DALLAS_COMMON_CMD_WRITE_SCRATCH 0x0FU
|
||||
#define DALLAS_COMMON_CMD_COPY_SCRATCH 0x55U
|
||||
|
||||
#define DALLAS_COMMON_CMD_READ_MEM 0xF0U
|
||||
|
||||
#define DALLAS_COMMON_CMD_OVERDRIVE_SKIP_ROM 0x3CU
|
||||
#define DALLAS_COMMON_CMD_OVERDRIVE_MATCH_ROM 0x69U
|
||||
|
||||
typedef enum {
|
||||
DallasCommonCommandStateIdle,
|
||||
DallasCommonCommandStateRomCmd,
|
||||
DallasCommonCommandStateMemCmd,
|
||||
} DallasCommonCommandState;
|
||||
|
||||
typedef union {
|
||||
struct {
|
||||
uint8_t family_code;
|
||||
uint8_t serial_number[6];
|
||||
uint8_t checksum;
|
||||
} fields;
|
||||
uint8_t bytes[8];
|
||||
} DallasCommonRomData;
|
||||
|
||||
typedef union {
|
||||
struct {
|
||||
uint8_t address_lo;
|
||||
uint8_t address_hi;
|
||||
uint8_t status;
|
||||
} fields;
|
||||
uint8_t bytes[3];
|
||||
} DallasCommonAddressRegs;
|
||||
|
||||
/* Standard(ish) iButton commands */
|
||||
bool dallas_common_skip_rom(OneWireHost* host);
|
||||
|
||||
bool dallas_common_read_rom(OneWireHost* host, DallasCommonRomData* rom_data);
|
||||
|
||||
bool dallas_common_write_scratchpad(
|
||||
OneWireHost* host,
|
||||
uint16_t address,
|
||||
const uint8_t* data,
|
||||
size_t data_size);
|
||||
|
||||
bool dallas_common_read_scratchpad(
|
||||
OneWireHost* host,
|
||||
DallasCommonAddressRegs* regs,
|
||||
uint8_t* data,
|
||||
size_t data_size);
|
||||
|
||||
bool dallas_common_copy_scratchpad(
|
||||
OneWireHost* host,
|
||||
const DallasCommonAddressRegs* regs,
|
||||
uint32_t timeout_us);
|
||||
|
||||
bool dallas_common_read_mem(OneWireHost* host, uint16_t address, uint8_t* data, size_t data_size);
|
||||
|
||||
/* Combined operations */
|
||||
bool dallas_common_write_mem(
|
||||
OneWireHost* host,
|
||||
uint32_t timeout_us,
|
||||
size_t page_size,
|
||||
const uint8_t* data,
|
||||
size_t data_size);
|
||||
|
||||
/* Emulation */
|
||||
bool dallas_common_emulate_search_rom(OneWireSlave* bus, const DallasCommonRomData* rom_data);
|
||||
|
||||
bool dallas_common_emulate_read_rom(OneWireSlave* bus, const DallasCommonRomData* rom_data);
|
||||
|
||||
bool dallas_common_emulate_read_mem(OneWireSlave* bus, const uint8_t* data, size_t data_size);
|
||||
|
||||
/* Save & Load */
|
||||
bool dallas_common_save_rom_data(FlipperFormat* ff, const DallasCommonRomData* rom_data);
|
||||
|
||||
bool dallas_common_load_rom_data(
|
||||
FlipperFormat* ff,
|
||||
uint32_t format_version,
|
||||
DallasCommonRomData* rom_data);
|
||||
|
||||
/* Miscellaneous */
|
||||
bool dallas_common_is_valid_crc(const DallasCommonRomData* rom_data);
|
||||
|
||||
void dallas_common_render_brief_data(
|
||||
FuriString* result,
|
||||
const DallasCommonRomData* rom_data,
|
||||
const uint8_t* mem_data,
|
||||
size_t mem_size,
|
||||
const char* mem_name);
|
||||
|
||||
void dallas_common_render_crc_error(FuriString* result, const DallasCommonRomData* rom_data);
|
||||
|
||||
void dallas_common_apply_edits(DallasCommonRomData* rom_data, uint8_t family_code);
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include "../protocol_common_i.h"
|
||||
|
||||
#include <one_wire/one_wire_host.h>
|
||||
#include <one_wire/one_wire_slave.h>
|
||||
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
typedef bool (*iButtonProtocolDallasReadWriteFunc)(OneWireHost*, iButtonProtocolData*);
|
||||
typedef void (*iButtonProtocolDallasEmulateFunc)(OneWireSlave*, iButtonProtocolData*);
|
||||
typedef bool (*iButtonProtocolDallasSaveFunc)(FlipperFormat*, const iButtonProtocolData*);
|
||||
typedef bool (*iButtonProtocolDallasLoadFunc)(FlipperFormat*, uint32_t, iButtonProtocolData*);
|
||||
typedef void (*iButtonProtocolDallasRenderDataFunc)(FuriString*, const iButtonProtocolData*);
|
||||
typedef bool (*iButtonProtocolDallasIsValidFunc)(const iButtonProtocolData*);
|
||||
typedef void (
|
||||
*iButtonProtocolDallasGetEditableDataFunc)(iButtonEditableData*, iButtonProtocolData*);
|
||||
typedef void (*iButtonProtocolDallasApplyEditsFunc)(iButtonProtocolData*);
|
||||
|
||||
typedef struct {
|
||||
const uint8_t family_code;
|
||||
const uint32_t features;
|
||||
const size_t data_size;
|
||||
const char* manufacturer;
|
||||
const char* name;
|
||||
|
||||
iButtonProtocolDallasReadWriteFunc read;
|
||||
iButtonProtocolDallasReadWriteFunc write_blank;
|
||||
iButtonProtocolDallasReadWriteFunc write_copy;
|
||||
iButtonProtocolDallasEmulateFunc emulate;
|
||||
iButtonProtocolDallasSaveFunc save;
|
||||
iButtonProtocolDallasLoadFunc load;
|
||||
iButtonProtocolDallasRenderDataFunc render_data;
|
||||
iButtonProtocolDallasRenderDataFunc render_brief_data;
|
||||
iButtonProtocolDallasRenderDataFunc render_error;
|
||||
iButtonProtocolDallasIsValidFunc is_valid;
|
||||
iButtonProtocolDallasGetEditableDataFunc get_editable_data;
|
||||
iButtonProtocolDallasApplyEditsFunc apply_edits;
|
||||
} iButtonProtocolDallasBase;
|
||||
@@ -0,0 +1,276 @@
|
||||
#include "protocol_ds1971.h"
|
||||
|
||||
#include <core/core_defines.h>
|
||||
#include <toolbox/pretty_format.h>
|
||||
|
||||
#include "dallas_common.h"
|
||||
|
||||
#define DS1971_FAMILY_CODE 0x14U
|
||||
#define DS1971_FAMILY_NAME "DS1971"
|
||||
|
||||
#define DS1971_EEPROM_DATA_SIZE 32U
|
||||
#define DS1971_SRAM_PAGE_SIZE 32U
|
||||
#define DS1971_COPY_SCRATCH_DELAY_US 250U
|
||||
|
||||
#define DS1971_DATA_BYTE_COUNT 4U
|
||||
|
||||
#define DS1971_EEPROM_DATA_KEY "Eeprom Data"
|
||||
#define DS1971_MEMORY_TYPE "EEPROM"
|
||||
|
||||
#define DS1971_CMD_FINALIZATION 0xA5
|
||||
|
||||
typedef struct {
|
||||
OneWireSlave* bus;
|
||||
DallasCommonCommandState command_state;
|
||||
} DS1971ProtocolState;
|
||||
|
||||
typedef struct {
|
||||
DallasCommonRomData rom_data;
|
||||
uint8_t eeprom_data[DS1971_EEPROM_DATA_SIZE];
|
||||
DS1971ProtocolState state;
|
||||
} DS1971ProtocolData;
|
||||
|
||||
static bool dallas_ds1971_read(OneWireHost*, void*);
|
||||
static bool dallas_ds1971_write_copy(OneWireHost*, iButtonProtocolData*);
|
||||
static void dallas_ds1971_emulate(OneWireSlave*, iButtonProtocolData*);
|
||||
static bool dallas_ds1971_load(FlipperFormat*, uint32_t, iButtonProtocolData*);
|
||||
static bool dallas_ds1971_save(FlipperFormat*, const iButtonProtocolData*);
|
||||
static void dallas_ds1971_render_data(FuriString*, const iButtonProtocolData*);
|
||||
static void dallas_ds1971_render_brief_data(FuriString*, const iButtonProtocolData*);
|
||||
static void dallas_ds1971_render_error(FuriString*, const iButtonProtocolData*);
|
||||
static bool dallas_ds1971_is_data_valid(const iButtonProtocolData*);
|
||||
static void dallas_ds1971_get_editable_data(iButtonEditableData*, iButtonProtocolData*);
|
||||
static void dallas_ds1971_apply_edits(iButtonProtocolData*);
|
||||
static bool
|
||||
dallas_ds1971_read_mem(OneWireHost* host, uint8_t address, uint8_t* data, size_t data_size);
|
||||
static bool ds1971_emulate_read_mem(OneWireSlave* bus, const uint8_t* data, size_t data_size);
|
||||
|
||||
const iButtonProtocolDallasBase ibutton_protocol_ds1971 = {
|
||||
.family_code = DS1971_FAMILY_CODE,
|
||||
.features = iButtonProtocolFeatureExtData | iButtonProtocolFeatureWriteCopy,
|
||||
.data_size = sizeof(DS1971ProtocolData),
|
||||
.manufacturer = DALLAS_COMMON_MANUFACTURER_NAME,
|
||||
.name = DS1971_FAMILY_NAME,
|
||||
|
||||
.read = dallas_ds1971_read,
|
||||
.write_blank = NULL, // TODO: Implement writing to blank
|
||||
.write_copy = dallas_ds1971_write_copy,
|
||||
.emulate = dallas_ds1971_emulate,
|
||||
.save = dallas_ds1971_save,
|
||||
.load = dallas_ds1971_load,
|
||||
.render_data = dallas_ds1971_render_data,
|
||||
.render_brief_data = dallas_ds1971_render_brief_data,
|
||||
.render_error = dallas_ds1971_render_error,
|
||||
.is_valid = dallas_ds1971_is_data_valid,
|
||||
.get_editable_data = dallas_ds1971_get_editable_data,
|
||||
.apply_edits = dallas_ds1971_apply_edits,
|
||||
};
|
||||
|
||||
bool dallas_ds1971_read(OneWireHost* host, iButtonProtocolData* protocol_data) {
|
||||
DS1971ProtocolData* data = protocol_data;
|
||||
return onewire_host_reset(host) && dallas_common_read_rom(host, &data->rom_data) &&
|
||||
dallas_ds1971_read_mem(host, 0, data->eeprom_data, DS1971_EEPROM_DATA_SIZE);
|
||||
}
|
||||
|
||||
bool dallas_ds1971_write_copy(OneWireHost* host, iButtonProtocolData* protocol_data) {
|
||||
DS1971ProtocolData* data = protocol_data;
|
||||
|
||||
onewire_host_reset(host);
|
||||
onewire_host_write(host, DALLAS_COMMON_CMD_SKIP_ROM);
|
||||
// Starting writing from address 0x0000
|
||||
onewire_host_write(host, DALLAS_COMMON_CMD_WRITE_SCRATCH);
|
||||
onewire_host_write(host, 0x00);
|
||||
// Write data to scratchpad
|
||||
onewire_host_write_bytes(host, data->eeprom_data, DS1971_EEPROM_DATA_SIZE);
|
||||
|
||||
// Read data from scratchpad and verify
|
||||
bool pad_valid = false;
|
||||
if(onewire_host_reset(host)) {
|
||||
pad_valid = true;
|
||||
onewire_host_write(host, DALLAS_COMMON_CMD_SKIP_ROM);
|
||||
onewire_host_write(host, DALLAS_COMMON_CMD_READ_SCRATCH);
|
||||
onewire_host_write(host, 0x00);
|
||||
|
||||
for(size_t i = 0; i < DS1971_EEPROM_DATA_SIZE; ++i) {
|
||||
uint8_t scratch = onewire_host_read(host);
|
||||
if(data->eeprom_data[i] != scratch) {
|
||||
pad_valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy scratchpad to memory and confirm
|
||||
if(pad_valid) {
|
||||
if(onewire_host_reset(host)) {
|
||||
onewire_host_write(host, DALLAS_COMMON_CMD_SKIP_ROM);
|
||||
onewire_host_write(host, DALLAS_COMMON_CMD_COPY_SCRATCH);
|
||||
onewire_host_write(host, DS1971_CMD_FINALIZATION);
|
||||
|
||||
furi_delay_us(DS1971_COPY_SCRATCH_DELAY_US);
|
||||
}
|
||||
}
|
||||
|
||||
return pad_valid;
|
||||
}
|
||||
|
||||
static bool dallas_ds1971_reset_callback(bool is_short, void* context) {
|
||||
furi_assert(context);
|
||||
DS1971ProtocolData* data = context;
|
||||
|
||||
if(!is_short) {
|
||||
data->state.command_state = DallasCommonCommandStateIdle;
|
||||
onewire_slave_set_overdrive(data->state.bus, is_short);
|
||||
}
|
||||
|
||||
return !is_short;
|
||||
}
|
||||
|
||||
static bool dallas_ds1971_command_callback(uint8_t command, void* context) {
|
||||
furi_assert(context);
|
||||
DS1971ProtocolData* data = context;
|
||||
OneWireSlave* bus = data->state.bus;
|
||||
|
||||
switch(command) {
|
||||
case DALLAS_COMMON_CMD_SEARCH_ROM:
|
||||
if(data->state.command_state == DallasCommonCommandStateIdle) {
|
||||
data->state.command_state = DallasCommonCommandStateRomCmd;
|
||||
return dallas_common_emulate_search_rom(bus, &data->rom_data);
|
||||
|
||||
} else if(data->state.command_state == DallasCommonCommandStateRomCmd) {
|
||||
data->state.command_state = DallasCommonCommandStateMemCmd;
|
||||
ds1971_emulate_read_mem(bus, data->eeprom_data, DS1971_EEPROM_DATA_SIZE);
|
||||
return false;
|
||||
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
case DALLAS_COMMON_CMD_READ_ROM:
|
||||
if(data->state.command_state == DallasCommonCommandStateIdle) {
|
||||
data->state.command_state = DallasCommonCommandStateRomCmd;
|
||||
return dallas_common_emulate_read_rom(bus, &data->rom_data);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
case DALLAS_COMMON_CMD_SKIP_ROM:
|
||||
if(data->state.command_state == DallasCommonCommandStateIdle) {
|
||||
data->state.command_state = DallasCommonCommandStateRomCmd;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void dallas_ds1971_emulate(OneWireSlave* bus, iButtonProtocolData* protocol_data) {
|
||||
DS1971ProtocolData* data = protocol_data;
|
||||
data->state.bus = bus;
|
||||
|
||||
onewire_slave_set_reset_callback(bus, dallas_ds1971_reset_callback, protocol_data);
|
||||
onewire_slave_set_command_callback(bus, dallas_ds1971_command_callback, protocol_data);
|
||||
}
|
||||
|
||||
bool dallas_ds1971_load(
|
||||
FlipperFormat* ff,
|
||||
uint32_t format_version,
|
||||
iButtonProtocolData* protocol_data) {
|
||||
DS1971ProtocolData* data = protocol_data;
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
if(format_version < 2) break;
|
||||
if(!dallas_common_load_rom_data(ff, format_version, &data->rom_data)) break;
|
||||
if(!flipper_format_read_hex(
|
||||
ff, DS1971_EEPROM_DATA_KEY, data->eeprom_data, DS1971_EEPROM_DATA_SIZE))
|
||||
break;
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool dallas_ds1971_save(FlipperFormat* ff, const iButtonProtocolData* protocol_data) {
|
||||
const DS1971ProtocolData* data = protocol_data;
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
if(!dallas_common_save_rom_data(ff, &data->rom_data)) break;
|
||||
if(!flipper_format_write_hex(
|
||||
ff, DS1971_EEPROM_DATA_KEY, data->eeprom_data, DS1971_EEPROM_DATA_SIZE))
|
||||
break;
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void dallas_ds1971_render_data(FuriString* result, const iButtonProtocolData* protocol_data) {
|
||||
const DS1971ProtocolData* data = protocol_data;
|
||||
pretty_format_bytes_hex_canonical(
|
||||
result,
|
||||
DS1971_DATA_BYTE_COUNT,
|
||||
PRETTY_FORMAT_FONT_MONOSPACE,
|
||||
data->eeprom_data,
|
||||
DS1971_EEPROM_DATA_SIZE);
|
||||
}
|
||||
|
||||
void dallas_ds1971_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) {
|
||||
const DS1971ProtocolData* data = protocol_data;
|
||||
dallas_common_render_brief_data(
|
||||
result, &data->rom_data, data->eeprom_data, DS1971_EEPROM_DATA_SIZE, DS1971_MEMORY_TYPE);
|
||||
}
|
||||
|
||||
void dallas_ds1971_render_error(FuriString* result, const iButtonProtocolData* protocol_data) {
|
||||
const DS1971ProtocolData* data = protocol_data;
|
||||
|
||||
if(!dallas_common_is_valid_crc(&data->rom_data)) {
|
||||
dallas_common_render_crc_error(result, &data->rom_data);
|
||||
}
|
||||
}
|
||||
|
||||
bool dallas_ds1971_is_data_valid(const iButtonProtocolData* protocol_data) {
|
||||
const DS1971ProtocolData* data = protocol_data;
|
||||
return dallas_common_is_valid_crc(&data->rom_data);
|
||||
}
|
||||
|
||||
void dallas_ds1971_get_editable_data(
|
||||
iButtonEditableData* editable_data,
|
||||
iButtonProtocolData* protocol_data) {
|
||||
DS1971ProtocolData* data = protocol_data;
|
||||
editable_data->ptr = data->rom_data.bytes;
|
||||
editable_data->size = sizeof(DallasCommonRomData);
|
||||
}
|
||||
|
||||
void dallas_ds1971_apply_edits(iButtonProtocolData* protocol_data) {
|
||||
DS1971ProtocolData* data = protocol_data;
|
||||
dallas_common_apply_edits(&data->rom_data, DS1971_FAMILY_CODE);
|
||||
}
|
||||
|
||||
bool dallas_ds1971_read_mem(OneWireHost* host, uint8_t address, uint8_t* data, size_t data_size) {
|
||||
onewire_host_write(host, DALLAS_COMMON_CMD_READ_MEM);
|
||||
|
||||
onewire_host_write(host, address);
|
||||
onewire_host_read_bytes(host, data, (uint8_t)data_size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ds1971_emulate_read_mem(OneWireSlave* bus, const uint8_t* data, size_t data_size) {
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
uint8_t address;
|
||||
if(!onewire_slave_receive(bus, &address, sizeof(address))) break;
|
||||
if(address >= data_size) break;
|
||||
if(!onewire_slave_send(bus, data + address, data_size - address)) break;
|
||||
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
return success;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "protocol_dallas_base.h"
|
||||
|
||||
extern const iButtonProtocolDallasBase ibutton_protocol_ds1971;
|
||||
@@ -0,0 +1,152 @@
|
||||
#include "protocol_ds1990.h"
|
||||
|
||||
#include <core/string.h>
|
||||
#include <core/core_defines.h>
|
||||
|
||||
#include "dallas_common.h"
|
||||
|
||||
#include "../blanks/rw1990.h"
|
||||
#include "../blanks/tm2004.h"
|
||||
|
||||
#define DS1990_FAMILY_CODE 0x01U
|
||||
#define DS1990_FAMILY_NAME "DS1990"
|
||||
|
||||
#define DS1990_CMD_READ_ROM 0x0FU
|
||||
|
||||
typedef struct {
|
||||
OneWireSlave* bus;
|
||||
} DS1990ProtocolState;
|
||||
|
||||
typedef struct {
|
||||
DallasCommonRomData rom_data;
|
||||
DS1990ProtocolState state;
|
||||
} DS1990ProtocolData;
|
||||
|
||||
static bool dallas_ds1990_read(OneWireHost*, iButtonProtocolData*);
|
||||
static bool dallas_ds1990_write_blank(OneWireHost*, iButtonProtocolData*);
|
||||
static void dallas_ds1990_emulate(OneWireSlave*, iButtonProtocolData*);
|
||||
static bool dallas_ds1990_load(FlipperFormat*, uint32_t, iButtonProtocolData*);
|
||||
static bool dallas_ds1990_save(FlipperFormat*, const iButtonProtocolData*);
|
||||
static void dallas_ds1990_render_brief_data(FuriString*, const iButtonProtocolData*);
|
||||
static void dallas_ds1990_render_error(FuriString*, const iButtonProtocolData*);
|
||||
static bool dallas_ds1990_is_data_valid(const iButtonProtocolData*);
|
||||
static void dallas_ds1990_get_editable_data(iButtonEditableData*, iButtonProtocolData*);
|
||||
static void dallas_ds1990_apply_edits(iButtonProtocolData*);
|
||||
|
||||
const iButtonProtocolDallasBase ibutton_protocol_ds1990 = {
|
||||
.family_code = DS1990_FAMILY_CODE,
|
||||
.features = iButtonProtocolFeatureWriteBlank,
|
||||
.data_size = sizeof(DS1990ProtocolData),
|
||||
.manufacturer = DALLAS_COMMON_MANUFACTURER_NAME,
|
||||
.name = DS1990_FAMILY_NAME,
|
||||
|
||||
.read = dallas_ds1990_read,
|
||||
.write_blank = dallas_ds1990_write_blank,
|
||||
.write_copy = NULL, /* No data to write a copy */
|
||||
.emulate = dallas_ds1990_emulate,
|
||||
.save = dallas_ds1990_save,
|
||||
.load = dallas_ds1990_load,
|
||||
.render_data = NULL, /* No data to render */
|
||||
.render_brief_data = dallas_ds1990_render_brief_data,
|
||||
.render_error = dallas_ds1990_render_error,
|
||||
.is_valid = dallas_ds1990_is_data_valid,
|
||||
.get_editable_data = dallas_ds1990_get_editable_data,
|
||||
.apply_edits = dallas_ds1990_apply_edits,
|
||||
};
|
||||
|
||||
bool dallas_ds1990_read(OneWireHost* host, iButtonProtocolData* protocol_data) {
|
||||
DS1990ProtocolData* data = protocol_data;
|
||||
return onewire_host_reset(host) && dallas_common_read_rom(host, &data->rom_data);
|
||||
}
|
||||
|
||||
bool dallas_ds1990_write_blank(OneWireHost* host, iButtonProtocolData* protocol_data) {
|
||||
DS1990ProtocolData* data = protocol_data;
|
||||
|
||||
return rw1990_write_v1(host, data->rom_data.bytes, sizeof(DallasCommonRomData)) ||
|
||||
rw1990_write_v2(host, data->rom_data.bytes, sizeof(DallasCommonRomData)) ||
|
||||
tm2004_write(host, data->rom_data.bytes, sizeof(DallasCommonRomData));
|
||||
}
|
||||
|
||||
static bool dallas_ds1990_reset_callback(bool is_short, void* context) {
|
||||
DS1990ProtocolData* data = context;
|
||||
if(!is_short) {
|
||||
onewire_slave_set_overdrive(data->state.bus, is_short);
|
||||
}
|
||||
return !is_short;
|
||||
}
|
||||
|
||||
static bool dallas_ds1990_command_callback(uint8_t command, void* context) {
|
||||
furi_assert(context);
|
||||
DS1990ProtocolData* data = context;
|
||||
OneWireSlave* bus = data->state.bus;
|
||||
|
||||
switch(command) {
|
||||
case DALLAS_COMMON_CMD_SEARCH_ROM:
|
||||
dallas_common_emulate_search_rom(bus, &data->rom_data);
|
||||
break;
|
||||
case DALLAS_COMMON_CMD_READ_ROM:
|
||||
case DS1990_CMD_READ_ROM:
|
||||
dallas_common_emulate_read_rom(bus, &data->rom_data);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// No support for multiple consecutive commands
|
||||
return false;
|
||||
}
|
||||
|
||||
void dallas_ds1990_emulate(OneWireSlave* bus, iButtonProtocolData* protocol_data) {
|
||||
DS1990ProtocolData* data = protocol_data;
|
||||
data->state.bus = bus;
|
||||
|
||||
onewire_slave_set_reset_callback(bus, dallas_ds1990_reset_callback, protocol_data);
|
||||
onewire_slave_set_command_callback(bus, dallas_ds1990_command_callback, protocol_data);
|
||||
}
|
||||
|
||||
bool dallas_ds1990_save(FlipperFormat* ff, const iButtonProtocolData* protocol_data) {
|
||||
const DS1990ProtocolData* data = protocol_data;
|
||||
return dallas_common_save_rom_data(ff, &data->rom_data);
|
||||
}
|
||||
|
||||
bool dallas_ds1990_load(
|
||||
FlipperFormat* ff,
|
||||
uint32_t format_version,
|
||||
iButtonProtocolData* protocol_data) {
|
||||
DS1990ProtocolData* data = protocol_data;
|
||||
return dallas_common_load_rom_data(ff, format_version, &data->rom_data);
|
||||
}
|
||||
|
||||
void dallas_ds1990_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) {
|
||||
const DS1990ProtocolData* data = protocol_data;
|
||||
|
||||
for(size_t i = 0; i < sizeof(DallasCommonRomData); ++i) {
|
||||
furi_string_cat_printf(result, "%02X ", data->rom_data.bytes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void dallas_ds1990_render_error(FuriString* result, const iButtonProtocolData* protocol_data) {
|
||||
const DS1990ProtocolData* data = protocol_data;
|
||||
|
||||
if(!dallas_common_is_valid_crc(&data->rom_data)) {
|
||||
dallas_common_render_crc_error(result, &data->rom_data);
|
||||
}
|
||||
}
|
||||
|
||||
bool dallas_ds1990_is_data_valid(const iButtonProtocolData* protocol_data) {
|
||||
const DS1990ProtocolData* data = protocol_data;
|
||||
return dallas_common_is_valid_crc(&data->rom_data);
|
||||
}
|
||||
|
||||
void dallas_ds1990_get_editable_data(
|
||||
iButtonEditableData* editable_data,
|
||||
iButtonProtocolData* protocol_data) {
|
||||
DS1990ProtocolData* data = protocol_data;
|
||||
editable_data->ptr = data->rom_data.bytes;
|
||||
editable_data->size = sizeof(DallasCommonRomData);
|
||||
}
|
||||
|
||||
void dallas_ds1990_apply_edits(iButtonProtocolData* protocol_data) {
|
||||
DS1990ProtocolData* data = protocol_data;
|
||||
dallas_common_apply_edits(&data->rom_data, DS1990_FAMILY_CODE);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "protocol_dallas_base.h"
|
||||
|
||||
extern const iButtonProtocolDallasBase ibutton_protocol_ds1990;
|
||||
@@ -0,0 +1,225 @@
|
||||
#include "protocol_ds1992.h"
|
||||
|
||||
#include <core/core_defines.h>
|
||||
#include <toolbox/pretty_format.h>
|
||||
|
||||
#include "dallas_common.h"
|
||||
|
||||
#include "../blanks/tm2004.h"
|
||||
|
||||
#define DS1992_FAMILY_CODE 0x08U
|
||||
#define DS1992_FAMILY_NAME "DS1992"
|
||||
|
||||
#define DS1992_SRAM_DATA_SIZE 128U
|
||||
#define DS1992_SRAM_PAGE_SIZE 4U
|
||||
#define DS1992_COPY_SCRATCH_TIMEOUT_US 100U
|
||||
|
||||
#define DS1992_DATA_BYTE_COUNT 4U
|
||||
|
||||
#define DS1992_SRAM_DATA_KEY "Sram Data"
|
||||
#define DS1992_MEMORY_TYPE "SRAM"
|
||||
|
||||
typedef struct {
|
||||
OneWireSlave* bus;
|
||||
DallasCommonCommandState command_state;
|
||||
} DS1992ProtocolState;
|
||||
|
||||
typedef struct {
|
||||
DallasCommonRomData rom_data;
|
||||
uint8_t sram_data[DS1992_SRAM_DATA_SIZE];
|
||||
DS1992ProtocolState state;
|
||||
} DS1992ProtocolData;
|
||||
|
||||
static bool dallas_ds1992_read(OneWireHost*, void*);
|
||||
static bool dallas_ds1992_write_blank(OneWireHost*, iButtonProtocolData*);
|
||||
static bool dallas_ds1992_write_copy(OneWireHost*, iButtonProtocolData*);
|
||||
static void dallas_ds1992_emulate(OneWireSlave*, iButtonProtocolData*);
|
||||
static bool dallas_ds1992_load(FlipperFormat*, uint32_t, iButtonProtocolData*);
|
||||
static bool dallas_ds1992_save(FlipperFormat*, const iButtonProtocolData*);
|
||||
static void dallas_ds1992_render_data(FuriString*, const iButtonProtocolData*);
|
||||
static void dallas_ds1992_render_brief_data(FuriString*, const iButtonProtocolData*);
|
||||
static void dallas_ds1992_render_error(FuriString*, const iButtonProtocolData*);
|
||||
static bool dallas_ds1992_is_data_valid(const iButtonProtocolData*);
|
||||
static void dallas_ds1992_get_editable_data(iButtonEditableData*, iButtonProtocolData*);
|
||||
static void dallas_ds1992_apply_edits(iButtonProtocolData*);
|
||||
|
||||
const iButtonProtocolDallasBase ibutton_protocol_ds1992 = {
|
||||
.family_code = DS1992_FAMILY_CODE,
|
||||
.features = iButtonProtocolFeatureExtData | iButtonProtocolFeatureWriteBlank |
|
||||
iButtonProtocolFeatureWriteCopy,
|
||||
.data_size = sizeof(DS1992ProtocolData),
|
||||
.manufacturer = DALLAS_COMMON_MANUFACTURER_NAME,
|
||||
.name = DS1992_FAMILY_NAME,
|
||||
|
||||
.read = dallas_ds1992_read,
|
||||
.write_blank = dallas_ds1992_write_blank,
|
||||
.write_copy = dallas_ds1992_write_copy,
|
||||
.emulate = dallas_ds1992_emulate,
|
||||
.save = dallas_ds1992_save,
|
||||
.load = dallas_ds1992_load,
|
||||
.render_data = dallas_ds1992_render_data,
|
||||
.render_brief_data = dallas_ds1992_render_brief_data,
|
||||
.render_error = dallas_ds1992_render_error,
|
||||
.is_valid = dallas_ds1992_is_data_valid,
|
||||
.get_editable_data = dallas_ds1992_get_editable_data,
|
||||
.apply_edits = dallas_ds1992_apply_edits,
|
||||
};
|
||||
|
||||
bool dallas_ds1992_read(OneWireHost* host, iButtonProtocolData* protocol_data) {
|
||||
DS1992ProtocolData* data = protocol_data;
|
||||
return onewire_host_reset(host) && dallas_common_read_rom(host, &data->rom_data) &&
|
||||
dallas_common_read_mem(host, 0, data->sram_data, DS1992_SRAM_DATA_SIZE);
|
||||
}
|
||||
|
||||
bool dallas_ds1992_write_blank(OneWireHost* host, iButtonProtocolData* protocol_data) {
|
||||
DS1992ProtocolData* data = protocol_data;
|
||||
// TODO: Make this work, currently broken
|
||||
return tm2004_write(host, (uint8_t*)data, sizeof(DallasCommonRomData) + DS1992_SRAM_DATA_SIZE);
|
||||
}
|
||||
|
||||
bool dallas_ds1992_write_copy(OneWireHost* host, iButtonProtocolData* protocol_data) {
|
||||
DS1992ProtocolData* data = protocol_data;
|
||||
return dallas_common_write_mem(
|
||||
host,
|
||||
DS1992_COPY_SCRATCH_TIMEOUT_US,
|
||||
DS1992_SRAM_PAGE_SIZE,
|
||||
data->sram_data,
|
||||
DS1992_SRAM_DATA_SIZE);
|
||||
}
|
||||
|
||||
static bool dallas_ds1992_reset_callback(bool is_short, void* context) {
|
||||
furi_assert(context);
|
||||
DS1992ProtocolData* data = context;
|
||||
|
||||
if(!is_short) {
|
||||
data->state.command_state = DallasCommonCommandStateIdle;
|
||||
onewire_slave_set_overdrive(data->state.bus, is_short);
|
||||
}
|
||||
|
||||
return !is_short;
|
||||
}
|
||||
|
||||
static bool dallas_ds1992_command_callback(uint8_t command, void* context) {
|
||||
furi_assert(context);
|
||||
DS1992ProtocolData* data = context;
|
||||
OneWireSlave* bus = data->state.bus;
|
||||
|
||||
switch(command) {
|
||||
case DALLAS_COMMON_CMD_SEARCH_ROM:
|
||||
if(data->state.command_state == DallasCommonCommandStateIdle) {
|
||||
data->state.command_state = DallasCommonCommandStateRomCmd;
|
||||
return dallas_common_emulate_search_rom(bus, &data->rom_data);
|
||||
|
||||
} else if(data->state.command_state == DallasCommonCommandStateRomCmd) {
|
||||
data->state.command_state = DallasCommonCommandStateMemCmd;
|
||||
dallas_common_emulate_read_mem(bus, data->sram_data, DS1992_SRAM_DATA_SIZE);
|
||||
return false;
|
||||
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
case DALLAS_COMMON_CMD_READ_ROM:
|
||||
if(data->state.command_state == DallasCommonCommandStateIdle) {
|
||||
data->state.command_state = DallasCommonCommandStateRomCmd;
|
||||
return dallas_common_emulate_read_rom(bus, &data->rom_data);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
case DALLAS_COMMON_CMD_SKIP_ROM:
|
||||
if(data->state.command_state == DallasCommonCommandStateIdle) {
|
||||
data->state.command_state = DallasCommonCommandStateRomCmd;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void dallas_ds1992_emulate(OneWireSlave* bus, iButtonProtocolData* protocol_data) {
|
||||
DS1992ProtocolData* data = protocol_data;
|
||||
data->state.bus = bus;
|
||||
|
||||
onewire_slave_set_reset_callback(bus, dallas_ds1992_reset_callback, protocol_data);
|
||||
onewire_slave_set_command_callback(bus, dallas_ds1992_command_callback, protocol_data);
|
||||
}
|
||||
|
||||
bool dallas_ds1992_load(
|
||||
FlipperFormat* ff,
|
||||
uint32_t format_version,
|
||||
iButtonProtocolData* protocol_data) {
|
||||
DS1992ProtocolData* data = protocol_data;
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
if(format_version < 2) break;
|
||||
if(!dallas_common_load_rom_data(ff, format_version, &data->rom_data)) break;
|
||||
if(!flipper_format_read_hex(
|
||||
ff, DS1992_SRAM_DATA_KEY, data->sram_data, DS1992_SRAM_DATA_SIZE))
|
||||
break;
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool dallas_ds1992_save(FlipperFormat* ff, const iButtonProtocolData* protocol_data) {
|
||||
const DS1992ProtocolData* data = protocol_data;
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
if(!dallas_common_save_rom_data(ff, &data->rom_data)) break;
|
||||
if(!flipper_format_write_hex(
|
||||
ff, DS1992_SRAM_DATA_KEY, data->sram_data, DS1992_SRAM_DATA_SIZE))
|
||||
break;
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void dallas_ds1992_render_data(FuriString* result, const iButtonProtocolData* protocol_data) {
|
||||
const DS1992ProtocolData* data = protocol_data;
|
||||
pretty_format_bytes_hex_canonical(
|
||||
result,
|
||||
DS1992_DATA_BYTE_COUNT,
|
||||
PRETTY_FORMAT_FONT_MONOSPACE,
|
||||
data->sram_data,
|
||||
DS1992_SRAM_DATA_SIZE);
|
||||
}
|
||||
|
||||
void dallas_ds1992_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) {
|
||||
const DS1992ProtocolData* data = protocol_data;
|
||||
dallas_common_render_brief_data(
|
||||
result, &data->rom_data, data->sram_data, DS1992_SRAM_DATA_SIZE, DS1992_MEMORY_TYPE);
|
||||
}
|
||||
|
||||
void dallas_ds1992_render_error(FuriString* result, const iButtonProtocolData* protocol_data) {
|
||||
const DS1992ProtocolData* data = protocol_data;
|
||||
|
||||
if(!dallas_common_is_valid_crc(&data->rom_data)) {
|
||||
dallas_common_render_crc_error(result, &data->rom_data);
|
||||
}
|
||||
}
|
||||
|
||||
bool dallas_ds1992_is_data_valid(const iButtonProtocolData* protocol_data) {
|
||||
const DS1992ProtocolData* data = protocol_data;
|
||||
return dallas_common_is_valid_crc(&data->rom_data);
|
||||
}
|
||||
|
||||
void dallas_ds1992_get_editable_data(
|
||||
iButtonEditableData* editable_data,
|
||||
iButtonProtocolData* protocol_data) {
|
||||
DS1992ProtocolData* data = protocol_data;
|
||||
editable_data->ptr = data->rom_data.bytes;
|
||||
editable_data->size = sizeof(DallasCommonRomData);
|
||||
}
|
||||
|
||||
void dallas_ds1992_apply_edits(iButtonProtocolData* protocol_data) {
|
||||
DS1992ProtocolData* data = protocol_data;
|
||||
dallas_common_apply_edits(&data->rom_data, DS1992_FAMILY_CODE);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "protocol_dallas_base.h"
|
||||
|
||||
extern const iButtonProtocolDallasBase ibutton_protocol_ds1992;
|
||||
@@ -0,0 +1,251 @@
|
||||
#include "protocol_ds1996.h"
|
||||
|
||||
#include <core/core_defines.h>
|
||||
#include <toolbox/pretty_format.h>
|
||||
|
||||
#include "dallas_common.h"
|
||||
|
||||
#define DS1996_FAMILY_CODE 0x0CU
|
||||
#define DS1996_FAMILY_NAME "DS1996"
|
||||
|
||||
#define DS1996_SRAM_DATA_SIZE 8192U
|
||||
#define DS1996_SRAM_PAGE_SIZE 32U
|
||||
#define DS1996_COPY_SCRATCH_TIMEOUT_US 100U
|
||||
|
||||
#define DS1996_DATA_BYTE_COUNT 4U
|
||||
|
||||
#define DS1996_SRAM_DATA_KEY "Sram Data"
|
||||
#define DS1996_MEMORY_TYPE "SRAM"
|
||||
|
||||
typedef struct {
|
||||
OneWireSlave* bus;
|
||||
DallasCommonCommandState command_state;
|
||||
} DS1996ProtocolState;
|
||||
|
||||
typedef struct {
|
||||
DallasCommonRomData rom_data;
|
||||
uint8_t sram_data[DS1996_SRAM_DATA_SIZE];
|
||||
DS1996ProtocolState state;
|
||||
} DS1996ProtocolData;
|
||||
|
||||
static bool dallas_ds1996_read(OneWireHost*, void*);
|
||||
static bool dallas_ds1996_write_copy(OneWireHost*, iButtonProtocolData*);
|
||||
static void dallas_ds1996_emulate(OneWireSlave*, iButtonProtocolData*);
|
||||
static bool dallas_ds1996_load(FlipperFormat*, uint32_t, iButtonProtocolData*);
|
||||
static bool dallas_ds1996_save(FlipperFormat*, const iButtonProtocolData*);
|
||||
static void dallas_ds1996_render_data(FuriString*, const iButtonProtocolData*);
|
||||
static void dallas_ds1996_render_brief_data(FuriString*, const iButtonProtocolData*);
|
||||
static void dallas_ds1996_render_error(FuriString*, const iButtonProtocolData*);
|
||||
static bool dallas_ds1996_is_data_valid(const iButtonProtocolData*);
|
||||
static void dallas_ds1996_get_editable_data(iButtonEditableData*, iButtonProtocolData*);
|
||||
static void dallas_ds1996_apply_edits(iButtonProtocolData*);
|
||||
|
||||
const iButtonProtocolDallasBase ibutton_protocol_ds1996 = {
|
||||
.family_code = DS1996_FAMILY_CODE,
|
||||
.features = iButtonProtocolFeatureExtData | iButtonProtocolFeatureWriteCopy,
|
||||
.data_size = sizeof(DS1996ProtocolData),
|
||||
.manufacturer = DALLAS_COMMON_MANUFACTURER_NAME,
|
||||
.name = DS1996_FAMILY_NAME,
|
||||
|
||||
.read = dallas_ds1996_read,
|
||||
.write_blank = NULL, /* Data too big for known blanks */
|
||||
.write_copy = dallas_ds1996_write_copy,
|
||||
.emulate = dallas_ds1996_emulate,
|
||||
.save = dallas_ds1996_save,
|
||||
.load = dallas_ds1996_load,
|
||||
.render_data = dallas_ds1996_render_data,
|
||||
.render_brief_data = dallas_ds1996_render_brief_data,
|
||||
.render_error = dallas_ds1996_render_error,
|
||||
.is_valid = dallas_ds1996_is_data_valid,
|
||||
.get_editable_data = dallas_ds1996_get_editable_data,
|
||||
.apply_edits = dallas_ds1996_apply_edits,
|
||||
};
|
||||
|
||||
bool dallas_ds1996_read(OneWireHost* host, iButtonProtocolData* protocol_data) {
|
||||
DS1996ProtocolData* data = protocol_data;
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
if(!onewire_host_reset(host)) break;
|
||||
if(!dallas_common_read_rom(host, &data->rom_data)) break;
|
||||
if(!onewire_host_reset(host)) break;
|
||||
|
||||
onewire_host_write(host, DALLAS_COMMON_CMD_OVERDRIVE_SKIP_ROM);
|
||||
onewire_host_set_overdrive(host, true);
|
||||
|
||||
if(!dallas_common_read_mem(host, 0, data->sram_data, DS1996_SRAM_DATA_SIZE)) break;
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
onewire_host_set_overdrive(host, false);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool dallas_ds1996_write_copy(OneWireHost* host, iButtonProtocolData* protocol_data) {
|
||||
DS1996ProtocolData* data = protocol_data;
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
if(!onewire_host_reset(host)) break;
|
||||
|
||||
onewire_host_write(host, DALLAS_COMMON_CMD_OVERDRIVE_SKIP_ROM);
|
||||
onewire_host_set_overdrive(host, true);
|
||||
|
||||
if(!dallas_common_write_mem(
|
||||
host,
|
||||
DS1996_COPY_SCRATCH_TIMEOUT_US,
|
||||
DS1996_SRAM_PAGE_SIZE,
|
||||
data->sram_data,
|
||||
DS1996_SRAM_DATA_SIZE))
|
||||
break;
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
onewire_host_set_overdrive(host, false);
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool dallas_ds1996_reset_callback(bool is_short, void* context) {
|
||||
furi_assert(context);
|
||||
DS1996ProtocolData* data = context;
|
||||
data->state.command_state = DallasCommonCommandStateIdle;
|
||||
onewire_slave_set_overdrive(data->state.bus, is_short);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool dallas_ds1996_command_callback(uint8_t command, void* context) {
|
||||
furi_assert(context);
|
||||
DS1996ProtocolData* data = context;
|
||||
OneWireSlave* bus = data->state.bus;
|
||||
|
||||
switch(command) {
|
||||
case DALLAS_COMMON_CMD_SEARCH_ROM:
|
||||
if(data->state.command_state == DallasCommonCommandStateIdle) {
|
||||
data->state.command_state = DallasCommonCommandStateRomCmd;
|
||||
return dallas_common_emulate_search_rom(bus, &data->rom_data);
|
||||
|
||||
} else if(data->state.command_state == DallasCommonCommandStateRomCmd) {
|
||||
data->state.command_state = DallasCommonCommandStateMemCmd;
|
||||
return dallas_common_emulate_read_mem(bus, data->sram_data, DS1996_SRAM_DATA_SIZE);
|
||||
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
case DALLAS_COMMON_CMD_READ_ROM:
|
||||
if(data->state.command_state == DallasCommonCommandStateIdle) {
|
||||
data->state.command_state = DallasCommonCommandStateRomCmd;
|
||||
return dallas_common_emulate_read_rom(bus, &data->rom_data);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
case DALLAS_COMMON_CMD_SKIP_ROM:
|
||||
if(data->state.command_state == DallasCommonCommandStateIdle) {
|
||||
data->state.command_state = DallasCommonCommandStateRomCmd;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
case DALLAS_COMMON_CMD_OVERDRIVE_SKIP_ROM:
|
||||
if(data->state.command_state == DallasCommonCommandStateIdle) {
|
||||
data->state.command_state = DallasCommonCommandStateRomCmd;
|
||||
onewire_slave_set_overdrive(bus, true);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
case DALLAS_COMMON_CMD_MATCH_ROM:
|
||||
case DALLAS_COMMON_CMD_OVERDRIVE_MATCH_ROM:
|
||||
/* TODO: Match ROM command support */
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void dallas_ds1996_emulate(OneWireSlave* bus, iButtonProtocolData* protocol_data) {
|
||||
DS1996ProtocolData* data = protocol_data;
|
||||
data->state.bus = bus;
|
||||
|
||||
onewire_slave_set_reset_callback(bus, dallas_ds1996_reset_callback, protocol_data);
|
||||
onewire_slave_set_command_callback(bus, dallas_ds1996_command_callback, protocol_data);
|
||||
}
|
||||
|
||||
bool dallas_ds1996_load(
|
||||
FlipperFormat* ff,
|
||||
uint32_t format_version,
|
||||
iButtonProtocolData* protocol_data) {
|
||||
DS1996ProtocolData* data = protocol_data;
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
if(format_version < 2) break;
|
||||
if(!dallas_common_load_rom_data(ff, format_version, &data->rom_data)) break;
|
||||
if(!flipper_format_read_hex(
|
||||
ff, DS1996_SRAM_DATA_KEY, data->sram_data, DS1996_SRAM_DATA_SIZE))
|
||||
break;
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool dallas_ds1996_save(FlipperFormat* ff, const iButtonProtocolData* protocol_data) {
|
||||
const DS1996ProtocolData* data = protocol_data;
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
if(!dallas_common_save_rom_data(ff, &data->rom_data)) break;
|
||||
if(!flipper_format_write_hex(
|
||||
ff, DS1996_SRAM_DATA_KEY, data->sram_data, DS1996_SRAM_DATA_SIZE))
|
||||
break;
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void dallas_ds1996_render_data(FuriString* result, const iButtonProtocolData* protocol_data) {
|
||||
const DS1996ProtocolData* data = protocol_data;
|
||||
|
||||
pretty_format_bytes_hex_canonical(
|
||||
result,
|
||||
DS1996_DATA_BYTE_COUNT,
|
||||
PRETTY_FORMAT_FONT_MONOSPACE,
|
||||
data->sram_data,
|
||||
DS1996_SRAM_DATA_SIZE);
|
||||
}
|
||||
|
||||
void dallas_ds1996_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) {
|
||||
const DS1996ProtocolData* data = protocol_data;
|
||||
dallas_common_render_brief_data(
|
||||
result, &data->rom_data, data->sram_data, DS1996_SRAM_DATA_SIZE, DS1996_MEMORY_TYPE);
|
||||
}
|
||||
|
||||
void dallas_ds1996_render_error(FuriString* result, const iButtonProtocolData* protocol_data) {
|
||||
const DS1996ProtocolData* data = protocol_data;
|
||||
|
||||
if(!dallas_common_is_valid_crc(&data->rom_data)) {
|
||||
dallas_common_render_crc_error(result, &data->rom_data);
|
||||
}
|
||||
}
|
||||
|
||||
bool dallas_ds1996_is_data_valid(const iButtonProtocolData* protocol_data) {
|
||||
const DS1996ProtocolData* data = protocol_data;
|
||||
return dallas_common_is_valid_crc(&data->rom_data);
|
||||
}
|
||||
|
||||
void dallas_ds1996_get_editable_data(
|
||||
iButtonEditableData* editable_data,
|
||||
iButtonProtocolData* protocol_data) {
|
||||
DS1996ProtocolData* data = protocol_data;
|
||||
editable_data->ptr = data->rom_data.bytes;
|
||||
editable_data->size = sizeof(DallasCommonRomData);
|
||||
}
|
||||
|
||||
void dallas_ds1996_apply_edits(iButtonProtocolData* protocol_data) {
|
||||
DS1996ProtocolData* data = protocol_data;
|
||||
dallas_common_apply_edits(&data->rom_data, DS1996_FAMILY_CODE);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "protocol_dallas_base.h"
|
||||
|
||||
extern const iButtonProtocolDallasBase ibutton_protocol_ds1996;
|
||||
@@ -0,0 +1,141 @@
|
||||
#include "protocol_ds_generic.h"
|
||||
|
||||
#include <core/string.h>
|
||||
#include <core/core_defines.h>
|
||||
|
||||
#include "dallas_common.h"
|
||||
|
||||
#include "../blanks/tm2004.h"
|
||||
|
||||
#define DALLAS_GENERIC_FAMILY_CODE 0x00U
|
||||
#define DALLAS_GENERIC_FAMILY_NAME "DSGeneric"
|
||||
|
||||
typedef struct {
|
||||
OneWireSlave* bus;
|
||||
} DallasGenericProtocolState;
|
||||
|
||||
typedef struct {
|
||||
DallasCommonRomData rom_data;
|
||||
DallasGenericProtocolState state;
|
||||
} DallasGenericProtocolData;
|
||||
|
||||
static bool ds_generic_read(OneWireHost*, iButtonProtocolData*);
|
||||
static bool ds_generic_write_blank(OneWireHost*, iButtonProtocolData*);
|
||||
static void ds_generic_emulate(OneWireSlave*, iButtonProtocolData*);
|
||||
static bool ds_generic_load(FlipperFormat*, uint32_t, iButtonProtocolData*);
|
||||
static bool ds_generic_save(FlipperFormat*, const iButtonProtocolData*);
|
||||
static void ds_generic_render_brief_data(FuriString*, const iButtonProtocolData*);
|
||||
static void ds_generic_render_error(FuriString*, const iButtonProtocolData*);
|
||||
static bool ds_generic_is_data_valid(const iButtonProtocolData*);
|
||||
static void ds_generic_get_editable_data(iButtonEditableData*, iButtonProtocolData*);
|
||||
static void ds_generic_apply_edits(iButtonProtocolData*);
|
||||
|
||||
const iButtonProtocolDallasBase ibutton_protocol_ds_generic = {
|
||||
.family_code = DALLAS_GENERIC_FAMILY_CODE,
|
||||
.features = iButtonProtocolFeatureWriteBlank,
|
||||
.data_size = sizeof(DallasGenericProtocolData),
|
||||
.manufacturer = DALLAS_COMMON_MANUFACTURER_NAME,
|
||||
.name = DALLAS_GENERIC_FAMILY_NAME,
|
||||
|
||||
.read = ds_generic_read,
|
||||
.write_blank = ds_generic_write_blank,
|
||||
.write_copy = NULL, /* No data to write a copy */
|
||||
.emulate = ds_generic_emulate,
|
||||
.save = ds_generic_save,
|
||||
.load = ds_generic_load,
|
||||
.render_data = NULL, /* No data to render */
|
||||
.render_brief_data = ds_generic_render_brief_data,
|
||||
.render_error = ds_generic_render_error,
|
||||
.is_valid = ds_generic_is_data_valid,
|
||||
.get_editable_data = ds_generic_get_editable_data,
|
||||
.apply_edits = ds_generic_apply_edits,
|
||||
};
|
||||
|
||||
bool ds_generic_read(OneWireHost* host, iButtonProtocolData* protocol_data) {
|
||||
DallasGenericProtocolData* data = protocol_data;
|
||||
return onewire_host_reset(host) && dallas_common_read_rom(host, &data->rom_data);
|
||||
}
|
||||
|
||||
bool ds_generic_write_blank(OneWireHost* host, iButtonProtocolData* protocol_data) {
|
||||
DallasGenericProtocolData* data = protocol_data;
|
||||
return tm2004_write(host, data->rom_data.bytes, sizeof(DallasCommonRomData));
|
||||
}
|
||||
|
||||
static bool ds_generic_reset_callback(bool is_short, void* context) {
|
||||
DallasGenericProtocolData* data = context;
|
||||
if(!is_short) {
|
||||
onewire_slave_set_overdrive(data->state.bus, is_short);
|
||||
}
|
||||
return !is_short;
|
||||
}
|
||||
|
||||
static bool ds_generic_command_callback(uint8_t command, void* context) {
|
||||
furi_assert(context);
|
||||
DallasGenericProtocolData* data = context;
|
||||
OneWireSlave* bus = data->state.bus;
|
||||
|
||||
switch(command) {
|
||||
case DALLAS_COMMON_CMD_SEARCH_ROM:
|
||||
dallas_common_emulate_search_rom(bus, &data->rom_data);
|
||||
break;
|
||||
case DALLAS_COMMON_CMD_READ_ROM:
|
||||
dallas_common_emulate_read_rom(bus, &data->rom_data);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// No support for multiple consecutive commands
|
||||
return false;
|
||||
}
|
||||
|
||||
void ds_generic_emulate(OneWireSlave* bus, iButtonProtocolData* protocol_data) {
|
||||
DallasGenericProtocolData* data = protocol_data;
|
||||
data->state.bus = bus;
|
||||
|
||||
onewire_slave_set_reset_callback(bus, ds_generic_reset_callback, NULL);
|
||||
onewire_slave_set_command_callback(bus, ds_generic_command_callback, protocol_data);
|
||||
}
|
||||
|
||||
bool ds_generic_save(FlipperFormat* ff, const iButtonProtocolData* protocol_data) {
|
||||
const DallasGenericProtocolData* data = protocol_data;
|
||||
return dallas_common_save_rom_data(ff, &data->rom_data);
|
||||
}
|
||||
|
||||
bool ds_generic_load(
|
||||
FlipperFormat* ff,
|
||||
uint32_t format_version,
|
||||
iButtonProtocolData* protocol_data) {
|
||||
DallasGenericProtocolData* data = protocol_data;
|
||||
return dallas_common_load_rom_data(ff, format_version, &data->rom_data);
|
||||
}
|
||||
|
||||
void ds_generic_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) {
|
||||
const DallasGenericProtocolData* data = protocol_data;
|
||||
|
||||
for(size_t i = 0; i < sizeof(DallasCommonRomData); ++i) {
|
||||
furi_string_cat_printf(result, "%02X ", data->rom_data.bytes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void ds_generic_render_error(FuriString* result, const iButtonProtocolData* protocol_data) {
|
||||
UNUSED(result);
|
||||
UNUSED(protocol_data);
|
||||
}
|
||||
|
||||
bool ds_generic_is_data_valid(const iButtonProtocolData* protocol_data) {
|
||||
UNUSED(protocol_data);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ds_generic_get_editable_data(
|
||||
iButtonEditableData* editable_data,
|
||||
iButtonProtocolData* protocol_data) {
|
||||
DallasGenericProtocolData* data = protocol_data;
|
||||
editable_data->ptr = data->rom_data.bytes;
|
||||
editable_data->size = sizeof(DallasCommonRomData);
|
||||
}
|
||||
|
||||
void ds_generic_apply_edits(iButtonProtocolData* protocol_data) {
|
||||
UNUSED(protocol_data);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "protocol_dallas_base.h"
|
||||
|
||||
extern const iButtonProtocolDallasBase ibutton_protocol_ds_generic;
|
||||
@@ -0,0 +1,310 @@
|
||||
#include "protocol_group_dallas.h"
|
||||
|
||||
#include <furi_hal_resources.h>
|
||||
|
||||
#include "protocol_group_dallas_defs.h"
|
||||
|
||||
#define IBUTTON_ONEWIRE_ROM_SIZE 8U
|
||||
|
||||
typedef struct {
|
||||
OneWireHost* host;
|
||||
OneWireSlave* bus;
|
||||
} iButtonProtocolGroupDallas;
|
||||
|
||||
static iButtonProtocolGroupDallas* ibutton_protocol_group_dallas_alloc() {
|
||||
iButtonProtocolGroupDallas* group = malloc(sizeof(iButtonProtocolGroupDallas));
|
||||
|
||||
group->host = onewire_host_alloc(&ibutton_gpio);
|
||||
group->bus = onewire_slave_alloc(&ibutton_gpio);
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
static void ibutton_protocol_group_dallas_free(iButtonProtocolGroupDallas* group) {
|
||||
onewire_slave_free(group->bus);
|
||||
onewire_host_free(group->host);
|
||||
free(group);
|
||||
}
|
||||
|
||||
static size_t ibutton_protocol_group_dallas_get_max_data_size(iButtonProtocolGroupDallas* group) {
|
||||
UNUSED(group);
|
||||
size_t max_data_size = 0;
|
||||
|
||||
for(iButtonProtocolLocalId i = 0; i < iButtonProtocolDSMax; ++i) {
|
||||
const size_t current_rom_size = ibutton_protocols_dallas[i]->data_size;
|
||||
if(current_rom_size > max_data_size) {
|
||||
max_data_size = current_rom_size;
|
||||
}
|
||||
}
|
||||
|
||||
return max_data_size;
|
||||
}
|
||||
|
||||
static bool ibutton_protocol_group_dallas_get_id_by_name(
|
||||
iButtonProtocolGroupDallas* group,
|
||||
iButtonProtocolLocalId* id,
|
||||
const char* name) {
|
||||
UNUSED(group);
|
||||
// Handle older key files which refer to DS1990 as just "Dallas"
|
||||
if(strcmp(name, "Dallas") == 0) {
|
||||
*id = iButtonProtocolDS1990;
|
||||
return true;
|
||||
}
|
||||
|
||||
for(iButtonProtocolLocalId i = 0; i < iButtonProtocolDSMax; ++i) {
|
||||
if(strcmp(ibutton_protocols_dallas[i]->name, name) == 0) {
|
||||
*id = i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static uint32_t ibutton_protocol_group_dallas_get_features(
|
||||
iButtonProtocolGroupDallas* group,
|
||||
iButtonProtocolLocalId id) {
|
||||
UNUSED(group);
|
||||
furi_assert(id < iButtonProtocolDSMax);
|
||||
return ibutton_protocols_dallas[id]->features;
|
||||
}
|
||||
|
||||
static const char* ibutton_protocol_group_dallas_get_manufacturer(
|
||||
iButtonProtocolGroupDallas* group,
|
||||
iButtonProtocolLocalId id) {
|
||||
UNUSED(group);
|
||||
furi_assert(id < iButtonProtocolDSMax);
|
||||
return ibutton_protocols_dallas[id]->manufacturer;
|
||||
}
|
||||
|
||||
static const char* ibutton_protocol_group_dallas_get_name(
|
||||
iButtonProtocolGroupDallas* group,
|
||||
iButtonProtocolLocalId id) {
|
||||
UNUSED(group);
|
||||
furi_assert(id < iButtonProtocolDSMax);
|
||||
return ibutton_protocols_dallas[id]->name;
|
||||
}
|
||||
|
||||
static iButtonProtocolLocalId
|
||||
ibutton_protocol_group_dallas_get_id_by_family_code(uint8_t family_code) {
|
||||
iButtonProtocolLocalId id;
|
||||
|
||||
for(id = 0; id < iButtonProtocolDSGeneric; ++id) {
|
||||
if(ibutton_protocols_dallas[id]->family_code == family_code) break;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
static bool ibutton_protocol_group_dallas_read(
|
||||
iButtonProtocolGroupDallas* group,
|
||||
iButtonProtocolData* data,
|
||||
iButtonProtocolLocalId* id) {
|
||||
bool success = false;
|
||||
uint8_t rom_data[IBUTTON_ONEWIRE_ROM_SIZE];
|
||||
OneWireHost* host = group->host;
|
||||
|
||||
onewire_host_start(host);
|
||||
furi_delay_ms(100);
|
||||
|
||||
FURI_CRITICAL_ENTER();
|
||||
|
||||
if(onewire_host_search(host, rom_data, OneWireHostSearchModeNormal)) {
|
||||
/* Considering any found 1-Wire device a success.
|
||||
* It can be checked later with ibutton_key_is_valid(). */
|
||||
success = true;
|
||||
|
||||
/* If a 1-Wire device was found, id is guaranteed to be
|
||||
* one of the known keys or DSGeneric. */
|
||||
*id = ibutton_protocol_group_dallas_get_id_by_family_code(rom_data[0]);
|
||||
ibutton_protocols_dallas[*id]->read(host, data);
|
||||
}
|
||||
|
||||
onewire_host_reset_search(host);
|
||||
onewire_host_stop(host);
|
||||
|
||||
FURI_CRITICAL_EXIT();
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool ibutton_protocol_group_dallas_write_blank(
|
||||
iButtonProtocolGroupDallas* group,
|
||||
iButtonProtocolData* data,
|
||||
iButtonProtocolLocalId id) {
|
||||
furi_assert(id < iButtonProtocolDSMax);
|
||||
const iButtonProtocolDallasBase* protocol = ibutton_protocols_dallas[id];
|
||||
furi_assert(protocol->features & iButtonProtocolFeatureWriteBlank);
|
||||
|
||||
OneWireHost* host = group->host;
|
||||
|
||||
onewire_host_start(host);
|
||||
furi_delay_ms(100);
|
||||
|
||||
FURI_CRITICAL_ENTER();
|
||||
|
||||
const bool success = protocol->write_blank(host, data);
|
||||
onewire_host_stop(host);
|
||||
|
||||
FURI_CRITICAL_EXIT();
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool ibutton_protocol_group_dallas_write_copy(
|
||||
iButtonProtocolGroupDallas* group,
|
||||
iButtonProtocolData* data,
|
||||
iButtonProtocolLocalId id) {
|
||||
furi_assert(id < iButtonProtocolDSMax);
|
||||
|
||||
const iButtonProtocolDallasBase* protocol = ibutton_protocols_dallas[id];
|
||||
furi_assert(protocol->features & iButtonProtocolFeatureWriteCopy);
|
||||
|
||||
OneWireHost* host = group->host;
|
||||
|
||||
onewire_host_start(host);
|
||||
furi_delay_ms(100);
|
||||
|
||||
FURI_CRITICAL_ENTER();
|
||||
|
||||
const bool success = protocol->write_copy(host, data);
|
||||
onewire_host_stop(host);
|
||||
|
||||
FURI_CRITICAL_EXIT();
|
||||
return success;
|
||||
}
|
||||
|
||||
static void ibutton_protocol_group_dallas_emulate_start(
|
||||
iButtonProtocolGroupDallas* group,
|
||||
iButtonProtocolData* data,
|
||||
iButtonProtocolLocalId id) {
|
||||
furi_assert(id < iButtonProtocolDSMax);
|
||||
OneWireSlave* bus = group->bus;
|
||||
ibutton_protocols_dallas[id]->emulate(bus, data);
|
||||
onewire_slave_start(bus);
|
||||
}
|
||||
|
||||
static void ibutton_protocol_group_dallas_emulate_stop(
|
||||
iButtonProtocolGroupDallas* group,
|
||||
iButtonProtocolData* data,
|
||||
iButtonProtocolLocalId id) {
|
||||
furi_assert(id < iButtonProtocolDSMax);
|
||||
UNUSED(data);
|
||||
onewire_slave_stop(group->bus);
|
||||
}
|
||||
|
||||
static bool ibutton_protocol_group_dallas_save(
|
||||
iButtonProtocolGroupDallas* group,
|
||||
const iButtonProtocolData* data,
|
||||
iButtonProtocolLocalId id,
|
||||
FlipperFormat* ff) {
|
||||
UNUSED(group);
|
||||
furi_assert(id < iButtonProtocolDSMax);
|
||||
return ibutton_protocols_dallas[id]->save(ff, data);
|
||||
}
|
||||
|
||||
static bool ibutton_protocol_group_dallas_load(
|
||||
iButtonProtocolGroupDallas* group,
|
||||
iButtonProtocolData* data,
|
||||
iButtonProtocolLocalId id,
|
||||
uint32_t version,
|
||||
FlipperFormat* ff) {
|
||||
UNUSED(group);
|
||||
furi_assert(id < iButtonProtocolDSMax);
|
||||
return ibutton_protocols_dallas[id]->load(ff, version, data);
|
||||
}
|
||||
|
||||
static void ibutton_protocol_group_dallas_render_data(
|
||||
iButtonProtocolGroupDallas* group,
|
||||
const iButtonProtocolData* data,
|
||||
iButtonProtocolLocalId id,
|
||||
FuriString* result) {
|
||||
UNUSED(group);
|
||||
furi_assert(id < iButtonProtocolDSMax);
|
||||
const iButtonProtocolDallasBase* protocol = ibutton_protocols_dallas[id];
|
||||
furi_assert(protocol->render_data);
|
||||
protocol->render_data(result, data);
|
||||
}
|
||||
|
||||
static void ibutton_protocol_group_dallas_render_brief_data(
|
||||
iButtonProtocolGroupDallas* group,
|
||||
const iButtonProtocolData* data,
|
||||
iButtonProtocolLocalId id,
|
||||
FuriString* result) {
|
||||
UNUSED(group);
|
||||
furi_assert(id < iButtonProtocolDSMax);
|
||||
ibutton_protocols_dallas[id]->render_brief_data(result, data);
|
||||
}
|
||||
|
||||
static void ibutton_protocol_group_dallas_render_error(
|
||||
iButtonProtocolGroupDallas* group,
|
||||
const iButtonProtocolData* data,
|
||||
iButtonProtocolLocalId id,
|
||||
FuriString* result) {
|
||||
UNUSED(group);
|
||||
furi_assert(id < iButtonProtocolDSMax);
|
||||
ibutton_protocols_dallas[id]->render_error(result, data);
|
||||
}
|
||||
|
||||
static bool ibutton_protocol_group_dallas_is_valid(
|
||||
iButtonProtocolGroupDallas* group,
|
||||
const iButtonProtocolData* data,
|
||||
iButtonProtocolLocalId id) {
|
||||
UNUSED(group);
|
||||
furi_assert(id < iButtonProtocolDSMax);
|
||||
return ibutton_protocols_dallas[id]->is_valid(data);
|
||||
}
|
||||
|
||||
static void ibutton_protocol_group_dallas_get_editable_data(
|
||||
iButtonProtocolGroupDallas* group,
|
||||
iButtonProtocolData* data,
|
||||
iButtonProtocolLocalId id,
|
||||
iButtonEditableData* editable) {
|
||||
UNUSED(group);
|
||||
furi_assert(id < iButtonProtocolDSMax);
|
||||
ibutton_protocols_dallas[id]->get_editable_data(editable, data);
|
||||
}
|
||||
|
||||
static void ibutton_protocol_group_dallas_apply_edits(
|
||||
iButtonProtocolGroupDallas* group,
|
||||
iButtonProtocolData* data,
|
||||
iButtonProtocolLocalId id) {
|
||||
UNUSED(group);
|
||||
furi_assert(id < iButtonProtocolDSMax);
|
||||
ibutton_protocols_dallas[id]->apply_edits(data);
|
||||
}
|
||||
|
||||
const iButtonProtocolGroupBase ibutton_protocol_group_dallas = {
|
||||
.protocol_count = iButtonProtocolDSMax,
|
||||
|
||||
.alloc = (iButtonProtocolGroupAllocFunc)ibutton_protocol_group_dallas_alloc,
|
||||
.free = (iButtonProtocolGroupFreeFunc)ibutton_protocol_group_dallas_free,
|
||||
|
||||
.get_max_data_size =
|
||||
(iButtonProtocolGropuGetSizeFunc)ibutton_protocol_group_dallas_get_max_data_size,
|
||||
.get_id_by_name = (iButtonProtocolGroupGetIdFunc)ibutton_protocol_group_dallas_get_id_by_name,
|
||||
.get_features =
|
||||
(iButtonProtocolGroupGetFeaturesFunc)ibutton_protocol_group_dallas_get_features,
|
||||
|
||||
.get_manufacturer =
|
||||
(iButtonProtocolGroupGetStringFunc)ibutton_protocol_group_dallas_get_manufacturer,
|
||||
.get_name = (iButtonProtocolGroupGetStringFunc)ibutton_protocol_group_dallas_get_name,
|
||||
|
||||
.read = (iButtonProtocolGroupReadFunc)ibutton_protocol_group_dallas_read,
|
||||
.write_blank = (iButtonProtocolGroupWriteFunc)ibutton_protocol_group_dallas_write_blank,
|
||||
.write_copy = (iButtonProtocolGroupWriteFunc)ibutton_protocol_group_dallas_write_copy,
|
||||
|
||||
.emulate_start = (iButtonProtocolGroupApplyFunc)ibutton_protocol_group_dallas_emulate_start,
|
||||
.emulate_stop = (iButtonProtocolGroupApplyFunc)ibutton_protocol_group_dallas_emulate_stop,
|
||||
|
||||
.save = (iButtonProtocolGroupSaveFunc)ibutton_protocol_group_dallas_save,
|
||||
.load = (iButtonProtocolGroupLoadFunc)ibutton_protocol_group_dallas_load,
|
||||
|
||||
.render_data = (iButtonProtocolGroupRenderFunc)ibutton_protocol_group_dallas_render_data,
|
||||
.render_brief_data =
|
||||
(iButtonProtocolGroupRenderFunc)ibutton_protocol_group_dallas_render_brief_data,
|
||||
.render_error = (iButtonProtocolGroupRenderFunc)ibutton_protocol_group_dallas_render_error,
|
||||
|
||||
.is_valid = (iButtonProtocolGroupIsValidFunc)ibutton_protocol_group_dallas_is_valid,
|
||||
.get_editable_data =
|
||||
(iButtonProtocolGroupGetDataFunc)ibutton_protocol_group_dallas_get_editable_data,
|
||||
.apply_edits = (iButtonProtocolGroupApplyFunc)ibutton_protocol_group_dallas_apply_edits,
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "../protocol_group_base.h"
|
||||
|
||||
extern const iButtonProtocolGroupBase ibutton_protocol_group_dallas;
|
||||
@@ -0,0 +1,18 @@
|
||||
#include "protocol_group_dallas_defs.h"
|
||||
|
||||
#include "protocol_ds1990.h"
|
||||
#include "protocol_ds1992.h"
|
||||
#include "protocol_ds1996.h"
|
||||
#include "protocol_ds1971.h"
|
||||
#include "protocol_ds_generic.h"
|
||||
|
||||
const iButtonProtocolDallasBase* ibutton_protocols_dallas[] = {
|
||||
[iButtonProtocolDS1990] = &ibutton_protocol_ds1990,
|
||||
[iButtonProtocolDS1992] = &ibutton_protocol_ds1992,
|
||||
[iButtonProtocolDS1996] = &ibutton_protocol_ds1996,
|
||||
[iButtonProtocolDS1971] = &ibutton_protocol_ds1971,
|
||||
/* Add new 1-Wire protocols here */
|
||||
|
||||
/* Default catch-all 1-Wire protocol */
|
||||
[iButtonProtocolDSGeneric] = &ibutton_protocol_ds_generic,
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include "protocol_dallas_base.h"
|
||||
|
||||
typedef enum {
|
||||
iButtonProtocolDS1990,
|
||||
iButtonProtocolDS1992,
|
||||
iButtonProtocolDS1996,
|
||||
iButtonProtocolDS1971,
|
||||
/* Add new 1-Wire protocols here */
|
||||
|
||||
/* Default catch-all 1-Wire protocol */
|
||||
iButtonProtocolDSGeneric,
|
||||
iButtonProtocolDSMax,
|
||||
} iButtonProtocolDallas;
|
||||
|
||||
extern const iButtonProtocolDallasBase* ibutton_protocols_dallas[];
|
||||
+9
-1
@@ -1,5 +1,6 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include "protocol_cyfral.h"
|
||||
|
||||
#define CYFRAL_DATA_SIZE sizeof(uint16_t)
|
||||
@@ -324,7 +325,13 @@ static LevelDuration protocol_cyfral_encoder_yield(ProtocolCyfral* proto) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const ProtocolBase protocol_cyfral = {
|
||||
static void protocol_cyfral_render_brief_data(ProtocolCyfral* proto, FuriString* result) {
|
||||
for(size_t i = 0; i < CYFRAL_DATA_SIZE; ++i) {
|
||||
furi_string_cat_printf(result, "%02X ", ((uint8_t*)&proto->data)[i]);
|
||||
}
|
||||
}
|
||||
|
||||
const ProtocolBase ibutton_protocol_misc_cyfral = {
|
||||
.name = "Cyfral",
|
||||
.manufacturer = "Cyfral",
|
||||
.data_size = CYFRAL_DATA_SIZE,
|
||||
@@ -341,4 +348,5 @@ const ProtocolBase protocol_cyfral = {
|
||||
.start = (ProtocolEncoderStart)protocol_cyfral_encoder_start,
|
||||
.yield = (ProtocolEncoderYield)protocol_cyfral_encoder_yield,
|
||||
},
|
||||
.render_brief_data = (ProtocolRenderData)protocol_cyfral_render_brief_data,
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
#include "toolbox/protocols/protocol.h"
|
||||
|
||||
extern const ProtocolBase ibutton_protocol_misc_cyfral;
|
||||
@@ -0,0 +1,295 @@
|
||||
#include "protocol_group_misc.h"
|
||||
|
||||
#include <furi_hal_rfid.h>
|
||||
#include <furi_hal_ibutton.h>
|
||||
|
||||
#include <toolbox/protocols/protocol_dict.h>
|
||||
|
||||
#include "protocol_group_misc_defs.h"
|
||||
|
||||
#define IBUTTON_MISC_READ_TIMEOUT 100
|
||||
|
||||
#define IBUTTON_MISC_DATA_KEY_KEY_COMMON "Data"
|
||||
|
||||
typedef struct {
|
||||
ProtocolDict* dict;
|
||||
ProtocolId emulate_id;
|
||||
} iButtonProtocolGroupMisc;
|
||||
|
||||
static iButtonProtocolGroupMisc* ibutton_protocol_group_misc_alloc() {
|
||||
iButtonProtocolGroupMisc* group = malloc(sizeof(iButtonProtocolGroupMisc));
|
||||
|
||||
group->dict = protocol_dict_alloc(ibutton_protocols_misc, iButtonProtocolMiscMax);
|
||||
group->emulate_id = PROTOCOL_NO;
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
static void ibutton_protocol_group_misc_free(iButtonProtocolGroupMisc* group) {
|
||||
protocol_dict_free(group->dict);
|
||||
free(group);
|
||||
}
|
||||
|
||||
static size_t ibutton_protocol_group_misc_get_max_data_size(iButtonProtocolGroupMisc* group) {
|
||||
return protocol_dict_get_max_data_size(group->dict);
|
||||
}
|
||||
|
||||
static bool ibutton_protocol_group_misc_get_id_by_name(
|
||||
iButtonProtocolGroupMisc* group,
|
||||
iButtonProtocolLocalId* id,
|
||||
const char* name) {
|
||||
const ProtocolId found_id = protocol_dict_get_protocol_by_name(group->dict, name);
|
||||
|
||||
if(found_id != PROTOCOL_NO) {
|
||||
*id = found_id;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static uint32_t ibutton_protocol_group_misc_get_features(
|
||||
iButtonProtocolGroupMisc* group,
|
||||
iButtonProtocolLocalId id) {
|
||||
UNUSED(group);
|
||||
UNUSED(id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char* ibutton_protocol_group_misc_get_manufacturer(
|
||||
iButtonProtocolGroupMisc* group,
|
||||
iButtonProtocolLocalId id) {
|
||||
return protocol_dict_get_manufacturer(group->dict, id);
|
||||
}
|
||||
|
||||
static const char* ibutton_protocol_group_misc_get_name(
|
||||
iButtonProtocolGroupMisc* group,
|
||||
iButtonProtocolLocalId id) {
|
||||
return protocol_dict_get_name(group->dict, id);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uint32_t last_dwt_value;
|
||||
FuriStreamBuffer* stream;
|
||||
} iButtonReadContext;
|
||||
|
||||
static void ibutton_protocols_comparator_callback(bool level, void* context) {
|
||||
iButtonReadContext* read_context = context;
|
||||
|
||||
uint32_t current_dwt_value = DWT->CYCCNT;
|
||||
|
||||
LevelDuration data =
|
||||
level_duration_make(level, current_dwt_value - read_context->last_dwt_value);
|
||||
furi_stream_buffer_send(read_context->stream, &data, sizeof(LevelDuration), 0);
|
||||
|
||||
read_context->last_dwt_value = current_dwt_value;
|
||||
}
|
||||
|
||||
static bool ibutton_protocol_group_misc_read(
|
||||
iButtonProtocolGroupMisc* group,
|
||||
iButtonProtocolData* data,
|
||||
iButtonProtocolLocalId* id) {
|
||||
bool result = false;
|
||||
|
||||
protocol_dict_decoders_start(group->dict);
|
||||
|
||||
furi_hal_rfid_pins_reset();
|
||||
// pulldown pull pin, we sense the signal through the analog part of the RFID schematic
|
||||
furi_hal_rfid_pin_pull_pulldown();
|
||||
|
||||
iButtonReadContext read_context = {
|
||||
.last_dwt_value = DWT->CYCCNT,
|
||||
.stream = furi_stream_buffer_alloc(sizeof(LevelDuration) * 512, 1),
|
||||
};
|
||||
|
||||
furi_hal_rfid_comp_set_callback(ibutton_protocols_comparator_callback, &read_context);
|
||||
furi_hal_rfid_comp_start();
|
||||
|
||||
const uint32_t tick_start = furi_get_tick();
|
||||
|
||||
for(;;) {
|
||||
LevelDuration level;
|
||||
size_t ret = furi_stream_buffer_receive(
|
||||
read_context.stream, &level, sizeof(LevelDuration), IBUTTON_MISC_READ_TIMEOUT);
|
||||
|
||||
if((furi_get_tick() - tick_start) > IBUTTON_MISC_READ_TIMEOUT) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(ret > 0) {
|
||||
ProtocolId decoded_index = protocol_dict_decoders_feed(
|
||||
group->dict, level_duration_get_level(level), level_duration_get_duration(level));
|
||||
|
||||
if(decoded_index == PROTOCOL_NO) continue;
|
||||
|
||||
*id = decoded_index;
|
||||
|
||||
protocol_dict_get_data(
|
||||
group->dict,
|
||||
decoded_index,
|
||||
data,
|
||||
protocol_dict_get_data_size(group->dict, decoded_index));
|
||||
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
furi_hal_rfid_comp_stop();
|
||||
furi_hal_rfid_comp_set_callback(NULL, NULL);
|
||||
furi_hal_rfid_pins_reset();
|
||||
|
||||
furi_stream_buffer_free(read_context.stream);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void ibutton_protocol_group_misc_emulate_callback(void* context) {
|
||||
iButtonProtocolGroupMisc* group = context;
|
||||
|
||||
const LevelDuration level_duration =
|
||||
protocol_dict_encoder_yield(group->dict, group->emulate_id);
|
||||
|
||||
furi_hal_ibutton_emulate_set_next(level_duration_get_duration(level_duration));
|
||||
furi_hal_ibutton_pin_write(level_duration_get_level(level_duration));
|
||||
}
|
||||
|
||||
static void ibutton_protocol_group_misc_emulate_start(
|
||||
iButtonProtocolGroupMisc* group,
|
||||
iButtonProtocolData* data,
|
||||
iButtonProtocolLocalId id) {
|
||||
group->emulate_id = id;
|
||||
protocol_dict_set_data(group->dict, id, data, protocol_dict_get_data_size(group->dict, id));
|
||||
protocol_dict_encoder_start(group->dict, group->emulate_id);
|
||||
|
||||
furi_hal_ibutton_pin_configure();
|
||||
furi_hal_ibutton_emulate_start(0, ibutton_protocol_group_misc_emulate_callback, group);
|
||||
}
|
||||
|
||||
static void ibutton_protocol_group_misc_emulate_stop(
|
||||
iButtonProtocolGroupMisc* group,
|
||||
iButtonProtocolData* data,
|
||||
iButtonProtocolLocalId id) {
|
||||
UNUSED(group);
|
||||
UNUSED(data);
|
||||
UNUSED(id);
|
||||
furi_hal_ibutton_emulate_stop();
|
||||
furi_hal_ibutton_pin_reset();
|
||||
}
|
||||
|
||||
static bool ibutton_protocol_group_misc_save(
|
||||
iButtonProtocolGroupMisc* group,
|
||||
const iButtonProtocolData* data,
|
||||
iButtonProtocolLocalId id,
|
||||
FlipperFormat* ff) {
|
||||
const size_t data_size = protocol_dict_get_data_size(group->dict, id);
|
||||
return flipper_format_write_hex(ff, IBUTTON_MISC_DATA_KEY_KEY_COMMON, data, data_size);
|
||||
}
|
||||
|
||||
static bool ibutton_protocol_group_misc_load(
|
||||
iButtonProtocolGroupMisc* group,
|
||||
iButtonProtocolData* data,
|
||||
iButtonProtocolLocalId id,
|
||||
uint32_t version,
|
||||
FlipperFormat* ff) {
|
||||
const size_t data_size = protocol_dict_get_data_size(group->dict, id);
|
||||
switch(version) {
|
||||
case 1:
|
||||
case 2:
|
||||
return flipper_format_read_hex(ff, IBUTTON_MISC_DATA_KEY_KEY_COMMON, data, data_size);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void ibutton_protocol_group_misc_render_data(
|
||||
iButtonProtocolGroupMisc* group,
|
||||
const iButtonProtocolData* data,
|
||||
iButtonProtocolLocalId id,
|
||||
FuriString* result) {
|
||||
const size_t data_size = protocol_dict_get_data_size(group->dict, id);
|
||||
protocol_dict_set_data(group->dict, id, data, data_size);
|
||||
protocol_dict_render_data(group->dict, result, id);
|
||||
}
|
||||
|
||||
static void ibutton_protocol_group_misc_render_brief_data(
|
||||
iButtonProtocolGroupMisc* group,
|
||||
const iButtonProtocolData* data,
|
||||
iButtonProtocolLocalId id,
|
||||
FuriString* result) {
|
||||
const size_t data_size = protocol_dict_get_data_size(group->dict, id);
|
||||
protocol_dict_set_data(group->dict, id, data, data_size);
|
||||
protocol_dict_render_brief_data(group->dict, result, id);
|
||||
}
|
||||
|
||||
static void ibutton_protocol_group_misc_render_error(
|
||||
iButtonProtocolGroupMisc* group,
|
||||
const iButtonProtocolData* data,
|
||||
iButtonProtocolLocalId id,
|
||||
FuriString* result) {
|
||||
UNUSED(group);
|
||||
UNUSED(data);
|
||||
UNUSED(id);
|
||||
UNUSED(result);
|
||||
}
|
||||
|
||||
static bool ibutton_protocol_group_misc_is_valid(
|
||||
iButtonProtocolGroupMisc* group,
|
||||
const iButtonProtocolData* data,
|
||||
iButtonProtocolLocalId id) {
|
||||
UNUSED(group);
|
||||
UNUSED(data);
|
||||
UNUSED(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ibutton_protocol_group_misc_get_editable_data(
|
||||
iButtonProtocolGroupMisc* group,
|
||||
iButtonProtocolData* data,
|
||||
iButtonProtocolLocalId id,
|
||||
iButtonEditableData* editable) {
|
||||
editable->ptr = data;
|
||||
editable->size = protocol_dict_get_data_size(group->dict, id);
|
||||
}
|
||||
|
||||
static void ibutton_protocol_group_misc_apply_edits(
|
||||
iButtonProtocolGroupMisc* group,
|
||||
iButtonProtocolData* data,
|
||||
iButtonProtocolLocalId id) {
|
||||
const size_t data_size = protocol_dict_get_data_size(group->dict, id);
|
||||
protocol_dict_set_data(group->dict, id, data, data_size);
|
||||
}
|
||||
|
||||
const iButtonProtocolGroupBase ibutton_protocol_group_misc = {
|
||||
.protocol_count = iButtonProtocolMiscMax,
|
||||
|
||||
.alloc = (iButtonProtocolGroupAllocFunc)ibutton_protocol_group_misc_alloc,
|
||||
.free = (iButtonProtocolGroupFreeFunc)ibutton_protocol_group_misc_free,
|
||||
|
||||
.get_max_data_size =
|
||||
(iButtonProtocolGropuGetSizeFunc)ibutton_protocol_group_misc_get_max_data_size,
|
||||
.get_id_by_name = (iButtonProtocolGroupGetIdFunc)ibutton_protocol_group_misc_get_id_by_name,
|
||||
.get_features = (iButtonProtocolGroupGetFeaturesFunc)ibutton_protocol_group_misc_get_features,
|
||||
|
||||
.get_manufacturer =
|
||||
(iButtonProtocolGroupGetStringFunc)ibutton_protocol_group_misc_get_manufacturer,
|
||||
.get_name = (iButtonProtocolGroupGetStringFunc)ibutton_protocol_group_misc_get_name,
|
||||
|
||||
.read = (iButtonProtocolGroupReadFunc)ibutton_protocol_group_misc_read,
|
||||
.write_blank = NULL,
|
||||
.write_copy = NULL,
|
||||
|
||||
.emulate_start = (iButtonProtocolGroupApplyFunc)ibutton_protocol_group_misc_emulate_start,
|
||||
.emulate_stop = (iButtonProtocolGroupApplyFunc)ibutton_protocol_group_misc_emulate_stop,
|
||||
|
||||
.save = (iButtonProtocolGroupSaveFunc)ibutton_protocol_group_misc_save,
|
||||
.load = (iButtonProtocolGroupLoadFunc)ibutton_protocol_group_misc_load,
|
||||
|
||||
.render_data = (iButtonProtocolGroupRenderFunc)ibutton_protocol_group_misc_render_data,
|
||||
.render_brief_data =
|
||||
(iButtonProtocolGroupRenderFunc)ibutton_protocol_group_misc_render_brief_data,
|
||||
.render_error = (iButtonProtocolGroupRenderFunc)ibutton_protocol_group_misc_render_error,
|
||||
|
||||
.is_valid = (iButtonProtocolGroupIsValidFunc)ibutton_protocol_group_misc_is_valid,
|
||||
.get_editable_data =
|
||||
(iButtonProtocolGroupGetDataFunc)ibutton_protocol_group_misc_get_editable_data,
|
||||
.apply_edits = (iButtonProtocolGroupApplyFunc)ibutton_protocol_group_misc_apply_edits,
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "../protocol_group_base.h"
|
||||
|
||||
extern const iButtonProtocolGroupBase ibutton_protocol_group_misc;
|
||||
@@ -0,0 +1,10 @@
|
||||
#include "protocol_group_misc_defs.h"
|
||||
|
||||
#include "protocol_cyfral.h"
|
||||
#include "protocol_metakom.h"
|
||||
|
||||
const ProtocolBase* ibutton_protocols_misc[] = {
|
||||
[iButtonProtocolMiscCyfral] = &ibutton_protocol_misc_cyfral,
|
||||
[iButtonProtocolMiscMetakom] = &ibutton_protocol_misc_metakom,
|
||||
/* Add new misc protocols here */
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <toolbox/protocols/protocol.h>
|
||||
|
||||
typedef enum {
|
||||
iButtonProtocolMiscCyfral,
|
||||
iButtonProtocolMiscMetakom,
|
||||
iButtonProtocolMiscMax,
|
||||
} iButtonProtocolMisc;
|
||||
|
||||
extern const ProtocolBase* ibutton_protocols_misc[];
|
||||
+9
-1
@@ -1,5 +1,6 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include "protocol_metakom.h"
|
||||
|
||||
#define METAKOM_DATA_SIZE sizeof(uint32_t)
|
||||
@@ -300,7 +301,13 @@ static LevelDuration protocol_metakom_encoder_yield(ProtocolMetakom* proto) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const ProtocolBase protocol_metakom = {
|
||||
static void protocol_metakom_render_brief_data(ProtocolMetakom* proto, FuriString* result) {
|
||||
for(size_t i = 0; i < METAKOM_DATA_SIZE; ++i) {
|
||||
furi_string_cat_printf(result, "%02X ", ((uint8_t*)&proto->data)[i]);
|
||||
}
|
||||
}
|
||||
|
||||
const ProtocolBase ibutton_protocol_misc_metakom = {
|
||||
.name = "Metakom",
|
||||
.manufacturer = "Metakom",
|
||||
.data_size = METAKOM_DATA_SIZE,
|
||||
@@ -317,4 +324,5 @@ const ProtocolBase protocol_metakom = {
|
||||
.start = (ProtocolEncoderStart)protocol_metakom_encoder_start,
|
||||
.yield = (ProtocolEncoderYield)protocol_metakom_encoder_yield,
|
||||
},
|
||||
.render_brief_data = (ProtocolRenderData)protocol_metakom_render_brief_data,
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
#include "toolbox/protocols/protocol.h"
|
||||
|
||||
extern const ProtocolBase ibutton_protocol_misc_metakom;
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef int32_t iButtonProtocolId;
|
||||
|
||||
enum {
|
||||
iButtonProtocolIdInvalid = -1,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
iButtonProtocolFeatureExtData = (1U << 0),
|
||||
iButtonProtocolFeatureWriteBlank = (1U << 1),
|
||||
iButtonProtocolFeatureWriteCopy = (1U << 2),
|
||||
} iButtonProtocolFeature;
|
||||
|
||||
typedef struct {
|
||||
uint8_t* ptr;
|
||||
size_t size;
|
||||
} iButtonEditableData;
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "protocol_common.h"
|
||||
|
||||
typedef void iButtonProtocolData;
|
||||
typedef int32_t iButtonProtocolLocalId;
|
||||
@@ -0,0 +1,104 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <flipper_format.h>
|
||||
|
||||
#include "protocol_common_i.h"
|
||||
|
||||
typedef void iButtonProtocolGroupData;
|
||||
typedef int32_t iButtonProtocolGroupId;
|
||||
|
||||
typedef iButtonProtocolGroupData* (*iButtonProtocolGroupAllocFunc)(void);
|
||||
|
||||
typedef void (*iButtonProtocolGroupFreeFunc)(iButtonProtocolGroupData*);
|
||||
|
||||
typedef void (*iButtonProtocolGroupRenderFunc)(
|
||||
iButtonProtocolGroupData*,
|
||||
const iButtonProtocolData*,
|
||||
iButtonProtocolLocalId,
|
||||
FuriString*);
|
||||
|
||||
typedef bool (*iButtonProtocolGroupIsValidFunc)(
|
||||
iButtonProtocolGroupData*,
|
||||
const iButtonProtocolData*,
|
||||
iButtonProtocolLocalId);
|
||||
|
||||
typedef void (*iButtonProtocolGroupGetDataFunc)(
|
||||
iButtonProtocolGroupData*,
|
||||
iButtonProtocolData*,
|
||||
iButtonProtocolLocalId,
|
||||
iButtonEditableData*);
|
||||
|
||||
typedef void (*iButtonProtocolGroupApplyFunc)(
|
||||
iButtonProtocolGroupData*,
|
||||
iButtonProtocolData*,
|
||||
iButtonProtocolLocalId);
|
||||
|
||||
typedef size_t (*iButtonProtocolGropuGetSizeFunc)(iButtonProtocolGroupData*);
|
||||
|
||||
typedef uint32_t (
|
||||
*iButtonProtocolGroupGetFeaturesFunc)(iButtonProtocolGroupData*, iButtonProtocolLocalId);
|
||||
|
||||
typedef const char* (
|
||||
*iButtonProtocolGroupGetStringFunc)(iButtonProtocolGroupData*, iButtonProtocolLocalId);
|
||||
|
||||
typedef bool (*iButtonProtocolGroupGetIdFunc)(
|
||||
iButtonProtocolGroupData*,
|
||||
iButtonProtocolLocalId*,
|
||||
const char*);
|
||||
|
||||
typedef bool (*iButtonProtocolGroupReadFunc)(
|
||||
iButtonProtocolGroupData*,
|
||||
iButtonProtocolData*,
|
||||
iButtonProtocolLocalId*);
|
||||
|
||||
typedef bool (*iButtonProtocolGroupWriteFunc)(
|
||||
iButtonProtocolGroupData*,
|
||||
iButtonProtocolData*,
|
||||
iButtonProtocolLocalId);
|
||||
|
||||
typedef bool (*iButtonProtocolGroupSaveFunc)(
|
||||
iButtonProtocolGroupData*,
|
||||
const iButtonProtocolData*,
|
||||
iButtonProtocolLocalId,
|
||||
FlipperFormat*);
|
||||
|
||||
typedef bool (*iButtonProtocolGroupLoadFunc)(
|
||||
iButtonProtocolGroupData*,
|
||||
iButtonProtocolData*,
|
||||
iButtonProtocolLocalId,
|
||||
uint32_t,
|
||||
FlipperFormat*);
|
||||
|
||||
typedef struct {
|
||||
const uint32_t protocol_count;
|
||||
|
||||
iButtonProtocolGroupAllocFunc alloc;
|
||||
iButtonProtocolGroupFreeFunc free;
|
||||
|
||||
iButtonProtocolGropuGetSizeFunc get_max_data_size;
|
||||
iButtonProtocolGroupGetIdFunc get_id_by_name;
|
||||
iButtonProtocolGroupGetFeaturesFunc get_features;
|
||||
|
||||
iButtonProtocolGroupGetStringFunc get_manufacturer;
|
||||
iButtonProtocolGroupGetStringFunc get_name;
|
||||
|
||||
iButtonProtocolGroupReadFunc read;
|
||||
iButtonProtocolGroupWriteFunc write_blank;
|
||||
iButtonProtocolGroupWriteFunc write_copy;
|
||||
|
||||
iButtonProtocolGroupApplyFunc emulate_start;
|
||||
iButtonProtocolGroupApplyFunc emulate_stop;
|
||||
|
||||
iButtonProtocolGroupSaveFunc save;
|
||||
iButtonProtocolGroupLoadFunc load;
|
||||
|
||||
iButtonProtocolGroupRenderFunc render_data;
|
||||
iButtonProtocolGroupRenderFunc render_brief_data;
|
||||
iButtonProtocolGroupRenderFunc render_error;
|
||||
|
||||
iButtonProtocolGroupIsValidFunc is_valid;
|
||||
iButtonProtocolGroupGetDataFunc get_editable_data;
|
||||
|
||||
iButtonProtocolGroupApplyFunc apply_edits;
|
||||
} iButtonProtocolGroupBase;
|
||||
@@ -0,0 +1,9 @@
|
||||
#include "protocol_group_defs.h"
|
||||
|
||||
#include "dallas/protocol_group_dallas.h"
|
||||
#include "misc/protocol_group_misc.h"
|
||||
|
||||
const iButtonProtocolGroupBase* ibutton_protocol_groups[] = {
|
||||
[iButtonProtocolGroupDallas] = &ibutton_protocol_group_dallas,
|
||||
[iButtonProtocolGroupMisc] = &ibutton_protocol_group_misc,
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "protocol_group_base.h"
|
||||
|
||||
typedef enum {
|
||||
iButtonProtocolGroupDallas,
|
||||
iButtonProtocolGroupMisc,
|
||||
iButtonProtocolGroupMax
|
||||
} iButtonProtocolGroup;
|
||||
|
||||
extern const iButtonProtocolGroupBase* ibutton_protocol_groups[];
|
||||
+13
-3
@@ -1,5 +1,7 @@
|
||||
Import("env")
|
||||
|
||||
from fbt.util import GLOB_FILE_EXCLUSION
|
||||
|
||||
env.Append(
|
||||
CPPPATH=[
|
||||
"#/lib/digital_signal",
|
||||
@@ -26,7 +28,6 @@ sources = []
|
||||
libs_recurse = [
|
||||
"digital_signal",
|
||||
"micro-ecc",
|
||||
"one_wire",
|
||||
"u8g2",
|
||||
"update_util",
|
||||
]
|
||||
@@ -35,12 +36,21 @@ for lib in libs_recurse:
|
||||
sources += libenv.GlobRecursive("*.c*", lib)
|
||||
|
||||
libs_plain = [
|
||||
"heatshrink",
|
||||
"nanopb",
|
||||
]
|
||||
|
||||
for lib in libs_plain:
|
||||
sources += Glob(lib + "/*.c*", source=True)
|
||||
sources += Glob(
|
||||
lib + "/*.c*",
|
||||
exclude=GLOB_FILE_EXCLUSION,
|
||||
source=True,
|
||||
)
|
||||
|
||||
sources += Glob(
|
||||
"heatshrink/heatshrink_*.c*",
|
||||
exclude=GLOB_FILE_EXCLUSION,
|
||||
source=True,
|
||||
)
|
||||
|
||||
lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources)
|
||||
libenv.Install("${LIB_DIST_DIR}", lib)
|
||||
|
||||
+62
-2
@@ -56,6 +56,8 @@ void nfc_worker_start(
|
||||
while(furi_hal_nfc_is_busy()) {
|
||||
furi_delay_ms(10);
|
||||
}
|
||||
furi_hal_nfc_deinit();
|
||||
furi_hal_nfc_init();
|
||||
|
||||
nfc_worker->callback = callback;
|
||||
nfc_worker->context = context;
|
||||
@@ -628,6 +630,32 @@ void nfc_worker_emulate_mf_ultralight(NfcWorker* nfc_worker) {
|
||||
}
|
||||
}
|
||||
|
||||
static bool nfc_worker_mf_get_b_key_from_sector_trailer(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
uint16_t sector,
|
||||
uint64_t key,
|
||||
uint64_t* found_key) {
|
||||
// Some access conditions allow reading B key via A key
|
||||
|
||||
uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector);
|
||||
|
||||
Crypto1 crypto = {};
|
||||
MfClassicBlock block_tmp = {};
|
||||
MfClassicAuthContext auth_context = {.sector = sector, .key_a = MF_CLASSIC_NO_KEY, .key_b = 0};
|
||||
|
||||
furi_hal_nfc_sleep();
|
||||
|
||||
if(mf_classic_auth_attempt(tx_rx, &crypto, &auth_context, key)) {
|
||||
if(mf_classic_read_block(tx_rx, &crypto, block, &block_tmp)) {
|
||||
*found_key = nfc_util_bytes2num(&block_tmp.value[10], sizeof(uint8_t) * 6);
|
||||
|
||||
return *found_key;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void nfc_worker_mf_classic_key_attack(
|
||||
NfcWorker* nfc_worker,
|
||||
uint64_t key,
|
||||
@@ -673,6 +701,16 @@ static void nfc_worker_mf_classic_key_attack(
|
||||
mf_classic_set_key_found(data, i, MfClassicKeyA, key);
|
||||
FURI_LOG_D(TAG, "Key found");
|
||||
nfc_worker->callback(NfcWorkerEventFoundKeyA, nfc_worker->context);
|
||||
|
||||
uint64_t found_key;
|
||||
if(nfc_worker_mf_get_b_key_from_sector_trailer(tx_rx, i, key, &found_key)) {
|
||||
FURI_LOG_D(TAG, "Found B key via reading sector %d", i);
|
||||
mf_classic_set_key_found(data, i, MfClassicKeyB, found_key);
|
||||
|
||||
if(nfc_worker->state == NfcWorkerStateMfClassicDictAttack) {
|
||||
nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!mf_classic_is_key_found(data, i, MfClassicKeyB)) {
|
||||
@@ -762,23 +800,45 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) {
|
||||
if(mf_classic_authenticate_skip_activate(
|
||||
&tx_rx, block_num, key, MfClassicKeyA, !deactivated, cuid)) {
|
||||
mf_classic_set_key_found(data, i, MfClassicKeyA, key);
|
||||
FURI_LOG_D(TAG, "Key found");
|
||||
FURI_LOG_D(TAG, "Key A found");
|
||||
nfc_worker->callback(NfcWorkerEventFoundKeyA, nfc_worker->context);
|
||||
|
||||
uint64_t found_key;
|
||||
if(nfc_worker_mf_get_b_key_from_sector_trailer(
|
||||
&tx_rx, i, key, &found_key)) {
|
||||
FURI_LOG_D(TAG, "Found B key via reading sector %d", i);
|
||||
mf_classic_set_key_found(data, i, MfClassicKeyB, found_key);
|
||||
|
||||
if(nfc_worker->state == NfcWorkerStateMfClassicDictAttack) {
|
||||
nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context);
|
||||
}
|
||||
|
||||
nfc_worker_mf_classic_key_attack(nfc_worker, found_key, &tx_rx, i + 1);
|
||||
break;
|
||||
}
|
||||
nfc_worker_mf_classic_key_attack(nfc_worker, key, &tx_rx, i + 1);
|
||||
}
|
||||
furi_hal_nfc_sleep();
|
||||
deactivated = true;
|
||||
} else {
|
||||
mf_classic_set_key_not_found(data, i, MfClassicKeyA);
|
||||
is_key_a_found = false;
|
||||
FURI_LOG_D(TAG, "Key %dA not found in attack", i);
|
||||
}
|
||||
if(!is_key_b_found) {
|
||||
is_key_b_found = mf_classic_is_key_found(data, i, MfClassicKeyB);
|
||||
if(mf_classic_authenticate_skip_activate(
|
||||
&tx_rx, block_num, key, MfClassicKeyB, !deactivated, cuid)) {
|
||||
FURI_LOG_D(TAG, "Key found");
|
||||
FURI_LOG_D(TAG, "Key B found");
|
||||
mf_classic_set_key_found(data, i, MfClassicKeyB, key);
|
||||
nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context);
|
||||
nfc_worker_mf_classic_key_attack(nfc_worker, key, &tx_rx, i + 1);
|
||||
}
|
||||
deactivated = true;
|
||||
} else {
|
||||
mf_classic_set_key_not_found(data, i, MfClassicKeyB);
|
||||
is_key_b_found = false;
|
||||
FURI_LOG_D(TAG, "Key %dB not found in attack", i);
|
||||
}
|
||||
if(is_key_a_found && is_key_b_found) break;
|
||||
if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <gui/modules/widget.h>
|
||||
#include <nfc_worker_i.h>
|
||||
|
||||
#include "furi_hal.h"
|
||||
#include <furi_hal.h>
|
||||
|
||||
#define ALL_IN_ONE_LAYOUT_UNKNOWN 0
|
||||
#define ALL_IN_ONE_LAYOUT_A 1
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <gui/modules/widget.h>
|
||||
#include <nfc_worker_i.h>
|
||||
|
||||
#include "furi_hal.h"
|
||||
#include <furi_hal.h>
|
||||
|
||||
static const MfClassicAuthContext plantain_keys_4k[] = {
|
||||
{.sector = 0, .key_a = 0xFFFFFFFFFFFF, .key_b = 0xFFFFFFFFFFFF},
|
||||
@@ -118,7 +118,7 @@ bool plantain_4k_parser_parse(NfcDeviceData* dev_data) {
|
||||
}
|
||||
|
||||
furi_string_printf(
|
||||
dev_data->parsed_data, "\e#Plantain\nN:%llu-\nBalance:%ld\n", card_number, balance);
|
||||
dev_data->parsed_data, "\e#Plantain\nN:%llu-\nBalance:%lu\n", card_number, balance);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <gui/modules/widget.h>
|
||||
#include <nfc_worker_i.h>
|
||||
|
||||
#include "furi_hal.h"
|
||||
#include <furi_hal.h>
|
||||
|
||||
static const MfClassicAuthContext plantain_keys[] = {
|
||||
{.sector = 0, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
@@ -91,7 +91,7 @@ bool plantain_parser_parse(NfcDeviceData* dev_data) {
|
||||
}
|
||||
|
||||
furi_string_printf(
|
||||
dev_data->parsed_data, "\e#Plantain\nN:%llu-\nBalance:%ld\n", card_number, balance);
|
||||
dev_data->parsed_data, "\e#Plantain\nN:%llu-\nBalance:%lu\n", card_number, balance);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ bool troika_4k_parser_parse(NfcDeviceData* dev_data) {
|
||||
number >>= 4;
|
||||
|
||||
furi_string_printf(
|
||||
dev_data->parsed_data, "\e#Troika\nNum: %ld\nBalance: %d rur.", number, balance);
|
||||
dev_data->parsed_data, "\e#Troika\nNum: %lu\nBalance: %u rur.", number, balance);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ bool troika_parser_parse(NfcDeviceData* dev_data) {
|
||||
number >>= 4;
|
||||
|
||||
furi_string_printf(
|
||||
dev_data->parsed_data, "\e#Troika\nNum: %ld\nBalance: %d rur.", number, balance);
|
||||
dev_data->parsed_data, "\e#Troika\nNum: %lu\nBalance: %u rur.", number, balance);
|
||||
troika_parsed = true;
|
||||
} while(false);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <gui/modules/widget.h>
|
||||
#include <nfc_worker_i.h>
|
||||
|
||||
#include "furi_hal.h"
|
||||
#include <furi_hal.h>
|
||||
|
||||
static const MfClassicAuthContext two_cities_keys_4k[] = {
|
||||
{.sector = 0, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
@@ -136,7 +136,7 @@ bool two_cities_parser_parse(NfcDeviceData* dev_data) {
|
||||
|
||||
furi_string_printf(
|
||||
dev_data->parsed_data,
|
||||
"\e#Troika+Plantain\nPN: %llu-\nPB: %ld rur.\nTN: %ld\nTB: %d rur.\n",
|
||||
"\e#Troika+Plantain\nPN: %llu-\nPB: %lu rur.\nTN: %lu\nTB: %u rur.\n",
|
||||
card_number,
|
||||
balance,
|
||||
troika_number,
|
||||
|
||||
@@ -7,10 +7,17 @@
|
||||
|
||||
#define TAG "MfClassic"
|
||||
|
||||
#define MF_CLASSIC_AUTH_KEY_A_CMD (0x60U)
|
||||
#define MF_CLASSIC_AUTH_KEY_B_CMD (0x61U)
|
||||
#define MF_CLASSIC_READ_BLOCK_CMD (0x30)
|
||||
#define MF_CLASSIC_WRITE_BLOCK_CMD (0xA0)
|
||||
#define MF_CLASSIC_ACK_CMD 0xAU
|
||||
#define MF_CLASSIC_NACK_BUF_VALID_CMD 0x0U
|
||||
#define MF_CLASSIC_NACK_BUF_INVALID_CMD 0x4U
|
||||
#define MF_CLASSIC_AUTH_KEY_A_CMD 0x60U
|
||||
#define MF_CLASSIC_AUTH_KEY_B_CMD 0x61U
|
||||
#define MF_CLASSIC_READ_BLOCK_CMD 0x30U
|
||||
#define MF_CLASSIC_WRITE_BLOCK_CMD 0xA0U
|
||||
#define MF_CLASSIC_TRANSFER_CMD 0xB0U
|
||||
#define MF_CLASSIC_DECREMENT_CMD 0xC0U
|
||||
#define MF_CLASSIC_INCREMENT_CMD 0xC1U
|
||||
#define MF_CLASSIC_RESTORE_CMD 0xC2U
|
||||
|
||||
const char* mf_classic_get_type_str(MfClassicType type) {
|
||||
if(type == MfClassicTypeMini) {
|
||||
@@ -217,8 +224,8 @@ void mf_classic_get_read_sectors_and_keys(
|
||||
uint8_t first_block = mf_classic_get_first_block_num_of_sector(i);
|
||||
uint8_t total_blocks_in_sec = mf_classic_get_blocks_num_in_sector(i);
|
||||
bool blocks_read = true;
|
||||
for(size_t i = first_block; i < first_block + total_blocks_in_sec; i++) {
|
||||
blocks_read = mf_classic_is_block_read(data, i);
|
||||
for(size_t j = first_block; j < first_block + total_blocks_in_sec; j++) {
|
||||
blocks_read = mf_classic_is_block_read(data, j);
|
||||
if(!blocks_read) break;
|
||||
}
|
||||
if(blocks_read) {
|
||||
@@ -284,6 +291,10 @@ bool mf_classic_is_allowed_access_data_block(
|
||||
uint8_t* sector_trailer =
|
||||
data->block[mf_classic_get_sector_trailer_num_by_block(block_num)].value;
|
||||
|
||||
if(block_num == 0 && action == MfClassicActionDataWrite) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t sector_block;
|
||||
if(block_num <= 128) {
|
||||
sector_block = block_num & 0x03;
|
||||
@@ -353,6 +364,16 @@ static bool mf_classic_is_allowed_access(
|
||||
}
|
||||
}
|
||||
|
||||
bool mf_classic_is_value_block(MfClassicData* data, uint8_t block_num) {
|
||||
// Check if key A can write, if it can, it's transport configuration, not data block
|
||||
return !mf_classic_is_allowed_access_data_block(
|
||||
data, block_num, MfClassicKeyA, MfClassicActionDataWrite) &&
|
||||
(mf_classic_is_allowed_access_data_block(
|
||||
data, block_num, MfClassicKeyB, MfClassicActionDataInc) ||
|
||||
mf_classic_is_allowed_access_data_block(
|
||||
data, block_num, MfClassicKeyB, MfClassicActionDataDec));
|
||||
}
|
||||
|
||||
bool mf_classic_check_card_type(FuriHalNfcADevData* data) {
|
||||
uint8_t ATQA0 = data->atqa[0];
|
||||
uint8_t ATQA1 = data->atqa[1];
|
||||
@@ -405,6 +426,36 @@ void mf_classic_reader_add_sector(
|
||||
}
|
||||
}
|
||||
|
||||
bool mf_classic_block_to_value(const uint8_t* block, int32_t* value, uint8_t* addr) {
|
||||
uint32_t v = *(uint32_t*)&block[0];
|
||||
uint32_t v_inv = *(uint32_t*)&block[4];
|
||||
uint32_t v1 = *(uint32_t*)&block[8];
|
||||
|
||||
bool val_checks =
|
||||
((v == v1) && (v == ~v_inv) && (block[12] == (~block[13] & 0xFF)) &&
|
||||
(block[14] == (~block[15] & 0xFF)) && (block[12] == block[14]));
|
||||
if(value) {
|
||||
*value = (int32_t)v;
|
||||
}
|
||||
if(addr) {
|
||||
*addr = block[12];
|
||||
}
|
||||
return val_checks;
|
||||
}
|
||||
|
||||
void mf_classic_value_to_block(int32_t value, uint8_t addr, uint8_t* block) {
|
||||
uint32_t v_inv = ~((uint32_t)value);
|
||||
|
||||
memcpy(block, &value, 4); //-V1086
|
||||
memcpy(block + 4, &v_inv, 4); //-V1086
|
||||
memcpy(block + 8, &value, 4); //-V1086
|
||||
|
||||
block[12] = addr;
|
||||
block[13] = ~addr & 0xFF;
|
||||
block[14] = addr;
|
||||
block[15] = ~addr & 0xFF;
|
||||
}
|
||||
|
||||
void mf_classic_auth_init_context(MfClassicAuthContext* auth_ctx, uint8_t sector) {
|
||||
furi_assert(auth_ctx);
|
||||
auth_ctx->sector = sector;
|
||||
@@ -498,6 +549,7 @@ bool mf_classic_authenticate_skip_activate(
|
||||
|
||||
bool mf_classic_auth_attempt(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
Crypto1* crypto,
|
||||
MfClassicAuthContext* auth_ctx,
|
||||
uint64_t key) {
|
||||
furi_assert(tx_rx);
|
||||
@@ -506,15 +558,14 @@ bool mf_classic_auth_attempt(
|
||||
bool need_halt = (auth_ctx->key_a == MF_CLASSIC_NO_KEY) &&
|
||||
(auth_ctx->key_b == MF_CLASSIC_NO_KEY);
|
||||
|
||||
Crypto1 crypto;
|
||||
if(auth_ctx->key_a == MF_CLASSIC_NO_KEY) {
|
||||
// Try AUTH with key A
|
||||
if(mf_classic_auth(
|
||||
tx_rx,
|
||||
mf_classic_get_first_block_num_of_sector(auth_ctx->sector),
|
||||
mf_classic_get_sector_trailer_block_num_by_sector(auth_ctx->sector),
|
||||
key,
|
||||
MfClassicKeyA,
|
||||
&crypto,
|
||||
crypto,
|
||||
false,
|
||||
0)) {
|
||||
auth_ctx->key_a = key;
|
||||
@@ -530,10 +581,10 @@ bool mf_classic_auth_attempt(
|
||||
// Try AUTH with key B
|
||||
if(mf_classic_auth(
|
||||
tx_rx,
|
||||
mf_classic_get_first_block_num_of_sector(auth_ctx->sector),
|
||||
mf_classic_get_sector_trailer_block_num_by_sector(auth_ctx->sector),
|
||||
key,
|
||||
MfClassicKeyB,
|
||||
&crypto,
|
||||
crypto,
|
||||
false,
|
||||
0)) {
|
||||
auth_ctx->key_b = key;
|
||||
@@ -557,8 +608,9 @@ bool mf_classic_read_block(
|
||||
uint8_t plain_cmd[4] = {MF_CLASSIC_READ_BLOCK_CMD, block_num, 0x00, 0x00};
|
||||
nfca_append_crc16(plain_cmd, 2);
|
||||
|
||||
crypto1_encrypt(crypto, NULL, plain_cmd, 4 * 8, tx_rx->tx_data, tx_rx->tx_parity);
|
||||
tx_rx->tx_bits = 4 * 9;
|
||||
crypto1_encrypt(
|
||||
crypto, NULL, plain_cmd, sizeof(plain_cmd) * 8, tx_rx->tx_data, tx_rx->tx_parity);
|
||||
tx_rx->tx_bits = sizeof(plain_cmd) * 8;
|
||||
tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw;
|
||||
|
||||
if(furi_hal_nfc_tx_rx(tx_rx, 50)) {
|
||||
@@ -603,7 +655,12 @@ void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, u
|
||||
if(!key_a_found) break;
|
||||
FURI_LOG_D(TAG, "Try to read blocks with key A");
|
||||
key = nfc_util_bytes2num(sec_tr->key_a, sizeof(sec_tr->key_a));
|
||||
if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyA, &crypto, false, 0)) break;
|
||||
if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyA, &crypto, false, 0)) {
|
||||
mf_classic_set_key_not_found(data, sec_num, MfClassicKeyA);
|
||||
FURI_LOG_D(TAG, "Key %dA not found in read", sec_num);
|
||||
break;
|
||||
}
|
||||
|
||||
for(size_t i = start_block; i < start_block + total_blocks; i++) {
|
||||
if(!mf_classic_is_block_read(data, i)) {
|
||||
if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) {
|
||||
@@ -612,7 +669,11 @@ void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, u
|
||||
} else if(i > start_block) {
|
||||
// Try to re-auth to read block in case prevous block was protected from read
|
||||
furi_hal_nfc_sleep();
|
||||
if(!mf_classic_auth(tx_rx, i, key, MfClassicKeyA, &crypto, false, 0)) break;
|
||||
if(!mf_classic_auth(tx_rx, i, key, MfClassicKeyA, &crypto, false, 0)) {
|
||||
mf_classic_set_key_not_found(data, sec_num, MfClassicKeyA);
|
||||
FURI_LOG_D(TAG, "Key %dA not found in read", sec_num);
|
||||
break;
|
||||
}
|
||||
if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) {
|
||||
mf_classic_set_block_read(data, i, &block_tmp);
|
||||
blocks_read++;
|
||||
@@ -627,9 +688,17 @@ void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, u
|
||||
do {
|
||||
if(blocks_read == total_blocks) break;
|
||||
if(!key_b_found) break;
|
||||
if(key_a_found) {
|
||||
furi_hal_nfc_sleep();
|
||||
}
|
||||
FURI_LOG_D(TAG, "Try to read blocks with key B");
|
||||
key = nfc_util_bytes2num(sec_tr->key_b, sizeof(sec_tr->key_b));
|
||||
if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyB, &crypto, false, 0)) break;
|
||||
if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyB, &crypto, false, 0)) {
|
||||
mf_classic_set_key_not_found(data, sec_num, MfClassicKeyB);
|
||||
FURI_LOG_D(TAG, "Key %dB not found in read", sec_num);
|
||||
break;
|
||||
}
|
||||
|
||||
for(size_t i = start_block; i < start_block + total_blocks; i++) {
|
||||
if(!mf_classic_is_block_read(data, i)) {
|
||||
if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) {
|
||||
@@ -638,7 +707,11 @@ void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, u
|
||||
} else if(i > start_block) {
|
||||
// Try to re-auth to read block in case prevous block was protected from read
|
||||
furi_hal_nfc_sleep();
|
||||
if(!mf_classic_auth(tx_rx, i, key, MfClassicKeyB, &crypto, false, 0)) break;
|
||||
if(!mf_classic_auth(tx_rx, i, key, MfClassicKeyB, &crypto, false, 0)) {
|
||||
mf_classic_set_key_not_found(data, sec_num, MfClassicKeyB);
|
||||
FURI_LOG_D(TAG, "Key %dB not found in read", sec_num);
|
||||
break;
|
||||
}
|
||||
if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) {
|
||||
mf_classic_set_block_read(data, i, &block_tmp);
|
||||
blocks_read++;
|
||||
@@ -783,6 +856,9 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_
|
||||
bool is_encrypted = false;
|
||||
uint8_t plain_data[MF_CLASSIC_MAX_DATA_SIZE];
|
||||
MfClassicKey access_key = MfClassicKeyA;
|
||||
// Used for decrement and increment - copy to block on transfer
|
||||
uint8_t transfer_buf[MF_CLASSIC_BLOCK_SIZE] = {};
|
||||
bool transfer_buf_valid = false;
|
||||
|
||||
// Read command
|
||||
while(!command_processed) { //-V654
|
||||
@@ -801,18 +877,25 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_
|
||||
crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data);
|
||||
}
|
||||
|
||||
if(plain_data[0] == 0x50 && plain_data[1] == 0x00) {
|
||||
// After increment, decrement or restore the only allowed command is transfer
|
||||
uint8_t cmd = plain_data[0];
|
||||
if(transfer_buf_valid && cmd != MF_CLASSIC_TRANSFER_CMD) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(cmd == 0x50 && plain_data[1] == 0x00) {
|
||||
FURI_LOG_T(TAG, "Halt received");
|
||||
furi_hal_nfc_listen_sleep();
|
||||
command_processed = true;
|
||||
break;
|
||||
} else if(plain_data[0] == 0x60 || plain_data[0] == 0x61) {
|
||||
}
|
||||
if(cmd == MF_CLASSIC_AUTH_KEY_A_CMD || cmd == MF_CLASSIC_AUTH_KEY_B_CMD) {
|
||||
uint8_t block = plain_data[1];
|
||||
uint64_t key = 0;
|
||||
uint8_t sector_trailer_block = mf_classic_get_sector_trailer_num_by_block(block);
|
||||
MfClassicSectorTrailer* sector_trailer =
|
||||
(MfClassicSectorTrailer*)emulator->data.block[sector_trailer_block].value;
|
||||
if(plain_data[0] == 0x60) {
|
||||
if(cmd == MF_CLASSIC_AUTH_KEY_A_CMD) {
|
||||
key = nfc_util_bytes2num(sector_trailer->key_a, 6);
|
||||
access_key = MfClassicKeyA;
|
||||
} else {
|
||||
@@ -830,9 +913,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_
|
||||
crypto1_word(&emulator->crypto, emulator->cuid ^ nonce, 0);
|
||||
memcpy(tx_rx->tx_data, nt, sizeof(nt));
|
||||
tx_rx->tx_parity[0] = 0;
|
||||
for(size_t i = 0; i < sizeof(nt); i++) {
|
||||
tx_rx->tx_parity[0] |= nfc_util_odd_parity8(nt[i]) << (7 - i);
|
||||
}
|
||||
nfc_util_odd_parity(tx_rx->tx_data, tx_rx->tx_parity, sizeof(nt));
|
||||
tx_rx->tx_bits = sizeof(nt) * 8;
|
||||
tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent;
|
||||
} else {
|
||||
@@ -853,7 +934,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_
|
||||
}
|
||||
|
||||
if(tx_rx->rx_bits != 64) {
|
||||
FURI_LOG_W(TAG, "Incorrect nr + ar");
|
||||
FURI_LOG_W(TAG, "Incorrect nr + ar length: %d", tx_rx->rx_bits);
|
||||
command_processed = true;
|
||||
break;
|
||||
}
|
||||
@@ -861,16 +942,6 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_
|
||||
uint32_t nr = nfc_util_bytes2num(tx_rx->rx_data, 4);
|
||||
uint32_t ar = nfc_util_bytes2num(&tx_rx->rx_data[4], 4);
|
||||
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"%08lx key%c block %d nt/nr/ar: %08lx %08lx %08lx",
|
||||
emulator->cuid,
|
||||
access_key == MfClassicKeyA ? 'A' : 'B',
|
||||
sector_trailer_block,
|
||||
nonce,
|
||||
nr,
|
||||
ar);
|
||||
|
||||
crypto1_word(&emulator->crypto, nr, 1);
|
||||
uint32_t cardRr = ar ^ crypto1_word(&emulator->crypto, 0, 0);
|
||||
if(cardRr != prng_successor(nonce, 64)) {
|
||||
@@ -881,22 +952,30 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_
|
||||
}
|
||||
|
||||
uint32_t ans = prng_successor(nonce, 96);
|
||||
uint8_t responce[4] = {};
|
||||
nfc_util_num2bytes(ans, 4, responce);
|
||||
uint8_t response[4] = {};
|
||||
nfc_util_num2bytes(ans, 4, response);
|
||||
crypto1_encrypt(
|
||||
&emulator->crypto,
|
||||
NULL,
|
||||
responce,
|
||||
sizeof(responce) * 8,
|
||||
response,
|
||||
sizeof(response) * 8,
|
||||
tx_rx->tx_data,
|
||||
tx_rx->tx_parity);
|
||||
tx_rx->tx_bits = sizeof(responce) * 8;
|
||||
tx_rx->tx_bits = sizeof(response) * 8;
|
||||
tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent;
|
||||
|
||||
is_encrypted = true;
|
||||
} else if(is_encrypted && plain_data[0] == 0x30) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!is_encrypted) {
|
||||
FURI_LOG_T(TAG, "Invalid command before auth session established: %02X", cmd);
|
||||
break;
|
||||
}
|
||||
|
||||
if(cmd == MF_CLASSIC_READ_BLOCK_CMD) {
|
||||
uint8_t block = plain_data[1];
|
||||
uint8_t block_data[18] = {};
|
||||
uint8_t block_data[MF_CLASSIC_BLOCK_SIZE + 2] = {};
|
||||
memcpy(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE);
|
||||
if(mf_classic_is_sector_trailer(block)) {
|
||||
if(!mf_classic_is_allowed_access(
|
||||
@@ -931,24 +1010,24 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_
|
||||
sizeof(block_data) * 8,
|
||||
tx_rx->tx_data,
|
||||
tx_rx->tx_parity);
|
||||
tx_rx->tx_bits = 18 * 8;
|
||||
tx_rx->tx_bits = (MF_CLASSIC_BLOCK_SIZE + 2) * 8;
|
||||
tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent;
|
||||
} else if(is_encrypted && plain_data[0] == 0xA0) {
|
||||
} else if(cmd == MF_CLASSIC_WRITE_BLOCK_CMD) {
|
||||
uint8_t block = plain_data[1];
|
||||
if(block > mf_classic_get_total_block_num(emulator->data.type)) {
|
||||
break;
|
||||
}
|
||||
// Send ACK
|
||||
uint8_t ack = 0x0A;
|
||||
uint8_t ack = MF_CLASSIC_ACK_CMD;
|
||||
crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity);
|
||||
tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent;
|
||||
tx_rx->tx_bits = 4;
|
||||
|
||||
if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break;
|
||||
if(tx_rx->rx_bits != 18 * 8) break;
|
||||
if(tx_rx->rx_bits != (MF_CLASSIC_BLOCK_SIZE + 2) * 8) break;
|
||||
|
||||
crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data);
|
||||
uint8_t block_data[16] = {};
|
||||
uint8_t block_data[MF_CLASSIC_BLOCK_SIZE] = {};
|
||||
memcpy(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE);
|
||||
if(mf_classic_is_sector_trailer(block)) {
|
||||
if(mf_classic_is_allowed_access(
|
||||
@@ -967,6 +1046,8 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_
|
||||
if(mf_classic_is_allowed_access(
|
||||
emulator, block, access_key, MfClassicActionDataWrite)) {
|
||||
memcpy(block_data, plain_data, MF_CLASSIC_BLOCK_SIZE);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(memcmp(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE) != 0) {
|
||||
@@ -974,19 +1055,83 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_
|
||||
emulator->data_changed = true;
|
||||
}
|
||||
// Send ACK
|
||||
ack = 0x0A;
|
||||
ack = MF_CLASSIC_ACK_CMD;
|
||||
crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity);
|
||||
tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent;
|
||||
tx_rx->tx_bits = 4;
|
||||
} else if(
|
||||
cmd == MF_CLASSIC_DECREMENT_CMD || cmd == MF_CLASSIC_INCREMENT_CMD ||
|
||||
cmd == MF_CLASSIC_RESTORE_CMD) {
|
||||
uint8_t block = plain_data[1];
|
||||
|
||||
if(block > mf_classic_get_total_block_num(emulator->data.type)) {
|
||||
break;
|
||||
}
|
||||
|
||||
MfClassicAction action = MfClassicActionDataDec;
|
||||
if(cmd == MF_CLASSIC_INCREMENT_CMD) {
|
||||
action = MfClassicActionDataInc;
|
||||
}
|
||||
if(!mf_classic_is_allowed_access(emulator, block, access_key, action)) {
|
||||
break;
|
||||
}
|
||||
|
||||
int32_t prev_value;
|
||||
uint8_t addr;
|
||||
if(!mf_classic_block_to_value(emulator->data.block[block].value, &prev_value, &addr)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Send ACK
|
||||
uint8_t ack = MF_CLASSIC_ACK_CMD;
|
||||
crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity);
|
||||
tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent;
|
||||
tx_rx->tx_bits = 4;
|
||||
|
||||
if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break;
|
||||
if(tx_rx->rx_bits != (sizeof(int32_t) + 2) * 8) break;
|
||||
|
||||
crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data);
|
||||
int32_t value = *(int32_t*)&plain_data[0];
|
||||
if(value < 0) {
|
||||
value = -value;
|
||||
}
|
||||
if(cmd == MF_CLASSIC_DECREMENT_CMD) {
|
||||
value = -value;
|
||||
} else if(cmd == MF_CLASSIC_RESTORE_CMD) {
|
||||
value = 0;
|
||||
}
|
||||
|
||||
mf_classic_value_to_block(prev_value + value, addr, transfer_buf);
|
||||
transfer_buf_valid = true;
|
||||
// Commands do not ACK
|
||||
tx_rx->tx_bits = 0;
|
||||
} else if(cmd == MF_CLASSIC_TRANSFER_CMD) {
|
||||
uint8_t block = plain_data[1];
|
||||
if(!mf_classic_is_allowed_access(emulator, block, access_key, MfClassicActionDataDec)) {
|
||||
break;
|
||||
}
|
||||
if(memcmp(transfer_buf, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE) !=
|
||||
0) {
|
||||
memcpy(emulator->data.block[block].value, transfer_buf, MF_CLASSIC_BLOCK_SIZE);
|
||||
emulator->data_changed = true;
|
||||
}
|
||||
transfer_buf_valid = false;
|
||||
|
||||
uint8_t ack = MF_CLASSIC_ACK_CMD;
|
||||
crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity);
|
||||
tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent;
|
||||
tx_rx->tx_bits = 4;
|
||||
} else {
|
||||
// Unknown command
|
||||
FURI_LOG_T(TAG, "Unknown command: %02X", cmd);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!command_processed) {
|
||||
// Send NACK
|
||||
uint8_t nack = 0x04;
|
||||
uint8_t nack = transfer_buf_valid ? MF_CLASSIC_NACK_BUF_VALID_CMD :
|
||||
MF_CLASSIC_NACK_BUF_INVALID_CMD;
|
||||
if(is_encrypted) {
|
||||
crypto1_encrypt(&emulator->crypto, NULL, &nack, 4, tx_rx->tx_data, tx_rx->tx_parity);
|
||||
} else {
|
||||
@@ -1000,37 +1145,50 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_
|
||||
return true;
|
||||
}
|
||||
|
||||
void mf_classic_halt(FuriHalNfcTxRxContext* tx_rx, Crypto1* crypto) {
|
||||
furi_assert(tx_rx);
|
||||
|
||||
uint8_t plain_data[4] = {0x50, 0x00, 0x00, 0x00};
|
||||
|
||||
nfca_append_crc16(plain_data, 2);
|
||||
if(crypto) {
|
||||
crypto1_encrypt(
|
||||
crypto, NULL, plain_data, sizeof(plain_data) * 8, tx_rx->tx_data, tx_rx->tx_parity);
|
||||
} else {
|
||||
memcpy(tx_rx->tx_data, plain_data, sizeof(plain_data));
|
||||
nfc_util_odd_parity(tx_rx->tx_data, tx_rx->tx_parity, sizeof(plain_data));
|
||||
}
|
||||
|
||||
tx_rx->tx_bits = sizeof(plain_data) * 8;
|
||||
tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw;
|
||||
furi_hal_nfc_tx_rx(tx_rx, 50);
|
||||
}
|
||||
|
||||
bool mf_classic_write_block(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
MfClassicBlock* src_block,
|
||||
Crypto1* crypto,
|
||||
uint8_t block_num,
|
||||
MfClassicKey key_type,
|
||||
uint64_t key) {
|
||||
MfClassicBlock* src_block) {
|
||||
furi_assert(tx_rx);
|
||||
furi_assert(crypto);
|
||||
furi_assert(src_block);
|
||||
|
||||
Crypto1 crypto = {};
|
||||
uint8_t plain_data[18] = {};
|
||||
uint8_t resp = 0;
|
||||
bool write_success = false;
|
||||
uint8_t plain_data[MF_CLASSIC_BLOCK_SIZE + 2] = {};
|
||||
uint8_t resp;
|
||||
|
||||
do {
|
||||
furi_hal_nfc_sleep();
|
||||
if(!mf_classic_auth(tx_rx, block_num, key, key_type, &crypto, false, 0)) {
|
||||
FURI_LOG_D(TAG, "Auth fail");
|
||||
break;
|
||||
}
|
||||
// Send write command
|
||||
plain_data[0] = MF_CLASSIC_WRITE_BLOCK_CMD;
|
||||
plain_data[1] = block_num;
|
||||
nfca_append_crc16(plain_data, 2);
|
||||
crypto1_encrypt(&crypto, NULL, plain_data, 4 * 8, tx_rx->tx_data, tx_rx->tx_parity);
|
||||
crypto1_encrypt(crypto, NULL, plain_data, 4 * 8, tx_rx->tx_data, tx_rx->tx_parity);
|
||||
tx_rx->tx_bits = 4 * 8;
|
||||
tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw;
|
||||
|
||||
if(furi_hal_nfc_tx_rx(tx_rx, 50)) {
|
||||
if(tx_rx->rx_bits == 4) {
|
||||
crypto1_decrypt(&crypto, tx_rx->rx_data, 4, &resp);
|
||||
crypto1_decrypt(crypto, tx_rx->rx_data, 4, &resp);
|
||||
if(resp != 0x0A) {
|
||||
FURI_LOG_D(TAG, "NACK received on write cmd: %02X", resp);
|
||||
break;
|
||||
@@ -1048,7 +1206,7 @@ bool mf_classic_write_block(
|
||||
memcpy(plain_data, src_block->value, MF_CLASSIC_BLOCK_SIZE);
|
||||
nfca_append_crc16(plain_data, MF_CLASSIC_BLOCK_SIZE);
|
||||
crypto1_encrypt(
|
||||
&crypto,
|
||||
crypto,
|
||||
NULL,
|
||||
plain_data,
|
||||
(MF_CLASSIC_BLOCK_SIZE + 2) * 8,
|
||||
@@ -1058,8 +1216,8 @@ bool mf_classic_write_block(
|
||||
tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw;
|
||||
if(furi_hal_nfc_tx_rx(tx_rx, 50)) {
|
||||
if(tx_rx->rx_bits == 4) {
|
||||
crypto1_decrypt(&crypto, tx_rx->rx_data, 4, &resp);
|
||||
if(resp != 0x0A) {
|
||||
crypto1_decrypt(crypto, tx_rx->rx_data, 4, &resp);
|
||||
if(resp != MF_CLASSIC_ACK_CMD) {
|
||||
FURI_LOG_D(TAG, "NACK received on sending data");
|
||||
break;
|
||||
}
|
||||
@@ -1071,22 +1229,200 @@ bool mf_classic_write_block(
|
||||
FURI_LOG_D(TAG, "Failed to send data");
|
||||
break;
|
||||
}
|
||||
write_success = true;
|
||||
|
||||
// Send Halt
|
||||
plain_data[0] = 0x50;
|
||||
plain_data[1] = 0x00;
|
||||
nfca_append_crc16(plain_data, 2);
|
||||
crypto1_encrypt(&crypto, NULL, plain_data, 2 * 8, tx_rx->tx_data, tx_rx->tx_parity);
|
||||
tx_rx->tx_bits = 2 * 8;
|
||||
tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw;
|
||||
// No response is expected
|
||||
furi_hal_nfc_tx_rx(tx_rx, 50);
|
||||
write_success = true;
|
||||
} while(false);
|
||||
|
||||
return write_success;
|
||||
}
|
||||
|
||||
bool mf_classic_auth_write_block(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
MfClassicBlock* src_block,
|
||||
uint8_t block_num,
|
||||
MfClassicKey key_type,
|
||||
uint64_t key) {
|
||||
furi_assert(tx_rx);
|
||||
furi_assert(src_block);
|
||||
|
||||
Crypto1 crypto = {};
|
||||
bool write_success = false;
|
||||
|
||||
do {
|
||||
furi_hal_nfc_sleep();
|
||||
if(!mf_classic_auth(tx_rx, block_num, key, key_type, &crypto, false, 0)) {
|
||||
FURI_LOG_D(TAG, "Auth fail");
|
||||
break;
|
||||
}
|
||||
|
||||
if(!mf_classic_write_block(tx_rx, &crypto, block_num, src_block)) {
|
||||
FURI_LOG_D(TAG, "Write fail");
|
||||
break;
|
||||
}
|
||||
write_success = true;
|
||||
|
||||
mf_classic_halt(tx_rx, &crypto);
|
||||
} while(false);
|
||||
|
||||
return write_success;
|
||||
}
|
||||
|
||||
bool mf_classic_transfer(FuriHalNfcTxRxContext* tx_rx, Crypto1* crypto, uint8_t block_num) {
|
||||
furi_assert(tx_rx);
|
||||
furi_assert(crypto);
|
||||
|
||||
// Send transfer command
|
||||
uint8_t plain_data[4] = {MF_CLASSIC_TRANSFER_CMD, block_num, 0, 0};
|
||||
uint8_t resp = 0;
|
||||
bool transfer_success = false;
|
||||
|
||||
nfca_append_crc16(plain_data, 2);
|
||||
crypto1_encrypt(
|
||||
crypto, NULL, plain_data, sizeof(plain_data) * 8, tx_rx->tx_data, tx_rx->tx_parity);
|
||||
tx_rx->tx_bits = sizeof(plain_data) * 8;
|
||||
tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw;
|
||||
|
||||
do {
|
||||
if(furi_hal_nfc_tx_rx(tx_rx, 50)) {
|
||||
if(tx_rx->rx_bits == 4) {
|
||||
crypto1_decrypt(crypto, tx_rx->rx_data, 4, &resp);
|
||||
if(resp != 0x0A) {
|
||||
FURI_LOG_D(TAG, "NACK received on transfer cmd: %02X", resp);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Not ACK received");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Failed to send transfer cmd");
|
||||
break;
|
||||
}
|
||||
|
||||
transfer_success = true;
|
||||
} while(false);
|
||||
|
||||
return transfer_success;
|
||||
}
|
||||
|
||||
bool mf_classic_value_cmd(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
Crypto1* crypto,
|
||||
uint8_t block_num,
|
||||
uint8_t cmd,
|
||||
int32_t d_value) {
|
||||
furi_assert(tx_rx);
|
||||
furi_assert(crypto);
|
||||
furi_assert(
|
||||
cmd == MF_CLASSIC_INCREMENT_CMD || cmd == MF_CLASSIC_DECREMENT_CMD ||
|
||||
cmd == MF_CLASSIC_RESTORE_CMD);
|
||||
furi_assert(d_value >= 0);
|
||||
|
||||
uint8_t plain_data[sizeof(d_value) + 2] = {};
|
||||
uint8_t resp = 0;
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
// Send cmd
|
||||
plain_data[0] = cmd;
|
||||
plain_data[1] = block_num;
|
||||
nfca_append_crc16(plain_data, 2);
|
||||
crypto1_encrypt(crypto, NULL, plain_data, 4 * 8, tx_rx->tx_data, tx_rx->tx_parity);
|
||||
tx_rx->tx_bits = 4 * 8;
|
||||
tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw;
|
||||
|
||||
if(furi_hal_nfc_tx_rx(tx_rx, 50)) {
|
||||
if(tx_rx->rx_bits == 4) {
|
||||
crypto1_decrypt(crypto, tx_rx->rx_data, 4, &resp);
|
||||
if(resp != 0x0A) {
|
||||
FURI_LOG_D(TAG, "NACK received on write cmd: %02X", resp);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Not ACK received");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Failed to send write cmd");
|
||||
break;
|
||||
}
|
||||
|
||||
// Send data
|
||||
memcpy(plain_data, &d_value, sizeof(d_value));
|
||||
nfca_append_crc16(plain_data, sizeof(d_value));
|
||||
crypto1_encrypt(
|
||||
crypto, NULL, plain_data, (sizeof(d_value) + 2) * 8, tx_rx->tx_data, tx_rx->tx_parity);
|
||||
tx_rx->tx_bits = (sizeof(d_value) + 2) * 8;
|
||||
tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw;
|
||||
// inc, dec, restore do not ACK, but they do NACK
|
||||
if(furi_hal_nfc_tx_rx(tx_rx, 50)) {
|
||||
if(tx_rx->rx_bits == 4) {
|
||||
crypto1_decrypt(crypto, tx_rx->rx_data, 4, &resp);
|
||||
if(resp != 0x0A) {
|
||||
FURI_LOG_D(TAG, "NACK received on transfer cmd: %02X", resp);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Not NACK received");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
success = true;
|
||||
|
||||
} while(false);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool mf_classic_value_cmd_full(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
MfClassicBlock* src_block,
|
||||
uint8_t block_num,
|
||||
MfClassicKey key_type,
|
||||
uint64_t key,
|
||||
int32_t d_value) {
|
||||
furi_assert(tx_rx);
|
||||
furi_assert(src_block);
|
||||
|
||||
Crypto1 crypto = {};
|
||||
uint8_t cmd;
|
||||
bool success = false;
|
||||
|
||||
if(d_value > 0) {
|
||||
cmd = MF_CLASSIC_INCREMENT_CMD;
|
||||
} else if(d_value < 0) {
|
||||
cmd = MF_CLASSIC_DECREMENT_CMD;
|
||||
d_value = -d_value;
|
||||
} else {
|
||||
cmd = MF_CLASSIC_RESTORE_CMD;
|
||||
}
|
||||
|
||||
do {
|
||||
furi_hal_nfc_sleep();
|
||||
if(!mf_classic_auth(tx_rx, block_num, key, key_type, &crypto, false, 0)) {
|
||||
FURI_LOG_D(TAG, "Value cmd auth fail");
|
||||
break;
|
||||
}
|
||||
if(!mf_classic_value_cmd(tx_rx, &crypto, block_num, cmd, d_value)) {
|
||||
FURI_LOG_D(TAG, "Value cmd inc/dec/res fail");
|
||||
break;
|
||||
}
|
||||
|
||||
if(!mf_classic_transfer(tx_rx, &crypto, block_num)) {
|
||||
FURI_LOG_D(TAG, "Value cmd transfer fail");
|
||||
break;
|
||||
}
|
||||
|
||||
success = true;
|
||||
|
||||
// Send Halt
|
||||
mf_classic_halt(tx_rx, &crypto);
|
||||
} while(false);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool mf_classic_write_sector(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
MfClassicData* dest_data,
|
||||
@@ -1107,31 +1443,99 @@ bool mf_classic_write_sector(
|
||||
// Compare blocks
|
||||
if(memcmp(dest_data->block[i].value, src_data->block[i].value, MF_CLASSIC_BLOCK_SIZE) !=
|
||||
0) {
|
||||
bool key_a_write_allowed = mf_classic_is_allowed_access_data_block(
|
||||
dest_data, i, MfClassicKeyA, MfClassicActionDataWrite);
|
||||
bool key_b_write_allowed = mf_classic_is_allowed_access_data_block(
|
||||
dest_data, i, MfClassicKeyB, MfClassicActionDataWrite);
|
||||
if(mf_classic_is_value_block(dest_data, i)) {
|
||||
bool key_a_inc_allowed = mf_classic_is_allowed_access_data_block(
|
||||
dest_data, i, MfClassicKeyA, MfClassicActionDataInc);
|
||||
bool key_b_inc_allowed = mf_classic_is_allowed_access_data_block(
|
||||
dest_data, i, MfClassicKeyB, MfClassicActionDataInc);
|
||||
bool key_a_dec_allowed = mf_classic_is_allowed_access_data_block(
|
||||
dest_data, i, MfClassicKeyA, MfClassicActionDataDec);
|
||||
bool key_b_dec_allowed = mf_classic_is_allowed_access_data_block(
|
||||
dest_data, i, MfClassicKeyB, MfClassicActionDataDec);
|
||||
|
||||
if(key_a_found && key_a_write_allowed) {
|
||||
FURI_LOG_I(TAG, "Writing block %d with key A", i);
|
||||
uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6);
|
||||
if(!mf_classic_write_block(tx_rx, &src_data->block[i], i, MfClassicKeyA, key)) {
|
||||
FURI_LOG_E(TAG, "Failed to write block %d", i);
|
||||
write_success = false;
|
||||
break;
|
||||
}
|
||||
} else if(key_b_found && key_b_write_allowed) {
|
||||
FURI_LOG_I(TAG, "Writing block %d with key A", i);
|
||||
uint64_t key = nfc_util_bytes2num(sec_tr->key_b, 6);
|
||||
if(!mf_classic_write_block(tx_rx, &src_data->block[i], i, MfClassicKeyB, key)) {
|
||||
FURI_LOG_E(TAG, "Failed to write block %d", i);
|
||||
write_success = false;
|
||||
break;
|
||||
int32_t src_value, dst_value;
|
||||
|
||||
mf_classic_block_to_value(src_data->block[i].value, &src_value, NULL);
|
||||
mf_classic_block_to_value(dest_data->block[i].value, &dst_value, NULL);
|
||||
|
||||
int32_t diff = src_value - dst_value;
|
||||
|
||||
if(diff > 0) {
|
||||
if(key_a_found && key_a_inc_allowed) {
|
||||
FURI_LOG_I(TAG, "Incrementing block %d with key A by %ld", i, diff);
|
||||
uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6);
|
||||
if(!mf_classic_value_cmd_full(
|
||||
tx_rx, &src_data->block[i], i, MfClassicKeyA, key, diff)) {
|
||||
FURI_LOG_E(TAG, "Failed to increment block %d", i);
|
||||
write_success = false;
|
||||
break;
|
||||
}
|
||||
} else if(key_b_found && key_b_inc_allowed) {
|
||||
FURI_LOG_I(TAG, "Incrementing block %d with key B by %ld", i, diff);
|
||||
uint64_t key = nfc_util_bytes2num(sec_tr->key_b, 6);
|
||||
if(!mf_classic_value_cmd_full(
|
||||
tx_rx, &src_data->block[i], i, MfClassicKeyB, key, diff)) {
|
||||
FURI_LOG_E(TAG, "Failed to increment block %d", i);
|
||||
write_success = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Failed to increment block %d", i);
|
||||
}
|
||||
} else if(diff < 0) {
|
||||
if(key_a_found && key_a_dec_allowed) {
|
||||
FURI_LOG_I(TAG, "Decrementing block %d with key A by %ld", i, -diff);
|
||||
uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6);
|
||||
if(!mf_classic_value_cmd_full(
|
||||
tx_rx, &src_data->block[i], i, MfClassicKeyA, key, diff)) {
|
||||
FURI_LOG_E(TAG, "Failed to decrement block %d", i);
|
||||
write_success = false;
|
||||
break;
|
||||
}
|
||||
} else if(key_b_found && key_b_dec_allowed) {
|
||||
FURI_LOG_I(TAG, "Decrementing block %d with key B by %ld", i, diff);
|
||||
uint64_t key = nfc_util_bytes2num(sec_tr->key_b, 6);
|
||||
if(!mf_classic_value_cmd_full(
|
||||
tx_rx, &src_data->block[i], i, MfClassicKeyB, key, diff)) {
|
||||
FURI_LOG_E(TAG, "Failed to decrement block %d", i);
|
||||
write_success = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Failed to decrement block %d", i);
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Value block %d address changed, cannot write it", i);
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Failed to find key with write access");
|
||||
write_success = false;
|
||||
break;
|
||||
bool key_a_write_allowed = mf_classic_is_allowed_access_data_block(
|
||||
dest_data, i, MfClassicKeyA, MfClassicActionDataWrite);
|
||||
bool key_b_write_allowed = mf_classic_is_allowed_access_data_block(
|
||||
dest_data, i, MfClassicKeyB, MfClassicActionDataWrite);
|
||||
|
||||
if(key_a_found && key_a_write_allowed) {
|
||||
FURI_LOG_I(TAG, "Writing block %d with key A", i);
|
||||
uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6);
|
||||
if(!mf_classic_auth_write_block(
|
||||
tx_rx, &src_data->block[i], i, MfClassicKeyA, key)) {
|
||||
FURI_LOG_E(TAG, "Failed to write block %d", i);
|
||||
write_success = false;
|
||||
break;
|
||||
}
|
||||
} else if(key_b_found && key_b_write_allowed) {
|
||||
FURI_LOG_I(TAG, "Writing block %d with key A", i);
|
||||
uint64_t key = nfc_util_bytes2num(sec_tr->key_b, 6);
|
||||
if(!mf_classic_auth_write_block(
|
||||
tx_rx, &src_data->block[i], i, MfClassicKeyB, key)) {
|
||||
FURI_LOG_E(TAG, "Failed to write block %d", i);
|
||||
write_success = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Failed to find key with write access");
|
||||
write_success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Blocks %d are equal", i);
|
||||
|
||||
@@ -120,6 +120,12 @@ bool mf_classic_is_allowed_access_data_block(
|
||||
MfClassicKey key,
|
||||
MfClassicAction action);
|
||||
|
||||
bool mf_classic_is_value_block(MfClassicData* data, uint8_t block_num);
|
||||
|
||||
bool mf_classic_block_to_value(const uint8_t* block, int32_t* value, uint8_t* addr);
|
||||
|
||||
void mf_classic_value_to_block(int32_t value, uint8_t addr, uint8_t* block);
|
||||
|
||||
bool mf_classic_is_key_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type);
|
||||
|
||||
void mf_classic_set_key_found(
|
||||
@@ -168,6 +174,7 @@ bool mf_classic_authenticate_skip_activate(
|
||||
|
||||
bool mf_classic_auth_attempt(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
Crypto1* crypto,
|
||||
MfClassicAuthContext* auth_ctx,
|
||||
uint64_t key);
|
||||
|
||||
@@ -177,6 +184,12 @@ void mf_classic_reader_add_sector(
|
||||
uint64_t key_a,
|
||||
uint64_t key_b);
|
||||
|
||||
bool mf_classic_read_block(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
Crypto1* crypto,
|
||||
uint8_t block_num,
|
||||
MfClassicBlock* block);
|
||||
|
||||
void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, uint8_t sec_num);
|
||||
|
||||
uint8_t mf_classic_read_card(
|
||||
@@ -188,13 +201,38 @@ uint8_t mf_classic_update_card(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data
|
||||
|
||||
bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_rx);
|
||||
|
||||
void mf_classic_halt(FuriHalNfcTxRxContext* tx_rx, Crypto1* crypto);
|
||||
|
||||
bool mf_classic_write_block(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
Crypto1* crypto,
|
||||
uint8_t block_num,
|
||||
MfClassicBlock* src_block);
|
||||
|
||||
bool mf_classic_auth_write_block(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
MfClassicBlock* src_block,
|
||||
uint8_t block_num,
|
||||
MfClassicKey key_type,
|
||||
uint64_t key);
|
||||
|
||||
bool mf_classic_transfer(FuriHalNfcTxRxContext* tx_rx, Crypto1* crypto, uint8_t block_num);
|
||||
|
||||
bool mf_classic_value_cmd(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
Crypto1* crypto,
|
||||
uint8_t block_num,
|
||||
uint8_t cmd,
|
||||
int32_t d_value);
|
||||
|
||||
bool mf_classic_value_cmd_full(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
MfClassicBlock* src_block,
|
||||
uint8_t block_num,
|
||||
MfClassicKey key_type,
|
||||
uint64_t key,
|
||||
int32_t d_value);
|
||||
|
||||
bool mf_classic_write_sector(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
MfClassicData* dest_data,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "mifare_ultralight.h"
|
||||
#include "nfc_util.h"
|
||||
#include <furi.h>
|
||||
#include "furi_hal_nfc.h"
|
||||
#include <furi_hal_nfc.h>
|
||||
|
||||
#define TAG "MfUltralight"
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ void nfc_util_num2bytes(uint64_t src, uint8_t len, uint8_t* dest) {
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t nfc_util_bytes2num(uint8_t* src, uint8_t len) {
|
||||
uint64_t nfc_util_bytes2num(const uint8_t* src, uint8_t len) {
|
||||
furi_assert(src);
|
||||
furi_assert(len <= 8);
|
||||
|
||||
@@ -45,3 +45,26 @@ uint8_t nfc_util_even_parity32(uint32_t data) {
|
||||
uint8_t nfc_util_odd_parity8(uint8_t data) {
|
||||
return nfc_util_odd_byte_parity[data];
|
||||
}
|
||||
|
||||
void nfc_util_odd_parity(const uint8_t* src, uint8_t* dst, uint8_t len) {
|
||||
furi_assert(src);
|
||||
furi_assert(dst);
|
||||
|
||||
uint8_t parity = 0;
|
||||
uint8_t bit = 0;
|
||||
while(len--) {
|
||||
parity |= nfc_util_odd_parity8(*src) << (7 - bit); // parity is MSB first
|
||||
bit++;
|
||||
if(bit == 8) {
|
||||
*dst = parity;
|
||||
dst++;
|
||||
parity = 0;
|
||||
bit = 0;
|
||||
}
|
||||
src++;
|
||||
}
|
||||
|
||||
if(bit) {
|
||||
*dst = parity;
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
void nfc_util_num2bytes(uint64_t src, uint8_t len, uint8_t* dest);
|
||||
|
||||
uint64_t nfc_util_bytes2num(uint8_t* src, uint8_t len);
|
||||
uint64_t nfc_util_bytes2num(const uint8_t* src, uint8_t len);
|
||||
|
||||
uint8_t nfc_util_even_parity32(uint32_t data);
|
||||
|
||||
uint8_t nfc_util_odd_parity8(uint8_t data);
|
||||
|
||||
void nfc_util_odd_parity(const uint8_t* src, uint8_t* dst, uint8_t len);
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
Import("env")
|
||||
|
||||
env.Append(
|
||||
LINT_SOURCES=[
|
||||
Dir("."),
|
||||
],
|
||||
CPPPATH=[
|
||||
"#/lib/one_wire",
|
||||
],
|
||||
SDK_HEADERS=[
|
||||
File("one_wire_host.h"),
|
||||
File("one_wire_slave.h"),
|
||||
File("maxim_crc.h"),
|
||||
],
|
||||
)
|
||||
|
||||
libenv = env.Clone(FW_LIB_NAME="one_wire")
|
||||
libenv.ApplyLibFlags()
|
||||
|
||||
sources = libenv.GlobRecursive("*.c*")
|
||||
|
||||
lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources)
|
||||
libenv.Install("${LIB_DIST_DIR}", lib)
|
||||
Return("lib")
|
||||
@@ -1,110 +0,0 @@
|
||||
#include <furi.h>
|
||||
#include <one_wire/maxim_crc.h>
|
||||
#include "ibutton_key.h"
|
||||
|
||||
struct iButtonKey {
|
||||
uint8_t data[IBUTTON_KEY_DATA_SIZE];
|
||||
iButtonKeyType type;
|
||||
};
|
||||
|
||||
iButtonKey* ibutton_key_alloc() {
|
||||
iButtonKey* key = malloc(sizeof(iButtonKey));
|
||||
memset(key, 0, sizeof(iButtonKey));
|
||||
return key;
|
||||
}
|
||||
|
||||
void ibutton_key_free(iButtonKey* key) {
|
||||
free(key);
|
||||
}
|
||||
|
||||
void ibutton_key_set(iButtonKey* to, const iButtonKey* from) {
|
||||
memcpy(to, from, sizeof(iButtonKey));
|
||||
}
|
||||
|
||||
void ibutton_key_set_data(iButtonKey* key, uint8_t* data, uint8_t data_count) {
|
||||
furi_check(data_count > 0);
|
||||
furi_check(data_count <= IBUTTON_KEY_DATA_SIZE);
|
||||
|
||||
memset(key->data, 0, IBUTTON_KEY_DATA_SIZE);
|
||||
memcpy(key->data, data, data_count);
|
||||
}
|
||||
|
||||
void ibutton_key_clear_data(iButtonKey* key) {
|
||||
memset(key->data, 0, IBUTTON_KEY_DATA_SIZE);
|
||||
}
|
||||
|
||||
const uint8_t* ibutton_key_get_data_p(iButtonKey* key) {
|
||||
return key->data;
|
||||
}
|
||||
|
||||
uint8_t ibutton_key_get_data_size(iButtonKey* key) {
|
||||
return ibutton_key_get_size_by_type(key->type);
|
||||
}
|
||||
|
||||
void ibutton_key_set_type(iButtonKey* key, iButtonKeyType key_type) {
|
||||
key->type = key_type;
|
||||
}
|
||||
|
||||
iButtonKeyType ibutton_key_get_type(iButtonKey* key) {
|
||||
return key->type;
|
||||
}
|
||||
|
||||
const char* ibutton_key_get_string_by_type(iButtonKeyType key_type) {
|
||||
switch(key_type) {
|
||||
case iButtonKeyCyfral:
|
||||
return "Cyfral";
|
||||
break;
|
||||
case iButtonKeyMetakom:
|
||||
return "Metakom";
|
||||
break;
|
||||
case iButtonKeyDS1990:
|
||||
return "Dallas";
|
||||
break;
|
||||
default:
|
||||
furi_crash("Invalid iButton type");
|
||||
}
|
||||
}
|
||||
|
||||
bool ibutton_key_get_type_by_string(const char* type_string, iButtonKeyType* key_type) {
|
||||
if(strcmp(type_string, ibutton_key_get_string_by_type(iButtonKeyCyfral)) == 0) {
|
||||
*key_type = iButtonKeyCyfral;
|
||||
} else if(strcmp(type_string, ibutton_key_get_string_by_type(iButtonKeyMetakom)) == 0) {
|
||||
*key_type = iButtonKeyMetakom;
|
||||
} else if(strcmp(type_string, ibutton_key_get_string_by_type(iButtonKeyDS1990)) == 0) {
|
||||
*key_type = iButtonKeyDS1990;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t ibutton_key_get_size_by_type(iButtonKeyType key_type) {
|
||||
uint8_t size = 0;
|
||||
|
||||
switch(key_type) {
|
||||
case iButtonKeyCyfral:
|
||||
size = 2;
|
||||
break;
|
||||
case iButtonKeyMetakom:
|
||||
size = 4;
|
||||
break;
|
||||
case iButtonKeyDS1990:
|
||||
size = 8;
|
||||
break;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
uint8_t ibutton_key_get_max_size() {
|
||||
return IBUTTON_KEY_DATA_SIZE;
|
||||
}
|
||||
|
||||
bool ibutton_key_dallas_crc_is_valid(iButtonKey* key) {
|
||||
return (maxim_crc8(key->data, 8, MAXIM_CRC8_INIT) == 0);
|
||||
}
|
||||
|
||||
bool ibutton_key_dallas_is_1990_key(iButtonKey* key) {
|
||||
return (key->data[0] == 0x01);
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
/**
|
||||
* @file ibutton_key.h
|
||||
*
|
||||
* iButton key data holder
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define IBUTTON_KEY_DATA_SIZE 8
|
||||
#define IBUTTON_KEY_NAME_SIZE 22
|
||||
|
||||
typedef enum {
|
||||
iButtonKeyDS1990,
|
||||
iButtonKeyCyfral,
|
||||
iButtonKeyMetakom,
|
||||
} iButtonKeyType;
|
||||
|
||||
typedef struct iButtonKey iButtonKey;
|
||||
|
||||
/**
|
||||
* Allocate key
|
||||
* @return iButtonKey*
|
||||
*/
|
||||
iButtonKey* ibutton_key_alloc();
|
||||
|
||||
/**
|
||||
* Free key
|
||||
* @param key
|
||||
*/
|
||||
void ibutton_key_free(iButtonKey* key);
|
||||
|
||||
/**
|
||||
* Copy key
|
||||
* @param to
|
||||
* @param from
|
||||
*/
|
||||
void ibutton_key_set(iButtonKey* to, const iButtonKey* from);
|
||||
|
||||
/**
|
||||
* Set key data
|
||||
* @param key
|
||||
* @param data
|
||||
* @param data_count
|
||||
*/
|
||||
void ibutton_key_set_data(iButtonKey* key, uint8_t* data, uint8_t data_count);
|
||||
|
||||
/**
|
||||
* Clear key data
|
||||
* @param key
|
||||
*/
|
||||
void ibutton_key_clear_data(iButtonKey* key);
|
||||
|
||||
/**
|
||||
* Get pointer to key data
|
||||
* @param key
|
||||
* @return const uint8_t*
|
||||
*/
|
||||
const uint8_t* ibutton_key_get_data_p(iButtonKey* key);
|
||||
|
||||
/**
|
||||
* Get key data size
|
||||
* @param key
|
||||
* @return uint8_t
|
||||
*/
|
||||
uint8_t ibutton_key_get_data_size(iButtonKey* key);
|
||||
|
||||
/**
|
||||
* Set key type
|
||||
* @param key
|
||||
* @param key_type
|
||||
*/
|
||||
void ibutton_key_set_type(iButtonKey* key, iButtonKeyType key_type);
|
||||
|
||||
/**
|
||||
* Get key type
|
||||
* @param key
|
||||
* @return iButtonKeyType
|
||||
*/
|
||||
iButtonKeyType ibutton_key_get_type(iButtonKey* key);
|
||||
|
||||
/**
|
||||
* Get type string from key type
|
||||
* @param key_type
|
||||
* @return const char*
|
||||
*/
|
||||
const char* ibutton_key_get_string_by_type(iButtonKeyType key_type);
|
||||
|
||||
/**
|
||||
* Get key type from string
|
||||
* @param type_string
|
||||
* @param key_type
|
||||
* @return bool
|
||||
*/
|
||||
bool ibutton_key_get_type_by_string(const char* type_string, iButtonKeyType* key_type);
|
||||
|
||||
/**
|
||||
* Get key data size from type
|
||||
* @param key_type
|
||||
* @return uint8_t
|
||||
*/
|
||||
uint8_t ibutton_key_get_size_by_type(iButtonKeyType key_type);
|
||||
|
||||
/**
|
||||
* Get max key size
|
||||
* @return uint8_t
|
||||
*/
|
||||
uint8_t ibutton_key_get_max_size();
|
||||
|
||||
/**
|
||||
* Check if CRC for onewire key is valid
|
||||
* @param key
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool ibutton_key_dallas_crc_is_valid(iButtonKey* key);
|
||||
|
||||
/**
|
||||
* Check if onewire key is a DS1990 key
|
||||
* @param key
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool ibutton_key_dallas_is_1990_key(iButtonKey* key);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* @file ibutton_key_command.h
|
||||
*
|
||||
* List of misc commands for Dallas and blanks
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#define RW1990_1_CMD_WRITE_RECORD_FLAG 0xD1
|
||||
#define RW1990_1_CMD_READ_RECORD_FLAG 0xB5
|
||||
#define RW1990_1_CMD_WRITE_ROM 0xD5
|
||||
|
||||
#define RW1990_2_CMD_WRITE_RECORD_FLAG 0x1D
|
||||
#define RW1990_2_CMD_READ_RECORD_FLAG 0x1E
|
||||
#define RW1990_2_CMD_WRITE_ROM 0xD5
|
||||
|
||||
#define TM2004_CMD_READ_STATUS 0xAA
|
||||
#define TM2004_CMD_READ_MEMORY 0xF0
|
||||
#define TM2004_CMD_WRITE_ROM 0x3C
|
||||
#define TM2004_CMD_FINALIZATION 0x35
|
||||
#define TM2004_ANSWER_READ_MEMORY 0xF5
|
||||
|
||||
#define TM01_CMD_WRITE_RECORD_FLAG 0xC1
|
||||
#define TM01_CMD_WRITE_ROM 0xC5
|
||||
#define TM01_CMD_SWITCH_TO_CYFRAL 0xCA
|
||||
#define TM01_CMD_SWITCH_TO_METAKOM 0xCB
|
||||
|
||||
#define DS1990_CMD_READ_ROM 0x33
|
||||
@@ -1,352 +0,0 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include "ibutton_worker_i.h"
|
||||
#include "ibutton_key_command.h"
|
||||
|
||||
void ibutton_worker_mode_idle_start(iButtonWorker* worker);
|
||||
void ibutton_worker_mode_idle_tick(iButtonWorker* worker);
|
||||
void ibutton_worker_mode_idle_stop(iButtonWorker* worker);
|
||||
|
||||
void ibutton_worker_mode_emulate_start(iButtonWorker* worker);
|
||||
void ibutton_worker_mode_emulate_tick(iButtonWorker* worker);
|
||||
void ibutton_worker_mode_emulate_stop(iButtonWorker* worker);
|
||||
|
||||
void ibutton_worker_mode_read_start(iButtonWorker* worker);
|
||||
void ibutton_worker_mode_read_tick(iButtonWorker* worker);
|
||||
void ibutton_worker_mode_read_stop(iButtonWorker* worker);
|
||||
|
||||
void ibutton_worker_mode_write_start(iButtonWorker* worker);
|
||||
void ibutton_worker_mode_write_tick(iButtonWorker* worker);
|
||||
void ibutton_worker_mode_write_stop(iButtonWorker* worker);
|
||||
|
||||
const iButtonWorkerModeType ibutton_worker_modes[] = {
|
||||
{
|
||||
.quant = FuriWaitForever,
|
||||
.start = ibutton_worker_mode_idle_start,
|
||||
.tick = ibutton_worker_mode_idle_tick,
|
||||
.stop = ibutton_worker_mode_idle_stop,
|
||||
},
|
||||
{
|
||||
.quant = 100,
|
||||
.start = ibutton_worker_mode_read_start,
|
||||
.tick = ibutton_worker_mode_read_tick,
|
||||
.stop = ibutton_worker_mode_read_stop,
|
||||
},
|
||||
{
|
||||
.quant = 1000,
|
||||
.start = ibutton_worker_mode_write_start,
|
||||
.tick = ibutton_worker_mode_write_tick,
|
||||
.stop = ibutton_worker_mode_write_stop,
|
||||
},
|
||||
{
|
||||
.quant = 1000,
|
||||
.start = ibutton_worker_mode_emulate_start,
|
||||
.tick = ibutton_worker_mode_emulate_tick,
|
||||
.stop = ibutton_worker_mode_emulate_stop,
|
||||
},
|
||||
};
|
||||
|
||||
/*********************** IDLE ***********************/
|
||||
|
||||
void ibutton_worker_mode_idle_start(iButtonWorker* worker) {
|
||||
UNUSED(worker);
|
||||
}
|
||||
|
||||
void ibutton_worker_mode_idle_tick(iButtonWorker* worker) {
|
||||
UNUSED(worker);
|
||||
}
|
||||
|
||||
void ibutton_worker_mode_idle_stop(iButtonWorker* worker) {
|
||||
UNUSED(worker);
|
||||
}
|
||||
|
||||
/*********************** READ ***********************/
|
||||
|
||||
typedef struct {
|
||||
uint32_t last_dwt_value;
|
||||
FuriStreamBuffer* stream;
|
||||
} iButtonReadContext;
|
||||
|
||||
void ibutton_worker_comparator_callback(bool level, void* context) {
|
||||
iButtonReadContext* read_context = context;
|
||||
|
||||
uint32_t current_dwt_value = DWT->CYCCNT;
|
||||
|
||||
LevelDuration data =
|
||||
level_duration_make(level, current_dwt_value - read_context->last_dwt_value);
|
||||
furi_stream_buffer_send(read_context->stream, &data, sizeof(LevelDuration), 0);
|
||||
|
||||
read_context->last_dwt_value = current_dwt_value;
|
||||
}
|
||||
|
||||
bool ibutton_worker_read_comparator(iButtonWorker* worker) {
|
||||
bool result = false;
|
||||
|
||||
protocol_dict_decoders_start(worker->protocols);
|
||||
|
||||
furi_hal_rfid_pins_reset();
|
||||
// pulldown pull pin, we sense the signal through the analog part of the RFID schematic
|
||||
furi_hal_rfid_pin_pull_pulldown();
|
||||
|
||||
iButtonReadContext read_context = {
|
||||
.last_dwt_value = DWT->CYCCNT,
|
||||
.stream = furi_stream_buffer_alloc(sizeof(LevelDuration) * 512, 1),
|
||||
};
|
||||
|
||||
furi_hal_rfid_comp_set_callback(ibutton_worker_comparator_callback, &read_context);
|
||||
furi_hal_rfid_comp_start();
|
||||
|
||||
uint32_t tick_start = furi_get_tick();
|
||||
while(true) {
|
||||
LevelDuration level;
|
||||
size_t ret =
|
||||
furi_stream_buffer_receive(read_context.stream, &level, sizeof(LevelDuration), 100);
|
||||
|
||||
if((furi_get_tick() - tick_start) > 100) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(ret > 0) {
|
||||
ProtocolId decoded_index = protocol_dict_decoders_feed(
|
||||
worker->protocols,
|
||||
level_duration_get_level(level),
|
||||
level_duration_get_duration(level));
|
||||
|
||||
if(decoded_index == PROTOCOL_NO) continue;
|
||||
|
||||
protocol_dict_get_data(
|
||||
worker->protocols, decoded_index, worker->key_data, ibutton_key_get_max_size());
|
||||
|
||||
switch(decoded_index) {
|
||||
case iButtonProtocolCyfral:
|
||||
furi_check(worker->key_p != NULL);
|
||||
ibutton_key_set_type(worker->key_p, iButtonKeyCyfral);
|
||||
ibutton_key_set_data(worker->key_p, worker->key_data, ibutton_key_get_max_size());
|
||||
result = true;
|
||||
break;
|
||||
case iButtonProtocolMetakom:
|
||||
furi_check(worker->key_p != NULL);
|
||||
ibutton_key_set_type(worker->key_p, iButtonKeyMetakom);
|
||||
ibutton_key_set_data(worker->key_p, worker->key_data, ibutton_key_get_max_size());
|
||||
result = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
furi_hal_rfid_comp_stop();
|
||||
furi_hal_rfid_comp_set_callback(NULL, NULL);
|
||||
furi_hal_rfid_pins_reset();
|
||||
|
||||
furi_stream_buffer_free(read_context.stream);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ibutton_worker_read_dallas(iButtonWorker* worker) {
|
||||
bool result = false;
|
||||
onewire_host_start(worker->host);
|
||||
furi_delay_ms(100);
|
||||
FURI_CRITICAL_ENTER();
|
||||
if(onewire_host_search(worker->host, worker->key_data, NORMAL_SEARCH)) {
|
||||
onewire_host_reset_search(worker->host);
|
||||
|
||||
// key found, verify
|
||||
if(onewire_host_reset(worker->host)) {
|
||||
onewire_host_write(worker->host, DS1990_CMD_READ_ROM);
|
||||
bool key_valid = true;
|
||||
for(uint8_t i = 0; i < ibutton_key_get_max_size(); i++) {
|
||||
if(onewire_host_read(worker->host) != worker->key_data[i]) {
|
||||
key_valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(key_valid) {
|
||||
result = true;
|
||||
|
||||
furi_check(worker->key_p != NULL);
|
||||
ibutton_key_set_type(worker->key_p, iButtonKeyDS1990);
|
||||
ibutton_key_set_data(worker->key_p, worker->key_data, ibutton_key_get_max_size());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
onewire_host_reset_search(worker->host);
|
||||
}
|
||||
onewire_host_stop(worker->host);
|
||||
FURI_CRITICAL_EXIT();
|
||||
return result;
|
||||
}
|
||||
|
||||
void ibutton_worker_mode_read_start(iButtonWorker* worker) {
|
||||
UNUSED(worker);
|
||||
furi_hal_power_enable_otg();
|
||||
}
|
||||
|
||||
void ibutton_worker_mode_read_tick(iButtonWorker* worker) {
|
||||
bool valid = false;
|
||||
if(ibutton_worker_read_dallas(worker)) {
|
||||
valid = true;
|
||||
} else if(ibutton_worker_read_comparator(worker)) {
|
||||
valid = true;
|
||||
}
|
||||
|
||||
if(valid) {
|
||||
if(worker->read_cb != NULL) {
|
||||
worker->read_cb(worker->cb_ctx);
|
||||
}
|
||||
|
||||
ibutton_worker_switch_mode(worker, iButtonWorkerIdle);
|
||||
}
|
||||
}
|
||||
|
||||
void ibutton_worker_mode_read_stop(iButtonWorker* worker) {
|
||||
UNUSED(worker);
|
||||
furi_hal_power_disable_otg();
|
||||
}
|
||||
|
||||
/*********************** EMULATE ***********************/
|
||||
static void onewire_slave_callback(void* context) {
|
||||
furi_assert(context);
|
||||
iButtonWorker* worker = context;
|
||||
ibutton_worker_notify_emulate(worker);
|
||||
}
|
||||
|
||||
void ibutton_worker_emulate_dallas_start(iButtonWorker* worker) {
|
||||
uint8_t* device_id = onewire_device_get_id_p(worker->device);
|
||||
const uint8_t* key_id = ibutton_key_get_data_p(worker->key_p);
|
||||
const uint8_t key_size = ibutton_key_get_max_size();
|
||||
memcpy(device_id, key_id, key_size);
|
||||
|
||||
onewire_slave_attach(worker->slave, worker->device);
|
||||
onewire_slave_set_result_callback(worker->slave, onewire_slave_callback, worker);
|
||||
onewire_slave_start(worker->slave);
|
||||
}
|
||||
|
||||
void ibutton_worker_emulate_dallas_stop(iButtonWorker* worker) {
|
||||
onewire_slave_stop(worker->slave);
|
||||
onewire_slave_detach(worker->slave);
|
||||
}
|
||||
|
||||
void ibutton_worker_emulate_timer_cb(void* context) {
|
||||
furi_assert(context);
|
||||
iButtonWorker* worker = context;
|
||||
|
||||
LevelDuration level =
|
||||
protocol_dict_encoder_yield(worker->protocols, worker->protocol_to_encode);
|
||||
|
||||
furi_hal_ibutton_emulate_set_next(level_duration_get_duration(level));
|
||||
|
||||
if(level_duration_get_level(level)) {
|
||||
furi_hal_ibutton_pin_high();
|
||||
} else {
|
||||
furi_hal_ibutton_pin_low();
|
||||
}
|
||||
}
|
||||
|
||||
void ibutton_worker_emulate_timer_start(iButtonWorker* worker) {
|
||||
furi_assert(worker->key_p);
|
||||
const uint8_t* key_id = ibutton_key_get_data_p(worker->key_p);
|
||||
const uint8_t key_size = ibutton_key_get_max_size();
|
||||
|
||||
switch(ibutton_key_get_type(worker->key_p)) {
|
||||
case iButtonKeyDS1990:
|
||||
return;
|
||||
break;
|
||||
case iButtonKeyCyfral:
|
||||
worker->protocol_to_encode = iButtonProtocolCyfral;
|
||||
break;
|
||||
case iButtonKeyMetakom:
|
||||
worker->protocol_to_encode = iButtonProtocolMetakom;
|
||||
break;
|
||||
}
|
||||
|
||||
protocol_dict_set_data(worker->protocols, worker->protocol_to_encode, key_id, key_size);
|
||||
protocol_dict_encoder_start(worker->protocols, worker->protocol_to_encode);
|
||||
|
||||
furi_hal_ibutton_start_drive();
|
||||
furi_hal_ibutton_emulate_start(0, ibutton_worker_emulate_timer_cb, worker);
|
||||
}
|
||||
|
||||
void ibutton_worker_emulate_timer_stop(iButtonWorker* worker) {
|
||||
UNUSED(worker);
|
||||
furi_hal_ibutton_emulate_stop();
|
||||
}
|
||||
|
||||
void ibutton_worker_mode_emulate_start(iButtonWorker* worker) {
|
||||
furi_assert(worker->key_p);
|
||||
|
||||
furi_hal_rfid_pins_reset();
|
||||
furi_hal_rfid_pin_pull_pulldown();
|
||||
|
||||
switch(ibutton_key_get_type(worker->key_p)) {
|
||||
case iButtonKeyDS1990:
|
||||
ibutton_worker_emulate_dallas_start(worker);
|
||||
break;
|
||||
case iButtonKeyCyfral:
|
||||
case iButtonKeyMetakom:
|
||||
ibutton_worker_emulate_timer_start(worker);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ibutton_worker_mode_emulate_tick(iButtonWorker* worker) {
|
||||
UNUSED(worker);
|
||||
}
|
||||
|
||||
void ibutton_worker_mode_emulate_stop(iButtonWorker* worker) {
|
||||
furi_assert(worker->key_p);
|
||||
|
||||
furi_hal_rfid_pins_reset();
|
||||
|
||||
switch(ibutton_key_get_type(worker->key_p)) {
|
||||
case iButtonKeyDS1990:
|
||||
ibutton_worker_emulate_dallas_stop(worker);
|
||||
break;
|
||||
case iButtonKeyCyfral:
|
||||
case iButtonKeyMetakom:
|
||||
ibutton_worker_emulate_timer_stop(worker);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*********************** WRITE ***********************/
|
||||
|
||||
void ibutton_worker_mode_write_start(iButtonWorker* worker) {
|
||||
furi_hal_power_enable_otg();
|
||||
onewire_host_start(worker->host);
|
||||
}
|
||||
|
||||
void ibutton_worker_mode_write_tick(iButtonWorker* worker) {
|
||||
furi_check(worker->key_p != NULL);
|
||||
iButtonWriterResult writer_result = ibutton_writer_write(worker->writer, worker->key_p);
|
||||
iButtonWorkerWriteResult result;
|
||||
switch(writer_result) {
|
||||
case iButtonWriterOK:
|
||||
result = iButtonWorkerWriteOK;
|
||||
break;
|
||||
case iButtonWriterSameKey:
|
||||
result = iButtonWorkerWriteSameKey;
|
||||
break;
|
||||
case iButtonWriterNoDetect:
|
||||
result = iButtonWorkerWriteNoDetect;
|
||||
break;
|
||||
case iButtonWriterCannotWrite:
|
||||
result = iButtonWorkerWriteCannotWrite;
|
||||
break;
|
||||
default:
|
||||
result = iButtonWorkerWriteNoDetect;
|
||||
break;
|
||||
}
|
||||
|
||||
if(worker->write_cb != NULL) {
|
||||
worker->write_cb(worker->cb_ctx, result);
|
||||
}
|
||||
}
|
||||
|
||||
void ibutton_worker_mode_write_stop(iButtonWorker* worker) {
|
||||
furi_hal_power_disable_otg();
|
||||
onewire_host_stop(worker->host);
|
||||
}
|
||||
@@ -1,298 +0,0 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include "ibutton_writer.h"
|
||||
#include "ibutton_key_command.h"
|
||||
|
||||
/*********************** PRIVATE ***********************/
|
||||
|
||||
struct iButtonWriter {
|
||||
OneWireHost* host;
|
||||
};
|
||||
|
||||
static void writer_write_one_bit(iButtonWriter* writer, bool value, uint32_t delay) {
|
||||
onewire_host_write_bit(writer->host, value);
|
||||
furi_delay_us(delay);
|
||||
}
|
||||
|
||||
static void writer_write_byte_ds1990(iButtonWriter* writer, uint8_t data) {
|
||||
for(uint8_t n_bit = 0; n_bit < 8; n_bit++) {
|
||||
onewire_host_write_bit(writer->host, data & 1);
|
||||
furi_delay_us(5000);
|
||||
data = data >> 1;
|
||||
}
|
||||
}
|
||||
|
||||
static bool writer_compare_key_ds1990(iButtonWriter* writer, iButtonKey* key) {
|
||||
bool result = false;
|
||||
|
||||
if(ibutton_key_get_type(key) == iButtonKeyDS1990) {
|
||||
FURI_CRITICAL_ENTER();
|
||||
bool presence = onewire_host_reset(writer->host);
|
||||
|
||||
if(presence) {
|
||||
onewire_host_write(writer->host, DS1990_CMD_READ_ROM);
|
||||
|
||||
result = true;
|
||||
for(uint8_t i = 0; i < ibutton_key_get_data_size(key); i++) {
|
||||
if(ibutton_key_get_data_p(key)[i] != onewire_host_read(writer->host)) {
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FURI_CRITICAL_EXIT();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool writer_write_TM2004(iButtonWriter* writer, iButtonKey* key) {
|
||||
uint8_t answer;
|
||||
bool result = true;
|
||||
|
||||
if(ibutton_key_get_type(key) == iButtonKeyDS1990) {
|
||||
FURI_CRITICAL_ENTER();
|
||||
|
||||
// write rom, addr is 0x0000
|
||||
onewire_host_reset(writer->host);
|
||||
onewire_host_write(writer->host, TM2004_CMD_WRITE_ROM);
|
||||
onewire_host_write(writer->host, 0x00);
|
||||
onewire_host_write(writer->host, 0x00);
|
||||
|
||||
// write key
|
||||
for(uint8_t i = 0; i < ibutton_key_get_data_size(key); i++) {
|
||||
// write key byte
|
||||
onewire_host_write(writer->host, ibutton_key_get_data_p(key)[i]);
|
||||
answer = onewire_host_read(writer->host);
|
||||
// TODO: check answer CRC
|
||||
|
||||
// pulse indicating that data is correct
|
||||
furi_delay_us(600);
|
||||
writer_write_one_bit(writer, 1, 50000);
|
||||
|
||||
// read written key byte
|
||||
answer = onewire_host_read(writer->host); //-V519
|
||||
|
||||
// check that written and read are same
|
||||
if(ibutton_key_get_data_p(key)[i] != answer) {
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!writer_compare_key_ds1990(writer, key)) {
|
||||
result = false;
|
||||
}
|
||||
|
||||
onewire_host_reset(writer->host);
|
||||
|
||||
FURI_CRITICAL_EXIT();
|
||||
} else {
|
||||
result = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool writer_write_1990_1(iButtonWriter* writer, iButtonKey* key) {
|
||||
bool result = false;
|
||||
|
||||
if(ibutton_key_get_type(key) == iButtonKeyDS1990) {
|
||||
FURI_CRITICAL_ENTER();
|
||||
|
||||
// unlock
|
||||
onewire_host_reset(writer->host);
|
||||
onewire_host_write(writer->host, RW1990_1_CMD_WRITE_RECORD_FLAG);
|
||||
furi_delay_us(10);
|
||||
writer_write_one_bit(writer, 0, 5000);
|
||||
|
||||
// write key
|
||||
onewire_host_reset(writer->host);
|
||||
onewire_host_write(writer->host, RW1990_1_CMD_WRITE_ROM);
|
||||
for(uint8_t i = 0; i < ibutton_key_get_data_size(key); i++) {
|
||||
// inverted key for RW1990.1
|
||||
writer_write_byte_ds1990(writer, ~ibutton_key_get_data_p(key)[i]);
|
||||
furi_delay_us(30000);
|
||||
}
|
||||
|
||||
// lock
|
||||
onewire_host_write(writer->host, RW1990_1_CMD_WRITE_RECORD_FLAG);
|
||||
writer_write_one_bit(writer, 1, 10000);
|
||||
|
||||
FURI_CRITICAL_EXIT();
|
||||
|
||||
if(writer_compare_key_ds1990(writer, key)) {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool writer_write_1990_2(iButtonWriter* writer, iButtonKey* key) {
|
||||
bool result = false;
|
||||
|
||||
if(ibutton_key_get_type(key) == iButtonKeyDS1990) {
|
||||
FURI_CRITICAL_ENTER();
|
||||
|
||||
// unlock
|
||||
onewire_host_reset(writer->host);
|
||||
onewire_host_write(writer->host, RW1990_2_CMD_WRITE_RECORD_FLAG);
|
||||
furi_delay_us(10);
|
||||
writer_write_one_bit(writer, 1, 5000);
|
||||
|
||||
// write key
|
||||
onewire_host_reset(writer->host);
|
||||
onewire_host_write(writer->host, RW1990_2_CMD_WRITE_ROM);
|
||||
for(uint8_t i = 0; i < ibutton_key_get_data_size(key); i++) {
|
||||
writer_write_byte_ds1990(writer, ibutton_key_get_data_p(key)[i]);
|
||||
furi_delay_us(30000);
|
||||
}
|
||||
|
||||
// lock
|
||||
onewire_host_write(writer->host, RW1990_2_CMD_WRITE_RECORD_FLAG);
|
||||
writer_write_one_bit(writer, 0, 10000);
|
||||
|
||||
FURI_CRITICAL_EXIT();
|
||||
|
||||
if(writer_compare_key_ds1990(writer, key)) {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
// TODO: adapt and test
|
||||
static bool writer_write_TM01(
|
||||
iButtonWriter* writer,
|
||||
iButtonKey type,
|
||||
const uint8_t* key,
|
||||
uint8_t key_length) {
|
||||
bool result = true;
|
||||
|
||||
{
|
||||
// TODO test and encoding
|
||||
FURI_CRITICAL_ENTER();
|
||||
|
||||
// unlock
|
||||
onewire_host_reset(writer->host);
|
||||
onewire_host_write(writer->host, TM01::CMD_WRITE_RECORD_FLAG);
|
||||
onewire_write_one_bit(1, 10000);
|
||||
|
||||
// write key
|
||||
onewire_host_reset(writer->host);
|
||||
onewire_host_write(writer->host, TM01::CMD_WRITE_ROM);
|
||||
|
||||
// TODO: key types
|
||||
//if(type == KEY_METAKOM || type == KEY_CYFRAL) {
|
||||
//} else {
|
||||
for(uint8_t i = 0; i < key->get_type_data_size(); i++) {
|
||||
write_byte_ds1990(key->get_data()[i]);
|
||||
furi_delay_us(10000);
|
||||
}
|
||||
//}
|
||||
|
||||
// lock
|
||||
onewire_host_write(writer->host, TM01::CMD_WRITE_RECORD_FLAG);
|
||||
onewire_write_one_bit(0, 10000);
|
||||
|
||||
FURI_CRITICAL_EXIT();
|
||||
}
|
||||
|
||||
if(!compare_key_ds1990(key)) {
|
||||
result = false;
|
||||
}
|
||||
|
||||
{
|
||||
FURI_CRITICAL_ENTER();
|
||||
|
||||
if(key->get_key_type() == iButtonKeyType::KeyMetakom ||
|
||||
key->get_key_type() == iButtonKeyType::KeyCyfral) {
|
||||
onewire_host_reset(writer->host);
|
||||
if(key->get_key_type() == iButtonKeyType::KeyCyfral)
|
||||
onewire_host_write(writer->host, TM01::CMD_SWITCH_TO_CYFRAL);
|
||||
else
|
||||
onewire_host_write(writer->host, TM01::CMD_SWITCH_TO_METAKOM);
|
||||
onewire_write_one_bit(1);
|
||||
}
|
||||
|
||||
FURI_CRITICAL_EXIT();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
*/
|
||||
|
||||
static iButtonWriterResult writer_write_DS1990(iButtonWriter* writer, iButtonKey* key) {
|
||||
iButtonWriterResult result = iButtonWriterNoDetect;
|
||||
bool same_key = writer_compare_key_ds1990(writer, key);
|
||||
|
||||
if(!same_key) {
|
||||
// currently we can write:
|
||||
// RW1990_1, TM08v2, TM08vi-2 by write_1990_1()
|
||||
// RW1990_2 by write_1990_2()
|
||||
// RW2004, RW2004, TM2004 with EEPROM by write_TM2004();
|
||||
|
||||
bool write_result = true;
|
||||
do {
|
||||
if(writer_write_1990_1(writer, key)) break;
|
||||
if(writer_write_1990_2(writer, key)) break;
|
||||
if(writer_write_TM2004(writer, key)) break;
|
||||
write_result = false;
|
||||
} while(false);
|
||||
|
||||
if(write_result) {
|
||||
result = iButtonWriterOK;
|
||||
} else {
|
||||
result = iButtonWriterCannotWrite;
|
||||
}
|
||||
} else {
|
||||
result = iButtonWriterSameKey;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*********************** PUBLIC ***********************/
|
||||
|
||||
iButtonWriter* ibutton_writer_alloc(OneWireHost* host) {
|
||||
iButtonWriter* writer = malloc(sizeof(iButtonWriter));
|
||||
writer->host = host;
|
||||
return writer;
|
||||
}
|
||||
|
||||
void ibutton_writer_free(iButtonWriter* writer) {
|
||||
free(writer);
|
||||
}
|
||||
|
||||
iButtonWriterResult ibutton_writer_write(iButtonWriter* writer, iButtonKey* key) {
|
||||
iButtonWriterResult result = iButtonWriterNoDetect;
|
||||
|
||||
furi_kernel_lock();
|
||||
bool blank_present = onewire_host_reset(writer->host);
|
||||
furi_kernel_unlock();
|
||||
|
||||
if(blank_present) {
|
||||
switch(ibutton_key_get_type(key)) {
|
||||
case iButtonKeyDS1990:
|
||||
result = writer_write_DS1990(writer, key);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void ibutton_writer_start(iButtonWriter* writer) {
|
||||
furi_hal_power_enable_otg();
|
||||
onewire_host_start(writer->host);
|
||||
}
|
||||
|
||||
void ibutton_writer_stop(iButtonWriter* writer) {
|
||||
onewire_host_stop(writer->host);
|
||||
furi_hal_power_disable_otg();
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/**
|
||||
* @file ibutton_writer.h
|
||||
*
|
||||
* iButton blanks writer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <furi_hal_gpio.h>
|
||||
#include "ibutton_key.h"
|
||||
#include "../one_wire_host.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
iButtonWriterOK,
|
||||
iButtonWriterSameKey,
|
||||
iButtonWriterNoDetect,
|
||||
iButtonWriterCannotWrite,
|
||||
} iButtonWriterResult;
|
||||
|
||||
typedef struct iButtonWriter iButtonWriter;
|
||||
|
||||
/**
|
||||
* Allocate writer
|
||||
* @param host
|
||||
* @return iButtonWriter*
|
||||
*/
|
||||
iButtonWriter* ibutton_writer_alloc(OneWireHost* host);
|
||||
|
||||
/**
|
||||
* Deallocate writer
|
||||
* @param writer
|
||||
*/
|
||||
void ibutton_writer_free(iButtonWriter* writer);
|
||||
|
||||
/**
|
||||
* Write key to blank
|
||||
* @param writer
|
||||
* @param key
|
||||
* @return iButtonWriterResult
|
||||
*/
|
||||
iButtonWriterResult ibutton_writer_write(iButtonWriter* writer, iButtonKey* key);
|
||||
|
||||
/**
|
||||
* Start writing. Must be called before write attempt
|
||||
* @param writer
|
||||
*/
|
||||
void ibutton_writer_start(iButtonWriter* writer);
|
||||
|
||||
/**
|
||||
* Stop writing
|
||||
* @param writer
|
||||
*/
|
||||
void ibutton_writer_stop(iButtonWriter* writer);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,8 +0,0 @@
|
||||
#include "ibutton_protocols.h"
|
||||
#include "protocol_cyfral.h"
|
||||
#include "protocol_metakom.h"
|
||||
|
||||
const ProtocolBase* ibutton_protocols[] = {
|
||||
[iButtonProtocolCyfral] = &protocol_cyfral,
|
||||
[iButtonProtocolMetakom] = &protocol_metakom,
|
||||
};
|
||||
@@ -1,11 +0,0 @@
|
||||
#pragma once
|
||||
#include "toolbox/protocols/protocol.h"
|
||||
|
||||
typedef enum {
|
||||
iButtonProtocolCyfral,
|
||||
iButtonProtocolMetakom,
|
||||
|
||||
iButtonProtocolMax,
|
||||
} iButtonProtocol;
|
||||
|
||||
extern const ProtocolBase* ibutton_protocols[];
|
||||
@@ -1,4 +0,0 @@
|
||||
#pragma once
|
||||
#include "toolbox/protocols/protocol.h"
|
||||
|
||||
extern const ProtocolBase protocol_cyfral;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user