MOBI_RET mobi_decompress_lz77(unsigned char *out, const unsigned char *in, size_t *len_out, const size_t len_in) {
    MOBI_RET ret = MOBI_SUCCESS;
    MOBIBuffer *buf_in = mobi_buffer_init_null((unsigned char *) in, len_in);
    if (buf_in == NULL) {
        debug_print("%s\n", "Memory allocation failed");
        return MOBI_MALLOC_FAILED;
    }
    MOBIBuffer *buf_out = mobi_buffer_init_null(out, *len_out);
    if (buf_out == NULL) {
        mobi_buffer_free_null(buf_in);
        debug_print("%s\n", "Memory allocation failed");
        return MOBI_MALLOC_FAILED;
    }
    while (ret == MOBI_SUCCESS && buf_in->offset < buf_in->maxlen) {
        uint8_t byte = mobi_buffer_get8(buf_in);
        /* byte pair: space + char */
        if (byte >= 0xc0) {
            mobi_buffer_add8(buf_out, ' ');
            mobi_buffer_add8(buf_out, byte ^ 0x80);
        }
        /* length, distance pair */
        /* 0x8000 + (distance << 3) + ((length-3) & 0x07) */
        else if (byte >= 0x80) {
            uint8_t next = mobi_buffer_get8(buf_in);
            uint16_t distance = ((((byte << 8) | ((uint8_t)next)) >> 3) & 0x7ff);
            uint8_t length = (next & 0x7) + 3;
            while (length--) {
                mobi_buffer_move(buf_out, -distance, 1);
            }
        }
        /* single char, not modified */
        else if (byte >= 0x09) {
            mobi_buffer_add8(buf_out, byte);
        }
        /* val chars not modified */
        else if (byte >= 0x01) {
            mobi_buffer_copy(buf_out, buf_in, byte);
        }
        /* char '\0', not modified */
        else {
            mobi_buffer_add8(buf_out, byte);
        }
        if (buf_in->error || buf_out->error) {
            ret = MOBI_BUFFER_END;
        }
    }
    *len_out = buf_out->offset;
    mobi_buffer_free_null(buf_out);
    mobi_buffer_free_null(buf_in);
    return ret;
}
MOBI_RET mobi_decompress_lz77(unsigned char *out, const unsigned char *in, size_t *len_out, const size_t len_in) {
    MOBI_RET ret = MOBI_SUCCESS;
    MOBIBuffer *buf_in = mobi_buffer_init_null((unsigned char *) in, len_in);
    if (buf_in == NULL) {
        debug_print("%s\n", "Memory allocation failed");
        return MOBI_MALLOC_FAILED;
    }
    MOBIBuffer *buf_out = mobi_buffer_init_null(out, *len_out);
    if (buf_out == NULL) {
        mobi_buffer_free_null(buf_in);
        debug_print("%s\n", "Memory allocation failed");
        return MOBI_MALLOC_FAILED;
    }
    while (ret == MOBI_SUCCESS && buf_in->offset < buf_in->maxlen) {
        uint8_t byte = mobi_buffer_get8(buf_in);
         
        if (byte >= 0xc0) {
            mobi_buffer_add8(buf_out, ' ');
            mobi_buffer_add8(buf_out, byte ^ 0x80);
        }
         
         
        else if (byte >= 0x80) {
            uint8_t next = mobi_buffer_get8(buf_in);
            uint16_t distance = ((((byte << 8) | ((uint8_t)next)) >> 3) & 0x7ff);
            uint8_t length = (next & 0x7) + 3;
            while (length--) {
                mobi_buffer_move(buf_out, -distance, 1);
            }
        }
         
        else if (byte >= 0x09) {
            mobi_buffer_add8(buf_out, byte);
        }
         
        else if (byte >= 0x01) {
            mobi_buffer_copy(buf_out, buf_in, byte);
        }
         
        else {
            mobi_buffer_add8(buf_out, byte);
        }
        if (buf_in->error || buf_out->error) {
            ret = MOBI_BUFFER_END;
        }
    }
    *len_out = buf_out->offset;
    mobi_buffer_free_null(buf_out);
    mobi_buffer_free_null(buf_in);
    return ret;
}