KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pads_sch_sdb.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 modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your option)
9 * any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include "pads_sch_sdb.h"
21
22#include <cstring>
23
24#include <ki_exception.h>
25
26namespace PADS_SCH_BINARY
27{
28
29namespace
30{
31
32 constexpr uint8_t MAGIC1 = 0xFE;
33 constexpr uint16_t VERSION_12 = 0x000C;
34 constexpr uint16_t VERSION_13 = 0x000D;
35 constexpr size_t HEADER_SIZE = 0x20;
36 constexpr size_t POOL_COUNT = 20;
37 constexpr size_t POOL_SIZE = 28;
38 constexpr size_t POOL_OFFSET = HEADER_SIZE;
39 constexpr size_t PAYLOAD_OFFSET = POOL_OFFSET + POOL_COUNT * POOL_SIZE;
40 constexpr size_t FOOTER_SIZE = 42;
41 constexpr char FOOTER_GUID[] = "{F4997D70-AF8A-11D0-A373-000000000000}";
42 constexpr size_t FOOTER_GUID_SIZE = 38;
43 constexpr uint32_t DATABASE_CONTROLLER = 0x24;
44 constexpr size_t SHEET_HEADER_SIZE = 20;
45 constexpr size_t SHEET_POOL_COUNT = 24;
46 constexpr size_t SHEET_POOL_SIZE = 28;
47 constexpr size_t SHEET_SERIALIZED_POOL_COUNT = 23;
48 constexpr size_t SHEET_POOL_COUNT_OFFSET = 12;
49 constexpr size_t SHEET_POOL_USED_BYTES_OFFSET = 16;
50 constexpr size_t SHEET_POOL_ALLOCATED_BYTES_OFFSET = 8;
51 constexpr size_t FIRST_OLE_FIXED_STATE_SIZE = 18;
52 constexpr size_t BETWEEN_OLE_TRAILER_SIZE = 0x66;
53 constexpr size_t FINAL_OLE_TRAILER_SIZE = 0x4E;
54 constexpr size_t CFB_HEADER_SIZE = 512;
55 constexpr uint8_t MFC_CLASS_MARKER[] = { 0xFF, 0xFF, 0x01, 0x00 };
56 constexpr char OLE_ITEM_CLASS_NAME[] = "CPowerPCBCntrItem";
57 constexpr uint8_t CFB_MAGIC[] = { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 };
58 constexpr uint8_t CFB_ZERO_CLSID[16] = {};
59 constexpr uint8_t CFB_BYTE_ORDER[] = { 0xFE, 0xFF };
60 constexpr uint8_t CFB_ZERO_PADDING[6] = {};
61
62 constexpr size_t POOL_ALLOCATED_BYTES_OFFSET = 4;
63 constexpr size_t POOL_COUNT_OFFSET = 8;
64 constexpr size_t POOL_USED_BYTES_OFFSET = 12;
65 constexpr size_t POOL_HANDLE_OFFSET = 16;
66
67} // namespace
68
69
70bool PADS_SCH_SDB::HasFamilyMagic( const std::vector<uint8_t>& aBytes )
71{
72 return PADS_IO::HasSdbMagic( aBytes, MAGIC1 );
73}
74
75
76bool PADS_SCH_SDB::IsSupportedVersion( uint16_t aVersion )
77{
78 return aVersion == VERSION_12 || aVersion == VERSION_13;
79}
80
81
82void PADS_SCH_SDB::Load( std::vector<uint8_t> aBytes )
83{
84 m_data = std::move( aBytes );
85 m_version = m_data.size() >= 4 ? m_cursor.U16At( 2 ) : 0;
86 m_pools = {};
87 m_blocks.clear();
88 m_oleItems.clear();
91
96}
97
98
100{
101 if( m_data.size() < 2 )
102 throwAt( m_data.size(), "file too small for PADS Logic binary magic" );
103
104 if( m_data[0] != 0x00 )
105 throwAt( 0, "invalid PADS Logic binary magic" );
106
107 if( m_data[1] != MAGIC1 )
108 throwAt( 1, "invalid PADS Logic binary magic" );
109
110 if( m_data.size() < 4 )
111 throwAt( m_data.size(), "file too small for PADS Logic binary version" );
112
114 throwAt( 2, "unsupported PADS Logic binary version" );
115
116 if( m_data.size() < HEADER_SIZE )
117 throwAt( m_data.size(), "file too small for PADS Logic binary header" );
118
119 uint16_t expectedRevision = m_version == VERSION_12 ? 1 : 0;
120
121 if( m_cursor.U16At( 4 ) != expectedRevision )
122 throwAt( 4, "invalid version-specific PADS Logic format flags" );
123
124 m_payloadOffset = PAYLOAD_OFFSET;
125}
126
127
129{
130 if( m_data.size() < PAYLOAD_OFFSET )
131 throwAt( m_data.size(), "file too small for PADS Logic pool directory" );
132
133 for( size_t i = 0; i < POOL_COUNT; ++i )
134 {
135 size_t offset = POOL_OFFSET + i * POOL_SIZE;
136 PADS_IO::SDB_RECORD record( m_cursor, offset );
137 SCH_SDB_POOL& pool = m_pools[i];
138
139 pool.allocatedBytes = record.U32( POOL_ALLOCATED_BYTES_OFFSET );
140 pool.count = record.U32( POOL_COUNT_OFFSET );
141 pool.usedBytes = record.U32( POOL_USED_BYTES_OFFSET );
142 pool.handle = record.U32( POOL_HANDLE_OFFSET );
143
144 if( pool.usedBytes > pool.allocatedBytes )
145 throwAt( offset + POOL_USED_BYTES_OFFSET, "pool serialized bytes exceed allocation" );
146
147 if( pool.count == 0 && pool.usedBytes != 0 )
148 throwAt( offset + POOL_USED_BYTES_OFFSET, "empty pool has serialized bytes" );
149 }
150}
151
152
154{
155 if( m_data.size() < PAYLOAD_OFFSET + FOOTER_SIZE )
156 throwAt( m_data.size(), "file too small for PADS Logic binary footer" );
157
158 m_footerOffset = m_data.size() - FOOTER_SIZE;
159
160 if( std::optional<PADS_IO::SDB_FOOTER_ERROR> error =
161 PADS_IO::CheckSdbFooter( m_data, m_footerOffset, FOOTER_GUID, FOOTER_GUID_SIZE ) )
162 {
163 throwAt( error->offset, error->detail );
164 }
165
166 size_t backPointerOffset = m_footerOffset + FOOTER_GUID_SIZE;
167
168 if( m_cursor.U32At( backPointerOffset ) < m_payloadOffset )
169 throwAt( backPointerOffset, "container-item back-pointer precedes schematic payload" );
170}
171
172
174{
175 auto checkedAdd = [this]( size_t aBase, size_t aBytes, size_t aSourceOffset, const wxString& aDetail )
176 {
177 if( aBase > m_footerOffset || aBytes > m_footerOffset - aBase )
178 throwAt( aSourceOffset, aDetail );
179
180 return aBase + aBytes;
181 };
182
183 auto requireBytes = [this]( size_t aOffset, const auto& aExpected, size_t aBytes, const wxString& aDetail )
184 {
185 for( size_t i = 0; i < aBytes; ++i )
186 {
187 if( m_data[aOffset + i] != static_cast<uint8_t>( aExpected[i] ) )
188 throwAt( aOffset + i, aDetail );
189 }
190 };
191
192 size_t offset = m_payloadOffset;
193
194 if( m_cursor.U32At( offset ) != DATABASE_CONTROLLER )
195 throwAt( offset, "invalid first schematic controller ordinal" );
196
197 size_t outerStart = offset;
198 size_t outerEnd = checkedAdd( offset, 4, offset, "controller ordinal extends past footer" );
199
200 for( size_t i = 1; i < m_pools.size(); ++i )
201 {
202 size_t source = POOL_OFFSET + i * POOL_SIZE + POOL_USED_BYTES_OFFSET;
203 size_t blockEnd =
204 checkedAdd( outerEnd, m_pools[i].usedBytes, source, "controller payload extends past footer" );
205
206 outerEnd = blockEnd;
207 }
208
209 m_blocks.push_back( { SCH_SDB_BLOCK_KIND::FIXED_CONTROLLER, static_cast<int>( DATABASE_CONTROLLER ), outerStart,
210 outerEnd - outerStart, 19, 0 } );
211
212 offset = outerEnd;
213
214 uint32_t sheetCount = m_pools[3].count;
215 constexpr size_t minimumSheetBytes = SHEET_HEADER_SIZE + SHEET_POOL_COUNT * SHEET_POOL_SIZE;
216 size_t sheetCountOffset = POOL_OFFSET + 3 * POOL_SIZE + POOL_COUNT_OFFSET;
217
218 if( sheetCount > ( m_footerOffset - offset ) / minimumSheetBytes )
219 throwAt( sheetCountOffset, "sheet count extent exceeds schematic payload" );
220
221 for( uint32_t sheet = 0; sheet < sheetCount; ++sheet )
222 {
223 size_t sheetStart = offset;
224 size_t descriptors =
225 checkedAdd( sheetStart, SHEET_HEADER_SIZE, sheetStart, "sheet header extends past footer" );
226
227 if( m_cursor.U32At( sheetStart ) != SHEET_HEADER_SIZE || m_cursor.U32At( sheetStart + 4 ) != 5
228 || m_cursor.U32At( sheetStart + 8 ) != SHEET_HEADER_SIZE )
229 {
230 throwAt( sheetStart, "invalid sheet database header" );
231 }
232
233 offset = checkedAdd( descriptors, SHEET_POOL_COUNT * SHEET_POOL_SIZE, descriptors,
234 "sheet controller directory extends past footer" );
235
236 for( size_t i = 0; i < SHEET_SERIALIZED_POOL_COUNT; ++i )
237 {
238 size_t descriptor = descriptors + i * SHEET_POOL_SIZE;
239 uint32_t allocatedBytes = m_cursor.U32At( descriptor + SHEET_POOL_ALLOCATED_BYTES_OFFSET );
240 uint32_t count = m_cursor.U32At( descriptor + SHEET_POOL_COUNT_OFFSET );
241 uint32_t usedBytes = m_cursor.U32At( descriptor + SHEET_POOL_USED_BYTES_OFFSET );
242
243 if( usedBytes > allocatedBytes )
244 throwAt( descriptor + SHEET_POOL_USED_BYTES_OFFSET, "sheet pool serialized bytes exceed allocation" );
245
246 if( count == 0 && usedBytes != 0 )
247 throwAt( descriptor + SHEET_POOL_USED_BYTES_OFFSET, "empty sheet pool has serialized bytes" );
248
249 offset = checkedAdd( offset, usedBytes, descriptor + SHEET_POOL_USED_BYTES_OFFSET,
250 "sheet controller payload extends past footer" );
251 }
252
253 m_blocks.push_back( { SCH_SDB_BLOCK_KIND::SHEET, static_cast<int>( sheet ), sheetStart, offset - sheetStart,
254 SHEET_SERIALIZED_POOL_COUNT, 0 } );
255 }
256
257 size_t backPointerOffset = m_footerOffset + FOOTER_GUID_SIZE;
258
259 if( m_cursor.U32At( backPointerOffset ) != offset )
260 throwAt( backPointerOffset, "container-item back-pointer overlaps derived schematic blocks" );
261
262 uint32_t containerItemCount = m_cursor.U32At( offset );
263 size_t containerItemCountOffset = offset;
264 offset = checkedAdd( offset, 4, containerItemCountOffset, "embedded OLE item count extends past footer" );
265 m_blocks.push_back( { SCH_SDB_BLOCK_KIND::FOOTER_AUX, -1, containerItemCountOffset, 4, containerItemCount, 4 } );
266
267 if( containerItemCount == 0 )
268 {
269 if( offset != m_footerOffset )
270 throwAt( offset, "unowned bytes before schematic footer" );
271
272 return;
273 }
274
275 constexpr size_t firstItemFrameSize = 6 + FIRST_OLE_FIXED_STATE_SIZE + 4;
276 constexpr size_t finalItemMinimum = firstItemFrameSize + FINAL_OLE_TRAILER_SIZE;
277 size_t remainingItemBytes = m_footerOffset - offset;
278 uint64_t minimumItemBytes = finalItemMinimum + uint64_t( containerItemCount - 1 ) * BETWEEN_OLE_TRAILER_SIZE;
279
280 // A u32 item count times the 0x66-byte trailer is bounded well below uint64_t.
281 if( minimumItemBytes > remainingItemBytes )
282 {
283 throwAt( containerItemCountOffset, "embedded OLE item count exceeds schematic payload" );
284 }
285
286 size_t itemStart = offset;
287 requireBytes( itemStart, MFC_CLASS_MARKER, std::size( MFC_CLASS_MARKER ), "invalid embedded OLE MFC class marker" );
288 uint16_t classNameLength = m_cursor.U16At( itemStart + 4 );
289
290 if( classNameLength != std::size( OLE_ITEM_CLASS_NAME ) - 1 )
291 throwAt( itemStart + 4, "invalid embedded OLE MFC class name length" );
292
293 size_t lengthOffset = checkedAdd( itemStart, 6, itemStart, "OLE item class header extends past footer" );
294 lengthOffset =
295 checkedAdd( lengthOffset, classNameLength, itemStart + 4, "OLE item class name extends past footer" );
296 requireBytes( itemStart + 6, OLE_ITEM_CLASS_NAME, classNameLength, "invalid embedded OLE MFC class name" );
297 lengthOffset =
298 checkedAdd( lengthOffset, FIRST_OLE_FIXED_STATE_SIZE, lengthOffset, "OLE item state extends past footer" );
299 size_t firstCfbOffset = checkedAdd( lengthOffset, 4, lengthOffset, "OLE item length extends past footer" );
300 m_blocks.push_back( { SCH_SDB_BLOCK_KIND::FOOTER_AUX, -1, itemStart, firstCfbOffset - itemStart, 1, 0 } );
301
302 for( uint32_t item = 0; item < containerItemCount; ++item )
303 {
304 uint32_t cfbBytes = m_cursor.U32At( lengthOffset );
305
306 if( cfbBytes < CFB_HEADER_SIZE )
307 throwAt( lengthOffset, "embedded OLE CFB is smaller than its structural header" );
308
309 size_t cfbOffset = checkedAdd( lengthOffset, 4, lengthOffset, "OLE item length extends past footer" );
310 size_t cfbEnd = checkedAdd( cfbOffset, cfbBytes, lengthOffset, "OLE item CFB extends past footer" );
311 requireBytes( cfbOffset, CFB_MAGIC, std::size( CFB_MAGIC ), "invalid embedded OLE CFB magic" );
312 requireBytes( cfbOffset + 8, CFB_ZERO_CLSID, std::size( CFB_ZERO_CLSID ),
313 "invalid embedded OLE CFB reserved class ID" );
314 requireBytes( cfbOffset + 28, CFB_BYTE_ORDER, std::size( CFB_BYTE_ORDER ),
315 "invalid embedded OLE CFB byte order" );
316 requireBytes( cfbOffset + 34, CFB_ZERO_PADDING, std::size( CFB_ZERO_PADDING ),
317 "invalid embedded OLE CFB header padding" );
318 size_t trailer = item + 1 == containerItemCount ? FINAL_OLE_TRAILER_SIZE : BETWEEN_OLE_TRAILER_SIZE;
319 size_t itemEnd = checkedAdd( cfbEnd, trailer, cfbEnd, "OLE item trailer extends past footer" );
320 requireBytes( cfbEnd, FOOTER_GUID, FOOTER_GUID_SIZE, "invalid embedded OLE trailer class ID" );
321
322 SCH_SDB_BLOCK cfbBlock{
323 SCH_SDB_BLOCK_KIND::CFB_CONTAINER_ITEM, static_cast<int>( item ), cfbOffset, cfbBytes, 1, 0
324 };
325 m_blocks.push_back( cfbBlock );
326 m_blocks.push_back( { SCH_SDB_BLOCK_KIND::FOOTER_AUX, -1, cfbEnd, trailer, 1, 0 } );
327
328 constexpr size_t extentOffsetInTrailer = FOOTER_GUID_SIZE;
329 constexpr size_t boxOffsetInTrailer = extentOffsetInTrailer + 16;
330 constexpr size_t sheetPlaneOffsetInTrailer = boxOffsetInTrailer + 16;
331 constexpr size_t flagsOffsetInTrailer = sheetPlaneOffsetInTrailer + 4;
332 size_t extentOffset = cfbEnd + extentOffsetInTrailer;
333 size_t boxOffset = cfbEnd + boxOffsetInTrailer;
334 SCH_SDB_OLE_ITEM oleItem;
335 oleItem.cfb = cfbBlock;
336 oleItem.extent = { m_cursor.I32At( extentOffset ), m_cursor.I32At( extentOffset + 4 ),
337 m_cursor.I32At( extentOffset + 8 ), m_cursor.I32At( extentOffset + 12 ) };
338 oleItem.left = m_cursor.I32At( boxOffset );
339 oleItem.bottom = m_cursor.I32At( boxOffset + 4 );
340 oleItem.right = m_cursor.I32At( boxOffset + 8 );
341 oleItem.top = m_cursor.I32At( boxOffset + 12 );
342 oleItem.sheetPlane = m_cursor.U32At( cfbEnd + sheetPlaneOffsetInTrailer );
343 oleItem.flags = m_cursor.U32At( cfbEnd + flagsOffsetInTrailer );
344
345 if( oleItem.sheetPlane >= sheetCount )
346 throwAt( cfbEnd + sheetPlaneOffsetInTrailer, "embedded OLE item sheet index is out of range" );
347
348 oleItem.trailerOffset = cfbEnd;
349 oleItem.extentOffset = extentOffset;
350 oleItem.boxOffset = boxOffset;
351 m_oleItems.push_back( oleItem );
352
353 if( item + 1 < containerItemCount )
354 lengthOffset = itemEnd - 4;
355
356 offset = itemEnd;
357 }
358
359 if( offset != m_footerOffset )
360 throwAt( offset, "embedded OLE items do not end at schematic footer" );
361}
362
363
364void PADS_SCH_SDB::throwAt( size_t aOffset, const wxString& aDetail ) const
365{
367 wxString::Format( "PADS Logic binary v0x%04X at offset 0x%zX: %s", m_version, aOffset, aDetail.c_str() ) );
368}
369
370} // namespace PADS_SCH_BINARY
A bounds-checked reader positioned at one record inside a buffer.
uint32_t U32(size_t aOffset) const
void throwAt(size_t aOffset, const wxString &aDetail) const
std::vector< uint8_t > m_data
void Load(std::vector< uint8_t > aBytes)
std::array< SCH_SDB_POOL, 20 > m_pools
static bool IsSupportedVersion(uint16_t aVersion)
static bool HasFamilyMagic(const std::vector< uint8_t > &aBytes)
std::vector< SCH_SDB_OLE_ITEM > m_oleItems
PADS_IO::BINARY_CURSOR m_cursor
std::vector< SCH_SDB_BLOCK > m_blocks
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
bool HasSdbMagic(const std::vector< uint8_t > &aData, uint8_t aMagic1)
Check the PADS SDB container magic, a leading 0x00 followed by a format-specific second byte.
std::optional< SDB_FOOTER_ERROR > CheckSdbFooter(const std::vector< uint8_t > &aData, size_t aFooterStart, const char *aGuid, size_t aGuidLen)
Check a PADS SDB footer at aFooterStart: the ASCII GUID followed by an absolute back-pointer to the s...
std::array< int32_t, 4 > extent