1# -*- coding: utf-8 -*-
2# SPDX-License-Identifier: BSD-2-Clause
3"""Access libntp funtions from Python."""
4from __future__ import absolute_import
5import ctypes
6import ctypes.util
7import errno
8import os
9import os.path
10import sys
11import ntp.poly
12
13LIB = 'ntpc'
14
15
16def _fmt():
17    """Produce library naming scheme."""
18    if sys.platform.startswith('darwin'):
19        return 'lib%s.dylib'
20    if sys.platform.startswith('win32'):
21        return '%s.dll'
22    if sys.platform.startswith('cygwin'):
23        return 'lib%s.dll'
24    return 'lib%s.so'
25
26
27def _importado():
28    """Load the ntpc library or throw an OSError trying."""
29    ntpc_paths = []         # places to look
30
31    j = __file__.split(os.sep)[:-1]
32    ntpc_paths.append(os.sep.join(j + [_fmt() % LIB]))
33
34    ntpc_path = ctypes.util.find_library(LIB)
35    if ntpc_path:
36        ntpc_paths.append(ntpc_path)
37
38    return _dlo(ntpc_paths)
39
40
41def _dlo(paths):
42    """Try opening library from a list."""
43    for ntpc_path in paths:
44        try:
45            lib = ctypes.CDLL(ntpc_path, use_errno=True)
46            wrap_version = "@NTPSEC_VERSION_EXTENDED@"
47            clib_version = ntp.poly.polystr(ctypes.c_char_p.in_dll(lib, 'version').value)
48            if clib_version != wrap_version:
49                sys.stderr.write("ntp.ntpc wrong version '%s' != '%s'\n" % (clib_version, wrap_version))
50            return lib
51        except OSError:
52            pass
53    raise OSError("Can't find %s library" % LIB)
54
55
56_ntpc = _importado()
57progname = ctypes.c_char_p.in_dll(_ntpc, 'progname')
58# log_sys = ctypes.c_bool.in_dll(_ntpc, 'syslogit')
59# log_term = ctypes.c_bool.in_dll(_ntpc, 'termlogit')
60# log_pid = ctypes.c_bool.in_dll(_ntpc, 'termlogit_pid')
61# log_time = ctypes.c_bool.in_dll(_ntpc, 'msyslog_include_timestamp')
62
63TYPE_SYS = ctypes.c_int.in_dll(_ntpc, 'SYS_TYPE').value
64TYPE_PEER = ctypes.c_int.in_dll(_ntpc, 'PEER_TYPE').value
65TYPE_CLOCK = ctypes.c_int.in_dll(_ntpc, 'CLOCK_TYPE').value
66
67
68def checkname(name):
69    """Check if name is a valid algorithm name."""
70    _ntpc.do_checkname.restype = ctypes.c_int
71    mid_bytes = ntp.poly.polybytes(name)
72    _ntpc.do_checkname.argtypes = [ctypes.c_char_p]
73    return _ntpc.do_checkname(mid_bytes)
74
75
76def mac(data, key, name):
77    """Compute HMAC or CMAC from data, key, and algorithm name."""
78    resultlen = ctypes.c_size_t()
79    result = (ctypes.c_char * 64)()
80    result.value = b'\0' * 64
81    _ntpc.do_mac.restype = None
82    _ntpc.do_mac(ntp.poly.polybytes(name),
83                 ntp.poly.polybytes(data), len(data),
84                 ntp.poly.polybytes(key), len(key),
85                 ctypes.byref(result), ctypes.byref(resultlen))
86    return result.value
87
88
89def setprogname(in_string):
90    """Set program name for logging purposes."""
91    mid_bytes = ntp.poly.polybytes(in_string)
92    _setprogname(mid_bytes)
93
94
95def _lfp_wrap(callback, in_string):
96    """NTP l_fp to other Python-style format."""
97    mid_bytes = ntp.poly.polybytes(in_string)
98    out_value = callback(mid_bytes)
99    err = ctypes.get_errno()
100    if err == errno.EINVAL:
101        raise ValueError('ill-formed hex date')
102    return out_value
103
104
105def statustoa(i_type, i_st):
106    """Convert a time stamp to something readable."""
107    mid_str = _statustoa(i_type, i_st)
108    return ntp.poly.polystr(mid_str)
109
110
111def prettydate(in_string):
112    """Convert a time stamp to something readable."""
113    mid_str = _lfp_wrap(_prettydate, in_string)
114    return ntp.poly.polystr(mid_str)
115
116
117def lfptofloat(in_string):
118    """NTP l_fp to Python-style float time."""
119    return _lfp_wrap(_lfptofloat, in_string)
120
121
122def msyslog(level, in_string):
123    """Log send a message to terminal or output."""
124    mid_bytes = ntp.poly.polybytes(in_string)
125    _msyslog(level, mid_bytes)
126
127
128# Set return type and argument types of hidden ffi handlers
129_msyslog = _ntpc.msyslog
130_msyslog.restype = None
131_msyslog.argtypes = [ctypes.c_int, ctypes.c_char_p]
132
133_setprogname = _ntpc.ntpc_setprogname
134_setprogname.restype = None
135_setprogname.argtypes = [ctypes.c_char_p]
136
137_prettydate = _ntpc.ntpc_prettydate
138_prettydate.restype = ctypes.c_char_p
139_prettydate.argtypes = [ctypes.c_char_p]
140
141_lfptofloat = _ntpc.ntpc_lfptofloat
142_lfptofloat.restype = ctypes.c_double
143_lfptofloat.argtypes = [ctypes.c_char_p]
144
145# Status string display from peer status word.
146_statustoa = _ntpc.statustoa
147_statustoa.restype = ctypes.c_char_p
148_statustoa.argtypes = [ctypes.c_int, ctypes.c_int]
149
150# Set time to nanosecond precision.
151set_tod = _ntpc.ntpc_set_tod
152set_tod.restype = ctypes.c_int
153set_tod.argtypes = [ctypes.c_int, ctypes.c_int]
154
155# Adjust system time by slewing.
156adj_systime = _ntpc.ntpc_adj_systime
157adj_systime.restype = ctypes.c_bool
158adj_systime.argtypes = [ctypes.c_double]
159
160# Adjust system time by stepping.
161step_systime = _ntpc.ntpc_step_systime
162step_systime.restype = ctypes.c_bool
163step_systime.argtypes = [ctypes.c_double]
164