/*
 * Copyright(C) 2008-2010 Проект ИОК
 *
 * Этот файл содержит информацию, являющуюся
 * собственностью компании Крипто Про.
 *
 * Любая часть этого файла не может быть скопирована,
 * исправлена, переведена на другие языки,
 * локализована или модифицирована любым способом,
 * откомпилирована, передана по сети с или на
 * любую компьютерную систему без предварительного
 * заключения соглашения с компанией Крипто Про.
 */

 /****
  * $Id: f12def.c 224337 2021-01-26 12:46:58Z frolov $
  *
  * Работа со считывателями и ключевой информацией (READER).
  *
  * Библиотека чтения / записи ключевой информации на дискеты (FAT12).
  *
  * Функции поддержки информации с ключевой дискеты.
  * Номера поддерживаемых info-функций.
  *
  ****/

#include "fat12prj.h"
#include "f12def.h"
#include "fat12_script_wrapper.h"
#include "reader.kit/rdr_ver.h"
#include "flash_group.h"
#include "os_specific.h"

static const TCHAR KEYDEV_PATH[] = _TEXT("\\CONFIG\\KeyDevices\\");
static const TCHAR APP_PATH[] = _TEXT("\\CONFIG\\Apppath\\");
static const TCHAR SCRIPT_SUFFIX[] = _TEXT("\\Script");
const size_t SCRIPT_TIMEOUT = 5;

TCHAR * fat12_default_get_script(const TCHAR* path_to_item)
{
    TCHAR * name = NULL;
    TCHAR buf[PATH_MAX + 1];
    size_t nlen = 0;
    if (!path_to_item)
	return NULL;
    nlen = _tcslen(path_to_item) + _tcslen(SCRIPT_SUFFIX);
    if (path_to_item == NULL)
	return NULL;
    name = (TCHAR *)malloc((nlen+1) * sizeof(TCHAR));
    if (name == NULL)
	return NULL;
    _sntprintf(name, nlen + 1, TEXT("%s%s"), path_to_item, SCRIPT_SUFFIX);
    nlen = PATH_MAX;
    if (config_get_string(name, &nlen, buf) != ERROR_SUCCESS)
    {
	free(name);
	return NULL;
    }
    free(name);
    nlen = sizeof(APP_PATH) / sizeof(TCHAR) + _tcslen(buf);
    name = (TCHAR *)malloc(nlen * sizeof(TCHAR));
    if (name == NULL)
	return NULL;
    _sntprintf(name, nlen, TEXT("%s%s"), APP_PATH, buf);
    nlen = PATH_MAX;
    if (config_get_string(name, &nlen, buf) != ERROR_SUCCESS)
    {
	free(name);
	return NULL;
    }
    free(name);
    name = support_tcsdup(buf);
    return name;
}

//expects buffer in format: "string1\nstring2\nstring3\n\0"
//ignores last line if it doesn't contain newline.
//creates buffer of null-terminated strings by substituting newline with null-terminator. string1\0string2\0\0
void convert_buffer(TCHAR* buffer, size_t buffer_len) {
    TCHAR* ptr = NULL;
    size_t cur_offset = 0;
    while (cur_offset < buffer_len) {
	ptr = _tcschr(buffer + cur_offset, TEXT('\n'));
	if (ptr)
	    *ptr = TEXT('\0');
	else {
	    *(buffer + cur_offset) = TEXT('\0');
	    *(buffer + cur_offset + 1) = TEXT('\0');
	    return;
	}
	cur_offset += _tcslen(buffer + cur_offset) + 1;
    }
}

DWORD get_usb_labels(const TCHAR* path_to_item, TCHAR* buffer, size_t* buffer_len) {
    TCHAR* script = fat12_default_get_script(path_to_item);
    TCHAR* env[] = { TEXT("test"), (TCHAR*)0 };
    if (fat12_default_lexec(script, TEXT("list"), TEXT(""), env, SCRIPT_TIMEOUT, buffer, buffer_len) != 0) {
	free(script);
	return (DWORD)SCARD_E_READER_UNAVAILABLE;
    }
    if (!buffer) {
	free(script);
	return ERROR_SUCCESS;
    }
    if (*buffer_len < 2) { //if less then two symbols, we can not form buf with newlines.
	free(script);
	return (DWORD)SCARD_E_READER_UNAVAILABLE;  //we expect script to return lines separated with newline and with newline.
    }
    convert_buffer(buffer, *buffer_len);
    free(script);
    return ERROR_SUCCESS;
}

static DWORD get_path_by_uuid_from_script(const TCHAR* path_to_item, const TCHAR* uuid, TCHAR* buffer, size_t* buffer_len) {
    TCHAR* script = fat12_default_get_script(path_to_item);
    TCHAR* env[] = { TEXT("emptyenv"), (TCHAR*)0 };
    TCHAR buf[PATH_MAX + 1] = { 0 };
    size_t local_buf_len = PATH_MAX;
    if (fat12_default_lexec(script, TEXT("find"), uuid, env, SCRIPT_TIMEOUT, buf, &local_buf_len) != 0) {
	free(script);
	return (DWORD)SCARD_E_READER_UNAVAILABLE;
    }
    convert_buffer(buf, local_buf_len);//trim newline.
    local_buf_len = _tcslen(buf); //len can change after convert buffer.
    if (0 == local_buf_len) {
	free(script);
	return (DWORD)SCARD_E_READER_UNAVAILABLE;
    }
    if (!buffer) {
	*buffer_len = local_buf_len;
	free(script);
	return ERROR_SUCCESS;
    }
    if (*buffer_len < local_buf_len) {
	*buffer_len = local_buf_len;
	free(script);
	return (DWORD)ERROR_MORE_DATA;
    }
    _tcscpy(buffer, buf);
    *buffer_len = local_buf_len;
    free(script);
    return ERROR_SUCCESS;
}

//takes null-terminated string and adds slash to its end, if there is no slash. returns error if len is not enough. 
DWORD ensure_correct_path(TCHAR* buf, size_t whole_buf_len) {
    const TCHAR slash_sym[] = TEXT("/");
    size_t last_meaningful_symbol_index = _tcslen(buf) - 1;
    if (buf[last_meaningful_symbol_index] == slash_sym[0])
	return ERROR_SUCCESS;
    if (last_meaningful_symbol_index + 1 >= whole_buf_len)
	return (DWORD)ERROR_MORE_DATA;
    _tcscat(buf, slash_sym);
    return ERROR_SUCCESS;
}

DWORD get_path_by_label(const TCHAR* path_to_item, const TCHAR* uuid, TCHAR** out_cur_path) {
    TCHAR buffer[PATH_MAX + 1 + 1]; //1 for additional slash and 1 for null-terminator
    size_t initial_buffer_len = PATH_MAX + 1; //1 for slash
    size_t buf_from_function_len = initial_buffer_len; //will be set by get_path_by_uuid_from_script, and not used after that.
    TCHAR* res_buf = NULL;
    DWORD code = get_path_by_uuid_from_script(path_to_item, uuid, buffer, &buf_from_function_len);
    if (code)
	return code;

    if (!is_dir(buffer)) {
	return (DWORD)ERROR_INVALID_PARAMETER;
    }
    code = ensure_correct_path(buffer, initial_buffer_len);
    if (ERROR_SUCCESS != code)
	return code;
    res_buf = (TCHAR*)malloc((_tcslen(buffer) + 1) * sizeof(TCHAR));
    if (!res_buf)
	return (DWORD)NTE_NO_MEMORY;
    _tcscpy(res_buf, buffer);
    *out_cur_path = res_buf;
    return ERROR_SUCCESS;
}

DWORD fat12_default_unregister(TSupSysContext* context, TSupSysInfo* info) {
    UNUSED(info);
    TFat12Context* ctx = (TFat12Context*)context;
    if (ctx) {
	free(ctx->path);
	free(ctx->path_to_item);
	delete_mutex(ctx->lock);
	free(ctx->volume_label);
	free(ctx);
    }
    return ERROR_SUCCESS;
}

DWORD islongnames_by_item_path(const TCHAR* path_to_item, BOOL* long_names) {
    TCHAR longnames_path[MAX_PATH] = { 0 };
    _tcscpy(longnames_path, path_to_item);
    _tcscat(longnames_path, TEXT("\\long_names"));
    *long_names = get_registry_long_names_flag(longnames_path);
    return ERROR_SUCCESS;
}

DWORD fat12_default_register(
    TSupSysContext *context,
    TSupSysInfo *info)
{
    TFat12Context *ctx;
    TSupSysInfoRegister *inf = (TSupSysInfoRegister*)info;
    DWORD code;

    UNUSED(context);
    /* Провека входных параметров. */

    SUPSYS_PRE_INFO(info, TSupSysInfoRegister);
    support_set_library(inf->support_module);

    if (!inf->in_group_name.text)
	return (DWORD)SCARD_E_READER_UNAVAILABLE;
    ctx = malloc(sizeof(TFat12Context));
    if (ctx == NULL)
	return (DWORD)NTE_NO_MEMORY;
    memset(ctx, 0, sizeof(TFat12Context));

    ctx->volume_label = malloc((inf->in_group_name.length + 1) * sizeof(TCHAR));
    if (!ctx->volume_label) {
	free(ctx);
	return (DWORD)NTE_NO_MEMORY;
    }
    _tcscpy(ctx->volume_label, inf->in_group_name.text);
    _tcscpy(ctx->nickname, inf->in_group_name.text); //nickname
    ctx->path_to_item = NULL;
    code = convert_path_to_device_nickname_path(&inf->path_to_item, &ctx->path_to_item);
    if (ERROR_SUCCESS != code) {
	free(ctx->volume_label);
	free(ctx);
	return code;
    }

    code = islongnames_by_item_path(inf->path_to_item.text, &ctx->long_names);
    if (code && ERROR_FILE_NOT_FOUND != code) {
	free(ctx->path_to_item);
	free(ctx->volume_label);
	free(ctx);
	return code;
    }
    ctx->connect[0] = 0;
    ctx->path = NULL;
    ctx->file_name = NULL;
    ctx->folder = NULL;
    ctx->flags = 0;
    //lock by nickname
    _tcscpy(ctx->lock_name, ctx->volume_label);
    _tcscat(ctx->lock_name, TEXT("_lock"));

    ctx->handle = NULL;

    if (open_named_mutex(  ctx->lock_name,&ctx->lock ))
    {
	free(ctx->path_to_item);
	free(ctx->volume_label);
	free(ctx);
	return (DWORD)SCARD_E_READER_UNAVAILABLE;
    }
    ctx->locked = FALSE;

    inf->context = ctx;
    return ERROR_SUCCESS;
}

static void release_current_path(TCHAR** out_cur_path) {
    free(*out_cur_path);
    *out_cur_path = NULL;
}

DWORD fat12_default_lock(
    TSupSysContext *context,
    TSupSysInfo *info)
{
    TFat12Context *ctx = (TFat12Context*)context;
    SUPSYS_PRE_CONTEXT(context, TFat12Context);
    UNUSED(info);
    if (ctx->locked)
	return ERROR_SUCCESS;

    if (lock_mutex(ctx->lock))
	return (DWORD)SCARD_W_CANCELLED_BY_USER;

    ctx->locked = TRUE;
    return ERROR_SUCCESS;
}

/*++++
 * Unlock function
 ++++*/
DWORD fat12_default_unlock(
    TSupSysContext *context,
    TSupSysInfo *info)
{
    TFat12Context *ctx = (TFat12Context*)context;
    SUPSYS_PRE_CONTEXT(context, TFat12Context);
    UNUSED(info);

    if (ctx->locked)
    {
	unlock_mutex(ctx->lock);
	ctx->locked = FALSE;
    }

    return ERROR_SUCCESS;
}

DWORD remove_character_from_string(const TCHAR* source, TCHAR sym_to_remove, TCHAR* out_string, size_t* buf_len) {
    size_t src_num = 0;
    size_t dest_num = 0;
    size_t res_len = 0;
    size_t src_len = 0;
    TCHAR res_string[MAX_PATH+1];
    if (!source)
	return (DWORD)ERROR_INVALID_PARAMETER;
    src_len = _tcslen(source);
    if (src_len > MAX_PATH)
	return (DWORD)ERROR_INVALID_PARAMETER;

    while (src_num <= src_len) { //will copy null-terminator also.
	if (source[src_num] != sym_to_remove) {
	    res_string[dest_num] = source[src_num];
	    dest_num++;
	}
	src_num++;
    }
    res_len = _tcslen(res_string);
    if (!out_string) {
	*buf_len = res_len;
	return ERROR_SUCCESS;
    }
    else if (*buf_len < res_len) {
	*buf_len = res_len;
	return (DWORD)ERROR_MORE_DATA;
    }
    _tcscpy(out_string, res_string);
    *buf_len = res_len;
    return (DWORD)ERROR_SUCCESS;
}

DWORD fat12_default_unique_get(
    TSupSysContext *context,
    TSupSysInfo *info)
{
    TSupSysInfoText *inf = (TSupSysInfoText*)info;
    TFat12Context *ctx = (TFat12Context*)context;
    const TCHAR* unique_prefix = TEXT("FLASH_");
    SUPSYS_PRE_CONTEXT(context, TFat12Context);
    SUPSYS_PRE_INFO(info, TSupSysInfoText);
    TCHAR uuid_delimiter = TEXT('-');

    TCHAR stripped_uuid[PATH_MAX + 1];
    size_t stripped_uuid_len = PATH_MAX;
    DWORD code = 0;
    size_t required_len = 0;
    if (!ctx->volume_label)
	return (DWORD)ERROR_INVALID_PARAMETER;

    // Вернуть "честный" unique мы не можем, но хотя бы можем проверить, есть ли папка
    if (DIR_EXIST != fat12_is_dir_exist(ctx->path)) {
	return (DWORD)ERROR_FILE_NOT_FOUND;
    }

    code = remove_character_from_string(ctx->volume_label, uuid_delimiter, NULL, &stripped_uuid_len);
    if (code)
	return code;
    required_len = _tcslen(unique_prefix) + stripped_uuid_len;

    if (inf->text == NULL || inf->length == 0) {
	inf->length = required_len;
	return ERROR_SUCCESS;
    }
    else if (inf->length < required_len) {
	inf->length = required_len;
	return (DWORD)ERROR_MORE_DATA;
    }
    code = remove_character_from_string(ctx->volume_label, uuid_delimiter, stripped_uuid, &stripped_uuid_len);
    if (code)
	return code;
    _tcscpy(inf->text, unique_prefix);
    _tcscat(inf->text, stripped_uuid);
    inf->length = required_len;
    return ERROR_SUCCESS;
}

DWORD fat12_default_connect_carrier(TSupSysContext* context, TSupSysInfo* info) {
    UNUSED(info);
    SUPSYS_PRE_CONTEXT(context, TFat12Context);
    TFat12Context* ctx = (TFat12Context*) context;
    if (!ctx->volume_label || !ctx->path_to_item)
	return (DWORD)ERROR_INVALID_PARAMETER;
    release_current_path(&ctx->path);
    DWORD code = get_path_by_label(ctx->path_to_item, ctx->volume_label, &ctx->path);
    if (ERROR_SUCCESS != code) {
	return code;
    }
    return ERROR_SUCCESS;
}

DWORD fat12_default_disconnect_carrier(TSupSysContext* context, TSupSysInfo* info) {
    UNUSED(info);
    SUPSYS_PRE_CONTEXT(context, TFat12Context);
    TFat12Context* ctx = (TFat12Context*) context;
    release_current_path(&ctx->path);
    return ERROR_SUCCESS;
}

//trims additional items after device nickname (if they exist)
DWORD convert_path_to_device_nickname_path(TSupSysInfoText* in_path, TCHAR** out_path_to_item) {
    TCHAR* res_path = NULL;
    const size_t len = _tcslen(KEYDEV_PATH);
    const TCHAR * ptr = NULL;

    if (!in_path->text)
	return (DWORD)SCARD_E_READER_UNAVAILABLE;

    if (_tcsnicmp(in_path->text, KEYDEV_PATH, len) != 0)
    {
	return (DWORD)SCARD_E_READER_UNAVAILABLE;
    }

    res_path = (TCHAR *)calloc(in_path->length + 1, sizeof(TCHAR));
    if (res_path == NULL)
    {
	return (DWORD)NTE_NO_MEMORY;
    }

    ptr = _tcschr(in_path->text + len, _TEXT('\\'));
    if (ptr == NULL)
	_tcscpy(res_path, in_path->text);
    else
	_tcsncpy(res_path, in_path->text, ptr - in_path->text);
    *out_path_to_item = res_path;
    return ERROR_SUCCESS;
}

DWORD fat12_group_context_dup(TSupSysContext* context, TSupSysInfo* info){
    SUPSYS_PRE_INFO(info, TSupSysInfoContextDup);
    SUPSYS_PRE_CONTEXT(context, TFat12GroupContext);

    TFat12GroupContext *src_ctx = (TFat12GroupContext*)context;
    TSupSysInfoContextDup *inf = (TSupSysInfoContextDup*)info;

    TFat12GroupContext* dup_ctx = (TFat12GroupContext*)malloc(sizeof(TFat12GroupContext));
    if(NULL == dup_ctx){
	return (DWORD)NTE_NO_MEMORY;
    }
    memset(dup_ctx, 0, sizeof(TFat12GroupContext));

    size_t path_len = _tcslen(src_ctx->path_to_item);
    dup_ctx->path_to_item = (TCHAR*)malloc((path_len+1) * sizeof(TCHAR));
    if(NULL == dup_ctx->path_to_item){
	free(dup_ctx);
	return (DWORD)NTE_NO_MEMORY;
    }
    _tcscpy(dup_ctx->path_to_item, src_ctx->path_to_item);
    dup_ctx->m_creation_type = TCtxCreationType_duplicated;
    inf->dest_context = dup_ctx;
    return ERROR_SUCCESS; 
}

DWORD fat12_group_context_free(TSupSysContext* context, TSupSysInfo* info){
    UNUSED(info);
    TFat12GroupContext *ctx = (TFat12GroupContext*)context;     
    if(NULL != ctx){
	free(ctx->path_to_item);
	SUPSYS_PRE(ctx->m_creation_type == TCtxCreationType_duplicated);
	free(ctx);
    }
    return ERROR_SUCCESS;
}

DWORD fat12_group_register(TSupSysContext* context, TSupSysInfo* info) {
    TFat12GroupContext* ctx = NULL;
    TSupSysInfoRegister *inf = (TSupSysInfoRegister*)info;
    DWORD code = 0;
    UNUSED(context);
    SUPSYS_PRE_INFO(info, TSupSysInfoRegister);
    ctx = (TFat12GroupContext*)malloc(sizeof(TFat12GroupContext));
    if (!ctx)
	return (DWORD)NTE_NO_MEMORY;
    memset(ctx, 0, sizeof(TFat12GroupContext));
    code = convert_path_to_device_nickname_path(&inf->path_to_item, &ctx->path_to_item);
    if (ERROR_SUCCESS != code) {
	free(ctx);
	return code;
    }
    ctx->m_creation_type = TCtxCreationType_registered;
    inf->context = ctx;
    return ERROR_SUCCESS;
}

DWORD fat12_group_unregister(TSupSysContext* context, TSupSysInfo* info) {
    UNUSED(info);
    TFat12GroupContext* ctx = (TFat12GroupContext*)context;
    if (ctx) {
	free(ctx->path_to_item);
	SUPSYS_PRE(ctx->m_creation_type == TCtxCreationType_registered);
	free(ctx);
    }
    return ERROR_SUCCESS;
}
#ifndef READERUNITTEST_INCLUDED
static DWORD fat12_default_info_ids(
    TSupSysContext *context,
    TSupSysInfo *info);

static const TSupSysFunctionTableItem fat12_default_funs[] =
{
    { SUPSYS_FUN_NICKNAME, fat12_info_nickname },
    { SUPSYS_FUN_NAME, fat12_info_name },
    { SUPSYS_FUN_INSTANCE, fat12_info_instance },
    { SUPSYS_FUN_CONTEXT_DUP, fat12_context_dup },
    { SUPSYS_FUN_CONTEXT_FREE, fat12_context_free },
    { SUPSYS_FUN_IDS_BLOCK, fat12_default_info_ids },
    { SUPSYS_FUN_FLAGS, fat12_info_system_flag },

    { SUPSYS_FUN_VERSIONSUPPORT, info_versionsupport},
    { SUPSYS_FUN_REGISTER, fat12_default_register },
    { SUPSYS_FUN_UNREGISTER, fat12_default_unregister },
    { SUPSYS_FUN_CONNECT, fat12_connect },
    { SUPSYS_FUN_DISCONNECT, fat12_disconnect },

    { READER_FUN_LOCK, fat12_default_lock },
    { READER_FUN_UNLOCK, fat12_default_unlock },

    { READER_FUN_CONNECT_CARRIER, fat12_default_connect_carrier },
    { READER_FUN_DISCONNECT_CARRIER, fat12_default_disconnect_carrier },
    { READER_FUN_GET_PARAM, fat12_get_param },
    { READER_FUN_PASSWORD_TYPE, fat12_get_password_type },

    { READER_FUN_UNIQUE_GET, fat12_default_unique_get },


    { READER_FUN_FOLDER_ENUM_OPEN, fat12_info_locked_folder_enum_open },
    { READER_FUN_FOLDER_ENUM_NEXT, fat12_info_locked_folder_enum_next },
    { READER_FUN_FOLDER_ENUM_CLOSE, fat12_info_locked_folder_enum_close },

    { READER_FUN_FOLDER_OPEN, fat12_folder_open },
    { READER_FUN_FOLDER_CLOSE, fat12_folder_close },

    { READER_FUN_FOLDER_CLEAR, fat12_folder_clear },
    { READER_FUN_OPEN, fat12_open },
    { READER_FUN_CLOSE, fat12_close },
    { READER_FUN_UNLINK, fat12_unlink },

    { READER_FUN_READ, fat12_read },
    { READER_FUN_WRITE, fat12_write },
    { READER_FUN_CHSIZE, fat12_chsize },
    { READER_FUN_LENGTH, fat12_length },
};

static const TSupSysFunctionTable fat12_default_table =
{
    sizeof(fat12_default_funs) / sizeof(TSupSysFunctionTableItem),
    fat12_default_funs
};

const TSupSysEHandle *default_reader_get_table(void)
{
    return (const TSupSysEHandle*)&fat12_default_table;
}

static const TSupSysInfoIDSBaseItem DEFAULT_IDS_ITEMS[] =
{
    { SUPSYS_IDS_BLOCK_BASE, IDS_DEFAULT_NAME, SUPSYS_IDS_BASE_QUANT_V2 },
    { SUPSYS_IDS_BLOCK_ICON, IDI_DEFAULT, SUPSYS_IDS_ICON_QUANT_V1 },
    { READER_IDS_BLOCK, IDS_DEFAULT_INSERT_TEXT, READER_IDS_QUANT_V1 }
};

/*!
 * \ingroup fat12_internal
 * \brief Описание смещений ресурса для DEFAULT
 */
static const TSupSysInfoIDSBase DEFAULT_IDS =
{
    sizeof(DEFAULT_IDS_ITEMS) / sizeof(TSupSysInfoIDSBaseItem),
    DEFAULT_IDS_ITEMS
};

/*!
 * \ingroup default_fun_general
 * \brief Функция возвращающая смещение ресурса для создаваемых динамических
 * \brief media
 * \param context [in] контекст считывателя. Не используется.
 * \param info [out] структура #TSupSysInfoIDSBase
 * \return код ошибки.
 * \retval #ERROR_SUCCESS смещение ресурса возвращено.
 * \retval #(DWORD)ERROR_INVALID_PARAMETER при неправильных параметрах
 */
static DWORD fat12_default_info_ids(
    TSupSysContext *context,
    TSupSysInfo *info)
{
    UNUSED(context);

    SUPSYS_PRE_INFO(info, TSupSysInfoIDSBase);
    memcpy(info, &DEFAULT_IDS, sizeof(TSupSysInfoIDSBase));
    return ERROR_SUCCESS;
}

static DWORD fat12_group_info_nickname(TSupSysContext* context, TSupSysInfo* info) {
    UNUSED(context);
    SUPSYS_PRE_INFO(info, TSupSysInfoNickname);
    TSupSysInfoNickname* inf = (TSupSysInfoNickname*)info;
    _tcscpy(inf->nickname, _TEXT("PNP FLASH"));
    return ERROR_SUCCESS;
}

//TODO extract function from register functions, write test on it.

static const TSupSysFunctionTableItem FLASH_GROUP_FUNS[] =
{
    { SUPSYS_FUN_NICKNAME, fat12_group_info_nickname },
    { SUPSYS_FUN_CONTEXT_DUP, fat12_group_context_dup },
    { SUPSYS_FUN_CONTEXT_FREE, fat12_group_context_free },
    { SUPSYS_FUN_FLAGS, flash_group_info_system_flag },
    { SUPSYS_FUN_GROUP_ENUM_OPEN, flash_group_enum_open },
    { SUPSYS_FUN_GROUP_ENUM_NEXT, flash_group_enum_next },
    { SUPSYS_FUN_GROUP_ENUM_CLOSE, flash_group_enum_close },
    { SUPSYS_FUN_VERSIONSUPPORT, info_versionsupport},
    { SUPSYS_FUN_REGISTER, fat12_group_register},
    { SUPSYS_FUN_UNREGISTER, fat12_group_unregister},
};


static const TSupSysFunctionTable FLASH_GROUP_TABLE =
{
    sizeof(FLASH_GROUP_FUNS) / sizeof(TSupSysFunctionTableItem),
    FLASH_GROUP_FUNS
};

const TSupSysEHandle * default_reader_get_group_table(void)
{
    return (const TSupSysEHandle*)&FLASH_GROUP_TABLE;
}
#endif //READERUNITTEST_INCLUDED
