old format

This commit is contained in:
randogoth 2025-09-14 15:55:46 +03:00
parent 5bcfd63d11
commit 8e9bbdebf4

View file

@ -27,89 +27,117 @@ DEVICE_ID = 'QWR70154'
def get(length=10, type='hex16', timeout=1.5): def get(length=10, type='hex16', timeout=1.5):
""" """
Fetch data from the Randonautica Quantum Random Numbers JSON API. Fetch data from the Randonautica Quantum Random Numbers JSON API.
Falls back to local randomness if the API is unavailable.
length (int): number of values/bytes to get Non-breaking behavior:
type (str): one of {'hex16','int32','uniform','normal','base64'} - length is ONLY used for type='hex16' (number of hex BYTES).
timeout (float): HTTP timeout in seconds - All other types return exactly ONE value.
Returns by type:
- 'uniform' -> float in [0,1)
- 'normal' -> float (Gaussian)
- 'int32' -> signed 32-bit int
- 'base64' -> str (single base64 blob)
- 'hex16' -> str of length 2*length (hex chars)
Falls back to local randomness if the API is unavailable or returns
an unexpected shape.
""" """
if type not in typeS: if type not in typeS:
raise Exception("type must be one of %s" % list(typeS.keys())) raise Exception("type must be one of %s" % list(typeS.keys()))
# Try QRNG first # Build URL. `length` only meaningful for hex16; QRNG ignores it for others.
url = URL + typeS[type] + '?' + urlencode({ params = {'device_id': DEVICE_ID}
'device_id': DEVICE_ID, if type == 'hex16':
'length': length, params['length'] = int(length)
})
url = URL + typeS[type] + '?' + urlencode(params)
# Try API first
try: try:
data = __get_json(url, timeout=timeout) data = __get_json(url, timeout=timeout)
# Validate expected shape minimally return _parse_api_value(type, data, length)
if isinstance(data, dict) and "data" in data and isinstance(data["data"], list):
return data
# If shape unexpected, fall back
except Exception: except Exception:
pass # Network/parse/shape error -> fallback
return _fallback_value(type, length)
# Fallback path: generate locally with best available primitives
return _fallback_response(type=type, length=length)
def __get_json(url, timeout=1.5): def __get_json(url, timeout=1.5):
resp = requests.get(url, verify=False, timeout=timeout) resp = requests.get(url, verify=False, timeout=timeout)
resp.raise_for_status() resp.raise_for_status()
return resp.json() return resp.json()
def _fallback_response(type: str, length: int): # ----- Helpers -----
def _parse_api_value(t, payload, length):
""" """
Produce a response object that mirrors the QRNG API: Accept common shapes:
{ - {"data":[...]} (usual)
"type": <type>, - bare number/string
"length": <length>, - single-item list
"data": [...], Enforce return types listed in get() docstring.
"success": true,
"source": "fallback"
}
""" """
if type == 'uniform': # Helper to pull first item from possible shapes
# Uniform floats in [0.0, 1.0) def first_item(x):
data = [random.random() for _ in range(length)] if isinstance(x, dict) and "data" in x and x["data"]:
return x["data"][0] if isinstance(x["data"], list) else x["data"]
if isinstance(x, list) and x:
return x[0]
return x
elif type == 'normal': if t == 'uniform':
# Standard normal N(0,1) v = float(first_item(payload))
data = [random.gauss(0.0, 1.0) for _ in range(length)] if not (0.0 <= v < 1.0):
raise ValueError("uniform out of range")
return v
elif type == 'int32': if t == 'normal':
# 32-bit signed ints (match typical randint32 range: [-2^31, 2^31-1]) return float(first_item(payload))
# secrets is preferable to random for stronger entropy.
data = []
for _ in range(length):
u = secrets.randbits(32)
# Convert to signed 32-bit
if u & (1 << 31):
u = u - (1 << 32)
data.append(u)
elif type == 'hex16': if t == 'int32':
# Hex string of length*2 bytes? The API name suggests 16-bit hex bytes. v = int(first_item(payload))
# Historically this endpoint returns hex bytes; emulate by returning a single hex string # normalize to signed 32-bit just in case
# of 2*length characters (each byte -> 2 hex chars). If API returns list, adapt as needed. v = ((v + 2**31) % 2**32) - 2**31
# Here we return a list of hex byte strings to match "data": [...] return v
data = [secrets.token_hex(1) for _ in range(length)]
elif type == 'base64': if t == 'base64':
# Return base64-encoded random bytes; match as a list of base64 strings v = first_item(payload)
data = [] if not isinstance(v, str):
for _ in range(length): # Sometimes APIs deliver bytes; normalize to str
b = os.urandom(16) # 16 bytes per element; adjust if your API uses a different size v = str(v)
data.append(base64.b64encode(b).decode('ascii')) return v
if t == 'hex16':
# Expect list of hex bytes or a single hex string; normalize to one string of 2*length chars
d = payload.get("data", payload) if isinstance(payload, dict) else payload
if isinstance(d, list):
s = ''.join(str(b) for b in d)
else: else:
# Should never hit due to earlier check s = str(d)
data = [] # If API returned more/less, trim/pad to exact 2*length for strict non-breaking behavior
want = max(0, int(length)) * 2
if len(s) < want:
# pad with extra local entropy to reach length
s += secrets.token_hex((want - len(s) + 1) // 2)[:(want - len(s))]
elif len(s) > want and want > 0:
s = s[:want]
return s
return { # Shouldn't reach here
"type": type, raise ValueError("Unknown type")
"length": length,
"data": data, def _fallback_value(t, length):
"success": True, if t == 'uniform':
"source": "pseudo" return random.random()
} if t == 'normal':
return random.gauss(0.0, 1.0)
if t == 'int32':
u = secrets.randbits(32)
# signed 32-bit
return u - (1 << 32) if (u & (1 << 31)) else u
if t == 'base64':
# 16 random bytes
return base64.b64encode(os.urandom(16)).decode('ascii')
if t == 'hex16':
# exactly `length` bytes -> 2*length hex chars
return secrets.token_hex(int(length))
# Shouldn't reach here
raise ValueError("Unknown type")