Diffstat (limited to 'gammu/emb/gammu/depend/nokia/dct3trac/wmx.c') (more/less context) (ignore whitespace changes)
-rw-r--r-- | gammu/emb/gammu/depend/nokia/dct3trac/wmx.c | 480 |
1 files changed, 480 insertions, 0 deletions
diff --git a/gammu/emb/gammu/depend/nokia/dct3trac/wmx.c b/gammu/emb/gammu/depend/nokia/dct3trac/wmx.c new file mode 100644 index 0000000..64eda37 --- a/dev/null +++ b/gammu/emb/gammu/depend/nokia/dct3trac/wmx.c | |||
@@ -0,0 +1,480 @@ | |||
1 | /** | ||
2 | * Nokia DCT3 Firmware Debug Trace Monitor | ||
3 | * wumpus 2003 -- www.blacksphere.tk | ||
4 | * SIM stuff by The Monty | ||
5 | * | ||
6 | * Command line arguments: | ||
7 | * gammu --nokiadebug v00-0F,20,21 | ||
8 | * (v=verbose) | ||
9 | */ | ||
10 | |||
11 | #include "../../../../common/gsmstate.h" | ||
12 | |||
13 | #ifdef GSM_ENABLE_NOKIA_DCT3 | ||
14 | |||
15 | #include <string.h> | ||
16 | #include <signal.h> | ||
17 | |||
18 | #include "../../../../common/misc/coding/coding.h" | ||
19 | #include "../../../../common/gsmcomon.h" | ||
20 | #include "../../../../common/gsmstate.h" | ||
21 | #include "../../../../common/service/gsmpbk.h" | ||
22 | #include "../../../../common/phone/nokia/dct3/dct3func.h" | ||
23 | #include "../../../gammu.h" | ||
24 | #include "../dct3.h" | ||
25 | #include "wmx.h" | ||
26 | #include "wmx-util.h" | ||
27 | #include "wmx-gsm.h" | ||
28 | #include "wmx-sim.h" | ||
29 | #include "wmx-list.h" | ||
30 | |||
31 | extern GSM_Reply_Function UserReplyFunctionsX[]; | ||
32 | |||
33 | /* Global variables suck */ | ||
34 | GSMDecoder *gsmdec; | ||
35 | struct wmx_tracestruct *traces; | ||
36 | |||
37 | static GSM_Error DCT3_ReplySwitchDebug(GSM_Protocol_Message msg, GSM_StateMachine *s) | ||
38 | { | ||
39 | switch(msg.Buffer[2]) { | ||
40 | case 0x70: | ||
41 | printf("Debug Trace Enabled\n"); | ||
42 | break; | ||
43 | case 0x71: | ||
44 | printf("Debug Trace Disabled\n"); | ||
45 | break; | ||
46 | } | ||
47 | return ERR_NONE; | ||
48 | } | ||
49 | |||
50 | /** | ||
51 | * RPC confirmation/reply | ||
52 | */ | ||
53 | static GSM_Error DCT3_ReplyRPC(GSM_Protocol_Message msg, GSM_StateMachine *s) | ||
54 | { | ||
55 | printf("RPC Reply "); | ||
56 | printf("call=%02x rettype=%02x data=", msg.Buffer[2], msg.Buffer[3]); | ||
57 | if(msg.Buffer[3] == 3) { | ||
58 | /* string */ | ||
59 | printf("%s", &msg.Buffer[4]); | ||
60 | } else { | ||
61 | dumpraw("RPC Reply data", &msg.Buffer[4], msg.Length-4); | ||
62 | } | ||
63 | printf("\n"); | ||
64 | return ERR_NONE; | ||
65 | } | ||
66 | |||
67 | /* disassemble mdisnd (0x18xx) packet */ | ||
68 | static void mdisnd_data(unsigned char type, unsigned char *buffer, size_t length) | ||
69 | { | ||
70 | GSMDecoder_l1l2data dat; | ||
71 | size_t x; | ||
72 | int ch; | ||
73 | |||
74 | if(type==0x1B && length>2) { | ||
75 | /* channel packet */ | ||
76 | ch = buffer[1]; | ||
77 | dat.tx = GSMDECODER_SEND; | ||
78 | dat.ch = ch; | ||
79 | printf("%02X ch=%02X ",buffer[0],ch); | ||
80 | if (ch == 0x80 || ch == 0xB0) { | ||
81 | printf("\n"); | ||
82 | GSMDecoder_L2packet(gsmdec, &dat, &buffer[2], length-2); | ||
83 | } else if (ch == 0x70) { | ||
84 | dumpraw("MDI send ch70 prefix", &buffer[2], 2); | ||
85 | printf("\n"); | ||
86 | GSMDecoder_L2packet(gsmdec, &dat, &buffer[4], length-4); | ||
87 | } else { | ||
88 | dumpraw("MDI recv 1B packet", &buffer[2], length-2); | ||
89 | } | ||
90 | } else { | ||
91 | /* hex */ | ||
92 | for(x=0; x<length; x++) { | ||
93 | printf("%02x ",buffer[x]&0xFF); | ||
94 | } | ||
95 | } | ||
96 | } | ||
97 | |||
98 | /* disassemble mdircv (0x19xx) packet */ | ||
99 | static void mdircv_data(unsigned char type, unsigned char *buffer, size_t length) | ||
100 | { | ||
101 | size_t x; | ||
102 | int ch; | ||
103 | GSMDecoder_l1l2data dat; | ||
104 | |||
105 | if (type==0x80 && length>1) { | ||
106 | // buffer[0] channel | ||
107 | // buffer[1] flag1 | ||
108 | // buffer[2] flag2 | ||
109 | // buffer[3..5] timestamp | ||
110 | // buffer[6..7] unknown_hw1 | ||
111 | // buffer[8..9] unknown_hw2 | ||
112 | ch = buffer[0]; | ||
113 | dat.tx = GSMDECODER_RECEIVE; | ||
114 | dat.ch = ch; | ||
115 | dat.bsic = buffer[1]; | ||
116 | dat.err = buffer[2]; | ||
117 | dat.seq = (buffer[3]<<16)|(buffer[4]<<8)|(buffer[5]); | ||
118 | dat.arfcn = (buffer[6]<<8)|buffer[7]; | ||
119 | dat.timeshift = (buffer[8]<<8)|buffer[9]; | ||
120 | |||
121 | printf("ch=%02X bsic=%i err=%i t=%06X arfcn=%i shift=%i", | ||
122 | ch, buffer[1], buffer[2], | ||
123 | dat.seq, dat.arfcn, dat.timeshift | ||
124 | ); | ||
125 | |||
126 | //dumpraw("MDI recv 80 header", &buffer[6], 4); | ||
127 | printf(" "); | ||
128 | if(buffer[2] == 0) { /* unencrypted */ | ||
129 | if(ch == 0x70) { | ||
130 | /* Normal header + 2b prefix */ | ||
131 | dumpraw("MDI recv ch70 prefix", &buffer[10], 2); | ||
132 | printf("\n"); | ||
133 | GSMDecoder_L2packet(gsmdec, &dat, &buffer[12], length-12); | ||
134 | } else if (ch == 0x80 || ch == 0xB0) { | ||
135 | /* Normal header */ | ||
136 | printf("\n"); | ||
137 | GSMDecoder_L2packet(gsmdec, &dat, &buffer[10], length-10); | ||
138 | } else if (ch == 0x50 || ch == 0x60) { | ||
139 | /* Short header */ | ||
140 | |||
141 | printf("\n"); | ||
142 | GSMDecoder_L2short_packet(gsmdec, &dat, &buffer[10], length-10); | ||
143 | } else { | ||
144 | dumpraw("MDI send 80 packet", &buffer[10], length-10); | ||
145 | } | ||
146 | } else { | ||
147 | /* Encrypted (?) */ | ||
148 | dumpraw("MDI send err 80", &buffer[10], length-10); | ||
149 | } | ||
150 | } else { | ||
151 | /* hex */ | ||
152 | for(x=0; x<length; x++) { | ||
153 | printf("%02x ",buffer[x]&0xFF); | ||
154 | } | ||
155 | } | ||
156 | } | ||
157 | |||
158 | static GSM_Error DCT3_ReplyDebugTrace(GSM_Protocol_Message msg, GSM_StateMachine *s) | ||
159 | { | ||
160 | int x; | ||
161 | int id,timestamp,number,length; | ||
162 | struct wmx_tracetype *minor; | ||
163 | char *desc; | ||
164 | |||
165 | //printf("Debug Trace Received\n"); | ||
166 | /* parse frame | ||
167 | Debug trace packet: | ||
168 | packet type 0x00 | ||
169 | source subsystem 0x01 (LOCAL) | ||
170 | verder formaat zie notebook | ||
171 | 0x08 ID (payload=offset 0x02 here) | ||
172 | 0x0A timestamp | ||
173 | 0x0C seq nr | ||
174 | 0x0D .. parameters | ||
175 | */ | ||
176 | id = ((msg.Buffer[2]&0xFF)<<8)|(msg.Buffer[3]&0xFF); | ||
177 | timestamp = ((msg.Buffer[4]&0xFF)<<8)|(msg.Buffer[5]&0xFF); | ||
178 | number = msg.Buffer[6]&0xFF; | ||
179 | length = msg.Buffer[7]&0xFF; | ||
180 | |||
181 | /* filter */ | ||
182 | //if((id&0xFF00)==0x1900 && id != 0x1980) | ||
183 | //return GE_NONE; | ||
184 | //printf("%02x\n",msg.Buffer[10]); | ||
185 | //if(msg.Buffer[10]!=0x40) | ||
186 | //return GE_NONE; | ||
187 | /* Query trace type name */ | ||
188 | desc = "Unknown"; | ||
189 | if(traces != NULL) { | ||
190 | minor = wmx_tracestruct_queryminor(traces, id); | ||
191 | if(minor != NULL) desc = minor->desc; | ||
192 | } | ||
193 | printf("<%04X> %s\n", id, desc); | ||
194 | printf("t=%04x nr=%02x: ", timestamp, number); | ||
195 | |||
196 | /* TODO -- decode debug types on phone type */ | ||
197 | switch(id>>8) { | ||
198 | case 0x33: | ||
199 | case 0x34: | ||
200 | case 0x35: | ||
201 | case 0x37: | ||
202 | case 0x38: | ||
203 | case 0x39: | ||
204 | case 0x3A: | ||
205 | case 0x3B: | ||
206 | case 0x3C: | ||
207 | case 0x5F: | ||
208 | /* text */ | ||
209 | /* skip length byte */ | ||
210 | printf("\""); | ||
211 | for(x=8; x<msg.Length; x++) { | ||
212 | printf("%c",msg.Buffer[x]&0xFF); | ||
213 | } | ||
214 | printf("\""); | ||
215 | break; | ||
216 | /* | ||
217 | case 0x6801: | ||
218 | for(x=8; x<msg.Length; x++) { | ||
219 | printf("%02x%c ",msg.Buffer[x]&0xFF,msg.Buffer[x]&0xFF); | ||
220 | } | ||
221 | break; | ||
222 | */ | ||
223 | case 0x18: /* MDISND */ | ||
224 | |||
225 | /* skip these: | ||
226 | +00 length | ||
227 | +01 type (also xx in 0x18xx) | ||
228 | */ | ||
229 | if(msg.Length<10 || msg.Buffer[9]!=(id&0xFF)) { | ||
230 | printf("C %02X: param:%02x", id&0xFF, msg.Buffer[8]); | ||
231 | } else { | ||
232 | //printf("D %02X: ", id&0xFF); | ||
233 | printf("D %02X: ", id&0xFF); | ||
234 | mdisnd_data((unsigned char)(id&0xFF), (unsigned char*)&msg.Buffer[10], msg.Length-10); | ||
235 | } | ||
236 | break; | ||
237 | case 0x19: /* MDIRCV */ | ||
238 | if(msg.Length<10 || msg.Buffer[9]!=(id&0xFF)) { | ||
239 | printf("C %02X: param:%02x", id&0xFF, msg.Buffer[8]); | ||
240 | } else { | ||
241 | printf("D %02X: ", id&0xFF); | ||
242 | mdircv_data((unsigned char)(id&0xFF), (unsigned char*)&msg.Buffer[10], msg.Length-10); | ||
243 | //dumpraw((unsigned char*)&msg.Buffer[10], msg.Length-10); | ||
244 | } | ||
245 | break; | ||
246 | case 0x20: /* 0x25 SIM commands */ | ||
247 | /* | ||
248 | for(x=8;x<msg.Length;x++) | ||
249 | printf("%02x ", msg.Buffer[x]&0xFF); | ||
250 | */ | ||
251 | printf("SIM command "); | ||
252 | if(msg.Buffer[8]==0xa0) { // check if valid (class=a0) | ||
253 | simCommand_data(msg.Buffer[9], (unsigned char)(id&0xFF), (unsigned char*)&msg.Buffer[10], msg.Length-10); | ||
254 | // TODO: pass the msg.Buffer[9] and skip 1rst arg | ||
255 | } else { | ||
256 | printf("Unknown 0x25 packet (NOT SIM cmd): "); | ||
257 | for(x=8;x<msg.Length;x++) printf("%02x ", msg.Buffer[x]&0xFF); | ||
258 | printf("\n"); | ||
259 | } | ||
260 | break; | ||
261 | case 0x22: /* 0x27 SIM answer to command (error/ok/etc..) */ | ||
262 | if(msg.Length<10) { | ||
263 | // Unknown response | ||
264 | for(x=0;x<msg.Length-10;x++) printf("%02x ", msg.Buffer[x]&0xFF); | ||
265 | printf(" (Unknown 0x27 packet ? ? )\n"); | ||
266 | } else { | ||
267 | simAnswer_Process((unsigned char)(id&0xFF), (unsigned char*)&msg.Buffer[8], length); | ||
268 | } | ||
269 | break; | ||
270 | case 0x23: /* 0x28 SIM response data to commands */ | ||
271 | if(msg.Length<10) { | ||
272 | // Unknown response | ||
273 | for(x=0;x<msg.Length-10;x++) printf("%02x ", msg.Buffer[x]&0xFF); | ||
274 | printf(" (Unknown 0x28 packet)\n"); | ||
275 | } else { | ||
276 | simResponse_Process((unsigned char)(id&0xFF), (unsigned char*)&msg.Buffer[8], length); | ||
277 | } | ||
278 | break; | ||
279 | default: | ||
280 | /* hex */ | ||
281 | for(x=8; x<msg.Length; x++) { | ||
282 | printf("%02x ",msg.Buffer[x]&0xFF); | ||
283 | } | ||
284 | break; | ||
285 | } | ||
286 | printf("\n"); | ||
287 | return ERR_NONE; | ||
288 | } | ||
289 | |||
290 | |||
291 | static GSM_Error DCT3_ReplyMyPacket(GSM_Protocol_Message msg, GSM_StateMachine *s) | ||
292 | { | ||
293 | int x; | ||
294 | |||
295 | printf("MyPacket "); | ||
296 | for(x=0; x<msg.Length; x++) { | ||
297 | printf("%02x ",msg.Buffer[x]&0xFF); | ||
298 | } | ||
299 | printf("\n"); | ||
300 | return ERR_NONE; | ||
301 | } | ||
302 | |||
303 | #define ID_DebugTrace 0x666 | ||
304 | #define ID_DebugSwitch 0x667 | ||
305 | #define ID_RPC 0x668 | ||
306 | |||
307 | void DCT3SetDebug(int argc, char *argv[]) | ||
308 | { | ||
309 | int x,count; | ||
310 | unsigned int y; | ||
311 | unsigned char reqDisable[] = {0x01, 0x01, 0x71}; | ||
312 | // unsigned char reqTest[] = {0x01, 0x01, 0x96, 0xFF, 0xFF}; | ||
313 | |||
314 | /* RPC testing packets: */ | ||
315 | |||
316 | /* RPC: Get version */ | ||
317 | //unsigned char reqTest2[] = {0x01, 0x01, 0x00, 0x03, 0x00}; | ||
318 | /* RPC: read I/O 0x6D mask 0xFF */ | ||
319 | //unsigned char reqTest2[] = {0x01, 0x01, 0x02, 0x01, 0x02, 0x6D, 0xFF}; /* */ | ||
320 | /* RPC: write I/O 0x03 mask 0xFF value 0x31 */ | ||
321 | //unsigned char reqTest2[] = {0x01, 0x01, 0x01, 0x01, 0x07, 0x03, 0xFF, 0x31}; /* write I/O */ | ||
322 | |||
323 | /* RPC: write forged FBUS packet to MDISND */ | ||
324 | // unsigned char reqTest2[] = {0x01, 0x01, 0x16, 0x01, 0x06, | ||
325 | //0x14, // R0 -- length | ||
326 | //0x05, // R1 -- MDI type identifier 0x05(FBUS) | ||
327 | //0x1e, 0x0c, 0x00, 0x66, | ||
328 | //0x00, 0x0e, 0x01, 0x01, | ||
329 | //0x66, 0x55, 0x44, 0x33, | ||
330 | //0x0d, 0x01, 0x01, 0x01, | ||
331 | //0x1b, 0x58, 0x01, 0x44}; | ||
332 | //1805 t=cb37 nr=e2 :D 05: | ||
333 | |||
334 | /* debug enable packet */ | ||
335 | unsigned char reqEnable[] = { | ||
336 | 0x00, 0x01, 0x70, | ||
337 | /* Debug bits | ||
338 | byte[bit>>3]&(1<<(7-(bit&7))) | ||
339 | */ | ||
340 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x00 */ | ||
341 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x40 */ | ||
342 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x80 */ | ||
343 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0xC0 */ | ||
344 | /* Debug verbose bits | ||
345 | byte[bit>>3]&(1<<(7-(bit&7))) | ||
346 | */ | ||
347 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
348 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
349 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
350 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
351 | }; | ||
352 | |||
353 | #define ENABLE_BIT(bit,verbose) reqEnable[3 + (bit>>3)] |= 1<<(7-(bit&7)); if(verbose){reqEnable[3 + 32 + (bit>>3)] |= 1<<(7-(bit&7));} | ||
354 | |||
355 | /* Enable some bit | ||
356 | TODO command line or GUI interface | ||
357 | */ | ||
358 | //ENABLE_BIT(0x18, 1);/* Enable MDISND debugging */ | ||
359 | //ENABLE_BIT(0x19, 1);/* Enable MDIRCV debugging */ | ||
360 | //ENABLE_BIT(0x31, 1); | ||
361 | |||
362 | gsmdec = GSMDecoder_new(); | ||
363 | /* Open XML file .. needs to be argument */ | ||
364 | { | ||
365 | FILE *xout = fopen("out.xml", "w"); | ||
366 | GSMDecoder_xmlout(gsmdec, xout); | ||
367 | } | ||
368 | printf("Debug Trace Mode -- wumpus 2003\n"); | ||
369 | traces = wmx_tracestruct_load(argv[2]); | ||
370 | if(traces == NULL) | ||
371 | printf("Warning: could not load trace description file %s\n", argv[2]); | ||
372 | printf("Activating ranges:\n"); | ||
373 | count = 0; | ||
374 | for(x=3; x<argc; x++) { | ||
375 | char *ptr = argv[x]; | ||
376 | unsigned from,to,verbose; | ||
377 | |||
378 | while(*ptr) { | ||
379 | verbose = 0; | ||
380 | if(*ptr == 'v') { | ||
381 | verbose = 1; | ||
382 | ptr++; | ||
383 | } | ||
384 | to = from = strtol(ptr, &ptr, 16); | ||
385 | if(*ptr == '-') { | ||
386 | ptr ++; | ||
387 | to = strtol(ptr, &ptr, 16); | ||
388 | } | ||
389 | if(*ptr != ',' && *ptr != 0) { | ||
390 | printf("Invalid parameter '%s'\n", argv[x]); | ||
391 | return; | ||
392 | } | ||
393 | if(*ptr == ',') | ||
394 | ptr++; | ||
395 | if(from > 0xFF) from=0xFF; | ||
396 | if(to > 0xFF) to=0xFF; | ||
397 | printf(" %02x-%02x verbose=%i\n",from,to,verbose); | ||
398 | for(y=from; y<=to; y++) { | ||
399 | ENABLE_BIT(y, verbose); | ||
400 | count++; | ||
401 | } | ||
402 | } | ||
403 | } | ||
404 | if(count == 0) { | ||
405 | printf("Nothing activated -- bailing out\n"); | ||
406 | return; | ||
407 | } | ||
408 | //ENABLE_BIT(0x20, 1); /* SIM commands (literal) */ | ||
409 | //ENABLE_BIT(0x21, 1); /* SIML2 commands (literal) */ | ||
410 | //ENABLE_BIT(0x22, 1); /* SIM commands (literal) */ | ||
411 | //ENABLE_BIT(0x3B, 1);/* PHCTRL state */ | ||
412 | |||
413 | GSM_Init(true); | ||
414 | |||
415 | /* We Need DCT3 */ | ||
416 | if (CheckDCT3Only()!=ERR_NONE) return; | ||
417 | |||
418 | error=DCT3_EnableSecurity (&s, 0x01); | ||
419 | Print_Error(error); | ||
420 | |||
421 | s.User.UserReplyFunctions=UserReplyFunctionsX; | ||
422 | |||
423 | //error=GSM_WaitFor (&s, reqTest, sizeof(reqTest), 0x40, 1, ID_DebugSwitch); | ||
424 | |||
425 | //error=GSM_WaitFor (&s, reqTest2, sizeof(reqTest2), 0xD1, 4, ID_RPC); | ||
426 | |||
427 | /* Enable Debug Mode */ | ||
428 | error=GSM_WaitFor (&s, reqEnable, sizeof(reqEnable), 0x40, 4, ID_DebugSwitch); | ||
429 | |||
430 | Print_Error(error); | ||
431 | signal(SIGINT, interrupt); | ||
432 | printf("Press Ctrl+C to interrupt...\n"); | ||
433 | x=0; | ||
434 | |||
435 | /* | ||
436 | while(x<100) { | ||
437 | //printf(": %02x\n",x); | ||
438 | s.Phone.Data.RequestID= ID_DebugTrace; | ||
439 | res = s.Device.Functions->ReadDevice(&s, buff, 255); | ||
440 | if(res) { | ||
441 | printf("%02x\n",x); | ||
442 | for(y=0;y<res;y++) { | ||
443 | //printf("%02x\n",x,buff[y]&0xFF); | ||
444 | s.Protocol.Functions->StateMachine(&s,buff[y]); | ||
445 | x++; | ||
446 | } | ||
447 | } | ||
448 | } | ||
449 | */ | ||
450 | ; | ||
451 | |||
452 | /* todo: wait and dump for some time */ | ||
453 | while (!gshutdown) { | ||
454 | GSM_ReadDevice(&s,true); | ||
455 | my_sleep(10); | ||
456 | } | ||
457 | signal(SIGINT, SIG_DFL); | ||
458 | printf("Disabling\n"); | ||
459 | error=GSM_WaitFor (&s, reqDisable, sizeof(reqDisable), 0x40, 10, ID_DebugSwitch); | ||
460 | Print_Error(error); | ||
461 | |||
462 | GSMDecoder_free(gsmdec); | ||
463 | } | ||
464 | |||
465 | static GSM_Reply_Function UserReplyFunctionsX[] = { | ||
466 | {DCT3_ReplySwitchDebug, "\x40",0x02,0x70,ID_DebugSwitch }, | ||
467 | {DCT3_ReplySwitchDebug, "\x40",0x02,0x71,ID_DebugSwitch }, | ||
468 | {DCT3_ReplyDebugTrace, "\x00",0x00,0x00,ID_IncomingFrame}, | ||
469 | {DCT3_ReplyMyPacket, "\x40",0x00,0x00,ID_IncomingFrame}, | ||
470 | |||
471 | {DCT3_ReplyRPC, "\xD2",0x00,0x00,ID_RPC }, | ||
472 | |||
473 | {NULL, "\x00",0x00,0x00,ID_None } | ||
474 | }; | ||
475 | |||
476 | #endif | ||
477 | |||
478 | /* How should editor hadle tabs in this file? Add editor commands here. | ||
479 | * vim: noexpandtab sw=8 ts=8 sts=8: | ||
480 | */ | ||