Dont use QueryPerfomanceCounter for monotonic() as it is wildly inaccurate, despite what the documentation from MuppetSoft says

This commit is contained in:
Kovid Goyal 2015-10-29 09:37:59 +05:30
parent 06c5f0c1f2
commit b7f0999949
3 changed files with 25 additions and 14 deletions

View File

@ -9,6 +9,7 @@ __copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
import binascii, os, random, struct, base64, httplib
from hashlib import md5, sha256
from itertools import permutations
from threading import Lock
from calibre.srv.errors import HTTPAuthRequired, HTTPSimpleResponse, InvalidCredentials
from calibre.srv.http_request import parse_uri
@ -16,6 +17,7 @@ from calibre.srv.utils import parse_http_dict, encode_path
from calibre.utils.monotonic import monotonic
MAX_AGE_SECONDS = 3600
nonce_counter, nonce_counter_lock = 0, Lock()
def as_bytestring(x):
if not isinstance(x, bytes):
@ -34,12 +36,18 @@ def base64_decode(s):
def synthesize_nonce(key_order, realm, secret, timestamp=None):
'''
Create a nonce. Can be used for either digest or cookie based auth.
The nonce is of the form timestamp:hash with has being a hash of the
The nonce is of the form timestamp:hash with hash being a hash of the
timestamp, server secret and realm. This allows the timestamp to be
validated and stale nonce's to be rejected.
'''
if timestamp is None:
timestamp = binascii.hexlify(struct.pack(b'!d', float(monotonic())))
global nonce_counter
with nonce_counter_lock:
nonce_counter += 1
# The resolution of monotonic() on windows is very low (10s of
# milliseconds) so to ensure nonce values are not re-used, we have a
# global counter
timestamp = binascii.hexlify(struct.pack(b'!dQ', float(monotonic()), nonce_counter))
h = sha256_hex(key_order.format(timestamp, realm, secret))
nonce = ':'.join((timestamp, h))
return nonce
@ -51,7 +59,7 @@ def validate_nonce(key_order, nonce, realm, secret):
def is_nonce_stale(nonce, max_age_seconds=MAX_AGE_SECONDS):
try:
timestamp = struct.unpack(b'!d', binascii.unhexlify(as_bytestring(nonce.partition(':')[0])))[0]
timestamp = struct.unpack(b'!dQ', binascii.unhexlify(as_bytestring(nonce.partition(':')[0])))[0]
return timestamp + max_age_seconds < monotonic()
except Exception:
pass

View File

@ -217,8 +217,8 @@ class LoopTest(BaseTest):
b = monotonic()
self.assertGreaterEqual(b, a)
a = monotonic()
time.sleep(0.01)
time.sleep(0.1)
b = monotonic()
self.assertGreaterEqual(b, a)
self.assertGreaterEqual(b - a, 0.01)
self.assertLessEqual(b - a, 0.02)
self.assertGreaterEqual(b - a, 0.09)
self.assertLessEqual(b - a, 0.2)

View File

@ -26,17 +26,20 @@
#ifdef _MSC_VER
#include <Windows.h>
static LARGE_INTEGER frequency = {0}, ts = {0};
/* static PyObject* monotonic(PyObject *self, PyObject *args) { */
/* return PyFloat_FromDouble(((double)GetTickCount64())/SEC_TO_MS); */
/* } */
static PyObject* monotonic(PyObject *self, PyObject *args) {
if (!QueryPerformanceCounter(&ts)) { PyErr_SetFromWindowsErr(0); return NULL; }
return PyFloat_FromDouble(((double)ts.QuadPart)/frequency.QuadPart);
return PyFloat_FromDouble(((double)GetTickCount64())/SEC_TO_MS);
}
/* QueryPerformanceCounter() is wildly inaccurate, so we use the more stable
* the lower resolution GetTickCount64() (this is what python 3.x uses)
* static LARGE_INTEGER frequency = {0}, ts = {0};
* static PyObject* monotonic(PyObject *self, PyObject *args) {
* if (!QueryPerformanceCounter(&ts)) { PyErr_SetFromWindowsErr(0); return NULL; }
* return PyFloat_FromDouble(((double)ts.QuadPart)/frequency.QuadPart);
* }
*/
#elif defined(__APPLE__)
#include <mach/mach_time.h>
static mach_timebase_info_data_t timebase = {0};
@ -72,7 +75,7 @@ PyMODINIT_FUNC
initmonotonic(void) {
PyObject *m;
#ifdef _MSC_VER
if(!QueryPerformanceFrequency(&frequency)) { PyErr_SetFromWindowsErr(0); return; }
/* if(!QueryPerformanceFrequency(&frequency)) { PyErr_SetFromWindowsErr(0); return; } */
#endif
#ifdef __APPLE__
mach_timebase_info(&timebase);