# HG changeset patch
# User Andrew Janke <andrew@apjanke.net>
# Date 1571863153 14400
#      Wed Oct 23 16:39:13 2019 -0400
# Node ID cfa52532ea58a441c3cf9614a90aa16aeb2eb9a3
# Parent  aa39866f0d17819c30fe8d0d606b60187d3c5fe6
tcp/udp: normalize timevals so tv_usec <= 999,999

diff --git a/src/tcp/tcp_class.cc b/src/tcp/tcp_class.cc
--- a/src/tcp/tcp_class.cc
+++ b/src/tcp/tcp_class.cc
@@ -65,16 +65,35 @@ to_ip_str (const sockaddr_in *in)
   b[3] = (addr>>0)&0xff;
 
   std::stringstream n;
   n << b[0] << "." << b[1] << "." << b[2] << "." << b[3];
 
   return n.str ();
 }
 
+// Some systems (like macOS) reject tv_usec values > 999,999.
+// This function normalizes a timeval to have <= 999,999 usec by
+// carrying over excess usec to the tv_sec field.
+static struct timeval
+normalize_timeval (struct timeval in, const char *caller_fcn_name)
+{
+  struct timeval out;
+  
+  if (in.tv_sec < 0)
+    error ("%s: timeval tv_sec value may not be <0", caller_fcn_name);
+  if (in.tv_usec < 0)
+    error ("%s: timeval tv_usec value may not be <0", caller_fcn_name);
+  
+  out.tv_sec = in.tv_sec + (in.tv_usec / 1000000);
+  out.tv_usec = in.tv_usec % 1000000;
+
+  return out;
+}
+
 DEFINE_OV_TYPEID_FUNCTIONS_AND_DATA (octave_tcp, "octave_tcp", "octave_tcp");
 
 octave_tcp::octave_tcp (void)
 : fieldnames(8), fd (-1), timeout(-1), name("")
 {
   static bool type_registered = false;
 
   if (! type_registered)
@@ -255,16 +274,19 @@ octave_tcp::print_raw (std::ostream& os,
   os << "    remoteport: " << get_remote_port ();
   newline(os);
   os << "    remotehost: " << get_remote_addr ();
   newline(os);
   os << "     localport: " << get_local_port ();
   newline (os);
 }
 
+// readtimeout is in msec:
+//  less than 0             => wait for 1 second
+//  greater or equal to 0   => wait for readtimeout msec
 int
 octave_tcp::read (uint8_t *buf, unsigned int len, int readtimeout)
 {
   struct timeval tv;
 
   fd_set readfds;
 
   if (get_fd () < 0)
@@ -277,32 +299,26 @@ octave_tcp::read (uint8_t *buf, unsigned
   ssize_t read_retval = -1;
 
   // While not interrupted in blocking mode
   while (bytes_read < len)
     {
 
       OCTAVE_QUIT;
 
-      /* tv.tv_sec = timeout / 1000;
-       * tv.tv_usec = (timeout % 1000) * 1000;
-       */
-
       if (readtimeout < 0) 
         {
           tv.tv_sec = 1;
           tv.tv_usec = 0;
         }
       else
         {
           tv.tv_sec = 0;
-          if (readtimeout > 1000)
-            tv.tv_usec = 1000 * 1000;
-          else
-            tv.tv_usec = readtimeout * 1000;
+          tv.tv_usec = readtimeout * 1000;
+          tv = normalize_timeval (tv, "udp_read");
         }
 
       FD_ZERO (&readfds);
       FD_SET (get_fd (), &readfds);
 
       if (::select (get_fd ()+1, &readfds, NULL, NULL, &tv) < 0)
         {
           error ("tcp_read: Error while reading/select: %d - %s\n", SOCKETERR, STRSOCKETERR);
diff --git a/src/udp/udp_class.cc b/src/udp/udp_class.cc
--- a/src/udp/udp_class.cc
+++ b/src/udp/udp_class.cc
@@ -66,16 +66,35 @@ to_ip_str (const sockaddr_in *in)
   b[3] = (addr>>0)&0xff;
 
   std::stringstream n;
   n << b[0] << "." << b[1] << "." << b[2] << "." << b[3];
 
   return n.str ();
 }
 
+// Some systems (like macOS) reject tv_usec values > 999,999.
+// This function normalizes a timeval to have <= 999,999 usec by
+// carrying over excess usec to the tv_sec field.
+static struct timeval
+normalize_timeval (struct timeval in, const char *caller_fcn_name)
+{
+  struct timeval out;
+  
+  if (in.tv_sec < 0)
+    error ("%s: timeval tv_sec value may not be <0", caller_fcn_name);
+  if (in.tv_usec < 0)
+    error ("%s: timeval tv_usec value may not be <0", caller_fcn_name);
+  
+  out.tv_sec = in.tv_sec + (in.tv_usec / 1000000);
+  out.tv_usec = in.tv_usec % 1000000;
+
+  return out;
+}
+
 static bool 
 lookup_addr (const std::string &ip, sockaddr_in *in)
 {
   in->sin_addr.s_addr = inet_addr (ip.c_str());
 
   if (in->sin_addr.s_addr == INADDR_NONE)
     {
       struct hostent * host = gethostbyname (ip.c_str());
@@ -303,16 +322,19 @@ octave_udp::get_bytesavailable () const
     {
       return 0;
     }
   ioctl (get_fd (), FIONREAD, &available);
 
   return available;
 }
 
+// readtimeout is in msec:
+//  less than 0             => wait for 1 second
+//  greater or equal to 0   => wait for readtimeout msec
 int
 octave_udp::read (uint8_t *buf, unsigned int len, int readtimeout)
 {
   struct sockaddr_in addr;
   socklen_t addrlen = sizeof (addr);
   struct timeval tv;
 
   fd_set readfds;
@@ -334,20 +356,18 @@ octave_udp::read (uint8_t *buf, unsigned
       if (readtimeout < 0) 
         {
           tv.tv_sec = 1;
           tv.tv_usec = 0;
         }
       else
         {
           tv.tv_sec = 0;
-          if (readtimeout > 1000)
-            tv.tv_usec = 1000 * 1000;
-          else
-            tv.tv_usec = readtimeout * 1000;
+          tv.tv_usec = readtimeout * 1000;
+          tv = normalize_timeval (tv, "udp_read");
         }
 
       FD_ZERO (&readfds);
       FD_SET (get_fd (), &readfds);
 
       if (::select(get_fd ()+1, &readfds, NULL, NULL, &tv) < 0)
         {
             error("udp_read: Error while reading/select: %d - %s\n", SOCKETERR, STRSOCKETERR);
@@ -370,29 +390,29 @@ octave_udp::read (uint8_t *buf, unsigned
                 error ("udp_read: Connection lost: %d - %s\n", SOCKETERR, STRSOCKETERR);
                 break;
             }
           else
             {
               bytes_read += read_retval;
             }
         }
-        else 
-          {
-            // Timeout
-            if (readtimeout >= 0)
-              {
-                // real timeout
-                if (readtimeout <= 1000)
-                  break;
-                // timed out 1 sec of an actual timeout
-                else
-	          readtimeout -= 1000;
-              }
-          }
+      else 
+        {
+          // Timeout
+          if (readtimeout >= 0)
+            {
+              // real timeout
+              if (readtimeout <= 1000)
+                break;
+              // timed out 1 sec of an actual timeout
+              else
+                readtimeout -= 1000;
+            }
+        }
 
     }
 
   return bytes_read;
 }
 
 int
 octave_udp::write (const std::string &str)
