win32/nicdrv.c
Go to the documentation of this file.
1 /*
2  * Licensed under the GNU General Public License version 2 with exceptions. See
3  * LICENSE file in the project root for full license information
4  */
5 
33 #ifdef WIN32
34 
35 
36 #include <sys/types.h>
37 #include <stdio.h>
38 #include <fcntl.h>
39 #include <string.h>
40 
41 #include <winsock2.h>
42 #include "ethercattype.h"
43 #include <Mmsystem.h>
44 #include "nicdrv.h"
45 #include "osal_win32.h"
46 
47 #endif
48 
50 enum
51 {
56 };
57 
64 const uint16 priMAC[3] = { 0x0101, 0x0101, 0x0101 };
66 const uint16 secMAC[3] = { 0x0404, 0x0404, 0x0404 };
67 
69 #define RX_PRIM priMAC[1]
70 
71 #define RX_SEC secMAC[1]
72 
73 static char errbuf[PCAP_ERRBUF_SIZE];
74 
75 static void ecx_clear_rxbufstat(int *rxbufstat)
76 {
77  int i;
78  for(i = 0; i < EC_MAXBUF; i++)
79  {
80  rxbufstat[i] = EC_BUF_EMPTY;
81  }
82 }
83 
90 int ecx_setupnic(ecx_portt *port, const char *ifname, int secondary)
91 {
92  int i, rval;
93  pcap_t **psock;
94 
95  rval = 0;
96  if (secondary)
97  {
98  /* secondary port struct available? */
99  if (port->redport)
100  {
101  /* when using secondary socket it is automatically a redundant setup */
102  psock = &(port->redport->sockhandle);
103  *psock = NULL;
104  port->redstate = ECT_RED_DOUBLE;
105  port->redport->stack.sock = &(port->redport->sockhandle);
106  port->redport->stack.txbuf = &(port->txbuf);
107  port->redport->stack.txbuflength = &(port->txbuflength);
108  port->redport->stack.tempbuf = &(port->redport->tempinbuf);
109  port->redport->stack.rxbuf = &(port->redport->rxbuf);
110  port->redport->stack.rxbufstat = &(port->redport->rxbufstat);
111  port->redport->stack.rxsa = &(port->redport->rxsa);
112  ecx_clear_rxbufstat(&(port->redport->rxbufstat[0]));
113  }
114  else
115  {
116  /* fail */
117  return 0;
118  }
119  }
120  else
121  {
122  InitializeCriticalSection(&(port->getindex_mutex));
123  InitializeCriticalSection(&(port->tx_mutex));
124  InitializeCriticalSection(&(port->rx_mutex));
125  port->sockhandle = NULL;
126  port->lastidx = 0;
127  port->redstate = ECT_RED_NONE;
128  port->stack.sock = &(port->sockhandle);
129  port->stack.txbuf = &(port->txbuf);
130  port->stack.txbuflength = &(port->txbuflength);
131  port->stack.tempbuf = &(port->tempinbuf);
132  port->stack.rxbuf = &(port->rxbuf);
133  port->stack.rxbufstat = &(port->rxbufstat);
134  port->stack.rxsa = &(port->rxsa);
135  ecx_clear_rxbufstat(&(port->rxbufstat[0]));
136  psock = &(port->sockhandle);
137  }
138  /* we use pcap socket to send RAW packets in windows user mode*/
139  *psock = pcap_open(ifname, 65536, PCAP_OPENFLAG_PROMISCUOUS |
142  if (NULL == *psock)
143  {
144  printf("interface %s could not open with pcap\n", ifname);
145  return 0;
146  }
147 
148  for (i = 0; i < EC_MAXBUF; i++)
149  {
150  ec_setupheader(&(port->txbuf[i]));
151  port->rxbufstat[i] = EC_BUF_EMPTY;
152  }
153  ec_setupheader(&(port->txbuf2));
154 
155  return 1;
156 }
157 
163 {
164  timeEndPeriod(15);
165 
166  if (port->sockhandle != NULL)
167  {
168  DeleteCriticalSection(&(port->getindex_mutex));
169  DeleteCriticalSection(&(port->tx_mutex));
170  DeleteCriticalSection(&(port->rx_mutex));
171  pcap_close(port->sockhandle);
172  port->sockhandle = NULL;
173  }
174  if ((port->redport) && (port->redport->sockhandle != NULL))
175  {
176  pcap_close(port->redport->sockhandle);
177  port->redport->sockhandle = NULL;
178  }
179 
180  return 0;
181 }
182 
188 void ec_setupheader(void *p)
189 {
190  ec_etherheadert *bp;
191  bp = p;
192  bp->da0 = htons(0xffff);
193  bp->da1 = htons(0xffff);
194  bp->da2 = htons(0xffff);
195  bp->sa0 = htons(priMAC[0]);
196  bp->sa1 = htons(priMAC[1]);
197  bp->sa2 = htons(priMAC[2]);
198  bp->etype = htons(ETH_P_ECAT);
199 }
200 
206 {
207  int idx;
208  int cnt;
209 
210  EnterCriticalSection(&(port->getindex_mutex));
211 
212  idx = port->lastidx + 1;
213  /* index can't be larger than buffer array */
214  if (idx >= EC_MAXBUF)
215  {
216  idx = 0;
217  }
218  cnt = 0;
219  /* try to find unused index */
220  while ((port->rxbufstat[idx] != EC_BUF_EMPTY) && (cnt < EC_MAXBUF))
221  {
222  idx++;
223  cnt++;
224  if (idx >= EC_MAXBUF)
225  {
226  idx = 0;
227  }
228  }
229  port->rxbufstat[idx] = EC_BUF_ALLOC;
230  if (port->redstate != ECT_RED_NONE)
231  port->redport->rxbufstat[idx] = EC_BUF_ALLOC;
232  port->lastidx = idx;
233 
234  LeaveCriticalSection(&(port->getindex_mutex));
235 
236  return idx;
237 }
238 
244 void ecx_setbufstat(ecx_portt *port, int idx, int bufstat)
245 {
246  port->rxbufstat[idx] = bufstat;
247  if (port->redstate != ECT_RED_NONE)
248  port->redport->rxbufstat[idx] = bufstat;
249 }
250 
257 int ecx_outframe(ecx_portt *port, int idx, int stacknumber)
258 {
259  int lp, rval;
260  ec_stackT *stack;
261 
262  if (!stacknumber)
263  {
264  stack = &(port->stack);
265  }
266  else
267  {
268  stack = &(port->redport->stack);
269  }
270  lp = (*stack->txbuflength)[idx];
271  (*stack->rxbufstat)[idx] = EC_BUF_TX;
272  rval = pcap_sendpacket(*stack->sock, (*stack->txbuf)[idx], lp);
273  if (rval == PCAP_ERROR)
274  {
275  (*stack->rxbufstat)[idx] = EC_BUF_EMPTY;
276  }
277 
278  return rval;
279 }
280 
286 int ecx_outframe_red(ecx_portt *port, int idx)
287 {
288  ec_comt *datagramP;
289  ec_etherheadert *ehp;
290  int rval;
291 
292  ehp = (ec_etherheadert *)&(port->txbuf[idx]);
293  /* rewrite MAC source address 1 to primary */
294  ehp->sa1 = htons(priMAC[1]);
295  /* transmit over primary socket*/
296  rval = ecx_outframe(port, idx, 0);
297  if (port->redstate != ECT_RED_NONE)
298  {
299  EnterCriticalSection( &(port->tx_mutex) );
300  ehp = (ec_etherheadert *)&(port->txbuf2);
301  /* use dummy frame for secondary socket transmit (BRD) */
302  datagramP = (ec_comt*)&(port->txbuf2[ETH_HEADERSIZE]);
303  /* write index to frame */
304  datagramP->index = idx;
305  /* rewrite MAC source address 1 to secondary */
306  ehp->sa1 = htons(secMAC[1]);
307  /* transmit over secondary socket */
308  port->redport->rxbufstat[idx] = EC_BUF_TX;
309  if (pcap_sendpacket(port->redport->sockhandle, (u_char const *)&(port->txbuf2), port->txbuflength2) == PCAP_ERROR)
310  {
311  port->redport->rxbufstat[idx] = EC_BUF_EMPTY;
312  }
313  LeaveCriticalSection( &(port->tx_mutex) );
314  }
315 
316  return rval;
317 }
318 
324 static int ecx_recvpkt(ecx_portt *port, int stacknumber)
325 {
326  int lp, bytesrx;
327  ec_stackT *stack;
328  struct pcap_pkthdr * header;
329  unsigned char const * pkt_data;
330  int res;
331 
332  if (!stacknumber)
333  {
334  stack = &(port->stack);
335  }
336  else
337  {
338  stack = &(port->redport->stack);
339  }
340  lp = sizeof(port->tempinbuf);
341 
342  res = pcap_next_ex(*stack->sock, &header, &pkt_data);
343  if (res <=0 )
344  {
345  port->tempinbufs = 0;
346  return 0;
347  }
348  bytesrx = header->len;
349  if (bytesrx > lp)
350  {
351  bytesrx = lp;
352  }
353  memcpy(*stack->tempbuf, pkt_data, bytesrx);
354  port->tempinbufs = bytesrx;
355  return (bytesrx > 0);
356 }
357 
374 int ecx_inframe(ecx_portt *port, int idx, int stacknumber)
375 {
376  uint16 l;
377  int rval;
378  int idxf;
379  ec_etherheadert *ehp;
380  ec_comt *ecp;
381  ec_stackT *stack;
382  ec_bufT *rxbuf;
383 
384  if (!stacknumber)
385  {
386  stack = &(port->stack);
387  }
388  else
389  {
390  stack = &(port->redport->stack);
391  }
392  rval = EC_NOFRAME;
393  rxbuf = &(*stack->rxbuf)[idx];
394  /* check if requested index is already in buffer ? */
395  if ((idx < EC_MAXBUF) && ((*stack->rxbufstat)[idx] == EC_BUF_RCVD))
396  {
397  l = (*rxbuf)[0] + ((uint16)((*rxbuf)[1] & 0x0f) << 8);
398  /* return WKC */
399  rval = ((*rxbuf)[l] + ((uint16)(*rxbuf)[l + 1] << 8));
400  /* mark as completed */
401  (*stack->rxbufstat)[idx] = EC_BUF_COMPLETE;
402  }
403  else
404  {
405  EnterCriticalSection(&(port->rx_mutex));
406  /* non blocking call to retrieve frame from socket */
407  if (ecx_recvpkt(port, stacknumber))
408  {
409  rval = EC_OTHERFRAME;
410  ehp =(ec_etherheadert*)(stack->tempbuf);
411  /* check if it is an EtherCAT frame */
412  if (ehp->etype == htons(ETH_P_ECAT))
413  {
414  ecp =(ec_comt*)(&(*stack->tempbuf)[ETH_HEADERSIZE]);
415  l = etohs(ecp->elength) & 0x0fff;
416  idxf = ecp->index;
417  /* found index equals requested index ? */
418  if (idxf == idx)
419  {
420  /* yes, put it in the buffer array (strip ethernet header) */
421  memcpy(rxbuf, &(*stack->tempbuf)[ETH_HEADERSIZE], (*stack->txbuflength)[idx] - ETH_HEADERSIZE);
422  /* return WKC */
423  rval = ((*rxbuf)[l] + ((uint16)((*rxbuf)[l + 1]) << 8));
424  /* mark as completed */
425  (*stack->rxbufstat)[idx] = EC_BUF_COMPLETE;
426  /* store MAC source word 1 for redundant routing info */
427  (*stack->rxsa)[idx] = ntohs(ehp->sa1);
428  }
429  else
430  {
431  /* check if index exist and someone is waiting for it */
432  if (idxf < EC_MAXBUF && (*stack->rxbufstat)[idxf] == EC_BUF_TX)
433  {
434  rxbuf = &(*stack->rxbuf)[idxf];
435  /* put it in the buffer array (strip ethernet header) */
436  memcpy(rxbuf, &(*stack->tempbuf)[ETH_HEADERSIZE], (*stack->txbuflength)[idxf] - ETH_HEADERSIZE);
437  /* mark as received */
438  (*stack->rxbufstat)[idxf] = EC_BUF_RCVD;
439  (*stack->rxsa)[idxf] = ntohs(ehp->sa1);
440  }
441  else
442  {
443  /* strange things happened */
444  }
445  }
446  }
447  }
448  LeaveCriticalSection( &(port->rx_mutex) );
449 
450  }
451 
452  /* WKC if matching frame found */
453  return rval;
454 }
455 
468 static int ecx_waitinframe_red(ecx_portt *port, int idx, osal_timert *timer)
469 {
470  osal_timert timer2;
471  int wkc = EC_NOFRAME;
472  int wkc2 = EC_NOFRAME;
473  int primrx, secrx;
474 
475  /* if not in redundant mode then always assume secondary is OK */
476  if (port->redstate == ECT_RED_NONE)
477  wkc2 = 0;
478  do
479  {
480  /* only read frame if not already in */
481  if (wkc <= EC_NOFRAME)
482  wkc = ecx_inframe(port, idx, 0);
483  /* only try secondary if in redundant mode */
484  if (port->redstate != ECT_RED_NONE)
485  {
486  /* only read frame if not already in */
487  if (wkc2 <= EC_NOFRAME)
488  wkc2 = ecx_inframe(port, idx, 1);
489  }
490  /* wait for both frames to arrive or timeout */
491  } while (((wkc <= EC_NOFRAME) || (wkc2 <= EC_NOFRAME)) && !osal_timer_is_expired(timer));
492  /* only do redundant functions when in redundant mode */
493  if (port->redstate != ECT_RED_NONE)
494  {
495  /* primrx if the received MAC source on primary socket */
496  primrx = 0;
497  if (wkc > EC_NOFRAME) primrx = port->rxsa[idx];
498  /* secrx if the received MAC source on psecondary socket */
499  secrx = 0;
500  if (wkc2 > EC_NOFRAME) secrx = port->redport->rxsa[idx];
501 
502  /* primary socket got secondary frame and secondary socket got primary frame */
503  /* normal situation in redundant mode */
504  if ( ((primrx == RX_SEC) && (secrx == RX_PRIM)) )
505  {
506  /* copy secondary buffer to primary */
507  memcpy(&(port->rxbuf[idx]), &(port->redport->rxbuf[idx]), port->txbuflength[idx] - ETH_HEADERSIZE);
508  wkc = wkc2;
509  }
510  /* primary socket got nothing or primary frame, and secondary socket got secondary frame */
511  /* we need to resend TX packet */
512  if ( ((primrx == 0) && (secrx == RX_SEC)) ||
513  ((primrx == RX_PRIM) && (secrx == RX_SEC)) )
514  {
515  /* If both primary and secondary have partial connection retransmit the primary received
516  * frame over the secondary socket. The result from the secondary received frame is a combined
517  * frame that traversed all slaves in standard order. */
518  if ( (primrx == RX_PRIM) && (secrx == RX_SEC) )
519  {
520  /* copy primary rx to tx buffer */
521  memcpy(&(port->txbuf[idx][ETH_HEADERSIZE]), &(port->rxbuf[idx]), port->txbuflength[idx] - ETH_HEADERSIZE);
522  }
523  osal_timer_start (&timer2, EC_TIMEOUTRET);
524  /* resend secondary tx */
525  ecx_outframe(port, idx, 1);
526  do
527  {
528  /* retrieve frame */
529  wkc2 = ecx_inframe(port, idx, 1);
530  } while ((wkc2 <= EC_NOFRAME) && !osal_timer_is_expired(&timer2));
531  if (wkc2 > EC_NOFRAME)
532  {
533  /* copy secondary result to primary rx buffer */
534  memcpy(&(port->rxbuf[idx]), &(port->redport->rxbuf[idx]), port->txbuflength[idx] - ETH_HEADERSIZE);
535  wkc = wkc2;
536  }
537  }
538  }
539 
540  /* return WKC or EC_NOFRAME */
541  return wkc;
542 }
543 
551 int ecx_waitinframe(ecx_portt *port, int idx, int timeout)
552 {
553  int wkc;
554  osal_timert timer;
555 
556  osal_timer_start (&timer, timeout);
557  wkc = ecx_waitinframe_red(port, idx, &timer);
558 
559  return wkc;
560 }
561 
574 int ecx_srconfirm(ecx_portt *port, int idx, int timeout)
575 {
576  int wkc = EC_NOFRAME;
577  osal_timert timer1, timer2;
578 
579  osal_timer_start (&timer1, timeout);
580  do
581  {
582  /* tx frame on primary and if in redundant mode a dummy on secondary */
583  ecx_outframe_red(port, idx);
584  if (timeout < EC_TIMEOUTRET)
585  {
586  osal_timer_start (&timer2, timeout);
587  }
588  else
589  {
590  /* normally use partial timeout for rx */
591  osal_timer_start (&timer2, EC_TIMEOUTRET);
592  }
593  /* get frame from primary or if in redundant mode possibly from secondary */
594  wkc = ecx_waitinframe_red(port, idx, &timer2);
595  /* wait for answer with WKC>=0 or otherwise retry until timeout */
596  } while ((wkc <= EC_NOFRAME) && !osal_timer_is_expired (&timer1));
597 
598  return wkc;
599 }
600 
601 
602 #ifdef EC_VER1
603 
604 int ec_setupnic(const char *ifname, int secondary)
605 {
606  return ecx_setupnic(&ecx_port, ifname, secondary);
607 }
608 
609 int ec_closenic(void)
610 {
611  return ecx_closenic(&ecx_port);
612 }
613 
614 int ec_getindex(void)
615 {
616  return ecx_getindex(&ecx_port);
617 }
618 
619 void ec_setbufstat(int idx, int bufstat)
620 {
621  ecx_setbufstat(&ecx_port, idx, bufstat);
622 }
623 
624 int ec_outframe(int idx, int stacknumber)
625 {
626  return ecx_outframe(&ecx_port, idx, stacknumber);
627 }
628 
629 int ec_outframe_red(int idx)
630 {
631  return ecx_outframe_red(&ecx_port, idx);
632 }
633 
634 int ec_inframe(int idx, int stacknumber)
635 {
636  return ecx_inframe(&ecx_port, idx, stacknumber);
637 }
638 
639 int ec_waitinframe(int idx, int timeout)
640 {
641  return ecx_waitinframe(&ecx_port, idx, timeout);
642 }
643 
644 int ec_srconfirm(int idx, int timeout)
645 {
646  return ecx_srconfirm(&ecx_port, idx, timeout);
647 }
648 
649 #endif
int ec_outframe(int idx, int sock)
int ecx_getindex(ecx_portt *port)
Definition: win32/nicdrv.c:205
ec_bufT rxbuf[EC_MAXBUF]
Definition: erika/nicdrv.h:43
int rxsa[EC_MAXBUF]
Definition: erika/nicdrv.h:47
ec_bufT txbuf[EC_MAXBUF]
Definition: erika/nicdrv.h:68
boolean osal_timer_is_expired(osal_timert *self)
Definition: erika/osal.c:74
void ec_setupheader(void *p)
Definition: win32/nicdrv.c:188
int(* rxsa)[EC_MAXBUF]
Definition: erika/nicdrv.h:34
int txbuflength[EC_MAXBUF]
Definition: erika/nicdrv.h:70
int ec_setupnic(const char *ifname, int secondary)
#define RX_PRIM
Definition: win32/nicdrv.c:69
struct pcap pcap_t
Definition: pcap/pcap.h:87
#define EC_MAXBUF
Definition: ethercattype.h:62
ec_bufT txbuf2
Definition: erika/nicdrv.h:72
static int ecx_recvpkt(ecx_portt *port, int stacknumber)
Definition: win32/nicdrv.c:324
int rxsa[EC_MAXBUF]
Definition: erika/nicdrv.h:62
int ecx_outframe_red(ecx_portt *port, int idx)
Definition: win32/nicdrv.c:286
static char errbuf[PCAP_ERRBUF_SIZE]
Definition: win32/nicdrv.c:73
int(* txbuflength)[EC_MAXBUF]
Definition: erika/nicdrv.h:26
PACKED_BEGIN struct PACKED ec_etherheadert
bpf_u_int32 len
Definition: pcap/pcap.h:170
ec_bufT * tempbuf
Definition: erika/nicdrv.h:28
int ecx_srconfirm(ecx_portt *port, int idx, int timeout)
Definition: win32/nicdrv.c:574
const uint16 secMAC[3]
Definition: win32/nicdrv.c:66
int ecx_setupnic(ecx_portt *port, const char *ifname, int secondary)
Definition: win32/nicdrv.c:90
int txbuflength2
Definition: erika/nicdrv.h:74
uint16_t uint16
Definition: osal.h:29
#define RX_SEC
Definition: win32/nicdrv.c:71
General typedefs and defines for EtherCAT.
#define etohs(A)
Definition: ethercattype.h:536
const uint16 priMAC[3]
Definition: win32/nicdrv.c:64
#define ETH_HEADERSIZE
Definition: ethercattype.h:103
int rxbufstat[EC_MAXBUF]
Definition: erika/nicdrv.h:45
ec_stackT stack
Definition: erika/nicdrv.h:55
static void ecx_clear_rxbufstat(int *rxbufstat)
Definition: win32/nicdrv.c:75
ec_bufT rxbuf[EC_MAXBUF]
Definition: erika/nicdrv.h:58
pthread_mutex_t tx_mutex
Definition: linux/nicdrv.h:85
ec_stackT stack
Definition: erika/nicdrv.h:40
ec_bufT tempinbuf
Definition: erika/nicdrv.h:64
ec_bufT(* rxbuf)[EC_MAXBUF]
Definition: erika/nicdrv.h:30
int pcap_next_ex(pcap_t *, struct pcap_pkthdr **, const u_char **)
int ec_srconfirm(int idx, int timeout)
int ecx_outframe(ecx_portt *port, int idx, int stacknumber)
Definition: win32/nicdrv.c:257
void ecx_setbufstat(ecx_portt *port, int idx, int bufstat)
Definition: win32/nicdrv.c:244
int tempinbufs
Definition: erika/nicdrv.h:66
int ecx_inframe(ecx_portt *port, int idx, int stacknumber)
Definition: win32/nicdrv.c:374
#define PCAP_OPENFLAG_PROMISCUOUS
Defines if the adapter has to go in promiscuous mode.
Definition: remote-ext.h:203
ec_bufT(* txbuf)[EC_MAXBUF]
Definition: erika/nicdrv.h:24
void ec_setbufstat(int idx, int bufstat)
ecx_redportt * redport
Definition: erika/nicdrv.h:80
PACKED_BEGIN struct PACKED ec_comt
#define PCAP_OPENFLAG_MAX_RESPONSIVENESS
This flag configures the adapter for maximum responsiveness.
Definition: remote-ext.h:248
void pcap_close(pcap_t *)
static int ecx_waitinframe_red(ecx_portt *port, int idx, osal_timert *timer)
Definition: win32/nicdrv.c:468
int pcap_sendpacket(pcap_t *, const u_char *, int)
#define PCAP_ERROR
Definition: pcap/pcap.h:253
#define EC_TIMEOUTRET
Definition: ethercattype.h:64
#define EC_OTHERFRAME
Definition: ethercattype.h:43
pthread_mutex_t getindex_mutex
Definition: linux/nicdrv.h:84
#define PCAP_OPENFLAG_NOCAPTURE_LOCAL
Defines if the local adapter will capture its own generated traffic.
Definition: remote-ext.h:237
int ec_outframe_red(int idx)
int redstate
Definition: erika/nicdrv.h:78
#define ETH_P_ECAT
Definition: ethercattype.h:459
int wkc
Definition: aliastool.c:47
int rxbufstat[EC_MAXBUF]
Definition: erika/nicdrv.h:60
int ecx_closenic(ecx_portt *port)
Definition: win32/nicdrv.c:162
uint8 ec_bufT[EC_BUFSIZE]
Definition: ethercattype.h:87
ec_bufT tempinbuf
Definition: erika/nicdrv.h:49
int ec_closenic(void)
int ec_getindex(void)
pcap_t * pcap_open(const char *source, int snaplen, int flags, int read_timeout, struct pcap_rmtauth *auth, char *errbuf)
int ecx_waitinframe(ecx_portt *port, int idx, int timeout)
Definition: win32/nicdrv.c:551
int ec_waitinframe(int idx, int timeout)
void osal_timer_start(osal_timert *self, uint32 timeout_usec)
Definition: erika/osal.c:59
int(* rxbufstat)[EC_MAXBUF]
Definition: erika/nicdrv.h:32
int sockhandle
Definition: erika/nicdrv.h:56
#define EC_NOFRAME
Definition: ethercattype.h:41
int * sock
Definition: erika/nicdrv.h:22
pthread_mutex_t rx_mutex
Definition: linux/nicdrv.h:86
uint8 rxbuf[1024]
Definition: eoe_test.c:50
#define PCAP_ERRBUF_SIZE
Definition: pcap/pcap.h:76


soem
Author(s): Arthur Ketels and M.J.G. van den Molengraft
autogenerated on Mon Feb 28 2022 23:46:57