From 249ba03c36d6bc61b78bdd5a7f1ca55701f1c287 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sat, 10 Jul 2021 14:52:59 +0200 Subject: [PATCH] expose 2^52 amount value limit in header, check that limit in test cases, add TALER_amount_multiply and TALER_amount_divide2 operations --- debian/changelog | 6 + src/include/taler_amount_lib.h | 31 ++++ src/util/amount.c | 275 ++++++++++++++------------------- src/util/test_amount.c | 67 +++++++- 4 files changed, 213 insertions(+), 166 deletions(-) diff --git a/debian/changelog b/debian/changelog index 74922b011..b827c937e 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +taler-exchange (0.9.0-12) unstable; urgency=low + + * Fix typo in taler-auditor shell script: clean before building. + + -- Christian Grothoff Mon, 28 Jun 2021 14:02:10 +0100 + taler-exchange (0.9.0-11) unstable; urgency=low * Fix typo in taler-auditor-sync. diff --git a/src/include/taler_amount_lib.h b/src/include/taler_amount_lib.h index 7a0b299c4..5defc37eb 100644 --- a/src/include/taler_amount_lib.h +++ b/src/include/taler_amount_lib.h @@ -64,6 +64,11 @@ extern "C" */ #define TALER_AMOUNT_FRAC_LEN 8 +/** + * Maximum legal 'value' for an amount, based on IEEE double (for JavaScript compatibility). + */ +#define TALER_AMOUNT_MAX_VALUE (1LLU << 52) + GNUNET_NETWORK_STRUCT_BEGIN @@ -332,6 +337,32 @@ TALER_amount_divide (struct TALER_Amount *result, const struct TALER_Amount *dividend, uint32_t divisor); +/** + * Divide one amount by another. Note that this function + * may introduce a rounding error. It rounds down. + * + * @param dividend amount to divide + * @param divisor by what to divide, must be positive + * @return @a dividend / @a divisor, rounded down. -1 on currency missmatch, + * INT_MAX for division by zero + */ +int +TALER_amount_divide2 (const struct TALER_Amount *dividend, + const struct TALER_Amount *divisor); + + +/** + * Multiply an @a amount by a @ factor. + * + * @param[out] result where to store @a amount * @a factor + * @param amount amount to multiply + * @param factor factor by which to multiply + */ +enum TALER_AmountArithmeticResult +TALER_amount_multiply (struct TALER_Amount *result, + const struct TALER_Amount *amount, + uint32_t factor); + /** * Normalize the given amount. diff --git a/src/util/amount.c b/src/util/amount.c index 3aec54616..9012788f6 100644 --- a/src/util/amount.c +++ b/src/util/amount.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014 Taler Systems SA + Copyright (C) 2014-2021 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -24,11 +24,6 @@ #include "platform.h" #include "taler_util.h" -/** - * Maximum legal 'value' for an amount, based on IEEE double (for JavaScript compatibility). - */ -#define MAX_AMOUNT_VALUE (1LLU << 52) - /** * Set @a a to "invalid". @@ -44,14 +39,6 @@ invalidate (struct TALER_Amount *a) } -/** - * Parse monetary amount, in the format "T:V.F". - * - * @param str amount string - * @param[out] amount amount to write the result to - * @return #GNUNET_OK if the string is a valid monetary amount specification, - * #GNUNET_SYSERR if it is invalid. - */ enum GNUNET_GenericReturnValue TALER_string_to_amount (const char *str, struct TALER_Amount *amount) @@ -128,8 +115,8 @@ TALER_string_to_amount (const char *str, n = *value - '0'; if ( (amount->value * 10 < amount->value) || (amount->value * 10 + n < amount->value) || - (amount->value > MAX_AMOUNT_VALUE) || - (amount->value * 10 + n > MAX_AMOUNT_VALUE) ) + (amount->value > TALER_AMOUNT_MAX_VALUE) || + (amount->value * 10 + n > TALER_AMOUNT_MAX_VALUE) ) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Value specified in amount `%s' is too large\n", @@ -182,15 +169,6 @@ TALER_string_to_amount (const char *str, } -/** - * Parse monetary amount, in the format "T:V.F". - * The result is stored in network byte order (NBO). - * - * @param str amount string - * @param[out] amount_nbo amount to write the result to - * @return #GNUNET_OK if the string is a valid amount specification, - * #GNUNET_SYSERR if it is invalid. - */ enum GNUNET_GenericReturnValue TALER_string_to_amount_nbo (const char *str, struct TALER_AmountNBO *amount_nbo) @@ -207,12 +185,6 @@ TALER_string_to_amount_nbo (const char *str, } -/** - * Convert amount from host to network representation. - * - * @param res where to store amount in network representation - * @param[out] d amount in host representation - */ void TALER_amount_hton (struct TALER_AmountNBO *res, const struct TALER_Amount *d) @@ -227,12 +199,6 @@ TALER_amount_hton (struct TALER_AmountNBO *res, } -/** - * Convert amount from network to host representation. - * - * @param[out] res where to store amount in host representation - * @param dn amount in network representation - */ void TALER_amount_ntoh (struct TALER_Amount *res, const struct TALER_AmountNBO *dn) @@ -247,14 +213,6 @@ TALER_amount_ntoh (struct TALER_Amount *res, } -/** - * Get the value of "zero" in a particular currency. - * - * @param cur currency description - * @param[out] amount amount to write the result to - * @return #GNUNET_OK if @a cur is a valid currency specification, - * #GNUNET_SYSERR if it is invalid. - */ enum GNUNET_GenericReturnValue TALER_amount_get_zero (const char *cur, struct TALER_Amount *amount) @@ -274,15 +232,11 @@ TALER_amount_get_zero (const char *cur, } -/** - * Test if the given amount is valid. - * - * @param amount amount to check - * @return #GNUNET_OK if @a amount is valid - */ enum GNUNET_GenericReturnValue TALER_amount_is_valid (const struct TALER_Amount *amount) { + if (amount->value > TALER_AMOUNT_MAX_VALUE) + return GNUNET_SYSERR; return ('\0' != amount->currency[0]) ? GNUNET_OK : GNUNET_NO; } @@ -301,15 +255,6 @@ test_valid_nbo (const struct TALER_AmountNBO *a) } -/** - * Test if @a a1 and @a a2 are the same currency. - * - * @param a1 amount to test - * @param a2 amount to test - * @return #GNUNET_YES if @a a1 and @a a2 are the same currency - * #GNUNET_NO if the currencies are different, - * #GNUNET_SYSERR if either amount is invalid - */ enum GNUNET_GenericReturnValue TALER_amount_cmp_currency (const struct TALER_Amount *a1, const struct TALER_Amount *a2) @@ -324,15 +269,6 @@ TALER_amount_cmp_currency (const struct TALER_Amount *a1, } -/** - * Test if @a a1 and @a a2 are the same currency, NBO variant. - * - * @param a1 amount to test - * @param a2 amount to test - * @return #GNUNET_YES if @a a1 and @a a2 are the same currency - * #GNUNET_NO if the currencies are different, - * #GNUNET_SYSERR if either amount is invalid - */ enum GNUNET_GenericReturnValue TALER_amount_cmp_currency_nbo (const struct TALER_AmountNBO *a1, const struct TALER_AmountNBO *a2) @@ -347,19 +283,6 @@ TALER_amount_cmp_currency_nbo (const struct TALER_AmountNBO *a1, } -/** - * Compare the value/fraction of two amounts. Does not compare the currency. - * Comparing amounts of different currencies will cause the program to abort(). - * If unsure, check with #TALER_amount_cmp_currency() first to be sure that - * the currencies of the two amounts are identical. - * - * @param a1 first amount - * @param a2 second amount - * @return result of the comparison, - * -1 if `a1 < a2` - * 1 if `a1 > a2` - * 0 if `a1 == a2`. - */ int TALER_amount_cmp (const struct TALER_Amount *a1, const struct TALER_Amount *a2) @@ -390,19 +313,6 @@ TALER_amount_cmp (const struct TALER_Amount *a1, } -/** - * Compare the value/fraction of two amounts. Does not compare the currency. - * Comparing amounts of different currencies will cause the program to abort(). - * If unsure, check with #TALER_amount_cmp_currency() first to be sure that - * the currencies of the two amounts are identical. NBO variant. - * - * @param a1 first amount - * @param a2 second amount - * @return result of the comparison - * -1 if `a1 < a2` - * 1 if `a1 > a2` - * 0 if `a1 == a2`. - */ int TALER_amount_cmp_nbo (const struct TALER_AmountNBO *a1, const struct TALER_AmountNBO *a2) @@ -419,14 +329,6 @@ TALER_amount_cmp_nbo (const struct TALER_AmountNBO *a1, } -/** - * Perform saturating subtraction of amounts. - * - * @param[out] diff where to store (@a a1 - @a a2), or invalid if @a a2 > @a a1 - * @param a1 amount to subtract from - * @param a2 amount to subtract - * @return operation status, negative on failures - */ enum TALER_AmountArithmeticResult TALER_amount_subtract (struct TALER_Amount *diff, const struct TALER_Amount *a1, @@ -482,14 +384,6 @@ TALER_amount_subtract (struct TALER_Amount *diff, } -/** - * Perform addition of amounts. - * - * @param[out] sum where to store @a a1 + @a a2, set to "invalid" on overflow - * @param a1 first amount to add - * @param a2 second amount to add - * @return operation status, negative on failures - */ enum TALER_AmountArithmeticResult TALER_amount_add (struct TALER_Amount *sum, const struct TALER_Amount *a1, @@ -500,7 +394,8 @@ TALER_amount_add (struct TALER_Amount *sum, struct TALER_Amount res; if (GNUNET_YES != - TALER_amount_cmp_currency (a1, a2)) + TALER_amount_cmp_currency (a1, + a2)) { invalidate (sum); return TALER_AAR_INVALID_CURRENCIES_INCOMPATIBLE; @@ -509,8 +404,10 @@ TALER_amount_add (struct TALER_Amount *sum, diff and a1/a2 */ n1 = *a1; n2 = *a2; - if ( (GNUNET_SYSERR == TALER_amount_normalize (&n1)) || - (GNUNET_SYSERR == TALER_amount_normalize (&n2)) ) + if ( (GNUNET_SYSERR == + TALER_amount_normalize (&n1)) || + (GNUNET_SYSERR == + TALER_amount_normalize (&n2)) ) { invalidate (sum); return TALER_AAR_INVALID_NORMALIZATION_FAILED; @@ -526,7 +423,7 @@ TALER_amount_add (struct TALER_Amount *sum, invalidate (sum); return TALER_AAR_INVALID_RESULT_OVERFLOW; } - if (res.value > MAX_AMOUNT_VALUE) + if (res.value > TALER_AMOUNT_MAX_VALUE) { /* too large to be legal */ invalidate (sum); @@ -548,14 +445,6 @@ TALER_amount_add (struct TALER_Amount *sum, } -/** - * Normalize the given amount. - * - * @param[in,out] amount amount to normalize - * @return #GNUNET_OK if normalization worked - * #GNUNET_NO if value was already normalized - * #GNUNET_SYSERR if value was invalid or could not be normalized - */ enum GNUNET_GenericReturnValue TALER_amount_normalize (struct TALER_Amount *amount) { @@ -569,7 +458,7 @@ TALER_amount_normalize (struct TALER_Amount *amount) amount->fraction %= TALER_AMOUNT_FRAC_BASE; amount->value += overflow; if ( (amount->value < overflow) || - (amount->value > MAX_AMOUNT_VALUE) ) + (amount->value > TALER_AMOUNT_MAX_VALUE) ) { invalidate (amount); return GNUNET_SYSERR; @@ -600,12 +489,6 @@ amount_to_tail (const struct TALER_Amount *amount, } -/** - * Convert amount to string. - * - * @param amount amount to convert to string - * @return freshly allocated string representation - */ char * TALER_amount_to_string (const struct TALER_Amount *amount) { @@ -640,13 +523,6 @@ TALER_amount_to_string (const struct TALER_Amount *amount) } -/** - * Convert amount to string. - * - * @param amount amount to convert to string - * @return statically allocated buffer with string representation, - * NULL if the @a amount was invalid - */ const char * TALER_amount2s (const struct TALER_Amount *amount) { @@ -685,14 +561,6 @@ TALER_amount2s (const struct TALER_Amount *amount) } -/** - * Divide an amount by a @a divisor. Note that this function - * may introduce a rounding error! - * - * @param[out] result where to store @a dividend / @a divisor - * @param dividend amount to divide - * @param divisor by what to divide, must be positive - */ void TALER_amount_divide (struct TALER_Amount *result, const struct TALER_Amount *dividend, @@ -718,20 +586,109 @@ TALER_amount_divide (struct TALER_Amount *result, } -/** - * Round the amount to something that can be transferred on the wire. - * The rounding mode is specified via the smallest transferable unit, - * which must only have a fractional part *or* only a value (either - * of the two must be zero!). - * - * If the @a round_unit given is zero, we do nothing and return #GNUNET_NO. - * - * @param[in,out] amount amount to round down - * @param[in] round_unit unit that should be rounded down to, and - * either value part or the faction must be zero - * @return #GNUNET_OK on success, #GNUNET_NO if rounding was unnecessary, - * #GNUNET_SYSERR if the amount or currency or @a round_unit was invalid - */ +int +TALER_amount_divide2 (const struct TALER_Amount *dividend, + const struct TALER_Amount *divisor) +{ + double approx; + double d; + double r; + int ret; + struct TALER_Amount tmp; + struct TALER_Amount nxt; + + if (GNUNET_YES != + TALER_amount_cmp_currency (dividend, + divisor)) + { + GNUNET_break (0); + return -1; + } + if ( (0 == divisor->fraction) && + (0 == divisor->value) ) + return INT_MAX; + /* first, get rounded approximation */ + d = ((double) dividend->value) * ((double) TALER_AMOUNT_FRAC_BASE) + + ( (double) dividend->fraction); + r = ((double) divisor->value) * ((double) TALER_AMOUNT_FRAC_BASE) + + ( (double) divisor->fraction); + approx = d / r; + if (approx > ((double) INT_MAX)) + return INT_MAX; /* 'infinity' */ + /* round down */ + if (approx < 2) + ret = 0; + else + ret = (int) approx - 2; + /* Now do *exact* calculation, using well rounded-down factor as starting + point to avoid having to do too many steps. */ + GNUNET_assert (0 <= + TALER_amount_multiply (&tmp, + divisor, + ret)); + /* in practice, this loop will only run for one or two iterations */ + while (1) + { + GNUNET_assert (0 <= + TALER_amount_add (&nxt, + &tmp, + divisor)); + if (1 == + TALER_amount_cmp (&nxt, + dividend)) + break; /* nxt > dividend */ + ret++; + tmp = nxt; + } + return ret; +} + + +enum TALER_AmountArithmeticResult +TALER_amount_multiply (struct TALER_Amount *result, + const struct TALER_Amount *amount, + uint32_t factor) +{ + struct TALER_Amount in = *amount; + + if (GNUNET_SYSERR == + TALER_amount_normalize (&in)) + return TALER_AAR_INVALID_NORMALIZATION_FAILED; + memcpy (result->currency, + amount->currency, + TALER_CURRENCY_LEN); + if ( (0 == factor) || + ( (0 == in.value) && + (0 == in.fraction) ) ) + { + result->value = 0; + result->fraction = 0; + return TALER_AAR_RESULT_ZERO; + } + result->value = in.value * ((uint64_t) factor); + if (in.value != result->value / factor) + return TALER_AAR_INVALID_RESULT_OVERFLOW; + { + /* This multiplication cannot overflow since both inputs are 32-bit values */ + uint64_t tmp = ((uint64_t) factor) * ((uint64_t) in.fraction); + uint64_t res; + + res = tmp / TALER_AMOUNT_FRAC_BASE; + /* check for overflow */ + if (result->value + res < result->value) + return TALER_AAR_INVALID_RESULT_OVERFLOW; + result->value += res; + result->fraction = tmp % TALER_AMOUNT_FRAC_BASE; + } + if (result->value > TALER_AMOUNT_MAX_VALUE) + return TALER_AAR_INVALID_RESULT_OVERFLOW; + /* This check should be redundant... */ + GNUNET_assert (GNUNET_SYSERR != + TALER_amount_normalize (result)); + return TALER_AAR_RESULT_POSITIVE; +} + + enum GNUNET_GenericReturnValue TALER_amount_round_down (struct TALER_Amount *amount, const struct TALER_Amount *round_unit) diff --git a/src/util/test_amount.c b/src/util/test_amount.c index 8a83e4cf6..1af383dcc 100644 --- a/src/util/test_amount.c +++ b/src/util/test_amount.c @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2015 Taler Systems SA + (C) 2015, 2021 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -186,21 +186,27 @@ main (int argc, /* test addition with overflow */ a1.fraction = TALER_AMOUNT_FRAC_BASE - 1; - a1.value = UINT64_MAX - 5; + a1.value = TALER_AMOUNT_MAX_VALUE - 5; a2.fraction = 2; a2.value = 5; GNUNET_assert (TALER_AAR_INVALID_RESULT_OVERFLOW == - TALER_amount_add (&a3, &a1, &a2)); + TALER_amount_add (&a3, + &a1, + &a2)); /* test addition with underflow on fraction */ a1.fraction = 1; - a1.value = UINT64_MAX; + a1.value = TALER_AMOUNT_MAX_VALUE; a2.fraction = 2; a2.value = 0; GNUNET_assert (TALER_AAR_RESULT_POSITIVE == - TALER_amount_subtract (&a3, &a1, &a2)); - GNUNET_assert (UINT64_MAX - 1 == a3.value); - GNUNET_assert (TALER_AMOUNT_FRAC_BASE - 1 == a3.fraction); + TALER_amount_subtract (&a3, + &a1, + &a2)); + GNUNET_assert (TALER_AMOUNT_MAX_VALUE - 1 == + a3.value); + GNUNET_assert (TALER_AMOUNT_FRAC_BASE - 1 == + a3.fraction); /* test division */ GNUNET_assert (GNUNET_OK == @@ -288,6 +294,53 @@ main (int argc, &r)); GNUNET_assert (0 == TALER_amount_cmp (&a1, &a2)); + + /* test multiplication */ + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount ("BTC:0", + &a1)); + GNUNET_assert (TALER_AAR_RESULT_ZERO == + TALER_amount_multiply (&a2, + &a1, + 42)); + GNUNET_assert (0 == TALER_amount_cmp (&a1, + &a2)); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount ("BTC:5.001", + &a1)); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount ("BTC:5001", + &r)); + GNUNET_assert (TALER_AAR_RESULT_POSITIVE == + TALER_amount_multiply (&a2, + &a1, + 1000)); + GNUNET_assert (0 == TALER_amount_cmp (&r, + &a2)); + GNUNET_assert (1000 == + TALER_amount_divide2 (&a2, + &a1)); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount ("BTC:5006.00099999", + &r)); + GNUNET_assert (1000 == + TALER_amount_divide2 (&r, + &a1)); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount ("BTC:5000.99999999", + &r)); + GNUNET_assert (999 == + TALER_amount_divide2 (&r, + &a1)); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount ("BTC:0", + &a1)); + GNUNET_assert (INT_MAX == + TALER_amount_divide2 (&a2, + &a1)); + GNUNET_assert (0 == + TALER_amount_divide2 (&a1, + &a2)); return 0; }