Browse code

options: Introduce atoi_constrained and review usages of atoi_warn

This is a more powerful version of atoi_warn that can
- check minimum and maximum values
- report error seperately from parsed value

This can be used to simplify a lot of option parsing.

Change-Id: Ibc7526d59c1de17a0f9d8ed88f75c6f070ab11e7
Signed-off-by: Frank Lichtenheld <frank@lichtenheld.com>
Acked-by: Arne Schwabe <arne-openvpn@rfc2549.org>
Message-Id: <20250902144657.11854-1-gert@greenie.muc.de>
URL: https://sourceforge.net/p/openvpn/mailman/message/59228172/
Signed-off-by: Gert Doering <gert@greenie.muc.de>

Frank Lichtenheld authored on 2025/09/02 23:46:50
Showing 4 changed files
... ...
@@ -6411,16 +6411,12 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file,
6411 6411
     }
6412 6412
     else if (streq(p[0], "management-log-cache") && p[1] && !p[2])
6413 6413
     {
6414
-        int cache;
6415
-
6416 6414
         VERIFY_PERMISSION(OPT_P_GENERAL);
6417
-        cache = atoi_warn(p[1], msglevel);
6418
-        if (cache < 1)
6415
+        if (!atoi_constrained(p[1], &options->management_log_history_cache,
6416
+                              p[0], 1, INT_MAX, msglevel))
6419 6417
         {
6420
-            msg(msglevel, "--management-log-cache parameter is out of range");
6421 6418
             goto err;
6422 6419
         }
6423
-        options->management_log_history_cache = cache;
6424 6420
     }
6425 6421
 #endif /* ifdef ENABLE_MANAGEMENT */
6426 6422
 #ifdef ENABLE_PLUGIN
... ...
@@ -6969,16 +6965,11 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file,
6969 6969
     }
6970 6970
     else if (streq(p[0], "status-version") && p[1] && !p[2])
6971 6971
     {
6972
-        int version;
6973
-
6974 6972
         VERIFY_PERMISSION(OPT_P_GENERAL);
6975
-        version = atoi_warn(p[1], msglevel);
6976
-        if (version < 1 || version > 3)
6973
+        if (!atoi_constrained(p[1], &options->status_file_version, p[0], 1, 3, msglevel))
6977 6974
         {
6978
-            msg(msglevel, "--status-version must be 1 to 3");
6979 6975
             goto err;
6980 6976
         }
6981
-        options->status_file_version = version;
6982 6977
     }
6983 6978
     else if (streq(p[0], "remap-usr1") && p[1] && !p[2])
6984 6979
     {
... ...
@@ -7151,16 +7142,11 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file,
7151 7151
     }
7152 7152
     else if (streq(p[0], "shaper") && p[1] && !p[2])
7153 7153
     {
7154
-        int shaper;
7155
-
7156 7154
         VERIFY_PERMISSION(OPT_P_SHAPER);
7157
-        shaper = atoi_warn(p[1], msglevel);
7158
-        if (shaper < SHAPER_MIN || shaper > SHAPER_MAX)
7155
+        if (!atoi_constrained(p[1], &options->shaper, p[0], SHAPER_MIN, SHAPER_MAX, msglevel))
7159 7156
         {
7160
-            msg(msglevel, "Bad shaper value, must be between %d and %d", SHAPER_MIN, SHAPER_MAX);
7161 7157
             goto err;
7162 7158
         }
7163
-        options->shaper = shaper;
7164 7159
     }
7165 7160
     else if (streq(p[0], "port") && p[1] && !p[2])
7166 7161
     {
... ...
@@ -7739,7 +7725,11 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file,
7739 7739
     else if (streq(p[0], "script-security") && p[1] && !p[2])
7740 7740
     {
7741 7741
         VERIFY_PERMISSION(OPT_P_GENERAL);
7742
-        script_security_set(atoi_warn(p[1], msglevel));
7742
+        int security;
7743
+        if (atoi_constrained(p[1], &security, p[0], SSEC_NONE, SSEC_PW_ENV, msglevel))
7744
+        {
7745
+            script_security_set(security);
7746
+        }
7743 7747
     }
7744 7748
     else if (streq(p[0], "mssfix") && !p[3])
7745 7749
     {
... ...
@@ -7959,11 +7949,9 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file,
7959 7959
         int real, virtual;
7960 7960
 
7961 7961
         VERIFY_PERMISSION(OPT_P_GENERAL);
7962
-        real = atoi_warn(p[1], msglevel);
7963
-        virtual = atoi_warn(p[2], msglevel);
7964
-        if (real < 1 || virtual < 1)
7962
+        if (!atoi_constrained(p[1], &real, "hash-size real", 1, INT_MAX, msglevel)
7963
+            || !atoi_constrained(p[2], &virtual, "hash-size virtual", 1, INT_MAX, msglevel))
7965 7964
         {
7966
-            msg(msglevel, "--hash-size sizes must be >= 1 (preferably a power of 2)");
7967 7965
             goto err;
7968 7966
         }
7969 7967
         options->real_hash_size = real;
... ...
@@ -7974,11 +7962,9 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file,
7974 7974
         int cf_max, cf_per;
7975 7975
 
7976 7976
         VERIFY_PERMISSION(OPT_P_GENERAL);
7977
-        cf_max = atoi_warn(p[1], msglevel);
7978
-        cf_per = atoi_warn(p[2], msglevel);
7979
-        if (cf_max < 0 || cf_per < 0)
7977
+        if (!atoi_constrained(p[1], &cf_max, "connect-freq n", 1, INT_MAX, msglevel)
7978
+            || !atoi_constrained(p[2], &cf_per, "connect-freq seconds", 1, INT_MAX, msglevel))
7980 7979
         {
7981
-            msg(msglevel, "--connect-freq parms must be > 0");
7982 7980
             goto err;
7983 7981
         }
7984 7982
         options->cf_max = cf_max;
... ...
@@ -7986,15 +7972,12 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file,
7986 7986
     }
7987 7987
     else if (streq(p[0], "connect-freq-initial") && p[1] && p[2] && !p[3])
7988 7988
     {
7989
-        long cf_max, cf_per;
7989
+        int cf_max, cf_per;
7990 7990
 
7991 7991
         VERIFY_PERMISSION(OPT_P_GENERAL);
7992
-        char *e1, *e2;
7993
-        cf_max = strtol(p[1], &e1, 10);
7994
-        cf_per = strtol(p[2], &e2, 10);
7995
-        if (cf_max < 0 || cf_per < 0 || *e1 != '\0' || *e2 != '\0')
7992
+        if (!atoi_constrained(p[1], &cf_max, "connect-freq-initial n", 1, INT_MAX, msglevel)
7993
+            || !atoi_constrained(p[2], &cf_per, "connect-freq-initial seconds", 1, INT_MAX, msglevel))
7996 7994
         {
7997
-            msg(msglevel, "--connect-freq-initial parameters must be integers and >= 0");
7998 7995
             goto err;
7999 7996
         }
8000 7997
         options->cf_initial_max = cf_max;
... ...
@@ -8002,21 +7985,11 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file,
8002 8002
     }
8003 8003
     else if (streq(p[0], "max-clients") && p[1] && !p[2])
8004 8004
     {
8005
-        int max_clients;
8006
-
8007 8005
         VERIFY_PERMISSION(OPT_P_GENERAL);
8008
-        max_clients = atoi_warn(p[1], msglevel);
8009
-        if (max_clients < 0)
8006
+        if (!atoi_constrained(p[1], &options->max_clients, p[0], 1, MAX_PEER_ID, msglevel))
8010 8007
         {
8011
-            msg(msglevel, "--max-clients must be at least 1");
8012 8008
             goto err;
8013 8009
         }
8014
-        if (max_clients >= MAX_PEER_ID) /* max peer-id value */
8015
-        {
8016
-            msg(msglevel, "--max-clients must be less than %d", MAX_PEER_ID);
8017
-            goto err;
8018
-        }
8019
-        options->max_clients = max_clients;
8020 8010
     }
8021 8011
     else if (streq(p[0], "max-routes-per-client") && p[1] && !p[2])
8022 8012
     {
... ...
@@ -8188,27 +8161,13 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file,
8188 8188
     }
8189 8189
     else if (streq(p[0], "bcast-buffers") && p[1] && !p[2])
8190 8190
     {
8191
-        int n_bcast_buf;
8192
-
8193 8191
         VERIFY_PERMISSION(OPT_P_GENERAL);
8194
-        n_bcast_buf = atoi_warn(p[1], msglevel);
8195
-        if (n_bcast_buf < 1)
8196
-        {
8197
-            msg(msglevel, "--bcast-buffers parameter must be > 0");
8198
-        }
8199
-        options->n_bcast_buf = n_bcast_buf;
8192
+        atoi_constrained(p[1], &options->n_bcast_buf, p[0], 1, INT_MAX, msglevel);
8200 8193
     }
8201 8194
     else if (streq(p[0], "tcp-queue-limit") && p[1] && !p[2])
8202 8195
     {
8203
-        int tcp_queue_limit;
8204
-
8205 8196
         VERIFY_PERMISSION(OPT_P_GENERAL);
8206
-        tcp_queue_limit = atoi_warn(p[1], msglevel);
8207
-        if (tcp_queue_limit < 1)
8208
-        {
8209
-            msg(msglevel, "--tcp-queue-limit parameter must be > 0");
8210
-        }
8211
-        options->tcp_queue_limit = tcp_queue_limit;
8197
+        atoi_constrained(p[1], &options->tcp_queue_limit, p[0], 1, INT_MAX, msglevel);
8212 8198
     }
8213 8199
 #if PORT_SHARE
8214 8200
     else if (streq(p[0], "port-share") && p[1] && p[2] && !p[4])
... ...
@@ -8354,21 +8313,24 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file,
8354 8354
         int ageing_time, check_interval;
8355 8355
 
8356 8356
         VERIFY_PERMISSION(OPT_P_GENERAL);
8357
-        ageing_time = atoi_warn(p[1], msglevel);
8357
+        if (!atoi_constrained(p[1], &ageing_time, "stale-routes-check age", 1, INT_MAX, msglevel))
8358
+        {
8359
+            goto err;
8360
+        }
8361
+
8358 8362
         if (p[2])
8359 8363
         {
8360
-            check_interval = atoi_warn(p[2], msglevel);
8364
+            if (!atoi_constrained(p[2], &check_interval,
8365
+                                  "stale-routes-check interval", 1, INT_MAX, msglevel))
8366
+            {
8367
+                goto err;
8368
+            }
8361 8369
         }
8362 8370
         else
8363 8371
         {
8364 8372
             check_interval = ageing_time;
8365 8373
         }
8366 8374
 
8367
-        if (ageing_time < 1 || check_interval < 1)
8368
-        {
8369
-            msg(msglevel, "--stale-routes-check aging time and check interval must be >= 1");
8370
-            goto err;
8371
-        }
8372 8375
         options->stale_routes_ageing_time = ageing_time;
8373 8376
         options->stale_routes_check_interval = check_interval;
8374 8377
     }
... ...
@@ -8386,7 +8348,7 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file,
8386 8386
     else if (streq(p[0], "push-continuation") && p[1] && !p[2])
8387 8387
     {
8388 8388
         VERIFY_PERMISSION(OPT_P_PULL_MODE);
8389
-        options->push_continuation = atoi_warn(p[1], msglevel);
8389
+        atoi_constrained(p[1], &options->push_continuation, p[0], 0, 2, msglevel);
8390 8390
     }
8391 8391
     else if (streq(p[0], "auth-user-pass") && !p[2])
8392 8392
     {
... ...
@@ -8505,33 +8467,23 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file,
8505 8505
             {
8506 8506
                 if (!streq(p[2], "default"))
8507 8507
                 {
8508
-                    int offset = atoi_warn(p[2], msglevel);
8508
+                    int offset;
8509 8509
 
8510
-                    if (!(offset > -256 && offset < 256))
8510
+                    if (!atoi_constrained(p[2], &offset, "ip-win32 offset", -256, 256, msglevel))
8511 8511
                     {
8512
-                        msg(msglevel,
8513
-                            "--ip-win32 dynamic [offset] [lease-time]: offset (%d) must be > -256 and < 256",
8514
-                            offset);
8515 8512
                         goto err;
8516 8513
                     }
8517
-
8518 8514
                     to->dhcp_masq_custom_offset = true;
8519 8515
                     to->dhcp_masq_offset = offset;
8520 8516
                 }
8521 8517
 
8522 8518
                 if (p[3])
8523 8519
                 {
8524
-                    const int min_lease = 30;
8525
-                    int lease_time;
8526
-                    lease_time = atoi_warn(p[3], msglevel);
8527
-                    if (lease_time < min_lease)
8520
+                    if (!atoi_constrained(p[3], &to->dhcp_lease_time,
8521
+                                          "ip-win32 lease time", 30, INT_MAX, msglevel))
8528 8522
                     {
8529
-                        msg(msglevel,
8530
-                            "--ip-win32 dynamic [offset] [lease-time]: lease time parameter (%d) must be at least %d seconds",
8531
-                            lease_time, min_lease);
8532 8523
                         goto err;
8533 8524
                     }
8534
-                    to->dhcp_lease_time = lease_time;
8535 8525
                 }
8536 8526
             }
8537 8527
         }
... ...
@@ -8629,8 +8581,7 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file,
8629 8629
         }
8630 8630
         else if (streq(p[1], "NBT") && p[2] && !p[3])
8631 8631
         {
8632
-            int t;
8633
-            t = atoi_warn(p[2], msglevel);
8632
+            int t = atoi_warn(p[2], msglevel);
8634 8633
             if (!(t == 1 || t == 2 || t == 4 || t == 8))
8635 8634
             {
8636 8635
                 msg(msglevel, "--dhcp-option NBT: parameter (%d) must be 1, 2, 4, or 8", t);
... ...
@@ -8704,15 +8655,11 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file,
8704 8704
     }
8705 8705
     else if (streq(p[0], "tap-sleep") && p[1] && !p[2])
8706 8706
     {
8707
-        int s;
8708 8707
         VERIFY_PERMISSION(OPT_P_DHCPDNS);
8709
-        s = atoi_warn(p[1], msglevel);
8710
-        if (s < 0 || s >= 256)
8708
+        if (!atoi_constrained(p[1], &options->tuntap_options.tap_sleep, p[0], 0, 255, msglevel))
8711 8709
         {
8712
-            msg(msglevel, "--tap-sleep parameter must be between 0 and 255");
8713 8710
             goto err;
8714 8711
         }
8715
-        options->tuntap_options.tap_sleep = s;
8716 8712
     }
8717 8713
     else if (streq(p[0], "dhcp-renew") && !p[1])
8718 8714
     {
... ...
@@ -9152,30 +9099,19 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file,
9152 9152
         VERIFY_PERMISSION(OPT_P_GENERAL);
9153 9153
         if (p[1])
9154 9154
         {
9155
-            int replay_window;
9156
-
9157
-            replay_window = atoi_warn(p[1], msglevel);
9158
-            if (!(MIN_SEQ_BACKTRACK <= replay_window && replay_window <= MAX_SEQ_BACKTRACK))
9155
+            if (!atoi_constrained(p[1], &options->replay_window, "replay-window windows size",
9156
+                                  MIN_SEQ_BACKTRACK, MAX_SEQ_BACKTRACK, msglevel))
9159 9157
             {
9160
-                msg(msglevel, "replay-window window size parameter (%d) must be between %d and %d",
9161
-                    replay_window, MIN_SEQ_BACKTRACK, MAX_SEQ_BACKTRACK);
9162 9158
                 goto err;
9163 9159
             }
9164
-            options->replay_window = replay_window;
9165 9160
 
9166 9161
             if (p[2])
9167 9162
             {
9168
-                int replay_time;
9169
-
9170
-                replay_time = atoi_warn(p[2], msglevel);
9171
-                if (!(MIN_TIME_BACKTRACK <= replay_time && replay_time <= MAX_TIME_BACKTRACK))
9163
+                if (!atoi_constrained(p[2], &options->replay_time, "replay-window time window",
9164
+                                      MIN_TIME_BACKTRACK, MAX_TIME_BACKTRACK, msglevel))
9172 9165
                 {
9173
-                    msg(msglevel,
9174
-                        "replay-window time window parameter (%d) must be between %d and %d",
9175
-                        replay_time, MIN_TIME_BACKTRACK, MAX_TIME_BACKTRACK);
9176 9166
                     goto err;
9177 9167
                 }
9178
-                options->replay_time = replay_time;
9179 9168
             }
9180 9169
         }
9181 9170
         else
... ...
@@ -9771,7 +9707,7 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file,
9771 9771
         else if (!p[2])
9772 9772
         {
9773 9773
             char *endp = NULL;
9774
-            int i = strtol(provider, &endp, 10);
9774
+            long i = strtol(provider, &endp, 10);
9775 9775
 
9776 9776
             if (*endp == 0)
9777 9777
             {
... ...
@@ -9842,7 +9778,7 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file,
9842 9842
     else if (streq(p[0], "pkcs11-pin-cache") && p[1] && !p[2])
9843 9843
     {
9844 9844
         VERIFY_PERMISSION(OPT_P_GENERAL);
9845
-        options->pkcs11_pin_cache_period = atoi_warn(p[1], msglevel);
9845
+        options->pkcs11_pin_cache_period = positive_atoi(p[1], msglevel);
9846 9846
     }
9847 9847
     else if (streq(p[0], "pkcs11-id") && p[1] && !p[2])
9848 9848
     {
... ...
@@ -146,6 +146,37 @@ atoi_warn(const char *str, int msglevel)
146 146
     return (int)i;
147 147
 }
148 148
 
149
+bool
150
+atoi_constrained(const char *str, int *value, const char *name, int min, int max, int msglevel)
151
+{
152
+    ASSERT(min < max);
153
+
154
+    char *endptr;
155
+    long long i = strtoll(str, &endptr, 10);
156
+    if (i < INT_MIN || *endptr != '\0' || i > INT_MAX)
157
+    {
158
+        msg(msglevel, "%s: Cannot parse '%s' as integer", name, str);
159
+        return false;
160
+    }
161
+    if (i < min || i > max)
162
+    {
163
+        if (max == INT_MAX) /* nicer message for common case */
164
+        {
165
+            msg(msglevel, "%s: Must be an integer >= %d, not %lld",
166
+                name, min, i);
167
+        }
168
+        else
169
+        {
170
+            msg(msglevel, "%s: Must be an integer between %d and %d, not %lld",
171
+                name, min, max, i);
172
+        }
173
+        return false;
174
+    }
175
+
176
+    *value = i;
177
+    return true;
178
+}
179
+
149 180
 static const char *updatable_options[] = { "block-ipv6", "block-outside-dns",
150 181
                                            "dhcp-option", "dns",
151 182
                                            "ifconfig", "ifconfig-ipv6",
... ...
@@ -41,11 +41,23 @@ int positive_atoi(const char *str, int msglevel);
41 41
 
42 42
 /**
43 43
  * Converts a str to an integer if the string can be represented as an
44
- * integer number. Otherwise print a warning with msglevel and return 0
44
+ * integer number. Otherwise print a warning with \p msglevel and return 0
45 45
  */
46 46
 int atoi_warn(const char *str, int msglevel);
47 47
 
48 48
 /**
49
+ * Converts a str to an integer if the string can be represented as an
50
+ * integer number and is between \p min and \p max.
51
+ * The integer is stored in \p value.
52
+ * On error, print a warning with \p msglevel using \p name. \p value is
53
+ * not changed on error.
54
+ *
55
+ * @return \c true if the integer has been parsed and stored in value, \c false otherwise
56
+ */
57
+bool atoi_constrained(const char *str, int *value, const char *name, int min, int max,
58
+                      int msglevel);
59
+
60
+/**
49 61
  * Filter an option line by all pull filters.
50 62
  *
51 63
  * If a match is found, the line is modified depending on
... ...
@@ -351,6 +351,14 @@ test_atoi_variants(void **state)
351 351
     assert_int_equal(atoi_warn("0", msglevel), 0);
352 352
     assert_int_equal(atoi_warn("-1194", msglevel), -1194);
353 353
 
354
+    int parameter = 0;
355
+    assert_true(atoi_constrained("1234", &parameter, "test", 0, INT_MAX, msglevel));
356
+    assert_int_equal(parameter, 1234);
357
+    assert_true(atoi_constrained("0", &parameter, "test", -1, 0, msglevel));
358
+    assert_int_equal(parameter, 0);
359
+    assert_true(atoi_constrained("-1194", &parameter, "test", INT_MIN, INT_MAX, msglevel));
360
+    assert_int_equal(parameter, -1194);
361
+
354 362
     CLEAR(mock_msg_buf);
355 363
     assert_int_equal(positive_atoi("-1234", msglevel), 0);
356 364
     assert_string_equal(mock_msg_buf, "Cannot parse argument '-1234' as non-negative integer");
... ...
@@ -365,6 +373,12 @@ test_atoi_variants(void **state)
365 365
     assert_string_equal(mock_msg_buf, "Cannot parse argument '2147483653' as integer");
366 366
 
367 367
     CLEAR(mock_msg_buf);
368
+    parameter = -42;
369
+    assert_false(atoi_constrained("2147483653", &parameter, "test", 0, INT_MAX, msglevel));
370
+    assert_string_equal(mock_msg_buf, "test: Cannot parse '2147483653' as integer");
371
+    assert_int_equal(parameter, -42);
372
+
373
+    CLEAR(mock_msg_buf);
368 374
     assert_int_equal(positive_atoi("foo77", msglevel), 0);
369 375
     assert_string_equal(mock_msg_buf, "Cannot parse argument 'foo77' as non-negative integer");
370 376
 
... ...
@@ -373,6 +387,18 @@ test_atoi_variants(void **state)
373 373
     assert_string_equal(mock_msg_buf, "Cannot parse argument '77foo' as non-negative integer");
374 374
 
375 375
     CLEAR(mock_msg_buf);
376
+    parameter = -42;
377
+    assert_false(atoi_constrained("foo77", &parameter, "test", 0, INT_MAX, msglevel));
378
+    assert_string_equal(mock_msg_buf, "test: Cannot parse 'foo77' as integer");
379
+    assert_int_equal(parameter, -42);
380
+
381
+    CLEAR(mock_msg_buf);
382
+    parameter = -42;
383
+    assert_false(atoi_constrained("77foo", &parameter, "test", 0, INT_MAX, msglevel));
384
+    assert_string_equal(mock_msg_buf, "test: Cannot parse '77foo' as integer");
385
+    assert_int_equal(parameter, -42);
386
+
387
+    CLEAR(mock_msg_buf);
376 388
     assert_int_equal(atoi_warn("foo77", msglevel), 0);
377 389
     assert_string_equal(mock_msg_buf, "Cannot parse argument 'foo77' as integer");
378 390
 
... ...
@@ -380,6 +406,31 @@ test_atoi_variants(void **state)
380 380
     assert_int_equal(atoi_warn("77foo", msglevel), 0);
381 381
     assert_string_equal(mock_msg_buf, "Cannot parse argument '77foo' as integer");
382 382
 
383
+    /* special tests for _constrained */
384
+    CLEAR(mock_msg_buf);
385
+    parameter = -42;
386
+    assert_false(atoi_constrained("77", &parameter, "test", 0, 76, msglevel));
387
+    assert_string_equal(mock_msg_buf, "test: Must be an integer between 0 and 76, not 77");
388
+    assert_int_equal(parameter, -42);
389
+
390
+    CLEAR(mock_msg_buf);
391
+    parameter = -42;
392
+    assert_false(atoi_constrained("-77", &parameter, "test", -76, 76, msglevel));
393
+    assert_string_equal(mock_msg_buf, "test: Must be an integer between -76 and 76, not -77");
394
+    assert_int_equal(parameter, -42);
395
+
396
+    CLEAR(mock_msg_buf);
397
+    parameter = -42;
398
+    assert_false(atoi_constrained("-77", &parameter, "test", 0, INT_MAX, msglevel));
399
+    assert_string_equal(mock_msg_buf, "test: Must be an integer >= 0, not -77");
400
+    assert_int_equal(parameter, -42);
401
+
402
+    CLEAR(mock_msg_buf);
403
+    parameter = -42;
404
+    assert_false(atoi_constrained("0", &parameter, "test", 1, INT_MAX, msglevel));
405
+    assert_string_equal(mock_msg_buf, "test: Must be an integer >= 1, not 0");
406
+    assert_int_equal(parameter, -42);
407
+
383 408
     mock_set_debug_level(saved_log_level);
384 409
 }
385 410