← Back to context

Comment by lifthrasiir

5 months ago

> I have 2 options: - link GMP/mpdecimal/whatever (or hey, provide an abstraction layer and let a user choose) - accept function pointers to handle bignum tags

I would just provide two kinds of functions:

    // For each representative native type...
    cbor_read_t cbor_read_float(struct cbor *ctx, float *f);

    // And there is a generic number handling:
    struct cbor_num {
        int sign; // -1, 0 or 1
        int base; // 10 or 16
        int exponent;
        const char *digits;
        size_t digits_len;
    };
    cbor_read_t cbor_read_number(struct cbor *ctx, struct cbor_num *num);

    // And then someone will define the following on top of cbor_read_number:
    cbor_read_t my_cbor_read_mpz(struct cbor *ctx, mpz_t num);

Memory lifetime and similar has to be also considered here (left as an exercise), but the point is that you never need function pointers in this case. In fact I would actively avoid them because proper function pointer support is indeed a PITA as you said. They can generally be avoided with the (sorta) inversion of control, which is popular in compact C APIs and to some extent also in Rust APIs. It is just you haven't thought of this possibility.

> sscanf (also do not use sscanf) doesn't work if the value is actually a bignum, and yielding a bespoke bignum format is just as unusable as simply returning whatever's encoded in CBOR. How would I add two such values together? How would I display it? This is what bignum libraries are for.

In practice many bignums are just left as is. For example X.509 certificate serial numbers are technically bignums, but you never compute anything out of them. So you don't need any bignum to read serial numbers. If you do need computation then you need an adapter function as above, but the library proper needs no knowledge about such adapter. What's a problem now?

By the way, sscanf is fine here because the API's contract constrains sscanf's inputs enough to be safe. Sscanf in general is also safe when every `char*` outputs are bounded. It is certainly a difficult beast, but so is everything about C.

This isn't responsive to what I wrote:

> and yielding a bespoke bignum format is just as unusable as simply returning whatever's encoded in CBOR. How would I add two such values together? How would I display it? This is what bignum libraries are for.

I know this is what you've been getting at. Maybe I've been unclear about why this isn't useful, but here are the main points:

- Without bignum functionality, your data structure doesn't provide any more functionality than memcpy. How do I apply the base? How do I apply the exponent? How would I add two of them together? This may as well just be a `char *`.

- Speaking of just being a `char *`, CBOR's bignums are just that, so you'd just call `mpz_init_set_str` on whatever is in the buffer (zero terminate it in a different buffer, I guess, whatever). Parsing into your struct here is counterproductive.

- Even the minimal functionality you're proposing here is added bloat to every application that doesn't care about bignums and wants to ignore the tag (probably almost all applications). Ameliorating this requires conditional compilation.

> In practice many bignums are just left as is.

I'd believe this; I'd also believe there's very little real need for them generally. This is an argument for not including them in a data serialization format.

> By the way, sscanf is fine here

The problem with sscanf isn't that it can never be safe, it's that if you're not safe every time you blow everything up. It's better to just not use it.