/*
 * libkarma/karma.c
 *
 * Copyright (c) Frank Zschockelt <libkarma@freakysoft.de> 2004-2005
 *
 * You may distribute and modify this program under the terms of 
 * the GNU GPL, version 2 or later.
 *
 */

#include <sys/socket.h>
#ifdef __NetBSD__
#include <netinet/in.h>
#endif
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "lkarma.h"
#include "karma.h"
#include "karmaLan.h"
#include "properties.h"
#include "md5.h"
#include "util.h"
#include "fdb.h"

#ifndef MSG_FIN
#define MSG_FIN 0
#endif

static int busy_handler(int rio, uint32_t waitfor);

static uint32_t recv_header(int rio);
static int32_t recv_status(int rio);
static int32_t recv_properties(int rio, uint32_t identifier, uint32_t r_id,
                               char ** properties);

char *lk_karmaLan_fidToPath(int rio, uint32_t file_id)
{
    return NULL;
}

/* 
  Help-Functions
*/

int lk_karmaLan_connect(char * ip)
{
    struct hostent *host;
    struct sockaddr_in dest;
    int rio;
    
    host = gethostbyname(ip);
    if(host == NULL){
        lk_errors_set(E_HOST);
        return -1; /* err: gethostbyname() failed (see gethostbyname) */
    }

    rio = socket(AF_INET, SOCK_STREAM, 0);
    if(rio == -1){
        lk_errors_set(E_SOCKET);
        return -1; /* err: socket() failed (see socket) */
    }
    
    memset(&dest, 0, sizeof(dest));
    dest.sin_addr = *(struct in_addr *)host->h_addr_list[0];
    dest.sin_family = AF_INET;
    dest.sin_port = htons(RIOPORT);

    if(connect(rio, (struct sockaddr *)&dest, sizeof(dest)) == -1){
        lk_errors_set(E_CONNECT);
        return -1; /* err: connect() failed (see connect) */
    } else return rio;    
}

int lk_karmaLan_send_request(int rio, uint32_t identifier, char *payload,
                             int plen)
{
    char msg[HEADERLENGTH];

    /* protocol signature */
    memcpy(msg, RIO_HEADER, 4);
    /* command number from identifier */
    identifier=lk_htorl(identifier);
    memcpy(&msg[4], &identifier, sizeof(identifier));

    if(payload==NULL)
        send(rio, msg, HEADERLENGTH, MSG_FIN);
    else{
        send(rio, msg, HEADERLENGTH, 0);
        send(rio, payload, plen, MSG_FIN);
    }
    
    return 0;
}

/*
    recv the header of the karma,
    it'll return -1 if the first part of the header
    doesn't match with the standard rio header, but if it
    does, it'll return the command identifier
*/
static uint32_t recv_header(int rio)
{
    uint32_t h;
    char sig[4];
    size_t n;
    
    n = recv(rio, &sig, 4, MSG_WAITALL);
    if(memcmp(sig, RIO_HEADER, 4)){
        lk_errors_set(E_BADHEADER);
        return -1; /* err: broken Rio Header */
    }
    n = recv(rio, &h, sizeof(h), MSG_WAITALL);
    return lk_rtohl(h);
}

/*
    returns the status as int32_t
*/
static int32_t recv_status(int rio)
{
    int32_t s;
    recv(rio, &s, sizeof(s), MSG_WAITALL);
    return lk_rtohl(s);
}

/*
    recv the properties,
    copies the data to **properties, 
    memory will be allocated by recv_properties,
    returns -1 if the header doesn't match with identifier 
    or the status indicates an failure.
*/
static int32_t recv_properties(int rio, uint32_t identifier, uint32_t r_id,
                               char ** properties)
{
    uint64_t i=0;
    char id[4];

    if(r_id > 0){
        r_id=lk_htorl(r_id);
        memcpy(id, &r_id, sizeof(r_id));
        lk_karmaLan_send_request(rio, identifier, id, 4);
    }else lk_karmaLan_send_request(rio, identifier, NULL, 0);

    *properties=NULL;
    
    if(busy_handler(rio, identifier)){
        if(recv_status(rio) != 0){
            lk_errors_set(E_FAILEDREQ);
            return -1; /* err: failed request */
        }
    }else return -1; /* already set by busy_handler */

    do{
        if(i%8192==0)
            *properties=(char *)realloc(*properties, (i+8193));
        (*properties)[i+4]='!';
        recv(rio, &(*properties)[i], 4, MSG_WAITALL);
        i+=4;
    }while((*properties)[i-1]!=0);

    return 0;
}

static int busy_handler(int rio, uint32_t waitfor)
{
    uint32_t header;
    header=recv_header(rio);
    if(header==BUSY){
        do{
            recv_status(rio);
            recv_status(rio);
        }while((header=recv_header(rio))==BUSY);
    }
    if(header==waitfor)
        return 1;
    else {
        lk_errors_set(E_BADHEADER);
        return 0;
    }
}

/* 
  Basic Protocol Functions
*/

int lk_karmaLan_get_protocol_version(int rio, uint32_t * major_version,
                                     uint32_t * minor_version)
{
    char versions[8];

    *major_version=lk_htorl(*major_version);
    *minor_version=lk_htorl(*minor_version);
    memcpy(versions, major_version, sizeof(major_version));
    memcpy(&versions[4], minor_version, sizeof(minor_version));

    lk_karmaLan_send_request(rio, 0, versions, 8);
    if(!busy_handler(rio, GET_PROTOCOL_VERSION))
        return -1; /* already set by busy_handler */

    recv(rio, major_version, sizeof(uint32_t), 0);
    recv(rio, minor_version, sizeof(uint32_t), 0);
    *major_version=lk_rtohl(*major_version);
    *minor_version=lk_rtohl(*minor_version);
    return 0;
}

int32_t lk_karmaLan_get_authentication_salt(int rio, char ** salt)
{
    *salt=(char *)malloc(SALTLENGTH);

    lk_karmaLan_send_request(rio, GET_AUTHENTICATION_SALT, NULL, 0);
    if(!busy_handler(rio, GET_AUTHENTICATION_SALT))
        return -1; /* already set by busy_handler */
    recv(rio, *salt, SALTLENGTH, 0);
    
    return 0;
}

uint32_t lk_karmaLan_authenticate(int rio, char * pass)
{
    md5_context ctx;
    unsigned char * md5input;
    unsigned char md5sum[16];

    char * salt;
    uint32_t rights;


    if(lk_karmaLan_get_authentication_salt(rio, &salt) == -1)
        return -1; /* get_authenticatoin_salt had set the errormsg */

    md5input=(unsigned char *)malloc((strlen(pass)+SALTLENGTH+1));
    memcpy(md5input, salt, SALTLENGTH);
    memcpy(&md5input[SALTLENGTH], pass, strlen(pass));

    md5_starts(&ctx);
    md5_update(&ctx, (uint8_t *)md5input, SALTLENGTH+strlen(pass));
    md5_finish(&ctx, md5sum);

    free(salt);
    free(md5input);

    lk_karmaLan_send_request(rio, AUTHENTICATE, (char *)md5sum, 16);
    if(!busy_handler(rio, AUTHENTICATE))
        return -1; /* already set by busy_handler */
    recv_status(rio); /* we don't care about the status, 
                         because we want to get the rights... */
    recv(rio, &rights, sizeof(rights), 0);
    rights=lk_rtohl(rights);
    return rights;
}

int lk_karmaLan_get_device_details(int rio, char **name, char **version, 
                                   uint32_t * storagedevices)
{
    *name=(char *)malloc(DETAILSLENGTH);
    *version=(char *)malloc(DETAILSLENGTH);

    lk_karmaLan_send_request(rio, GET_DEVICE_DETAILS, NULL, 0);
    if(!busy_handler(rio, GET_DEVICE_DETAILS))
        return -1; /* err already set by busy_handler */

    recv(rio, *name, DETAILSLENGTH, 0);
    recv(rio, *version, DETAILSLENGTH, 0);
    recv(rio, storagedevices, sizeof(uint32_t), 0);
    *storagedevices=lk_rtohl(*storagedevices);
    return 0;
}

int lk_karmaLan_get_storage_details(int rio, uint32_t storage_id,
                                    uint32_t *n_files, uint64_t *s_size,
                                    uint64_t *f_space,
                                    uint32_t *highest_file_id)
{
    char id[4];

    storage_id=lk_htorl(storage_id);
    memcpy(id, &storage_id, sizeof(storage_id));
    lk_karmaLan_send_request(rio, GET_STORAGE_DETAILS, id, sizeof(uint32_t));

    if(!busy_handler(rio, GET_STORAGE_DETAILS)) 
        return -1; /* err already set by busy_handler */
    if(recv_status(rio) != 0){
        lk_errors_set(E_FAILEDREQ);
        return -1; /* err: failed request */
    }

    recv(rio, n_files, sizeof(uint32_t), 0);
    recv(rio, s_size, sizeof(uint64_t), 0);
    recv(rio, f_space, sizeof(uint64_t), 0);
    recv(rio, highest_file_id, sizeof(uint32_t), 0);
    *n_files=lk_rtohl(*n_files);
    *s_size=lk_rtohll(*s_size);
    *f_space=lk_rtohll(*f_space);
    *highest_file_id=lk_rtohl(*highest_file_id);

    return 0;
}

int lk_karmaLan_get_device_settings(int rio)
{
    int ret;
    char *settings;
    
    recv_properties(rio, GET_DEVICE_SETTINGS, 0, &settings);
    if (settings == NULL)
        return -1;

    ret = lk_karma_parse_settings(settings);
    free(settings);

    return ret;
}


int32_t lk_karmaLan_update_device_settings(int rio, char * properties)
{
    int len;
    len=strlen(properties);
    while(len%4) ++len;
    lk_karmaLan_send_request(rio, UPDATE_DEVICE_SETTINGS, properties, len);
    if(busy_handler(rio, UPDATE_DEVICE_SETTINGS)){
        if(recv_status(rio) != 0){
            lk_errors_set(E_FAILEDREQ);
            return -1; /* err: failed request */
        }else return 0;
    }
    else return -1; /* already set by busy_handler */
}

int32_t lk_karmaLan_request_io_lock(int rio, uint32_t type)
{
    /* unused: int status; */
    char p[4];

    type=lk_htorl(type);
    memcpy(p, &type, sizeof(type));
    lk_karmaLan_send_request(rio, REQUEST_IO_LOCK, p, 4);
    if(busy_handler(rio, REQUEST_IO_LOCK))
        if(recv_status(rio) != 0){
            lk_errors_set(E_FAILEDREQ);
            return -1; /* err: failed request */
        }else return 0;
    else return -1; /* already set by busy_handler */
}

int32_t lk_karmaLan_release_io_lock(int rio)
{
    lk_karmaLan_send_request(rio, RELEASE_IO_LOCK, NULL, 0);
    if(busy_handler(rio, RELEASE_IO_LOCK)){
        if(recv_status(rio) != 0){
            lk_errors_set(E_FAILEDREQ);
            return -1; /* err: failed request */
        }else return 0;
    }
    else return -1; /* already set by busy_handler */
}

/*Not yet supported by the Rio Karma.*/
int32_t lk_karmaLan_prepare(int rio, uint64_t size, uint32_t *file_id,
                            uint32_t storage_id)
{
    char p[16];
    
    size=lk_htorl(size);
    *file_id=lk_htorl(*file_id);
    storage_id=lk_htorl(storage_id);
    memcpy(p, &size, sizeof(size));
    memcpy(&(p[4]), file_id, sizeof(file_id));
    memcpy(&(p[12]), &storage_id, sizeof(storage_id));

    lk_karmaLan_send_request(rio, PREPARE, p, 16);
    if(!busy_handler(rio, PREPARE))
        return -1; /* already set by busy_handler */
    recv(rio, file_id, sizeof(file_id), 0);
    *file_id=lk_rtohl(*file_id);
    
    if(recv_status(rio) != 0){
        lk_errors_set(E_FAILEDREQ);
        return -1; /* err: failed request */
    }else return 0;
}

int32_t lk_karmaLan_write_file_chunk(int rio, uint64_t offset, uint64_t size,
                                     uint32_t file_id, uint32_t storage_id,
                                     const char *data)
{
    char * packet;
    uint64_t align_diff = ( (size % 4) ? 4 - (size % 4) : 0 );
    uint64_t aligned_size = size + align_diff;
    
    packet=malloc(sizeof(uint64_t)*3 + aligned_size);
    offset=lk_htorll(offset);
    size=lk_htorll(size);
    file_id=lk_htorl(file_id);
    storage_id=lk_htorl(storage_id);
    memcpy(packet, &offset, sizeof(offset));
    memcpy(&packet[8], &size, sizeof(size));
    size=lk_rtohll(size);
    memcpy(&packet[16], &file_id, sizeof(file_id));
    memcpy(&packet[20], &storage_id, sizeof(storage_id));
    memcpy(&packet[24], data, size);
    if (align_diff)
        memset(&packet[24+size], 0, align_diff);

    lk_karmaLan_send_request(rio, WRITE_FILE_CHUNK, packet,
                             aligned_size + sizeof(uint64_t)*3);
    free(packet);
    
    if(busy_handler(rio, WRITE_FILE_CHUNK)){
        if(recv_status(rio) != 0){
            lk_errors_set(E_FAILEDREQ);
            return -1; /* err: failed request */
        }else return 0;
    }
    else return -1; /* already set by busy_handler */
}

int32_t lk_karmaLan_get_all_file_details(int rio, char ** properties)
{
    return recv_properties(rio, GET_ALL_FILE_DETAILS, 0, properties);
}

int32_t lk_karmaLan_get_file_details(int rio, uint32_t file_id,
                                     char **properties)
{
    return recv_properties(rio, GET_FILE_DETAILS, file_id, properties);
}

int32_t lk_karmaLan_update_file_details(int rio, uint32_t file_id,
                                        char *properties)
{
    char * packet; /* unused *tmp; */
    int len,rlen;

    rlen=strlen(properties);
    len=rlen;
    ++len;
    while(len%4) ++len;
    packet=malloc(sizeof(uint32_t)+len);
    file_id=lk_htorl(file_id);
    memcpy(packet, &file_id, sizeof(file_id));
    memcpy(&packet[4], properties, len);
    memset(&packet[rlen+sizeof(uint32_t)], 0, len-rlen);
    
    lk_karmaLan_send_request(rio, UPDATE_FILE_DETAILS, packet,
                             len+sizeof(uint32_t));
    free(packet);
    
    if(busy_handler(rio, UPDATE_FILE_DETAILS)){
        lk_properties_inc_devgeneration();
        if(recv_status(rio) != 0){
            lk_errors_set(E_FAILEDREQ);
            return -1; /* err: failed request */
        }else return 0;
    }else return -1; /* already set by busy_handler */
}

int32_t lk_karmaLan_read_file_chunk(int rio, uint64_t offset, uint64_t size,
                                    uint32_t file_id, char ** data,
                                    uint64_t * retsize)
{    /* Warning:  except for the last call, "size" must be a multiple of 4 */
    char p[20];
    uint64_t realsize;
    int r;
    
    *retsize=0;
    offset=lk_htorll(offset);
    size=lk_htorll(size);
    file_id=lk_htorl(file_id);
    memcpy(&p, &offset, sizeof(offset));
    memcpy(&p[8], &size, sizeof(size));
    memcpy(&p[16], &file_id, sizeof(file_id));
    lk_karmaLan_send_request(rio, READ_FILE_CHUNK, p, 20);

    if(!busy_handler(rio, READ_FILE_CHUNK)) 
        return -1;                         /* already set by busy_handler */
    recv(rio, &realsize, sizeof(uint64_t), 0);
    realsize=lk_rtohll(realsize);
    r = realsize%4;                                  /* division reminder */
    realsize = realsize + (r?(4-r):0);  /* padding to multiple of 4 bytes */
    *data=(char *)malloc(realsize+1);
    if(recv_status(rio) != 0){
        lk_errors_set(E_FAILEDREQ);
        return -1;
    }
    *retsize=recv(rio, *data, realsize, MSG_WAITALL);
    if (r) *retsize -= (4-r);           /* return the unpadded chunk size */

    if(recv_status(rio) != 0){
        lk_errors_set(E_FAILEDREQ);
        return -1;                                 /* err: failed request */
    }else return 0;
}

int32_t lk_karmaLan_delete_file(int rio, uint32_t file_id)
{
    char id[4];
    
    file_id=lk_htorl(file_id);
    memcpy(id, &file_id, sizeof(file_id));
    lk_karmaLan_send_request(rio, DELETE_FILE, id, 4);
    if(busy_handler(rio, DELETE_FILE)){
        lk_properties_inc_devgeneration();
        if(recv_status(rio) != 0){
            lk_errors_set(E_FAILEDREQ);
            return -1; /* err: failed request */
        }else return 0;
    }
    else return -1; /* already set by busy_handler */
}

/* Now, that command would be scary ;)
     (It's not supported by the rio karma ;))
*/
int32_t lk_karmaLan_format_storage(int rio, uint32_t storage_id)
{
    char id[4];

    storage_id=lk_htorl(storage_id);
    memcpy(id, &storage_id, sizeof(storage_id));
    lk_karmaLan_send_request(rio, FORMAT_STORAGE, id, 4);
    if(busy_handler(rio, FORMAT_STORAGE)){
        if(recv_status(rio) != 0){
            lk_errors_set(E_FAILEDREQ);
            return -1;/* err: failed request */
        }else return 0;
    }
    else return -1; /* already set by busy_handler */
}

int32_t lk_karmaLan_hangup(int rio)
{
    lk_karmaLan_send_request(rio, HANGUP, NULL, 0);
    if(busy_handler(rio, HANGUP)){
        if(recv_status(rio) != 0){
            lk_errors_set(E_FAILEDREQ);
            return -1;/* err: failed request */
        }else return 0;
    }else return -1; /* already set by busy_handler */
}

/*Not yet supported by the Rio Karma.*/    
char blubb[]="I shot the ipod, but I didn't shoot no deputy, oh no! oh!";
int32_t lk_karmaLan_device_operation(int rio, uint64_t size, char *data,
                                     char **newdata)
{
    char * d;
    
    d=malloc(sizeof(char)*sizeof(uint64_t)+sizeof(char)*size);

    size=lk_htorll(size);
    memcpy(d, &size, sizeof(size));
    size=lk_rtohll(size);
    memcpy(&d[8], data, size);
    lk_karmaLan_send_request(rio, DEVICE_OPERATION, d, size+sizeof(uint64_t));

    if(!busy_handler(rio, DEVICE_OPERATION)) 
        return -1; /* already set by busy_handler */

    recv(rio, &size, sizeof(uint64_t), 0);
    size=lk_rtohll(size);
    if(recv_status(rio) != 0){
        lk_errors_set(E_FAILEDREQ);
        return -1; /* err: failed request */
    }
    *newdata=realloc(*newdata, size);
    recv(rio, *newdata, size, MSG_WAITALL);
    
    return 0;    
}

/* -------------------------------------------------------------------------- */
int lk_karmaLan_write_smalldb(void)
{
    return 0;
}   

/* 
  Advanced Protocol Functions
*/

void lk_karmaLan_load_database(int rio)
{
    char *settings;

    lk_karmaLan_get_device_settings(rio);
    if(lk_properties_cache_obsolete()){ /* DB was changed */
        lk_karmaLan_get_all_file_details(rio, &settings);
        lk_properties_import(settings);
        free(settings);
        lk_fdb_load(1);
    }else{
        lk_properties_load();
        lk_fdb_load(0);
    }
}

void lk_karmaLan_update_database(int rio)
{
    char *settings;
    uint32_t old = device_generation;

    lk_karmaLan_get_device_settings(rio);
    if(device_generation != old){ /* DB was changed */
        lk_properties_destroy(); 
        lk_properties_init();
        lk_karmaLan_get_all_file_details(rio, &settings);
        lk_properties_import(settings);
        free(settings);
    }
}
