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, see <https://www.gnu.org/licenses/>.
18 */
19
21
22#include <algorithm>
23#include <cstdio>
24#include <cstring>
25
26#include <ki_exception.h>
27#include <wx/filename.h>
28#include <wx/translation.h>
29
30
31using namespace DIPTRACE;
32
33
34BINARY_READER::BINARY_READER( const wxString& aFileName ) :
35 m_offset( 0 ),
36 m_version( 0 ),
38{
39 FILE* fp = wxFopen( aFileName, wxT( "rb" ) );
40
41 if( fp == nullptr )
42 THROW_IO_ERRORF( _( "Cannot open file '%s'." ), aFileName );
43
44 fseek( fp, 0, SEEK_END );
45 long len = ftell( fp );
46
47 if( len < 0 )
48 {
49 fclose( fp );
50 THROW_IO_ERRORF( _( "Cannot determine length of file '%s'." ), aFileName );
51 }
52
53 // Reject absurd sizes before allocating so a corrupt or hostile length cannot drive an
54 // arbitrarily large allocation; real DipTrace files are a few MB.
55 static constexpr long MAX_FILE_SIZE = 1L << 30; // 1 GiB
56
57 if( len > MAX_FILE_SIZE )
58 {
59 fclose( fp );
60 THROW_IO_ERRORF( _( "DipTrace file '%s' is too large (%ld bytes)." ), aFileName, len );
61 }
62
63 // resize() can throw (bad_alloc/length_error); close the handle on that path too.
64 try
65 {
66 m_data.resize( static_cast<size_t>( len ) );
67 }
68 catch( ... )
69 {
70 fclose( fp );
71 throw;
72 }
73
74 fseek( fp, 0, SEEK_SET );
75
76 size_t bytesRead = fread( m_data.data(), 1, m_data.size(), fp );
77 fclose( fp );
78
79 if( bytesRead != m_data.size() )
80 THROW_IO_ERRORF( _( "Error reading file '%s'." ), aFileName );
81}
82
83
87
88
89// --- Position management ----------------------------------------------------
90
91
92void BINARY_READER::SetOffset( size_t aOffset )
93{
94 if( aOffset > m_data.size() )
95 THROW_IO_ERRORF( _( "Seek past end of file (offset %zu, size %zu)." ), aOffset, m_data.size() );
96
97 m_offset = aOffset;
98}
99
100
101void BINARY_READER::Skip( size_t aBytes )
102{
103 // Wrap-safe: m_offset <= size is an invariant, so size - m_offset never underflows; writing it
104 // as m_offset + aBytes would wrap for an attacker-sized count and pass the check.
105 if( aBytes > m_data.size() - m_offset )
106 ThrowEOFError( aBytes );
107
108 m_offset += aBytes;
109}
110
111
112// --- Primitive readers ------------------------------------------------------
113
114
116{
117 if( m_offset + 1 > m_data.size() )
118 ThrowEOFError( 1 );
119
120 uint8_t val = m_data[m_offset];
121 m_offset += 1;
122 return val;
123}
124
125
127{
128 if( m_offset + 3 > m_data.size() )
129 ThrowEOFError( 3 );
130
131 const uint8_t* p = &m_data[m_offset];
132 int raw = ( static_cast<int>( p[0] ) << 16 )
133 | ( static_cast<int>( p[1] ) << 8 )
134 | ( static_cast<int>( p[2] ) );
135 m_offset += 3;
136 return raw - INT3_BIAS;
137}
138
139
141{
142 if( m_offset + 4 > m_data.size() )
143 ThrowEOFError( 4 );
144
145 const uint8_t* p = &m_data[m_offset];
146 unsigned int raw = ( static_cast<unsigned int>( p[0] ) << 24 )
147 | ( static_cast<unsigned int>( p[1] ) << 16 )
148 | ( static_cast<unsigned int>( p[2] ) << 8 )
149 | ( static_cast<unsigned int>( p[3] ) );
150 m_offset += 4;
151
152 // Subtract in int64 so a raw value with the high bit set (>= 2^31) cannot overflow the
153 // intermediate signed int before the bias is applied.
154 return static_cast<int>( static_cast<int64_t>( raw ) - INT4_BIAS );
155}
156
157
159{
161 return ReadStringUTF16();
162
165 {
166 return ReadStringASCII();
167 }
168
169 return ReadStringUTF16();
170}
171
172
173void BINARY_READER::ReadColor( uint8_t& r, uint8_t& g, uint8_t& b )
174{
175 r = ReadByte();
176 g = ReadByte();
177 b = ReadByte();
178}
179
180
181void BINARY_READER::ReadBytes( uint8_t* aDst, size_t aCount )
182{
183 // Wrap-safe (m_offset <= size invariant); m_offset + aCount would wrap for a huge count.
184 if( aCount > m_data.size() - m_offset )
185 ThrowEOFError( aCount );
186
187 std::memcpy( aDst, &m_data[m_offset], aCount );
188 m_offset += aCount;
189}
190
191
192// --- Peek methods -----------------------------------------------------------
193
194
196{
197 if( m_offset + 3 > m_data.size() )
198 {
199 THROW_IO_ERRORF( _( "Unexpected end of file at offset 0x%06zX: need 3 bytes for int3, have %zu remaining." ),
200 m_offset,
201 m_data.size() - m_offset );
202 }
203
204 const uint8_t* p = &m_data[m_offset];
205 int raw = ( static_cast<int>( p[0] ) << 16 )
206 | ( static_cast<int>( p[1] ) << 8 )
207 | ( static_cast<int>( p[2] ) );
208 return raw - INT3_BIAS;
209}
210
211
213{
214 if( m_offset + 4 > m_data.size() )
215 {
216 THROW_IO_ERRORF( _( "Unexpected end of file at offset 0x%06zX: need 4 bytes for int4, have %zu remaining." ),
217 m_offset,
218 m_data.size() - m_offset );
219 }
220
221 const uint8_t* p = &m_data[m_offset];
222 unsigned int raw = ( static_cast<unsigned int>( p[0] ) << 24 )
223 | ( static_cast<unsigned int>( p[1] ) << 16 )
224 | ( static_cast<unsigned int>( p[2] ) << 8 )
225 | ( static_cast<unsigned int>( p[3] ) );
226
227 // Subtract in int64 so a raw value with the high bit set cannot overflow the intermediate int.
228 return static_cast<int>( static_cast<int64_t>( raw ) - INT4_BIAS );
229}
230
231
233{
234 if( m_offset >= m_data.size() )
235 THROW_IO_ERRORF( _( "Unexpected end of file at offset 0x%06zX: need 1 byte." ), m_offset );
236
237 return m_data[m_offset];
238}
239
240
241// --- Coordinate conversion --------------------------------------------------
242
243
244int BINARY_READER::DipTraceToKiCadNm( int aDipTraceCoord )
245{
246 return static_cast<int>( static_cast<int64_t>( aDipTraceCoord ) * 100 / 3 );
247}
248
249
250double BINARY_READER::DipTraceToMM( int aDipTraceCoord )
251{
252 return static_cast<double>( aDipTraceCoord ) * DIPTRACE_COORD_TO_MM;
253}
254
255
256// --- Search helpers ---------------------------------------------------------
257
258
259size_t BINARY_READER::FindPattern( const uint8_t* aPattern, size_t aPatternLen,
260 size_t aStart, size_t aEnd ) const
261{
262 if( aEnd == 0 || aEnd > m_data.size() )
263 aEnd = m_data.size();
264
265 if( aStart >= aEnd || aPatternLen == 0 || aPatternLen > ( aEnd - aStart ) )
266 return std::string::npos;
267
268 auto it = std::search( m_data.begin() + aStart,
269 m_data.begin() + aEnd,
270 aPattern,
271 aPattern + aPatternLen );
272
273 if( it == m_data.begin() + aEnd )
274 return std::string::npos;
275
276 return static_cast<size_t>( it - m_data.begin() );
277}
278
279
280size_t BINARY_READER::FindString( const wxString& aStr, size_t aStart, size_t aEnd ) const
281{
282 if( aStr.IsEmpty() )
283 return std::string::npos;
284
285 // Encode the string as UTF-16-BE, which is the v39+ on-disk representation.
286 // The on-disk format has a 2-byte length prefix before the encoded characters.
287 wxMBConvUTF16BE conv;
288
289 // wxMBConvUTF16BE::FromWChar includes a BOM; we must skip it.
290 // We encode manually: each wxChar becomes 2 bytes in UTF-16-BE.
291 size_t charCount = aStr.length();
292 std::vector<uint8_t> encoded( charCount * 2 );
293
294 for( size_t i = 0; i < charCount; i++ )
295 {
296 wxChar ch = aStr[i];
297 encoded[i * 2] = static_cast<uint8_t>( ( ch >> 8 ) & 0xFF );
298 encoded[i * 2 + 1] = static_cast<uint8_t>( ch & 0xFF );
299 }
300
301 // Search for the encoded character data in the file buffer.
302 size_t matchPos = FindPattern( encoded.data(), encoded.size(), aStart, aEnd );
303
304 if( matchPos == std::string::npos )
305 return std::string::npos;
306
307 // The length prefix sits 2 bytes before the encoded character data.
308 if( matchPos < 2 )
309 return std::string::npos;
310
311 return matchPos - 2;
312}
313
314
315// --- Try-read methods -------------------------------------------------------
316
317
318bool BINARY_READER::TryReadString( wxString& aResult )
319{
321 return TryReadStringUTF16( aResult );
322
325 {
326 return TryReadStringASCII( aResult );
327 }
328
329 return TryReadStringUTF16( aResult );
330}
331
332
333void BINARY_READER::DetectStringEncoding( size_t aProbeOffset )
334{
335 size_t savedOffset = m_offset;
336 STRING_ENCODING savedEncoding = m_stringEncoding;
337
338 // The probe helpers below dispatch on m_stringEncoding, so force each framing explicitly.
339 m_offset = aProbeOffset;
341 wxString asciiStr;
342 bool asciiOk = TryReadStringASCII( asciiStr ) && !asciiStr.IsEmpty();
343
344 m_offset = aProbeOffset;
346 wxString utf16Str;
347 bool utf16Ok = TryReadStringUTF16( utf16Str ) && !utf16Str.IsEmpty();
348
349 m_offset = savedOffset;
350 m_stringEncoding = savedEncoding;
351
352 // Only commit when exactly one framing yields a printable string; otherwise leave the
353 // version-based default in place.
354 if( asciiOk && !utf16Ok )
356 else if( utf16Ok && !asciiOk )
358}
359
360
361// --- Private string readers -------------------------------------------------
362
363
365{
366 if( m_offset + 2 > m_data.size() )
367 ThrowEOFError( 2 );
368
369 const uint8_t* p = &m_data[m_offset];
370 int charCount = ( static_cast<int>( p[0] ) << 8 ) | static_cast<int>( p[1] );
371 m_offset += 2;
372
373 if( charCount == 0 )
374 return wxString();
375
376 if( charCount < 0 || charCount > MAX_STRING_CHARS )
377 THROW_IO_ERRORF( _( "Unreasonable string length %d at offset 0x%06zX." ), charCount, m_offset - 2 );
378
379 size_t byteCount = static_cast<size_t>( charCount ) * 2;
380
381 if( m_offset + byteCount > m_data.size() )
382 ThrowEOFError( byteCount );
383
384 // wxMBConvUTF16BE converts from a big-endian byte stream.
385 wxMBConvUTF16BE conv;
386 wxString result = wxString( reinterpret_cast<const char*>( &m_data[m_offset] ),
387 conv, byteCount );
388
389 m_offset += byteCount;
390 return result;
391}
392
393
395{
396 int byteCount = ReadInt3();
397
398 if( byteCount == 0 )
399 return wxString();
400
401 if( byteCount < 0 || byteCount > MAX_STRING_CHARS )
402 THROW_IO_ERRORF( _( "Unreasonable v37 string length %d at offset 0x%06zX." ), byteCount, m_offset - 3 );
403
404 size_t count = static_cast<size_t>( byteCount );
405
406 if( m_offset + count > m_data.size() )
407 ThrowEOFError( count );
408
409 wxString result = wxString::From8BitData(
410 reinterpret_cast<const char*>( &m_data[m_offset] ), count );
411 m_offset += count;
412 return result;
413}
414
415
416bool BINARY_READER::TryReadStringUTF16( wxString& aResult )
417{
418 size_t savedOffset = m_offset;
419
420 if( m_offset + 2 > m_data.size() )
421 return false;
422
423 const uint8_t* p = &m_data[m_offset];
424 int charCount = ( static_cast<int>( p[0] ) << 8 ) | static_cast<int>( p[1] );
425 m_offset += 2;
426
427 if( charCount == 0 )
428 {
429 aResult = wxString();
430 return true;
431 }
432
433 if( charCount < 0 || charCount > 500 )
434 {
435 m_offset = savedOffset;
436 return false;
437 }
438
439 size_t byteCount = static_cast<size_t>( charCount ) * 2;
440
441 if( m_offset + byteCount > m_data.size() )
442 {
443 m_offset = savedOffset;
444 return false;
445 }
446
447 wxMBConvUTF16BE conv;
448 wxString candidate = wxString( reinterpret_cast<const char*>( &m_data[m_offset] ),
449 conv, byteCount );
450
451 if( !IsPrintableString( candidate ) )
452 {
453 m_offset = savedOffset;
454 return false;
455 }
456
457 m_offset += byteCount;
458 aResult = candidate;
459 return true;
460}
461
462
463bool BINARY_READER::TryReadStringASCII( wxString& aResult )
464{
465 size_t savedOffset = m_offset;
466
467 if( m_offset + 3 > m_data.size() )
468 return false;
469
470 const uint8_t* p = &m_data[m_offset];
471 int byteCount = ( static_cast<int>( p[0] ) << 16 )
472 | ( static_cast<int>( p[1] ) << 8 )
473 | ( static_cast<int>( p[2] ) );
474 byteCount -= INT3_BIAS;
475 m_offset += 3;
476
477 if( byteCount == 0 )
478 {
479 aResult = wxString();
480 return true;
481 }
482
483 if( byteCount < 0 || byteCount > 500 )
484 {
485 m_offset = savedOffset;
486 return false;
487 }
488
489 size_t count = static_cast<size_t>( byteCount );
490
491 if( m_offset + count > m_data.size() )
492 {
493 m_offset = savedOffset;
494 return false;
495 }
496
497 wxString candidate = wxString::From8BitData(
498 reinterpret_cast<const char*>( &m_data[m_offset] ), count );
499
500 if( !IsPrintableString( candidate ) )
501 {
502 m_offset = savedOffset;
503 return false;
504 }
505
506 m_offset += count;
507 aResult = candidate;
508 return true;
509}
510
511
512bool BINARY_READER::IsPrintableString( const wxString& aStr )
513{
514 for( size_t i = 0; i < aStr.length(); i++ )
515 {
516 wxChar ch = aStr[i];
517
518 if( ch == '\r' || ch == '\n' || ch == '\t' )
519 continue;
520
521 if( ch < 0x20 )
522 return false;
523 }
524
525 return true;
526}
527
528
529void BINARY_READER::ThrowEOFError( size_t aBytesNeeded ) const
530{
531 size_t remaining = ( m_offset < m_data.size() ) ? ( m_data.size() - m_offset ) : 0;
532
533 THROW_IO_ERRORF( _( "Unexpected end of file at offset 0x%06zX: need %zu bytes, have %zu remaining." ),
534 m_offset,
535 aBytesNeeded,
536 remaining );
537}
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_ERRORF(msg,...)
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.