/*
 * vim: filetype=c:tabstop=4:ai:expandtab
 * SPDX-License-Identifier: ICU
 * scspell-id: eec1f540-f62e-11ec-8889-80ee73e9b8e7
 *
 * ---------------------------------------------------------------------------
 *
 * Copyright (c) 2007-2013 Michael Mondy
 * Copyright (c) 2012-2016 Harry Reed
 * Copyright (c) 2017 Charles Anthony
 * Copyright (c) 2021-2023 The DPS8M Development Team
 *
 * All rights reserved.
 *
 * This software is made available under the terms of the ICU
 * License, version 1.8.1 or later.  For more details, see the
 * LICENSE.md file at the top-level directory of this distribution.
 *
 * ---------------------------------------------------------------------------
 */

#include <stdio.h>
#include <ctype.h>
#include <netdb.h>
#include <sys/ioctl.h>
#include <unistd.h>

#if defined(__sun__) || defined(_AIX)
# include <strings.h>
#endif

#include "dps8.h"
#include "dps8_sys.h"
#include "dps8_faults.h"
#include "dps8_iom.h"
#include "dps8_socket_dev.h"
#include "dps8_cable.h"
#include "dps8_cpu.h"
#include "dps8_utils.h"

#define DBG_CTR 1

#ifndef bzero
# define bzero(b,len) (memset((b), '\0', (len)), (void) 0)
#endif /* ifndef bzero */

static struct {
    const char *name;
    int code;
} errnos[] = {
    {"        ", 0},
#include "errnos.h"
};
#define N_ERRNOS (sizeof (errnos) / sizeof (errnos[0]))

#ifdef WITH_SOCKET_DEV
# define SKC_UNIT_IDX(uptr) ((uptr) - sk_unit)

struct skc_state_s
  {
    char device_name [MAX_DEV_NAME_LEN];
  };
static struct skc_state_s skc_state[N_SKC_UNITS_MAX];

# define N_FDS 1024

static struct
  {
    int   fd_unit[N_FDS];     // unit number that a FD is associated with; -1 is free.
    word6 fd_dev_code[N_FDS]; // dev_code that a FD is associated with; -1 is free.
    bool  fd_nonblock[N_FDS]; // socket() call had NON_BLOCK set
    struct
      {
        enum
          {
            unit_idle = 0,
            unit_accept,
            unit_read
          } unit_state;
         //fd_set accept_fds;
         int  accept_fd;
         int  read_fd;
         uint read_buffer_sz;
         uint words_processed;
      } unit_data[N_SKC_UNITS_MAX][N_DEV_CODES];
  } sk_data;

# define N_SKC_UNITS 64 // default

static t_stat sk_show_nunits (UNUSED FILE * st, UNUSED UNIT * uptr,
                              UNUSED int val, UNUSED const void * desc)
  {
    sim_printf("Number of socket units in system is %d\n", skc_dev.numunits);
    return SCPE_OK;
  }

static t_stat sk_set_nunits (UNUSED UNIT * uptr, UNUSED int32 value,
                             const char * cptr, UNUSED void * desc)
  {
    if (! cptr)
      return SCPE_ARG;
    int n = atoi (cptr);
    if (n < 1 || n > N_SKC_UNITS_MAX)
      return SCPE_ARG;
    skc_dev.numunits = (uint32) n;
    return SCPE_OK;
  }

static t_stat skc_show_device_name (UNUSED FILE * st, UNIT * uptr,
                                    UNUSED int val, UNUSED const void * desc)
  {
    int n = (int) SKC_UNIT_IDX (uptr);
    if (n < 0 || n >= N_SKC_UNITS_MAX)
      return SCPE_ARG;
    if (skc_state[n].device_name[1] != 0)
      sim_printf("Name: %s", skc_state[n].device_name);
    return SCPE_OK;
  }

static t_stat skc_set_device_name (UNIT * uptr, UNUSED int32 value,
                                   const char * cptr, UNUSED void * desc)
  {
    int n = (int) SKC_UNIT_IDX (uptr);
    if (n < 0 || n >= N_SKC_UNITS_MAX)
      return SCPE_ARG;
    if (cptr)
      {
        strncpy (skc_state[n].device_name, cptr, MAX_DEV_NAME_LEN-1);
        skc_state[n].device_name[MAX_DEV_NAME_LEN-1] = 0;
      }
    else
      skc_state[n].device_name[0] = 0;
    return SCPE_OK;
  }

static MTAB sk_mod [] =
  {
    {
      MTAB_XTD | MTAB_VDV | MTAB_NMO | MTAB_VALR, /* Mask               */
      0,                                          /* Match              */
      "NUNITS",                                   /* Print string       */
      "NUNITS",                                   /* Match string       */
      sk_set_nunits,                              /* Validation routine */
      sk_show_nunits,                             /* Display routine    */
      "Number of socket units in the system",     /* Value descriptor   */
      NULL                                        /* Help               */
    },
    {
      MTAB_XTD | MTAB_VUN | MTAB_VALR | MTAB_NC,  /* Mask               */
      0,                                          /* Match              */
      "NAME",                                     /* Print string       */
      "NAME",                                     /* Match string       */
      skc_set_device_name,                        /* Validation routine */
      skc_show_device_name,                       /* Display routine    */
      "Set the device name",                      /* Value descriptor   */
      NULL                                        /* Help               */
    },
    MTAB_eol
  };

UNIT sk_unit [N_SKC_UNITS_MAX] = {
# ifdef NO_C_ELLIPSIS
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL }
# else
  [0 ... (N_SKC_UNITS_MAX -1)] = {
    UDATA ( NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL
  },
# endif
};

static DEBTAB sk_dt [] =
  {
    { "NOTIFY", DBG_NOTIFY, NULL },
    {   "INFO",   DBG_INFO, NULL },
    {    "ERR",    DBG_ERR, NULL },
    {   "WARN",   DBG_WARN, NULL },
    {  "DEBUG",  DBG_DEBUG, NULL },
    {    "ALL",    DBG_ALL, NULL }, // Don't move as it messes up DBG message
    {     NULL,          0, NULL }
  };

static t_stat sk_reset (UNUSED DEVICE * dptr)
  {
    return SCPE_OK;
  }

DEVICE skc_dev = {
    "SKC",            /* Name                */
    sk_unit,          /* Unit                */
    NULL,             /* Registers           */
    sk_mod,           /* Modifiers           */
    N_SKC_UNITS,      /* Number of units     */
    10,               /* Address radix       */
    31,               /* Address width       */
    1,                /* Address increment   */
    8,                /* Data radix          */
    9,                /* Data width          */
    NULL,             /* Examine routine     */
    NULL,             /* Deposit routine     */
    sk_reset,         /* Reset routine       */
    NULL,             /* Boot routine        */
    NULL,             /* Attach routine      */
    NULL,             /* Detach routine      */
    NULL,             /* Context             */
    DEV_DEBUG,        /* Flags               */
    0,                /* Debug control flags */
    sk_dt,            /* Debug flag names    */
    NULL,             /* Memory size change  */
    NULL,             /* Logical name        */
    NULL,             /* Attach help         */
    NULL,             /* Help                */
    NULL,             /* Help context        */
    NULL,             /* Device description  */
    NULL              /* End                 */
};

void sk_init(void)
  {
    // sets unit_state to unit_idle
    memset(& sk_data, 0, sizeof(sk_data));
    for (uint i = 0; i < N_FDS; i ++)
      sk_data.fd_unit[i] = -1;
    //for (uint i = 0; i < N_SKC_UNITS_MAX; i ++)
      //for (word6 j = 0; j < N_DEV_CODES; j ++)
        //FD_ZERO (& sk_data.unit_data[i][j].accept_fds);
  }

static void set_error_str (word36 * error_str, const char * str)
  {
    char work [8];
    //strncpy (work, "        ", 8);
    //strncpy (work, str, 8);
    for (uint i = 0; i < 8; i ++)
     work[i] = ' ';
    size_t l = strlen (str);
    if (l > 8)
      l = 8;
    for (uint i = 0; i < l; i ++)
     work[i] = str[i];
    error_str[0] = 0;
    error_str[1] = 0;
    putbits36_8 (error_str + 0,  1, (word8) work [0]);
    putbits36_8 (error_str + 0, 10, (word8) work [1]);
    putbits36_8 (error_str + 0, 19, (word8) work [2]);
    putbits36_8 (error_str + 0, 28, (word8) work [3]);
    putbits36_8 (error_str + 1,  1, (word8) work [4]);
    putbits36_8 (error_str + 1, 10, (word8) work [5]);
    putbits36_8 (error_str + 1, 19, (word8) work [6]);
    putbits36_8 (error_str + 1, 28, (word8) work [7]);
  }

static void set_error (word36 * error_str, int _errno)
  {
    if (errno == 0)
      return;
    for (uint i = 0; i < N_ERRNOS; i ++)
      {
        if (errnos[i].code == _errno)
          {
            set_error_str (error_str, errnos[i].name);
            return;
          }
      }
    char huh [256];
    sprintf (huh, "E%d", _errno);
    huh[8] = 0;
    set_error_str (error_str, huh);
  }

static void skt_socket (uint unit_idx, word5 dev_code, word36 * buffer)
  {
// /* Data block for socket() call */
// dcl 1 SOCKETDEV_socket_data aligned,
//       2 domain fixed bin,   // 0
//       2 type fixed bin,     // 1
//       2 protocol fixed bin; // 2
//       2 fd fixed bin;       // 3
//       2 errno char(8);      // 4,5

    int domain =   (int) buffer[0];
    int type =     (int) buffer[1];
    int protocol = (int) buffer[2];

sim_printf ("socket() domain   %d\n", domain);
sim_printf ("socket() type     %d\n", type);
sim_printf ("socket() protocol %d\n", protocol);

    int _errno = 0;
    int fd = -1;

    if (domain != AF_INET)       // Only AF_INET
      {
sim_printf ("socket() domain EAFNOSUPPORT\n");
        _errno = EAFNOSUPPORT;
      }
# if defined(__APPLE__) || defined(_AIX) || defined(__HAIKU__)
    else if (type != SOCK_STREAM && type != (SOCK_STREAM)) // Only SOCK_STREAM or SOCK_STREAM + SOCK_NONBLOCK
# else
    else if (type != SOCK_STREAM && type != (SOCK_STREAM|SOCK_NONBLOCK)) // Only SOCK_STREAM or SOCK_STREAM + SOCK_NONBLOCK
# endif
      {
sim_printf ("socket() type EPROTOTYPE\n");
        _errno = EPROTOTYPE;
      }
    else if (protocol != 0) // Only IP
      {
sim_printf ("socket() protocol EPROTONOSUPPORT\n");
        _errno = EPROTONOSUPPORT;
      }
    else
      {
        fd = socket ((int) buffer[0], (int) buffer[1], (int) buffer[2]);
sim_printf ("socket() returned %d\n", fd);
        if (fd < 0)
          {
sim_printf ("errno %d\n", errno);
            _errno = errno;
          }
        else if (fd < N_FDS)
          {
            sk_data.fd_unit[fd] = (int) unit_idx;
            sk_data.fd_dev_code[fd] = dev_code;
# if defined(__APPLE__) || defined(_AIX) || defined(__HAIKU__)
            sk_data.fd_nonblock[fd] = 0;
# else
            sk_data.fd_nonblock[fd] = !! (type & SOCK_NONBLOCK);
# endif
          }
        else
          {
            close (fd);
            fd = -1;
            _errno = EMFILE;
          }
      }
    // sign extend int into word36
    buffer[3] = ((word36) ((word36s) fd)) & MASK36; // fd
    //buffer[5] = ((word36) ((word36s) _errno)) & MASK36; // errno
    set_error (& buffer[4], _errno);
  }

static void skt_gethostbyname (word36 * buffer)
  {
// dcl 1 SOCKETDEV_gethostbyname_data aligned,
//       2 name char varying (255),
//       3 addr fixed uns bin (32),
//       3 errno char(8);
//
//
//       len:36                    //  0
//       c1: 9, c2: 9, c3:9, c4:9  //  1
//       ...
//       c253: 9, c254: 9, c255: 9, pad: 9, //63
//       addr: 32, pad: 4,          // 65
//       errno: 72                   // 66, 67
//

    word9 cnt = getbits36_9 (buffer [0], 27);

# if 0
    sim_printf ("strlen: %hu\n", cnt);
    sim_printf ("name: \"");
    for (uint i = 0; i < cnt; i ++)
      {
         uint wordno = (i+4) / 4;
         uint offset = ((i+4) % 4) * 9;
         word9 ch = getbits36_9 (buffer[wordno], offset);
         if (isgraph (ch))
            sim_printf ("%c", ch);
         else
            sim_printf ("\\%03o", ch);
      }
    sim_printf ("\"\n");
# endif

    if (cnt > 256)
      {
        sim_warn ("socket$gethostbyname() clipping cnt from %u to 256\n", cnt);
        cnt = 256;
      }

    unsigned char name [257];
    for (uint i = 0; i < cnt; i ++)
      {
         uint wordno = (i+4) / 4;
         uint offset = ((i+4) % 4) * 9;
         word9 ch = getbits36_9 (buffer[wordno], offset);
         name [i] = (unsigned char) (ch & 255);
      }
    name[cnt] = 0;

    struct hostent * hostent = gethostbyname ((char *)name);
sim_printf ("gethostbyname returned %p\n", (void *) hostent);
    if (hostent)
      {
sim_printf ("addr_len %d\n", hostent->h_length);
sim_printf ("%hhu.%hhu.%hhu.%hhu\n", hostent->h_addr_list[0][0],hostent->h_addr_list[0][1], hostent->h_addr_list[0][2],hostent->h_addr_list[0][3]);

        uint32_t addr = * ((uint32_t *) & hostent->h_addr_list[0][0]);
sim_printf ("addr %08x\n", addr);
        addr = ntohl (addr);
sim_printf ("addr %08x\n", addr);
        buffer[65] = ((word36) addr) << 4;
        // Get the octets in the right order
        //putbits36_8 (& buffer[65],  0, (word8) (((unsigned char) (hostent->h_addr_list[0][0])) & 0xff));
        //putbits36_8 (& buffer[65],  8, (word8) (((unsigned char) (hostent->h_addr_list[0][1])) & 0xff));
        //putbits36_8 (& buffer[65], 16, (word8) (((unsigned char) (hostent->h_addr_list[0][2])) & 0xff));
        //putbits36_8 (& buffer[65], 24, (word8) (((unsigned char) (hostent->h_addr_list[0][3])) & 0xff));
        set_error (& buffer[66], 0);
      }
    else
      {
sim_printf ("h_errno %d\n", h_errno);
        switch (h_errno)
          {
            case HOST_NOT_FOUND: set_error_str (& buffer[66], "HOST_NOT_FOUND"); break;
            case NO_DATA: set_error_str (& buffer[66], "NO_DATA"); break;
            case NO_RECOVERY: set_error_str (& buffer[66], "NO_RECOVERY"); break;
            case TRY_AGAIN: set_error_str (& buffer[66], "TRY_AGAIN"); break;
            default: set_error_str (& buffer[66], "EHUH"); break;
          }
      }
  }

static void skt_bind (uint unit_idx, word6 dev_code, word36 * buffer)
  {
// dcl 1 SOCKETDEV_bind_data aligned,
//       2 socket fixed bin,              // 0
//       2 sockaddr_in,
//         3 sin_family fixed bin,        // 1
//         3 sin_port fixed uns bin (16), // 2
//         3 sin_addr,                    // 3
//           4 octets (4) fixed bin(8) unsigned unal,
//       2 errno char(8);               // 4,5

// /* Expecting tally to be 6 */
// /* sockaddr is from the API parameter */
// /* errno, errno are the values returned by the host socket() call */

    int socket_fd = (int) buffer[0];
    int sin_family = (int) buffer[1];
    unsigned short sin_port = (unsigned short) getbits36_16 (buffer [2], 0);
    word8 octet [4] = { getbits36_8 (buffer [3], 0),
                        getbits36_8 (buffer [3], 8),
                        getbits36_8 (buffer [3], 16),
                        getbits36_8 (buffer [3], 24)};
    uint32_t addr = (uint32_t) octet[0];
    addr <<= 8;
    addr |= (uint32_t) octet[1];
    addr <<= 8;
    addr |= (uint32_t) octet[2];
    addr <<= 8;
    addr |= (uint32_t) octet[3];

sim_printf ("bind() socket     %d\n",                  socket_fd);
sim_printf ("bind() sin_family %d\n",                  sin_family);
sim_printf ("bind() sin_port   %u\n",                  sin_port);
sim_printf ("bind() s_addr     %hhu.%hhu.%hhu.%hhu\n", octet[0], octet[1], octet[2], octet[3]);
sim_printf ("bind() s_addr     %08x\n",                addr);
  //(buffer [3] >> (36 - 1 * 8)) & MASK8,
  //(buffer [3] >> (36 - 2 * 8)) & MASK8,
  //(buffer [3] >> (36 - 3 * 8)) & MASK8,
  //(buffer [3] >> (36 - 4 * 8)) & MASK8),

    // Does this socket belong to us?
    if (sk_data.fd_unit[socket_fd] != (int) unit_idx || sk_data.fd_dev_code[socket_fd] != dev_code)
      {
        set_error (& buffer[4], EBADF);
        return;
      }

    struct sockaddr_in serv_addr;
    bzero ((char *) & serv_addr, sizeof(serv_addr));
    serv_addr.sin_family      = AF_INET;
    serv_addr.sin_addr.s_addr = htonl (addr);
    serv_addr.sin_port        = htons (sin_port);

    int _errno = 0;
    int rc = bind (socket_fd, (struct sockaddr *) & serv_addr, sizeof (serv_addr));
sim_printf ("bind() returned %d\n", rc);

    if (rc < 0)
      {
sim_printf ("errno %d\n", errno);
        _errno = errno;
      }
    set_error (& buffer[4], _errno);
  }

static void skt_listen (uint unit_idx, word6 dev_code, word36 * buffer)
  {
// dcl 1 SOCKETDEV_listen_data aligned,
//       2 sockfd fixed bin,  // 0
//       3 backlog fixed bin, // 1
//       2 rc fixed bin;      // 2
//       2 errno char(8);     // 3, 4
//
// /* Tally 5   */
// /* In:       */
// /*   sockfd  */
// /*   backlog */
// /* Out:      */
// /*   rc      */
// /*   errno   */

    int socket_fd = (int) buffer[0];
    int backlog = (int) buffer[1];
sim_printf ("listen() socket     %d\n", socket_fd);
sim_printf ("listen() backlog    %d\n", backlog   );

    int rc = 0;
    int _errno = 0;
    // Does this socket belong to us?
    if (sk_data.fd_unit[socket_fd] != (int) unit_idx || sk_data.fd_dev_code[socket_fd] != dev_code)
      {
sim_printf ("listen() socket doesn't belong to us\n");
sim_printf ("socket_fd %u fd_unit %d fd_dev_code %u unit_idx %u dev_code %u\n", socket_fd, sk_data.fd_unit[socket_fd], sk_data.fd_dev_code[socket_fd], unit_idx, dev_code);
        _errno = EBADF;
        goto done;
      }

    int on = 1;
    rc = setsockopt (socket_fd, SOL_SOCKET,  SO_REUSEADDR,
                   (char *) & on, sizeof (on));
sim_printf ("listen() setsockopt returned %d\n", rc);
    if (rc < 0)
      {
        _errno = errno;
        goto done;
      }

# ifdef FIONBIO
    rc = ioctl (socket_fd, FIONBIO, (char *) & on);
sim_printf ("listen() ioctl returned %d\n", rc);
    if (rc < 0)
      {
        _errno = errno;
        goto done;
      }
# endif

    rc = listen (socket_fd, backlog);
sim_printf ("listen() returned %d\n", rc);

    if (rc < 0)
      {
sim_printf ("errno %d\n", errno);
        _errno = errno;
        goto done;
      }

done:
    buffer[2] = ((word36) ((word36s) rc)) & MASK36; // rc
    set_error (& buffer[3], _errno);
  }

static int skt_accept (uint unit_idx, word6 dev_code, word36 * buffer)
  {
// dcl 1 SOCKETDEV_accept_data aligned,
//       2 sockfd fixed bin,                           // 0
//       2 rc fixed bin,                               // 1
//       2 sockaddr_in,
//         3 sin_family fixed bin,                     // 2
//         3 sin_port fixed uns bin (16),              // 3
//         3 sin_addr,
//           4 octets (4) fixed bin(8) unsigned unal,  // 4
//       2 errno char(8);                              // 5, 6

    int socket_fd = (int) buffer[0];
sim_printf ("accept() socket     %d\n", socket_fd);
    // Does this socket belong to us?
    if (sk_data.fd_unit[socket_fd] != (int) unit_idx || sk_data.fd_dev_code[socket_fd] != dev_code)
      {
        set_error (& buffer[4], EBADF);
        return IOM_CMD_DISCONNECT; // send terminate interrupt
      }
    //FD_SET (socket_fd, & sk_data.unit_data[unit_idx][dev_code].accept_fds);
    sk_data.unit_data[unit_idx][dev_code].accept_fd  = socket_fd;
    sk_data.unit_data[unit_idx][dev_code].unit_state = unit_accept;
    return IOM_CMD_DISCONNECT; // don't send terminate interrupt
  }

static void skt_close (uint unit_idx, word6 dev_code, word36 * buffer)
  {
// dcl 1 SOCKETDEV_close_data aligned,
//       2 sockfd fixed bin,  // 0
//       2 rc fixed bin,      // 1
//       2 errno char(8);     // 2, 3
//
// /* Tally 4  */
// /* In:      */
// /*   sockfd */
// /* Out:     */
// /*   rc     */
// /*   errno  */

    int socket_fd = (int) buffer[0];
sim_printf ("close() socket     %d\n", socket_fd);

    int rc     = 0;
    int _errno = 0;
    // Does this socket belong to us?
    if (sk_data.fd_unit[socket_fd] != (int) unit_idx || sk_data.fd_dev_code[socket_fd] != dev_code)
      {
sim_printf ("close() socket doesn't belong to us\n");
        _errno = EBADF;
        goto done;
      }
    sk_data.fd_unit[socket_fd] = -1;

    if (sk_data.unit_data[unit_idx][dev_code].unit_state == unit_accept &&
        sk_data.unit_data[unit_idx][dev_code].accept_fd == socket_fd)
      {
        sk_data.unit_data[unit_idx][dev_code].unit_state = unit_idle;
        sk_data.unit_data[unit_idx][dev_code].accept_fd = -1;
      }
    rc = close (socket_fd);

sim_printf ("close() close returned %d\n", rc);
    if (rc < 0)
      {
        _errno = errno;
        goto done;
      }

done:
    buffer[1] = ((word36) ((word36s) rc)) & MASK36; // rc
    set_error (& buffer[2], _errno);
  }

static int skt_read8 (uint unit_idx, word6 dev_code, UNUSED uint tally, word36 * buffer)
  {
// dcl 1 SOCKETDEV_read_data8 aligned,
//       2 sockfd fixed bin,                                  // 0
//       2 count  fixed bin, /* buffer size */                // 1
//       2 rc     fixed bin,                                  // 2
//       2 errno  char(8),                                    // 3,4
//       2 buffer char (0 refer (SOCKETDEV_read_data9.count); // 5,....

/* Tally >= 5 */
/* In:        */
/*   sockfd   */
/*   count    */
/* Out:       */
/*   rc       */
/*   buffer   */

    int socket_fd = (int) buffer[0];
    uint count = (uint) buffer[1];
sim_printf ("read8() socket     %d\n", socket_fd);

    // Does this socket belong to us?
    if (sk_data.fd_unit[socket_fd] != (int) unit_idx || sk_data.fd_dev_code[socket_fd] != dev_code)
      {
sim_printf ("read8() socket doesn't belong to us\n");
        set_error (& buffer[4], EBADF);
        return IOM_CMD_DISCONNECT; // send terminate interrupt
      }
    sk_data.unit_data[unit_idx][dev_code].read_fd        = socket_fd;
    sk_data.unit_data[unit_idx][dev_code].read_buffer_sz = count;
    sk_data.unit_data[unit_idx][dev_code].unit_state     = unit_read;
    return IOM_CMD_DISCONNECT; // don't send terminate interrupt
  }

static int skt_write8 (uint iom_unit_idx, uint chan, uint unit_idx, word6 dev_code, uint tally, word36 * buffer)
  {
    iom_chan_data_t * p = & iom_chan_data[iom_unit_idx][chan];
// dcl 1 SOCKETDEV_write_data8 aligned,
//       2 sockfd fixed bin,                                  // 0
//       2 count  fixed bin, /* buffer size */                // 1
//       2 rc     fixed bin,                                  // 2
//       2 errno  char(8),                                    // 3,4
//       2 buffer char (0 refer (SOCKETDEV_read_data9.count); // 5,....

    if (tally < 5)
      {
        p->stati = 050012; // BUG: arbitrary error code; config switch
        return IOM_CMD_ERROR;
      }

/* Tally >= 5 */
/* In:        */
/*   sockfd   */
/*   count    */
/* Out:       */
/*   rc       */
/*   errno    */
/*   buffer   */

    int socket_fd = (int) buffer[0];
sim_printf ("write8() socket     %d\n", socket_fd);

    ssize_t rc = 0;
    int _errno = 0;
    // Does this socket belong to us?
    if (sk_data.fd_unit[socket_fd] != (int) unit_idx || sk_data.fd_dev_code[socket_fd] != dev_code)
      {
sim_printf ("write8() socket doesn't belong to us\n");
        set_error (& buffer[3], EBADF);
        return IOM_CMD_DISCONNECT; // send terminate interrupt
      }

   // Tally is at most 4096, so buffer words is at most 4096 - 5 => 4091
   // count (4 chars/word) is at most 4091 * 4
    word36 count36 = buffer[1];
    if (count36 > (4091 * 4))
      {
        p->stati = 050012; // BUG: arbitrary error code; config switch
        return IOM_CMD_ERROR;
      }
    uint count = (uint) count36;

    uint count_words = (count + 3) / 4;
    if ((count_words + 5) > tally)
      {
        p->stati = 050012; // BUG: arbitrary error code; config switch
        return IOM_CMD_ERROR;
      }

    uint8_t netdata [count];
    for (uint n = 0; n < count; n ++)
      {
         uint wordno = (uint) n / 4;
         uint charno = (uint) n % 4;
         netdata[n] = getbits36_8 (buffer [5 + wordno], charno * 9 + 1);
//sim_printf ("%012llo %u %u %u %03u\n", buffer [5 + wordno], n, wordno, charno, netdata[n]);
      }

    rc = write (socket_fd, netdata, count);
    if (rc == -1)
      _errno = errno;

    buffer[2] = ((word36) ((word36s) rc)) & MASK36; // rc
    set_error (& buffer[3], _errno);
    return IOM_CMD_DISCONNECT; // send terminate interrupt
  }

static int get_ddcw (iom_chan_data_t * p, uint iom_unit_idx, uint chan, bool * ptro, uint expected_tally, uint * tally)
  {
    bool send, uff;
    int rc = iom_list_service (iom_unit_idx, chan, ptro, & send, & uff);
    if (rc < 0)
      {
        p->stati = 05001; // BUG: arbitrary error code; config switch
        sim_warn ("%s list service failed\n", __func__);
        return IOM_CMD_ERROR;
      }
    if (uff)
      {
        sim_warn ("%s ignoring uff\n", __func__); // XXX
      }
    if (! send)
      {
        sim_warn ("%s nothing to send\n", __func__);
        p->stati = 05001; // BUG: arbitrary error code; config switch
        return IOM_CMD_ERROR;
      }
    if (IS_IDCW (p) || IS_TDCW (p))
      {
        sim_warn ("%s expected DDCW\n", __func__);
        p->stati = 05001; // BUG: arbitrary error code; config switch
        return IOM_CMD_ERROR;
      }

    * tally = p->DDCW_TALLY;
    if (* tally == 0)
      {
        sim_debug (DBG_DEBUG, & skc_dev,
                   "%s: Tally of zero interpreted as 010000(4096)\n",
                   __func__);
        * tally = 4096;
      }

    sim_debug (DBG_DEBUG, & skc_dev,
               "%s: Tally %d (%o)\n", __func__, * tally, * tally);

    if (expected_tally && * tally && * tally != expected_tally) //-V560
      {
        sim_warn ("socket_dev socket call expected tally of %d; got %d\n", expected_tally, * tally);
        p->stati = 05001; // BUG: arbitrary error code; config switch
        return IOM_CMD_ERROR;
      }
    return IOM_CMD_PROCEED;
  }

static int sk_cmd (uint iom_unit_idx, uint chan)
  {
    iom_chan_data_t * p = & iom_chan_data[iom_unit_idx][chan];

    sim_debug (DBG_DEBUG, & skc_dev, "IDCW_DEV_CODE %d\n", p->IDCW_DEV_CODE);
    uint unit_idx = get_ctlr_idx (iom_unit_idx, chan);
sim_printf ("device %u\n", p->IDCW_DEV_CODE);
    bool ptro;
    switch (p->IDCW_DEV_CMD)
      {
        case 0: // CMD 00 Request status -- controller status, not device
          {
            p->stati = 04000; // have_status = 1
            sim_debug (DBG_DEBUG, & skc_dev,
                       "%s: Request status: %04o\n", __func__, p->stati);
            sim_debug (DBG_DEBUG, & skc_dev,
                       "%s: Request status control: %o\n", __func__, p->IDCW_CHAN_CTRL);
            sim_debug (DBG_DEBUG, & skc_dev,
                       "%s: Request status channel command: %o\n", __func__, p->IDCW_CHAN_CMD);
          }
          break;

        case 01:               // CMD 01 -- socket()
          {
            sim_debug (DBG_DEBUG, & skc_dev,
                       "%s: socket_dev_$socket\n", __func__);
            const uint expected_tally = 6;
            uint tally;
            int rc = get_ddcw (p, iom_unit_idx, chan, & ptro, expected_tally, & tally);
            if (rc)
              return rc;

            // Fetch parameters from core into buffer

            word36 buffer [expected_tally];
            uint words_processed;
            iom_indirect_data_service (iom_unit_idx, chan, buffer,
                                       & words_processed, false);

            skt_socket (unit_idx, p->IDCW_DEV_CODE, buffer);

            iom_indirect_data_service (iom_unit_idx, chan, buffer,
                                       & words_processed, true);
          }
          break;

        case 02:               // CMD 02 -- bind()
          {
            sim_debug (DBG_DEBUG, & skc_dev,
                       "%s: socket_dev_$bind\n", __func__);

            const uint expected_tally = 6;
            uint tally;
            int rc = get_ddcw (p, iom_unit_idx, chan, & ptro, expected_tally, & tally);
            if (rc)
              return rc;

            // Fetch parameters from core into buffer

            word36 buffer [expected_tally];
            uint words_processed;
            iom_indirect_data_service (iom_unit_idx, chan, buffer,
                                       & words_processed, false);

            skt_bind (unit_idx, p->IDCW_DEV_CODE, buffer);

            iom_indirect_data_service (iom_unit_idx, chan, buffer,
                                       & words_processed, true);

          }
          break;

        case 03:               // CMD 03 -- Debugging
          {
            sim_printf ("socket_dev received command 3\r\n");
            p->stati = 04000;
          }
          break;

        case 04:               // CMD 04 -- gethostbyname()
          {
            sim_debug (DBG_DEBUG, & skc_dev,
                       "%s: socket_dev_$gethostbyname\n", __func__);

            const uint expected_tally = 68;
            uint tally;
            int rc = get_ddcw (p, iom_unit_idx, chan, & ptro, expected_tally, & tally);
            if (rc)
              return rc;

            // Fetch parameters from core into buffer

            word36 buffer [expected_tally];
            uint words_processed;
            iom_indirect_data_service (iom_unit_idx, chan, buffer,
                                       & words_processed, false);

            skt_gethostbyname (buffer);

            iom_indirect_data_service (iom_unit_idx, chan, buffer,
                                       & words_processed, true);

          }
          break;

        case 05:               // CMD 05 -- listen()
          {
            sim_debug (DBG_DEBUG, & skc_dev,
                       "%s: socket_dev_$listen\n", __func__);

            const uint expected_tally = 5;
            uint tally;
            int rc = get_ddcw (p, iom_unit_idx, chan, & ptro, expected_tally, & tally);
            if (rc)
              return rc;

            // Fetch parameters from core into buffer

            word36 buffer [expected_tally];
            uint words_processed;
            iom_indirect_data_service (iom_unit_idx, chan, buffer,
                                       & words_processed, false);

            skt_listen (unit_idx, p->IDCW_DEV_CODE, buffer);

            iom_indirect_data_service (iom_unit_idx, chan, buffer,
                                       & words_processed, true);

          }
          break;

        case 06:               // CMD 06 -- accept()
          {
            sim_debug (DBG_DEBUG, & skc_dev,
                       "%s: socket_dev_$accept\n", __func__);

            const uint expected_tally = 7;
            uint tally;
            int rc = get_ddcw (p, iom_unit_idx, chan, & ptro, expected_tally, & tally);
            if (rc)
              return rc;

            // Fetch parameters from core into buffer

            word36 buffer [expected_tally];
            uint words_processed;
            iom_indirect_data_service (iom_unit_idx, chan, buffer,
                                       & words_processed, false);
            sk_data.unit_data[unit_idx][p->IDCW_DEV_CODE].words_processed = words_processed;

            rc = skt_accept (unit_idx, p->IDCW_DEV_CODE, buffer);

            iom_indirect_data_service (iom_unit_idx, chan, buffer,
                                       & words_processed, true);

            return rc; // 3:command pending, don't send terminate interrupt, or
                       // 2:sent terminate interrupt
          }
          /*NOTREACHED*/ /* unreachable */
          break;

        case 07:               // CMD 07 -- close()
          {
            sim_debug (DBG_DEBUG, & skc_dev,
                       "%s: socket_dev_$close\n", __func__);

            const uint expected_tally = 4;
            uint tally;
            int rc = get_ddcw (p, iom_unit_idx, chan, & ptro, expected_tally, & tally);
            if (rc)
              return rc;

            // Fetch parameters from core into buffer

            word36 buffer [expected_tally];
            uint words_processed;
            iom_indirect_data_service (iom_unit_idx, chan, buffer,
                                       & words_processed, false);

            skt_close (unit_idx, p->IDCW_DEV_CODE, buffer);

            iom_indirect_data_service (iom_unit_idx, chan, buffer,
                                       & words_processed, true);
          }
          break;

        case 8:               // CMD 8 -- read8()
          {
            sim_debug (DBG_DEBUG, & skc_dev,
                       "%s: socket_dev_$read8\n", __func__);

            const uint expected_tally = 0;
            uint tally;
            int rc = get_ddcw (p, iom_unit_idx, chan, & ptro, expected_tally, & tally);
            if (rc)
              return rc;

            // Fetch parameters from core into buffer

            word36 buffer[4096];  /* tally size is max 4096 bytes */
            uint words_processed;
            iom_indirect_data_service (iom_unit_idx, chan, buffer,
                                       & words_processed, false);
            sk_data.unit_data[unit_idx][p->IDCW_DEV_CODE].words_processed = words_processed;

            rc = skt_read8 (unit_idx, p->IDCW_DEV_CODE, tally, buffer);

            iom_indirect_data_service (iom_unit_idx, chan, buffer,
                                       & words_processed, true);
            return rc; // 3:command pending, don't send terminate interrupt, or
                       // 2:sent terminate interrupt
          }
          /*NOTREACHED*/ /* unreachable */
          break;

        case 9:               // CMD 9 -- write8()
          {
            sim_debug (DBG_DEBUG, & skc_dev,
                       "%s: socket_dev_$write8\n", __func__);

            const uint expected_tally = 0;
            uint tally;
            int rc = get_ddcw (p, iom_unit_idx, chan, & ptro, expected_tally, & tally);
            if (rc)
              return rc;

            // Fetch parameters from core into buffer

            word36 buffer[4096];  /* tally size is max 4096 bytes */
            uint words_processed;
            iom_indirect_data_service (iom_unit_idx, chan, buffer,
                                       & words_processed, false);

            rc = skt_write8 (iom_unit_idx, chan, unit_idx, p->IDCW_DEV_CODE, tally, buffer);

            iom_indirect_data_service (iom_unit_idx, chan, buffer,
                                       & words_processed, true);
          return rc;
          }
          /*NOTREACHED*/ /* unreachable */
          break;

        case 040:               // CMD 040 -- Reset Status
          {
            p->stati = 04000;
            sim_debug (DBG_DEBUG, & skc_dev,
                       "%s: Reset status is %04o.\n",
                       __func__, p->stati);
            return IOM_CMD_PROCEED;
          }

        default:
          {
            p->stati = 04501;
            p->chanStatus = chanStatIncorrectDCW;
            if (p->IDCW_DEV_CMD != 051) // ignore bootload console probe
              sim_warn ("%s: Unknown command 0%o\n", __func__, p->IDCW_DEV_CMD);
          }
          return IOM_CMD_ERROR;

      } // IDCW_DEV_CMD

    sim_debug (DBG_DEBUG, & skc_dev, "stati %04o\n", p->stati);

# if 0
    if (p->IDCW_CHAN_CTRL == 3) // marker bit set
      {
        send_marker_interrupt (iom_unit_idx, (int) chan);
      }
# endif
    return IOM_CMD_DISCONNECT; // don't continue down the dcw list.
  }

iom_cmd_rc_t skc_iom_cmd (uint iom_unit_idx, uint chan)
  {
    iom_chan_data_t * p = & iom_chan_data[iom_unit_idx] [chan];
// Is it an IDCW?

    int rc = 0;
    if (IS_IDCW (p))
      {
        rc = sk_cmd (iom_unit_idx, chan);
      }
    else // DDCW/TDCW
      {
        sim_warn ("%s expected IDCW\n", __func__);
        return IOM_CMD_ERROR;
      }
    return rc; //  Don't continue down the dcw list.
  }

static void do_try_accept (uint unit_idx, word6 dev_code)
  {
    struct sockaddr_in from = { .sin_family = AF_INET };
    socklen_t size = sizeof (from);
    int _errno = 0;
    int fd = accept (sk_data.unit_data[unit_idx][dev_code].accept_fd, (struct sockaddr *) & from, & size);
    if (fd == -1)
      {
        if (errno == EAGAIN || errno == EWOULDBLOCK)
          return;
        _errno = errno;
      }
    else if (fd < N_FDS)
      {
        sk_data.fd_unit[fd] = (int) unit_idx;
        sk_data.fd_dev_code[fd] = dev_code;
        sk_data.fd_nonblock[fd] = false ; // !! (type & SOCK_NONBLOCK);
      }
    else
      {
        close (fd);
        fd = -1;
        _errno = EMFILE;
      }
    word36 buffer [7];
    // sign extend int into word36
    buffer[0] = ((word36) ((word36s) sk_data.unit_data[unit_idx][dev_code].accept_fd)) & MASK36;
    buffer[1] = ((word36) ((word36s) fd)) & MASK36;
    buffer[2] = ((word36) ((word36s) from.sin_family)) & MASK36;
    uint16_t port = ntohs (from.sin_port);
    putbits36_16 (& buffer[3], 0, port);
    uint32_t addr = ntohl (from.sin_addr.s_addr);
    buffer[4]     = ((word36) addr) << 4;
    set_error (& buffer[5], _errno);
    // This makes me nervous; it is assuming that the decoded channel control
    // list data for the channel is intact, and that buffer is still in place.
    uint iom_unit_idx    = (uint) cables->sk_to_iom[unit_idx][0].iom_unit_idx;
    uint chan            = (uint) cables->sk_to_iom[unit_idx][0].chan_num;
    uint words_processed = sk_data.unit_data[unit_idx][dev_code].words_processed;
    iom_indirect_data_service (iom_unit_idx, chan, buffer,
                               & words_processed, true);
    iom_chan_data_t * p  = & iom_chan_data[iom_unit_idx][chan];
    p->stati = 04000; // -V536
    sk_data.unit_data[unit_idx][dev_code].unit_state = unit_idle;
    send_terminate_interrupt (iom_unit_idx, chan);
  }

static void do_try_read (uint unit_idx, word6 dev_code)
  {
    int _errno = 0;
    uint count           = sk_data.unit_data[unit_idx][dev_code].read_buffer_sz;
    uint buffer_size_wds = (count + 3) / 4;
    word36 buffer [buffer_size_wds];
    // Make clang analyzer happy
    memset (buffer, 0, sizeof (word36) * buffer_size_wds);
    uint8_t netdata [count];
    ssize_t nread = read (sk_data.unit_data[unit_idx][dev_code].read_fd, & netdata, count);
    if (nread == -1)
      {
        if (errno == EAGAIN || errno == EWOULDBLOCK)
          return;
        _errno = errno;
        nread = 0;
      }

    // sign extend int into word36
    buffer[0] = ((word36) ((word36s) sk_data.unit_data[unit_idx][dev_code].read_fd)) & MASK36;
    buffer[1] = ((word36) (sk_data.unit_data[unit_idx][dev_code].read_buffer_sz)) & MASK36;
    buffer[2] = ((word36) ((word36s) nread)) & MASK36;
    set_error (& buffer[3], _errno);

    for (ssize_t n = 0; n < nread; n ++)
      {
         uint wordno = (uint) n / 4;
         uint charno = (uint) n % 4;
         putbits36_9 (& buffer [5 + wordno], charno * 9, (word9) netdata [n]);
      }

    // This makes me nervous; it is assuming that the decoded channel control
    // list data for the channel is intact, and that buffer is still in place.
    uint iom_unit_idx    = (uint) cables->sk_to_iom[unit_idx][0].iom_unit_idx;
    uint chan            = (uint) cables->sk_to_iom[unit_idx][0].chan_num;
    uint words_processed = sk_data.unit_data[unit_idx][dev_code].words_processed;
    iom_indirect_data_service (iom_unit_idx, chan, buffer,
                               & words_processed, true);
    sk_data.unit_data[unit_idx][dev_code].unit_state = unit_idle;
    send_terminate_interrupt (iom_unit_idx, chan);
  }

void sk_process_event (void)
  {
// Accepts
    for (uint unit_idx = 0; unit_idx < N_SKC_UNITS_MAX; unit_idx ++)
      {
        for (word6 dev_code = 0; dev_code < N_DEV_CODES; dev_code ++)
          {
            if (sk_data.unit_data[unit_idx][dev_code].unit_state == unit_accept)
              {
                do_try_accept (unit_idx, dev_code);
              }
            else if (sk_data.unit_data[unit_idx][dev_code].unit_state == unit_read)
              {
                do_try_read (unit_idx, dev_code);
              }
          }
      }
  }
#endif /* ifdef WITH_SOCKET_DEV */
