Browse code
In-progress OTP code
Ed Langley authored on 26/05/2017 19:10:44
Showing 8 changed files
Showing 8 changed files
- .editorconfig
- Makefile.in
- conversation.cc
- dual_control_tool.cc
- generator.cc
- generator.h
- generator_test.cc
- test_util.h
... | ... |
@@ -1,10 +1,10 @@ |
1 |
-CXXFLAGS += -fPIC -fno-stack-protector -std=c++11 |
|
2 |
-CFLAGS += -fPIC -fno-stack-protector |
|
3 |
-LDFLAGS = -lpam |
|
1 |
+CXXFLAGS += -fPIC -fno-stack-protector -std=c++14 -g |
|
2 |
+CFLAGS += -fPIC -fno-stack-protector -g |
|
3 |
+LDFLAGS = -lcryptopp -lpam |
|
4 | 4 |
|
5 | 5 |
INTEGRATION_OBJS = sys_syslog.o sys_fstream.o sys_unistd.o sys_pwd.o sys_pam.o \ |
6 | 6 |
sys_stdlib.o sys_time.o |
7 |
-OBJS = dual_control.o request.o validator.o conversation.o user.o token.o logger.o session.o installer.o system.o |
|
7 |
+OBJS = dual_control.o request.o validator.o conversation.o user.o token.o logger.o session.o installer.o system.o generator.o |
|
8 | 8 |
TEST_SOURCES := $(wildcard *_test.cc) |
9 | 9 |
TESTS := $(patsubst %.cc,%.out,$(TEST_SOURCES)) |
10 | 10 |
TESTRUNS := $(patsubst %.out,RUN_%,$(TESTS)) |
... | ... |
@@ -46,7 +46,7 @@ distclean: clean |
46 | 46 |
-include .depend |
47 | 47 |
|
48 | 48 |
%_test.out: %_test.o $(OBJS) |
49 |
- $(CXX) $(CXXFLAGS) $(CPPFLAGS) -o $@ $^ |
|
49 |
+ $(CXX) $(CXXFLAGS) $(CPPFLAGS) -o $@ $^ -lcryptopp |
|
50 | 50 |
|
51 | 51 |
RUN_%: %.out |
52 | 52 |
@echo running $< |
... | ... |
@@ -70,5 +70,3 @@ endif |
70 | 70 |
.PHONY: format |
71 | 71 |
format: |
72 | 72 |
@./format.sh *.cc *.h |
73 |
- |
|
74 |
- |
... | ... |
@@ -16,20 +16,22 @@ |
16 | 16 |
|
17 | 17 |
#include "conversation.h" |
18 | 18 |
|
19 |
- |
|
20 | 19 |
namespace |
21 | 20 |
{ |
22 | 21 |
class impl : public conversation_ifc |
23 | 22 |
{ |
24 | 23 |
private: |
25 | 24 |
pam pam_; |
26 |
- pam_conv_result conversation(const std::string &msg, int msg_style, const pam_request &request) { |
|
25 |
+ pam_conv_result conversation (const std::string &msg, int msg_style, |
|
26 |
+ const pam_request &request) |
|
27 |
+ { |
|
27 | 28 |
const pam_conv *conv; |
28 | 29 |
int get_conv_result = pam_.get_conv (request.handle(), &conv); |
30 |
+ |
|
29 | 31 |
if (get_conv_result != PAM_SUCCESS) { |
30 | 32 |
return pam_conv_result {get_conv_result, PAM_CONV_ERR, std::vector<pam_response *>()}; |
31 | 33 |
} |
32 |
- |
|
34 |
+ |
|
33 | 35 |
pam_message message; |
34 | 36 |
message.msg = const_cast<char *> (msg.c_str()); |
35 | 37 |
message.msg_style = msg_style; |
... | ... |
@@ -39,8 +41,8 @@ private: |
39 | 41 |
std::vector<pam_response *> responses (1); |
40 | 42 |
|
41 | 43 |
int conv_result = conv->conv (1, messages.data(), |
42 |
- responses.data(), |
|
43 |
- conv->appdata_ptr); |
|
44 |
+ responses.data(), |
|
45 |
+ conv->appdata_ptr); |
|
44 | 46 |
|
45 | 47 |
return pam_conv_result {get_conv_result, conv_result, responses}; |
46 | 48 |
} |
... | ... |
@@ -51,12 +53,16 @@ public: |
51 | 53 |
{ |
52 | 54 |
conversation_result result; |
53 | 55 |
|
54 |
- pam_conv_result token_result (conversation("Dual control token: ", PAM_PROMPT_ECHO_OFF, request)); |
|
56 |
+ pam_conv_result token_result (conversation ("Dual control token: ", |
|
57 |
+ PAM_PROMPT_ECHO_OFF, request)); |
|
58 |
+ |
|
55 | 59 |
if (token_result.get_conv_result != PAM_SUCCESS) { |
56 | 60 |
return result; |
57 | 61 |
} |
58 | 62 |
|
59 |
- pam_conv_result reason_result (conversation("Reason: ", PAM_PROMPT_ECHO_ON, request)); |
|
63 |
+ pam_conv_result reason_result (conversation ("Reason: ", PAM_PROMPT_ECHO_ON, |
|
64 |
+ request)); |
|
65 |
+ |
|
60 | 66 |
if (reason_result.get_conv_result != PAM_SUCCESS) { |
61 | 67 |
return result; |
62 | 68 |
} |
... | ... |
@@ -18,6 +18,7 @@ |
18 | 18 |
#include "user.h" |
19 | 19 |
#include "sys_unistd.h" |
20 | 20 |
#include "sys_pwd.h" |
21 |
+#include "sys_time.h" |
|
21 | 22 |
#include "token.h" |
22 | 23 |
#include "system.h" |
23 | 24 |
#include "sys_fstream.h" |
... | ... |
@@ -39,9 +40,14 @@ installer init_installer() |
39 | 40 |
unistd unistd (unistd::create()); |
40 | 41 |
directory directory (directory::create (unistd, pwd)); |
41 | 42 |
stdlib stdlib (stdlib::get()); |
42 |
- generator generator{make_generator (stdlib)}; |
|
43 |
- installer installer (installer::create (tokens, unistd, |
|
44 |
- directory, generator)); |
|
43 |
+ sys_time timer (sys_time::get()); |
|
44 |
+ int code_digits = 6; |
|
45 |
+ std::shared_ptr<totp_generator> totp_generator = |
|
46 |
+ std::make_shared<totp_generator> (timer, "\x00", code_digits); |
|
47 |
+ generator generator = std::bind (&TOTPGenerator::generate_token, |
|
48 |
+ totp_generator); |
|
49 |
+ installer installer (installer::create (tokens, unistd, directory, |
|
50 |
+ generator)); |
|
45 | 51 |
|
46 | 52 |
return installer; |
47 | 53 |
} |
48 | 54 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,126 @@ |
1 |
+/* Copyright (C) CJ Affiliate |
|
2 |
+ * |
|
3 |
+ * You may use, distribute and modify this code under the |
|
4 |
+ * terms of the GNU General Public License version 2 or |
|
5 |
+ * later. |
|
6 |
+ * |
|
7 |
+ * You should have received a copy of the license with this |
|
8 |
+ * file. If not, you will find a copy in the "LICENSE" file |
|
9 |
+ * at https://github.com/cjdev/dual-control. |
|
10 |
+ */ |
|
11 |
+ |
|
12 |
+#include "generator.h" |
|
13 |
+#include <iostream> |
|
14 |
+ |
|
15 |
+int ipow (int base, int exp) |
|
16 |
+{ |
|
17 |
+ int result = 1; |
|
18 |
+ |
|
19 |
+ while (exp) { |
|
20 |
+ if (exp & 1) { |
|
21 |
+ result *= base; |
|
22 |
+ } |
|
23 |
+ |
|
24 |
+ exp >>= 1; |
|
25 |
+ base *= base; |
|
26 |
+ } |
|
27 |
+ |
|
28 |
+ return result; |
|
29 |
+} |
|
30 |
+ |
|
31 |
+unsigned long bytesToInt (const std::string &bytes) |
|
32 |
+{ |
|
33 |
+ unsigned long result = 0; |
|
34 |
+ auto byteCount = bytes.size() - 1; |
|
35 |
+ |
|
36 |
+ for (auto byte = bytes.cbegin(); byte < bytes.cend(); byte++, byteCount--) { |
|
37 |
+ const uint8_t val = static_cast<uint8_t> (*byte); |
|
38 |
+ result |= val << (byteCount * 8); |
|
39 |
+ } |
|
40 |
+ |
|
41 |
+ return result; |
|
42 |
+} |
|
43 |
+ |
|
44 |
+time_t time_step (const time_t time, const int step) |
|
45 |
+{ |
|
46 |
+ // Time is > 0 so division produces the result we want. |
|
47 |
+ return time / step; |
|
48 |
+} |
|
49 |
+ |
|
50 |
+class impl : public token_generator_ifc |
|
51 |
+{ |
|
52 |
+private: |
|
53 |
+ const sys_time &sys_time; |
|
54 |
+ unsigned int code_digits; |
|
55 |
+ const std::shared_ptr<CryptoPP::SecByteBlock> key; |
|
56 |
+ |
|
57 |
+ unsigned long truncate (const std::string &mac) const; |
|
58 |
+ |
|
59 |
+ unsigned long hotp (const CryptoPP::SecByteBlock &key, |
|
60 |
+ const CryptoPP::Integer &counter) const; |
|
61 |
+ |
|
62 |
+ // TODO: move elsewhere |
|
63 |
+ CryptoPP::SecByteBlock generate_key (unsigned int size) const; |
|
64 |
+ |
|
65 |
+ unsigned long totp_generator::truncate (const std::string &mac) const |
|
66 |
+ { |
|
67 |
+ uint8_t offset = static_cast<uint8_t > (mac[19]) & static_cast<uint8_t> |
|
68 |
+ (0x0f); |
|
69 |
+ std::string offsetBytes = mac.substr (offset, 4); |
|
70 |
+ return bytesToInt (offsetBytes) & 0x7fffffff; |
|
71 |
+ } |
|
72 |
+ |
|
73 |
+ unsigned long totp_generator::hotp (const CryptoPP::SecByteBlock &key, |
|
74 |
+ const CryptoPP::Integer &counter) const |
|
75 |
+ { |
|
76 |
+ std::string mac; |
|
77 |
+ |
|
78 |
+ CryptoPP::SecByteBlock counter_bytes (8); |
|
79 |
+ // Do I know that 8 is sufficient here? . . . |
|
80 |
+ counter.Encode (counter_bytes.BytePtr(), 8, CryptoPP::Integer::UNSIGNED); |
|
81 |
+ |
|
82 |
+ CryptoPP::HMAC<CryptoPP::SHA1> hmac (key, key.size()); |
|
83 |
+ |
|
84 |
+ CryptoPP::StringSink *stringSink = new CryptoPP::StringSink (mac); |
|
85 |
+ CryptoPP::HashFilter *hashFilter = new CryptoPP::HashFilter (hmac, |
|
86 |
+ stringSink); |
|
87 |
+ CryptoPP::StringSource ss2 (counter_bytes, counter_bytes.size(), true, |
|
88 |
+ hashFilter); |
|
89 |
+ |
|
90 |
+ unsigned long result = truncate (mac); |
|
91 |
+ |
|
92 |
+ result = result % ipow (10, code_digits); |
|
93 |
+ return result; |
|
94 |
+ } |
|
95 |
+ |
|
96 |
+ CryptoPP::SecByteBlock totp_generator::generate_key (unsigned int size) |
|
97 |
+ const |
|
98 |
+ { |
|
99 |
+ CryptoPP::AutoSeededRandomPool prng; |
|
100 |
+ |
|
101 |
+ CryptoPP::SecByteBlock key (size); |
|
102 |
+ prng.GenerateBlock (key, key.size()); |
|
103 |
+ return key; |
|
104 |
+ } |
|
105 |
+ |
|
106 |
+public: |
|
107 |
+ std::string generate_token () const override |
|
108 |
+ { |
|
109 |
+ time_t foo = 111; |
|
110 |
+ const CryptoPP::Integer &time = sys_time.time (&foo); |
|
111 |
+ std::cout << "At the tone, the time is: " << foo << " or " << time << "\a" |
|
112 |
+ << std::endl; |
|
113 |
+ int time_step_size = 30; |
|
114 |
+ CryptoPP::Integer current_step = time_step (time.ConvertToLong(), |
|
115 |
+ time_step_size); |
|
116 |
+ long otp = hotp (*key, current_step); |
|
117 |
+ std::ostringstream is; |
|
118 |
+ is << std::setfill ('0') << std::setw (6)<< otp; |
|
119 |
+ return is.str(); |
|
120 |
+ } |
|
121 |
+}; |
|
122 |
+ |
|
123 |
+// Generator goes here.... |
|
124 |
+std::string totp_generator::generate_token () const |
|
125 |
+; |
|
126 |
+ |
... | ... |
@@ -9,6 +9,7 @@ |
9 | 9 |
* at https://github.com/cjdev/dual-control. |
10 | 10 |
*/ |
11 | 11 |
|
12 |
+#pragma once |
|
12 | 13 |
#ifndef GENERATOR_H_ |
13 | 14 |
#define GENERATOR_H_ |
14 | 15 |
|
... | ... |
@@ -17,25 +18,39 @@ |
17 | 18 |
#include <sstream> |
18 | 19 |
#include <iomanip> |
19 | 20 |
#include <cmath> |
21 |
+#include <ctime> |
|
22 |
+ |
|
23 |
+#include <cryptopp/base32.h> |
|
24 |
+#include <cryptopp/hex.h> |
|
25 |
+#include <cryptopp/hmac.h> |
|
26 |
+#include <cryptopp/osrng.h> |
|
20 | 27 |
|
21 | 28 |
#include "sys_stdlib.h" |
29 |
+#include "sys_time.h" |
|
22 | 30 |
|
23 | 31 |
using generator = std::function<std::string()>; |
32 |
+int ipow (int base, int exp); |
|
33 |
+time_t time_step (const time_t time, const int step); |
|
24 | 34 |
|
25 |
-inline std::string token_from_int (int x) |
|
35 |
+class token_generator_ifc |
|
26 | 36 |
{ |
27 |
- int v = std::abs (x % 1000000); |
|
28 |
- std::ostringstream is; |
|
29 |
- is << std::setfill ('0') << std::setw (6)<< v; |
|
30 |
- return is.str(); |
|
31 |
-} |
|
37 |
+public: |
|
38 |
+ virtual std::string generate_token () const; |
|
39 |
+}; |
|
32 | 40 |
|
33 |
-inline generator make_generator (const stdlib &stdlib) |
|
41 |
+class totp_generator : public token_generator |
|
34 | 42 |
{ |
35 |
- return [stdlib] { |
|
36 |
- return token_from_int (stdlib.rand()); |
|
37 |
- }; |
|
38 |
-} |
|
43 |
+public: |
|
44 |
+ totp_generator (const class sys_time &sys_time, |
|
45 |
+ const std::string &key_c, |
|
46 |
+ const int code_digits) : |
|
47 |
+ sys_time (sys_time), code_digits (code_digits), |
|
48 |
+ key (std::make_shared<CryptoPP::SecByteBlock> (CryptoPP::SecByteBlock ( |
|
49 |
+ reinterpret_cast<const unsigned char *> (key_c.c_str()), key_c.size()))) |
|
50 |
+ {}; |
|
51 |
+ |
|
52 |
+ std::string generate_token () const; |
|
53 |
+}; |
|
39 | 54 |
|
40 | 55 |
#endif |
41 | 56 |
|
... | ... |
@@ -13,9 +13,12 @@ |
13 | 13 |
#include <initializer_list> |
14 | 14 |
#include <vector> |
15 | 15 |
#include <climits> |
16 |
+#include <ctime> |
|
17 |
+#include <iostream> |
|
16 | 18 |
|
17 | 19 |
#include "generator.h" |
18 | 20 |
#include "sys_stdlib.h" |
21 |
+#include "sys_time.h" |
|
19 | 22 |
#include "test_util.h" |
20 | 23 |
|
21 | 24 |
class fake_stdlib : public stdlib_ifc |
... | ... |
@@ -40,16 +43,46 @@ public: |
40 | 43 |
} |
41 | 44 |
}; |
42 | 45 |
|
46 |
+class fake_time : public sys_time_ifc |
|
47 |
+{ |
|
48 |
+ |
|
49 |
+private: |
|
50 |
+ std::vector<time_t> samples_; |
|
51 |
+ mutable std::vector<time_t>::iterator current_; |
|
52 |
+public: |
|
53 |
+ fake_time ( const std::initializer_list<time_t> &samples) |
|
54 |
+ : samples_ (samples.begin(), samples.end()), |
|
55 |
+ current_ (samples_.begin()) {} |
|
56 |
+ time_t time (time_t *time_ptr) const override |
|
57 |
+ { |
|
58 |
+ if (current_ != samples_.end()) { |
|
59 |
+ auto rval = *current_; |
|
60 |
+ |
|
61 |
+ if (time_ptr != nullptr) { |
|
62 |
+ *time_ptr = rval; |
|
63 |
+ } |
|
64 |
+ |
|
65 |
+ current_ += 1; |
|
66 |
+ return rval; |
|
67 |
+ } |
|
68 |
+ |
|
69 |
+ return 0; |
|
70 |
+ } |
|
71 |
+}; |
|
72 |
+ |
|
43 | 73 |
int six_digits() |
44 | 74 |
{ |
45 | 75 |
// given |
46 |
- std::initializer_list<int> samples { 1 }; |
|
47 |
- auto test_stdlib = std::make_shared<fake_stdlib> (samples); |
|
48 |
- stdlib stdlib (test_stdlib); |
|
49 |
- generator generator = make_generator (stdlib); |
|
76 |
+ std::initializer_list<time_t> samples { 1 }; |
|
77 |
+ auto test_stdtime = std::make_shared<fake_time> (samples); |
|
78 |
+ |
|
79 |
+ sys_time stdtime (test_stdtime); |
|
80 |
+ // Fake the Key |
|
81 |
+ std::string key = "\xff\x91\xebO\x04\xa4\xda$\xd2$a\x95Vs\xaf`"; |
|
82 |
+ auto generator = totp_generator (stdtime, key, 6); |
|
50 | 83 |
|
51 | 84 |
// when |
52 |
- auto actual = generator(); |
|
85 |
+ auto actual = generator.generate_token(); |
|
53 | 86 |
|
54 | 87 |
// then |
55 | 88 |
check (actual.size() == 6, "size is wrong"); |
... | ... |
@@ -62,30 +95,36 @@ int six_digits() |
62 | 95 |
int modulated_source_modulates_tokens() |
63 | 96 |
{ |
64 | 97 |
// given |
65 |
- std::initializer_list<int> samples { 1, 2, 3 }; |
|
66 |
- auto test_stdlib = std::make_shared<fake_stdlib> (samples); |
|
67 |
- stdlib stdlib (test_stdlib); |
|
68 |
- generator generator = make_generator (stdlib); |
|
98 |
+ std::initializer_list<time_t> samples { 1, 31 }; |
|
99 |
+ auto test_stdtime = std::make_shared<fake_time> (samples); |
|
100 |
+ |
|
101 |
+ sys_time stdtime (test_stdtime); |
|
102 |
+ // Fake the Key |
|
103 |
+ std::string key = "\xff\x91\xebO\x04\xa4\xda$\xd2$a\x95Vs\xaf`"; |
|
104 |
+ auto generator = totp_generator (stdtime, key, 6); |
|
69 | 105 |
|
70 | 106 |
// when |
71 |
- auto actual1 = generator(); |
|
72 |
- auto actual2 = generator(); |
|
107 |
+ auto actual1 = generator.generate_token(); |
|
108 |
+ auto actual2 = generator.generate_token(); |
|
73 | 109 |
|
74 | 110 |
// then |
75 |
- check (actual1 != actual2, "samples should be different"); |
|
111 |
+ check (actual1 != actual2, "tokens should be different"); |
|
76 | 112 |
succeed(); |
77 | 113 |
} |
78 | 114 |
|
79 | 115 |
int int_max() |
80 | 116 |
{ |
81 | 117 |
// given |
82 |
- std::initializer_list<int> samples { INT_MAX }; |
|
83 |
- auto test_stdlib = std::make_shared<fake_stdlib> (samples); |
|
84 |
- stdlib stdlib (test_stdlib); |
|
85 |
- generator generator = make_generator (stdlib); |
|
118 |
+ std::initializer_list<time_t> samples { INT_MAX }; |
|
119 |
+ auto test_stdtime = std::make_shared<fake_time> (samples); |
|
120 |
+ |
|
121 |
+ sys_time stdtime (test_stdtime); |
|
122 |
+ // Fake the Key |
|
123 |
+ std::string key = "\xff\x91\xebO\x04\xa4\xda$\xd2$a\x95Vs\xaf`"; |
|
124 |
+ auto generator = totp_generator (stdtime, key, 6); |
|
86 | 125 |
|
87 | 126 |
// when |
88 |
- auto actual = generator(); |
|
127 |
+ auto actual = generator.generate_token(); |
|
89 | 128 |
|
90 | 129 |
// then |
91 | 130 |
check (actual.size() == 6, "size is wrong"); |
... | ... |
@@ -98,13 +137,16 @@ int int_max() |
98 | 137 |
int int_min() |
99 | 138 |
{ |
100 | 139 |
// given |
101 |
- std::initializer_list<int> samples { INT_MIN }; |
|
102 |
- auto test_stdlib = std::make_shared<fake_stdlib> (samples); |
|
103 |
- stdlib stdlib (test_stdlib); |
|
104 |
- generator generator = make_generator (stdlib); |
|
140 |
+ std::initializer_list<time_t> samples { INT_MIN }; |
|
141 |
+ auto test_stdtime = std::make_shared<fake_time> (samples); |
|
142 |
+ |
|
143 |
+ sys_time stdtime (test_stdtime); |
|
144 |
+ // Fake the Key |
|
145 |
+ std::string key = "\xff\x91\xebO\x04\xa4\xda$\xd2$a\x95Vs\xaf`"; |
|
146 |
+ auto generator = totp_generator (stdtime, key, 6); |
|
105 | 147 |
|
106 | 148 |
// when |
107 |
- auto actual = generator(); |
|
149 |
+ auto actual = generator.generate_token(); |
|
108 | 150 |
|
109 | 151 |
// then |
110 | 152 |
check (actual.size() == 6, "size is wrong"); |
... | ... |
@@ -114,12 +156,39 @@ int int_min() |
114 | 156 |
succeed(); |
115 | 157 |
} |
116 | 158 |
|
159 |
+// totp test |
|
160 |
+int int_precomputed() |
|
161 |
+{ |
|
162 |
+ // given |
|
163 |
+ // The token for key 76I6WTYEUTNCJUREMGKVM45PMA and time '2017/01/01 00:00:00' is 258675 |
|
164 |
+ time_t theTime = 1483257600; |
|
165 |
+ /// TODO: int -> time_t |
|
166 |
+ std::initializer_list<time_t> samples { theTime }; // |
|
167 |
+ auto test_stdtime = std::make_shared<fake_time> (samples); |
|
168 |
+ |
|
169 |
+ sys_time stdtime (test_stdtime); |
|
170 |
+ // Fake the Key |
|
171 |
+ std::string key = "\xff\x91\xebO\x04\xa4\xda$\xd2$a\x95Vs\xaf`"; |
|
172 |
+ auto generator = totp_generator (stdtime, key, 6); |
|
173 |
+ std::string expected = "258675"; |
|
174 |
+ |
|
175 |
+ // when |
|
176 |
+ auto actual = generator.generate_token(); |
|
177 |
+ |
|
178 |
+ // then |
|
179 |
+ check (actual.size() == 6, "size is wrong"); |
|
180 |
+ check (actual == expected, |
|
181 |
+ "precomputed value failed to match"); // TODO: Does == work for std::string like I want it to? |
|
182 |
+ succeed(); |
|
183 |
+} |
|
184 |
+ |
|
117 | 185 |
int run_tests() |
118 | 186 |
{ |
119 | 187 |
test (six_digits); |
120 | 188 |
test (modulated_source_modulates_tokens); |
121 | 189 |
test (int_max); |
122 | 190 |
test (int_min); |
191 |
+ test (int_precomputed); |
|
123 | 192 |
succeed(); |
124 | 193 |
} |
125 | 194 |
|
... | ... |
@@ -127,4 +196,3 @@ int main (int argc, char *argv[]) |
127 | 196 |
{ |
128 | 197 |
return !run_tests(); |
129 | 198 |
} |
130 |
- |
... | ... |
@@ -16,7 +16,7 @@ |
16 | 16 |
#include <cstdio> |
17 | 17 |
#define check(assertion, msg) \ |
18 | 18 |
if (!(assertion)) { \ |
19 |
- fprintf(stderr, "assertion failed: %s\n", msg); \ |
|
19 |
+ fprintf(stderr, "> assertion failed: %s\n", msg); \ |
|
20 | 20 |
return 0; \ |
21 | 21 |
} |
22 | 22 |
|
... | ... |
@@ -32,11 +32,13 @@ |
32 | 32 |
|
33 | 33 |
#define test(NAME) \ |
34 | 34 |
{ \ |
35 |
- int result = NAME (); \ |
|
36 |
- if (!result) { \ |
|
37 |
- fprintf(stderr, "test failed: %s\n", #NAME); \ |
|
38 |
- return 0; \ |
|
39 |
- } \ |
|
35 |
+ int result = NAME (); \ |
|
36 |
+ if (!result) { \ |
|
37 |
+ fprintf(stderr, "> test failed: %s\n", #NAME); \ |
|
38 |
+ return 0; \ |
|
39 |
+ } else { \ |
|
40 |
+ fprintf (stderr, "> test passed: %s\n", #NAME); \ |
|
41 |
+ } \ |
|
40 | 42 |
} |
41 | 43 |
|
42 | 44 |
#define succeed() return 1 |