/***************************************************************************//**
  @file ltrsrv_client.c
  @author Borisov Alexey <borisov@lcard.ru>
  @date   11.02.2012

  Файл содержит логику работы с клиентами LTR-сервера. Сервер слушает порт
  #LTRSRV_TCP_CLIENT_PORT на предмет подключений от клиентов. При первом подключении
  выполняется обработка команды INIT для определения, к какому крейту/модулю (или
  управляющему каналу) выполняеся подключение.
  Затем выполняется обработка команд или потока данных, в зависимости от типа
  подключения. Обработка команд вынесена в ltrsrv_client_cmd.c.


  Управление каналом данных:
  При появлении данных от клиента, вызывается функция ltrsrv_notify_client_data_rdy(),
  чтобы оповестить крейт о наличии данных. После чего модуль управления крейтом,
  если у него есть возможность, читает данные c помощью ltrsrv_client_get_words()
  или запрещает прием ltrsrv_client_recv_stop() (с последующим
  ltrsrv_client_recv_resume() при появлении места).
  При этом ltrsrv_client_get_words() вызывается только когда есть данные для чтения
  и если recv возвращае 0, считается что сокет закрыт.

  Передачей данных клиенту управляет полностью модуль управления крейтом. Для
  передачи он вызывает ltrsrv_client_put_words(), если все данные не передались,
  то он вызывает ltrsrv_client_wait_snd_rdy() с указанием callback-функции, которая
  вызовется, когда сокет станет доступен для записи
  *****************************************************************************/

#include "ltrsrv_client_p.h"
#include "ltrsrv_defs.h"
#include "ltrsrv_wait.h"
#include "ltrsrv_crates.h"
#include "ltrsrv_cfg.h"
#include "ltrsrv_log.h"
#include "ltrsrv_eth.h"
#ifdef LTRD_LOGPROTO_ENABLED
    #include "ltrsrv_log_proto.h"
#endif

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#define LTRSRV_TCP_CLIENT_PORT    11111

/* признак хендла клиента - чтобы быть уверенным при разиминовании void* */
#define LTRSRV_CLIENT_HANDLE_SIGN  0xCC00A5FA

#define _CLIENT_CHECK_HND(hnd) (hnd) == NULL ? LTRSRV_ERR_CLIENT_HANDLE : \
    (hnd)->sign != LTRSRV_CLIENT_HANDLE_SIGN ? LTRSRV_ERR_CLIENT_HANDLE : 0

#define _CLIENT_CHECK_HND_DATA_STREAM(hnd) (hnd) == NULL ? LTRSRV_ERR_CLIENT_HANDLE : \
    (hnd)->sign != LTRSRV_CLIENT_HANDLE_SIGN ? LTRSRV_ERR_CLIENT_HANDLE : \
    (hnd)->state != LTRSRV_CLIENT_STATE_DATA_STREAM ? LTRSRV_ERR_CLIENT_HANDLE : 0






#define LTR_CLIENT_CMD_START                       0xFFFFFFFF





#define LTRSRV_CLIENT_DEFAULT_CMD_BUF_SIZE         512


#define LTR_CLIENT_SRVCTL_STRING "server control"


static t_socket f_listen_sock = L_INVALID_SOCKET;
static t_ltr_client* f_clients[LTRSRV_CLIENT_COUNT_MAX];
static int f_client_cnt = 0;


static void f_client_cb(t_socket sock, int event, void* data);
extern const char* ltrsrv_get_client_cmd_str(int cmd);


/* Закрытие клиента. Закрывается сокет клиента, клиент удаляется из списков
   клинтов, прикрепленных к модулям (если был), у
   даляется вся испльзуемая клиентом память. Описатель становится не действительным */
void ltrsrv_client_close(struct st_ltr_client *client) {
    int i, fnd;

    ltrsrv_wait_remove_tmr(client);

    if (client->close_req && (client->close_req_err!=0) && (client->close_req_err!=LTRSRV_ERR_SOCKET_CLOSED)) {
        ltrsrv_log_rec(LTRSRV_LOGLVL_WARN, client->log_str, client->close_req_err, "Close client by error");
    } else {
        ltrsrv_log_rec(LTRSRV_LOGLVL_DBG_HIGH, client->log_str, 0, "Close client connection");
    }

    client->sign = 0;
    if (client->sock != L_INVALID_SOCKET) {
        ltrsrv_wait_remove_sock(client->sock);
        //shutdown(client->sock, SHUT_RDWR);
        closesocket(client->sock);
        client->sock = L_INVALID_SOCKET;
    }

    /* если это соединение с модулем - то удаляем регистрацию клиента */
    if (client->state == LTRSRV_CLIENT_STATE_DATA_STREAM) {
        ltrsrv_crate_unregister_client(client->crate, client->slot, client);
    }

    if (client->wt_cb) {
        /* если остался незавершенный запрос, то оставляем клиента
         * до завершения этого запроса */
        client->state = LTRSRV_CLIENT_STATE_CLOSED;
    } else {
        /* удаляем клиента из списка клиентов */
        for (i=f_client_cnt-1, fnd=0; (i>=0) && !fnd; i--) {
            if (client==f_clients[i]) {
                if (i!=(f_client_cnt-1)) {
                    memmove(&f_clients[i], &f_clients[i+1],
                            (f_client_cnt-i-1)*sizeof(f_clients[0]));
                }
                fnd = 1;
            }
        }

        f_client_cnt--;

        free(client->log_str); client->log_str = NULL;
        free(client->cmd); client->cmd = NULL;

        free(client);
    }
}

static void f_client_close_cb(t_socket sock, int event, void *data) {
    t_ltr_client *client = (t_ltr_client *)data;
    int err = _CLIENT_CHECK_HND(client);
    if (!err) {
        ltrsrv_client_close(client);
    }
}

void ltrsrv_client_close_req(t_ltr_client *client, int err) {
    if (!client->close_req) {
        client->close_req = 1;
        client->close_req_err = err;
        ltrsrv_wait_add_tmr(client, 0, 0, f_client_close_cb, client);
    }
}

/* Создание строки для вывода сообщений о клиенте.
    Строка сохраняется в client->log_str */
int p_client_create_logstr(t_ltr_client *client) {
    int err = 0;
    if (client->log_str) {
        free(client->log_str);
        client->log_str = 0;
    }

    switch (client->state) {
        case LTRSRV_CLIENT_STATE_INIT:
            client->log_str = (char*)malloc(sizeof(LTRSRV_LOGSRC_CLIENT_INIT) +
                                     strlen(client->location) + 7);
            if (client->log_str) {
                sprintf(client->log_str, "%s [%s]", LTRSRV_LOGSRC_CLIENT_INIT,
                    client->location);
            }
            break;
#ifdef LTRD_LOGPROTO_ENABLED
        case LTRSRV_CLIENT_STATE_LOGPROTO:
            client->log_str = (char*)malloc(sizeof(LTRSRV_LOGSRC_CLIENT_LOGPROTO) +
                                     strlen(client->location) + 7);
            if (client->log_str) {
                sprintf(client->log_str, "%s [%s]", LTRSRV_LOGSRC_CLIENT_LOGPROTO,
                    client->location);
            }
            break;
#endif
        case LTRSRV_CLIENT_STATE_CONTROL:
        case LTRSRV_CLIENT_STATE_DATA_STREAM:
            if (client->crate) {
                char* crate_str = NULL;
                char slot_str[10];

                crate_str = (char*)malloc(strlen(client->crate->descr.devname) +
                                   strlen(client->crate->descr.serial) +
                                   +5);
                if (crate_str) {
                    int strsize = 0;
                    sprintf(crate_str, "%s[%s]", client->crate->descr.devname,
                        client->crate->descr.serial);
                    if (client->state == LTRSRV_CLIENT_STATE_CONTROL)
                        strcpy(slot_str, "control");
                    else
                        sprintf(slot_str, "slot %2d", client->slot+1);

                    strsize = sizeof(LTRSRV_LOGSRC_CLIENT) +
                              strlen(client->location) + strlen(slot_str) +
                              (crate_str ? strlen(crate_str) : 0) + 7;
                    client->log_str = (char*)malloc(strsize);
                    if (client->log_str) {
                        sprintf(client->log_str, "%s[%s]->%s, %s", LTRSRV_LOGSRC_CLIENT,
                                client->location, crate_str, slot_str);
                    }
                    else err = LTRSRV_ERR_MEMALLOC;

                    free(crate_str);
                } else {
                    err = LTRSRV_ERR_MEMALLOC;
                }
            } else {
                client->log_str = (char*)malloc(sizeof(LTRSRV_LOGSRC_CLIENT) +
                                     strlen(client->location) + 5 +
                                         sizeof(LTR_CLIENT_SRVCTL_STRING));
                if (client->log_str) {
                    sprintf(client->log_str, "%s[%s]->%s", LTRSRV_LOGSRC_CLIENT,
                            client->location, LTR_CLIENT_SRVCTL_STRING);
                } else {
                    err = LTRSRV_ERR_MEMALLOC;
                }
            }
            break;
        default:
            break;
    }
    return err;
}

/* Получение количества управляющих клиентов, подключенных к крейту */
int p_client_get_ctl_cnt_from_crate(t_ltr_crate* crate) {
    int cnt = 0,i;
    for (i=0; i < f_client_cnt; i++) {
        if ((f_clients[i]->crate == crate) &&
                (f_clients[i]->state == LTRSRV_CLIENT_STATE_CONTROL)) {
            cnt++;
        }
    }
    return cnt;
}

/* Передача ответа - ответ записывается в сокет, если не смогли записать весь -
   встаем на ожидание, пока сокет не станет доступным для записи */
int p_client_cmd_proc_send(t_ltr_client* client) {
    int err=0, send_size, sended;

    send_size = client->cmd_size - client->cmd_pos;
    /* появилось место в сокете для передачи данных => пробуем передать остаток буфера */
    sended = send(client->sock, &((uint8_t*)client->cmd)[client->cmd_pos],
                               send_size, 0);

    if (sended == send_size) {
        /* если все послали - команда завершена - переходим к приему новой команды */
        client->cmd_pos  = 0;
        client->cmd_state = CMD_STATE_IDLE;
        client->wt_flags &= ~LTRSRV_WAITFLG_SEND;
        /* обработка по завершению передачи команды */
        p_client_cmd_post_process(client);
        err = ltrsrv_wait_add_sock(client->sock, client->wt_flags, 0, f_client_cb, client);
    } else if ((sended >= 0) || ((sended == L_SOCKET_ERROR) &&
                               L_SOCK_LAST_ERR_BLOCK())) {
        /* если передали часть данных (или ничего не передали, так как нет места),
         * то обновляем указатель переданных данных и встаем на ожидание сокета */
        client->cmd_state = CMD_STATE_SEND_RESP;
        client->cmd_pos += sended > 0 ? sended : 0;
        client->wt_flags |= LTRSRV_WAITFLG_SEND;
        err = ltrsrv_wait_add_sock(client->sock, client->wt_flags, 0, f_client_cb, client);
    } else {
        err = LTRSRV_ERR_SOCKET_SEND;
        ltrsrv_client_close_req(client, err);
    }
    return err;
}

/* изменение размера буфера под команду для клиента (если выделенный до
 * этого буфер не достаточен) */
int p_client_cmd_realloc_buf(t_ltr_client * client, unsigned int new_size) {
    int err = 0;
    if (new_size > client->cmd_max_size) {
        void* old_cmd = client->cmd;
        /* выделяем новый буфер хотя бы в 2 раза больше старого, чтобы много раз
           не выделять по малу */
        new_size = MAX(new_size, client->cmd_max_size*2);
        client->cmd = (t_ltr_client_cmd *)malloc(new_size);
        if (client->cmd != NULL) {
            /* если выделили, копируем содержимое старого буфера и удаляем его */
            memcpy(client->cmd, old_cmd, client->cmd_max_size);
            free(old_cmd);
            client->cmd_max_size = new_size;
        } else {
            /* если неудача - устанавливаем старый буфер и возвращаем ошибку */
            client->cmd = (t_ltr_client_cmd *)old_cmd;
            err = LTRSRV_ERR_MEMALLOC;
        }
    }
    return err;
}


/* Обработка приема данных соответтсвующих команде от клиента.
 * Должна вызываться при появлении данных от клиента, если он находится в
 * состоянии обработки команд */
static int f_cmd_proc_recv(t_ltr_client *client) {
    int err = 0, received;

    /* если не передали еще старую команду - то новую обслуживать не можем */
    if (client->cmd_state == CMD_STATE_SEND_RESP) {
        err = LTRSRV_ERR_CLIENT_CMD_OVERRUN;
        ltrsrv_client_close_req(client, err);
    } else {
        /* если это первый блок команды - то еще не знаем ее размер.
           запускаем частичный прием на минимальный размер */
        if (client->cmd_state == CMD_STATE_IDLE) {
            client->cmd_state = CMD_STATE_RECV_UNKNOWN_SIZE;
            client->cmd_size = LTR_CLIENT_CMD_SIZE;
            client->cmd_pos = 0;
        }
        err = ltrsrv_sock_recv(client->sock, &((uint8_t*)client->cmd)[client->cmd_pos],
                                    client->cmd_size - client->cmd_pos, &received);
        if (!err) {
            unsigned int new_pos = client->cmd_pos + received;

            /* если приняли признак старта команды - проверяем его */
            if ((client->cmd_pos < sizeof(client->cmd->sign))  &&
                 (new_pos >= sizeof(client->cmd->sign)) &&
                    client->cmd->sign != LTR_CLIENT_CMD_START) {
                err = LTRSRV_ERR_CLIENT_CMD_SIGN;
                ltrsrv_client_close_req(client, err);
            }

            /* отдельно прверяем, что первой командой всегда идет INIT */
            if ((client->cmd_pos < LTR_CLIENT_CMD_SIZE)  &&
                 (new_pos >= LTR_CLIENT_CMD_SIZE)) {
                if ((client->state == LTRSRV_CLIENT_STATE_INIT) &&
                        (client->cmd->cmd != LTR_CLIENT_CMD_INIT_CONNECTION)) {
                    err = LTRSRV_ERR_CLIENT_INVALID_INIT_CMD;
                    ltrsrv_client_close_req(client, err);
                }
            }

            /* если длина команды пока не известна - пробуем ее определить */
            if (!err && (new_pos == client->cmd_size) &&
                    (client->cmd_state == CMD_STATE_RECV_UNKNOWN_SIZE)) {
                err = p_client_cmd_get_len(client);

                /* если оказалось, что размер команды нужен больше, чем мы думали -
                   выдиляем новый буфер под команду */
                if (!err && (client->cmd_size > client->cmd_max_size)) {
                    err = p_client_cmd_realloc_buf(client, client->cmd_size);
                }
            }

            if (!err) {
                /* если приняли всю команду, и мы уже поняли ее размер - переходим к обработке */
                if ((new_pos == client->cmd_size) &&
                        (client->cmd_state == CMD_STATE_RECV_FINAL)) {
                    err = p_client_cmd_process(client);
                } else {
                    client->cmd_pos = new_pos;
                }
            }

            if (err && (client->state != LTRSRV_CLIENT_STATE_INIT)) {
                ltrsrv_log_rec(LTRSRV_LOGLVL_ERR, client->log_str, err,
                               "Client control command failed (cmd 0x%X - %s)",
                               client->last_cmd_code, ltrsrv_get_client_cmd_str(client->last_cmd_code));
            }
        } else {
            ltrsrv_client_close_req(client, err);
        }
    }

    return err;
}


/* Возобновление приема данных от клиента (вызывается, если буфер был до этого заполнен,
 * и в нем освободилось место) */
int ltrsrv_client_recv_resume(t_ltr_client *client) {
    int err = 0;
#ifdef _WIN32
    /* в Windows похоже не активируется событие готовности приема при полностью
     * заполненной очереди сокета (нет отличия пустой от полной). В связи с этим,
     * если возобновляем чтение после того, как буфер был заполнен, и чтение
     * приостановленно, пробуем забрать данные у клиента без проверки событий */
    err = ltrsrv_notify_client_data_rdy(client, client->crate, client->slot);
#endif

    client->wt_flags |= LTRSRV_WAITFLG_RECV;
    if (!err)
        err =ltrsrv_wait_add_sock(client->sock, client->wt_flags, 0, f_client_cb, client);
    return err;
}

/* Прекращение приема данных от клиента (вызывается, если заполнен буфер для приема данных) */
int ltrsrv_client_recv_stop(t_ltr_client *client) {
    client->wt_flags &= ~LTRSRV_WAITFLG_RECV;
    return ltrsrv_wait_add_sock(client->sock, client->wt_flags, 0, f_client_cb, client);
}

/* Ожидание готовности передачи клиенту (освобождения буфера сокета). По готовности
 * будет вызвана callback-функция cb */
int ltrsrv_client_wait_snd_rdy(t_ltr_client *client, t_ltr_client_snd_rdy_cb cb) {
    client->wt_flags |= LTRSRV_WAITFLG_SEND;
    client->snd_rdy_cb = cb;
    return ltrsrv_wait_add_sock(client->sock, client->wt_flags, 0, f_client_cb, client);
}

/* прием 4-байтных слов от клиента, установившего соединение с модулем */
int ltrsrv_client_get_words(t_ltr_client *client, uint32_t* wrds, int rcv_size) {
    int err= 0, received = 0;
    char* byte_wrds = (char*)wrds;

    err = ltrsrv_sock_recv(client->sock, &byte_wrds[client->part_recv_pos],
                           rcv_size*4-client->part_recv_pos, &received);

    if (!err) {
        /* если было принято до этого неполное слово данных, то копируем его
         * принятую часть тут в начало */
        if (client->part_recv_pos) {
            memcpy(byte_wrds, client->part_recv, client->part_recv_pos);
            received += client->part_recv_pos;
        }

        /* смотрим, остался ли в конце неполный хвост */
        client->part_recv_pos = received%4;
        /* сохраняем некратный остаток, если есть */
        if (client->part_recv_pos != 0) {
            ltrsrv_log_rec(LTRSRV_LOGLVL_DBG_HIGH, client->log_str, 0, "partial client recv = %d", client->part_recv_pos);
            memcpy(client->part_recv, &byte_wrds[received-client->part_recv_pos], client->part_recv_pos);
        }
        received/=4;
    } else {
        ltrsrv_client_close_req(client, err);
    }
    return err ? err :received;
}

int ltrsrv_client_get_bytes(struct st_ltr_client *client, uint8_t *bytes, int rcv_size) {
    int recvd;
    int err = ltrsrv_sock_recv(client->sock, bytes, rcv_size, &recvd);

    return err == 0 ? recvd : err;
}

int ltrsrv_client_put_bytes(struct st_ltr_client *client, const uint8_t *bytes, int put_size) {
    int sent;

    sent= send(client->sock, bytes, put_size, MSG_NOSIGNAL);
    if (sent < 0) {
        if (L_SOCK_LAST_ERR_BLOCK())
            sent = 0;
    }
    return sent;
}

int ltrsrv_client_put_words(struct st_ltr_client* client, const uint32_t* wrds, int snd_size) {
    int err= 0, sent=0;
    /* если остались частично не переданный хвост от команды, то пробуем передать его */
    if (client->part_snd_pos) {
        sent = send(client->sock, client->part_snd, client->part_snd_pos, MSG_NOSIGNAL);
        if (sent < 0) {
            if (L_SOCK_LAST_ERR_BLOCK())
                sent = 0;
        }

        if (sent >= 0) {
            /* если передали только часть хвоста, то допередаем остаток */
            if ((sent != 0) && (sent < client->part_snd_pos)) {
                memmove(client->part_snd, &client->part_snd[sent], client->part_snd_pos - sent);
            }
            /* уменьшаем остаток на sent */
            client->part_snd_pos-=sent;
        } else {
            err = LTRSRV_ERR_SOCKET_SEND;
        }
        sent = 0;
    }

    /* передаем основные данные только в случае, если передан успешно остаток
       предыдущего слова */
    if (!err && (client->part_snd_pos==0) && (snd_size!=0)) {
        sent = send(client->sock, (char*)wrds, snd_size*4, MSG_NOSIGNAL);
        if (sent < 0) {
            if (L_SOCK_LAST_ERR_BLOCK())
                sent = 0;
        }

        if (sent >= 0) {
            client->part_snd_pos = sent % 4;
            /* если есть остаток, не кратный 4, то сохраняем его для последующей
               передачи */
            if (client->part_snd_pos) {
                const char* byte_wrds = (const char*)wrds;
                client->part_snd_pos = 4 - client->part_snd_pos;
                ltrsrv_log_rec(LTRSRV_LOGLVL_DBG_HIGH, client->log_str, 0,
                               "partial client send = %d", client->part_snd_pos);
                memcpy(client->part_snd, &byte_wrds[sent], client->part_snd_pos);
            }
            sent=(sent+3)/4;
        } else {
            err = LTRSRV_ERR_SOCKET_SEND;
        }
    }

    /* если остался не переданный кусок данных, то будем ждать, пока
       не появится возможность их передать. Callback при этом сохраняем */
    if (!err && (client->part_snd_pos!=0))
        err = ltrsrv_client_wait_snd_rdy(client, client->snd_rdy_cb);

    /* по ошибке передачи закрываем клиентское соединение */
    if (err) {
        ltrsrv_client_close_req(client, err);
    }

    return err ? err :sent;
}

/***************************************************************************//**
    Callback-функция, вызываемая при событии на сокете клиента. Обрабатываем
    команды управляющего клиента и при инициализации, а так же потоком соединения
    с модулем
    @param[in] sock   сокет по которому произошло событие
    @param[in] event  флаги t_ltrsrv_wait_event, определяющие, какие события произошли
    @param[in] data   не используется
    ***************************************************************************/
static void f_client_cb(t_socket sock, int event, void* data) {
    t_ltr_client *client = (t_ltr_client *)data;
    int err = _CLIENT_CHECK_HND(client);
    if (!err) {
        if (!err && (event & LTRSRV_EVENT_RCV_RDY)) {
            /* если это управляющее соединения или стадия инициализации - то это
               команда */
            if ((client->state == LTRSRV_CLIENT_STATE_INIT) ||
                    (client->state  == LTRSRV_CLIENT_STATE_CONTROL)) {
                err = f_cmd_proc_recv(client);
#ifdef LTRD_LOGPROTO_ENABLED
            } else if (client->state == LTRSRV_CLIENT_STATE_LOGPROTO) {
                err = ltrsrv_logproto_notify_data_rdy(client);
#endif
            } else {
                /* иначе оповещаем, что пришли новые данные для модуля */
                err = ltrsrv_notify_client_data_rdy(client, client->crate, client->slot);
            }
        }


        if (!err && (event & LTRSRV_EVENT_SEND_RDY)) {
            /* для управляющего соединения или при инициализации готовность на
               прием означает, что можно передать еще часть ответа */
            if ((client->state == LTRSRV_CLIENT_STATE_INIT) ||
                    (client->state  == LTRSRV_CLIENT_STATE_CONTROL)) {
                err = p_client_cmd_proc_send(client);
            } else {
                /* вначале пробуем передать частично не переданное слово, если есть */
                if (client->part_snd_pos)
                    err = ltrsrv_client_put_words(client, NULL, 0);


                /* если хвост передали успешно, то можно завершать
                   ожидание и вызвать callback верхнего уровня */
                if (!err && (client->part_snd_pos==0)) {
                    /* для связи с модулем это означает, что нужно вызвать
                       callback, чтобы известить, что клиент готов для передачи
                       ему новых данных */
                    client->wt_flags &= ~LTRSRV_WAITFLG_SEND;
                    err = ltrsrv_wait_add_sock(client->sock, client->wt_flags,0,
                                               f_client_cb, client);
                    if (!err && client->snd_rdy_cb)
                        client->snd_rdy_cb(client);
                }
            }
        }

        /* при запросе на закрытые при обработке события от клиента сразу
         * закрываем, не дожидаясь выполнения фонового закрытия.
         * это позволяет сразу открыть клиента после закрытия без потенциальной
         * ошибки из-за задержки фонового закрытия */
        if (client->close_req) {
            ltrsrv_client_close(client);
        }
    }
}

/***************************************************************************//**
    Callback-функция, вызываемая при событии на прослушиваемом сокете
        (как правило при возникновении нового подключения от клиента)
    @param[in] sock   сокет по которому произошло событие
    @param[in] event  флаги t_ltrsrv_wait_event, определяющие, какие события произошли
    @param[in] data   не используется
    ***************************************************************************/
static void f_accept_cb(t_socket sock, int event, void* data) {
    if (event == LTRSRV_EVENT_RCV_RDY) {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        t_socket client_sock = accept(sock, (struct sockaddr*)&peer, &len);
        if (client_sock == L_SOCKET_ERROR) {
            ltrsrv_log_rec(LTRSRV_LOGLVL_ERR, LTRSRV_LOGSRC_SERVER, LTRSRV_ERR_SOCKET_ACCEPT,
                           "Can't accept client connection");
        } else {
            if (f_client_cnt < LTRSRV_CLIENT_COUNT_MAX) {
                int err=0;
                t_ltr_client *client = (t_ltr_client *)malloc(sizeof(t_ltr_client));
                if (client==NULL) {
                    err = LTRSRV_ERR_MEMALLOC;
                } else {

                    memset(client, 0, sizeof(t_ltr_client));
                    client->sign = LTRSRV_CLIENT_HANDLE_SIGN;
                    client->sock = client_sock;

                    ltrsrv_addr_to_string((struct sockaddr*)&peer, sizeof(peer), client->location, sizeof(client->location));

                    SOCK_SET_NONBLOCK(client_sock, err);
                    if (!err)
                        err = p_client_create_logstr(client);

                    if (!err) {
                        /* по возможности пробуем отключить алгоритм Нагла, чтобы
                         * не замедлять передачи между ltrapi и ltrd */
                        int flag = 1;
                        if (setsockopt(client_sock, IPPROTO_TCP, TCP_NODELAY,
                                       (char*)&flag, sizeof(flag)) == L_SOCKET_ERROR) {
                            ltrsrv_log_rec(LTRSRV_LOGLVL_WARN, client->log_str, 0, "Cannot set TCP_NODELAY option for socket");
                        }
                    }

                    if (!err) {
                        client->cmd = (t_ltr_client_cmd*)malloc(
                                    LTRSRV_CLIENT_DEFAULT_CMD_BUF_SIZE);
                        if (client->cmd == NULL)
                            err = LTRSRV_ERR_MEMALLOC;
                    }

                    if (!err) {
                        client->state = LTRSRV_CLIENT_STATE_INIT;
                        client->cmd_size = LTR_CLIENT_CMD_SIZE;
                        client->cmd_max_size = LTRSRV_CLIENT_DEFAULT_CMD_BUF_SIZE;
                        client->cmd_state = CMD_STATE_RECV_UNKNOWN_SIZE;
                        client->part_snd_pos = 0;
                        client->part_recv_pos = 0;


                        ltrsrv_log_rec(LTRSRV_LOGLVL_DBG_MED, client->log_str, 0,
                                        "New connection from client is accepted");

                        /* ожидаем от клиента сообщений */
                        client->wt_flags = LTRSRV_WAITFLG_RECV;
                        err = ltrsrv_wait_add_sock(client_sock, client->wt_flags,0,
                                                   f_client_cb, client);
                        if (!err) {
                            f_clients[f_client_cnt++] = client;
                        }                        
                    } else {
                        ltrsrv_log_rec(LTRSRV_LOGLVL_ERR, LTRSRV_LOGSRC_CLIENT, err,
                                       "Cannot create new client");
                    }

                    if (err)
                        ltrsrv_client_close(client);

                } /* if (client==NULL) else */
            } else {
                ltrsrv_log_rec(LTRSRV_LOGLVL_WARN, LTRSRV_LOGSRC_CLIENT, 0,
                               "All clients slots are busy. Connection closed");
                /* если очередь заполнена - закрываем соединение с клиентом */
                closesocket(client_sock);
            }
        }
    }
}


int ltrsrv_client_notify_mod_closed(struct st_ltr_client* client) {
    int err = _CLIENT_CHECK_HND(client);
    if (!err)
        ltrsrv_client_close_req(client, 0);
    return err;
}

int ltrsrv_client_notify_crate_closed(t_ltr_crate* crate) {
    int i;
    for (i=f_client_cnt-1; i >=0; i--) {
        if (f_clients[i]->crate == crate) {
            f_clients[i]->crate = NULL;
            ltrsrv_client_close_req(f_clients[i], 0);
        }
    }
    return 0;
}



int ltrsrv_client_sndbuf_ov(struct st_ltr_client* client) {
    int err = _CLIENT_CHECK_HND(client);
    if (!err)
        ltrsrv_log_rec(LTRSRV_LOGLVL_WARN, client->log_str, 0, "Client send socket buffer overflow!");
    return err;
}



int ltrsrv_clients_init(void) {
    t_socket s = L_INVALID_SOCKET;
    struct sockaddr_in servaddr;
    int err = 0;

    if (!err) {
        s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (s == L_INVALID_SOCKET)
            err = LTRSRV_ERR_SOCKET_CREATE;
    }

    /* Переводим сокет в неблокирующий режим работы */
    if (!err) {
        SOCK_SET_NONBLOCK(s, err);
        if (!err) {
            /* устанавливаем возможность использования сокета после закрытия
               для того, чтобы открыть сокет даже если сервер только закрылся */
            int val = 1;
            err = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*)&val, sizeof(val));
        }
    }

    if (!err) {
        /* заполняем структуру с адресом LTR-сервера */
        memset(&servaddr, 0, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(LTRSRV_TCP_CLIENT_PORT);
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

        if (bind(s, (struct sockaddr*)&servaddr, sizeof(servaddr)) == L_SOCKET_ERROR) {
            err = errno;
            err = LTRSRV_ERR_SOCKET_BIND;
        }
    }

    if (!err) {
        if (L_SOCKET_ERROR == listen(s, LTRSRV_CLIENT_LISTEN_QUEUE_SIZE)) {
            err = LTRSRV_ERR_SOCKET_LISTEN;
        }
    }


    if (!err) {
        f_listen_sock = s;
        err = ltrsrv_wait_add_sock(s, LTRSRV_WAITFLG_RECV, 0, f_accept_cb, NULL);
    } else {
        shutdown(s, SHUT_RDWR);
        closesocket(s);
    }


    if (err) {
        ltrsrv_log_rec(LTRSRV_LOGLVL_ERR_FATAL, LTRSRV_LOGSRC_CLIENT, err,
                       "Cannot create listen socket for client connections");
    }
    return err;
}

/* закрытие всей клиенской части сервера */
void ltrsrv_clients_close(void) {
    int i;

    for (i=f_client_cnt-1; i>=0; i--) {
        ltrsrv_client_close(f_clients[i]);
    }

    if (f_listen_sock != L_INVALID_SOCKET) {
        ltrsrv_wait_remove_sock(f_listen_sock);
        shutdown(f_listen_sock, SHUT_RDWR);
        closesocket(f_listen_sock);
    }
}





t_ltr_crate *ltrsrv_client_crate(struct st_ltr_client *client) {
    return client->crate;
}


const char *ltrsrv_client_log_str(struct st_ltr_client *client) {
    return client->log_str;
}
