7 #include <satcat5/ptp_client.h>
12 namespace log = satcat5::log;
16 using satcat5::ptp::ClientMode;
17 using satcat5::ptp::ClientState;
19 using satcat5::ptp::DispatchTo;
31 #define div_round satcat5::util::div_round<unsigned>
34 #ifndef SATCAT5_PTP_DOMAIN
35 #define SATCAT5_PTP_DOMAIN 0
38 #ifndef SATCAT5_PTP_SDO_ID
39 #define SATCAT5_PTP_SDO_ID 0
42 #ifndef SATCAT5_PTP_PORT
43 #define SATCAT5_PTP_PORT 1
47 #ifndef SATCAT5_PTP_RATE
48 #define SATCAT5_PTP_RATE 3
52 #ifndef SATCAT5_PTP_RMAX
53 #define SATCAT5_PTP_RMAX 8
59 #ifndef SATCAT5_SPTP_ENABLE
60 #define SATCAT5_SPTP_ENABLE 1
66 #ifndef SATCAT5_UTC_OFFSET
67 #define SATCAT5_UTC_OFFSET 37
71 static constexpr
unsigned DEBUG_VERBOSE = 0;
75 static constexpr u16 MSGLEN_ANNOUNCE = 64;
76 static constexpr u16 MSGLEN_SYNC = 44;
77 static constexpr u16 MSGLEN_DELAY_REQ = 44;
78 static constexpr u16 MSGLEN_FOLLOW_UP = 44;
79 static constexpr u16 MSGLEN_DELAY_RESP = 54;
80 static constexpr u16 MSGLEN_PDELAY_REQ = 54;
81 static constexpr u16 MSGLEN_PDELAY_RESP = 54;
82 static constexpr u16 MSGLEN_PDELAY_RFU = 54;
83 static constexpr u16 MSGLEN_SIGNALING = 44;
86 constexpr
inline DispatchTo broadcast_to(
const ClientMode& mode) {
87 return (mode == ClientMode::MASTER_L2)
88 ? DispatchTo::BROADCAST_L2
89 : DispatchTo::BROADCAST_L3;
92 const char* satcat5::ptp::to_string(satcat5::ptp::ClientMode mode) {
94 case ClientMode::DISABLED:
return "Disabled";
95 case ClientMode::MASTER_L2:
return "MasterL2";
96 case ClientMode::MASTER_L3:
return "MasterL3";
97 case ClientMode::SLAVE_ONLY:
return "SlaveOnly";
98 case ClientMode::SLAVE_SPTP:
return "SlaveSimple";
99 default:
return "Passive";
103 const char* satcat5::ptp::to_string(satcat5::ptp::ClientState state) {
105 case ClientState::DISABLED:
return "Disabled";
106 case ClientState::LISTENING:
return "Listening";
107 case ClientState::MASTER:
return "Master";
108 case ClientState::PASSIVE:
return "Passive";
109 default:
return "Slave";
117 : m_iface(ptp_iface, ip_dispatch)
118 , m_mode(ClientMode::DISABLED)
119 , m_state(ClientState::DISABLED)
121 , m_clock_local(satcat5::ptp::DEFAULT_CLOCK)
122 , m_clock_remote(satcat5::ptp::DEFAULT_CLOCK)
123 , m_current_source(satcat5::ptp::PORT_NONE)
124 , m_announce_count(0)
125 , m_announce_every(0)
131 , m_sync_rate(SATCAT5_PTP_RATE)
132 , m_pdelay_rate(SATCAT5_PTP_RATE)
136 , m_utc_offset(SATCAT5_UTC_OFFSET)
148 #if SATCAT5_ALLOW_DELETION
157 m_current_source = satcat5::ptp::PORT_NONE;
159 case ClientMode::MASTER_L2: m_state = ClientState::MASTER;
break;
160 case ClientMode::MASTER_L3: m_state = ClientState::MASTER;
break;
161 case ClientMode::SLAVE_ONLY: m_state = ClientState::LISTENING;
break;
162 #if SATCAT5_SPTP_ENABLE
163 case ClientMode::SLAVE_SPTP: m_state = ClientState::LISTENING;
break;
165 case ClientMode::PASSIVE: m_state = ClientState::PASSIVE;
break;
166 default: m_state = ClientState::DISABLED;
break;
171 if (mode == ClientMode::MASTER_L3) {
172 m_mcast_primary.
join(m_iface.igmp(), satcat5::ip::ADDR_PTP_PRIMARY);
173 m_mcast_pdelay.
join(m_iface.igmp(), satcat5::ip::ADDR_PTP_PDELAY);
175 m_mcast_primary.
leave(m_iface.igmp());
176 m_mcast_pdelay.
leave(m_iface.igmp());
183 inline int rate_clamp(
int rate) {
184 return (rate < SATCAT5_PTP_RMAX) ? rate : SATCAT5_PTP_RMAX;
189 m_announce_rate = rate_clamp(rate);
195 m_sync_rate = rate_clamp(rate);
201 m_pdelay_rate = rate_clamp(rate);
205 bool Client::send_sync_bcast() {
206 return send_sync(broadcast_to(m_mode), ++m_sync_id);
215 if (m_state != ClientState::MASTER)
return false;
220 return send_sync(DispatchTo::STORED, ++m_sync_id);
225 if (m_state == ClientState::DISABLED)
return;
229 if (DEBUG_VERBOSE > 1)
236 if (!ok || rcvd_len < hdr.
length
238 || hdr.
msglen() > Header::MAX_MSGLEN) {
239 log::Log(log::WARNING,
"PtpClient: Malformed header");
241 }
else if (hdr.
msglen() == 0) {
242 rcvd_unexpected(hdr);
247 u8 msg_buff[Header::MAX_MSGLEN];
257 while (next && !next->
tlv_rcvd(hdr, tlv, tmp)) {
258 next = m_tlv_list.
next(next);
265 switch (hdr.
type & 0x0F) {
267 case Header::TYPE_DELAY_REQ: rcvd_delay_req(hdr, msg);
break;
268 case Header::TYPE_PDELAY_REQ: rcvd_pdelay_req(hdr, msg);
break;
269 case Header::TYPE_FOLLOW_UP: rcvd_follow_up(hdr, msg);
break;
270 case Header::TYPE_PDELAY_RFU: rcvd_pdelay_follow_up(hdr, msg);
break;
271 case Header::TYPE_DELAY_RESP: rcvd_delay_resp(hdr, msg);
break;
272 case Header::TYPE_PDELAY_RESP: rcvd_pdelay_resp(hdr, msg);
break;
273 case Header::TYPE_ANNOUNCE: rcvd_announce(hdr, msg);
break;
278 if (m_state == ClientState::MASTER) {
282 if (m_announce_count) {
284 }
else if (m_announce_every && send_announce()) {
285 m_announce_count = m_announce_every - 1;
286 SATCAT5_CLOCK->busywait_usec(10);
291 }
else if (m_sync_every && send_sync_bcast()) {
292 m_sync_count = m_sync_every - 1;
294 }
else if (m_state == ClientState::SLAVE) {
295 if (SATCAT5_SPTP_ENABLE && m_mode == ClientMode::SLAVE_SPTP) {
297 send_delay_req_sptp();
302 }
else if (m_state == ClientState::PASSIVE) {
308 void Client::timer_reset() {
310 m_announce_count = 0;
311 m_announce_every = 0;
315 int gcd_rate = (m_sync_rate > m_announce_rate) ? m_sync_rate : m_announce_rate;
316 if (m_state == ClientState::MASTER && gcd_rate > INT_MIN) {
320 if (m_announce_rate > INT_MIN)
321 m_announce_every = 1u << (gcd_rate - m_announce_rate);
322 if (m_sync_rate > INT_MIN)
323 m_sync_every = 1u << (gcd_rate - m_sync_rate);
324 }
else if (m_state == ClientState::PASSIVE && m_pdelay_rate > INT_MIN) {
328 }
else if (m_state == ClientState::SLAVE) {
329 bool sptp_mode = SATCAT5_SPTP_ENABLE && (m_mode == ClientMode::SLAVE_SPTP);
330 if (sptp_mode && m_sync_rate > INT_MIN) {
343 void Client::cache_miss() {
350 if (DEBUG_VERBOSE > 0 || m_cache_wdog >= 50) {
351 log::Log(log::WARNING,
"PtpClient: Unmatched SeqID");
363 next = m_tlv_list.
next(next);
368 void Client::notify_if_complete(
const Measurement* meas) {
375 while (next && temp.
done()) {
377 next = m_tlv_list.
next(next);
384 void Client::client_timeout() {
385 log::Log(log::WARNING,
"PtpClient: Connection timeout.");
386 if (m_state == ClientState::SLAVE) {
388 m_state = ClientState::LISTENING;
394 if (DEBUG_VERBOSE > 0)
log::Log(log::DEBUG,
"PtpClient: Announcement");
399 s16 utc_offset = rd.read_s16();
405 if (m_state == ClientState::LISTENING) {
408 log::Log(log::INFO,
"PtpClient: Selected master.");
412 m_state = ClientState::SLAVE;
413 m_clock_remote = clk_info;
414 m_utc_offset = utc_offset;
416 }
else if (m_state == ClientState::MASTER) {
418 }
else if (m_state == ClientState::SLAVE && hdr.
src_port == m_current_source) {
420 m_clock_remote = clk_info;
421 m_utc_offset = utc_offset;
426 if (DEBUG_VERBOSE > 0)
log::Log(log::DEBUG,
"PtpClient: Sync");
427 bool mode_sptp = SATCAT5_SPTP_ENABLE && (m_mode == ClientMode::SLAVE_SPTP);
430 if (m_state == ClientState::SLAVE && hdr.
src_port == m_current_source) {
432 if (!mode_sptp) timer_reset();
435 if (m_cache_wdog) --m_cache_wdog;
444 auto meas = mode_sptp ? m_cache.
find(hdr) : m_cache.
push(hdr);
445 if (!meas) {cache_miss();
return;}
449 auto rcvd_2step = hdr.
flags & Header::FLAG_TWO_STEP;
453 if (rcvd_2step && rcvd_sptp) {
457 }
else if (rcvd_2step) {
462 if (send_delay_req(hdr.
seq_id))
469 if (DEBUG_VERBOSE > 0)
log::Log(log::DEBUG,
"PtpClient: Follow-up");
470 bool mode_sptp = SATCAT5_SPTP_ENABLE && (m_mode == ClientMode::SLAVE_SPTP);
473 if (m_state == ClientState::SLAVE && hdr.
src_port == m_current_source) {
485 notify_if_complete(meas);
486 }
else if (send_delay_req(hdr.
seq_id)) {
490 }
else {cache_miss();}
495 if (DEBUG_VERBOSE > 0)
log::Log(log::DEBUG,
"PtpClient: Delay request");
499 if (m_state == ClientState::MASTER) {
501 if (SATCAT5_SPTP_ENABLE && rcvd_sptp) {
505 send_delay_resp(hdr);
511 if (DEBUG_VERBOSE > 0)
log::Log(log::DEBUG,
"PtpClient: PDelay request");
513 if (m_state == ClientState::PASSIVE) {
514 send_pdelay_resp(hdr);
519 if (DEBUG_VERBOSE > 0)
log::Log(log::DEBUG,
"PtpClient: Delay response");
523 if (m_state == ClientState::SLAVE
524 && m_mode != ClientMode::SLAVE_SPTP
525 && hdr.
src_port == m_current_source) {
531 if (m_cache_wdog) --m_cache_wdog;
538 if (DEBUG_VERBOSE > 0)
541 notify_if_complete(meas);
542 }
else {cache_miss();}
547 if (DEBUG_VERBOSE > 0)
log::Log(log::DEBUG,
"PtpClient: PDelay response");
549 if (m_state == ClientState::PASSIVE) {
553 auto rcvd_2step = hdr.
flags & Header::FLAG_TWO_STEP;
565 if (!rcvd_2step) notify_if_complete(meas);
566 }
else {cache_miss();}
571 if (DEBUG_VERBOSE > 0)
log::Log(log::DEBUG,
"PtpClient: PDelay response follow up");
581 notify_if_complete(meas);
582 }
else {cache_miss();}
585 void Client::rcvd_unexpected(
const Header& hdr) {
590 Header Client::make_header(u8 type, u16 seq_id) {
595 hdr.
domain = SATCAT5_PTP_DOMAIN;
596 hdr.
sdo_id = SATCAT5_PTP_SDO_ID;
609 if (type == Header::TYPE_ANNOUNCE)
610 hdr.
flags |= Header::FLAG_PTP_TIMESCALE;
611 if (type == Header::TYPE_DELAY_REQ || type == Header::TYPE_DELAY_RESP)
612 hdr.
flags |= Header::FLAG_UNICAST;
613 if (SATCAT5_SPTP_ENABLE && m_mode == ClientMode::SLAVE_SPTP)
617 switch (type & 0x0F) {
619 case Header::TYPE_DELAY_REQ: hdr.
length = MSGLEN_DELAY_REQ;
break;
620 case Header::TYPE_PDELAY_REQ: hdr.
length = MSGLEN_PDELAY_REQ;
break;
621 case Header::TYPE_PDELAY_RESP: hdr.
length = MSGLEN_PDELAY_RESP;
break;
622 case Header::TYPE_FOLLOW_UP: hdr.
length = MSGLEN_FOLLOW_UP;
break;
623 case Header::TYPE_DELAY_RESP: hdr.
length = MSGLEN_DELAY_RESP;
break;
624 case Header::TYPE_PDELAY_RFU: hdr.
length = MSGLEN_PDELAY_RFU;
break;
625 case Header::TYPE_ANNOUNCE: hdr.
length = MSGLEN_ANNOUNCE;
break;
626 default: hdr.
length = 0;
break;
630 switch (type & 0x0F) {
631 case Header::TYPE_ANNOUNCE: hdr.
log_interval = (s8)(-m_announce_rate);
break;
633 case Header::TYPE_FOLLOW_UP: hdr.
log_interval = (s8)(-m_sync_rate);
break;
634 case Header::TYPE_DELAY_RESP: hdr.
log_interval = 0;
break;
641 bool Client::send_announce() {
642 if (DEBUG_VERBOSE > 1)
log::Log(log::DEBUG,
"PtpClient: send_announce");
646 Header hdr = make_header(Header::TYPE_ANNOUNCE, ++m_announce_id);
647 hdr.
length += tlv_send(hdr, 0);
649 if (!wr)
return false;
652 wr->write_u16(m_utc_offset);
659 bool Client::send_sync(DispatchTo addr, u16 seq_id, u16 flags, u64 tref) {
660 if (DEBUG_VERBOSE > 1)
log::Log(log::DEBUG,
"PtpClient: send_sync");
669 bool req_2step = send_sptp || (flags & Header::FLAG_TWO_STEP);
681 }
else if (t1 == TIME_ZERO) {
682 hdr.
flags |= Header::FLAG_TWO_STEP;
692 hdr.
length += tlv_send(hdr, 0);
694 if (!wr)
return false;
698 if (hdr.
flags & Header::FLAG_TWO_STEP) {
699 return wr->
write_finalize() && send_follow_up(addr, seq_id, flags, tref);
705 bool Client::send_follow_up(
706 satcat5::ptp::DispatchTo addr,
707 u16 seq_id, u16 flags, u64 tref)
709 if (DEBUG_VERBOSE > 1)
log::Log(log::DEBUG,
"PtpClient: send_follow_up");
713 if (t1 == TIME_ZERO)
log::Log(log::ERROR,
"PtpClient: Bad hardware timestamp.");
718 Header hdr = make_header(Header::TYPE_FOLLOW_UP, seq_id);
721 hdr.
length += tlv_send(hdr, 0);
723 if (!wr)
return false;
730 void Client::send_delay_req_sptp() {
731 if (DEBUG_VERBOSE > 1)
log::Log(log::DEBUG,
"PtpClient: send_delay_req_sptp");
734 unsigned timeout = 5 << m_sync_rate;
735 if (++m_request_wdog >= timeout) {
736 client_timeout();
return;
741 Header hdr = make_header(Header::TYPE_DELAY_REQ, ++m_sync_id);
744 auto meas = m_cache.
push(hdr);
749 bool Client::send_delay_req(u16 seq_id, u16 flags) {
750 if (DEBUG_VERBOSE > 1)
log::Log(log::DEBUG,
"PtpClient: send_delay_req");
761 Header hdr = make_header(Header::TYPE_DELAY_REQ, seq_id);
764 hdr.
length += tlv_send(hdr, 0);
766 if (!wr)
return false;
773 bool Client::send_delay_resp(
const Header& ref) {
774 if (DEBUG_VERBOSE > 1)
log::Log(log::DEBUG,
"PtpClient: send_delay_resp");
779 if (t4 == TIME_ZERO)
log::Log(log::ERROR,
"PtpClient: Bad hardware timestamp.");
784 Header hdr = make_header(Header::TYPE_DELAY_RESP, ref.
seq_id);
786 hdr.
length += tlv_send(hdr, 0);
788 if (!wr)
return false;
796 bool Client::send_pdelay_req() {
797 if (DEBUG_VERBOSE > 1)
log::Log(log::DEBUG,
"PtpClient: send_pdelay_req");
805 Header hdr = make_header(Header::TYPE_PDELAY_REQ, ++m_pdelay_id);
806 hdr.
length += tlv_send(hdr, 0);
808 if (!wr)
return false;
818 auto meas = m_cache.
push(hdr);
819 meas->
t1 = t1_actual;
826 if (DEBUG_VERBOSE > 1)
log::Log(log::DEBUG,
"PtpClient: send_pdelay_resp");
837 Header hdr = make_header(Header::TYPE_PDELAY_RESP, ref.
seq_id);
841 if (t3 == TIME_ZERO) {
849 hdr.
length += tlv_send(hdr, 0);
851 if (!wr)
return false;
857 if (hdr.
flags & Header::FLAG_TWO_STEP || t3 == TIME_ZERO) {
865 if (DEBUG_VERBOSE > 1)
log::Log(log::DEBUG,
"PtpClient: send_pdelay_follow_up");
872 Header hdr = make_header(Header::TYPE_PDELAY_RFU, ref.
seq_id);
879 hdr.
length += tlv_send(hdr, 0);
881 if (!wr)
return false;
891 , m_dstmac(satcat5::eth::MACADDR_NONE)
897 if (m_dstmac != satcat5::eth::MACADDR_NONE) {
904 , m_addr(client->get_iface(), satcat5::ip::PROTO_UDP)
910 if (m_addr.
ready()) {
Ephemeral Readable interface for a simple array.
Limited read of next N bytes.
bool read_bytes(unsigned nbytes, void *dst) override
Read 0 or more bytes into a buffer.
unsigned get_read_ready() const override
How many bytes can be read without blocking?
void read_finalize() override
Consume any remaining bytes in this frame, if applicable.
u8 read_u8()
One of many functions for reading integer/floating point values, see details.
Abstract API for writing byte-streams and packets.
void write_u8(u8 data)
One of many functions for writing integer/floating point values, see details.
virtual bool write_finalize()
Mark end of frame and release temporary working data.
void write_obj(const T &obj)
Templated wrapper for any object with the following method: void write_to(satcat5::io::Writeable* wr)...
bool ready() const override
Is this address object ready for use? Child MUST override this method.
Protocol handler and dispatch unit for Internet Protocol v4 (IPv4).
The Log class creates and formats one log message.
Log & write_obj(const T &obj)
Templated wrapper for custom output formatting.
Log & write(const char *str)
Formatting methods for various data types.
void timer_stop()
Stop all future notifications.
void timer_once(unsigned msec)
Configure a one-time notification after X milliseconds.
void timer_every(unsigned msec)
Configure a repeating notification every X milliseconds.
PTP callback accepts each complete measurement from the Source.
Client for the IEEE 1588-2019 Precision Time Protocol (PTP)
void timer_event() override
Child class MUST override this method.
void set_announce_rate(int rate)
Set the ANNOUNCE message rate to 2^N / sec.
void set_sync_rate(int rate)
Set the SYNC message rate to 2^N / sec.
void set_pdelay_rate(int rate)
Set the pdelay message rate to 0.9 x 2^N / sec.
bool send_sync_unicast(const satcat5::eth::MacAddr &mac, const satcat5::ip::Addr &ip=satcat5::ip::ADDR_NONE, const satcat5::eth::VlanTag &vtag=satcat5::eth::VTAG_NONE)
Send a unicast SYNC message to the designated address.
void ptp_rcvd(satcat5::io::LimitedRead &rd)
Dispatch calls this method for each incoming packet.
void set_mode(satcat5::ptp::ClientMode mode)
Mode and state accessors.
void ptp_callback(satcat5::ptp::Client *client)
Set the callback object for incoming messages.
satcat5::ptp::Time ptp_tx_start()
Accessors for one-step and two-step timestamps.
satcat5::ptp::Time ptp_tx_timestamp()
Accessors for one-step and two-step timestamps.
void store_addr(const satcat5::eth::MacAddr &mac, const satcat5::ip::Addr &ip=satcat5::ip::ADDR_NONE, const satcat5::eth::VlanTag &vtag=satcat5::eth::VTAG_NONE)
Set the address for use with DispatchTo::STORED.
satcat5::ptp::Time ptp_rx_timestamp()
Accessors for one-step and two-step timestamps.
void store_reply_addr()
Set the address for use with DispatchTo::STORED.
satcat5::io::Writeable * ptp_send(satcat5::ptp::DispatchTo where, unsigned num_bytes, u8 ptp_msg_type)
Send a PTP message to the designated address(es).
satcat5::ptp::Time ptp_time_now()
Accessors for one-step and two-step timestamps.
Generic API for network ports that support PTP.
satcat5::ptp::Measurement * find(const satcat5::ptp::Header &hdr, const satcat5::ptp::PortId &port)
Find the first matching measurement in the cache.
satcat5::ptp::Measurement * push(const satcat5::ptp::Header &hdr)
Create a new measurement, overwriting the oldest.
void notify_callbacks(const satcat5::ptp::Measurement &meas)
Notify all Callback objects of a new Measurement.
Helper class for sending unicast Sync messages to an L2 client.
void timer_event()
Child class MUST override this method.
SyncUnicastL2(satcat5::ptp::Client *client)
Create this object.
Helper class for sending unicast Sync messages to an L3 client.
SyncUnicastL3(satcat5::ptp::Client *client)
Create this object.
void timer_event()
Child class MUST override this method.
High-precision timestamp for use with PTP / IEEE1588.
bool read_from(satcat5::io::Readable *src)
Read the standard 10-byte timestamp from a PTP message (e.g., originTimestamp: u48 seconds + u32 nano...
u64 correction() const
Get the correctionField value in subnanoseconds.
Users should derive custom TLV objects from this base class.
virtual unsigned tlv_send(const satcat5::ptp::Header &hdr, satcat5::io::Writeable *wr)
Child class SHOULD override this method to append outgoing TLV(s).
virtual bool tlv_rcvd(const satcat5::ptp::Header &hdr, const satcat5::ptp::TlvHeader &tlv, satcat5::io::LimitedRead &rd)
Child class SHOULD override this method to read incoming TLV(s).
virtual void tlv_meas(satcat5::ptp::Measurement &meas)
Child class MAY override this method to read or modify each complete two-way handshake event.
T * next(const T *item) const
Fetch pointer to the next item.
Diagnostic logging to UART and/or Ethernet ports.
constexpr satcat5::ptp::Time TIME_ZERO(0LL)
Common time-related constants.
TLV metadata for the IEEE 1588-2019 Precision Time Protocol (PTP).
An Ethernet MAC address (with serializable interface).
constexpr u64 to_ptp_clockid() const
Convert to PTP Clock-ID (IEEE 1588-2008 7.5.2.2.2 Note 2).
Header contents for an 802.1Q Virtual-LAN tag.
void join(satcat5::igmp::Client *igmp, const satcat5::ip::Addr &addr)
Join a multicast group.
void leave(satcat5::igmp::Client *igmp)
Leave a multicast group.
IPv4 address is a 32-bit unsigned integer.
Clock configuration metadata for the ANNOUNCE message.
u64 grandmasterIdentity
Fields defined in Section 13.5.1, Table 43.
bool read_from(satcat5::io::Readable *rd)
Read clock information from a given source.
Timestamps and metadata for a two-way time-transfer handshake.
satcat5::ptp::Time t1
Timestamp T1 (A to B / Tx)
bool done() const
Is this measurement completed? (i.e., T1/T2/T3/T4 all known)
satcat5::ptp::Header ref
Reference header is copied from the initiating PTP message (i.e., SYNC or PDELAY_REQ) and used to mat...
satcat5::ptp::Time t2
Timestamp T2 (A to B / Rx)
satcat5::ptp::Time t4
Timestamp T4 (B to A / Rx)
satcat5::ptp::Time t3
Timestamp T3 (B to A / Tx)
Struct used for sourcePortIdentity and requestingPortIdentity.
TimeRef and TimeVal define the API for monotonic timers.
Miscellaneous mathematical utility functions.