#ifndef SML3__NW_UDP_H_
#define SML3__NW_UDP_H_

/* ++++++++++++++++++++
 * +++ Beschreibung +++
 * ++++++++++++++++++++

  Funktionen fuer UDP
+++ */


/* ++++++++++++++++
 * +++ Beispiel +++
 * ++++++++++++++++

  Bsp fuer UDP-Server
  -------------------

  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <sys/types.h>
  #include <unistd.h>
  #include <errno.h>
  #include <signal.h>
  #include <sys/socket.h>  // setsockopt()
  #include <sammel3.h>

  // Setzen von Socketoptionen
  int optfunk(int sockfd) {
  #if defined(AF_INET6) && defined(IPV6_V6ONLY)
    int on;
    // Hybridmodus bei IPv6 (auch IPv4 als Mapped Adressen (:ffff:<IPv4>) akzeptieren) einschalten
    on = 0;
    setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on));
  #endif

    return 0;
  }

  int main(int argc, char **argv) {
    int sockfd, ipv;
    struct sockaddr *sad;
    socklen_t salen, satmp;

    // Server-Socket anlegen
    ipv = 4;
    sockfd = SML3_nw_udp_server(NULL, "1234", &ipv, &salen, optfunk);
    if (sockfd < 0) { fprintf(stderr, "%s\n", SML3_fehlermsg()); exit(1); }
    printf("Serversocket: IPv%d verwendet\n", ipv);
    if ((sad = malloc(salen)) == NULL) {  // fuer Remotehost-Info
      fprintf(stderr, "malloc: %s\n", strerror(errno));
      exit(1);
    }

    // Datagramme empfangen
    for (;;) {
      char ipnr[64], dgram[4096];
      int port;
      ssize_t slen;

      // warten auf Datagramm
      satmp = salen;
      slen = recvfrom(sockfd, dgram, sizeof(dgram), 0, sad, &satmp);
      if (slen < 0) { fprintf(stderr, "recvfrom: %s\n", strerror(errno)); continue; }

      if (SML3_nw_udp_peername(sockfd, sad, satmp, ipnr, sizeof(ipnr), &port) == 0) {
        printf("Datagramm (%d Bytes) von IP=%s und Port=%d\n", (int)slen, ipnr, port);
      }

      // Datagramm zuruecksenden
      if (sendto(sockfd, dgram, slen, 0, sad, satmp) < 0) {
        fprintf(stderr, "sendto: %s\n", strerror(errno));
        continue;
      }
    }
    close(sockfd);
    free(sad);

    exit(0);
  }


  Bsp fuer verbundenen UDP-Client
  -------------------------------

  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <sys/types.h>
  #include <unistd.h>
  #include <errno.h>
  #include <sammel3.h>

  int main(int argc, char **argv) {
    int connectfd, ipv;
    char ipnr[64], dgram[4096];
    int port;

    // Verbindung oeffnen
    ipv = 4;
    connectfd = SML3_nw_udp_connect("localhost", "1234", &ipv, NULL, NULL);
    if (connectfd < 0) { fprintf(stderr, "%s\n", SML3_fehlermsg()); exit(1); }
    fprintf(stderr, "Connectsocket: IPv%d verwendet\n", ipv);

    if (SML3_nw_udp_peername(connectfd, NULL, 0, ipnr, sizeof(ipnr), &port) == 0) {
      fprintf(stderr, "Verbunden mit IP=%s und Port=%d\n", ipnr, port);
    }

    // Daten von stdin zeilenweise an Server senden und Antwort ausgeben
    // evtl. jetzt erst bemerken, dass Server nicht existiert
    for (;;) {
      ssize_t slen;
      if (fgets(dgram, sizeof(dgram), stdin) == NULL) { break; }
      if (SML3_writen(connectfd, dgram, strlen(dgram), 1) < 0) {
        fprintf(stderr, "%s\n", SML3_fehlermsg());
        break;
      }
      slen = read(connectfd, dgram, sizeof(dgram));
      if (slen <= 0) { break; }
      printf("%.*s", (int)slen, dgram); fflush(stdout);
    }

    close(connectfd);

    exit(0);
  }


  Bsp fuer verbindungslosen UDP-Client
  ------------------------------------

  // kann auch als Broadcast-Client verwendet werden:
  // dazu muss die Broadcast-Socketoption gesetzt werden
  //   (int on = 1;
  //    setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
  //   )
  // und der Clientsocket wird mit SML3_nw_udp_client() an die Broadcast-Adresse gebunden
  // und es darf nur IPv4 verwendet werden
  // und recvfrom() wird (mit Timeout) mehrmals aufgerufen, um alle Antworten zu erhalten

  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <sys/types.h>
  #include <unistd.h>
  #include <errno.h>
  #include <sammel3.h>

  int main(int argc, char **argv) {
    int sockfd, ipv;
    char ipnr[64], dgram[4096];
    int port;
    struct sockaddr *sad_local, *sad_remote;
    socklen_t salen_local, salen_remote;

    // Clientsocket anlegen
    ipv = 4;
    sockfd = SML3_nw_udp_client("localhost", "1234", &ipv, &sad_local, &salen_local, NULL);
    if (sockfd < 0) { fprintf(stderr, "%s\n", SML3_fehlermsg()); exit(1); }
    fprintf(stderr, "Clientsocket: IPv%d verwendet\n", ipv);
    if ((sad_remote = malloc(salen_local)) == NULL) {  // fuer Remotehost-Info
      fprintf(stderr, "malloc: %s\n", strerror(errno));
      exit(1);
    }

    // Daten von stdin zeilenweise an Server senden und Antwort ausgeben
    // ob Server existiert, ist nicht erfahrbar
    for (;;) {
      ssize_t slen;

      if (fgets(dgram, sizeof(dgram), stdin) == NULL) { break; }
      // Datagramm senden
      if (sendto(sockfd, dgram, strlen(dgram), 0, sad_local, salen_local) < 0) {
        fprintf(stderr, "sendto: %s\n", strerror(errno));
        break;
      }

      // Datagramm empfangen, nach 5 Sekunden abbrechen, falls Server nicht existiert
      alarm(5);
      salen_remote = salen_local;
      slen = recvfrom(sockfd, dgram, sizeof(dgram), 0, sad_remote, &salen_remote);
      alarm(0);
      if (slen < 0) { fprintf(stderr, "recvfrom: %s\n", strerror(errno)); break; }
      if (SML3_nw_udp_peername(sockfd, sad_remote, salen_remote, ipnr, sizeof(ipnr), &port) == 0) {
        fprintf(stderr, "[Datagramm (%d Bytes) von IP=%s und Port=%d]\n", (int)slen, ipnr, port);
      }
      printf("%.*s", (int)slen, dgram); fflush(stdout);
    }

    close(sockfd);
    free(sad_local);
    free(sad_remote);

    exit(0);
  }
+++ */


/* ++++++++++++++++++ */
/* +++ Funktionen +++ */
/* ++++++++++++++++++ */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>


/** SML3_nw_udp_server [ohne Opt-Funktion: thread-sicher]:
 * UDP-Server: Server-Socket anlegen (Verwendung mit sendto() und recvfrom())
 * Hybridmodus bei IPv6 (auch IPv4 als Mapped Adressen (:ffff:<IPv4>) akzeptieren) ist abgeschaltet
 * 1.Arg: Host/IP-Nummer oder NULL = alle IPs des Hosts
 * 2.Arg: Servicename oder Port
 * 3.Arg: IP-Version: Uebergabe gewuenschte und Rueckgabe verwendete
 *        4 = IPv4
 *        6 = IPv6
 *        0 = egal
 * 4.Arg: Rueckgabe Adresslaenge
 * 5.Arg: Funktion zum Setzen von Socketoptionen
 *          (int optfunk(int sockfd) -> Rueckgabe: 0 = OK oder -1 = Fehler)
 *        oder NULL = keine
 * Rueckgabe: blockierender Socketdeskriptor oder -1 = Fehler
 * SML3-errno-Wert: EINVAL = Fehler Uebergabeparameter
 *                  ENOTCONN = Socket nicht erhalten
 *                  weitere aus SML3_getaddrinfo(), Funktion(5.Arg)
 */
extern int SML3_nw_udp_server(const char *, const char *, int *, socklen_t *, int (*)(int));


/** SML3_nw_udp_client [ohne Opt-Funktion: thread-sicher]:
 * UDP-Client: verbindungslos (zur Verwendung mit sendto() und recvfrom())
 * 1.Arg: Host/IP-Nummer
 * 2.Arg: Servicename oder Port
 * 3.Arg: IP-Version: Uebergabe gewuenschte und Rueckgabe verwendete
 *        4 = IPv4
 *        6 = IPv6
 *        0 = egal
 * 4.Arg: Adresse fuer Rueckgabe Serverinfo fuer sendto()
 *        (muss mit free() freigegeben werden)
 * 5.Arg: Rueckgabe Adresslaenge 4.Arg fuer sendto()
 * 6.Arg: Funktion zum Setzen von Socketoptionen
 *          (int optfunk(int sockfd) -> Rueckgabe: 0 = OK oder -1 = Fehler)
 *        oder NULL = keine
 * Rueckgabe: blockierender Socketdeskriptor oder -1 = Fehler
 * SML3-errno-Wert: EINVAL = Fehler Uebergabeparameter
 *                  ENOTCONN = Verbindung nicht moeglich
 *                  weitere aus SML3_getaddrinfo(), Funktion(5.Arg)
 *
 * ACHTUNG: Ob die Verbindung tatsaechlich funktioniert, kann nicht ermittelt
 *          werden, da die Schreibvorgaenge ohne Fehler ablaufen.
 */
extern int SML3_nw_udp_client(const char *, const char *, int *, struct sockaddr **, socklen_t *, int (*)(int));


/** SML3_nw_udp_connect [ohne Opt-Funktion: thread-sicher]:
 * UDP-Client: verbindungsorientiert (zur Verwendung mit SML3_writen() und read())
 * 1.Arg: Host/IP-Nummer
 * 2.Arg: Servicename oder Port
 * 3.Arg: IP-Version: Uebergabe gewuenschte und Rueckgabe verwendete
 *        4 = IPv4
 *        6 = IPv6
 *        0 = egal
 * 4.Arg: Host/IP-Nummer des Absenders fuer bind() oder NULL = automatisch
 * 5.Arg: Funktion zum Setzen von Socketoptionen
 *          (int optfunk(int sockfd) -> Rueckgabe: 0 = OK oder -1 = Fehler)
 *        oder NULL = keine
 * Rueckgabe: blockierender Socketdeskriptor oder -1 = Fehler
 * SML3-errno-Wert: EINVAL = Fehler Uebergabeparameter
 *                  ENOTCONN = Verbindung nicht moeglich
 *                  ENOMEM = Allokationsfehler
 *                  weitere aus SML3_getaddrinfo(), Funktion(5.Arg)
 *
 * ACHTUNG: Ob die Verbindung tatsaechlich da ist, erweist sich erst beim
 *          ersten Schreibvorgang zum Server (i.a. errno = ECONNREFUSED).
 *          Ausserdem sollte ein Datagram mit EINEM read()-Vorgang gelesen
 *          werden, da restliche Daten sonst verloren gehen koennten.
 */
extern int SML3_nw_udp_connect(const char *, const char *, int *, const char *, int (*)(int));


/* SML3_nw_udp_peername [thread-sicher]:
 * IP und Port des UDP-Remote-Hosts erhalten
 * 1.Arg: Socketdeskriptor
 * 2.+3.Arg: Serverinfo + Adresslaenge von recvfrom()
 *           (bei SML3_nw_udp_connect(): NULL, 0)
 * 4.Arg: fuer Rueckgabe IP
 * 5.Arg: sizeof(4.Arg)
 * 6.Arg: Rueckgabe Port oder NULL
 * Rueckgabe: 0 = OK oder -1 = Fehler
 * SML3-errno-Wert: ENOMEM = Allokationsfehler
 *                  ENXIO = Fehler getpeername()
 *                  weitere aus SML3_getnameinfo()
 */
extern int SML3_nw_udp_peername(int, const struct sockaddr *, socklen_t, char *, size_t, int *);

#endif /* SML3__NW_UDP_H_ */
