kopia lustrzana https://github.com/OpenRTX/OpenRTX
503 wiersze
16 KiB
C
503 wiersze
16 KiB
C
|
/***************************************************************************
|
||
|
* Copyright (C) 2022 by Federico Amedeo Izzo IU2NUO, *
|
||
|
* Niccolò Izzo IU2KIN *
|
||
|
* Frederik Saraci IU2NRO *
|
||
|
* Silvano Seva IU2KWO *
|
||
|
* *
|
||
|
* This program is free software; you can redistribute it and/or modify *
|
||
|
* it under the terms of the GNU General Public License as published by *
|
||
|
* the Free Software Foundation; either version 3 of the License, or *
|
||
|
* (at your option) any later version. *
|
||
|
* *
|
||
|
* This program is distributed in the hope that it will be useful, *
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||
|
* GNU General Public License for more details. *
|
||
|
* *
|
||
|
* You should have received a copy of the GNU General Public License *
|
||
|
* along with this program; if not, see <http://www.gnu.org/licenses/> *
|
||
|
***************************************************************************/
|
||
|
|
||
|
#include <interfaces/cps_io.h>
|
||
|
#include <string.h>
|
||
|
#include <stdio.h>
|
||
|
#include <time.h>
|
||
|
|
||
|
#define CPS_CHUNK_SIZE 1024
|
||
|
|
||
|
static FILE *cps_file = NULL;
|
||
|
const char *default_author = "Codeplug author.";
|
||
|
const char *default_descr = "Codeplug description.";
|
||
|
|
||
|
/**
|
||
|
* Internal: read and validate codeplug header
|
||
|
*
|
||
|
* @param header: pointer to the header struct to be populated
|
||
|
* @return 0 on success, -1 on failure
|
||
|
*/
|
||
|
int _readHeader(cps_header_t *header)
|
||
|
{
|
||
|
fseek(cps_file, 0L, SEEK_SET);
|
||
|
fread(header, sizeof(cps_header_t), 1, cps_file);
|
||
|
// Validate magic number
|
||
|
if(header->magic != CPS_MAGIC)
|
||
|
return -1;
|
||
|
// Validate version number
|
||
|
if(((header->version_number & 0xff00) >> 8) != CPS_VERSION_MAJOR ||
|
||
|
(header->version_number & 0x00ff) > CPS_VERSION_MINOR)
|
||
|
return -1;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Internal: write codeplug header
|
||
|
*
|
||
|
* @param header: header struct to be written
|
||
|
* @return 0 on success, -1 on failure
|
||
|
*/
|
||
|
int _writeHeader(cps_header_t header)
|
||
|
{
|
||
|
fseek(cps_file, 0L, SEEK_SET);
|
||
|
fwrite(&header, sizeof(cps_header_t), 1, cps_file);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Internal: push down data at a given offset by a given amount
|
||
|
*
|
||
|
* @param offset: offset at which to start to push down data
|
||
|
* @param amount: amount of free space to be created
|
||
|
* @return 0 on success, -1 on failure
|
||
|
*/
|
||
|
|
||
|
int _pushDown(uint32_t offset, uint32_t amount)
|
||
|
{
|
||
|
// Get end of file
|
||
|
fseek(cps_file, 0, SEEK_END);
|
||
|
long end = ftell(cps_file);
|
||
|
// If offset equals end, just return
|
||
|
if (offset == end)
|
||
|
return 0;
|
||
|
// Move data downwards in chunks of fixed size
|
||
|
char buffer[CPS_CHUNK_SIZE] = { 0 };
|
||
|
for(int i = 1; i <= ((end - offset) / CPS_CHUNK_SIZE); i++)
|
||
|
{
|
||
|
fseek(cps_file, end - i * CPS_CHUNK_SIZE, SEEK_SET);
|
||
|
fread(buffer, CPS_CHUNK_SIZE, 1, cps_file);
|
||
|
fseek(cps_file, end - i * CPS_CHUNK_SIZE + amount, SEEK_SET);
|
||
|
fwrite(buffer, CPS_CHUNK_SIZE, 1, cps_file);
|
||
|
}
|
||
|
// Once initial offset is reached, move the last incomplete block
|
||
|
fseek(cps_file, offset, SEEK_SET);
|
||
|
fread(buffer, (end - offset) % CPS_CHUNK_SIZE, 1, cps_file);
|
||
|
fseek(cps_file, offset + amount, SEEK_SET);
|
||
|
fwrite(buffer, (end - offset) % CPS_CHUNK_SIZE, 1, cps_file);
|
||
|
fseek(cps_file, offset, SEEK_SET);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Internal: updates the contact numbering after a contact addition or removal
|
||
|
*
|
||
|
* @param pos: position at which the new contact was inserted or removed
|
||
|
* @param add: if true a contact was inserted, otherwise it was removed
|
||
|
* @return 0 on success, -1 on failure
|
||
|
*/
|
||
|
int _updateCtNumbering(uint16_t pos, bool add)
|
||
|
{
|
||
|
cps_header_t header = { 0 };
|
||
|
if (_readHeader(&header))
|
||
|
return -1;
|
||
|
for(int i = 0; i < header.ch_count; i++)
|
||
|
{
|
||
|
channel_t c = { 0 };
|
||
|
cps_readChannel(&c, i);
|
||
|
if (c.mode == OPMODE_M17 && c.m17.contact_index >= pos)
|
||
|
{
|
||
|
if (add)
|
||
|
c.m17.contact_index++;
|
||
|
else
|
||
|
c.m17.contact_index--;
|
||
|
cps_writeChannel(c, i);
|
||
|
}
|
||
|
if (c.mode == OPMODE_DMR && c.dmr.contact_index >= pos)
|
||
|
{
|
||
|
if (add)
|
||
|
c.dmr.contact_index++;
|
||
|
else
|
||
|
c.dmr.contact_index--;
|
||
|
cps_writeChannel(c, i);
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Internal: updates the channel numbering after a channel addition or removal
|
||
|
*
|
||
|
* @param pos: position at which the new channel was inserted or removed
|
||
|
* @param add: if true a channel was inserted, otherwise it was removed
|
||
|
* @return 0 on success, -1 on failure
|
||
|
*/
|
||
|
int _updateChNumbering(uint16_t pos, bool add)
|
||
|
{
|
||
|
cps_header_t header = { 0 };
|
||
|
if (_readHeader(&header))
|
||
|
return -1;
|
||
|
for(int i = 0; i < header.b_count; i++)
|
||
|
{
|
||
|
bankHdr_t b_header = { 0 };
|
||
|
cps_readBankHeader(&b_header, i);
|
||
|
for(int j = 0; j < b_header.ch_count; j++)
|
||
|
{
|
||
|
int32_t ch = cps_readBankData(i, j);
|
||
|
if (ch >= pos)
|
||
|
{
|
||
|
if (add)
|
||
|
ch++;
|
||
|
else
|
||
|
ch--;
|
||
|
cps_writeBankData(ch, i, j);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Internal: get bank data offset
|
||
|
*
|
||
|
* @param pos: position of the bank to be read
|
||
|
* @return the offset in the file where the bank data is stored, -1 if error
|
||
|
*/
|
||
|
long _getBankDataOffset(uint16_t pos)
|
||
|
{
|
||
|
cps_header_t header = { 0 };
|
||
|
if (_readHeader(&header))
|
||
|
return -1;
|
||
|
if (pos >= header.b_count + 1)
|
||
|
return -1;
|
||
|
if (pos == header.b_count)
|
||
|
{
|
||
|
// No bank is present, no offset to read
|
||
|
if (header.b_count == 0)
|
||
|
return ftell(cps_file) +
|
||
|
header.ct_count * sizeof(contact_t) +
|
||
|
header.ch_count * sizeof(channel_t) +
|
||
|
sizeof(uint32_t);
|
||
|
// Read last bank offset
|
||
|
fseek(cps_file,
|
||
|
header.ct_count * sizeof(contact_t) +
|
||
|
header.ch_count * sizeof(channel_t) +
|
||
|
(header.b_count - 1) * sizeof(uint32_t),
|
||
|
SEEK_CUR);
|
||
|
uint32_t offset = 0;
|
||
|
fread(&offset, sizeof(uint32_t), 1, cps_file);
|
||
|
long bdata_pos = ftell(cps_file);
|
||
|
bankHdr_t last_bank = { 0 };
|
||
|
cps_readBankHeader(&last_bank, header.b_count - 1);
|
||
|
return bdata_pos + offset + sizeof(bankHdr_t) + last_bank.ch_count * sizeof(uint32_t);
|
||
|
}
|
||
|
fseek(cps_file,
|
||
|
header.ct_count * sizeof(contact_t) +
|
||
|
header.ch_count * sizeof(channel_t) +
|
||
|
pos * sizeof(uint32_t),
|
||
|
SEEK_CUR);
|
||
|
uint32_t offset = 0;
|
||
|
fread(&offset, sizeof(uint32_t), 1, cps_file);
|
||
|
return ftell(cps_file) +
|
||
|
(header.b_count - pos - 1) * sizeof(uint32_t) +
|
||
|
offset;
|
||
|
}
|
||
|
|
||
|
int cps_open(char *cps_name)
|
||
|
{
|
||
|
if (!cps_name)
|
||
|
cps_name = "default.rtxc";
|
||
|
cps_file = fopen(cps_name, "r+");
|
||
|
if (!cps_file)
|
||
|
return -1;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void cps_close()
|
||
|
{
|
||
|
fclose(cps_file);
|
||
|
}
|
||
|
|
||
|
int cps_create(char *cps_name)
|
||
|
{
|
||
|
// Clear or create cps file
|
||
|
FILE *new_cps = NULL;
|
||
|
if (!cps_name)
|
||
|
cps_name = "default.rtxc";
|
||
|
new_cps = fopen(cps_name, "w");
|
||
|
if (!new_cps)
|
||
|
return -1;
|
||
|
// Write new header
|
||
|
cps_header_t header = { 0 };
|
||
|
header.magic = CPS_MAGIC;
|
||
|
header.version_number = CPS_VERSION_MAJOR << 8 | CPS_VERSION_MINOR;
|
||
|
strncpy(header.author, default_author, 17);
|
||
|
strncpy(header.descr, default_descr, 23);
|
||
|
// TODO: Implement unix timestamp in Miosix
|
||
|
header.timestamp = time(NULL);
|
||
|
header.ct_count = 0;
|
||
|
header.ch_count = 0;
|
||
|
header.b_count = 0;
|
||
|
fwrite(&header, sizeof(cps_header_t), 1, new_cps);
|
||
|
fclose(new_cps);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int cps_readContact(contact_t *contact, uint16_t pos)
|
||
|
{
|
||
|
cps_header_t header = { 0 };
|
||
|
if (_readHeader(&header))
|
||
|
return -1;
|
||
|
if (pos >= header.ct_count)
|
||
|
return -1;
|
||
|
fseek(cps_file, pos * sizeof(contact_t), SEEK_CUR);
|
||
|
fread(contact, sizeof(contact_t), 1, cps_file);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int cps_readChannel(channel_t *channel, uint16_t pos)
|
||
|
{
|
||
|
cps_header_t header = { 0 };
|
||
|
if (_readHeader(&header))
|
||
|
return -1;
|
||
|
if (pos >= header.ch_count)
|
||
|
return -1;
|
||
|
fseek(cps_file,
|
||
|
header.ct_count * sizeof(contact_t) +
|
||
|
pos * sizeof(channel_t),
|
||
|
SEEK_CUR);
|
||
|
fread(channel, sizeof(channel_t), 1, cps_file);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int cps_readBankHeader(bankHdr_t *b_header, uint16_t pos)
|
||
|
{
|
||
|
cps_header_t header = { 0 };
|
||
|
if (_readHeader(&header))
|
||
|
return -1;
|
||
|
if (pos >= header.b_count)
|
||
|
return -1;
|
||
|
fseek(cps_file,
|
||
|
header.ct_count * sizeof(contact_t) +
|
||
|
header.ch_count * sizeof(channel_t) +
|
||
|
pos * sizeof(uint32_t),
|
||
|
SEEK_CUR);
|
||
|
uint32_t offset = 0;
|
||
|
fread(&offset, sizeof(uint32_t), 1, cps_file);
|
||
|
fseek(cps_file, (header.b_count - pos - 1) * sizeof(uint32_t) + offset, SEEK_CUR);
|
||
|
fread(b_header, sizeof(bankHdr_t), 1, cps_file);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int32_t cps_readBankData(uint16_t bank_pos, uint16_t pos)
|
||
|
{
|
||
|
cps_header_t header = { 0 };
|
||
|
if (_readHeader(&header))
|
||
|
return -1;
|
||
|
if (bank_pos >= header.b_count)
|
||
|
return -1;
|
||
|
fseek(cps_file,
|
||
|
header.ct_count * sizeof(contact_t) +
|
||
|
header.ch_count * sizeof(channel_t) +
|
||
|
bank_pos * sizeof(uint32_t),
|
||
|
SEEK_CUR);
|
||
|
uint32_t offset = 0;
|
||
|
fread(&offset, sizeof(uint32_t), 1, cps_file);
|
||
|
fseek(cps_file, (header.b_count - bank_pos - 1) * sizeof(uint32_t) + offset, SEEK_CUR);
|
||
|
bankHdr_t b_header = { 0 };
|
||
|
fread(&b_header, sizeof(bankHdr_t), 1, cps_file);
|
||
|
if (pos >= b_header.ch_count)
|
||
|
return -1;
|
||
|
fseek(cps_file, pos * sizeof(uint32_t), SEEK_CUR);
|
||
|
uint32_t ch_index = 0;
|
||
|
fread(&ch_index, sizeof(uint32_t), 1, cps_file);
|
||
|
return ch_index;
|
||
|
}
|
||
|
|
||
|
int cps_writeContact(contact_t contact, uint16_t pos)
|
||
|
{
|
||
|
cps_header_t header = { 0 };
|
||
|
if (_readHeader(&header))
|
||
|
return -1;
|
||
|
if (pos >= header.ct_count)
|
||
|
return -1;
|
||
|
fseek(cps_file, pos * sizeof(contact_t), SEEK_CUR);
|
||
|
fwrite(&contact, sizeof(contact_t), 1, cps_file);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int cps_writeChannel(channel_t channel, uint16_t pos)
|
||
|
{
|
||
|
cps_header_t header = { 0 };
|
||
|
if (_readHeader(&header))
|
||
|
return -1;
|
||
|
if (pos >= header.ch_count)
|
||
|
return -1;
|
||
|
fseek(cps_file,
|
||
|
header.ct_count * sizeof(contact_t) +
|
||
|
pos * sizeof(channel_t),
|
||
|
SEEK_CUR);
|
||
|
fwrite(&channel, sizeof(channel_t), 1, cps_file);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int cps_writeBankHeader(bankHdr_t b_header, uint16_t pos)
|
||
|
{
|
||
|
cps_header_t header = { 0 };
|
||
|
if (_readHeader(&header))
|
||
|
return -1;
|
||
|
if (pos >= header.b_count)
|
||
|
return -1;
|
||
|
fseek(cps_file,
|
||
|
header.ct_count * sizeof(contact_t) +
|
||
|
header.ch_count * sizeof(channel_t) +
|
||
|
pos * sizeof(uint32_t),
|
||
|
SEEK_CUR);
|
||
|
uint32_t offset = 0;
|
||
|
fread(&offset, sizeof(uint32_t), 1, cps_file);
|
||
|
fseek(cps_file, header.b_count - pos * sizeof(uint32_t) + offset, SEEK_CUR);
|
||
|
fwrite(&b_header, sizeof(bankHdr_t), 1, cps_file);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int cps_writeBankData(uint32_t ch, uint16_t bank_pos, uint16_t pos)
|
||
|
{
|
||
|
cps_header_t header = { 0 };
|
||
|
if (_readHeader(&header))
|
||
|
return -1;
|
||
|
if (pos >= header.b_count + 1)
|
||
|
return -1;
|
||
|
fseek(cps_file,
|
||
|
header.ct_count * sizeof(contact_t) +
|
||
|
header.ch_count * sizeof(channel_t) +
|
||
|
bank_pos * sizeof(uint32_t),
|
||
|
SEEK_CUR);
|
||
|
uint32_t offset = 0;
|
||
|
fread(&offset, sizeof(uint32_t), 1, cps_file);
|
||
|
fseek(cps_file, (header.b_count - bank_pos - 1) * sizeof(uint32_t) + offset, SEEK_CUR);
|
||
|
bankHdr_t b_header = { 0 };
|
||
|
fread(&b_header, sizeof(bankHdr_t), 1, cps_file);
|
||
|
if (pos >= b_header.ch_count)
|
||
|
return -1;
|
||
|
fseek(cps_file, pos * sizeof(uint32_t), SEEK_CUR);
|
||
|
fwrite(&ch, sizeof(uint32_t), 1, cps_file);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int cps_insertContact(contact_t contact, uint16_t pos)
|
||
|
{
|
||
|
cps_header_t header = { 0 };
|
||
|
if (_readHeader(&header))
|
||
|
return -1;
|
||
|
if (pos >= header.ct_count + 1)
|
||
|
return -1;
|
||
|
long ct_pos = ftell(cps_file) + pos * sizeof(contact_t);
|
||
|
_pushDown(ct_pos, sizeof(contact_t));
|
||
|
fwrite(&contact, sizeof(contact_t), 1, cps_file);
|
||
|
header.ct_count++;
|
||
|
_writeHeader(header);
|
||
|
if (_updateCtNumbering(pos, true))
|
||
|
return -1;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int cps_insertChannel(channel_t channel, uint16_t pos)
|
||
|
{
|
||
|
cps_header_t header = { 0 };
|
||
|
if (_readHeader(&header))
|
||
|
return -1;
|
||
|
if (pos >= header.ch_count + 1)
|
||
|
return -1;
|
||
|
long ch_pos = ftell(cps_file) +
|
||
|
header.ct_count * sizeof(contact_t) +
|
||
|
pos * sizeof(channel_t);
|
||
|
_pushDown(ch_pos, sizeof(channel_t));
|
||
|
fwrite(&channel, sizeof(channel_t), 1, cps_file);
|
||
|
header.ch_count++;
|
||
|
_writeHeader(header);
|
||
|
_updateChNumbering(pos, true);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int cps_insertBankHeader(bankHdr_t b_header, uint16_t pos)
|
||
|
{
|
||
|
cps_header_t header = { 0 };
|
||
|
if (_readHeader(&header))
|
||
|
return -1;
|
||
|
if (pos >= header.b_count + 1)
|
||
|
return -1;
|
||
|
fseek(cps_file,
|
||
|
header.ct_count * sizeof(contact_t) +
|
||
|
header.ch_count * sizeof(channel_t) +
|
||
|
pos * sizeof(uint32_t),
|
||
|
SEEK_CUR);
|
||
|
long b_offset_pos = ftell(cps_file);
|
||
|
uint32_t b_offset = _getBankDataOffset(pos) - _getBankDataOffset(0);
|
||
|
// Read position of the new offset
|
||
|
_pushDown(b_offset_pos, sizeof(uint32_t));
|
||
|
fwrite(&b_offset, sizeof(uint32_t), 1, cps_file);
|
||
|
// Update all the offsets following the moved bank
|
||
|
for(int i = 0; i < header.b_count - pos; i++)
|
||
|
{
|
||
|
long p = ftell(cps_file);
|
||
|
uint32_t o = 0;
|
||
|
fread(&o, sizeof(uint32_t), 1, cps_file);
|
||
|
fseek(cps_file, p, SEEK_SET);
|
||
|
o += sizeof(bankHdr_t);
|
||
|
fwrite(&o, sizeof(uint32_t), 1, cps_file);
|
||
|
}
|
||
|
header.b_count++;
|
||
|
_writeHeader(header);
|
||
|
_pushDown(_getBankDataOffset(pos), sizeof(bankHdr_t));
|
||
|
fwrite(&b_header, sizeof(bankHdr_t), 1, cps_file);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int cps_insertBankData(uint32_t ch, uint16_t bank_pos, uint16_t pos)
|
||
|
{
|
||
|
cps_header_t header = { 0 };
|
||
|
if (_readHeader(&header))
|
||
|
return -1;
|
||
|
if (bank_pos >= header.b_count)
|
||
|
return -1;
|
||
|
fseek(cps_file,
|
||
|
header.ct_count * sizeof(contact_t) +
|
||
|
header.ch_count * sizeof(channel_t) +
|
||
|
bank_pos * sizeof(uint32_t),
|
||
|
SEEK_CUR);
|
||
|
uint32_t offset = 0;
|
||
|
fread(&offset, sizeof(uint32_t), 1, cps_file);
|
||
|
// Update all the offsets following the moved bank
|
||
|
for(int i = 0; i < header.b_count - bank_pos - 1; i++)
|
||
|
{
|
||
|
long p = ftell(cps_file);
|
||
|
uint32_t o = 0;
|
||
|
fread(&o, sizeof(uint32_t), 1, cps_file);
|
||
|
fseek(cps_file, p, SEEK_SET);
|
||
|
o += sizeof(uint32_t);
|
||
|
fwrite(&o, sizeof(uint32_t), 1, cps_file);
|
||
|
}
|
||
|
// Update bank header
|
||
|
fseek(cps_file, offset, SEEK_CUR);
|
||
|
bankHdr_t b_header = { 0 };
|
||
|
long h_pos = ftell(cps_file);
|
||
|
fread(&b_header, sizeof(bankHdr_t), 1, cps_file);
|
||
|
if (pos >= b_header.ch_count + 1)
|
||
|
return -1;
|
||
|
b_header.ch_count++;
|
||
|
fseek(cps_file, h_pos, SEEK_SET);
|
||
|
fwrite(&b_header, sizeof(bankHdr_t), 1, cps_file);
|
||
|
fseek(cps_file, pos * sizeof(uint32_t), SEEK_CUR);
|
||
|
long p = ftell(cps_file);
|
||
|
_pushDown(p, sizeof(uint32_t));
|
||
|
fwrite(&ch, sizeof(uint32_t), 1, cps_file);
|
||
|
return 0;
|
||
|
}
|