File indexing completed on 2025-01-05 05:23:52

0001 /*
0002     This file is part of the Okteta Kasten module, made within the KDE community.
0003 
0004     SPDX-FileCopyrightText: 2010 Friedrich W. H. Kossebau <kossebau@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0007 */
0008 
0009 #include "bytearraysrecstreamencoder.hpp"
0010 
0011 // lib
0012 #include <bytearrayview.hpp>
0013 // Okteta gui
0014 #include <Okteta/ByteArrayTableLayout>
0015 // Okteta core
0016 #include <Okteta/AbstractByteArrayModel>
0017 // KF
0018 #include <KConfigGroup>
0019 #include <KSharedConfig>
0020 #include <KLocalizedString>
0021 // Qt
0022 #include <QTextStream>
0023 // Std
0024 #include <algorithm>
0025 #include <iterator>
0026 
0027 const std::array<QString, Kasten::SRecStreamEncoderSettings::AddressSizeCount>
0028 Kasten::SRecStreamEncoderSettings::addressSizeConfigValueList = {
0029     QStringLiteral("32"),
0030     QStringLiteral("24"),
0031     QStringLiteral("16"),
0032 };
0033 
0034 template <>
0035 inline Kasten::SRecStreamEncoderSettings::AddressSizeId KConfigGroup::readEntry(const char *key,
0036                                                                                    const Kasten::SRecStreamEncoderSettings::AddressSizeId &defaultValue) const
0037 {
0038     const QString entry = readEntry(key, QString());
0039 
0040     auto it = std::find(Kasten::SRecStreamEncoderSettings::addressSizeConfigValueList.cbegin(),
0041                         Kasten::SRecStreamEncoderSettings::addressSizeConfigValueList.cend(),
0042                         entry);
0043     if (it == Kasten::SRecStreamEncoderSettings::addressSizeConfigValueList.cend()) {
0044         return defaultValue;
0045     }
0046 
0047     const int listIndex = std::distance(Kasten::SRecStreamEncoderSettings::addressSizeConfigValueList.cbegin(), it);
0048     return static_cast<Kasten::SRecStreamEncoderSettings::AddressSizeId>(listIndex);
0049 }
0050 
0051 template <>
0052 inline void KConfigGroup::writeEntry(const char *key,
0053                                      const Kasten::SRecStreamEncoderSettings::AddressSizeId &value,
0054                                      KConfigBase::WriteConfigFlags flags)
0055 {
0056     const int listIndex = static_cast<int>(value);
0057     writeEntry(key, Kasten::SRecStreamEncoderSettings::addressSizeConfigValueList[listIndex], flags);
0058 }
0059 
0060 namespace Kasten {
0061 
0062 static inline constexpr
0063 int addressSize(SRecStreamEncoderSettings::AddressSizeId id)
0064 {
0065     return 4 - static_cast<int>(id);
0066 }
0067 
0068 SRecStreamEncoderSettings::SRecStreamEncoderSettings() = default;
0069 
0070 bool SRecStreamEncoderSettings::operator==(const SRecStreamEncoderSettings& other) const
0071 {
0072     return (addressSizeId == other.addressSizeId);
0073 }
0074 
0075 void SRecStreamEncoderSettings::loadConfig(const KConfigGroup& configGroup)
0076 {
0077     addressSizeId = configGroup.readEntry(AddressSizeConfigKey, DefaultAddressSize);
0078 }
0079 
0080 void SRecStreamEncoderSettings::saveConfig(KConfigGroup& configGroup) const
0081 {
0082     configGroup.writeEntry(AddressSizeConfigKey, addressSizeId);
0083 }
0084 
0085 
0086 const char ByteArraySRecStreamEncoder::hexDigits[16] = {
0087     '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
0088 };
0089 
0090 void ByteArraySRecStreamEncoder::streamLine(QTextStream& textStream, RecordType recordType,
0091                                             const unsigned char* line)
0092 {
0093     // checksum is ones' complement of sum of the values in the line
0094     unsigned char checksum = 0;
0095 
0096     textStream << startCode << charOfRecordType(recordType);
0097 
0098     const uint length = line[0];
0099     for (uint i = 0; i < length; ++i) {
0100         const unsigned char byte = line[i];
0101         textStream << hexValueOfNibble(byte >> 4)
0102                    << hexValueOfNibble(byte);
0103         checksum += byte;
0104     }
0105 
0106     checksum = ~checksum;
0107     textStream << hexValueOfNibble(checksum >> 4) << hexValueOfNibble(checksum) << '\n';
0108 }
0109 
0110 void ByteArraySRecStreamEncoder::streamBlockHeader(QTextStream& textStream, unsigned char* line)
0111 //                         const char* moduleName = 0, const char* description = 0,
0112 //                         quint8 version = 0, quint8 revision = 0 )
0113 {
0114     // cmp. https://linux.die.net/man/1/srec_cat
0115     // WP says: vendor specific data rather than program data
0116 //     constexpr int moduleNameLineOffset = 3;
0117 //     constexpr int moduleNameLength = 10;
0118 //     constexpr int versionLineOffset = moduleNameLineOffset + moduleNameLength;
0119 //     constexpr int versionLength = 1;
0120 //     constexpr int revisionLineOffset = versionLineOffset + versionLength;
0121 //     constexpr int revisionLength = 1;
0122 //     constexpr int descriptionLineOffset = revisionLineOffset + revisionLength;
0123 //     constexpr int descriptionLength = 18;
0124     constexpr int headerByteCount = 3;
0125 
0126     line[addressLineOffset] = 0; // address unused
0127     line[addressLineOffset + 1] = 0; // address unused
0128 
0129     // leave data empty for now
0130     line[byteCountLineOffset] = headerByteCount;
0131 
0132     streamLine(textStream, RecordType::BlockHeader, line);
0133 }
0134 
0135 void ByteArraySRecStreamEncoder::streamRecordCount(QTextStream& textStream, unsigned char* line,
0136                                                    quint16 recordCount)
0137 {
0138     constexpr int recordCountLineSize = 2;
0139     constexpr int recordCountByteCount = byteCountLineSize + recordCountLineSize;
0140 
0141     line[byteCountLineOffset] = recordCountByteCount;
0142     writeBigEndian(&line[addressLineOffset], recordCount, recordCountLineSize);
0143 
0144     streamLine(textStream, RecordType::RecordCount, line);
0145 }
0146 
0147 // from M68000PRM.pdf:
0148 // comp. with tty 28 bytes are max (with 3b address)
0149 // terminated with CR if downloading
0150 // s-record may have some initial field, e.g. for line-number
0151 // end of x-block: The address field may optionally contain the x-byte address of the instruction to which control is to be passed.
0152 // Under VERSAdos, the resident linkerOs ENTRY command can be used to
0153 // specify this address. If this address is not specified, the first entry point speci-
0154 // fication encountered in the object module input will be used. There is no code/
0155 // data field.
0156 
0157 // TODO: recordType is not limited to valid values, also brings recalculation of addressLineSize
0158 void ByteArraySRecStreamEncoder::streamBlockEnd(QTextStream& textStream, unsigned char* line,
0159                                                 RecordType recordType, quint32 startAddress)
0160 {
0161     const int addressLineSize = endOfBlockAddressSize(recordType);
0162     const int blockEndByteCount = byteCountLineSize + addressLineSize;
0163 
0164     line[byteCountLineOffset] = blockEndByteCount;
0165     writeBigEndian(&line[addressLineOffset], startAddress, addressLineSize);
0166 
0167     streamLine(textStream, recordType, line);
0168 }
0169 
0170 ByteArraySRecStreamEncoder::ByteArraySRecStreamEncoder()
0171     : AbstractByteArrayStreamEncoder(i18nc("name of the encoding target", "S-Record"), QStringLiteral("text/x-srecord"))
0172 {
0173     const KConfigGroup configGroup(KSharedConfig::openConfig(), ConfigGroupId);
0174     mSettings.loadConfig(configGroup);
0175 }
0176 
0177 ByteArraySRecStreamEncoder::~ByteArraySRecStreamEncoder() = default;
0178 
0179 void ByteArraySRecStreamEncoder::setSettings(const SRecStreamEncoderSettings& settings)
0180 {
0181     if (mSettings == settings) {
0182         return;
0183     }
0184 
0185     mSettings = settings;
0186     KConfigGroup configGroup(KSharedConfig::openConfig(), ConfigGroupId);
0187     mSettings.saveConfig(configGroup);
0188     Q_EMIT settingsChanged();
0189 }
0190 
0191 bool ByteArraySRecStreamEncoder::encodeDataToStream(QIODevice* device,
0192                                                     const ByteArrayView* byteArrayView,
0193                                                     const Okteta::AbstractByteArrayModel* byteArrayModel,
0194                                                     const Okteta::AddressRange& range)
0195 {
0196     Q_UNUSED(byteArrayView);
0197 
0198     bool success = true;
0199 
0200     // encode
0201     QTextStream textStream(device);
0202 
0203     // prepare
0204     constexpr int maxLineLength = 64 / 2;
0205     const int addressLineSize = addressSize(mSettings.addressSizeId);
0206     const int maxDataPerLineCount = maxLineLength - byteCountLineSize - addressLineSize;
0207     const int dataLineOffset = addressLineOffset + addressLineSize;
0208 
0209     const Okteta::ByteArrayTableLayout layout(byteArrayView->noOfBytesPerLine(),
0210                                               byteArrayView->firstLineOffset(),
0211                                               byteArrayView->startOffset(), 0, byteArrayModel->size());
0212 
0213     const Okteta::Coord startCoord = layout.coordOfIndex(range.start());
0214     const int lastLinePosition = layout.lastLinePosition(startCoord.line());
0215     const int dataPerLineCount = qMin(byteArrayView->noOfBytesPerLine(), maxDataPerLineCount);
0216     const RecordType dataSequenceType = dataSequenceRecordType(mSettings.addressSizeId);
0217     const RecordType endOfBlockType = endOfBlockRecordType(mSettings.addressSizeId);
0218 
0219     unsigned char line[maxLineLength];
0220 
0221     unsigned char* const lineData = &line[dataLineOffset];
0222     const int firstDataEnd = lastLinePosition - startCoord.pos() + 1;
0223     int d = 0;
0224     int nextDataEnd = qMin(firstDataEnd, dataPerLineCount);
0225     Okteta::Address recordOffset = range.start();
0226     int recordCount = 0;
0227 
0228     // header
0229     streamBlockHeader(textStream, line);
0230 
0231     Okteta::Address i = range.start();
0232     while (i <= range.end()) {
0233         const Okteta::Byte byte = byteArrayModel->byte(i);
0234         lineData[d] = byte;
0235 
0236         ++d;
0237         ++i;
0238         if (d == nextDataEnd) {
0239             line[byteCountLineOffset] = d + 1 + addressLineSize;
0240             writeBigEndian(&line[addressLineOffset], recordOffset, addressLineSize);
0241 
0242             streamLine(textStream, dataSequenceType, line);
0243 
0244             ++recordCount;
0245             recordOffset = i;
0246             d = 0;
0247             nextDataEnd = qMin(range.end() - i + 1, dataPerLineCount);
0248         }
0249     }
0250 
0251     // footer
0252     streamRecordCount(textStream, line, recordCount);
0253     streamBlockEnd(textStream, line, endOfBlockType);
0254 
0255     return success;
0256 }
0257 
0258 }
0259 
0260 #include "moc_bytearraysrecstreamencoder.cpp"