Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

input: add import module for tektronix isf file format #186

Closed
wants to merge 20 commits into from

Conversation

filipkosecek
Copy link

No description provided.

src/input/isf.c Outdated
size_t i, channel_ix;

channel_ix = 0;
/* Isf wfid looks something like WFID "Ch1, ..."; thus we must skip character '"' */
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/* Isf wfid looks something like WFID "Ch1, ..."; thus we must skip character '"' */
/* If wfid looks something like WFID "Ch1, ..."; thus we must skip character '"' */

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for being inactive for a long time. This comment was supposed to mean that WFID follows this pattern in ISF format. So Isf was not a typing error, in case I understand your review correctly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggest writing acronyms in all-caps ?, "ISF WFID" looks a lot less accidental

@acassis
Copy link

acassis commented Jun 30, 2022

ping @filipkosecek

Copy link
Contributor

@fenugrec fenugrec left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very sparsely commented, so that without a prior knowledge of the format details, it's hard to properly review the implementation.

Instead of merging master back into this branch, you should probably be rebasing on top of master.

disclaimer : I am neither maintainer nor committer in this project.

@@ -89,6 +90,7 @@ static const struct sr_input_module *input_module_list[] = {
&input_raw_analog,
&input_logicport,
&input_saleae,
&input_isf,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indentation doesn't seem to fit with other lines here

src/input/isf.c Outdated
float ymult;
float xincr;
int bytnr;
int byte_order;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not "enum byteorder byte_order " ? you defined nice enums but are not using them here...

src/input/isf.c Outdated
{
char value[10];

find_string_value(beg, value, 9);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not a fan of hardcoding an array length like this. Either a macro for the buffer size, or using sizeof, would be an improvement IMO

src/input/isf.c Outdated
inc->channel_name[channel_ix++] = beg[i++];
}

static void find_string_value(const char *beg, char *value, size_t value_len)
Copy link
Contributor

@fenugrec fenugrec Dec 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for these helper functions, even though they're simple enough to understand, would benefit from a short comment above to describe the purpose, args, etc

return SR_OK;
}

static int format_match(GHashTable *metadata, unsigned int *confidence)
Copy link
Contributor

@fenugrec fenugrec Dec 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would help to clarify what values "confidence" can return. Higher values are better ? what is the expected range of returned values?

src/input/isf.c Outdated
}
}

if(value & (1 << (8*inc->bytnr-1))){
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unclear what this is supposed to do

src/input/isf.c Outdated

inc = in->priv;
bytnr = inc->bytnr;
memcpy(data, in->buf->str + offset, inc->bytnr);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no bounds checking on inc->bytnr vs sizeof data[]

src/input/isf.c Outdated
bytnr = inc->bytnr;
g_assert(bytnr == 4);
fp.i = 0;
memcpy(data, in->buf->str + offset, 4);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hardcoded size arg

src/input/isf.c Outdated
fdata = g_malloc0(sizeof(float) * num_samples);
for(i = 0; i < num_samples; ++i){
if(inc->bn_fmt == RI){
fdata[i] = ((float) read_int_value(in, offset) - inc->yoff) * inc->ymult + inc->yzero;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are you calling read_int_value and casting it to float ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function read_int_value returns an integer and a float value inc->yoff is then subtracted from the result. In my opinion, it should be cast to float but I may be wrong.

src/input/isf.c Outdated
for(i = 0; i < num_samples; ++i){
if(inc->bn_fmt == RI){
fdata[i] = ((float) read_int_value(in, offset) - inc->yoff) * inc->ymult + inc->yzero;
}else if(inc->bn_fmt == FP)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whenever you use braces in the first if {} clause, recommend having braces for all others. Can't remember what the kernel coding style guide says about this though

@marcows
Copy link
Contributor

marcows commented Jan 22, 2023

I'm not quite convinced of the ISF file format as it seems possible to differ dependent on the device. However, the import of my .isf example was more than twice as fast as the same data from .csv file and you don't need to configure the import.

There are a few coding style violations which happen throughout the isf.c file, affected rules are:

  • use tabs for indentation, not spaces
  • add a space between statement and opening parenthesis
  • add a space before opening brace in struct declaration
  • put the case statement to the same indentation level as its switch counterpart

As was already mentioned, the merge commit doesn't belong here.

Copy link
Contributor

@marcows marcows left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't make a full review, just focussed on file format specific stuff.

src/input/isf.c Outdated
for(i = 0; i < HEADER_ITEMS_PARAMETERS; ++i){
pattern = find_item(buf, header_items[i]);
if(pattern == NULL)
return SR_ERR_DATA;
Copy link
Contributor

@marcows marcows Jan 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return SR_ERR_DATA;
/* Ignore unknown command/header. */
continue;

With this change, the .isf from Tektronix DPO 4034 can be imported properly, before it aborted.

The .isf file begins as follows:

:WFMPRE:NR_PT 1000000;:WFMPRE:BYT_NR 2;BIT_NR 16;ENCDG BINARY;BN_FMT RI;BYT_OR MSB;WFID "Ch1, DC coupling, 1.000V/div, 400.0ms/div, 1000000 points, Sample mode";NR_PT 1000000;PT_FMT Y;XUNIT "s";XINCR 4.0000E-6;XZERO -2.0000;PT_OFF 0;YUNIT "V";YMULT 156.2500E-6;YOFF -25.6000E+3;YZERO 0.0E+0;VSCALE 1.0000;HSCALE 400.0000E-3;VPOS -4.0000;VOFFSET 0.0E+0;HDELAY 0.0E+0;:CURVE #72000000

Formatted for improved readability:

:WFMPRE:NR_PT 1000000;
:WFMPRE:BYT_NR 2;
BIT_NR 16;
ENCDG BINARY;
BN_FMT RI;
BYT_OR MSB;
WFID "Ch1, DC coupling, 1.000V/div, 400.0ms/div, 1000000 points, Sample mode";
NR_PT 1000000;
PT_FMT Y;
XUNIT "s";
XINCR 4.0000E-6;
XZERO -2.0000;
PT_OFF 0;
YUNIT "V";
YMULT 156.2500E-6;
YOFF -25.6000E+3;
YZERO 0.0E+0;
VSCALE 1.0000;
HSCALE 400.0000E-3;
VPOS -4.0000;
VOFFSET 0.0E+0;
HDELAY 0.0E+0;
:CURVE #72000000

In principle sigrok-cli generates the same output as with .csv, but there are only two instead of three digits after decimal point (but the third digit is always 0 in my case). I'm not sure if that matters:

.csv:

META samplerate: 250000
CH1: 4.880 
CH1: 4.840 
...

.isf:

META samplerate: 250000
Ch1: 4.88 
Ch1: 4.84 
...

src/input/isf.c Outdated

find_string_value(beg, value, 9);

if(strcmp(value, "BINARY") != 0){
Copy link
Contributor

@marcows marcows Jan 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In https://www.tek.com/en/support/faqs/what-format-isf-file there is the following preamble (formatted for readability):

:WFMPRE:BYT_NR 2;
BIT_NR 16;
ENCDG BIN;
BN_FMT RI;
BYT_OR MSB;
NR_PT 10000;
WFID "Ch1, DC coupling, 2.0E0 V/div, 1.0E-5 s/div, 10000 points, Sample mode";
PT_FMT Y;
XINCR 1.0E-8;
PT_OFF 0;
XZERO 3.5E-4;
XUNIT "s";
YMULT 3.125E-4;
YZERO 0.0E0;
YOFF 0.0E0;
YUNIT "V";
:CURVE #520000

Here the argument for ENCDG is BIN. I read a bit in MSO4000 and DPO4000 Series Digital Phosphor Oscilloscopes Programmer Manual and noticed some commands or keywords can be shortened. E.g. BIN/BINARY is specified as BINary, so the ary part is optional (I'm not sure if BINa or BINar would/should work).

Conclusion: It might be possible that some oscilloscope models don't use the full name.

src/input/isf.c Outdated
case BN_FMT:
if(strncmp(beg, "RI", 2) == 0)
inc->bn_fmt = RI;
else if(strncmp(beg, "FP", 2) == 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In MSO4000 and DPO4000 Series Digital Phosphor Oscilloscopes Programmer Manual there are RI (signed integer) and RP (positive integer), see WFMOutpre:BN_Fmt.

Where did you get FP (floating point)?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marcows I used Tektronix TDS5000B Series Online Programmer Manual which specifies FP (floating point).

Add comments, conform to the kernel coding style
and add bounds checking in ISF import module.
Don't require WFID in ISF header and increase minimum
amount of bytes required to process the header.
Rename members of struct context to be more descriptive.
Don't perform header parsing until "CURVE#" (indicating
data section start) is found. This approach doesn't require
minimum header size. If a large amount of bytes has been loaded
and "CURVE#" still has not been found, an error code is returned.
src/input/isf.c Outdated

find_string_value(buf, buflen, value, MAX_ENCODING_STRING_SIZE);

/* "BIN" and "BINARY" are accepted as suggested in a pull request comment. */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably not necessary to specify that the idea was from a PR comment

src/input/isf.c Outdated
channel_ix = 0;
/* ISF WFID looks something like WFID "Ch1, ..."; hence we must skip character '"' */
i = 1;
while (i < buflen && buf[i] != ',' && buf[i] != '"' && channel_ix < MAX_CHANNEL_NAME_SIZE - 1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not a fan of this : very long line, with many conditions, and the following instruction block isn't braced. Makes this hard to process if editor wraps the line due to limited width

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I braced the instruction block and wrapped the condition line.

src/input/isf.c Outdated
return NULL;

/* Curve metadata length is an ASCII byte, hence -48. */
metadata_length = (size_t) *data_ptr - 48;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you mean the length is encoded as an ascii digit '0' to '9' ? or that they simply mapped lengths 0 to (255-48) to chars 0x30 to 0xff ?

Suggest bounds checking before subtracting, in case of malformed packet.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The length is encoded as an ascii digit '0' to '9'. Fixed the comment and bounds checking is performed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of using 48 you should use '0' to make this obvious.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of using 48 you should use '0' to make this obvious.

Agreed. This is something often written if (val > '9' || val < '0') return bad; metadata_length = val - '0'

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced numeric values with character values to make it obvious.

*/
static int format_match(GHashTable *metadata, unsigned int *confidence)
{
const char default_extension[] = ".isf";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggest to do extension matching with non-case-sensitive functions (some files may end up with uppercase .ISF and then fail on linux/mac)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The extension matching is now done using a non-case-sensitive function.

bytnr = inc->bytnr;

/* Value bytnr is checked in function "receive". */
memcpy(data, in->buf->str + offset, bytnr);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bounds check on bytnr before memcpy ? not sure if that's what you mean in the comment on the previous line
[EDIT] ok, yes I see now, it is already checked. But I would probably just replace that comment with the bounds check, even if it's redundant - very inexpensive precaution and 1% easier to review

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bounds checking on bytnr is now performed.

src/input/isf.c Outdated

/* Increase the confidence if the extension is '.isf'. */
fn = g_hash_table_lookup(metadata, GINT_TO_POINTER(SR_INPUT_META_FILENAME));
if (fn != NULL && (fn_len = strlen(fn)) >= strlen(default_extension)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I didn't catch this last time but you have an assignment inside a conditional, which should be split (I think that is now mentioned in the HACKING doc)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

if (pattern == NULL) {
/* WFID is not required. */
if (i == WFID)
continue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me (.isf created by Tektronix DPO 4034) "WFMTYPE" was the problem, not "WFID", as could be determined from the .isf header I had posted:
#186 (comment)

The change in your previous commit (the removed lines above had been added there), which ignored all unknown header items (proposal from my review), worked for me. After this change it didn't work anymore.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thing is that it is pretty hard to identify which header items should be mandatory as the header slightly varies depending on the device used.

Do you think it would be better to initialize header items to default values and make them all optional?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure because I didn't had a deep look at some specifications. But I don't think there are sensible default values possible in most cases.

I couldn't find WFMTYPE in the manual linked in the comment at the beginning of isf.c file.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default option for WFMTYPE could be ANALOG value. The other options are probably rarely used since they are used for radio frequency data. The options are specified in this manual on page 2-586.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants