1 // Written by Thorsten Brehm, started November 2012.
2 //
3 // Copyright (C) 2012  Thorsten Brehm, brehmt at gmail dt com
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Library General Public
7 // License as published by the Free Software Foundation; either
8 // version 2 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 // Library General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 //
19 // $Id$
20 
21 /**
22  * GZ Container File Format
23  *
24  * A (gzipped) binary file.
25  *
26  * Byte-order is little endian (we're ignoring big endian machines - for now...,
27  * - still usable, but they are writing an incompatible format).
28  *
29  * The binary file may contain an arbitrary number of containers.
30  *
31  * Each container has three elements:
32  *  - container type
33  *  - container payload size
34  *  - container payload data (of given "size" in bytes)
35  *
36  * A container may consist of strings, property trees, raw binary data, and whatever else
37  * is implemented.
38  *
39  * Use this for storing/loading user-data only - not for distribution files (scenery, aircraft etc).
40  */
41 
42 #ifdef HAVE_CONFIG_H
43 #  include <simgear_config.h>
44 #endif
45 
46 #include "gzcontainerfile.hxx"
47 
48 #include <simgear/props/props_io.hxx>
49 #include <simgear/misc/stdint.hxx>
50 #include <simgear/misc/sg_path.hxx>
51 
52 #include <string.h>
53 
54 using namespace std;
55 using namespace simgear;
56 
57 #if 1
58     #define MY_SG_DEBUG SG_DEBUG
59 #else
60     #define MY_SG_DEBUG SG_ALERT
61 #endif
62 
63 /** The header is always the first container in each file, so it's ID doesn't
64  * really matter (as long as it isn't changed) - it cannot conflict with user-specific types. */
65 const ContainerType HeaderType = 0;
66 
67 /** Magic word to detect big/little-endian file formats */
68 static const uint32_t EndianMagic = 0x11223344;
69 
70 /**************************************************************************
71  * gzContainerWriter
72  **************************************************************************/
73 
gzContainerWriter(const SGPath & name,const std::string & fileMagic)74 gzContainerWriter::gzContainerWriter(const SGPath& name,
75                                      const std::string& fileMagic) :
76         sg_gzofstream(name, ios_out | ios_binary),
77         filename(name.utf8Str())
78 {
79     /* write byte-order marker **************************************/
80     write((char*)&EndianMagic, sizeof(EndianMagic));
81 
82     /* write file header ********************************************/
83     writeContainer(HeaderType, fileMagic.c_str());
84 }
85 
86 /** Write the header of a single container. */
87 bool
writeContainerHeader(ContainerType Type,size_t Size)88 gzContainerWriter::writeContainerHeader(ContainerType Type, size_t Size)
89 {
90     uint32_t ui32Data;
91     uint64_t ui64Data;
92 
93     SG_LOG(SG_IO, MY_SG_DEBUG, "Writing container " << Type << ", size " << Size);
94 
95     // write container type
96     ui32Data = Type;
97     write((char*)&ui32Data, sizeof(ui32Data));
98     if (fail())
99         return false;
100 
101     // write container payload size
102     ui64Data = Size;
103     write((char*)&ui64Data, sizeof(ui64Data));
104     return !fail();
105 }
106 
107 /** Write a complete container. */
108 bool
writeContainer(ContainerType Type,const char * pData,size_t Size)109 gzContainerWriter::writeContainer(ContainerType Type, const char* pData, size_t Size)
110 {
111     // write container type and size
112     if (!writeContainerHeader(Type, Size))
113         return false;
114 
115     // write container payload
116     write(pData, Size);
117     return !fail();
118 }
119 
120 
121 /** Save a single string in a separate container */
122 bool
writeContainer(ContainerType Type,const char * stringBuffer)123 gzContainerWriter::writeContainer(ContainerType Type, const char* stringBuffer)
124 {
125     return writeContainer(Type, stringBuffer, strlen(stringBuffer)+1);
126 }
127 
128 /** Save a property tree in a separate container */
129 bool
writeContainer(ContainerType Type,SGPropertyNode * root)130 gzContainerWriter::writeContainer(ContainerType Type, SGPropertyNode* root)
131 {
132     stringstream oss;
133     writeProperties(oss, root, true);
134     return writeContainer(Type, oss.str().c_str());
135 }
136 
137 /**************************************************************************
138  * gzContainerReader
139  **************************************************************************/
140 
gzContainerReader(const SGPath & name,const std::string & fileMagic)141 gzContainerReader::gzContainerReader(const SGPath& name,
142                                      const std::string& fileMagic) :
143         sg_gzifstream(SGPath(name), ios_in | ios_binary),
144         filename(name.utf8Str())
145 {
146     bool ok = (good() && !eof());
147 
148     /* check byte-order marker **************************************/
149     if (ok)
150     {
151         uint32_t EndianCheck;
152         read((char*)&EndianCheck, sizeof(EndianCheck));
153         if (eof() || !good())
154         {
155             SG_LOG(SG_IO, SG_ALERT, "Error reading file " << name);
156             ok = false;
157         }
158         else
159         if (EndianCheck != EndianMagic)
160         {
161             SG_LOG(SG_IO, SG_ALERT, "Byte-order mismatch. This file was created for another architecture: " << filename);
162             ok = false;
163         }
164     }
165 
166     /* read file header *********************************************/
167     if (ok)
168     {
169         char* FileHeader = NULL;
170         size_t Size = 0;
171         ContainerType Type = -1;
172         if (!readContainer(&Type, &FileHeader, &Size))
173         {
174             SG_LOG(SG_IO, SG_ALERT, "File format not recognized. Missing file header: " << filename);
175             ok = false;
176         }
177         else
178         if ((HeaderType != Type)||(strcmp(FileHeader, fileMagic.c_str())))
179         {
180             SG_LOG(SG_IO, MY_SG_DEBUG, "Invalid header. Container type " << Type << ", Header " <<
181                    ((FileHeader) ? FileHeader : "(none)"));
182             SG_LOG(SG_IO, SG_ALERT, "File not recognized. This is not a valid '" << fileMagic << "' file: " << filename);
183             ok = false;
184         }
185 
186         if (FileHeader)
187         {
188             free(FileHeader);
189             FileHeader = NULL;
190         }
191     }
192 
193     if (!ok)
194     {
195         setstate(badbit);
196     }
197 }
198 
199 /** Read the header of a single container. */
200 bool
readContainerHeader(ContainerType * pType,size_t * pSize)201 gzContainerReader::readContainerHeader(ContainerType* pType, size_t* pSize)
202 {
203     uint32_t ui32Data;
204     uint64_t ui64Data;
205 
206     *pSize = 0;
207     *pType = -1;
208 
209     read((char*) &ui32Data, sizeof(ui32Data));
210     if (eof())
211     {
212         SG_LOG(SG_IO, SG_ALERT, "File corrupt? Unexpected end of file when reading container type in " << filename);
213         return false;
214     }
215     *pType = (ContainerType) ui32Data;
216 
217     read((char*) &ui64Data, sizeof(ui64Data));
218     if (eof())
219     {
220         SG_LOG(SG_IO, SG_ALERT, "File corrupt? Unexpected end of file when reading container size in " << filename);
221         return false;
222     }
223     *pSize = ui64Data;
224 
225     return !fail();
226 }
227 
228 /** Read a single container and return its contents as a binary blob */
229 bool
readContainer(ContainerType * pType,char ** ppData,size_t * pSize)230 gzContainerReader::readContainer(ContainerType* pType, char** ppData, size_t* pSize)
231 {
232     size_t Size = 0;
233 
234     *ppData = 0;
235 
236     if (!readContainerHeader(pType, &Size))
237     {
238         SG_LOG(SG_IO, SG_ALERT, "Cannot load data. Invalid container header in " << filename);
239         return false;
240     }
241 
242     char* pData = (char*) malloc(Size);
243     if (!pData)
244     {
245         SG_LOG(SG_IO, SG_ALERT, "Cannot load data. No more memory when reading " << filename);
246         return false;
247     }
248 
249     read(pData, Size);
250     if (fail())
251     {
252         SG_LOG(SG_IO, SG_ALERT, "File corrupt? Unexpected end of file when reading " << filename);
253         free(pData);
254         pData = 0;
255         return false;
256     }
257 
258     *ppData = pData;
259     *pSize = Size;
260     return true;
261 }
262