Fix web-logbook ADIF export using UTF-16 char length instead of UTF-8 byte length#642
Conversation
…TF-8 byte length DatabaseOpr.downQSLTable — the ADIF generator behind the built-in web logbook download (LogHttpServer) — declared every <field:len> length with Java String.length() (a UTF-16 char count) instead of the UTF-8 byte count the ADIF grammar requires. For any non-ASCII field (an accented comment, an international POTA park name), the declared length is short, so a byte-correct consumer (LoTW/QRZ/Cloudlog/WSJT-X) reads fewer bytes than were written, truncating the field and mis-aligning the record boundary — the record is rejected or mangled. This is the un-migrated twin of the canonical writer: AdifRecord.build() and the ThirdPartyService upload paths already route every length through AdifFormat.utf8Length() (whose javadoc documents exactly this defect). Route downQSLTable and its appendPotaField helper through the same shared helper. utf8Length has an ASCII fast-path, so pure-ASCII rows stay byte-identical and there is no measurable cost. Also null-guard the comment field (mirroring AdifRecord.build's `comment == null ? ""`): the old comment.length() dereference NPE'd on a NULL comment, aborting the entire web-logbook download and leaking the cursor. Added DatabaseOprAdifLengthTest (Robolectric + MatrixCursor, mirroring DatabaseOprAdifModeTest): non-ASCII comment/operator/POTA sig_info each declare the UTF-8 byte length, ASCII stays char-length (byte-identical), and a NULL comment no longer throws. 4 of the 5 cases fail before the fix. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## dev #642 +/- ##
============================================
+ Coverage 36.71% 36.79% +0.08%
- Complexity 197 207 +10
============================================
Files 216 218 +2
Lines 26902 26994 +92
Branches 3289 3311 +22
============================================
+ Hits 9877 9933 +56
- Misses 16799 16829 +30
- Partials 226 232 +6
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR fixes ADIF export correctness for the built-in web logbook download path (DatabaseOpr.downQSLTable) by declaring ADIF <field:len> values as UTF-8 byte lengths (per ADIF grammar) rather than UTF-16 String.length() character counts, and by null-guarding comment to avoid aborting exports.
Changes:
- Update
DatabaseOpr.downQSLTableand its POTA helper to compute<field:len>viaAdifFormat.utf8Length(...)(UTF-8 byte count) and avoidcommentNPEs. - Add Robolectric unit tests covering non-ASCII field values (and NULL comment) for the web-logbook ADIF export.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| ft8af/app/src/main/java/com/k1af/ft8af/database/DatabaseOpr.java | Switch ADIF length declarations to UTF-8 byte lengths and null-guard comment during web-logbook ADIF generation. |
| ft8af/app/src/test/java/com/k1af/ft8af/database/DatabaseOprAdifLengthTest.java | Add tests verifying UTF-8 byte-length declarations for non-ASCII fields and confirming NULL comment no longer throws. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…aders ASCII Copilot review: %d under a default locale with Arabic-Indic numerals emitted non-ASCII digits in the length headers. Align with AdifRecord.build()'s Locale.US usage; drop the now-unneeded DefaultLocale lint suppression. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Summary
DatabaseOpr.downQSLTable— the ADIF generator behind the built-in web-logbook ADIF download (LogHttpServer, 4 call sites) — declared every<field:len>length using JavaString.length()(a UTF-16 char count) instead of the UTF-8 byte count the ADIF grammar requires.For any non-ASCII field value the declared length is too short, so a byte-correct ADIF consumer (LoTW, QRZ, Cloudlog, WSJT-X) reads fewer bytes than were actually written — truncating the field and mis-aligning everything after it, including the
<eor>record boundary. The record is rejected or silently mangled.Root cause
This method is the un-migrated twin of the canonical ADIF writer. The repo already diagnosed and fixed this exact class of bug:
AdifRecord.build()and theThirdPartyServiceupload paths route every field length throughAdifFormat.utf8Length(), whose javadoc spells out the failure mode ("declaring the shorter char count makes the receiving parser read fewer bytes than were written, truncating the field and mis-aligning everything after it, so LoTW/QRZ/Cloudlog reject or mangle the record").downQSLTablewas never converted, so the web-logbook download regressed the very bug fixed elsewhere.The file-export UI path (
ExportLogSheet→ShareLogs→AdifRecord) is already correct; only this web-download path was affected.Fix
downQSLTableand itsappendPotaFieldhelper through the sharedAdifFormat.utf8Length(). It has an ASCII fast-path, so pure-ASCII rows stay byte-identical and there is no measurable cost.commentfield (mirroringAdifRecord.build()'scomment == null ? ""). The oldcomment.length()dereference threw an NPE on a NULL comment, aborting the entire web-logbook download and leaking the cursor.Reachability
A foreign ADIF containing a non-ASCII
COMMENT(e.g.Café QSO) uploaded to the built-in web logbook is stored verbatim (LogHttpServer.doImportADI→LogFileImport→QSLRecord→doInsertQSLData). Downloading the logbook as ADIF then emits<comment:8>Café QSO <eor>(should be:9), truncating the trailingOand corrupting the record boundary.Concrete example
comment = "Café QSO"→ 8 UTF-16 chars, 9 UTF-8 bytes (é= 2 bytes).<comment:8>Café QSO <eor>→ consumer readsCafé QS, dropsO,<eor>desynced.<comment:9>Café QSO <eor>→ correct.Testing
DatabaseOprAdifLengthTest(Robolectric +MatrixCursor, mirroring the existingDatabaseOprAdifModeTest): non-ASCIIcomment/operator/ POTASIG_INFOeach declare the UTF-8 byte length; ASCII stays char-length (byte-identical); a NULLcommentno longer throws. 4 of 5 cases fail before the fix (3 assertion failures + 1 NPE), all pass after.:app:testDebugUnitTestsuite green.Risk
Low. The change only affects declared field lengths and is byte-identical for ASCII content (the common case); it aligns this path with the already-shipped
AdifFormat.utf8Lengthconvention used by every other ADIF writer. No protocol/DSP behavior changes.