1 /* (c) 2003 by Marcin Wiacek */
2 
3 #include "../../../gsmstate.h"
4 
5 #ifdef GSM_ENABLE_NOKIA3650
6 
7 #include <string.h>
8 #include <time.h>
9 
10 #include "../../../gsmcomon.h"
11 #include "../../../misc/coding/coding.h"
12 #include "../../../service/gsmlogo.h"
13 #include "../nfunc.h"
14 #include "../nfuncold.h"
15 #include "../../pfunc.h"
16 #include "../dct4s40/dct4func.h"
17 #include "n3650.h"
18 
N3650_ReplyGetFilePart(GSM_Protocol_Message * msg,GSM_StateMachine * s)19 static GSM_Error N3650_ReplyGetFilePart(GSM_Protocol_Message *msg, GSM_StateMachine *s)
20 {
21 	int old;
22 
23 	smprintf(s,"File part received\n");
24 	old = s->Phone.Data.File->Used;
25 
26 	if (msg->Length < 10) {
27 		if (old == 0) return ERR_UNKNOWN;
28 		return ERR_EMPTY;
29 	}
30 
31 	s->Phone.Data.File->Used += msg->Buffer[10]*256*256*256+
32 			    	    msg->Buffer[11]*256*256+
33 			    	    msg->Buffer[12]*256+
34 			    	    msg->Buffer[13];
35 	smprintf(s,"Length: %i\n",
36 			msg->Buffer[10]*256*256*256+
37 			msg->Buffer[11]*256*256+
38 			msg->Buffer[12]*256+
39 			msg->Buffer[13]);
40 	s->Phone.Data.File->Buffer = (unsigned char *)realloc(s->Phone.Data.File->Buffer,s->Phone.Data.File->Used);
41 	memcpy(s->Phone.Data.File->Buffer+old,msg->Buffer+18,s->Phone.Data.File->Used-old);
42 	if (s->Phone.Data.File->Used-old < 0x03 * 256 + 0xD4) return ERR_EMPTY;
43 	return ERR_NONE;
44 }
45 
N3650_GetFilePart(GSM_StateMachine * s,GSM_File * File,int * Handle UNUSED,size_t * Size)46 static GSM_Error N3650_GetFilePart(GSM_StateMachine *s, GSM_File *File, int *Handle UNUSED, size_t *Size)
47 {
48 	unsigned int 		len=10,i;
49 	GSM_Error		error;
50 	unsigned char 		*req;
51 	unsigned const char 	StartReq[11] = {
52 		N7110_FRAME_HEADER, 0x0D, 0x10, 0x01, 0x07,
53 		0x24,		/* len1 */
54 		0x12,		/* len2 */
55 		0x0E,		/* len3 */
56 		0x00};		/* File name */
57 	unsigned char		ContinueReq[] = {
58 		N7110_FRAME_HEADER, 0x0D, 0x20, 0x01, 0xF0,
59 		0x08, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00};
60 
61 	if (File->Used == 0) {
62 		req = malloc(strlen(File->ID_FullName) + 30);
63 		if (req == NULL) {
64 			return ERR_MOREMEMORY;
65 		}
66 		memcpy(req, StartReq, sizeof(StartReq));
67 		(*Size) = 0;
68 
69 		sprintf(req+10,"%s",File->ID_FullName);
70 		len+=strlen(File->ID_FullName)-1;
71 		req[7] = strlen(File->ID_FullName) + 3;
72 
73 		req[8] = strlen(File->ID_FullName);
74 		req[9] = 0;
75 		while (File->ID_FullName[req[8]] != '\\') {
76 			req[8]--;
77 			req[9]++;
78 		}
79 		for (i=req[8];i<strlen(File->ID_FullName);i++) {
80 			req[i+10] = req[i+1+10];
81 		}
82 		req[9]--;
83 
84 		EncodeUnicode(File->Name,File->ID_FullName+req[8]+1,req[9]);
85 		File->Folder = FALSE;
86 
87 		error = DCT4_SetPhoneMode(s, DCT4_MODE_TEST);
88 		if (error != ERR_NONE) return error;
89 
90 		s->Phone.Data.File = File;
91 		return GSM_WaitFor (s, req, len, 0x58, 4, ID_GetFile);
92 	}
93 
94 	s->Phone.Data.File = File;
95 	error = GSM_WaitFor (s, ContinueReq, 14, 0x58, 4, ID_GetFile);
96 
97 #if 0
98 	if (error == GE_EMPTY) {
99 		error = DCT4_SetPhoneMode(s, DCT4_MODE_NORMAL);
100 		if (error != ERR_NONE) return error;
101 		return GE_EMPTY;
102 	}
103 #endif
104 
105 	return error;
106 }
107 
N3650_ReplyGetFolderInfo(GSM_Protocol_Message * msg,GSM_StateMachine * s)108 static GSM_Error N3650_ReplyGetFolderInfo(GSM_Protocol_Message *msg, GSM_StateMachine *s)
109 {
110 	GSM_File	 	*File = s->Phone.Data.FileInfo;
111 	GSM_Phone_N3650Data	*Priv = &s->Phone.Data.Priv.N3650;
112 	int			i,pos = 6;
113 
114 	i = Priv->FilesLocationsUsed-1;
115 	while (1) {
116 		if (i==Priv->FilesLocationsCurrent-1) break;
117 		smprintf(s, "Copying %i to %i, max %i, current %i\n",
118 			i,i+msg->Buffer[5],
119 			Priv->FilesLocationsUsed,Priv->FilesLocationsCurrent);
120 		memcpy(Priv->Files[i+msg->Buffer[5]],Priv->Files[i],sizeof(GSM_File));
121 		i--;
122 	}
123 	Priv->FileEntries	  = msg->Buffer[5];
124 	Priv->FilesLocationsUsed += msg->Buffer[5];
125 	for (i=0;i<msg->Buffer[5];i++) {
126 		Priv->Files[Priv->FilesLocationsCurrent+i]->Folder = TRUE;
127 		if (msg->Buffer[pos+2] == 0x01) {
128 			Priv->Files[Priv->FilesLocationsCurrent+i]->Folder = FALSE;
129 			smprintf(s,"File ");
130 		}
131 		EncodeUnicode(Priv->Files[Priv->FilesLocationsCurrent+i]->Name,msg->Buffer+pos+9,msg->Buffer[pos+8]);
132 		smprintf(s,"%s\n",DecodeUnicodeString(Priv->Files[Priv->FilesLocationsCurrent+i]->Name));
133 		Priv->Files[Priv->FilesLocationsCurrent+i]->Level  = File->Level+1;
134 #if GSM_GNUC_PREREQ (7,0)
135 #pragma GCC diagnostic push
136 #pragma GCC diagnostic ignored "-Wformat-overflow"
137 #endif
138 		/* Here we check limits before doing sprintf */
139 		if (strlen(File->ID_FullName) + strlen(msg->Buffer+pos+9) + 20 >= sizeof(File->ID_FullName)) {
140 			return ERR_MOREMEMORY;
141 		}
142 		sprintf(
143 			Priv->Files[Priv->FilesLocationsCurrent+i]->ID_FullName,
144 			"%s\\%s",
145 			File->ID_FullName,
146 			msg->Buffer + pos + 9
147 		);
148 #if GSM_GNUC_PREREQ (7,0)
149 #pragma GCC diagnostic pop
150 #endif
151 		pos += msg->Buffer[pos+1];
152 	}
153 	smprintf(s, "\n");
154 	return ERR_NONE;
155 }
156 
N3650_GetFolderInfo(GSM_StateMachine * s,GSM_File * File)157 static GSM_Error N3650_GetFolderInfo(GSM_StateMachine *s, GSM_File *File)
158 {
159 	int 			len=10;
160 	unsigned char		*req;
161 	unsigned const char 	template[11] = {
162 		N7110_FRAME_HEADER, 0x0B, 0x00, 0x01, 0x07,
163 		0x18,		/* folder name length + 6 	*/
164 		0x12,		/* folder name length 		*/
165 		0x00,
166 		0x00};		/* folder name 			*/
167 
168 	req = malloc(strlen(File->ID_FullName) + 30);
169 	if (req == NULL) {
170 		return ERR_MOREMEMORY;
171 	}
172 	memcpy(req, template, sizeof(template));
173 
174 	/* FIXME: I doubt this works */
175 	sprintf(req+10,"%s", File->ID_FullName);
176 	len		+=strlen(File->ID_FullName);
177 	req[7] 		= strlen(File->ID_FullName) + 6;
178 	req[8] 		= strlen(File->ID_FullName);
179 	req[len++] 	= 0x00;
180 	req[len++] 	= 0x00;
181 
182 	s->Phone.Data.FileInfo = File;
183 	return GSM_WaitFor (s, req, len, 0x58, 4, ID_GetFile);
184 }
185 
N3650_GetNextFileFolder(GSM_StateMachine * s,GSM_File * File,gboolean start)186 static GSM_Error N3650_GetNextFileFolder(GSM_StateMachine *s, GSM_File *File, gboolean start)
187 {
188 	GSM_Error		error;
189 	GSM_Phone_N3650Data	*Priv = &s->Phone.Data.Priv.N3650;
190 
191 	if (start) {
192 		error = DCT4_SetPhoneMode(s, DCT4_MODE_LOCAL);
193 		if (error != ERR_NONE) return error;
194 
195 		Priv->Files[0]->Folder		= TRUE;
196 		Priv->Files[0]->Level		= 1;
197 		Priv->Files[0]->Name[0]		= 0;
198 		Priv->Files[0]->Name[1]		= 0;
199 		Priv->Files[0]->ID_FullName[0]	= 'Z';
200 		Priv->Files[0]->ID_FullName[1]	= ':';
201 		Priv->Files[0]->ID_FullName[2]	= 0;
202 
203 		Priv->Files[1]->Folder		= TRUE;
204 		Priv->Files[1]->Level		= 1;
205 		Priv->Files[1]->Name[0]		= 0;
206 		Priv->Files[1]->Name[1]		= 0;
207 		Priv->Files[1]->ID_FullName[0]	= 'E';
208 		Priv->Files[1]->ID_FullName[1]	= ':';
209 		Priv->Files[1]->ID_FullName[2]	= 0;
210 
211 		Priv->Files[2]->Folder		= TRUE;
212 		Priv->Files[2]->Level		= 1;
213 		Priv->Files[2]->Name[0]		= 0;
214 		Priv->Files[2]->Name[1]		= 0;
215 		Priv->Files[2]->ID_FullName[0]	= 'C';
216 		Priv->Files[2]->ID_FullName[1]	= ':';
217 		Priv->Files[2]->ID_FullName[2]	= 0;
218 
219 		Priv->FilesLocationsUsed 	= 3;
220 		Priv->FilesLocationsCurrent 	= 0;
221 		Priv->FileLev			= 1;
222 	}
223 
224 	if (Priv->FilesLocationsCurrent == Priv->FilesLocationsUsed) {
225 #if 0
226 		error = DCT4_SetPhoneMode(s, DCT4_MODE_NORMAL);
227 		if (error != ERR_NONE) return error;
228 #endif
229 
230 		return ERR_EMPTY;
231 	}
232 
233 	strcpy(File->ID_FullName,Priv->Files[Priv->FilesLocationsCurrent]->ID_FullName);
234 	File->Level	= Priv->Files[Priv->FilesLocationsCurrent]->Level;
235 	File->Folder	= Priv->Files[Priv->FilesLocationsCurrent]->Folder;
236 	CopyUnicodeString(File->Name,Priv->Files[Priv->FilesLocationsCurrent]->Name);
237 	Priv->FilesLocationsCurrent++;
238 
239 	if (!File->Folder) return ERR_NONE;
240 
241 	if (Priv->FilesLocationsCurrent > 1) {
242 		if (File->ID_FullName[0]!=Priv->Files[Priv->FilesLocationsCurrent-2]->ID_FullName[0]) {
243 			if (File->ID_FullName[0] == 'E') {
244 				error = DCT4_SetPhoneMode(s, DCT4_MODE_TEST);
245 				if (error != ERR_NONE) return error;
246 				error = DCT4_SetPhoneMode(s, DCT4_MODE_TEST);
247 				if (error != ERR_NONE) return error;
248 			}
249 			if (File->ID_FullName[0] == 'C') {
250 				error = DCT4_SetPhoneMode(s, DCT4_MODE_LOCAL);
251 				if (error != ERR_NONE) return error;
252 				error = DCT4_SetPhoneMode(s, DCT4_MODE_LOCAL);
253 				if (error != ERR_NONE) return error;
254 			}
255 #if 0
256 		if (error != ERR_NONE) return error;
257 #endif
258 		}
259 	}
260 
261 	File->ReadOnly  = FALSE;
262 	File->System    = FALSE;
263 	File->Protected = FALSE;
264 	File->Hidden    = FALSE;
265 
266 	return N3650_GetFolderInfo(s, File);
267 }
268 
N3650_Initialise(GSM_StateMachine * s)269 static GSM_Error N3650_Initialise (GSM_StateMachine *s)
270 {
271 	GSM_Phone_N3650Data 	*Priv = &s->Phone.Data.Priv.N3650;
272 	int			i=0;
273 
274 	for (i=0;i<10000;i++) {
275 		Priv->Files[i] = (GSM_File *)malloc(sizeof(GSM_File));
276 	        if (Priv->Files[i] == NULL) return ERR_MOREMEMORY;
277 	}
278 	return ERR_NONE;
279 }
280 
N3650_Terminate(GSM_StateMachine * s)281 static GSM_Error N3650_Terminate(GSM_StateMachine *s)
282 {
283 	GSM_Phone_N3650Data 	*Priv = &s->Phone.Data.Priv.N3650;
284 	int			i;
285 
286 	for (i=0;i<10000;i++) {
287 		free(Priv->Files[i]);
288 		Priv->Files[i]=NULL;
289 	}
290 	return ERR_NONE;
291 }
292 
293 static GSM_Reply_Function N3650ReplyFunctions[] = {
294 	{DCT4_ReplySetPhoneMode,	  "\x15",0x03,0x64,ID_Reset		  },
295 	{DCT4_ReplyGetPhoneMode,	  "\x15",0x03,0x65,ID_Reset		  },
296 	{NoneReply,		  	  "\x15",0x03,0x68,ID_Reset		  },
297 
298 	{DCT4_ReplyGetIMEI,		  "\x1B",0x03,0x01,ID_GetIMEI		  },
299 	{NOKIA_ReplyGetPhoneString,	  "\x1B",0x03,0x08,ID_GetHardware	  },
300 	{NOKIA_ReplyGetPhoneString,	  "\x1B",0x03,0x0C,ID_GetProductCode	  },
301 
302 	{N3650_ReplyGetFolderInfo,	  "\x58",0x03,0x0C,ID_GetFile		  },
303 	{N3650_ReplyGetFilePart,	  "\x58",0x03,0x0E,ID_GetFile		  },
304 
305 	{NULL,				  "\x00",0x00,0x00,ID_None		  }
306 };
307 
308 GSM_Phone_Functions N3650Phone = {
309 	"3650|NGAGE",
310 	N3650ReplyFunctions,
311 	NOTSUPPORTED,			/* 	Install			*/
312 	N3650_Initialise,
313 	N3650_Terminate,
314 	GSM_DispatchMessage,
315 	NOTSUPPORTED,			/* 	ShowStartInfo		*/
316 	NOKIA_GetManufacturer,
317 	DCT3DCT4_GetModel,
318 	DCT3DCT4_GetFirmware,
319 	DCT4_GetIMEI,
320 	NOTSUPPORTED,			/*	GetOriginalIMEI		*/
321 	NOTSUPPORTED,			/*	GetManufactureMonth	*/
322 	DCT4_GetProductCode,
323 	DCT4_GetHardware,
324 	NOTSUPPORTED,			/*	GetPPM			*/
325 	NOTSUPPORTED,			/*	GetSIMIMSI		*/
326 	NOTSUPPORTED,			/*	GetDateTime		*/
327 	NOTSUPPORTED,			/*	SetDateTime		*/
328 	NOTSUPPORTED,			/*	GetAlarm		*/
329 	NOTSUPPORTED,			/*	SetAlarm		*/
330 	NOTSUPPORTED,			/* 	GetLocale		*/
331 	NOTSUPPORTED,			/* 	SetLocale		*/
332 	NOTSUPPORTED,			/*	PressKey		*/
333 	DCT4_Reset,
334 	NOTSUPPORTED,			/*	ResetPhoneSettings	*/
335 	NOTSUPPORTED,			/*	EnterSecurityCode	*/
336 	NOTSUPPORTED,			/*	GetSecurityStatus	*/
337 	NOTSUPPORTED,			/*	GetDisplayStatus	*/
338 	NOTSUPPORTED,			/*	SetAutoNetworkLogin	*/
339 	NOTSUPPORTED,			/*	GetBatteryCharge	*/
340 	NOTSUPPORTED,			/*	GetSignalQuality	*/
341 	NOTSUPPORTED,			/*	GetNetworkInfo		*/
342 	NOTSUPPORTED,     		/*  	GetCategory 		*/
343  	NOTSUPPORTED,       		/*  	AddCategory 		*/
344         NOTSUPPORTED,      		/*  	GetCategoryStatus 	*/
345 	NOTSUPPORTED,			/*	GetMemoryStatus		*/
346 	NOTSUPPORTED,			/*	GetMemory		*/
347 	NOTSUPPORTED,			/*	GetNextMemory		*/
348 	NOTSUPPORTED,			/*	SetMemory		*/
349 	NOTSUPPORTED,			/*	AddMemory		*/
350 	NOTSUPPORTED,			/*	DeleteMemory		*/
351 	NOTIMPLEMENTED,			/*	DeleteAllMemory		*/
352 	NOTSUPPORTED,			/*	GetSpeedDial		*/
353 	NOTSUPPORTED,			/*	SetSpeedDial		*/
354 	NOTSUPPORTED,			/*	GetSMSC			*/
355 	NOTSUPPORTED,			/*	SetSMSC			*/
356 	NOTSUPPORTED,			/*	GetSMSStatus		*/
357 	NOTSUPPORTED,			/*	GetSMS			*/
358 	NOTSUPPORTED,			/*	GetNextSMS		*/
359 	NOTSUPPORTED,			/*	SetSMS			*/
360 	NOTSUPPORTED,			/*	AddSMS			*/
361 	NOTSUPPORTED,			/* 	DeleteSMS 		*/
362 	NOTSUPPORTED,			/*	SendSMS			*/
363 	NOTSUPPORTED,			/*	SendSavedSMS		*/
364 	NOTSUPPORTED,			/*	SetFastSMSSending	*/
365 	NOTSUPPORTED,			/*	SetIncomingSMS		*/
366 	NOTSUPPORTED,			/* 	SetIncomingCB		*/
367 	NOTSUPPORTED,			/*	GetSMSFolders		*/
368  	NOTSUPPORTED,			/* 	AddSMSFolder		*/
369  	NOTSUPPORTED,			/* 	DeleteSMSFolder		*/
370 	NOTIMPLEMENTED,			/*	DialVoice		*/
371         NOTIMPLEMENTED,			/*	DialService		*/
372 	NOTIMPLEMENTED,			/*	AnswerCall		*/
373 	NOTIMPLEMENTED,			/*	CancelCall		*/
374  	NOTIMPLEMENTED,			/* 	HoldCall 		*/
375  	NOTIMPLEMENTED,			/* 	UnholdCall 		*/
376  	NOTIMPLEMENTED,			/* 	ConferenceCall 		*/
377  	NOTIMPLEMENTED,			/* 	SplitCall		*/
378  	NOTIMPLEMENTED,			/* 	TransferCall		*/
379  	NOTIMPLEMENTED,			/* 	SwitchCall		*/
380  	NOTSUPPORTED,			/* 	GetCallDivert		*/
381  	NOTSUPPORTED,			/* 	SetCallDivert		*/
382  	NOTSUPPORTED,			/* 	CancelAllDiverts	*/
383 	NOTIMPLEMENTED,			/*	SetIncomingCall		*/
384 	NOTIMPLEMENTED,			/*  	SetIncomingUSSD		*/
385 	NOTSUPPORTED,			/*	SendDTMF		*/
386 	NOTSUPPORTED,			/*	GetRingtone		*/
387 	NOTSUPPORTED,			/*	SetRingtone		*/
388 	NOTSUPPORTED,			/*	GetRingtonesInfo	*/
389 	NOTIMPLEMENTED,			/* 	DeleteUserRingtones	*/
390 	NOTSUPPORTED,			/*	PlayTone		*/
391 	NOTSUPPORTED,			/*	GetWAPBookmark		*/
392 	NOTSUPPORTED,			/* 	SetWAPBookmark 		*/
393 	NOTSUPPORTED, 			/* 	DeleteWAPBookmark 	*/
394 	NOTSUPPORTED,			/* 	GetWAPSettings 		*/
395 	NOTSUPPORTED,			/* 	SetWAPSettings 		*/
396 	NOTSUPPORTED,			/*	GetSyncMLSettings	*/
397 	NOTSUPPORTED,			/*	SetSyncMLSettings	*/
398 	NOTSUPPORTED,			/*	GetChatSettings		*/
399 	NOTSUPPORTED,			/*	SetChatSettings		*/
400 	NOTSUPPORTED,			/* 	GetMMSSettings		*/
401 	NOTSUPPORTED,			/* 	SetMMSSettings		*/
402 	NOTSUPPORTED,			/*	GetMMSFolders		*/
403 	NOTSUPPORTED,			/*	GetNextMMSFile		*/
404 	NOTSUPPORTED,			/*	GetBitmap		*/
405 	NOTSUPPORTED,			/*	SetBitmap		*/
406 	NOTSUPPORTED,			/*	GetToDoStatus		*/
407 	NOTSUPPORTED,			/*	GetToDo			*/
408 	NOTSUPPORTED,			/*	GetNextToDo		*/
409 	NOTSUPPORTED,			/*	SetToDo			*/
410 	NOTSUPPORTED,			/*	AddToDo			*/
411 	NOTSUPPORTED,			/*	DeleteToDo		*/
412 	NOTSUPPORTED,			/*	DeleteAllToDo		*/
413 	NOTIMPLEMENTED,			/*	GetCalendarStatus	*/
414 	NOTIMPLEMENTED,			/*	GetCalendar		*/
415     	NOTSUPPORTED,			/*  	GetNextCalendar		*/
416 	NOTIMPLEMENTED,			/*	SetCalendar		*/
417 	NOTSUPPORTED,			/*	AddCalendar		*/
418 	NOTSUPPORTED,			/*	DeleteCalendar		*/
419 	NOTIMPLEMENTED,			/*	DeleteAllCalendar	*/
420 	NOTSUPPORTED,			/* 	GetCalendarSettings	*/
421 	NOTSUPPORTED,			/* 	SetCalendarSettings	*/
422 	NOTSUPPORTED,			/*	GetNoteStatus		*/
423 	NOTSUPPORTED,			/*	GetNote			*/
424 	NOTSUPPORTED,			/*	GetNextNote		*/
425 	NOTSUPPORTED,			/*	SetNote			*/
426 	NOTSUPPORTED,			/*	AddNote			*/
427 	NOTSUPPORTED,			/* 	DeleteNote		*/
428 	NOTSUPPORTED,			/*	DeleteAllNotes		*/
429 	NOTSUPPORTED, 			/*	GetProfile		*/
430 	NOTSUPPORTED, 			/*	SetProfile		*/
431     	NOTSUPPORTED,			/*  	GetFMStation        	*/
432     	NOTSUPPORTED,			/*  	SetFMStation        	*/
433     	NOTSUPPORTED,			/*  	ClearFMStations       	*/
434 	N3650_GetNextFileFolder,
435 	NOTSUPPORTED,			/*	GetFolderListing	*/
436 	NOTSUPPORTED,			/*	GetNextRootFolder	*/
437 	NOTSUPPORTED,			/*	SetFileAttributes	*/
438 	N3650_GetFilePart,
439 	NOTIMPLEMENTED,			/*	AddFilePart		*/
440 	NOTSUPPORTED,			/* 	SendFilePart		*/
441 	NOTSUPPORTED,	 		/* 	GetFileSystemStatus	*/
442 	NOTIMPLEMENTED,			/*	DeleteFile		*/
443 	NOTIMPLEMENTED,			/*	AddFolder		*/
444 	NOTSUPPORTED,			/* 	DeleteFolder		*/
445 	NOTSUPPORTED,			/* 	GetGPRSAccessPoint	*/
446 	NOTSUPPORTED,			/* 	SetGPRSAccessPoint	*/
447 	NOTSUPPORTED,			/* 	GetScreenshot		*/
448 	NOTSUPPORTED,			/* 	SetPower		*/
449 	NOTSUPPORTED,			/* 	PostConnect	*/
450 	NONEFUNCTION			/*	PreAPICall		*/
451 };
452 
453 #endif
454 
455 /* How should editor hadle tabs in this file? Add editor commands here.
456  * vim: noexpandtab sw=8 ts=8 sts=8:
457  */
458