KiCad PCB EDA Suite
Loading...
Searching...
No Matches
diptrace_binary_reader.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
25
26#include <algorithm>
27#include <cstdio>
28#include <cstring>
29
30#include <ki_exception.h>
31#include <wx/filename.h>
32#include <wx/translation.h>
33
34
35using namespace DIPTRACE;
36
37
38BINARY_READER::BINARY_READER( const wxString& aFileName ) :
39 m_offset( 0 ),
40 m_version( 0 ),
42{
43 FILE* fp = wxFopen( aFileName, wxT( "rb" ) );
44
45 if( fp == nullptr )
46 {
47 THROW_IO_ERROR( wxString::Format( _( "Cannot open file '%s'." ), aFileName ) );
48 }
49
50 fseek( fp, 0, SEEK_END );
51 long len = ftell( fp );
52
53 if( len < 0 )
54 {
55 fclose( fp );
57 wxString::Format( _( "Cannot determine length of file '%s'." ), aFileName ) );
58 }
59
60 m_data.resize( static_cast<size_t>( len ) );
61
62 fseek( fp, 0, SEEK_SET );
63
64 size_t bytesRead = fread( m_data.data(), 1, m_data.size(), fp );
65 fclose( fp );
66
67 if( bytesRead != m_data.size() )
68 {
69 THROW_IO_ERROR( wxString::Format( _( "Error reading file '%s'." ), aFileName ) );
70 }
71}
72
73
77
78
79// --- Position management ----------------------------------------------------
80
81
82void BINARY_READER::SetOffset( size_t aOffset )
83{
84 if( aOffset > m_data.size() )
85 {
86 THROW_IO_ERROR( wxString::Format( _( "Seek past end of file (offset %zu, size %zu)." ),
87 aOffset, m_data.size() ) );
88 }
89
90 m_offset = aOffset;
91}
92
93
94void BINARY_READER::Skip( size_t aBytes )
95{
96 if( m_offset + aBytes > m_data.size() )
97 ThrowEOFError( aBytes );
98
99 m_offset += aBytes;
100}
101
102
103// --- Primitive readers ------------------------------------------------------
104
105
107{
108 if( m_offset + 1 > m_data.size() )
109 ThrowEOFError( 1 );
110
111 uint8_t val = m_data[m_offset];
112 m_offset += 1;
113 return val;
114}
115
116
118{
119 if( m_offset + 3 > m_data.size() )
120 ThrowEOFError( 3 );
121
122 const uint8_t* p = &m_data[m_offset];
123 int raw = ( static_cast<int>( p[0] ) << 16 )
124 | ( static_cast<int>( p[1] ) << 8 )
125 | ( static_cast<int>( p[2] ) );
126 m_offset += 3;
127 return raw - INT3_BIAS;
128}
129
130
132{
133 if( m_offset + 4 > m_data.size() )
134 ThrowEOFError( 4 );
135
136 const uint8_t* p = &m_data[m_offset];
137 unsigned int raw = ( static_cast<unsigned int>( p[0] ) << 24 )
138 | ( static_cast<unsigned int>( p[1] ) << 16 )
139 | ( static_cast<unsigned int>( p[2] ) << 8 )
140 | ( static_cast<unsigned int>( p[3] ) );
141 m_offset += 4;
142 return static_cast<int>( raw ) - INT4_BIAS;
143}
144
145
147{
149 return ReadStringUTF16();
150
153 {
154 return ReadStringASCII();
155 }
156
157 return ReadStringUTF16();
158}
159
160
161void BINARY_READER::ReadColor( uint8_t& r, uint8_t& g, uint8_t& b )
162{
163 r = ReadByte();
164 g = ReadByte();
165 b = ReadByte();
166}
167
168
169void BINARY_READER::ReadBytes( uint8_t* aDst, size_t aCount )
170{
171 if( m_offset + aCount > m_data.size() )
172 ThrowEOFError( aCount );
173
174 std::memcpy( aDst, &m_data[m_offset], aCount );
175 m_offset += aCount;
176}
177
178
179// --- Peek methods -----------------------------------------------------------
180
181
183{
184 if( m_offset + 3 > m_data.size() )
185 {
186 THROW_IO_ERROR( wxString::Format(
187 _( "Unexpected end of file at offset 0x%06zX: need 3 bytes for int3, "
188 "have %zu remaining." ),
189 m_offset, m_data.size() - m_offset ) );
190 }
191
192 const uint8_t* p = &m_data[m_offset];
193 int raw = ( static_cast<int>( p[0] ) << 16 )
194 | ( static_cast<int>( p[1] ) << 8 )
195 | ( static_cast<int>( p[2] ) );
196 return raw - INT3_BIAS;
197}
198
199
201{
202 if( m_offset + 4 > m_data.size() )
203 {
204 THROW_IO_ERROR( wxString::Format(
205 _( "Unexpected end of file at offset 0x%06zX: need 4 bytes for int4, "
206 "have %zu remaining." ),
207 m_offset, m_data.size() - m_offset ) );
208 }
209
210 const uint8_t* p = &m_data[m_offset];
211 unsigned int raw = ( static_cast<unsigned int>( p[0] ) << 24 )
212 | ( static_cast<unsigned int>( p[1] ) << 16 )
213 | ( static_cast<unsigned int>( p[2] ) << 8 )
214 | ( static_cast<unsigned int>( p[3] ) );
215 return static_cast<int>( raw ) - INT4_BIAS;
216}
217
218
220{
221 if( m_offset >= m_data.size() )
222 {
223 THROW_IO_ERROR( wxString::Format(
224 _( "Unexpected end of file at offset 0x%06zX: need 1 byte." ), m_offset ) );
225 }
226
227 return m_data[m_offset];
228}
229
230
231// --- Coordinate conversion --------------------------------------------------
232
233
234int BINARY_READER::DipTraceToKiCadNm( int aDipTraceCoord )
235{
236 return static_cast<int>( static_cast<int64_t>( aDipTraceCoord ) * 100 / 3 );
237}
238
239
240double BINARY_READER::DipTraceToMM( int aDipTraceCoord )
241{
242 return static_cast<double>( aDipTraceCoord ) * DIPTRACE_COORD_TO_MM;
243}
244
245
246// --- Search helpers ---------------------------------------------------------
247
248
249size_t BINARY_READER::FindPattern( const uint8_t* aPattern, size_t aPatternLen,
250 size_t aStart, size_t aEnd ) const
251{
252 if( aEnd == 0 || aEnd > m_data.size() )
253 aEnd = m_data.size();
254
255 if( aStart >= aEnd || aPatternLen == 0 || aPatternLen > ( aEnd - aStart ) )
256 return std::string::npos;
257
258 auto it = std::search( m_data.begin() + aStart,
259 m_data.begin() + aEnd,
260 aPattern,
261 aPattern + aPatternLen );
262
263 if( it == m_data.begin() + aEnd )
264 return std::string::npos;
265
266 return static_cast<size_t>( it - m_data.begin() );
267}
268
269
270size_t BINARY_READER::FindString( const wxString& aStr, size_t aStart, size_t aEnd ) const
271{
272 if( aStr.IsEmpty() )
273 return std::string::npos;
274
275 // Encode the string as UTF-16-BE, which is the v39+ on-disk representation.
276 // The on-disk format has a 2-byte length prefix before the encoded characters.
277 wxMBConvUTF16BE conv;
278
279 // wxMBConvUTF16BE::FromWChar includes a BOM; we must skip it.
280 // We encode manually: each wxChar becomes 2 bytes in UTF-16-BE.
281 size_t charCount = aStr.length();
282 std::vector<uint8_t> encoded( charCount * 2 );
283
284 for( size_t i = 0; i < charCount; i++ )
285 {
286 wxChar ch = aStr[i];
287 encoded[i * 2] = static_cast<uint8_t>( ( ch >> 8 ) & 0xFF );
288 encoded[i * 2 + 1] = static_cast<uint8_t>( ch & 0xFF );
289 }
290
291 // Search for the encoded character data in the file buffer.
292 size_t matchPos = FindPattern( encoded.data(), encoded.size(), aStart, aEnd );
293
294 if( matchPos == std::string::npos )
295 return std::string::npos;
296
297 // The length prefix sits 2 bytes before the encoded character data.
298 if( matchPos < 2 )
299 return std::string::npos;
300
301 return matchPos - 2;
302}
303
304
305// --- Try-read methods -------------------------------------------------------
306
307
308bool BINARY_READER::TryReadString( wxString& aResult )
309{
311 return TryReadStringUTF16( aResult );
312
315 {
316 return TryReadStringASCII( aResult );
317 }
318
319 return TryReadStringUTF16( aResult );
320}
321
322
323void BINARY_READER::DetectStringEncoding( size_t aProbeOffset )
324{
325 size_t savedOffset = m_offset;
326 STRING_ENCODING savedEncoding = m_stringEncoding;
327
328 // The probe helpers below dispatch on m_stringEncoding, so force each framing explicitly.
329 m_offset = aProbeOffset;
331 wxString asciiStr;
332 bool asciiOk = TryReadStringASCII( asciiStr ) && !asciiStr.IsEmpty();
333
334 m_offset = aProbeOffset;
336 wxString utf16Str;
337 bool utf16Ok = TryReadStringUTF16( utf16Str ) && !utf16Str.IsEmpty();
338
339 m_offset = savedOffset;
340 m_stringEncoding = savedEncoding;
341
342 // Only commit when exactly one framing yields a printable string; otherwise leave the
343 // version-based default in place.
344 if( asciiOk && !utf16Ok )
346 else if( utf16Ok && !asciiOk )
348}
349
350
351// --- Private string readers -------------------------------------------------
352
353
355{
356 if( m_offset + 2 > m_data.size() )
357 ThrowEOFError( 2 );
358
359 const uint8_t* p = &m_data[m_offset];
360 int charCount = ( static_cast<int>( p[0] ) << 8 ) | static_cast<int>( p[1] );
361 m_offset += 2;
362
363 if( charCount == 0 )
364 return wxString();
365
366 if( charCount < 0 || charCount > MAX_STRING_CHARS )
367 {
368 THROW_IO_ERROR( wxString::Format(
369 _( "Unreasonable string length %d at offset 0x%06zX." ),
370 charCount, m_offset - 2 ) );
371 }
372
373 size_t byteCount = static_cast<size_t>( charCount ) * 2;
374
375 if( m_offset + byteCount > m_data.size() )
376 ThrowEOFError( byteCount );
377
378 // wxMBConvUTF16BE converts from a big-endian byte stream.
379 wxMBConvUTF16BE conv;
380 wxString result = wxString( reinterpret_cast<const char*>( &m_data[m_offset] ),
381 conv, byteCount );
382
383 m_offset += byteCount;
384 return result;
385}
386
387
389{
390 int byteCount = ReadInt3();
391
392 if( byteCount == 0 )
393 return wxString();
394
395 if( byteCount < 0 || byteCount > MAX_STRING_CHARS )
396 {
397 THROW_IO_ERROR( wxString::Format(
398 _( "Unreasonable v37 string length %d at offset 0x%06zX." ),
399 byteCount, m_offset - 3 ) );
400 }
401
402 size_t count = static_cast<size_t>( byteCount );
403
404 if( m_offset + count > m_data.size() )
405 ThrowEOFError( count );
406
407 wxString result = wxString::From8BitData(
408 reinterpret_cast<const char*>( &m_data[m_offset] ), count );
409 m_offset += count;
410 return result;
411}
412
413
414bool BINARY_READER::TryReadStringUTF16( wxString& aResult )
415{
416 size_t savedOffset = m_offset;
417
418 if( m_offset + 2 > m_data.size() )
419 return false;
420
421 const uint8_t* p = &m_data[m_offset];
422 int charCount = ( static_cast<int>( p[0] ) << 8 ) | static_cast<int>( p[1] );
423 m_offset += 2;
424
425 if( charCount == 0 )
426 {
427 aResult = wxString();
428 return true;
429 }
430
431 if( charCount < 0 || charCount > 500 )
432 {
433 m_offset = savedOffset;
434 return false;
435 }
436
437 size_t byteCount = static_cast<size_t>( charCount ) * 2;
438
439 if( m_offset + byteCount > m_data.size() )
440 {
441 m_offset = savedOffset;
442 return false;
443 }
444
445 wxMBConvUTF16BE conv;
446 wxString candidate = wxString( reinterpret_cast<const char*>( &m_data[m_offset] ),
447 conv, byteCount );
448
449 if( !IsPrintableString( candidate ) )
450 {
451 m_offset = savedOffset;
452 return false;
453 }
454
455 m_offset += byteCount;
456 aResult = candidate;
457 return true;
458}
459
460
461bool BINARY_READER::TryReadStringASCII( wxString& aResult )
462{
463 size_t savedOffset = m_offset;
464
465 if( m_offset + 3 > m_data.size() )
466 return false;
467
468 const uint8_t* p = &m_data[m_offset];
469 int byteCount = ( static_cast<int>( p[0] ) << 16 )
470 | ( static_cast<int>( p[1] ) << 8 )
471 | ( static_cast<int>( p[2] ) );
472 byteCount -= INT3_BIAS;
473 m_offset += 3;
474
475 if( byteCount == 0 )
476 {
477 aResult = wxString();
478 return true;
479 }
480
481 if( byteCount < 0 || byteCount > 500 )
482 {
483 m_offset = savedOffset;
484 return false;
485 }
486
487 size_t count = static_cast<size_t>( byteCount );
488
489 if( m_offset + count > m_data.size() )
490 {
491 m_offset = savedOffset;
492 return false;
493 }
494
495 wxString candidate = wxString::From8BitData(
496 reinterpret_cast<const char*>( &m_data[m_offset] ), count );
497
498 if( !IsPrintableString( candidate ) )
499 {
500 m_offset = savedOffset;
501 return false;
502 }
503
504 m_offset += count;
505 aResult = candidate;
506 return true;
507}
508
509
510bool BINARY_READER::IsPrintableString( const wxString& aStr )
511{
512 for( size_t i = 0; i < aStr.length(); i++ )
513 {
514 wxChar ch = aStr[i];
515
516 if( ch == '\r' || ch == '\n' || ch == '\t' )
517 continue;
518
519 if( ch < 0x20 )
520 return false;
521 }
522
523 return true;
524}
525
526
527void BINARY_READER::ThrowEOFError( size_t aBytesNeeded ) const
528{
529 size_t remaining = ( m_offset < m_data.size() ) ? ( m_data.size() - m_offset ) : 0;
530
531 THROW_IO_ERROR( wxString::Format(
532 _( "Unexpected end of file at offset 0x%06zX: need %zu bytes, have %zu remaining." ),
533 m_offset, aBytesNeeded, remaining ) );
534}
bool TryReadStringUTF16(wxString &aResult)
Attempt to read a UTF-16-BE string with validation.
size_t FindString(const wxString &aStr, size_t aStart, size_t aEnd) const
Search for a UTF-16-BE encoded string in the file data, including its two-byte length prefix.
int PeekInt3() const
Peek at the next 3-byte biased integer without advancing the position.
wxString ReadStringASCII()
Read a v37 legacy ASCII string: int3(byte_count) + raw ASCII bytes.
bool TryReadStringASCII(wxString &aResult)
Attempt to read a legacy ASCII string with validation.
void ReadBytes(uint8_t *aDst, size_t aCount)
Read a block of raw bytes into the caller's buffer.
int m_version
DipTrace format version.
void Skip(size_t aBytes)
Advance the read position by the given number of bytes.
uint8_t ReadByte()
Read a single unsigned byte and advance the position by 1.
size_t m_offset
Current read position (byte offset).
uint8_t PeekByte() const
Peek at the next byte without advancing the position.
void ThrowEOFError(size_t aBytesNeeded) const
Throw IO_ERROR with a message indicating a read past end of file.
void DetectStringEncoding(size_t aProbeOffset)
Detect the string encoding from the bytes at aProbeOffset, which must sit at the start of a non-empty...
STRING_ENCODING m_stringEncoding
Explicit string encoding override.
static bool IsPrintableString(const wxString &aStr)
Verify that all characters in aStr are printable or common whitespace (space, tab,...
void ReadColor(uint8_t &r, uint8_t &g, uint8_t &b)
Read a 3-byte RGB color value.
int ReadInt4()
Read a 4-byte big-endian biased integer (bias 1,000,000,000) and advance the position by 4.
int ReadInt3()
Read a 3-byte big-endian biased integer (bias 1,000,000) and advance the position by 3.
static int DipTraceToKiCadNm(int aDipTraceCoord)
Convert a DipTrace coordinate value (10 nm units) to KiCad nanometers.
size_t FindPattern(const uint8_t *aPattern, size_t aPatternLen, size_t aStart, size_t aEnd) const
Search for a byte pattern in the file data.
void SetOffset(size_t aOffset)
Set the read position to an absolute byte offset.
bool TryReadString(wxString &aResult)
Attempt to read a string at the current position.
wxString ReadStringUTF16()
Read a v39+ UTF-16-BE string: uint16-BE char count + UTF-16-BE data.
BINARY_READER(const wxString &aFileName)
Construct a reader by loading the given file into memory.
int PeekInt4() const
Peek at the next 4-byte biased integer without advancing the position.
static double DipTraceToMM(int aDipTraceCoord)
Convert a DipTrace coordinate value (10 nm units) to millimeters.
wxString ReadString()
Read a string using the configured encoding.
std::vector< uint8_t > m_data
Entire file contents loaded into memory.
#define _(s)
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
constexpr double DIPTRACE_COORD_TO_MM
DipTrace uses 762 units per mil (30 000 units per mm).
constexpr int MAX_STRING_CHARS
Maximum sane string length (in characters) accepted by the reader.
constexpr int INT4_BIAS
Bias value added to stored 4-byte unsigned integers.
constexpr int LEGACY_STRING_VERSION
Format version at or below which strings use the legacy ASCII encoding (int3 byte-count + raw ASCII b...
constexpr int INT3_BIAS
Bias value added to stored 3-byte unsigned integers.
wxString result
Test unit parsing edge cases and error handling.