~ubuntu-branches/ubuntu/saucy/nut/saucy

« back to all changes in this revision

Viewing changes to drivers/libhid.c

  • Committer: Bazaar Package Importer
  • Author(s): Reinhard Tartler
  • Date: 2005-07-20 19:48:50 UTC
  • mto: (16.1.1 squeeze)
  • mto: This revision was merged to the branch mainline in revision 4.
  • Revision ID: james.westby@ubuntu.com-20050720194850-oo61wjr33rrx2mre
Tags: upstream-2.0.2
ImportĀ upstreamĀ versionĀ 2.0.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
 * @file libhid.c
3
3
 * @brief HID Library - User API (Generic HID Access using MGE HIDParser)
4
4
 *
5
 
 * @author Copyright (C) 2003
 
5
 * @author Copyright (C) 2003 - 2005
6
6
 *      Arnaud Quette <arnaud.quette@free.fr> && <arnaud.quette@mgeups.com>
7
7
 *      Philippe Marzouk <philm@users.sourceforge.net> (dump_hex())
8
 
 *
 
8
 *      John Stamp <kinsayder@hotmail.com>
 
9
 *      
9
10
 * This program is sponsored by MGE UPS SYSTEMS - opensource.mgeups.com
10
11
 *
11
12
 *      The logic of this file is ripped from mge-shut driver (also from
37
38
 
38
39
#include "hid-usb.h"
39
40
 
 
41
#include <errno.h>
 
42
extern int errno;
 
43
 
40
44
HIDDevice curDevice;
41
45
 
42
46
static HIDData          hData;
43
47
static HIDParser        hParser;
44
48
 
45
49
unsigned char raw_buf[100];
 
50
unsigned char **report_buf;
46
51
int replen; /* size of the last report retrieved */
 
52
static int prev_report; /* previously retrieved report ID */
 
53
static time_t prev_report_ts = 0; /* timestamp of the previously retrieved report */
47
54
unsigned char ReportDesc[4096];
48
55
 
49
56
#define MAX_REPORT_SIZE         0x1800
52
59
extern void upsdebugx(int level, const char *fmt, ...);
53
60
#define TRACE upsdebugx
54
61
 
55
 
/* Units and exponents table (HID PDC, ļæ½3.2.3) */
 
62
/* Units and exponents table (HID PDC, 3.2.3) */
56
63
#define NB_HID_UNITS 10
57
64
const long HIDUnits[NB_HID_UNITS][2]=
58
65
{
59
 
  {0x00000000,0}, /* None */
60
 
  {0x00F0D121,7}, /* Voltage */
61
 
  {0x00100001,0}, /* Ampere */
62
 
  {0x0000D121,7}, /* VA */
63
 
  {0x0000D121,7}, /* Watts */
64
 
  {0x00001001,0}, /* second */
65
 
  {0x00010001,0}, /* ļæ½K */
66
 
  {0x00000000,0}, /* percent */
67
 
  {0x0000F001,0}, /* Hertz */
68
 
  {0x00101001,0}, /* As */
 
66
        {0x00000000,0}, /* None */
 
67
        {0x00F0D121,7}, /* Voltage */
 
68
        {0x00100001,0}, /* Ampere */
 
69
        {0x0000D121,7}, /* VA */
 
70
        {0x0000D121,7}, /* Watts */
 
71
        {0x00001001,0}, /* second */
 
72
        {0x00010001,0}, /* K */
 
73
        {0x00000000,0}, /* percent */
 
74
        {0x0000F001,0}, /* Hertz */
 
75
        {0x00101001,0}, /* As */
69
76
};
70
77
 
71
78
/* support functions */
72
79
void logical_to_physical(HIDData *Data);
73
80
void physical_to_logical(HIDData *Data);
 
81
const char *hid_lookup_path(int usage);
74
82
int hid_lookup_usage(char *name);
75
 
ushort lookup_path(const char *HIDpath, HIDData *data);
 
83
ushort lookup_path(char *HIDpath, HIDData *data);
76
84
void dump_hex (const char *msg, const unsigned char *buf, int len);
77
85
long get_unit_expo(long UnitType);
78
86
float expo(int a, int b);
79
87
 
80
88
 
81
 
HIDDevice *HIDOpenDevice(const char *port, MatchFlags *flg)
82
 
{
83
 
  int ReportSize;
84
 
 
85
 
  /* Init structure */
86
 
  curDevice.Name = NULL;
87
 
  curDevice.Vendor = NULL;
88
 
  curDevice.VendorID = -1;
89
 
  curDevice.Product = NULL;
90
 
  curDevice.ProductID = -1;
91
 
  curDevice.Serial = NULL;
92
 
  curDevice.Application = -1;
93
 
  curDevice.fd = -1;
94
 
 
95
 
  /* get and parse descriptors (dev, cfg and report) */
96
 
  if ((ReportSize = libusb_open(&curDevice, flg, ReportDesc)) == -1) {
97
 
    return NULL;
98
 
  }
99
 
  else {
100
 
    TRACE(2, "Report Descriptor size = %d", ReportSize);
101
 
    dump_hex ("Report Descriptor", ReportDesc, 200);
102
 
 
103
 
    /* HID Parser Init */
104
 
    ResetParser(&hParser);
105
 
    hParser.ReportDescSize = ReportSize;
106
 
    memcpy(hParser.ReportDesc, ReportDesc, ReportSize);
107
 
    HIDParse(&hParser, &hData);
108
 
 
109
 
    /* CHeck for matching UsageCode (1rst collection == application == type of device) */
110
 
    if ((hData.Path.Node[0].UPage == ((flg->UsageCode & 0xFFFF0000) / 0x10000))
111
 
        && (hData.Path.Node[0].Usage == (flg->UsageCode & 0x0000FFFF)))
112
 
      TRACE(2, "Found a device matching UsageCode (0x%08x)", flg->UsageCode);
113
 
    else {
114
 
      TRACE(2, "Found a device, but not matching UsageCode (0x%08x)", flg->UsageCode);
115
 
      return NULL;
116
 
    }
117
 
  }
118
 
  return &curDevice;
119
 
}
120
 
 
 
89
void HIDDumpTree(HIDDevice *hd)
 
90
{
 
91
        int i;
 
92
        char str[128];
 
93
 
 
94
        while (HIDParse(&hParser, &hData) != FALSE)
 
95
        {
 
96
                str[0] = '\0';
 
97
                for (i = 0; i < hData.Path.Size; i++)
 
98
                {
 
99
                  strcat(str, hid_lookup_path((hData.Path.Node[i].UPage * 0x10000) + hData.Path.Node[i].Usage));
 
100
                        if (i < (hData.Path.Size - 1))
 
101
                                strcat (str, ".");
 
102
                }
 
103
                TRACE(1, "Path: %s", str);
 
104
        }
 
105
}
 
106
                                                
 
107
HIDDevice *HIDOpenDevice(const char *port, MatchFlags *flg, int mode)
 
108
{
 
109
        int ReportSize;
 
110
 
 
111
        if ( mode == MODE_OPEN )
 
112
        {
 
113
                /* Init structure */
 
114
                curDevice.Name = NULL;
 
115
                curDevice.Vendor = NULL;
 
116
                curDevice.VendorID = -1;
 
117
                curDevice.Product = NULL;
 
118
                curDevice.ProductID = -1;
 
119
                curDevice.Serial = NULL;
 
120
                curDevice.Application = -1;
 
121
                curDevice.fd = -1;
 
122
        }
 
123
        else
 
124
        {
 
125
                TRACE(2, "Reopening device");
 
126
        }
 
127
 
 
128
        /* get and parse descriptors (dev, cfg and report) */
 
129
        ReportSize = libusb_open(&curDevice, flg, ReportDesc, mode);
 
130
 
 
131
        if (ReportSize == -1)
 
132
                return NULL;
 
133
        else
 
134
        {
 
135
                if ( mode == MODE_REOPEN )
 
136
                {
 
137
                        TRACE(2, "Device reopened successfully");
 
138
                        return &curDevice;
 
139
                }
 
140
        
 
141
                TRACE(2, "Report Descriptor size = %d", ReportSize);
 
142
                dump_hex ("Report Descriptor", ReportDesc, 200);
 
143
 
 
144
                /* HID Parser Init */
 
145
                ResetParser(&hParser);
 
146
                hParser.ReportDescSize = ReportSize;
 
147
                memcpy(hParser.ReportDesc, ReportDesc, ReportSize);
 
148
                HIDParse(&hParser, &hData);
 
149
        }
 
150
        return &curDevice;
 
151
}
 
152
 
 
153
/* int HIDGetItem(hid_info_t *ItemInfo, HIDItem *item) */
121
154
HIDItem *HIDGetItem(const char *ItemPath)
122
155
{
123
 
  return NULL;
124
 
}
125
 
 
126
 
float HIDGetItemValue(const char *path)
127
 
{
128
 
  int i, retcode;
129
 
  float Value;
130
 
 
131
 
  /* Prepare path of HID object */
132
 
  hData.Type = ITEM_FEATURE;
133
 
  hData.ReportID = 0;
134
 
 
135
 
  if((retcode = lookup_path(path, &hData)) > 0) {
136
 
    TRACE(2, "Path depth = %i", retcode);
137
 
    
138
 
    for (i = 0; i<retcode; i++)
139
 
      TRACE(2, "%i: UPage(%x), Usage(%x)", i,
140
 
                hData.Path.Node[i].UPage,
141
 
                hData.Path.Node[i].Usage);
142
 
    
143
 
    hData.Path.Size = retcode;
144
 
 
145
 
    /* Get info on object (reportID, offset and size) */
146
 
    if (FindObject(&hParser,&hData) == 1) {
147
 
      /* Get report with data */
148
 
      /* if ((replen=libusb_get_report(hData.ReportID, 
149
 
         raw_buf, MAX_REPORT_SIZE)) > 0) { => doesn't work! */
150
 
      if ((replen=libusb_get_report(hData.ReportID, raw_buf, 10)) > 0) {
151
 
        /* Extract the data value */
152
 
        GetValue((const unsigned char *) raw_buf, &hData);
153
 
 
154
 
        TRACE(2, "=>> Before exponent: %ld, %i/%i)", hData.Value,
155
 
              (int)hData.UnitExp, (int)get_unit_expo(hData.Unit) );
156
 
 
157
 
        /* Convert Logical Min, Max and Value in Physical */
158
 
        /* logical_to_physical(&hData); */
159
 
 
160
 
        Value = hData.Value;
161
 
 
162
 
        /* Process exponents */
163
 
        /* Value*=(float) pow(10,(int)hData.UnitExp - get_unit_expo(hData.Unit)); */
164
 
        Value*=(float) expo(10,(int)hData.UnitExp - get_unit_expo(hData.Unit));
165
 
        hData.Value = (long) Value;
166
 
 
167
 
        /* Convert Logical Min, Max and Value into Physical */
168
 
        logical_to_physical(&hData);
169
 
 
170
 
        dump_hex ("Report ", raw_buf, replen);
171
 
 
172
 
        return Value;
173
 
      }
174
 
      else
175
 
        TRACE(2, "Can't retrieve Report %i", hData.ReportID);
176
 
//exit(-1);
177
 
    }
178
 
    else
179
 
      TRACE(2, "Can't find object %s", path);
180
 
  }
181
 
  return -2; /* TODO: should be checked */
182
 
}
183
 
 
184
 
char *HIDGetItemString(const char *path)
185
 
{
186
 
  int i, retcode;
187
 
  
188
 
  /* Prepare path of HID object */
189
 
  hData.Type = ITEM_FEATURE;
190
 
  hData.ReportID = 0;
191
 
  
192
 
  if((retcode = lookup_path(path, &hData)) > 0) {
193
 
    TRACE(2, "Path depth = %i", retcode);
194
 
    
195
 
    for (i = 0; i<retcode; i++)
196
 
      TRACE(2, "%i: UPage(%x), Usage(%x)", i,
 
156
        /* was for libhid, not useful in our scope! */
 
157
        return NULL;
 
158
}
 
159
 
 
160
/* return 1 if OK, 0 on fail, <= -1 otherwise (ie disconnect) */
 
161
float HIDGetItemValue(char *path, float *Value)
 
162
{
 
163
        int i, retcode;
 
164
        float tmpValue;
 
165
 
 
166
        /* Prepare path of HID object */
 
167
        hData.Type = ITEM_FEATURE;
 
168
        hData.ReportID = 0;
 
169
        hData.Path.Size = 0;
 
170
 
 
171
        if((retcode = lookup_path(path, &hData)) > 0)
 
172
        {
 
173
                TRACE(4, "Path depth = %i", retcode);
 
174
 
 
175
                for (i = 0; i<retcode; i++)
 
176
                        TRACE(4, "%i: UPage(%x), Usage(%x)", i,
 
177
                                hData.Path.Node[i].UPage,
 
178
                                hData.Path.Node[i].Usage);
 
179
 
 
180
                hData.Path.Size = retcode;
 
181
 
 
182
                /* Get info on object (reportID, offset and size) */
 
183
                if (FindObject(&hParser, &hData) == 1)
 
184
                {
 
185
                        /* Get report with data */
 
186
                        /* if ((replen=libusb_get_report(hData.ReportID,
 
187
                        raw_buf, MAX_REPORT_SIZE)) > 0) { => doesn't work! */
 
188
                        /* Bufferize at least the last report */
 
189
                        if ( ( (prev_report == hData.ReportID) && (time(NULL) <= (prev_report_ts + MAX_TS)) )
 
190
                                || ((replen=libusb_get_report(hData.ReportID, raw_buf, 10)) > 0) )
 
191
                        {
 
192
                                /* Extract the data value */
 
193
                                GetValue((const unsigned char *) raw_buf, &hData);
 
194
 
 
195
                                TRACE(4, "=>> Before exponent: %ld, %i/%i)", hData.Value,
 
196
                                        (int)hData.UnitExp, (int)get_unit_expo(hData.Unit) );
 
197
 
 
198
                                /* Convert Logical Min, Max and Value in Physical */
 
199
                                /* logical_to_physical(&hData); */
 
200
 
 
201
                                tmpValue = hData.Value;
 
202
 
 
203
                                /* Process exponents */
 
204
                                /* Value*=(float) pow(10,(int)hData.UnitExp - get_unit_expo(hData.Unit)); */
 
205
                                tmpValue*=(float) expo(10,(int)hData.UnitExp - get_unit_expo(hData.Unit));
 
206
                                hData.Value = (long) tmpValue;
 
207
 
 
208
                                /* Convert Logical Min, Max and Value into Physical */
 
209
                                logical_to_physical(&hData);
 
210
 
 
211
                                TRACE(4, "=>> After conversion: %ld, %i/%i)", hData.Value,
 
212
                                        (int)hData.UnitExp, (int)get_unit_expo(hData.Unit) );
 
213
 
 
214
                                dump_hex ("Report ", raw_buf, replen);
 
215
 
 
216
                                *Value = hData.Value;
 
217
                                prev_report = hData.ReportID;
 
218
                                prev_report_ts = time(NULL);
 
219
                                return 1;
 
220
                        }
 
221
                        else
 
222
                        {
 
223
                                TRACE(2, "Can't retrieve Report %i (%i/%i)", hData.ReportID, replen, errno);
 
224
                                return -errno;
 
225
                        }
 
226
                }
 
227
                else
 
228
                        TRACE(2, "Can't find object %s", path);
 
229
        }
 
230
        return 0; /* TODO: should be checked */
 
231
}
 
232
 
 
233
char *HIDGetItemString(char *path)
 
234
{
 
235
  int i, retcode;
 
236
  
 
237
  /* Prepare path of HID object */
 
238
  hData.Type = ITEM_FEATURE;
 
239
  hData.ReportID = 0;
 
240
  hData.Path.Size = 0;
 
241
  
 
242
  if((retcode = lookup_path(path, &hData)) > 0) {
 
243
    TRACE(4, "Path depth = %i", retcode);
 
244
    
 
245
    for (i = 0; i<retcode; i++)
 
246
      TRACE(4, "%i: UPage(%x), Usage(%x)", i,
197
247
                hData.Path.Node[i].UPage,
198
248
                hData.Path.Node[i].Usage);
199
249
    
219
269
  return NULL;
220
270
}
221
271
 
222
 
bool HIDSetItemValue(const char *path, float value)
 
272
bool HIDSetItemValue(char *path, float value)
223
273
{
224
 
  float Value;
225
 
 
226
 
  /* Begin by a standard Get to fill in com structures ... */
227
 
  Value = HIDGetItemValue(path);
228
 
 
229
 
  /* ... And play with global vars */
230
 
  if (Value != -2) { /* Get succeed */
231
 
 
232
 
    TRACE(2, "=>> SET: Before set: %.2f (%ld)", Value, (long)value);
233
 
 
234
 
    /* Test if Item is settable */
235
 
    if (hData.Attribute != ATTR_DATA_CST) {
236
 
      /* Set new value for this item */
237
 
      /* And Process exponents restoration */
238
 
      Value = value * expo(10, get_unit_expo(hData.Unit) - (int)hData.UnitExp);
239
 
 
240
 
      hData.Value=(long) Value;
241
 
 
242
 
      TRACE(2, "=>> SET: after exp: %ld/%.2f (exp = %.2f)", hData.Value, Value,
243
 
             expo(10, (int)get_unit_expo(hData.Unit) - (int)hData.UnitExp));
244
 
 
245
 
      /* Convert Physical Min, Max and Value in Logical */
246
 
      physical_to_logical(&hData);
247
 
      TRACE(2, "=>> SET: after PL: %ld", hData.Value);
248
 
 
249
 
      SetValue(&hData, raw_buf);
250
 
 
251
 
      dump_hex ("==> Report after setvalue", raw_buf, replen);
252
 
 
253
 
      if (libusb_set_report(hData.ReportID, raw_buf, replen) > 0) {
254
 
        TRACE(2, "Set report succeeded");
255
 
        return TRUE;
256
 
      } else {
257
 
        TRACE(2, "Set report failed");
 
274
        float Value;
 
275
        int retcode;
 
276
        
 
277
        /* Begin by a standard Get to fill in com structures ... */
 
278
        retcode = HIDGetItemValue(path, &Value);
 
279
        
 
280
        /* ... And play with global vars */
 
281
        if (retcode == 1) /* Get succeed */
 
282
        {
 
283
                TRACE(2, "=>> SET: Before set: %.2f (%ld)", Value, (long)value);
 
284
                
 
285
                /* Test if Item is settable */
 
286
                if (hData.Attribute != ATTR_DATA_CST)
 
287
                {
 
288
                        /* Set new value for this item */
 
289
                        /* And Process exponents restoration */
 
290
                        Value = value * expo(10, get_unit_expo(hData.Unit) - (int)hData.UnitExp);
 
291
                        
 
292
                        hData.Value=(long) Value;
 
293
                        
 
294
                        TRACE(2, "=>> SET: after exp: %ld/%.2f (exp = %.2f)", hData.Value, Value,
 
295
                                expo(10, (int)get_unit_expo(hData.Unit) - (int)hData.UnitExp));
 
296
 
 
297
                        /* Convert Physical Min, Max and Value in Logical */
 
298
                        physical_to_logical(&hData);
 
299
                        TRACE(2, "=>> SET: after PL: %ld", hData.Value);
 
300
                        
 
301
                        SetValue(&hData, raw_buf);
 
302
                        
 
303
                        dump_hex ("==> Report after setvalue", raw_buf, replen);
 
304
                        
 
305
                        if (libusb_set_report(hData.ReportID, raw_buf, replen) > 0)
 
306
                        {
 
307
                                TRACE(2, "Set report succeeded");
 
308
                                return TRUE;
 
309
                        }
 
310
                        else
 
311
                        {
 
312
                                TRACE(2, "Set report failed");
 
313
                                return FALSE;
 
314
                        }
 
315
                        /* check if set succeed! => doesn't work on *Delay (decremented!) */
 
316
                        /*      Value = HIDGetItemValue(path);
 
317
                        
 
318
                        TRACE(2, "=>> SET: new value = %.2f (was set to %.2f)\n", 
 
319
                        Value, (float) value);
 
320
                        return TRUE;*/ /* (Value == value); */
 
321
                }
 
322
        }
258
323
        return FALSE;
259
 
      }
260
 
      /* check if set succeed! => doesn't work on *Delay (decremented!) */
261
 
      /*      Value = HIDGetItemValue(path);
262
 
      
263
 
      TRACE(2, "=>> SET: new value = %.2f (was set to %.2f)\n", 
264
 
      Value, (float) value);
265
 
      return TRUE;*/ /* (Value == value); */
266
 
    }
267
 
  }
268
 
  return FALSE;
269
324
}
270
 
HIDItem *HIDGetNextEvent(HIDDevice *dev)
 
325
 
 
326
int HIDGetEvents(HIDDevice *dev, HIDItem **eventsList)
271
327
{
272
 
  /*  unsigned char buf[20];
273
 
 
274
 
  upsdebugx(1, "Waiting for notifications\n");*/
275
 
 
276
 
  /* TODO: To be written */
277
 
  /* needs libusb-0.1.8 to work => use ifdef and autoconf */
278
 
  /* libusb_get_interrupt(&buf[0], 20, 5000); */
279
 
  return NULL;
 
328
        unsigned char buf[20];
 
329
        char itemPath[128];
 
330
        int size, offset = 0, itemCount = 0;
 
331
        
 
332
        upsdebugx(1, "Waiting for notifications...");
 
333
        
 
334
        /* needs libusb-0.1.8 to work => use ifdef and autoconf */
 
335
        if ((size = libusb_get_interrupt(&buf[0], 20, 5000)) > -1)
 
336
        {
 
337
                dump_hex ("Notification", buf, size);
 
338
                
 
339
                /* Convert report size in bits */
 
340
                size = (size - 1) * 8;
 
341
                
 
342
                /* Parse response Report and Set correspondant Django values */
 
343
                hData.ReportID = buf[0];
 
344
                hData.Type = ITEM_INPUT;
 
345
                
 
346
                while(offset < size)
 
347
                {
 
348
                        /* Set Offset */
 
349
                        hData.Offset = offset;
 
350
        
 
351
                        /* Reset HID Path but keep Report ID */
 
352
                        memset(&hData.Path, '\0', sizeof(HIDPath));
 
353
        
 
354
                        /* Get HID Object characteristics */
 
355
                        if(FindObject(&hParser, &hData))
 
356
                        {
 
357
                                /* Get HID Object value from report */
 
358
                                GetValue(buf, &hData);
 
359
                                memset(&itemPath, 0, sizeof(128));
 
360
                                lookup_path(&itemPath[0], &hData);
 
361
        
 
362
                                upsdebugx(3, "Object: %s = %ld", itemPath, hData.Value);
 
363
        
 
364
                                /* FIXME: enhance this or fix/change the HID parser (see libhid project) */
 
365
                                /* if ( strstr(itemPath, "000000") == NULL) */
 
366
                                if (strcmp(itemPath, "UPS.PowerSummary.PresentStatus.") > 0)
 
367
                                {
 
368
                                        eventsList[itemCount] = (HIDItem *)malloc(sizeof (HIDItem));
 
369
                                        eventsList[itemCount]->Path = strdup(itemPath);
 
370
                                        eventsList[itemCount]->Value = hData.Value;
 
371
                                        itemCount++;
 
372
                                }
 
373
                        }
 
374
                        offset += hData.Size;
 
375
                }
 
376
        }
 
377
        else
 
378
                itemCount = size; /* propagate error code */
 
379
 
 
380
        return itemCount;
280
381
}
 
382
 
281
383
void HIDCloseDevice(HIDDevice *dev)
282
384
{
283
 
  libusb_close(&curDevice);
 
385
        TRACE(2, "Closing device");
 
386
        libusb_close(&curDevice);
284
387
}
285
388
 
286
389
 
287
390
/*******************************************************
288
 
 * support functions
 
391
 * Support functions
289
392
 *******************************************************/
290
393
 
291
394
#define MAX_STRING                      64
292
395
 
293
396
void logical_to_physical(HIDData *Data)
294
397
{
295
 
  if(Data->PhyMax - Data->PhyMin > 0)
296
 
    {
297
 
      float Factor = (float)(Data->PhyMax - Data->PhyMin) / (Data->LogMax - Data->LogMin);
298
 
      /* Convert Value */
299
 
      Data->Value=(long)((Data->Value - Data->LogMin) * Factor) + Data->PhyMin;
300
 
 
301
 
      if(Data->Value > Data->PhyMax)
302
 
        Data->Value |= ~Data->PhyMax;
303
 
    }
304
 
  else /* => nothing to do!? */
305
 
    {
306
 
      /* Value.m_Value=(long)(pConvPrm->HValue); */
307
 
      if(Data->Value > Data->LogMax)
308
 
        Data->Value |= ~Data->LogMax;
309
 
    }
310
 
  
311
 
  /* if(Data->Value > Data->Value.m_Max)
312
 
    Value.m_Value |= ~Value.m_Max;
313
 
  */
 
398
        if(Data->PhyMax - Data->PhyMin > 0)
 
399
        {
 
400
                float Factor = (float)(Data->PhyMax - Data->PhyMin) / (Data->LogMax - Data->LogMin);
 
401
                /* Convert Value */
 
402
                Data->Value=(long)((Data->Value - Data->LogMin) * Factor) + Data->PhyMin;
 
403
                        
 
404
                if(Data->Value > Data->PhyMax)
 
405
                        Data->Value |= ~Data->PhyMax;
 
406
        }
 
407
        else /* => nothing to do!? */
 
408
        {
 
409
                /* Value.m_Value=(long)(pConvPrm->HValue); */
 
410
                if(Data->Value > Data->LogMax)
 
411
                        Data->Value |= ~Data->LogMax;
 
412
        }
 
413
        
 
414
        /* if(Data->Value > Data->Value.m_Max)
 
415
                Value.m_Value |= ~Value.m_Max;
 
416
        */
314
417
}
315
418
 
316
419
void physical_to_logical(HIDData *Data)
317
420
{
318
 
  TRACE(2, "PhyMax = %ld, PhyMin = %ld, LogMax = %ld, LogMin = %ld",
319
 
        Data->PhyMax, Data->PhyMin, Data->LogMax, Data->LogMin);
320
 
 
321
 
  if(Data->PhyMax - Data->PhyMin > 0)
322
 
    {
323
 
      float Factor=(float)(Data->LogMax - Data->LogMin) / (Data->PhyMax - Data->PhyMin);
324
 
      /* Convert Value */
325
 
      Data->Value=(long)((Data->Value - Data->PhyMin) * Factor) + Data->LogMin;
326
 
    }
327
 
    /* else => nothing to do!?
328
 
       {
329
 
       m_ConverterTab[iTab].HValue=m_ConverterTab[iTab].DValue;
330
 
       } */
 
421
        TRACE(2, "PhyMax = %ld, PhyMin = %ld, LogMax = %ld, LogMin = %ld",
 
422
                Data->PhyMax, Data->PhyMin, Data->LogMax, Data->LogMin);
 
423
        
 
424
        if(Data->PhyMax - Data->PhyMin > 0)
 
425
        {
 
426
                float Factor=(float)(Data->LogMax - Data->LogMin) / (Data->PhyMax - Data->PhyMin);
 
427
                
 
428
                /* Convert Value */
 
429
                Data->Value=(long)((Data->Value - Data->PhyMin) * Factor) + Data->LogMin;
 
430
        }
 
431
        /* else => nothing to do!?
 
432
        {
 
433
        m_ConverterTab[iTab].HValue=m_ConverterTab[iTab].DValue;
 
434
        } */
331
435
}
332
436
 
333
437
long get_unit_expo(long UnitType)
334
438
{
335
 
  int i = 0, exp = -1;
336
 
 
337
 
  while (i < NB_HID_UNITS) {
338
 
    if (HIDUnits[i][0] == UnitType) {
339
 
      exp = HIDUnits[i][1];
340
 
      break;
341
 
    }
342
 
    i++;
343
 
  }
344
 
  return exp;
 
439
        int i = 0, exp = -1;
 
440
        
 
441
        while (i < NB_HID_UNITS)
 
442
        {
 
443
                if (HIDUnits[i][0] == UnitType)
 
444
                {
 
445
                        exp = HIDUnits[i][1];
 
446
                        break;
 
447
                }
 
448
                i++;
 
449
        }
 
450
        return exp;
345
451
}
346
452
 
347
453
/* exponent function: return a^b */
348
 
/* TODO: check if needed to replace libmath->pow */
 
454
/* FIXME: check if needed/possible to replace libmath->pow */
349
455
float expo(int a, int b)
350
456
{
351
 
  if (b==0)
352
 
    return (float) 1;
353
 
  if (b>0)
354
 
    return (float) a * expo(a,b-1);
355
 
  if (b<0)
356
 
    return (float)((float)(1/(float)a) * (float) expo(a,b+1));
357
 
  
358
 
  /* not reached */
359
 
  return -1;
 
457
        if (b==0)
 
458
                return (float) 1;
 
459
        if (b>0)
 
460
                return (float) a * expo(a,b-1);
 
461
        if (b<0)
 
462
                return (float)((float)(1/(float)a) * (float) expo(a,b+1));
 
463
        
 
464
        /* not reached */
 
465
        return -1;
360
466
}
361
467
 
362
 
/* translate HID string path to numeric path and return path depth */
 
468
/* translate HID string path from/to numeric path and return path depth */
363
469
/* TODO: use usbutils functions (need to be externalised!) */
364
 
ushort lookup_path(const char *HIDpath, HIDData *data)
 
470
ushort lookup_path(char *HIDpath, HIDData *data)
365
471
{
366
 
  ushort i = 0, cond = 1;
367
 
  int cur_usage;
368
 
  char buf[MAX_STRING];
369
 
  char *start, *end; 
370
 
  
371
 
  strncpy(buf, HIDpath, strlen(HIDpath));
372
 
  buf[strlen(HIDpath)] = '\0';
373
 
  start = end = buf;
374
 
  
375
 
  TRACE(2, "entering lookup_path(%s)", buf);
376
 
  
377
 
  while (cond) {
378
 
    
379
 
    if ((end = strchr(start, '.')) == NULL) {
380
 
      cond = 0;                 
381
 
    }
382
 
    else
383
 
      *end = '\0';
384
 
    
385
 
    TRACE(2, "parsing %s", start);
386
 
    
387
 
    /* lookup code */
388
 
    if ((cur_usage = hid_lookup_usage(start)) == -1) {
389
 
      TRACE(2, "%s wasn't found", start);
390
 
      return 0;
391
 
    }
392
 
    else {
393
 
      data->Path.Node[i].UPage = (cur_usage & 0xFFFF0000) / 0x10000;
394
 
      data->Path.Node[i].Usage = cur_usage & 0x0000FFFF; 
395
 
      i++; 
396
 
    }
397
 
    
398
 
    if(cond)
399
 
      start = end +1 ;
400
 
  }
401
 
  data->Path.Size = i;
 
472
        ushort i = 0, cond = 1;
 
473
        int cur_usage;
 
474
        char buf[MAX_STRING];
 
475
        char *start, *end; 
 
476
        
 
477
        TRACE(3, "entering lookup_path()");
 
478
        
 
479
        /* Check the way we are called */
 
480
        if (data->Path.Size != 0)
 
481
        {
 
482
          /* FIXME: another bug? */
 
483
          strcat(HIDpath, "UPS.");
 
484
 
 
485
          // Numeric to String
 
486
          for (i = 1; i <= hData.Path.Size; i++)
 
487
                {
 
488
                  /* Deal with ?bogus? */
 
489
                  if ( ((hData.Path.Node[i].UPage * 0x10000) + hData.Path.Node[i].Usage) == 0)
 
490
                        continue;
 
491
 
 
492
                  /* manage indexed collection */
 
493
                  if (hData.Path.Node[i].UPage == 0x00FF)
 
494
                        {
 
495
                          TRACE(5, "Got an indexed collection");
 
496
                          sprintf(strrchr(HIDpath, '.'), "[%i]", hData.Path.Node[i].Usage);
 
497
                        }
 
498
                  else
 
499
                        strcat(HIDpath, hid_lookup_path((hData.Path.Node[i].UPage * 0x10000) + hData.Path.Node[i].Usage));
 
500
                        
 
501
                  if (i < (hData.Path.Size - 1))
 
502
                        strcat (HIDpath, ".");
 
503
                }
 
504
        }
 
505
  else
 
506
        {
 
507
          // String to Numeric 
 
508
          strncpy(buf, HIDpath, strlen(HIDpath));
 
509
          buf[strlen(HIDpath)] = '\0';
 
510
          start = end = buf;
 
511
  
 
512
          while (cond) {
 
513
    
 
514
                if ((end = strchr(start, '.')) == NULL) {
 
515
                  cond = 0;                     
 
516
                }
 
517
                else
 
518
                  *end = '\0';
 
519
    
 
520
                TRACE(4, "parsing %s", start);
 
521
    
 
522
                /* lookup code */
 
523
                if ((cur_usage = hid_lookup_usage(start)) == -1) {
 
524
                  TRACE(4, "%s wasn't found", start);
 
525
                  return 0;
 
526
                }
 
527
                else {
 
528
                  data->Path.Node[i].UPage = (cur_usage & 0xFFFF0000) / 0x10000;
 
529
                  data->Path.Node[i].Usage = cur_usage & 0x0000FFFF; 
 
530
                  i++; 
 
531
                }
 
532
    
 
533
                if(cond)
 
534
                  start = end +1 ;
 
535
          }
 
536
          data->Path.Size = i;
 
537
        }
 
538
 
402
539
  return i;
403
540
}
404
541
 
411
548
 
412
549
static usage_lkp_t usage_lkp[] = {
413
550
        /* Power Device Page */
414
 
        {  "PresentStatus", 0x00840002 },
415
 
        {  "UPS", 0x00840004 },
416
 
        {  "BatterySystem", 0x00840010 },
417
 
        {  "Battery", 0x00840012 },
418
 
        {  "BatteryID", 0x00840013 },   
419
 
        {  "PowerConverter", 0x00840016 },
420
 
        {  "OutletSystem", 0x00840018 },
421
 
        {  "Input", 0x0084001a },
422
 
        {  "Output", 0x0084001c },
423
 
        {  "Outlet", 0x00840020 },
424
 
        {  "OutletID", 0x00840021 },
425
 
        {  "PowerSummary", 0x00840024 },
426
 
        {  "Voltage", 0x00840030 },
427
 
        {  "Current", 0x00840031 },
428
 
        {  "Frequency", 0x00840032 },
429
 
        {  "PercentLoad", 0x00840035 },
430
 
        {  "Temperature", 0x00840036 },
431
 
        {  "ConfigVoltage", 0x00840040 },
432
 
        {  "ConfigCurrent", 0x00840041 },
433
 
        {  "ConfigFrequency", 0x00840042 },
434
 
        {  "ConfigApparentPower", 0x00840043 },
435
 
        {  "LowVoltageTransfer", 0x00840053 },
436
 
        {  "HighVoltageTransfer", 0x00840054 }, 
437
 
        {  "DelayBeforeReboot", 0x00840055 },   
438
 
        {  "DelayBeforeStartup", 0x00840056 },
439
 
        {  "DelayBeforeShutdown", 0x00840057 },
440
 
        {  "Test", 0x00840058 },
441
 
        {  "OverLoad", 0x00840065 }, /* mispelled in usb.ids */
442
 
        {  "ShutdownImminent", 0x00840069 },
443
 
        {  "SwitchOn/Off", 0x0084006b },
444
 
        {  "Switchable", 0x0084006c },
445
 
        {  "Boost", 0x0084006e },
446
 
        {  "Buck", 0x0084006f },
447
 
        {  "Flow", 0x0084001e },
 
551
        {  "Undefined",                         0x00840000 },
 
552
        {  "PresentStatus",                     0x00840002 },
 
553
        {  "UPS",                               0x00840004 },
 
554
        {  "BatterySystem",                     0x00840010 },
 
555
        {  "Battery",                           0x00840012 },
 
556
        {  "BatteryID",                         0x00840013 },
 
557
        {  "PowerConverter",                    0x00840016 },
 
558
        {  "PowerConverterID",                  0X00840017 },
 
559
        {  "OutletSystem",                      0x00840018 },
 
560
        {  "OutletSystemID",                    0x00840019 },
 
561
        {  "Input",                             0x0084001a },
 
562
        {  "Output",                            0x0084001c },
 
563
        {  "Flow",                              0x0084001e },
 
564
        {  "FlowID",                            0x0084001f },
 
565
        {  "Outlet",                            0x00840020 },
 
566
        {  "OutletID",                          0x00840021 },
 
567
        {  "PowerSummary",                      0x00840024 },
 
568
        {  "PowerSummaryID",                    0x00840025 },
 
569
        {  "Voltage",                           0x00840030 },
 
570
        {  "Current",                           0x00840031 },
 
571
        {  "Frequency",                         0x00840032 },
 
572
        {  "PercentLoad",                       0x00840035 },
 
573
        {  "Temperature",                       0x00840036 },
 
574
        {  "ConfigVoltage",                     0x00840040 },
 
575
        {  "ConfigCurrent",                     0x00840041 },
 
576
        {  "ConfigFrequency",                   0x00840042 },
 
577
        {  "ConfigApparentPower",               0x00840043 },
 
578
        {  "LowVoltageTransfer",                0x00840053 },
 
579
        {  "HighVoltageTransfer",               0x00840054 },   
 
580
        {  "DelayBeforeReboot",                 0x00840055 },
 
581
        {  "DelayBeforeStartup",                0x00840056 },
 
582
        {  "DelayBeforeShutdown",               0x00840057 },
 
583
        {  "Test",                              0x00840058 },
 
584
        {  "AudibleAlarmControl",               0x0084005a },
 
585
        {  "Good",                              0x00840061 },
 
586
        {  "InternalFailure",                   0x00840062 },
 
587
        {  "OverLoad",                          0x00840065 }, /* mispelled in usb.ids */
 
588
        {  "OverTemperature",                   0x00840067 },
 
589
        {  "ShutdownRequested",                 0x00840068 },
 
590
        {  "ShutdownImminent",                  0x00840069 },
 
591
        {  "SwitchOn/Off",                      0x0084006b },
 
592
        {  "Switchable",                        0x0084006c },
 
593
        {  "Used",                              0x0084006d },
 
594
        {  "Boost",                             0x0084006e },
 
595
        {  "Buck",                              0x0084006f },
 
596
        {  "CommunicationLost",                 0x00840073 },
 
597
        {  "iManufacturer",                     0x008400fd },
 
598
        {  "iProduct",                          0x008400fe },
 
599
        {  "iSerialNumber",                     0x008400ff },
448
600
        /* Battery System Page */
449
 
        {  "RemainingCapacityLimit", 0x00850029 },
450
 
        {  "BelowRemainingCapacityLimit", 0x00850042 },
451
 
        {  "RemainingCapacity", 0x00850066 },
452
 
        {  "RunTimeToEmpty", 0x00850068 },
453
 
        {  "ACPresent", 0x008500d0 },
454
 
        {  "Charging", 0x00850044 },
455
 
        {  "Discharging", 0x00850045 },
456
 
        {  "NeedReplacement", 0x0085004b },
 
601
        { "Undefined",                          0x00850000 },
 
602
        { "RemainingCapacityLimit",             0x00850029 },
 
603
        { "RemainingTimeLimit",                 0x0085002a },
 
604
        { "CapacityMode",                       0x0085002c },
 
605
        { "BelowRemainingCapacityLimit",        0x00850042 },
 
606
        { "RemainingTimeLimitExpired",          0x00850043 },
 
607
        { "Charging",                           0x00850044 },
 
608
        { "Discharging",                        0x00850045 },
 
609
        { "NeedReplacement",                    0x0085004b },
 
610
        { "RemainingCapacity",                  0x00850066 },
 
611
        { "FullChargeCapacity",                 0x00850067 },
 
612
        { "RunTimeToEmpty",                     0x00850068 },
 
613
        { "ManufacturerDate",                   0x00850085 },
 
614
        { "Rechargeable",                       0x0085008b },
 
615
        { "WarningCapacityLimit",               0x0085008c },
 
616
        { "CapacityGranularity1",               0x0085008d },
 
617
        { "CapacityGranularity2",               0x0085008e },
 
618
        { "iOEMInformation",                    0x0085008f },
 
619
        { "DesignCapacity",                     0x00850083 },
 
620
        { "iDeviceChemistry",                   0x00850089 },
 
621
        { "ACPresent",                          0x008500d0 },
 
622
        { "BatteryPresent",                     0x008500d1 },
 
623
        { "VoltageNotRegulated",                0x008500db },
457
624
/* TODO: per MFR specific usages */
458
625
        /* MGE UPS SYSTEMS Page */
459
 
        {  "iModel", 0xffff00f0 },
460
 
        {  "RemainingCapacityLimitSetting", 0xffff004d },
461
 
        {  "TestPeriod", 0xffff0045 },
462
 
        {  "LowVoltageBoostTransfer", 0xffff0050 },
463
 
        {  "HighVoltageBoostTransfer", 0xffff0051 },
464
 
        {  "LowVoltageBuckTransfer", 0xffff0052 },
465
 
        {  "HighVoltageBuckTransfer", 0xffff0053 },
 
626
        { "iModel",                             0xffff00f0 },
 
627
        { "RemainingCapacityLimitSetting",      0xffff004d },
 
628
        { "TestPeriod",                         0xffff0045 },
 
629
        { "LowVoltageBoostTransfer",            0xffff0050 },
 
630
        { "HighVoltageBoostTransfer",           0xffff0051 },
 
631
        { "LowVoltageBuckTransfer",             0xffff0052 },
 
632
        { "HighVoltageBuckTransfer",            0xffff0053 },
 
633
        /* APC Page */
 
634
        { "APCGeneralCollection",               0xff860005 },
 
635
        { "APCBattReplaceDate",                 0xff860016 },
 
636
        { "APCBattCapBeforeStartup",            0xFF860019 }, /* FIXME: need to be exploited */
 
637
        { "APC_UPS_FirmwareRevision",           0xff860042 },
 
638
        { "APC860052",                          0xff860052 },  /* Needed for APCForceShutdown path */
 
639
        { "APCStatusFlag",                      0xff860060 },
 
640
        { "APCPanelTest",                       0xff860072 }, /* FIXME: need to be exploited */
 
641
        { "APCShutdownAfterDelay",              0xff860076 }, /* FIXME: need to be exploited */
 
642
        { "APC_USB_FirmwareRevision",           0xff860079 }, /* FIXME: need to be exploited */
 
643
        { "APCForceShutdown",                   0xff86007c },
 
644
        { "APCDelayBeforeShutdown",             0xff86007d },
 
645
        { "APCDelayBeforeStartup",              0xff86007e }, /* FIXME: need to be exploited */
 
646
        /* FIXME: The below one seems to have been wrongly encoded by APC */
 
647
        /* Pages 84 to 88 are reserved for official HID definition! */
 
648
        { "BUPHibernate",                       0x00850058 }, /* FIXME: need to be exploited */
 
649
        { "BUPBattCapBeforeStartup",            0x00860012 }, /* FIXME: need to be exploited */
 
650
        { "BUPDelayBeforeStartup",              0x00860076 }, /* FIXME: need to be exploited */
 
651
        { "BUPSelfTest",                        0x00860010 }, /* FIXME: need to be exploited */
 
652
/*
 
653
 * USB USAGE NOTES for APC (from Russell Kroll in the old hidups
 
654
 *
 
655
 * FIXME: read 0xff86.... instead of 0x(00)86....?
 
656
 *
 
657
 *  0x860013 == 44200155090 - capability again                   
 
658
 *           == locale 4, 4 choices, 2 bytes, 00, 15, 50, 90     
 
659
 *           == minimum charge to return online                  
 
660
 *
 
661
 *  0x860060 == "441HMLL" - looks like a 'capability' string     
 
662
 *           == locale 4, 4 choices, 1 byte each                 
 
663
 *           == line sensitivity (high, medium, low, low)        
 
664
 *  NOTE! the above does not seem to correspond to my info 
 
665
 *
 
666
 *  0x860062 == D43133136127130                                  
 
667
 *           == locale D, 4 choices, 3 bytes, 133, 136, 127, 130 
 
668
 *           == high transfer voltage                            
 
669
 *
 
670
 *  0x860064 == D43103100097106                                  
 
671
 *           == locale D, 4 choices, 3 bytes, 103, 100, 097, 106 
 
672
 *           == low transfer voltage                             
 
673
 *
 
674
 *  0x860066 == 441HMLL (see 860060)                                   
 
675
 *
 
676
 *  0x860074 == 4410TLN                                          
 
677
 *           == locale 4, 4 choices, 1 byte, 0, T, L, N          
 
678
 *           == alarm setting (5s, 30s, low battery, none)       
 
679
 *
 
680
 *  0x860077 == 443060180300600                                  
 
681
 *           == locale 4, 4 choices, 3 bytes, 060,180,300,600    
 
682
 *           == wake-up delay (after power returns)              
 
683
 */
 
684
 
466
685
        /* end of structure. */
467
686
        {  "\0", 0x0 }
468
687
};
469
688
 
 
689
const char *hid_lookup_path(int usage)
 
690
{
 
691
        int i;
 
692
        static char raw_usage[10];
 
693
        
 
694
        TRACE(3, "Looking up %08x", usage);
 
695
 
 
696
        for (i = 0; (usage_lkp[i].usage_name[0] != '\0'); i++)
 
697
        {
 
698
                if (usage_lkp[i].usage_code == usage)
 
699
                        return usage_lkp[i].usage_name;
 
700
        }
 
701
 
 
702
        /* if the corresponding path isn't found,
 
703
                return the numeric usage in string form */
 
704
        sprintf (&raw_usage[0], "%08x", usage); 
 
705
        return &raw_usage[0];
 
706
}
 
707
 
470
708
int hid_lookup_usage(char *name)
471
 
{       
472
 
  int i;
473
 
        
474
 
  TRACE(2, "Looking up %s", name);
475
 
        
476
 
  if (name[0] == '[') /* manage indexed collection */
477
 
    return (0x00FF0000 + atoi(&name[1]));
478
 
  else {
479
 
    for (i = 0; (usage_lkp[i].usage_code != 0x0); i++)
480
 
      {
481
 
        if (!strcmp(usage_lkp[i].usage_name, name))
482
 
          {
483
 
            TRACE(2, "hid_lookup_usage: found %04x",
484
 
                      usage_lkp[i].usage_code);
485
 
            return usage_lkp[i].usage_code;
486
 
          }
487
 
      }
488
 
  }
489
 
  return -1;
 
709
{
 
710
        int i;
 
711
        
 
712
        TRACE(3, "Looking up %s", name);
 
713
        
 
714
        if (name[0] == '[') /* manage indexed collection */
 
715
                return (0x00FF0000 + atoi(&name[1]));
 
716
        else
 
717
        {
 
718
                for (i = 0; (usage_lkp[i].usage_code != 0x0); i++)
 
719
                {
 
720
                        if (!strcmp(usage_lkp[i].usage_name, name))
 
721
                        {
 
722
                                TRACE(4, "hid_lookup_usage: found %04x",
 
723
                                        usage_lkp[i].usage_code);
 
724
        
 
725
                                return usage_lkp[i].usage_code;
 
726
                        }
 
727
                }
 
728
        }
 
729
        return -1;
490
730
}
491
731
 
492
732
#define NIBBLE(_i)    (((_i) < 10) ? '0' + (_i) : 'A' + (_i) - 10)
493
733
 
494
734
void dump_hex (const char *msg, const unsigned char *buf, int len)
495
735
{
496
 
  int i;
497
 
  int nlocal;
498
 
  const unsigned char *pc;
499
 
  char *out;
500
 
  const unsigned char *start;
501
 
  char c;
502
 
  char line[100];
503
 
  
504
 
  start = buf;
505
 
  out = line;
506
 
  
507
 
  for (i = 0, pc = buf, nlocal = len; i < 16; i++, pc++) {
508
 
    if (nlocal > 0) {
509
 
      c = *pc;
510
 
      
511
 
      *out++ = NIBBLE ((c >> 4) & 0xF);
512
 
      *out++ = NIBBLE (c & 0xF);
513
 
      
514
 
      nlocal--;
515
 
    }
516
 
    else {
517
 
      *out++ = ' ';
518
 
      *out++ = ' ';
519
 
    }                           /* end else */
520
 
    *out++ = ' ';
521
 
  }                             /* end for */
522
 
  *out++ = 0;
523
 
  
524
 
  TRACE(2, "%s: (%d bytes) => %s", msg, len, line);
525
 
  
526
 
  buf += 16;
527
 
  len -= 16;
528
 
} /* end dump */
 
736
        int i;
 
737
        int nlocal;
 
738
        const unsigned char *pc;
 
739
        char *out;
 
740
        const unsigned char *start;
 
741
        char c;
 
742
        char line[100];
 
743
 
 
744
        start = buf;
 
745
        out = line;
 
746
        
 
747
        for (i = 0, pc = buf, nlocal = len; i < 16; i++, pc++)
 
748
        {
 
749
                if (nlocal > 0)
 
750
                {
 
751
                        c = *pc;
 
752
 
 
753
                        *out++ = NIBBLE ((c >> 4) & 0xF);
 
754
                        *out++ = NIBBLE (c & 0xF);
 
755
 
 
756
                        nlocal--;
 
757
                }
 
758
                else
 
759
                {
 
760
                        *out++ = ' ';
 
761
                        *out++ = ' ';
 
762
                }
 
763
                *out++ = ' ';
 
764
        }
 
765
        *out++ = 0;
 
766
 
 
767
        TRACE(3, "%s: (%d bytes) => %s", msg, len, line);
 
768
 
 
769
        buf += 16;
 
770
        len -= 16;
 
771
}