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 import binascii, os, random, struct, base64, httplib
from hashlib import md5, sha256 from hashlib import md5, sha256
from itertools import permutations from itertools import permutations
from threading import Lock
from calibre.srv.errors import HTTPAuthRequired, HTTPSimpleResponse, InvalidCredentials from calibre.srv.errors import HTTPAuthRequired, HTTPSimpleResponse, InvalidCredentials
from calibre.srv.http_request import parse_uri 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 from calibre.utils.monotonic import monotonic
MAX_AGE_SECONDS = 3600 MAX_AGE_SECONDS = 3600
nonce_counter, nonce_counter_lock = 0, Lock()
def as_bytestring(x): def as_bytestring(x):
if not isinstance(x, bytes): if not isinstance(x, bytes):
@ -34,12 +36,18 @@ def base64_decode(s):
def synthesize_nonce(key_order, realm, secret, timestamp=None): def synthesize_nonce(key_order, realm, secret, timestamp=None):
''' '''
Create a nonce. Can be used for either digest or cookie based auth. 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 timestamp, server secret and realm. This allows the timestamp to be
validated and stale nonce's to be rejected. validated and stale nonce's to be rejected.
''' '''
if timestamp is None: 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)) h = sha256_hex(key_order.format(timestamp, realm, secret))
nonce = ':'.join((timestamp, h)) nonce = ':'.join((timestamp, h))
return nonce 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): def is_nonce_stale(nonce, max_age_seconds=MAX_AGE_SECONDS):
try: 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() return timestamp + max_age_seconds < monotonic()
except Exception: except Exception:
pass pass

View File

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

View File

@ -26,17 +26,20 @@
#ifdef _MSC_VER #ifdef _MSC_VER
#include <Windows.h> #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) { static PyObject* monotonic(PyObject *self, PyObject *args) {
if (!QueryPerformanceCounter(&ts)) { PyErr_SetFromWindowsErr(0); return NULL; } return PyFloat_FromDouble(((double)GetTickCount64())/SEC_TO_MS);
return PyFloat_FromDouble(((double)ts.QuadPart)/frequency.QuadPart);
} }
/* 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__) #elif defined(__APPLE__)
#include <mach/mach_time.h> #include <mach/mach_time.h>
static mach_timebase_info_data_t timebase = {0}; static mach_timebase_info_data_t timebase = {0};
@ -72,7 +75,7 @@ PyMODINIT_FUNC
initmonotonic(void) { initmonotonic(void) {
PyObject *m; PyObject *m;
#ifdef _MSC_VER #ifdef _MSC_VER
if(!QueryPerformanceFrequency(&frequency)) { PyErr_SetFromWindowsErr(0); return; } /* if(!QueryPerformanceFrequency(&frequency)) { PyErr_SetFromWindowsErr(0); return; } */
#endif #endif
#ifdef __APPLE__ #ifdef __APPLE__
mach_timebase_info(&timebase); mach_timebase_info(&timebase);