diff options
author | Christian Grothoff <christian@grothoff.org> | 2010-03-10 10:55:01 +0000 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2010-03-10 10:55:01 +0000 |
commit | f6c1d5c870dadddc88c8f501448e2951b1c900b7 (patch) | |
tree | bf40faddf1ca8d96ab8fc2840507ef025a87c4f5 | |
parent | 02e738b91dff93aa75bfe38d5d58fdbd353f50be (diff) | |
download | gnunet-f6c1d5c870dadddc88c8f501448e2951b1c900b7.tar.gz gnunet-f6c1d5c870dadddc88c8f501448e2951b1c900b7.zip |
keepalive PINGs
-rw-r--r-- | TODO | 1 | ||||
-rw-r--r-- | src/core/gnunet-service-core.c | 201 | ||||
-rw-r--r-- | src/include/gnunet_time_lib.h | 27 | ||||
-rw-r--r-- | src/util/time.c | 36 |
4 files changed, 212 insertions, 53 deletions
@@ -13,7 +13,6 @@ away), in order in which they will likely be done: | |||
13 | * ARM [Safey] | 13 | * ARM [Safey] |
14 | 14 | ||
15 | Urgent items (before announcing ng.gnunet.org): | 15 | Urgent items (before announcing ng.gnunet.org): |
16 | * core fails to do keepalive on idle connections => disconnect! | ||
17 | * new webpage | 16 | * new webpage |
18 | - run peer => have a 0.9.x hostlist | 17 | - run peer => have a 0.9.x hostlist |
19 | => Deploy(able) development network | 18 | => Deploy(able) development network |
diff --git a/src/core/gnunet-service-core.c b/src/core/gnunet-service-core.c index f63ccec88..b13ea023b 100644 --- a/src/core/gnunet-service-core.c +++ b/src/core/gnunet-service-core.c | |||
@@ -101,6 +101,11 @@ | |||
101 | #define MAX_PONG_DELAY GNUNET_TIME_relative_multiply (MAX_PING_DELAY, 2) | 101 | #define MAX_PONG_DELAY GNUNET_TIME_relative_multiply (MAX_PING_DELAY, 2) |
102 | 102 | ||
103 | /** | 103 | /** |
104 | * What is the minimum frequency for a PING message? | ||
105 | */ | ||
106 | #define MIN_PING_FREQUENCY GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5) | ||
107 | |||
108 | /** | ||
104 | * How often do we recalculate bandwidth quotas? | 109 | * How often do we recalculate bandwidth quotas? |
105 | */ | 110 | */ |
106 | #define QUOTA_UPDATE_FREQUENCY GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5) | 111 | #define QUOTA_UPDATE_FREQUENCY GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5) |
@@ -477,6 +482,11 @@ struct Neighbour | |||
477 | GNUNET_SCHEDULER_TaskIdentifier quota_update_task; | 482 | GNUNET_SCHEDULER_TaskIdentifier quota_update_task; |
478 | 483 | ||
479 | /** | 484 | /** |
485 | * ID of task used for sending keep-alive pings. | ||
486 | */ | ||
487 | GNUNET_SCHEDULER_TaskIdentifier keep_alive_task; | ||
488 | |||
489 | /** | ||
480 | * ID of task used for cleaning up dead neighbour entries. | 490 | * ID of task used for cleaning up dead neighbour entries. |
481 | */ | 491 | */ |
482 | GNUNET_SCHEDULER_TaskIdentifier dead_clean_task; | 492 | GNUNET_SCHEDULER_TaskIdentifier dead_clean_task; |
@@ -1074,6 +1084,8 @@ free_neighbour (struct Neighbour *n) | |||
1074 | GNUNET_SCHEDULER_cancel (sched, n->quota_update_task); | 1084 | GNUNET_SCHEDULER_cancel (sched, n->quota_update_task); |
1075 | if (n->dead_clean_task != GNUNET_SCHEDULER_NO_TASK) | 1085 | if (n->dead_clean_task != GNUNET_SCHEDULER_NO_TASK) |
1076 | GNUNET_SCHEDULER_cancel (sched, n->dead_clean_task); | 1086 | GNUNET_SCHEDULER_cancel (sched, n->dead_clean_task); |
1087 | if (n->keep_alive_task != GNUNET_SCHEDULER_NO_TASK) | ||
1088 | GNUNET_SCHEDULER_cancel (sched, n->keep_alive_task); | ||
1077 | GNUNET_free_non_null (n->public_key); | 1089 | GNUNET_free_non_null (n->public_key); |
1078 | GNUNET_free_non_null (n->pending_ping); | 1090 | GNUNET_free_non_null (n->pending_ping); |
1079 | GNUNET_free_non_null (n->pending_pong); | 1091 | GNUNET_free_non_null (n->pending_pong); |
@@ -1082,6 +1094,53 @@ free_neighbour (struct Neighbour *n) | |||
1082 | 1094 | ||
1083 | 1095 | ||
1084 | /** | 1096 | /** |
1097 | * Check if we have encrypted messages for the specified neighbour | ||
1098 | * pending, and if so, check with the transport about sending them | ||
1099 | * out. | ||
1100 | * | ||
1101 | * @param n neighbour to check. | ||
1102 | */ | ||
1103 | static void process_encrypted_neighbour_queue (struct Neighbour *n); | ||
1104 | |||
1105 | |||
1106 | /** | ||
1107 | * Encrypt size bytes from in and write the result to out. Use the | ||
1108 | * key for outbound traffic of the given neighbour. | ||
1109 | * | ||
1110 | * @param n neighbour we are sending to | ||
1111 | * @param iv initialization vector to use | ||
1112 | * @param in ciphertext | ||
1113 | * @param out plaintext | ||
1114 | * @param size size of in/out | ||
1115 | * @return GNUNET_OK on success | ||
1116 | */ | ||
1117 | static int | ||
1118 | do_encrypt (struct Neighbour *n, | ||
1119 | const GNUNET_HashCode * iv, | ||
1120 | const void *in, void *out, size_t size) | ||
1121 | { | ||
1122 | if (size != (uint16_t) size) | ||
1123 | { | ||
1124 | GNUNET_break (0); | ||
1125 | return GNUNET_NO; | ||
1126 | } | ||
1127 | GNUNET_assert (size == | ||
1128 | GNUNET_CRYPTO_aes_encrypt (in, | ||
1129 | (uint16_t) size, | ||
1130 | &n->encrypt_key, | ||
1131 | (const struct | ||
1132 | GNUNET_CRYPTO_AesInitializationVector | ||
1133 | *) iv, out)); | ||
1134 | #if DEBUG_CORE | ||
1135 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
1136 | "Encrypted %u bytes for `%4s' using key %u\n", size, | ||
1137 | GNUNET_i2s (&n->peer), n->encrypt_key.crc32); | ||
1138 | #endif | ||
1139 | return GNUNET_OK; | ||
1140 | } | ||
1141 | |||
1142 | |||
1143 | /** | ||
1085 | * Consider freeing the given neighbour since we may not need | 1144 | * Consider freeing the given neighbour since we may not need |
1086 | * to keep it around anymore. | 1145 | * to keep it around anymore. |
1087 | * | 1146 | * |
@@ -1092,6 +1151,68 @@ consider_free_neighbour (struct Neighbour *n); | |||
1092 | 1151 | ||
1093 | 1152 | ||
1094 | /** | 1153 | /** |
1154 | * Task triggered when a neighbour entry is about to time out | ||
1155 | * (and we should prevent this by sending a PING). | ||
1156 | * | ||
1157 | * @param cls the 'struct Neighbour' | ||
1158 | * @param tc scheduler context (not used) | ||
1159 | */ | ||
1160 | static void | ||
1161 | send_keep_alive (void *cls, | ||
1162 | const struct GNUNET_SCHEDULER_TaskContext *tc) | ||
1163 | { | ||
1164 | struct Neighbour *n = cls; | ||
1165 | struct GNUNET_TIME_Relative retry; | ||
1166 | struct GNUNET_TIME_Relative left; | ||
1167 | struct MessageEntry *me; | ||
1168 | struct PingMessage pp; | ||
1169 | struct PingMessage *pm; | ||
1170 | |||
1171 | n->keep_alive_task = GNUNET_SCHEDULER_NO_TASK; | ||
1172 | /* send PING */ | ||
1173 | me = GNUNET_malloc (sizeof (struct MessageEntry) + | ||
1174 | sizeof (struct PingMessage)); | ||
1175 | me->deadline = GNUNET_TIME_relative_to_absolute (MAX_PING_DELAY); | ||
1176 | me->priority = PING_PRIORITY; | ||
1177 | me->size = sizeof (struct PingMessage); | ||
1178 | n->encrypted_tail->next = me; | ||
1179 | n->encrypted_tail = me; | ||
1180 | pm = (struct PingMessage *) &me[1]; | ||
1181 | pm->header.size = htons (sizeof (struct PingMessage)); | ||
1182 | pm->header.type = htons (GNUNET_MESSAGE_TYPE_CORE_PING); | ||
1183 | pp.challenge = htonl (n->ping_challenge); | ||
1184 | pp.target = n->peer; | ||
1185 | #if DEBUG_CORE | ||
1186 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
1187 | "Encrypting `%s' and `%s' messages for `%4s'.\n", | ||
1188 | "SET_KEY", "PING", GNUNET_i2s (&n->peer)); | ||
1189 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
1190 | "Sending `%s' to `%4s' with challenge %u encrypted using key %u\n", | ||
1191 | "PING", | ||
1192 | GNUNET_i2s (&n->peer), n->ping_challenge, n->encrypt_key.crc32); | ||
1193 | #endif | ||
1194 | do_encrypt (n, | ||
1195 | &n->peer.hashPubKey, | ||
1196 | &pp.challenge, | ||
1197 | &pm->challenge, | ||
1198 | sizeof (struct PingMessage) - | ||
1199 | sizeof (struct GNUNET_MessageHeader)); | ||
1200 | process_encrypted_neighbour_queue (n); | ||
1201 | /* reschedule PING job */ | ||
1202 | left = GNUNET_TIME_absolute_get_remaining (GNUNET_TIME_absolute_add (n->last_activity, | ||
1203 | GNUNET_CONSTANTS_IDLE_CONNECTION_TIMEOUT)); | ||
1204 | retry = GNUNET_TIME_relative_max (GNUNET_TIME_relative_divide (left, 2), | ||
1205 | MIN_PING_FREQUENCY); | ||
1206 | n->keep_alive_task | ||
1207 | = GNUNET_SCHEDULER_add_delayed (sched, | ||
1208 | GNUNET_TIME_relative_divide (left, 2), | ||
1209 | &send_keep_alive, | ||
1210 | n); | ||
1211 | |||
1212 | } | ||
1213 | |||
1214 | |||
1215 | /** | ||
1095 | * Task triggered when a neighbour entry might have gotten stale. | 1216 | * Task triggered when a neighbour entry might have gotten stale. |
1096 | * | 1217 | * |
1097 | * @param cls the 'struct Neighbour' | 1218 | * @param cls the 'struct Neighbour' |
@@ -1102,6 +1223,7 @@ consider_free_task (void *cls, | |||
1102 | const struct GNUNET_SCHEDULER_TaskContext *tc) | 1223 | const struct GNUNET_SCHEDULER_TaskContext *tc) |
1103 | { | 1224 | { |
1104 | struct Neighbour *n = cls; | 1225 | struct Neighbour *n = cls; |
1226 | |||
1105 | n->dead_clean_task = GNUNET_SCHEDULER_NO_TASK; | 1227 | n->dead_clean_task = GNUNET_SCHEDULER_NO_TASK; |
1106 | consider_free_neighbour (n); | 1228 | consider_free_neighbour (n); |
1107 | } | 1229 | } |
@@ -1127,7 +1249,7 @@ consider_free_neighbour (struct Neighbour *n) | |||
1127 | return; /* no chance */ | 1249 | return; /* no chance */ |
1128 | 1250 | ||
1129 | left = GNUNET_TIME_absolute_get_remaining (GNUNET_TIME_absolute_add (n->last_activity, | 1251 | left = GNUNET_TIME_absolute_get_remaining (GNUNET_TIME_absolute_add (n->last_activity, |
1130 | MAX_PONG_DELAY)); | 1252 | GNUNET_CONSTANTS_IDLE_CONNECTION_TIMEOUT)); |
1131 | if (left.value > 0) | 1253 | if (left.value > 0) |
1132 | { | 1254 | { |
1133 | if (n->dead_clean_task != GNUNET_SCHEDULER_NO_TASK) | 1255 | if (n->dead_clean_task != GNUNET_SCHEDULER_NO_TASK) |
@@ -1157,16 +1279,6 @@ consider_free_neighbour (struct Neighbour *n) | |||
1157 | 1279 | ||
1158 | 1280 | ||
1159 | /** | 1281 | /** |
1160 | * Check if we have encrypted messages for the specified neighbour | ||
1161 | * pending, and if so, check with the transport about sending them | ||
1162 | * out. | ||
1163 | * | ||
1164 | * @param n neighbour to check. | ||
1165 | */ | ||
1166 | static void process_encrypted_neighbour_queue (struct Neighbour *n); | ||
1167 | |||
1168 | |||
1169 | /** | ||
1170 | * Function called when the transport service is ready to | 1282 | * Function called when the transport service is ready to |
1171 | * receive an encrypted message for the respective peer | 1283 | * receive an encrypted message for the respective peer |
1172 | * | 1284 | * |
@@ -1330,43 +1442,6 @@ do_decrypt (struct Neighbour *n, | |||
1330 | 1442 | ||
1331 | 1443 | ||
1332 | /** | 1444 | /** |
1333 | * Encrypt size bytes from in and write the result to out. Use the | ||
1334 | * key for outbound traffic of the given neighbour. | ||
1335 | * | ||
1336 | * @param n neighbour we are sending to | ||
1337 | * @param iv initialization vector to use | ||
1338 | * @param in ciphertext | ||
1339 | * @param out plaintext | ||
1340 | * @param size size of in/out | ||
1341 | * @return GNUNET_OK on success | ||
1342 | */ | ||
1343 | static int | ||
1344 | do_encrypt (struct Neighbour *n, | ||
1345 | const GNUNET_HashCode * iv, | ||
1346 | const void *in, void *out, size_t size) | ||
1347 | { | ||
1348 | if (size != (uint16_t) size) | ||
1349 | { | ||
1350 | GNUNET_break (0); | ||
1351 | return GNUNET_NO; | ||
1352 | } | ||
1353 | GNUNET_assert (size == | ||
1354 | GNUNET_CRYPTO_aes_encrypt (in, | ||
1355 | (uint16_t) size, | ||
1356 | &n->encrypt_key, | ||
1357 | (const struct | ||
1358 | GNUNET_CRYPTO_AesInitializationVector | ||
1359 | *) iv, out)); | ||
1360 | #if DEBUG_CORE | ||
1361 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
1362 | "Encrypted %u bytes for `%4s' using key %u\n", size, | ||
1363 | GNUNET_i2s (&n->peer), n->encrypt_key.crc32); | ||
1364 | #endif | ||
1365 | return GNUNET_OK; | ||
1366 | } | ||
1367 | |||
1368 | |||
1369 | /** | ||
1370 | * Select messages for transmission. This heuristic uses a combination | 1445 | * Select messages for transmission. This heuristic uses a combination |
1371 | * of earliest deadline first (EDF) scheduling (with bounded horizon) | 1446 | * of earliest deadline first (EDF) scheduling (with bounded horizon) |
1372 | * and priority-based discard (in case no feasible schedule exist) and | 1447 | * and priority-based discard (in case no feasible schedule exist) and |
@@ -2645,9 +2720,16 @@ handle_pong (struct Neighbour *n, | |||
2645 | cnm.peer = n->peer; | 2720 | cnm.peer = n->peer; |
2646 | send_to_all_clients (&cnm.header, GNUNET_YES, GNUNET_CORE_OPTION_SEND_CONNECT); | 2721 | send_to_all_clients (&cnm.header, GNUNET_YES, GNUNET_CORE_OPTION_SEND_CONNECT); |
2647 | process_encrypted_neighbour_queue (n); | 2722 | process_encrypted_neighbour_queue (n); |
2648 | break; | 2723 | /* fall-through! */ |
2649 | case PEER_STATE_KEY_CONFIRMED: | 2724 | case PEER_STATE_KEY_CONFIRMED: |
2650 | /* duplicate PONG? */ | 2725 | n->last_activity = GNUNET_TIME_absolute_get (); |
2726 | if (n->keep_alive_task != GNUNET_SCHEDULER_NO_TASK) | ||
2727 | GNUNET_SCHEDULER_cancel (sched, n->keep_alive_task); | ||
2728 | n->keep_alive_task | ||
2729 | = GNUNET_SCHEDULER_add_delayed (sched, | ||
2730 | GNUNET_TIME_relative_divide (GNUNET_CONSTANTS_IDLE_CONNECTION_TIMEOUT, 2), | ||
2731 | &send_keep_alive, | ||
2732 | n); | ||
2651 | break; | 2733 | break; |
2652 | default: | 2734 | default: |
2653 | GNUNET_break (0); | 2735 | GNUNET_break (0); |
@@ -2711,8 +2793,9 @@ handle_set_key (struct Neighbour *n, const struct SetKeyMessage *m) | |||
2711 | sizeof (struct GNUNET_PeerIdentity))) | 2793 | sizeof (struct GNUNET_PeerIdentity))) |
2712 | { | 2794 | { |
2713 | GNUNET_log (GNUNET_ERROR_TYPE_WARNING, | 2795 | GNUNET_log (GNUNET_ERROR_TYPE_WARNING, |
2714 | _("Received `%s' message that was not for me. Ignoring.\n"), | 2796 | _("Received `%s' message that was for `%s', not for me. Ignoring.\n"), |
2715 | "SET_KEY"); | 2797 | "SET_KEY", |
2798 | GNUNET_i2s (&m->target)); | ||
2716 | return; | 2799 | return; |
2717 | } | 2800 | } |
2718 | if ((ntohl (m->purpose.size) != | 2801 | if ((ntohl (m->purpose.size) != |
@@ -3106,6 +3189,13 @@ handle_encrypted_message (struct Neighbour *n, | |||
3106 | NULL, NULL); | 3189 | NULL, NULL); |
3107 | } | 3190 | } |
3108 | n->last_activity = GNUNET_TIME_absolute_get (); | 3191 | n->last_activity = GNUNET_TIME_absolute_get (); |
3192 | if (n->keep_alive_task != GNUNET_SCHEDULER_NO_TASK) | ||
3193 | GNUNET_SCHEDULER_cancel (sched, n->keep_alive_task); | ||
3194 | n->keep_alive_task | ||
3195 | = GNUNET_SCHEDULER_add_delayed (sched, | ||
3196 | GNUNET_TIME_relative_divide (GNUNET_CONSTANTS_IDLE_CONNECTION_TIMEOUT, 2), | ||
3197 | &send_keep_alive, | ||
3198 | n); | ||
3109 | off = sizeof (struct EncryptedMessage); | 3199 | off = sizeof (struct EncryptedMessage); |
3110 | deliver_messages (n, buf, size, off); | 3200 | deliver_messages (n, buf, size, off); |
3111 | } | 3201 | } |
@@ -3233,6 +3323,13 @@ handle_transport_receive (void *cls, | |||
3233 | n->last_activity = now; | 3323 | n->last_activity = now; |
3234 | if (!up) | 3324 | if (!up) |
3235 | n->time_established = now; | 3325 | n->time_established = now; |
3326 | if (n->keep_alive_task != GNUNET_SCHEDULER_NO_TASK) | ||
3327 | GNUNET_SCHEDULER_cancel (sched, n->keep_alive_task); | ||
3328 | n->keep_alive_task | ||
3329 | = GNUNET_SCHEDULER_add_delayed (sched, | ||
3330 | GNUNET_TIME_relative_divide (GNUNET_CONSTANTS_IDLE_CONNECTION_TIMEOUT, 2), | ||
3331 | &send_keep_alive, | ||
3332 | n); | ||
3236 | } | 3333 | } |
3237 | } | 3334 | } |
3238 | 3335 | ||
diff --git a/src/include/gnunet_time_lib.h b/src/include/gnunet_time_lib.h index 8336a04fd..fa443a463 100644 --- a/src/include/gnunet_time_lib.h +++ b/src/include/gnunet_time_lib.h | |||
@@ -204,6 +204,20 @@ struct GNUNET_TIME_Relative GNUNET_TIME_relative_min (struct | |||
204 | struct | 204 | struct |
205 | GNUNET_TIME_Relative t2); | 205 | GNUNET_TIME_Relative t2); |
206 | 206 | ||
207 | |||
208 | /** | ||
209 | * Return the maximum of two relative time values. | ||
210 | * | ||
211 | * @param t1 first timestamp | ||
212 | * @param t2 other timestamp | ||
213 | * @return timestamp that is larger | ||
214 | */ | ||
215 | struct GNUNET_TIME_Relative GNUNET_TIME_relative_max (struct | ||
216 | GNUNET_TIME_Relative | ||
217 | t1, | ||
218 | struct | ||
219 | GNUNET_TIME_Relative t2); | ||
220 | |||
207 | /** | 221 | /** |
208 | * Return the minimum of two absolute time values. | 222 | * Return the minimum of two absolute time values. |
209 | * | 223 | * |
@@ -314,6 +328,19 @@ struct GNUNET_TIME_Relative GNUNET_TIME_relative_multiply (struct | |||
314 | factor); | 328 | factor); |
315 | 329 | ||
316 | /** | 330 | /** |
331 | * Divide relative time by a given factor. | ||
332 | * | ||
333 | * @param rel some duration | ||
334 | * @param factor integer to divide by | ||
335 | * @return FOREVER if rel=FOREVER or factor==0; otherwise rel/factor | ||
336 | */ | ||
337 | struct GNUNET_TIME_Relative GNUNET_TIME_relative_divide (struct | ||
338 | GNUNET_TIME_Relative | ||
339 | rel, | ||
340 | unsigned int | ||
341 | factor); | ||
342 | |||
343 | /** | ||
317 | * Add relative times together. | 344 | * Add relative times together. |
318 | * | 345 | * |
319 | * @param a1 some relative time | 346 | * @param a1 some relative time |
diff --git a/src/util/time.c b/src/util/time.c index 36a3c8631..ce2f9517f 100644 --- a/src/util/time.c +++ b/src/util/time.c | |||
@@ -137,6 +137,22 @@ GNUNET_TIME_relative_min (struct | |||
137 | } | 137 | } |
138 | 138 | ||
139 | 139 | ||
140 | /** | ||
141 | * Return the maximum of two relative time values. | ||
142 | * | ||
143 | * @param t1 first timestamp | ||
144 | * @param t2 other timestamp | ||
145 | * @return timestamp that is larger | ||
146 | */ | ||
147 | struct GNUNET_TIME_Relative | ||
148 | GNUNET_TIME_relative_max (struct | ||
149 | GNUNET_TIME_Relative | ||
150 | t1, struct GNUNET_TIME_Relative t2) | ||
151 | { | ||
152 | return (t1.value > t2.value) ? t1 : t2; | ||
153 | } | ||
154 | |||
155 | |||
140 | 156 | ||
141 | /** | 157 | /** |
142 | * Return the minimum of two relative time values. | 158 | * Return the minimum of two relative time values. |
@@ -277,6 +293,26 @@ GNUNET_TIME_relative_multiply (struct GNUNET_TIME_Relative rel, | |||
277 | 293 | ||
278 | 294 | ||
279 | /** | 295 | /** |
296 | * Divide relative time by a given factor. | ||
297 | * | ||
298 | * @param rel some duration | ||
299 | * @param factor integer to divide by | ||
300 | * @return FOREVER if rel=FOREVER or factor==0; otherwise rel/factor | ||
301 | */ | ||
302 | struct GNUNET_TIME_Relative | ||
303 | GNUNET_TIME_relative_divide (struct GNUNET_TIME_Relative rel, | ||
304 | unsigned int factor) | ||
305 | { | ||
306 | struct GNUNET_TIME_Relative ret; | ||
307 | if ( (factor == 0) || | ||
308 | (rel.value == GNUNET_TIME_UNIT_FOREVER_REL.value) ) | ||
309 | return GNUNET_TIME_UNIT_FOREVER_REL; | ||
310 | ret.value = rel.value / (unsigned long long) factor; | ||
311 | return ret; | ||
312 | } | ||
313 | |||
314 | |||
315 | /** | ||
280 | * Calculate the estimate time of arrival/completion | 316 | * Calculate the estimate time of arrival/completion |
281 | * for an operation. | 317 | * for an operation. |
282 | * | 318 | * |