Logo Search packages:      
Sourcecode: tucnak2 version File versions  Download package

net.c

/*
    Tucnak - VHF contest log
    Copyright (C) 2002-2006  Ladislav Vaiz <ok1zia@nagano.cz>

    This program is free software; you can redistribute it and/or                                                        
    modify it under the terms of the GNU General Public License                                                          
    version 2 as published by the Free Software Foundation.

*/

#include "header.h"

/*
"relation" layer:
    KA seq          keepalive
    AK seq          ack

    AC tdate;pcall;tname;qrvbands   active contest
    CL              close contest
    
    LA source;stamp
    QS band;date_str;time_str;callsign_or_error; mode;rsts;qsonrs;rstr;\
       qsonrr;exc;locator;source; operator;stamp;callsign;; \
       ser_id;remark
    TA talk
    SK now_time;operator;src_shortpband;qrg;
       we_call;pband;time_str;callsign;   
       locator;remark
    ID 192.168.1.1:55555   
    SR start of replication block
    ER end of replication block
    GR band;ccmd;slaveid                            grab band
    DO                                              discovery peers with operator
    PO peer0ip:peer0port;oper0;peer1i:peer1port;operr1;...       peers list with operator(master->slave)
    PT band;srcip:srcport;tx                        ptt state
    WI band;srcip:srcport;text                      current inputline
    WT band;srcip:srcport;item_no;text              current tmpqso item
    RT band;destip:dstport                          request all tmpqso informations (PT, WI, WT)
    
    DI                                              OBSOLETE discovery peers
    PE peer0ip:peer0port;peer1i:peer1port;...       OBSOLETE peers list (master->slave)
    CR destip:destport;srcip:srcport                configuration request
    CF destip:destport;srcip:srcport;config_line    one configuration line

    WR destip:destport;srcip:srcport                        C_W request
    CW destip:destport;srcip:srcport;call;locator;date      one C_W line

    DX DX de HL2IFR:    24900.2  BA4ED        AS-136                         0807Z
    OP operator
    
*/

void remove_conn_timer(union cba_t cba);

struct net *net;
char *ns_desc[]={"INIT", "SABM", "CONN", "WAIT", "DIST", "DEAD"};

struct net *init_net(void){
     struct net *net;

/*    dbg("init_net()\n");*/
    net = g_new0(struct net, 1);

    init_net_tcp(net); 
    init_net_udp(net); /* require net->my.sin_port */
    
    return net;
}

void free_net(struct net *net){
    free_net_ifaces(net);
    free_net_tcp(net);
    free_net_udp(net);
    g_free(net);
}

#define NET_DELIM " \t,"



int init_net_udp(struct net *net){
    int on;
    struct sockaddr_in sin;
    socklen_t socklen;

    init_net_ifaces(net);
    
/*    dbg("init_net_udp()\n");*/
    net->udptimer_id = -1;
    net->udpsock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (net->udpsock < 0) goto err;
/*    dbg("   udpsock=%d\n", net->udpsock);*/

    on=1;
    if (setsockopt(net->udpsock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))){
        dbg_bcast("Can't set SO_REUSEADDR\n");
        goto err;
    }
    
    on=1;
    if (setsockopt(net->udpsock, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on))){
        dbg_bcast("Can't set SO_BROADCAST\n");
        goto err;
    }

    if (fcntl(net->udpsock, F_SETFL, O_NONBLOCK)){
        dbg_bcast("Can't set O_NONBLOCK\n");
        goto err;
    }
    
    memset(&sin, 0, sizeof(struct sockaddr_in));
    sin.sin_family = AF_INET;
    sin.sin_port = htons(NET_PORT);
    sin.sin_addr.s_addr = INADDR_ANY;
    if (bind(net->udpsock, (struct sockaddr *)&sin, sizeof(sin))){
        dbg_bcast("Can't bind UDP port %d\n", NET_PORT);
        goto err;
    }
    
    memset(&sin, 0, sizeof(sin));
    socklen = sizeof(sin);
    getsockname(net->udpsock, (struct sockaddr *) &sin, &socklen);
    
    net->udptimer_period = UDP_ANNOUNCE;
    net->udptimer_id = install_timer(1, udp_timer, 
                                     (union cba_t) net);
    set_handlers(net->udpsock, udp_read_handler, NULL, udp_exception_handler, CBA0);

/*    dbg("I'M %s:%d\n", inet_ntoa(net->my.sin_addr),
 *    ntohs(net->my.sin_port));*/
    dbg_bcast("My ID: %s\n", net->myid);
    return 0;
err:;
    close(net->udpsock);
    net->udpsock = -1;
    
    /* kill_timer not needed */
    return 1;    
}


int init_net_tcp(struct net *net){
    int on, port;
    struct sockaddr_in sin;

    /*dbg("init_net_tcp()\n");*/

    net->peers = g_ptr_array_new();
    
    net->tcpsock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    /*dbg("   tcpsock=%d\n", net->tcpsock);*/

#ifndef __CYGWIN__    
    on=1;
    if (setsockopt(net->tcpsock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))){
        dbg_sock("Can't set SO_REUSEADDR\n");
        goto x;
    }
#endif    
    
    if (fcntl(net->tcpsock, F_SETFL, O_NONBLOCK)){
        dbg_sock("Can't set O_NONBLOCK\n");
        goto x;
    }
        
    for (port=NET_PORT; port<65536; port++){
        net->my.sin_port = htons(port);
        net->global.sin_port = htons(port);
        memset(&sin, 0, sizeof(struct sockaddr_in));
        sin.sin_family = AF_INET;
        sin.sin_port = net->my.sin_port;
        sin.sin_addr.s_addr = INADDR_ANY;
        if (bind(net->tcpsock, (struct sockaddr *)&sin, sizeof(sin))==0) break;

/*        dbg("Can't bind tcp port %d\n", NET_PORT);*/
        if (port==65535) goto x;
        continue;
    }
    
    if (listen(net->tcpsock, 10)){
        dbg_sock("Can't listen on socket %d, tcp port %d \n", net->tcpsock, net->my.sin_port);
        goto x;
    }

    sock_debug(net->tcpsock, "listen init_net_tcp");
    set_handlers(net->tcpsock, tcp_accept_handler, NULL, tcp_exception_handler, CBA0);
    return 0;
x:;
    sock_debug(net->tcpsock, "close net->tcpsock");
    close(net->tcpsock);
    net->my.sin_port=0;
    net->global.sin_port=0;
    net->tcpsock=-1;
    return -1;
}

enum assign_types {AT_UNASSIGNED, AT_LOOPBACK, AT_ASSIGNED};

int init_net_ifaces(struct net *net){
    struct iface_struct ifaces[MAX_INTERFACES];
    int i,mi,j,ii;
    GPtrArray *if_ignore, *ip_ignore, *ip_announce;
    char *token_ptr;
    gchar *c;
    GString *gs;
    enum assign_types assign_type;

    if_ignore = g_ptr_array_new();
    ip_ignore = g_ptr_array_new();
    ip_announce = g_ptr_array_new();
    
    if (cfg->net_if_ignore){
        gchar *net_if_ignore;
        net_if_ignore = g_strdup(cfg->net_if_ignore);
        for (c=strtok_r(net_if_ignore, NET_DELIM, &token_ptr); c!=NULL; 
             c=strtok_r(NULL, NET_DELIM, &token_ptr)){
            dbg_bcast("adding iface '%s' for ignoring", c);
            c=g_strdup(c);
/*            dbg("dup=%p\n", c);*/
            g_ptr_array_add(if_ignore, c);
        }   
        g_free(net_if_ignore);
    }
    
    if (cfg->net_ip_ignore){
        gchar *net_ip_ignore;
        net_ip_ignore = g_strdup(cfg->net_ip_ignore);
        for (c=strtok_r(cfg->net_ip_ignore, NET_DELIM, &token_ptr); c!=NULL; 
             c=strtok_r(NULL, NET_DELIM, &token_ptr)){
            dbg_bcast("adding addr '%s' for ignoring\n", c);
            g_ptr_array_add(ip_ignore, g_strdup(c));
        }   
        g_free(net_ip_ignore);
    }
    
    
    net->myid = g_strdup_printf("%s:%d", inet_ntoa(net->my.sin_addr), ntohs(net->my.sin_port));
    assign_type = AT_UNASSIGNED;
    dbg_bcast("preassigned net->myid=%s", net->myid); 
    

    mi = get_interfaces(ifaces, MAX_INTERFACES); /* network byteorder */
    j = 0;
    for (i=0;i<mi; i++){
        struct sockaddr_in *sin;
        int is_lo;
        
        dbg_sock("iface %s\n",ifaces[i].name);
        is_lo=!strcmp(ifaces[i].name, "lo");
        
/*        if (mi>1 && strcmp(ifaces[i].name, "lo")==0) continue;*/
        if ((int)ifaces[i].ip.s_addr==0) continue;  /* 0.0.0.0 happens on cygwin */
        
        for (ii=0; ii<if_ignore->len; ii++){
            gchar *c;
            c = (gchar *)g_ptr_array_index(if_ignore, ii);
            if (strcmp(c, ifaces[i].name)==0) {
                dbg_bcast(VTEXT(T_IGN_IFACE_S),ifaces[i].name);
                goto next_loop;
            }
        }
        
        sin = (struct sockaddr_in *)&net->bcast_addr[j];

        sin->sin_family = AF_INET;
        sin->sin_addr.s_addr = ifaces[i].ip.s_addr | ~(ifaces[i].netmask.s_addr);
        sin->sin_port = htons(NET_PORT);
        
        
        for (ii=0; ii<ip_ignore->len; ii++){
            gchar *c;
            struct in_addr iad;
            
            c = (gchar *)g_ptr_array_index(ip_ignore, ii);
            if (!inet_aton(c, &iad)) continue; /* invalid addr */
            if (iad.s_addr==sin->sin_addr.s_addr) {
                dbg_bcast(VTEXT(T_IGN_ADDR_S), inet_ntoa(sin->sin_addr));
                goto next_loop;
            }
        }
        
        
        dbg_bcast("adding iface '%s'\n", ifaces[i].name);
        
        if (assign_type!=AT_ASSIGNED) {
            net->my.sin_addr.s_addr = ifaces[i].ip.s_addr;
            net->global.sin_addr.s_addr = ifaces[i].ip.s_addr;
            if (net->myid) g_free(net->myid);
            net->myid = g_strdup_printf("%s:%d", inet_ntoa(net->my.sin_addr), ntohs(net->my.sin_port));
            assign_type=is_lo?AT_LOOPBACK:AT_ASSIGNED;
            dbg_bcast("assigned net->myid=%s\n", net->myid); 
        }
        j++;

        dbg_bcast(VTEXT(T_ADD_IFACE_SSD), ifaces[i].name, inet_ntoa(sin->sin_addr), NET_PORT);
next_loop:;
    }

    if (cfg->net_ip_announce){
        for (c=strtok_r(cfg->net_ip_announce, NET_DELIM, &token_ptr); c!=NULL; 
             c=strtok_r(NULL, NET_DELIM, &token_ptr)){
            
            struct in_addr iad;
            struct sockaddr_in *sin;

            if (!inet_aton(c, &iad)) continue; /* invald addr */

            sin = (struct sockaddr_in *)&net->bcast_addr[j];

            sin->sin_family = AF_INET;
            sin->sin_addr.s_addr = iad.s_addr;
            sin->sin_port = htons(NET_PORT);
            
            log_addf(VTEXT(T_FORCED_ADDR), inet_ntoa(sin->sin_addr));
            j++;
        }   
    }
    
    
    net->max_addrs = j;

    g_ptr_array_free_all(if_ignore);
    g_ptr_array_free_all(ip_ignore);
    g_ptr_array_free_all(ip_announce);
    
    gs=g_string_sized_new(100);
    for (i=0; i<net->max_addrs; i++){
        struct sockaddr_in *sin;

        if (i>0) g_string_sprintfa(gs, ", ");
        sin = (struct sockaddr_in *)&net->bcast_addr[i];
        g_string_sprintfa(gs, "%s:%d", inet_ntoa(sin->sin_addr), ntohs(sin->sin_port));
    }
    dbg_bcast("Broadcast to: %s", gs->str);
    g_string_free(gs, TRUE);
    
    return 0;
}         

void free_net_udp(struct net *net){
    if (net->udptimer_id >= 0) kill_timer(net->udptimer_id);
    close(net->udpsock);

}

void free_conn(struct conn *conn){
    set_handlers(conn->sock, NULL, NULL, NULL, CBA0);
    sock_debug(conn->sock, "close free_conn()");
    close(conn->sock);
    tcp_kill(conn);
    tcp_set_state(conn, NS_DEAD);
    if (conn->wrbuf) g_string_free(conn->wrbuf, 1);
    if (conn->rdbuf) g_string_free(conn->rdbuf, 1);
    CONDGFREE(conn->operator);
    CONDGFREE(conn->remote_id);
    CONDGFREE(conn->remote_ac);
    /* look at udp_read_handler near to end */
}

void free_net_ifaces(struct net *net){
    if (net->myid) g_free(net->myid);
}


void free_net_tcp(struct net *net){
    struct conn *conn;
    int i;
    
    sock_debug(net->tcpsock, "close net->tcpsock (2)");
    close(net->tcpsock);

    if (net->master) {
        free_conn(net->master);
        g_free(net->master);
        net->master = NULL;
    }
    
    for (i=net->peers->len-1; i>=0; i--){
        conn = (struct conn *) g_ptr_array_index(net->peers, i);
        
        free_conn(conn);
        g_ptr_array_remove_index(net->peers, i);
        g_free(conn);
        
    }
    CONDGFREE(net->allpeers);
}


/****************** SELECT's HANDLERS ********************************/

void udp_read_handler(union cba_t cba){
    char s[1024];
    struct sockaddr_in sin;
    socklen_t socklen;
    gchar **items;
    int i;
    time_t expire, now;
    struct sockaddr_in peer;
    struct sockaddr_in master;
    
/*    dbg("udp_read_handler()\n");*/

    now = time(NULL);
    
    memset(s, 0, sizeof(s));
    socklen = sizeof(sin);
    recvfrom(net->udpsock, s, sizeof(s)-1, 0, 
            (struct sockaddr *)&sin, &socklen);

    if (strlen(s)>0 && s[strlen(s)-1]=='\n') s[strlen(s)-1]='\0';
    if (strlen(s)>0 && s[strlen(s)-1]=='\r') s[strlen(s)-1]='\0';
    
    items=g_strsplit(s, ";", 0);
    for (i=0;i<7;i++) {
        if (items[i]==NULL) {
            g_strfreev(items);        
            return;
        }
    }

    if (strcmp(items[0], "tucnak")!=0) {
        g_strfreev(items);
        return;
    }
     
    peer.sin_addr.s_addr   = inet_addr(items[2]); 
    peer.sin_port          = htons(atoi(items[3]));
    master.sin_addr.s_addr = inet_addr(items[4]); 
    master.sin_port        = htons(atoi(items[5]));
    expire                 = atoi(items[6]); 
    
    g_strfreev(items);        
    
    if (expire < now) return; /* ignore expired information */
    if (expire < now-NET_MAX_SKEW_NEG ||
        expire > now+NET_MAX_SKEW_POS) {
        log_addf(VTEXT(T_TIMESKEW), now-expire, inet_ntoa(master.sin_addr), ntohs(master.sin_port));
    }
    if (expire > now+NET_MAX_SKEW_POS){
        dbg_bcast("ignoring exire in future, ignoring\n");
        return;
    }
    
    /* network byteorder */
    if (net->my.sin_addr.s_addr == peer.sin_addr.s_addr && 
        net->my.sin_port        == peer.sin_port) return;
    
    dbg_bcast("rcvd: %s: %s", inet_ntoa(sin.sin_addr),s); 
    
    if (cmp_sin(&master, &net->global)==0){ /* updating expire time */
        net->global_expire          = expire;
    }
        
    if (cmp_sin(&master, &net->global)<0){ /* received master is "global" */
        net->global.sin_family      = AF_INET;
        net->global.sin_addr.s_addr = master.sin_addr.s_addr;
        net->global.sin_port        = master.sin_port;
        net->global_expire          = expire;
        dbg_bcast("new master is %s:%d, now=%d, expires %d \n", inet_ntoa(net->global.sin_addr), ntohs(net->global.sin_port), now%1000, expire%1000);
    }
    if (cmp_sin(&master, &net->global)>0){ /* received master my slave */
        dbg_bcast("tucnak %s:%d means it is master but isn't true\n", inet_ntoa(net->global.sin_addr), ntohs(net->global.sin_port));
        kill_timer(net->udptimer_id);
        net->udptimer_id = install_timer(1, udp_timer, (union cba_t) net);
    }
   
    /* is net->master "global" master? */
    if (net->master) {
        if (cmp_sin(&net->master->sin, &net->global)!=0) { /* net->master > net->global, disconnect */
            dbg_sock("disconnecting from %s:%d\n", inet_ntoa(net->master->sin.sin_addr), ntohs(net->master->sin.sin_port));
            log_addf(VTEXT(T_DISCONNECTING_SD), inet_ntoa(net->master->sin.sin_addr), ntohs(net->master->sin.sin_port));
/*            CONDGFREE(net->master->operator);
            CONDGFREE(net->master->remote_id);
            CONDGFREE(net->master->remote_ac);*/
            free_conn(net->master);
            g_free(net->master);
            net->master = NULL;
        }
    }
    
    if (!net->master && cmp_sin(&net->global, &net->my)<0){
        dbg_sock("Now i'm a master\n");
        net->master = g_new0(struct conn, 1);

        net->master->sin.sin_family      = AF_INET;
        net->master->sin.sin_addr.s_addr = net->global.sin_addr.s_addr;
        net->master->sin.sin_port        = net->global.sin_port;
        
        tcp_connect(net->master);
    }
}

void udp_exception_handler(union cba_t cba){

    dbg("udp_exception_handler()\n");
}


void tcp_accept_handler(union cba_t cba){
    int sock,i;
    struct sockaddr_in sin;
    socklen_t socklen;
    
    struct conn *conn;
    
    
    /*dbg("tcp_accept_handler()\n");*/

    socklen = sizeof(sin);
    sock = accept(net->tcpsock, (struct sockaddr *)&sin, &socklen);
    if (!socklen || sock<0) return;

    dbg_sock("Accepted socket %d %s:%d\n", sock, inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
    dbg("Accepted socket %d %s:%d\n", sock, inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
    log_addf(VTEXT(T_ACCEPTED_SD), inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));

    for (i=0;i<net->peers->len;i++){
        union cba_t cba;

        conn=(struct conn *)g_ptr_array_index(net->peers, i);
        if (conn->sock!=sock) continue;
        if (conn->timer) kill_timer(conn->timer);
        cba.conn=conn;
        remove_conn_timer(cba);
    }
    
    conn = g_new0(struct conn, 1);
    conn->sin.sin_family      = AF_INET;
    conn->sin.sin_addr.s_addr = sin.sin_addr.s_addr;
    conn->sin.sin_port        = sin.sin_port;
    conn->sock = sock;
    tcp_set_state(conn, NS_CONNECTED);
    
    set_handlers(conn->sock, tcp_read_handler, 
            NULL, tcp_exception_handler, (union cba_t)conn);

    g_ptr_array_add(net->peers, conn);

    dump_all_sources(ctest);

    dbg("tcp_accept_handler: net_send_id() sock=%d\n", conn->sock);
    net_send_id();
    net_send_ac();
    net_send_operator();
}



void tcp_connected_handler(union cba_t cba){
    struct conn *conn;
    int ret, err;
    socklen_t optlen;
    
    conn = cba.conn;
/*    dbg("tcp_connected_handler %s:%d\n", inet_ntoa(conn->sin.sin_addr),
 *    ntohs(conn->sin.sin_port));*/

    optlen = sizeof(err);
    ret = getsockopt(conn->sock, SOL_SOCKET, SO_ERROR, (void *)&err, &optlen);
/*    dbg("   getsockopt(SO_ERROR) returns %d err is %d\n", ret, err);  */
    if (ret!=0){
        tcp_disconnect(conn);
        return;
    }
    if (err!=0){
        tcp_disconnect(conn);
        return;
    }
    log_addf(VTEXT(T_CONNECTED_SD), inet_ntoa(conn->sin.sin_addr), ntohs(conn->sin.sin_port));
    dump_all_sources(ctest);
    tcp_set_state(conn, NS_CONNECTED);
    set_handlers(conn->sock, tcp_read_handler, 
            NULL, tcp_exception_handler, (union cba_t)conn);
    dbg("tcp_connected_handler: net_send_id() sock=%d\n", conn->sock);
    net_send_id();
    net_send_ac();
    net_send_operator();
   
}


void tcp_read_handler(union cba_t cba){
    struct conn *conn;            
    char s[1030];
    int ret,err;
    gchar *d, *line;
    char errstr[1030];
    
    
    conn = cba.conn;
/*    dbg("tcp_read_handler %s:%d\n", inet_ntoa(conn->sin.sin_addr),
 *    ntohs(conn->sin.sin_port));*/
    
    ret=read(conn->sock, s, 1024);
    err=errno;
   /* dbg("   read(%d) returns %d (%d) '%s'\n", conn->sock, ret, err,
     strerror_r(err, errstr, sizeof(errstr)));*/
    if (ret<=0) {
        dbg_sock(" !!! read ERROR sock=%d err=%d %s\n", conn->sock, err, strerror_r(err, errstr, sizeof(errstr)));
        dbg(" !!! read ERROR sock=%d err=%d %s\n", conn->sock, err, strerror_r(err, errstr, sizeof(errstr)));
        tcp_disconnect(conn);
        return;
    }
    
    s[ret]='\0';
    dbg_recv("s%d: read '%s' (%d)", conn->sock, s, ret);
    
    if (!conn->rdbuf) conn->rdbuf = g_string_sized_new(1024);
    g_string_append(conn->rdbuf, s);
    
    /* na tomhle to pada */
/*    strcpy(s, "ID 192.168.1.132:55556\nAC 20060506;OK1KRQ;II. subregionalni zavod 2006;cegk\nLA 192.168.1.68:55555;1146999049;192.168.1.132:55555;1146991645;192.168.1.132:55556;1147001847;192.168.1.111:55555;1146932813;192.168.1.82:55555;1147003677;192.168.1.42:55555;1147002456;\n");*/
/*    dbg("   s='%s'\n", s);*/
    dbg_recv("tcp_read_handler() entry conn->rdbuf=%p '%s'\n", conn->rdbuf, conn->rdbuf->str);
    while (1){
        dbg_recv("tcp_read_handler() 1 conn->rdbuf=%p '%s'\n", conn->rdbuf, conn->rdbuf->str);
        if (!conn->rdbuf) raise(SIGSEGV);    
        d=index(conn->rdbuf->str, '\n');
        if (!d) break;
        line = g_strndup(conn->rdbuf->str, d - conn->rdbuf->str);
/*        dbg("       line='%s'\n", line);*/
        dbg_recv("tcp_read_handler() 2 conn->rdbuf=%p '%s'\n", conn->rdbuf, conn->rdbuf->str);
        g_string_erase(conn->rdbuf, 0, d - conn->rdbuf->str + 1);
        dbg_recv("tcp_read_handler() 3 conn->rdbuf=%p '%s'\n", conn->rdbuf, conn->rdbuf->str);
/*        dbg("   line='%s', remains '%s'\n", line, conn->rdbuf->str);*/
        if (strlen(line)>0) rel_read(conn, line);
        g_free(line);
    }
    dbg_recv("tcp_read_handler() exit conn->rdbuf=%p\n", conn->rdbuf);
}


void tcp_write_handler(union cba_t cba){
    struct conn *conn;
    int towrite, written, err;
    char errstr[1030];

    conn = cba.conn;
/*    dbg("tcp_write_handler %s:%d\n", inet_ntoa(conn->sin.sin_addr), ntohs(conn->sin.sin_port));*/
    
    if (!conn->wrbuf){
        set_handlers(conn->sock, tcp_read_handler, 
                NULL, tcp_exception_handler, (union cba_t)conn);
        return;
    }
    towrite = strlen(conn->wrbuf->str);
    written = write(conn->sock, conn->wrbuf->str, towrite);
    err=errno;
    dbg_send("s%d: tcp_write_handler '%s' (%d/%d) %s", conn->sock, conn->wrbuf->str, towrite, written,err);
    /*dbg("    towrite=%d  written=%d \n", towrite, written); */
    if (written<=0){
        dbg_sock("!!!!! ERROR %s\n", strerror_r(err, errstr, sizeof(errstr))); 
        tcp_disconnect(conn);
        return;
    }
    if (towrite == written){
        g_string_free(conn->wrbuf, 1);
        conn->wrbuf = NULL;
        set_handlers(conn->sock, tcp_read_handler, 
                NULL, tcp_exception_handler, (union cba_t)conn);
        return;
        
    }
    g_string_erase(conn->wrbuf, 0, written);

}


void tcp_exception_handler(union cba_t cba){
    struct conn *conn;
    
    conn = cba.conn;
    dbg("tcp_exception_handler %s:%d\n", inet_ntoa(conn->sin.sin_addr), ntohs(conn->sin.sin_port)); 
    
    tcp_disconnect(conn);
}

/*************************** TIMERS *********************************/

void udp_timer(union cba_t cba){
    struct net *net;
    struct sockaddr_in sin;
    socklen_t socklen;
    int i;
    GString *gs;
    time_t now;
    

    net = cba.net;
    
    now = time(NULL);
    if (net->global_expire < now){ /* global expired */
        net->global.sin_family      = AF_INET;
        net->global.sin_addr.s_addr = net->my.sin_addr.s_addr;
        net->global.sin_port        = net->my.sin_port;
        net->global_expire          = now + NET_GLOBAL_EXPIRE;
        dbg_bcast("expired, i'm new master %s:%d now=%d, expire=%d \n", inet_ntoa(net->global.sin_addr), ntohs(net->global.sin_port), now%1000, net->global_expire%1000);
    }
    

    if (cmp_sin(&net->global, &net->my)==0) {  /* sending maximal time */
/*        dbg("setting expire to maximal\n");*/
        net->global_expire          = now + NET_GLOBAL_EXPIRE;
    }
    
  /*  dbg("udp_timer()\n"); */
    
/*     0     1      2     3      4         5          6  */  
/* "tucnak;version;myip;myport;masterip;masterport;masterexpire */
    gs = g_string_sized_new(100); 
    g_string_sprintfa(gs, "tucnak;%s;", VERSION_STRING); 
    g_string_append(gs, inet_ntoa(net->my.sin_addr));
    g_string_append_c(gs,';');

    g_string_sprintfa(gs, "%d;", htons(net->my.sin_port));
    
    g_string_sprintfa(gs, "%s;%d;%d", inet_ntoa(net->global.sin_addr), 
                                    ntohs(net->global.sin_port),
                                    (int)net->global_expire);

    
/*    dbg("snd'%s' now=%d ex=%d\n", gs->str, now%1000, net->global_expire%1000);*/
    dbg_bcast("send: %s\n", gs->str);
    g_string_append(gs,"\r\n");
    
    for (i=0; i<net->max_addrs; i++){
        socklen = sizeof(sin);
        sendto(net->udpsock, gs->str, strlen(gs->str), 
                0, net->bcast_addr+i, socklen);
    }
    
    g_string_free(gs, 1);
    net->udptimer_id = install_timer(net->udptimer_period, udp_timer, 
                                     (union cba_t) net);
    
}

void send_ack_timer(union cba_t cba){
    struct conn *conn;
    char s[1026];

    conn = cba.conn;
    /*dbg("send_ack_timer %s:%d\n", inet_ntoa(conn->sin.sin_addr),
     * ntohs(conn->sin.sin_port));*/
    conn->timer = 0;
    
    g_snprintf(s, 1024, "KA %d\n", ++conn->relseq);
    rel_write(conn, s);
    tcp_set_state(conn, NS_WAIT_ACK);
}

void kill_conn_timer(union cba_t cba){
    struct conn *conn;

    conn = cba.conn;
/*    dbg("kill_conn_timer %s:%d\n", inet_ntoa(conn->sin.sin_addr),
 *    ntohs(conn->sin.sin_port));*/
    conn->timer = 0;
    tcp_disconnect(conn);
}

void remove_conn_timer(union cba_t cba){
    struct conn *conn, *cx;
    int i;

    conn = cba.conn;
/*    dbg("remove_conn_timer %s:%d\n", inet_ntoa(conn->sin.sin_addr),
 *    ntohs(conn->sin.sin_port));*/
    conn->timer = 0;
    
    if (net->master == conn) {
        free_conn(net->master);
        g_free(net->master);
        net->master = NULL;
    }

    for (i=net->peers->len-1; i>=0; i--){
        cx = (struct conn *) g_ptr_array_index(net->peers, i);
        if (cx == conn) {
            g_ptr_array_remove_index(net->peers, i);
            free_conn(conn);
            g_free(conn);
        }
    }
    
}


int conn_prod_state(struct conn *conn){
    if (!conn) return 0;
    if (conn->state == NS_CONNECTED || conn->state == NS_WAIT_ACK ) return 1;
    return 0;
}

void tcp_set_state(struct conn *conn, enum net_state state){
    
/*    dbg("tcp_set_state %d -> %d \n", conn->state, state);*/
    
    if (conn->timer) kill_timer(conn->timer);
    conn->timer = 0;
    conn->state = state;
    switch (conn->state){
        case NS_INIT:
            break;
        case NS_CONNECTING:
            conn->timer = install_timer(CONNECTION_TIMEOUT, kill_conn_timer, (union cba_t)conn);
            break;
        case NS_CONNECTED:
            conn->timer = install_timer(SEND_ACK_TIMEOUT, send_ack_timer, (union cba_t)conn);
            break;
        case NS_WAIT_ACK:
            conn->timer = install_timer(ACK_TIMEOUT, kill_conn_timer, (union cba_t)conn);
            break;
        case NS_DISCONNECTED:
            conn->timer = install_timer(REMOVE_TIMEOUT, remove_conn_timer, (union cba_t)conn);
            break;
        case NS_DEAD:
            break;    
    }
}

char *tcp_state_s(struct conn *conn){
    switch (conn->state){
        case NS_INIT:
            return "INIT";
        case NS_CONNECTING:
            return "CONNECTING";
        case NS_CONNECTED:
            return "CONNECTED";
        case NS_WAIT_ACK:
            return "WAIT_ACK";
        case NS_DISCONNECTED:
            return "DISCONNECTED";
        case NS_DEAD:
            return "DEAD";
    }
    return "???";
}

void tcp_connect(struct conn *conn){
    int ret;
    char errstr[1030];
    
/*    dbg("tcp->connect %s:%d\n", inet_ntoa(conn->sin.sin_addr), ntohs(conn->sin.sin_port));  */
   
    conn->sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
/*    dbg("socket returns %d \n", conn->sock);*/
    
    if (fcntl(conn->sock, F_SETFL, O_NONBLOCK)){
        dbg_sock("Can't set O_NONBLOCK\n");
        return;
    }
    
    ret = connect(conn->sock, (struct sockaddr *) &conn->sin, sizeof(struct sockaddr_in));
/*    dbg("connect returns %d (%d) '%s'\n", ret, errno, strerror_r(errno));*/
    if (ret==0){
        set_handlers(conn->sock, tcp_read_handler, NULL, tcp_exception_handler, (union cba_t)conn);
        tcp_set_state(conn, NS_CONNECTED);
        log_addf(VTEXT(T_CONNECTED_SD), inet_ntoa(conn->sin.sin_addr), ntohs(conn->sin.sin_port));
        dump_all_sources(ctest);
        dbg("tcp_connect: net_send_id() sock=%d\n", conn->sock);
        net_send_id();
        net_send_ac();
        net_send_operator();
        return;
    }
    if (ret<0){
        if (errno==EALREADY || errno==EINPROGRESS){
            tcp_set_state(conn, NS_CONNECTING);
            set_handlers(conn->sock, NULL, tcp_connected_handler, tcp_exception_handler, (union cba_t)conn);
            log_addf(VTEXT(T_CONNECTING_SD), inet_ntoa(conn->sin.sin_addr), ntohs(conn->sin.sin_port));
            return;
        }  
        dbg_sock("connect error %s\n", strerror_r(errno, errstr, sizeof(errstr)));
        tcp_disconnect(conn);
    }
    
}

void tcp_disconnect(struct conn *conn){
/*    dbg("tcp->disconnect %s:%d\n", inet_ntoa(conn->sin.sin_addr),
 *    ntohs(conn->sin.sin_port));*/
    log_addf(VTEXT(T_DISCONNECTED_SD), inet_ntoa(conn->sin.sin_addr), ntohs(conn->sin.sin_port));
    tcp_set_state(conn, NS_DISCONNECTED);
    set_handlers(conn->sock, NULL, NULL, NULL, CBA0);
    dbg_sock("s%d: close tcp_disconnect", conn->sock);
    dbg("tcp_disconnect(%d)\n", conn->sock);
    dump_all_sources(ctest);
    close(conn->sock);
    if (conn->wrbuf) g_string_free(conn->wrbuf, 1);
    if (conn->rdbuf) g_string_free(conn->rdbuf, 1);
    conn->wrbuf = conn->rdbuf = NULL;
}

void tcp_kill(struct conn *conn){
    /*dbg("tcp->disconnect %s:%d\n", inet_ntoa(conn->sin.sin_addr),
     * ntohs(conn->sin.sin_port));*/
    set_handlers(conn->sock, NULL, NULL, NULL, CBA0);
    tcp_set_state(conn, NS_DISCONNECTED);
    dbg_sock("s%d: close tcp_kill", conn->sock);
    dump_all_sources(ctest);
    close(conn->sock);
}

int cmp_sin(struct sockaddr_in *a, struct sockaddr_in *b){
    int sign;
    
    sign = ntohl(a->sin_addr.s_addr) - ntohl(b->sin_addr.s_addr);
    if (sign) return sign;
    sign = ntohs(a->sin_port) - ntohs(b->sin_port);
    return sign;
}


/***************** "RELATION" LAYER ********************************/

void rel_write(struct conn *conn, gchar *s){
    int towrite, written,err;
    GString *oldwrbuf;
    char errstr[1030];
    
    oldwrbuf=conn->wrbuf;
    if (!conn->wrbuf) conn->wrbuf = g_string_sized_new(1024);
    g_string_append(conn->wrbuf, s);

    if (oldwrbuf){
        /* conn->wrbuf!=NULL at start of rel_write means there is unsent data.
         * It's send in tcp_write_handler */
        dbg_send("s%d: rel_write '%s' appended to wrbuf");
        return;
    }

    towrite = strlen(conn->wrbuf->str);
    written = write(conn->sock, conn->wrbuf->str, towrite);
    err=errno;
    dbg_send("s%d: rel_write '%s' (%d/%d/%d)", conn->sock, conn->wrbuf->str, towrite, written,err);
    if (written<0){
        if (err==EAGAIN){
            set_handlers(conn->sock, tcp_read_handler, 
                    tcp_write_handler, tcp_exception_handler, (union cba_t)conn);
            return;
        }
        dbg_sock("error writing %s\n", strerror_r(err, errstr, sizeof(errstr)));
        tcp_disconnect(conn);
        return;
    }
        
    if (towrite == written){
        g_string_free(conn->wrbuf, 1);
        conn->wrbuf = NULL;
        return;
    }
    g_string_erase(conn->wrbuf, 0, written);
    set_handlers(conn->sock, tcp_read_handler, 
            tcp_write_handler, tcp_exception_handler, (union cba_t)conn);
                
}

void rel_write_all(gchar *s){
    int i;
    struct conn *conn;
    char ss[256];
    
    strncpy(ss, s, 2);
    ss[2]='\0';

    if (net->master && conn_prod_state(net->master)) {
        /*dbg("rel_write_all('%s') %d(%d)", ss, net->master->sock, tcp_state_s(net->master));*/
        rel_write(net->master, s);
        return;
    }
    
    /*dbg("rel_write_all('%s') ",ss);*/
    for (i=0; i<net->peers->len; i++){
        conn = (struct conn *) g_ptr_array_index(net->peers, i);    
        if (conn_prod_state(conn)) {
            /*dbg("%d(%s) ", conn->sock,tcp_state_s(conn) );*/
            rel_write(conn, s);
        }
    }
/*    dbg("\n"); */
}

/* writes to all almost conn */
void rel_write_almost_all(struct conn *almost_conn, gchar *s){
    int i;
    struct conn *conn;
   
/*    dbg("rel_write_almost_all(%d, '%s')\n", almost_conn->sock, s);  */
    if (net->master && conn_prod_state(net->master)) {
        if (net->master == almost_conn) return;
/*        dbg("   rel_write(%d)\n", net->master->sock);*/
        rel_write(net->master, s);
        return;
    }
    
    for (i=0; i<net->peers->len; i++){
        conn = (struct conn *) g_ptr_array_index(net->peers, i);    
        if (conn == almost_conn) continue;
        if (conn_prod_state(conn)) {
/*            dbg("   rel_write(%d)\n", conn->sock);       */
            rel_write(conn, s);
        }
    }
    
}

struct conn *find_conn_by_remote_id(gchar *remote_id){
    int i;
    struct conn *conn;

    for (i=net->peers->len-1;i>=0;i--){ /* backward loop because one ip+port can be here twice (DIST,DIST,DIST,CONN) */
        conn=(struct conn *)g_ptr_array_index(net->peers, i);
        if (!conn->remote_id) continue;
        if (strcmp(conn->remote_id,remote_id)==0){
            return conn;
        }
    }
    return NULL;
}

int net_route(gchar *s, gchar **items){
    struct conn *conn;
    gchar *c;
    
    if (!items[0]) return -1;
    
    if (strcmp(net->myid,items[0])==0) return 0; /* packet is for me */
    
    conn = find_conn_by_remote_id(items[0]);
    if (!conn || !conn_prod_state(conn)) return -1;
    
    c=g_strconcat(s,"\n",NULL);
/*    dbg("routing '%s'\n",c);*/
    rel_write(conn, c);
    g_free(c);
    return 1;
}

static struct conn *sconn; /* a little hack with global var :-( */

void rel_read(struct conn *conn, gchar *line){
    char s[1026];
    gchar **items, *c;
    

/*    dbg("rel_read '%s'\n", line);*/
    
    if (strncmp(line, "KA", 2)==0){    /* keepalive */
/*        dbg("   recvd KA, sending AK\n");*/
        
        g_snprintf(s, 1024, "AK%s\n", line+2);
        rel_write(conn, s);
        return;
    }

    if (strncmp(line, "AK", 2)==0){   /* keepalive ack */
/*        dbg("   recvd AK, setting NS_CONNECTED\n");*/
        /* todo AK number */
        tcp_set_state(conn, NS_CONNECTED);
        
        return;
    }
    
  /************** app layer ********************/  
    
/*    dbg("app_read sock=%d '%s'\n", conn->sock, line);*/
    
    if (strncmp(line, "ID", 2)==0){     /* peers ID */
/*        log_addf("%d ID '%s'",conn->sock, line); */
        if (strlen(line)<4) return; 
        c = line+3;

        if (conn->remote_id) g_free(conn->remote_id);
        conn->remote_id = g_strdup(c);
        return;
    }
    
    if (strncmp(line, "AC", 2)==0){     /* active contest */
/*        log_addf("%d AC '%s'",conn->sock, line); */
        if (strlen(line)<4) return; 
        c = line+3;

        if (conn->remote_ac) g_free(conn->remote_ac);
        conn->remote_ac = g_strdup(c);
        
        if (!ctest) return;

        net_test_same_contest(conn, c);
        return;
    }
    
    if (strncmp(line, "CL", 2)==0){       /* close contest */
/*        log_addf("%d CL", conn->sock);*/
        if (conn->remote_ac) {
            g_free(conn->remote_ac);
            conn->remote_ac = NULL;
        }
        conn->is_same_ctest = 0;
        return;
    }
    
    if (strncmp(line, "LA", 2)==0){      /* latest qsos for each source */
        int i;
        GHashTable *remote_latests;
        
        if (!conn->is_same_ctest) return;
        
        if (strlen(line)<3) return; 
        c = line+3;
        
        remote_latests = g_hash_table_new(g_str_hash, g_str_equal);
        
        items=g_strsplit(c, ";", 0);
        for (i=0; items[i]!=NULL && items[i+1]!=NULL; i+=2){
            gchar *source, *latest_s;
            
            source = items[i];
            latest_s = items[i+1];
            g_hash_table_insert(remote_latests, g_strdup(source), g_strdup(latest_s));
            dbg_sock("  rcvd LA: %s = %d\n", source, latest_s);
        }
        g_strfreev(items);
        
        /*dbg("LA recvd sock=%d:\n", conn->sock);*/
        dbg_str_hash(remote_latests);
/*        dbg("ctest->bystamp:\n");
        dbg_str_hash(ctest->bystamp);*/
        sconn = conn;
        rel_write(conn, "SR\n");
        g_hash_table_foreach(ctest->bystamp, compare_remote_with_me, remote_latests);
        rel_write(conn, "ER\n");
        
        g_hash_table_foreach_remove(remote_latests, free_gpointer_item, NULL);
        g_hash_table_destroy(remote_latests);
        
        return;
    }
    if (strncmp(line, "QS", 2)==0){ /* qso */
        if (!ctest) {
            dbg_sock("!!! QS recvd, ctest==NULL!! \n");
            return;
        }
        if (!conn->is_same_ctest) return;

        if (strlen(line)<4) return; 
        c = line+3;
        qso_from_net(conn, c);
        return;
    }

    if (strncmp(line, "SR", 2)==0){ /* start of replication */
        if (!conn->is_same_ctest) return;
        conn->replicating=1;
        return;
    }
    
    if (strncmp(line, "ER", 2)==0){ /* end of replication */
        if (!conn->is_same_ctest) return;
        conn->replicating=0;
        recalc_all_stats(ctest); /* include statsfifo */
        /*check_autosave();*/
        return;
    }
    
    if (strncmp(line, "TA", 2)==0){ /* talk */
        gchar *d;
        if (!conn->is_same_ctest) return;

        if (strlen(line)<4) return; 
        c = line+3;

        /*dbg_sock("TA conn=%p\n", conn);*/
        sw_talk_read(c);
        d=g_strconcat("TA ",c,"\n",NULL);
        rel_write_almost_all(conn, d);
        g_free(d);
        return;
    }
    
    if (strncmp(line, "SK", 2)==0){ /* sked */
        gchar *d;
        if (!conn->is_same_ctest) return;

        if (strlen(line)<4) return; 
        c = line+3;

        /*dbg("SK conn=%p\n", conn);*/
        sw_sked_read(c, 0);
        d=g_strconcat("SK ",c,"\n",NULL);
        rel_write_almost_all(conn, d);
        g_free(d);
        return;
    }

    if (strncmp(line, "DI", 2)==0){ /* peers discovery */
        int i;
        GString *gs;
        struct conn *tmpconn;

        /*dbg("DI recvd\n");*/
        gs =g_string_sized_new(100);
        g_string_append(gs,"PE ");
        
        g_string_sprintfa(gs, "%s;", net->myid);
        
        for (i=0;i<net->peers->len;i++){
            tmpconn = (struct conn *)g_ptr_array_index(net->peers,i);
        
            if (!conn_prod_state(tmpconn)) continue;
            if (!tmpconn->remote_id) return;
            g_string_sprintfa(gs, "%s;", tmpconn->remote_id);
                    
        }
        g_string_sprintfa(gs,"\n");
        dbg_sock("sending '%s'\n", gs->str);
        rel_write(conn, gs->str);
        g_string_free(gs,TRUE);
        return;
    }
    
    if (strncmp(line, "PE", 2)==0){ /* peers reply */
         /* TODO security - only if required */
            
        if (strlen(line)<4) return; 
        c = line+3;

        dbg_sock("PEers '%s'\n", c);
        CONDGFREE(net->allpeers);
        net->allpeers=g_strdup(c);

        if (!net->peerfunc) return;
        do_peer_menu((void (*)(union cba_t cba)) net->peerfunc);
        net->peerfunc=NULL;
        return;
    }

    if (strncmp(line, "CR", 2)==0){ /* configuration request */
        gchar *d,*cc;
        GString *gs;
        char *token_ptr;
        int tmpi;
            
        if (strlen(line)<4) return; 
        c = line+3;
        
        
        items=g_strsplit(c, ";", 0);
        if (net_route(line,items)) return;
        if (!items[0] || !items[1]) return; /* dst,src */
        
        gs=g_string_sized_new(10000);
        save_rc_string(gs);

        /* sets this socket temporarily to blocking mode */
        fcntl(conn->sock, F_SETFL, 0);
        tmpi=0;
        for (cc=strtok_r(gs->str,"\n",&token_ptr); cc!=NULL; cc=strtok_r(NULL,"\n",&token_ptr)){
/*            if (tmpi++==4) break; */ /* TODO FIXME */
            d=g_strdup_printf("CF %s;%s;%s\n",items[1],items[0],cc); 
            rel_write(conn,d);
            g_free(d);
        }

        d=g_strdup_printf("CF %s;%s;eof\n",items[1],items[0]); 
        rel_write(conn,d);
        
        fcntl(conn->sock, F_SETFL, O_NONBLOCK);
        
        g_free(d);
        g_string_free(gs,TRUE);
        g_strfreev(items);
        return;
    }   
    
    if (strncmp(line, "CF", 2)==0){ /* configuration line */
        gchar *d;
            
        if (strlen(line)<4) return; 
        c = line+3;
        
        
        items=g_strsplit(c, ";", 2);
        if (net_route(line,items)) goto x_cf;
        
        if (!items[0] || !items[1] || !items[2]) goto x_cf; /* dst,src */
        
        
        d = index(items[2],'#');
        if (d) *d='\0';
        g_strstrip(items[2]);
        if (strlen(items[2])==0) goto x_cf;
        
        if (strcmp(items[2],"eof")==0){
            log_addf(VTEXT(T_LOADED_CFG_SSSS),cfg->pcall, cfg->pwwlo, cfg->padr1,cfg->padr2);
            goto x_cf;
        }

        d = g_strconcat(items[2],"\n",NULL);
/*        dbg("doing something with '%s'\n",items[2]);*/
        read_rc_line(d);
        g_free(d);
x_cf:;        
        g_strfreev(items);
        return;
    }   
    
    if (strncmp(line, "WR", 2)==0){ /* C_W request */
        gchar *d,*cc;
        GString *gs;
        char *token_ptr;
        int tmpi;
            
        if (strlen(line)<4) return; 
        c = line+3;
        
        
        items=g_strsplit(c, ";", 0);
        if (net_route(line,items)) return;
        if (!items[0] || !items[1]) return; /* dst,src */
        
        gs=g_string_sized_new(10000);
        save_cw_string(cw,gs);

        /* sets this socket temporarily to blocking mode */
        fcntl(conn->sock, F_SETFL, 0);
        tmpi=0;
        for (cc=strtok_r(gs->str,"\n",&token_ptr); cc!=NULL; cc=strtok_r(NULL,"\n",&token_ptr)){
/*            if (tmpi++==4) break; */ /* TODO FIXME */
            d=g_strdup_printf("CW %s;%s;%s\n",items[1],items[0],cc); 
            rel_write(conn,d);
            g_free(d);
        }

        d=g_strdup_printf("CW %s;%s;eof\n",items[1],items[0]); 
        rel_write(conn,d);
        
        fcntl(conn->sock, F_SETFL, O_NONBLOCK);
        
        g_free(d);
        g_string_free(gs,TRUE);
        g_strfreev(items);
        return;
    }   
    
    if (strncmp(line, "CW", 2)==0){ /* C_W data */
            
        if (strlen(line)<4) return; 
        c = line+3;
        
        
        items=g_strsplit(c, ";", 2);
        if (net_route(line,items)) goto x_cw;
        
        if (!items[0] || !items[1] || !items[2]) goto x_cw; /* dst;src;call loc stamp */
        
        if (strcmp(items[2],"eof")==0){
            gchar *s;
            int ret;

            log_addf(VTEXT(T_LOADED_CW));
            
            s = g_strconcat(getenv("HOME"), "/tucnak/tucnakcw", NULL);
            ret=save_cw_into_file(cw, s);        
            if (ret){
                errbox(VTEXT(T_CANT_WRITE), ret);
            }else{
                log_addf(VTEXT(T_SAVED_S), s);
            }
            g_free(s);
            goto x_cw;
        }

/*        dbg("doing something with '%s'\n",items[2]);*/
        load_one_cw(cw,items[2]);
x_cw:;        
        g_strfreev(items);
        return;
    }   

    if (strncmp(line, "DX", 2)==0){ /* DX spot */
        gchar *d;

        if (strlen(line)<4) return; 
        c = line+3;

        /*dbg("DX conn=%p\n", conn);*/
        dxc_read_spot(c);
        d=g_strconcat("DX ",c,"\n",NULL);
        rel_write_almost_all(conn, d);
        g_free(d);
        redraw_later();
        return;
    }

    if (strncmp(line, "RG", 2)==0){ /* QRG for sked */
        gchar *d;
        struct band *b;

        if (!conn->is_same_ctest) return;
        
        if (strlen(line)<4) return; 
        c=line+3;

        items=g_strsplit(c, ";", 2);
        if (!items[0] || !items[1]) goto x_rg;
        
        b = find_band_by_pband(items[0]);
        if (!b) {
            dbg_qsos("!!! unknown band '%s'", items[0]);
            goto x_rg;
        }
        g_free(b->skedqrg);
        b->skedqrg = fixsemi(g_strdup(items[1]));
            b->dirty_save = 1;
        
        d=g_strconcat("RG ",c,"\n",NULL);
        rel_write_almost_all(conn, d);
        g_free(d);
        
x_rg:
        g_strfreev(items);
        return;
    }
    if (strncmp(line, "GR", 2)==0){ /* grab band command */
        gchar *d;
        struct band *b;
        enum ccmd ccmd;
        
        if (!conn->is_same_ctest) return;
        
        if (strlen(line)<4) return; 
        c=line+3;

        items=g_strsplit(c, ";", 3);
        if (!items[0] || !items[1] || !items[2]) goto x_gr;
        
        b = find_band_by_pband(items[0]);
        if (!b) {
            dbg_qsos("!!! unknown band '%s'", items[0]);
            goto x_gr;
        }
        ccmd=(enum ccmd)atoi(items[1]);
        
        net_grab(b,ccmd,items[2]);
        d=g_strconcat("GR ",c,"\n",NULL);
        rel_write_almost_all(conn, d);
        g_free(d);
        
x_gr:
        g_strfreev(items);
        return;
    }
    if (strncmp(line, "WC", 2)==0){ /* OBSOLETE working callsign */
        return;
    }
    if (strncmp(line, "WT", 2)==0){ /* working callsign */
        gchar *d;
        struct band *b;
        struct spypeer *sp;
        
        if (!conn->is_same_ctest) return;
        
        if (strlen(line)<4) return; 
        c=line+3;

        items=g_strsplit(c, ";", 4);
        if (!items[0] || !items[1] || !items[2] || !items[3]) goto x_wt;
        
        b = find_band_by_pband(items[0]);
        if (!b) {
            dbg_qsos("!!! unknown band '%s'", items[0]);
            goto x_wt;
        }

        if (atoi(items[2])==WT_OPERATOR){
            CONDGFREE(conn->operator);
            conn->operator=g_strdup(items[3]);
        }

        sp = get_spypeer_by_peerid(b->spypeers, items[1]);
        if (sp) {       /* we want spy info from items[1] */
          
            switch(atoi(items[2])){
                case WT_CLEAR:
                    clear_spypeer(sp);
                    break;
                case WT_CALLSIGN:
                    CONDGFREE(sp->callsign);
                    sp->callsign=g_strdup(items[3]);
                    break;
                case WT_RSTS:
                    CONDGFREE(sp->rsts);
                    sp->rsts=g_strdup(items[3]);
                    break;
                case WT_RSTR:
                    CONDGFREE(sp->rstr);
                    sp->rstr=g_strdup(items[3]);
                    break;
                case WT_QSONRS:
                    CONDGFREE(sp->qsonrs);
                    sp->qsonrs=g_strdup(items[3]);
                    break;
                case WT_QSONRR:
                    CONDGFREE(sp->qsonrr);
                    sp->qsonrr=g_strdup(items[3]);
                    break;
                case WT_EXC:
                    CONDGFREE(sp->exc);
                    sp->exc=g_strdup(items[3]);
                    break;
                case WT_LOCATOR:
                    CONDGFREE(sp->locator);
                    sp->locator=g_strdup(items[3]);
                    break;
                case WT_OPERATOR:
                    CONDGFREE(sp->operator);
                    sp->operator=g_strdup(items[3]);
                    break;
                case WT_REMARK:
                    CONDGFREE(sp->remark);
                    sp->remark=g_strdup(items[3]);
                    break;
            }
        }
        
        d=g_strconcat("WT ",c,"\n",NULL);
        rel_write_almost_all(conn, d);
        g_free(d);
        redraw_later();
        
x_wt:
        g_strfreev(items);
        return;
    }
    if (strncmp(line, "PT", 2)==0){ /* peer TX */
        gchar *d;
        struct band *b;
        struct spypeer *sp;

        if (!conn->is_same_ctest) return;
        
        if (strlen(line)<4) return; 
        c=line+3;

        items=g_strsplit(c, ";", 3);
        if (!items[0] || !items[1] || !items[2]) goto x_pt;
        
        b = find_band_by_pband(items[0]);
        if (!b) {
            dbg_qsos("!!! unknown band '%s'", items[0]);
            goto x_pt;
        }
        sp = get_spypeer_by_peerid(b->spypeers, items[1]);
        if (sp) {      /* we want spy info from items[1] */
            sp->peertx = atoi(items[2]);
        }
        
        d=g_strconcat("PT ",c,"\n",NULL);
        rel_write_almost_all(conn, d);
        g_free(d);
        redraw_later();
        
x_pt:
        g_strfreev(items);
        return;
    }
    if (strncmp(line, "WI", 2)==0){ /* working inputline */
        gchar *d;
        struct band *b;
        struct spypeer *sp;

        if (!conn->is_same_ctest) return;
        
        if (strlen(line)<4) return; 
        c=line+3;

        items=g_strsplit(c, ";", 3);
        if (!items[0] || !items[1] || !items[2]) goto x_wi;
        
        b = find_band_by_pband(items[0]);
        if (!b) {
            dbg_qsos("!!! unknown band '%s'", items[0]);
            goto x_wi;
        }
        sp = get_spypeer_by_peerid(b->spypeers, items[1]);
        if (sp) {       /* we want spy info from items[1] */
            CONDGFREE(sp->inputline);
            sp->inputline = g_strdup(items[2]);
        }
        
        d=g_strconcat("WI ",c,"\n",NULL);
        rel_write_almost_all(conn, d);
        g_free(d);
        redraw_later();
        
x_wi:
        g_strfreev(items);
        return;
    }
    if (strncmp(line, "DO", 2)==0){ /* peers discovery with operators*/
        int i;
        GString *gs;
        struct conn *tmpconn;
        char *op;

        /*dbg("DI recvd\n");*/
        gs =g_string_sized_new(100);
        g_string_append(gs,"PO ");
        
        /* LOOK ALSO menu.c menu_load_from_peer */
        
        op="---";
        if (ctest && aband && conn->operator) op=aband->operator;
        g_string_sprintfa(gs, "%s;%s;", net->myid,op);
        
        for (i=0;i<net->peers->len;i++){
            tmpconn = (struct conn *)g_ptr_array_index(net->peers,i);
        
            if (!conn_prod_state(tmpconn)) continue;
            if (!tmpconn->remote_id) return;
            op="---";
            if (ctest && aband && tmpconn->operator) op=tmpconn->operator;
            g_string_sprintfa(gs, "%s;%s;", tmpconn->remote_id, op);
                    
        }
        g_string_sprintfa(gs,"\n");
        dbg_sock("sending '%s'\n", gs->str);
        rel_write(conn, gs->str);
        g_string_free(gs,TRUE);
        return;
    }
    
    if (strncmp(line, "PO", 2)==0){ /* peers reply with operators*/
         /* TODO security - only if required */
            
        if (strlen(line)<4) return; 
        c = line+3;

        dbg_sock("PEers&OPs '%s'\n", c);
        CONDGFREE(net->allpeers);
        net->allpeers=g_strdup(c);

        if (!net->peerfunc) return;
        do_peer_operators_menu((void (*)(union cba_t cba)) net->peerfunc);
        net->peerfunc=NULL;
        return;
    }
    
    if (strncmp(line, "RT", 2)==0){ /* request all tmpqso states*/
        gchar *d;
        struct band *b;

        if (!conn->is_same_ctest) return;
        
        if (strlen(line)<4) return; 
        c=line+3;

        items=g_strsplit(c, ";", 2);
        if (!items[0] || !items[1]) goto x_rt;
        
        b = find_band_by_pband(items[0]);
        if (!b) {
            dbg_qsos("!!! unknown band '%s'", items[0]);
            goto x_rt;
        }
        if (strcmp(items[1], net->myid)==0) {
            wkd_tmpqso(b, WT_CALLSIGN, b->tmpqsos[0].callsign);
            wkd_tmpqso(b, WT_RSTS, b->tmpqsos[0].rsts);
            wkd_tmpqso(b, WT_RSTR, b->tmpqsos[0].rstr);
            wkd_tmpqso(b, WT_QSONRS, b->tmpqsos[0].qsonrs);
            wkd_tmpqso(b, WT_QSONRR, b->tmpqsos[0].qsonrr);
            wkd_tmpqso(b, WT_EXC, b->tmpqsos[0].exc);
            wkd_tmpqso(b, WT_LOCATOR, b->tmpqsos[0].locator);
            wkd_tmpqso(b, WT_REMARK, b->tmpqsos[0].remark);
            wkd_tmpqso(b, WT_OPERATOR, b->operator);
            peer_tx(b, gses->last_cq_timer_id>0?2:gses->tx);
            send_inputline(b);
            goto x_rt;  /* noone has same myid, don't need to route */
        }
        
        d=g_strconcat("RT ",c,"\n",NULL);
        rel_write_almost_all(conn, d);
        g_free(d);
x_rt:
        g_strfreev(items);
        return;
    }
    
    if (strncmp(line, "OP", 2)==0){ /* operator */
        gchar *d;

        if (strlen(line)<4) return; 
        c=line+3;

        CONDGFREE(conn->operator);
        conn->operator=g_strdup(c);
        
        d=g_strconcat("OP ",c,"\n",NULL);
        rel_write_almost_all(conn, d);
        g_free(d);

    }

}


gchar *qso_to_string(struct qso *q){
    gchar *c;

    c = g_strdup_printf("QS %s;%s;%s;%s;"
                           "%d;%s;%s;%s;" 
                           "%s;%s;%s;%s;"
                           "%s;%d;%s;%s;"
                           "%d;%s\n",
            q->band->pband, q->date_str, q->time_str, q->error?"ERROR":q->callsign,
            q->mode,        C0(q->rsts), C0(q->qsonrs),   C0(q->rstr), 
            C0(q->qsonrr),  C0(q->exc),  C0(q->locator),  q->source,
            q->operator,    (int)q->stamp,    q->callsign, "",
            q->ser_id, fixsemi(C0(q->remark)));
    return c;
}

void compare_remote_with_me(gpointer key, gpointer value, gpointer data){
    gchar *source, *rem_latest_str;
    GIndexArray *ia;
    GHashTable *remote_latests;
    struct qso dummyq, *pdq=&dummyq, *q;
    int i;
    gchar *c;
    gint first_qso;
   
    source = (gchar*) key;
    ia = (GIndexArray *) value;
    remote_latests = (GHashTable *)data;
        
    /*dbg("nsource=%s, ia->len=%d\n", source, ia->len);*/

    rem_latest_str = (gchar *) g_hash_table_lookup(remote_latests, source);
    
    if (rem_latest_str){
       /* dbg("remote has some qso from this source\n");*/
        memset(&dummyq, 0, sizeof(dummyq));
        dummyq.stamp = atoi(rem_latest_str);
        first_qso = g_index_array_bsearch_index(ia, &pdq, compare_stamp);  
        
        if (first_qso<0) { /* remote's last qso not found in my qsos */
            /*dbg("remote's last qso not found between my qsos");*/
            q = (struct qso *) g_index_array_index(ia, ia->len-1); /* my last qso (ia->len!=0) */
            if (compare_stamp(&q, &pdq)<0){
                dbg_qsos("  %s: remote has newer qso than me",source);
            }else{
                dbg_qsos("  %s: ooops remote's last not found but is older!",source);
                first_qso = 0; /* Ooops! remote's last qso is missing */
            }
        }
        
    /*    dbg("nsource=%s, first_qso=%d ia->len=%d\n", source, first_qso, ia->len);*/

        /* there can be some qsos with the same stamp before */
        /* these must be replicated too */
        /*dbg("first_qso(1)=%d\n", first_qso);*/
        while(first_qso>0){
            q = (struct qso *) g_index_array_index(ia, first_qso-1);
            if (compare_stamp(&q, &pdq)!=0) break;
            first_qso--;
        }
    }else{ /* remote has no qso from this source */
        dbg_qsos("  %s: remote has no qso\n", source);
        first_qso = 0;
    }
            
    dbg("replicating sock=%d source=%22s ser_id %d...%d \n", sconn->sock, source, first_qso, ia->len-1);
   
    for (i=first_qso; i<ia->len; i++){
        q = (struct qso *) g_index_array_index(ia, i);
        if (!q) continue;
        c = qso_to_string(q);
       /* dbg("replicating %s %s %d %s/%d\n",q->callsign, q->qsonrs, q->stamp, source,q->ser_id);*/
        rel_write(sconn, c);

        if (strlen(c)>0 && c[strlen(c)-1]=='\n') c[strlen(c)]='\0';
        dump_qso(q,"replicating2\n");
        
        g_free(c);
    }
    dbg_qsos("----end of replications\n");
    
    
}

void qso_from_net(struct conn *conn, gchar *str){
    int i;
    gchar **items;
    struct band *b;
    struct qso *lastqso, *eq, *q;
    gint last_qsonr, mode, qsonr, stamp, ser_id;
    gchar *date_str, *time_str, *callsign_or_error, *rsts, *qsonrs;
    gchar *rstr, *qsonrr, *exc, *locator, *source, *operator, *callsign, *remark;
    int found,sortit;
    int same_km=0;

    
    items=g_strsplit(str, ";", 0);
    for (i=0;i<=16;i++) if (items[i]==NULL) {g_strfreev(items);return;}

    date_str=items[1]; time_str=items[2];  callsign_or_error=items[3]; 
    mode=atoi(items[4]); rsts=items[5];     qsonrs=items[6];    rstr=items[7];
    qsonrr=items[8];     exc=items[9];      locator=items[10];  source=items[11];
    operator=items[12];  stamp=atoi(items[13]); callsign=items[14];
    ser_id=atoi(items[16]);

    if (!items[17]){
        remark="";
    }else{
        remark=items[17];
    }

   /* dbg("qso_from_net '%s'\n", str);*/
    /*dbg("qso_from_net sock=%d %d %s %-10s %s | ", */
            /*conn->sock, stamp, qsonrs, callsign, source);*/

    dbg_qsos("\n----qso_from_net: '%s'", str);

    b = find_band_by_pband(items[0]);
    if (!b) {
        dbg_qsos("!!! unknown band '%s'", items[0]);
        goto x;
    }
    
    dbg_qsos("from %s '%s'\n",conn->remote_id, str);

    qsonr = atoi(qsonrs);
    q = get_qso_by_id(b, source, ser_id);
            if (q) { dbg(" ID ");dbg_qsos("  found by ID (%s:%d)", source, ser_id);dump_qso(q,"id");}
    if (!q && ctest->qsoused){
        q = get_qso_by_qsonr(b, qsonr);
        if (q) { dbg_qsos("  found by QSO NR");dump_qso(q,"nr");}
    }
    found=1;
    if (!q) {
        found=0;
        dbg_qsos("  QSO not found, creating new");
        if (ctest->qsoused){  /* fill with errors */
            lastqso = get_qso(b, b->qsos->len-1); /* -1 is correct */
            if (lastqso){
                last_qsonr = atoi(lastqso->qsonrs);
                dump_qso(lastqso,"lastqso");
            }else
                last_qsonr = 0;
            if (last_qsonr+1 != qsonr) dbg_qsos("  fill with ERRORS QSONRs <%d..%d)\n", last_qsonr+1, qsonr);  
            for (i=last_qsonr+1; i<qsonr; i++){
                dbg("!!! missing qso nr %d, adding error\n", i);
                eq = g_new0(struct qso, 1);
                eq->source   = g_strdup("neterr"); /* ignored by add_qso_to_index */
                eq->operator = g_strdup("net");
                eq->date_str = g_strdup("");    
                eq->time_str = g_strdup("");    
                eq->callsign = g_strdup("ERROR");    
                eq->rsts     = g_strdup("");        
                eq->rstr     = g_strdup("");    
                eq->qsonrs   = g_strdup_printf("%03d", i);    
                eq->qsonrr   = g_strdup("");    
                eq->exc      = g_strdup("");    
                eq->locator  = g_strdup("");    
                eq->remark   = g_strdup("");    
                eq->error    = 1;
                add_qso(b, eq);
                update_stats(b, b->stats, eq);
                        minute_stats(b);
                dump_qso(eq, "error");
            }
            if (!some_replicating(net)) check_autosave();
        }
        q = g_new0(struct qso, 1);
      /*  dbg("N ");*/
    }else{
        dbg_qsos("  updating existing qso");
        dump_qso(q,"updating");
    }
   /* dbg("F%d S%d ", found, stamp-q->stamp);*/
    
    if (stamp < q->stamp) {
     /*   dbg("stamp < q->stamp\n");*/
        goto add;
    }
    sortit=stamp!=q->stamp;
    
    q->band = b;
    STORE_STR   (q,date_str); 
    STORE_STR   (q,time_str); 
    
    if (q->locator){
        same_km=!strcasecmp(q->locator, locator);
    }else{
        same_km=2; /*new qso or contest without locator*/
    }

    if (strcasecmp(callsign_or_error, "ERROR")==0){
        if (!q->error) same_km=0;
        q->error = 1;
    }else{
        if (q->error) same_km=0;
        q->error=0;
    }
    
    
    STORE_INT   (q,mode);
    STORE_STR   (q,rsts); 
    STORE_STR   (q,qsonrs); 
    STORE_STR   (q,rstr); 
    STORE_STR   (q,qsonrr); 
    STORE_STR   (q,exc); 
    STORE_STR_UC(q,locator); 
    STORE_STR   (q,remark); 
    fixsemi(q->remark);
    STORE_INT   (q,ser_id);
    compute_qrbqtf(q);

    if (q && q->source && strcmp(q->source,"neterr")==0){
        /* spojeni bylo predtim sitova chyba, musime prepsat zdroj */
        STORE_STR(q,source); 
        add_qso_to_index(q,0);
        if (!q->error) invalidate_tmpqso(b, q);
        sortit=1;
    }else{
        /* updatuje se existujici qso */
        dbg("--------- found=%d\n", found);
   /*     if (!found)  STORE_STR(q,source); */

        if (q && q->source && strcmp(source, q->source)!=0){ 
            dbg("--------- bude se prepisovat source %s -> %s\n", q->source, source);
            dump_all_sources(ctest);
            dbg("---before remove_qso_from_index\n");
            dump_all_sources(ctest);
            remove_qso_from_index(q);
            dbg("---after remove_qso_from_index\n");
            dump_all_sources(ctest);
            
            STORE_STR(q, source);
            dbg("---before add_qso_to_index\n");
            dump_all_sources(ctest);
            add_qso_to_index(q, 1);
            dbg("---after add--\n");
            dump_all_sources(ctest);
        }else{
            STORE_STR(q, source);
        }
        
        
    }
    
    STORE_STR_UC(q,operator); 
    STORE_INT   (q,stamp);
    STORE_STR_UC(q,callsign); 
    /* items[15] unused (century) */
    
    if (sortit) g_hash_table_foreach(ctest->bystamp, foreach_source_qsort_by_stamp, NULL);
/*update_stats(b->stats, b, q);*/

    write_qso_to_swap(b, q); 

    if (!ctest->redraw_timer_id)
        ctest->redraw_timer_id = install_timer(DELAY_AFTER_REPLICATION, timer_redraw, CBA0);
    
add:;    
    dirty_band(b); 
    if (!found){
/*      dbg("adding\n");*/
        add_qso(b, q);
        if (!q->error) invalidate_tmpqso(b, q);
        update_stats(b, b->stats, q);
            minute_stats(b);
#ifdef HAVE_SDL
          if (b==aband) map_add_qso(q);   /*plot new QSO only if it is on active band*/
#endif        
    }else{
#ifdef HAVE_SDL
            if (!same_km) map_clear_qso(q);
#endif            
/*      dbg("dont adding\n");*/
    }
    replicate_qso(conn, q);
    if (!some_replicating(net)) check_autosave();
    dump_qso(q,"after");
x:; /* only unknown band */
    g_strfreev(items);
    dbg_qsos("\n");
}

/*****************************************************************/
void net_send_id(){
    gchar *c;

    c = g_strdup_printf("ID %s:%d\n", 
            inet_ntoa(net->my.sin_addr), 
            ntohs(net->my.sin_port)); 
    rel_write_all(c);
    g_free(c);
}    

void net_send_ac(){
    gchar *c;
    int i,j;
    struct conn *conn;
    char qrvbands[50];

    if (!ctest) return;

    for (i=j=0;i<ctest->bands->len;i++){
        struct band *b;
        b=(struct band *)g_ptr_array_index(ctest->bands, i);
        qrvbands[j++]=b->bandchar;
    }
    qrvbands[j]='\0';
    
    
    c = g_strdup_printf("AC %s;%s;%s;%s\n", 
            ctest->cdate?ctest->cdate:"", 
            ctest->pcall?ctest->pcall:"", 
            ctest->tname?ctest->tname:"",
            qrvbands);
    rel_write_all(c);
    g_free(c);

    if (net->master){
        net_test_same_contest(net->master, net->master->remote_ac);
        return;
    }
    
    for (i=0; i<net->peers->len; i++){
        conn = (struct conn *) g_ptr_array_index(net->peers, i);    
        net_test_same_contest(conn, conn->remote_ac);
    }
}
 
void net_send_operator(void){
    int i;

    if (!ctest) {
        if (cfg->operator && *cfg->operator) {
            gchar *c;

            c=g_strdup_printf("OP %s\n", cfg->operator);
            rel_write_all(c);
            g_free(c);
        }
        return;
    }
    for (i=0;i<ctest->bands->len;i++){
        struct band *b;
        b=(struct band *)g_ptr_array_index(ctest->bands, i);
        wkd_tmpqso(b, WT_OPERATOR, b->operator);
    }
}    

void net_test_same_contest(struct conn *conn, gchar *ac_text){
    gchar **items;
    gchar *cdate, *pcall, *tname, *qrvb;
    int i,j;
    char qrvbands[50];

/*    log_addf("%d test_same", conn->sock);*/
    if (!ctest || !ac_text) return;
    if (!conn_prod_state(conn)) return;
    

    for (i=j=0;i<ctest->bands->len;i++){
        struct band *b;
        b=(struct band *)g_ptr_array_index(ctest->bands, i);
        qrvbands[j++]=b->bandchar;
    }
    qrvbands[j]='\0';

    items=g_strsplit(ac_text, ";", 0);
    for (i=0;i<4;i++) {
        if (items[i]==NULL) {
            log_adds("Contest is not the same: FORMAT of AC frame");
            g_strfreev(items);        
            return;
        }
    }
    cdate = items[0];
    pcall = items[1];
    tname = items[2];
    qrvb  = items[3];

    if (strncmp(cdate, ctest->cdate, 8)==0 &&
        strcasecmp(pcall, ctest->pcall)==0 &&
        strcasecmp(tname, ctest->tname)==0 &&
        strcasecmp(qrvb, qrvbands)==0){

        gchar *latests_str;
        
        /*dbg(" To je on!! Meho srdce sampion!! sock=%d\n", conn->sock);*/
        
        latests_str = get_latests_str();
        rel_write(conn, latests_str);  
        g_free(latests_str);
        log_addf("Contest is the same");
        conn->is_same_ctest = 1;
    }else{    
        GString *gs;
        gs=g_string_new("Contest is not the same: ");
        if (strncmp(cdate, ctest->cdate, 8)) g_string_sprintfa(gs, "DATE(%s,%s) ", cdate, ctest->cdate);
        if (strcasecmp(pcall, ctest->pcall)) g_string_sprintfa(gs, "CALL(%s,%s) ", pcall, ctest->pcall);
        if (strcasecmp(tname, ctest->tname)) g_string_sprintfa(gs, "CONTEST(%s,%s) ", tname, ctest->tname);
        if (strcasecmp(qrvb, qrvbands))      g_string_sprintfa(gs, "BANDS(%s,%s) ", qrvb, qrvbands);
        log_addf(gs->str);
        g_string_free(gs, 1);
        conn->is_same_ctest = 0;
    }
    g_strfreev(items);        
    
}


void timer_redraw(union cba_t cba){
    struct band *b;
    int i;
    
    if (ctest){
        ST_START;
        for (i=0; i<ctest->bands->len; i++){
            b = g_ptr_array_index(ctest->bands, i);
            recalc_stats(b);
            
            if (ctest->qsoused && b->qsos->len+1 != atoi(b->tmpqsos[0].qsonrs)){
                g_free(b->tmpqsos[0].qsonrs);
                b->tmpqsos[0].qsonrs = g_strdup_printf("%03d", b->qsos->len+1);
                wkd_tmpqso(b, WT_QSONRS, b->tmpqsos[0].qsonrs);
            }
        }
        ST_STOP;
    }
    
    redraw_later();
    ctest->redraw_timer_id = 0;
    
    
}

void replicate_qso(struct conn *sourceconn, struct qso *q){
    gchar *c;
    int i;
    struct conn *conn;
    
    c = qso_to_string(q);
    dbg_qsos("replicating1: %s(%s:%d) band=%c", q->callsign, q->source, q->ser_id, q->band->bandchar);
        dump_qso(q,"replicating1");

    if (net->master && 
        conn_prod_state(net->master) &&
        (sourceconn==NULL || sourceconn->sock!=net->master->sock) ){

        /*dbg("replicating mast=%d %d %s %s\n", net->master->sock, q->stamp, q->qsonrs, q->callsign);*/
        rel_write(net->master, c);
    } 

    for (i=0; i<net->peers->len; i++){
        conn = g_ptr_array_index(net->peers, i);

        if (conn_prod_state(conn) && 
            (sourceconn==NULL || sourceconn->sock!=conn->sock)) {
                                  
           /* dbg("replicating sock=%d %d %s %s\n", conn->sock, q->stamp, q->qsonrs, q->callsign);*/
            rel_write(conn, c);
        }
    }
    
    g_free(c);
    
}


gchar *get_timer_str(struct conn *conn){
    static char s[100];
    ttime interval;
    
    strcpy(s,"");
    if (!conn) return s;

    interval=get_timer_time(conn->timer);
    interval/=1000;
    
    sprintf(s, "%4d", (int)interval);

    return s;
}

void send_config_request(int no){
    char *peerid,*c;
    struct conn *gw;
    gchar **items;
    int i;
    
    /*ndbg("send_config_request '%d'\n",no);*/

    items = g_strsplit(net->allpeers,";",0);

    for (i=0; items[i]!=NULL;i++){
        if (i==no) goto found;
    }
    g_strfreev(items);
    return;

found:    
    peerid=items[i];

    if (cmp_sin(&net->global, &net->my)==0) { /* i'm master */
        gw=find_conn_by_remote_id(peerid);
        if (!gw) {
            log_addf(VTEXT(T_PEER_NOT_FOUND), peerid);
            goto x;
        }
    }else{
        gw=net->master;
    }
    if (!conn_prod_state(gw)) goto x;
    /* gw - connection to requested peer/gateway to peer */

    c=g_strdup_printf("CR %s;%s\n",peerid,net->myid);
    rel_write(gw,c);
    g_free(c);
    
x:;                                 
    
    g_strfreev(items);
}

void send_cwdb_request(int no){
    char *peerid,*c;
    struct conn *gw;
    gchar **items;
    int i;
    
    /*dbg("send_cwdb_request '%d'\n",no);*/

    items = g_strsplit(net->allpeers,";",0);

    for (i=0; items[i]!=NULL;i++){
        if (i==no) goto found;
    }
    g_strfreev(items);
    return;

found:    
    peerid=items[i];

    if (cmp_sin(&net->global, &net->my)==0) { /* i'm master */
        gw=find_conn_by_remote_id(peerid);
        if (!gw) {
            log_addf(VTEXT(T_PEER_NOT_FOUND), peerid);
            goto x;
        }
    }else{
        gw=net->master;
    }
    if (!conn_prod_state(gw)) goto x;
    /* gw - connection to requested peer/gateway to peer */

    c=g_strdup_printf("WR %s;%s\n",peerid,net->myid);
    rel_write(gw,c);
    g_free(c);
    
x:;                                 
    
    g_strfreev(items);
}

int some_replicating(struct net *net){
    int i;
    struct conn *conn;
    
    if (net->master && 
        conn_prod_state(net->master) && 
        net->master->replicating) return 1;

    for (i=0;i<net->peers->len;i++){
        conn = (struct conn *) g_index_array_index(net->peers, i);
        if (!conn_prod_state(conn)) continue;
        if (conn->replicating) return 1;
    }
    return 0;
}


Generated by  Doxygen 1.6.0   Back to index