///
/// @file lutil_impl.c
/// @authors k. Headley
/// @date 05 dec 2019

/// Log utility API implementations

/// @sa doxygen-examples.c for more examples of Doxygen markup


/////////////////////////
// Terms of use
/////////////////////////
/*
 Copyright Information

 Copyright 2002-2019 MBARI
 Monterey Bay Aquarium Research Institute, all rights reserved.

 Terms of Use

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 3 of the License, or
 (at your option) any later version. You can access the GPLv3 license at
 http://www.gnu.org/licenses/gpl-3.0.html

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details
 (http://www.gnu.org/licenses/gpl-3.0.html)

 MBARI provides the documentation and software code "as is", with no warranty,
 express or implied, as to the software, title, non-infringement of third party
 rights, merchantability, or fitness for any particular purpose, the accuracy of
 the code, or the performance or results which you may obtain from its use. You
 assume the entire risk associated with use of the code, and you agree to be
 responsible for the entire cost of repair or servicing of the program with
 which you are using the code.

 In no event shall MBARI be liable for any damages, whether general, special,
 incidental or consequential damages, arising out of your use of the software,
 including, but not limited to, the loss or corruption of your data or damages
 of any kind resulting from use of the software, any prohibited use, or your
 inability to use the software. You agree to defend, indemnify and hold harmless
 MBARI and its officers, directors, and employees against any claim, loss,
 liability or expense, including attorneys' fees, resulting from loss of or
 damage to property or the injury to or death of any person arising out of the
 use of the software.

 The MBARI software is provided without obligation on the part of the
 Monterey Bay Aquarium Research Institute to assist in its use, correction,
 modification, or enhancement.

 MBARI assumes no responsibility or liability for any third party and/or
 commercial software required for the database or applications. Licensee agrees
 to obtain and maintain valid licenses for any additional third party software
 required.
 */

/////////////////////////
// Headers
/////////////////////////
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <time.h>
#include <string.h>
// for isspace(trim)
#include <ctype.h>

#include "cwg-shared.h"
#include "licor8xx-shared.h"
#include "lutil_impl.h"
#include "lutil_api.h"
#include "iowfile.h"
#if defined(__PIC24FJ256GA106__) || defined(__PIC24FJ256GA606__) || defined(__PIC24FJ1024GA606__)
#include "config.h"
#include "ioserial.h"
#include "timers.h"
#endif
#if defined(__APPLE__) || defined(__unix__) || defined(__CYGWIN__)
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#endif
#if defined(__WIN32) || defined(__WIN64)
#include <Windows.h>
#endif

/////////////////////////
// Macros
/////////////////////////

// These macros should only be defined for
// application main files rather than general C files
/*
/// @def PRODUCT
/// @brief header software product name
#define PRODUCT "carbonwg"

/// @def COPYRIGHT
/// @brief header software copyright info
#define COPYRIGHT "Copyright 2002-2019 MBARI Monterey Bay Aquarium Research Institute, all rights reserved."
/// @def NOWARRANTY
/// @brief header software terms of use
#define NOWARRANTY  \
"This program is distributed in the hope that it will be useful,\n"\
"but WITHOUT ANY WARRANTY; without even the implied warranty of\n"\
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"\
"GNU General Public License for more details (http://www.gnu.org/licenses/gpl-3.0.html)\n"
*/

/////////////////////////
// Declarations
/////////////////////////

int impl_zbin_traverse(log_impl_t *self, lutil_opflags_t flags, uint32_t count,  log_ofs_t *r_ofs, uint32_t *r_count);
int impl_zbin_read(log_impl_t *self, byte *dest, uint32_t len, uint32_t count, uint32_t *r_count);
int impl_zbin_compare(byte *src, limpl_search_def_t *search);
int impl_zbin_show(log_impl_t *self, int indent, lutil_opts_t *opts);
int impl_zbin_bshow(log_impl_t *self, byte *src, int indent, lutil_opts_t *opts);
int impl_zbin_inspect(log_impl_t *self, int indent, lutil_opts_t *opts);

int impl_obin_traverse(log_impl_t *self, lutil_opflags_t flags, uint32_t count,  DWORD *r_ofs, uint32_t *r_count);
int impl_obin_read(log_impl_t *self, byte *dest, uint32_t len, uint32_t count, uint32_t *r_count);
int impl_obin_compare(byte *src, limpl_search_def_t *search);
int impl_obin_show(log_impl_t *self, int indent, lutil_opts_t *opts);
int impl_obin_bshow(log_impl_t *self, byte *src, int indent, lutil_opts_t *opts);
int impl_obin_inspect(log_impl_t *self, int indent, lutil_opts_t *opts);

static int s_cmp_type(cmp_op_id op, cmp_type_t cmp_type, cmp_val_t *vset);


/////////////////////////
// Imports
/////////////////////////

/////////////////////////
// Module Global Variables
/////////////////////////

const char *lutil_src_names[]  ={"NONE","OBIN","ZBIN","RAW","SYS","ODQ","ZDQ"};
const char *lutil_cmp_names[]={"NONE","EQ","NE","GT","LT","GE","LE","RI","RO"};
const char *lutil_cmp_ops[]={"?","==","!=",">","<",">=","<=","><","<>"};
const char *lutil_cmp_type_names[]={"NONE","FLOAT","DOUBLE","I16","I32","I64","U16","U32","U64"};
const char *lutil_search_names[]={"NONE","IDX","TIME","RTYPE","RTID","RMID","MMID","XID","SN","ESN"};
const char *lless_state_names[]={"HELP","WAIT","QUIT","MOVE","PREV","NEXT","HEAD","TAIL","CMD","SEARCH"};
const char *lless_nav_names[]={"NAV_NONE","NAV_ABS","NAV_REL","NAV_HEAD","NAV_TAIL"};
const char *lutil_opflag_names[]={"REV","FWD"};

const char *linfo_rbin_names[RBIN_COUNT]={
    "RMID_INVAL","RMID_NONE","RMID_SAMPLE","RMID_SCAL","RMID_ZCAL",
    "RTID_INVAL","RTID_ZRECV1",
    "MMID_NONE","MMID_AIR","MMID_STD","MMID_ZERO",
    "MMID_PRES","MMID_POSTS","MMID_PREZ","MMID_POSTZ",
    "MMID_PREB","MMID_POSTB","MMID_BLINE","MMID_SAMPLE",
    "MMID_ZCAL","MMID_SCAL"
};

const char *linfo_ebin_names[EBIN_COUNT]={
    "E_SYNC","E_TMZERO","E_TMMONO","E_CTINVAL",
    "E_SEQINVAL","E_LENINVAL","E_RTID","E_RMID",
    "E_MMID","E_DATA"
};

#if defined(__PIC24FJ256GA106__) || defined(__PIC24FJ256GA606__) || defined(__PIC24FJ1024GA606__)
// persist source name string if variable substitution is used w/ SRC: option
static char opt_buf[CFG_MEM_STRING_BYTES]={0};
#endif

#ifdef WITH_DQUTIL
const char *dqutil_cmd_names[]={"?","SHOW","ADD","DEL","MOVE","INSPECT"};
#endif //WITH_DQUTIL

/////////////////////////
// Function Definitions
/////////////////////////

int limpl_init(log_impl_t *self, char *fpath, lutil_type_id type)
{
    int retval=-1;
    if(NULL!=self){

       XPRINT(0x6,"type[%d] fpath[%s]\r\n",type,fpath!=NULL?fpath:"");
        // zero/NULL all
        memset(self,0,sizeof(log_impl_t));

        // invalidate cursors
        int i=0;
        while(i<CUR_COUNT)self->cur[i++]=LOG_OFS_INVALID;

        // close file, reset file pointers
        self->fpath=fpath;
        if(NULL != self->fp){
            iow_fclose(self->fp);
        }
#if defined(__PIC24FJ256GA106__) || defined(__PIC24FJ256GA606__) || defined(__PIC24FJ1024GA606__)
        self->fp=&self->file_s;
#else
        self->fp=NULL;
#endif
        bool type_valid=true;
        switch (type) {
            case LU_ZBIN:
                self->sync_bytes = LI_ZREC_SYNC;
                self->buf_len = LI_ZREC_SIZEN(6);
                self->sync = limpl_sync_u32;
                self->seek = limpl_seek;
                self->search = limpl_search;
                self->traverse = impl_zbin_traverse;
                self->compare = impl_zbin_compare;
                self->read = impl_zbin_read;
                self->show = impl_zbin_show;
                self->bshow = impl_zbin_bshow;
                self->nshow = limpl_nshow;
                self->inspect = impl_zbin_inspect;
                break;
            case LU_OBIN:
                self->sync_bytes = LI_OREC_SYNC;
                self->buf_len = LI_OREC_SIZE;
                self->sync = limpl_sync_u32;
                self->seek = limpl_seek;
                self->search = limpl_search;
                self->traverse = impl_obin_traverse;
                self->compare = impl_obin_compare;
                self->read = impl_obin_read;
                self->show = impl_obin_show;
                self->bshow = impl_obin_bshow;
                self->nshow = limpl_nshow;
                self->inspect = impl_obin_inspect;
               break;
            case LU_RAW:
            case LU_SYS:
            case LU_ODQ:
            case LU_ZDQ:
                type_valid=false;
               OUT_CTTY("ERR unsupported log type [%02X]\r\n",type);
               // text logs
                break;
            default:
                type_valid=false;
                OUT_CTTY("ERR invalid log type [%02X]\r\n",type);
                // invalid type
                break;
        }

        // init
        if(type_valid && NULL!=self->fpath){

            // open file
            if(iow_fopen(&self->fp, self->fpath, IOW_READ)==0){
                iow_fseek(self->fp,0);
                // set EOF pointer
                self->cur[CUR_EOF] = iow_fsize(self->fp)-1;
                // find last record
                iow_fseek(self->fp, self->cur[CUR_EOF]);
                uint32_t r_ofs=0;
                if(self->sync(self,DIR_FLG_REV,&r_ofs,NULL)==0){
                    self->cur[CUR_LAST]=r_ofs;
                }else{
                    XTRACE(0xFE);
                }
                iow_fseek(self->fp, 0);
                // find first record
                if(self->sync(self,DIR_FLG_FWD,&self->cur[CUR_FIRST],NULL)==0){
                    // point to first record
                    iow_fseek(self->fp, self->cur[CUR_FIRST]);
                    self->cur[CUR_POS]=self->cur[CUR_FIRST];
                }
                retval=0;

                XPRINT(0x6,"first[%"FMT_U32"]\r\n",self->cur[CUR_FIRST]);
                XPRINT(0x6,"last[%"FMT_U32"]\r\n",self->cur[CUR_LAST]);
                XPRINT(0x6,"eof[%"FMT_U32"]\r\n",self->cur[CUR_EOF]);
                XPRINT(0x6,"sync[%04"FMT_X32"]\r\n",self->sync_bytes);
                XPRINT(0x6,"buf_len[%"FMT_U32"]\r\n",self->buf_len);
            }
        }

    }
    return retval;
}

int limpl_head(log_impl_t *self)
{
    int retval=-1;
    if(NULL!=self && NULL!=self->fp){
        iow_fseek(self->fp,self->cur[CUR_FIRST]);
        retval=0;
    }
    return retval;
}

int limpl_tail(log_impl_t *self)
{
    int retval=-1;
    if(NULL!=self && NULL!=self->fp){
        iow_fseek(self->fp,self->cur[CUR_LAST]);
        retval=0;
    }
    return retval;
}

uint32_t limpl_ftell(log_impl_t *self)
{
    uint32_t retval=0;
    if(NULL!=self && NULL!=self->fp){
        retval = iow_ftell(self->fp);
    }
    return retval;
}

int limpl_fseek(log_impl_t *self, uint32_t ofs)
{
    log_ofs_t retval=LOG_OFS_INVALID;
    if(NULL!=self && NULL!=self->fp && ofs<=self->cur[CUR_EOF]){
        retval = iow_fseek(self->fp,ofs);
    }
    return retval;
}

bool limpl_feof(log_impl_t *self)
{
    bool retval=false;
    if(NULL!=self && NULL!=self->fp){
        retval = iow_feof(self->fp);
    }
    return retval;
}

bool limpl_ferr(log_impl_t *self)
{
    bool retval=false;
    if(NULL!=self && NULL!=self->fp){
        retval = iow_ferror(self->fp);
    }
    return retval;
}

int limpl_search_def_show(limpl_search_def_t *self)
{
    int retval=0;

    if(NULL!=self){
        int wkey=12;
        int wval=16;
        retval+=OUT_CTTY("%*s %*s\r\n",wkey,"type",wval,lutil_search_names[self->type]);
        retval+=OUT_CTTY("%*s %*"FMT_U32"\r\n",wkey,"cmp_type",wval,self->cmp_type);
        retval+=OUT_CTTY("%*s %*"FMT_U32"\r\n",wkey,"op_id",wval,self->op_id);
        retval+=OUT_CTTY("%*s %*"FMT_U32"\r\n",wkey,"idx",wval,self->idx);
        retval+=OUT_CTTY("%*s ",wkey,"val_bytes");
        int i=0;
        for(i=0;i<2;i++){
            byte *bp = (byte *)&self->val[i];
            size_t j=0;
            for(j=0;j<sizeof(cmp_val_t);j++){
                retval+=OUT_CTTY("%02X",bp[j]);
            }
            if(i<1)
            retval+=OUT_CTTY("\r\n%*s ",wkey,"");
        }
        retval+=OUT_CTTY("\r\n");
    }
    return retval;
}

int limpl_sync_u32(log_impl_t *self, lutil_opflags_t flags, log_ofs_t *r_ofs, uint32_t *r_sync)
{
    int retval=-1;
    uint32_t sync_offset =0;

    if(NULL!=self->fp && NULL!=r_ofs){
        byte cur=0x0;
        size_t br=0;
        uint32_t ofs = iow_ftell(self->fp);
        sync_offset = ofs;
        enum{ST_FIRST=0,ST_SECOND,ST_THIRD,ST_FOURTH,ST_SYNC_VALID}state;
        state=ST_FIRST;

        XPRINT(0xF000000FE,"%s: ENTER fp[%"FMT_U32"] fwd[%c] sync[%08"FMT_X32"]\r\n",__func__,iow_ftell(self->fp),BOOLCHAR(DIR_IS_FWD(flags)),self->sync_bytes);

        byte *sync=(byte *)&self->sync_bytes;

        while(state!=ST_SYNC_VALID){
//                OUT_ERR("br[%"FMT_SZ"] cur[%02X] ferr[%d] feof[%d] fp[%"FMT_U32"]\r\n",br,cur,iow_ferror(self->fp),iow_feof(self->fp),iow_ftell(self->fp));
            state=ST_FIRST;
            cur=0;
            br=0;
            // mark offset in case this is the one
            sync_offset = iow_ftell(self->fp);
            if( (br=iow_fread(self->fp,&cur,1))==1 && cur==( DIR_IS_FWD(flags) ? sync[0] : sync[3]) ){
                state++;
                if(DIR_IS_REV(flags)){
                    ofs-=1;
                    if(ofs==0)break;
                    iow_fseek(self->fp,ofs);
                }
                if( (br=iow_fread(self->fp,&cur,1))==1 && cur==(DIR_IS_FWD(flags) ? sync[1] : sync[2]) ){
                    state++;
                    if(DIR_IS_REV(flags)){
                        ofs-=1;
                        if(ofs==0)break;
                        iow_fseek(self->fp,ofs);
                    }
                    if( (br=iow_fread(self->fp,&cur,1))==1 && cur==(DIR_IS_FWD(flags) ? sync[2] : sync[1]) ){
                        state++;
                        if(DIR_IS_REV(flags)){
                            ofs-=1;
//                            if(ofs==0)break;
                            iow_fseek(self->fp,ofs);
                            // save offset in case this is the one (reverse)
                            sync_offset = ofs;
                        }
                        if( (br=iow_fread(self->fp,&cur,1))==1 && cur==(DIR_IS_FWD(flags) ? sync[3] : sync[0])){
                            state=ST_SYNC_VALID;
                        }else{
                            if(br==0 || iow_ferror(self->fp) || (iow_feof(self->fp) && DIR_IS_FWD(flags)) || iow_ftell(self->fp)==0){
                                break;
                            }
                            if(NULL!=r_sync)(*r_sync)+=4;
                        }
                    }else{
                        if(br==0 || iow_ferror(self->fp) || (iow_feof(self->fp) && DIR_IS_FWD(flags)) || iow_ftell(self->fp)==0){
                            break;
                        }
                        if(NULL!=r_sync)(*r_sync)+=3;
                    }
                }else{
                    if(br==0 || iow_ferror(self->fp) || (iow_feof(self->fp) && DIR_IS_FWD(flags)) || iow_ftell(self->fp)==0){
                        break;
                    }
                    if(NULL!=r_sync)(*r_sync)+=2;
                }
            }else{
//               OUT_ERR("br[%"FMT_SZ"] cur[%02X] ferr[%d] feof[%d] fp[%"FMT_U32"]\r\n",br,cur,iow_ferror(self->fp),iow_feof(self->fp),iow_ftell(self->fp));
                if(br==0 || iow_ferror(self->fp) || (iow_feof(self->fp) && DIR_IS_FWD(flags)) || iow_ftell(self->fp)==0){
                    break;
                }
                if(NULL!=r_sync)(*r_sync)+=1;
            }

            if(DIR_IS_REV(flags)){
                if(ofs<3){
                    iow_fseek(self->fp,0);
                    break;
                }
                // if the sync start is the appears during
                // the middle of the sequence, start there
                // instead of backing up
                if(cur!=sync[3]){
                    ofs-=1;
                }
                iow_fseek(self->fp,ofs);
//                DPRINT("ofs[%4"FMT_U32"] fp[%4lu] st[%d] fr[%d] br[%u] cur[%02X]\r\n",ofs,iow_ftell(self->fp),state,fr,br,cur);
            }
            if(DIR_IS_FWD(flags) && cur==sync[0]){
                ofs-=1;
                iow_fseek(self->fp,ofs);
            }
//            OUT_ERR("state[%d] cur[%02X] fp[%"FMT_U32"]\r\n",state,cur,iow_ftell(self->fp));
        }
//        OUT_ERR("sync status: state[%d] fwd[%c] cur[%02X] br[%"FMT_SZ"] fp(%"FMT_U32"/%"FMT_U32")\r\n",state,BOOLCHAR(fwd),cur,br,iow_ftell(self->fp),iow_fsize(self->fp));
        if(state==ST_SYNC_VALID){
            *r_ofs = sync_offset;
            retval=0;
        }else if(iow_feof(self->fp) ){
            //           OUT_ERR(">>>>sync reached EOF\r\n");
            iow_clearerr(self->fp);
            retval=-2;
        }else if(iow_ftell(self->fp)== (uint32_t)0 ){
            //           OUT_ERR(">>>>sync reached SOF\r\n");
            retval=-3;
        }else{
            // IO/fsys error
            if(iow_ftell(self->fp)<iow_fsize(self->fp))
                OUT_ERR("%s: ERR   sync read state[%d] cur[%02X] iow_errno[%d] br[%"FMT_SZ"] fp(%"FMT_U32"/%"FMT_U32")\r\n",__func__,state,cur,iow_errno,br,iow_ftell(self->fp),iow_fsize(self->fp));
            retval=-4;
        }

    }// else invalid argument

    XPRINT(0xF000000FE,"%s: EXIT  fp[%"FMT_U32"] s_ofs[%"FMT_U32"] r_ofs[%"FMT_U32"] ret[%d]\r\n",__func__,iow_ftell(self->fp),sync_offset,*r_ofs,retval);

    return retval;
}

int limpl_next(log_impl_t *self, log_ofs_t *r_ofs)
{
    int retval=-1;
    if(NULL!=self && NULL!=r_ofs){

        if(self->sync(self,(DIR_FLG_FWD|OFS_FLG_REL),r_ofs,NULL)==0){
            retval=limpl_fseek(self,*r_ofs);
        }else{
            if(limpl_ftell(self)>=self->cur[CUR_LAST]){
                // park at EOF if done
                // (parking at last record causes sticking problems
                // reversing UP from last)
                limpl_fseek(self,self->cur[CUR_EOF]);
            }
        }// sync error
    }
    return retval;
}

int limpl_seek(log_impl_t *self, lutil_opflags_t flags, uint32_t start, uint32_t count, log_ofs_t *r_ofs, uint32_t *r_count)
{
    int retval=-1;
    int serr=0;

    XPRINT(0x6,"%s: ENTER fp[%p/%"FMT_U32"] dir[%c] ofs[%c] start[%"FMT_U32"] count[%"FMT_U32"] flags[%08"FMT_X32"]\r\n",__func__,self->fp,iow_ftell(self->fp),(DIR_IS_FWD(flags)?'F':'R'),(OFS_IS_ABS(flags)?'A':'R'),start,count,flags);

    if(NULL!=self && NULL!=self->fp && NULL!=r_ofs ){
        bool found_start=false;
        uint32_t test_count=0;
        uint32_t start_ofs=0;
        uint32_t save_ofs=iow_ftell(self->fp);
//        XPRINT(0x6,"%s:%d save_ofs[%"FMT_U32"] ofs[%c]\r\n",__func__,__LINE__,save_ofs,(OFS_IS_ABS(flags)?'A':'R'));

        // find start record
        // if start>=0 start at beginning
        if(OFS_IS_ABS(flags)){
            limpl_head(self);
            if( (serr=self->traverse(self, flags, start, &start_ofs, &test_count))==0){
                found_start=true;
            }else{
                XTRACE(0xE);
                retval=serr;
            }
        }else{
            // otherwise relative to current position
            XPRINT(0xE,"%s: CURPOS fp[%"FMT_U32"] dir[%c] start_ofs[%"FMT_U32"] serr[%d]\r\n",__func__,iow_ftell(self->fp),(DIR_IS_FWD(flags)?'F':'R'),start_ofs,serr);
            if(SYNC_IS_DIS(flags)){
                XTRACE(0xE);
                start_ofs=limpl_ftell(self);
                found_start=true;
            }else if((serr=self->sync(self,flags,&start_ofs,NULL))==0){
                XTRACE(0xE);
                found_start=true;
            }else{
                XTRACE(0xE);
                retval=serr;
            }
            XPRINT(0xE,"%s: CURPOS' fp[%"FMT_U32"] start_ofs[%"FMT_U32"] serr[%d]\r\n",__func__,iow_ftell(self->fp),start_ofs,serr);
        }
        XPRINT(0x6,"%s:%d found_start[%c] save_ofs[%"FMT_U32"] start_ofs[%"FMT_U32"]\r\n",__func__,__LINE__,BOOLCHAR(found_start), save_ofs,start_ofs);

        if(found_start ){
            if(count==0){
                if(NULL!=r_ofs)*r_ofs=start_ofs;
                if(NULL!=r_count)*r_count=0;
                retval=0;
                iow_fseek(self->fp,start_ofs);
            }else if(count>0){
                // seek until count or end of file
                iow_fseek(self->fp,start_ofs);

//                XPRINT(0x6,"%s:%d BEFORE TRAVERSE fp[%"FMT_U32"]\r\n",__func__,__LINE__,limpl_ftell(self));
                uint32_t count_adj = count;
                save_ofs=start_ofs;
                test_count=0;
                start_ofs=0;

                if( (serr=self->traverse(self, flags, count_adj, &start_ofs, &test_count))==0){
//                    XPRINT(0x6,"%s:%d AFTER TRAVERSE fp[%"FMT_U32"]\r\n",__func__,__LINE__,limpl_ftell(self));
                    // success
                    if(NULL!=r_ofs)*r_ofs=start_ofs;
                    if(NULL!=r_count)*r_count=test_count;
                    retval=0;
                    iow_fseek(self->fp,start_ofs);
                }else{
                    retval = serr;
                }
            }// else already done
        }else{
            // start not found
            XTRACE(0xE);
        }

        if(retval!=0){
            // restore original file pointer if error
            if(DIR_IS_FWD(flags)){
                XTRACE(0xE);
                limpl_tail(self);
            }else{
                XTRACE(0xE);
                limpl_head(self);
            }
        }
    }else{
        // invalid arg
        XTRACE(0xE);
    }

    XPRINT(0x6,"%s: EXIT fp[%"FMT_U32"] r_ofs[%"FMT_U32"] serr[%d] ret[%d]\r\n",__func__,iow_ftell(self->fp),(NULL!=r_ofs?*r_ofs:-1),serr,retval);

    return retval;
}

int limpl_search(log_impl_t *self, lutil_opflags_t flags, limpl_search_def_t *search, log_ofs_t *r_ofs)
{
    int retval=-1;

    if((g_xprint&0xE)!=0){
        limpl_search_def_show(search);
    }

    bool match_found=false;

    if(NULL!=self && NULL!=self->fp && NULL!=search && self->buf_len>0){

        int inchar=0;
        if(search->type==ST_IDX){
            // index search
            cmp_val_t cval[3];
            while(search->op_id!=OP_INVALID && !match_found){
                OFS_SET_REL(&flags);
//                OUT_CTTY(">>>> CMP SEEK fp[%"FMT_U32"]\r\n",limpl_ftell(self));
                if(self->sync(self,flags,r_ofs,NULL)==0){
                    cval[0].u32=search->val[0].u32;
                    cval[1].u32=search->val[1].u32;
                    cval[2].u32=search->idx;
                    if(DIR_IS_FWD(flags)){
                        if(search->idx < 0xFFFFFFFF){
                            search->idx++;
                        }else{
                            break;
                        }
                    }else{
                        if(search->idx > 0){
                            search->idx--;
                        }else{
                            break;
                        }
                    }
//                    OUT_CTTY(">>>> CMP[%s] RNG[%"FMT_U32"/%"FMT_U32"] IDX[%"FMT_U32"]<<<<<\r\n",lutil_cmp_ops[op], search->val[0].u32,search->val[1].u32,search->idx);
                    if(s_cmp_type(search->op_id,CTYPE_U32,cval)==0){
//                        OUT_CTTY(">>>> MATCH IDX[%"FMT_U32"]<<<<<\r\n",search->idx);
                        iow_fseek(self->fp,*r_ofs);
                        match_found=true;
                       break;
                    }
                }else{
                    // r_ofs is beginning of record read
                    // if first/last records read, quit
                    if(*r_ofs>=self->cur[CUR_LAST] || *r_ofs<=self->cur[CUR_FIRST]){
                        // reached end of file
                        OUT_CTTY("%s:end of file\r\n",__func__);
                        break;
                    }
                }

                inchar=0;
                inchar=iow_get_char(false);
                if(inchar==CTRL_D)break;

           }// while !match
        }else{
            // non-index search
            while(!match_found){
                OFS_SET_REL(&flags);
                if(self->seek(self,flags,LOG_OFS_INVALID,1,r_ofs,NULL)==0){
                    byte buf[self->buf_len];
                    ZERO_MEM(buf,self->buf_len);
                    uint32_t r_count=0;
                    int test=self->read(self, buf, self->buf_len, 1, &r_count);
                    if(test==0 && r_count==1){
                        if(self->compare(buf,search)==0){
                            match_found=true;
                            iow_fseek(self->fp,*r_ofs);
                        }else {
                            // r_ofs is beginning of record read
                            // if first/last records read, quit
                            if(*r_ofs>=self->cur[CUR_LAST] || *r_ofs<=self->cur[CUR_FIRST]){
                                // reached end of file
                                OUT_CTTY("%s:end of file\r\n",__func__);
                                break;
                            }
                        }
                    }else{
                        // read error
                        XTRACE(0xE);
                        break;
                    }
                }else{
                    // seek error
                    XTRACE(0xE);
                    break;
                }
                // r_ofs is beginning of record read
                // if first/last records read, quit
                if(*r_ofs>=self->cur[CUR_LAST] || *r_ofs<=self->cur[CUR_FIRST]){
                    // reached end of file
                    OUT_CTTY("%s:end of file\r\n",__func__);
                    break;
                }
                inchar=0;
                inchar=iow_get_char(false);
                if(inchar==CTRL_D)break;
            }// while
        }// non-idx search

        if(match_found)
            retval=0;

    }// invalid arg
    return retval;
}

int limpl_nshow(log_impl_t *self, int indent, lutil_opts_t *opts)
{
    int retval=-1;

    if(NULL!=self){
        uint32_t r_count=0;
        uint32_t r_ofs=0;
        uint32_t count_rem=opts->count;

        if( (g_xprint&0xFE)!=0){
            lutil_show_opts(opts);
	    }

        if(opts->last_n){
            // seek to nth from end
            iow_fseek(self->fp,self->cur[CUR_LAST]);

            self->seek(self, (OFS_FLG_REL|DIR_FLG_REV|SYN_FLG_NOSYNC), LOG_OFS_INVALID, opts->count-1, &r_ofs, &r_count);
        }else{
            // find start record
            iow_fseek(self->fp,self->cur[CUR_FIRST]);
            self->seek(self, (DIR_FLG_FWD|OFS_FLG_REL), LOG_OFS_INVALID, opts->start+1, &r_ofs, &r_count);
        }

        int inchar=0;
        while(count_rem>0){
            if(FILT_IS_EN(opts->opflags)){
                byte buf[self->buf_len];
                ZERO_MEM(buf,self->buf_len);
                uint32_t r_count=0;
                int test=self->read(self, buf, self->buf_len, 1, &r_count);
                if(test==0 && r_count==1){
                    if(self->compare(buf,&opts->search)==0){
                        self->bshow(self,buf,indent,opts);
                        OUT_CTTY("\r\n");
                        limpl_next(self,&r_ofs);
                        count_rem--;
                    }else {
                        limpl_next(self,&r_ofs);
                        // r_ofs is beginning of record read
                        // if first/last records read, quit
                        if(r_ofs>self->cur[CUR_LAST] || r_ofs<self->cur[CUR_FIRST]){
                            // reached end of file
                            OUT_CTTY("%s:end of file\r\n",__func__);
                            break;
                        }
                    }
                }else{
                    // read error
                    XTRACE(0xE);
                    break;
                }

            }else if(self->show(self,indent,opts)==0){
                //OUT_CTTY("\r\n");
                count_rem--;
                // sync to next?
            }else{
                XTRACE(0xE);
                uint32_t pos = iow_ftell(self->fp);
                if( (pos > self->cur[CUR_LAST]) || (pos < self->cur[CUR_FIRST])){
                    // reached end of file
                    retval=0;
                }
                break;
            }
            inchar=0;
            inchar=iow_get_char(false);
            if(inchar==CTRL_D)break;
        }// while
        if(count_rem<=0)retval=0;

    }else{TRACE();}// invalid arg
    return retval;
}

static void s_show_nav(lless_nav_t *self)
{
    if(NULL!=self){
        int wkey=12;
        int wval=16;
        OUT_CTTY("%*s %*s\r\n",wkey,"type",wval,lless_nav_names[self->type]);
        OUT_CTTY("%*s %*s\r\n",wkey,"dir",wval,lutil_opflag_names[ (DIR_IS_FWD(self->flags)?1:0)]);
        OUT_CTTY("%*s %*"FMT_U32"\r\n",wkey,"start",wval,self->start);
        OUT_CTTY("%*s %*"FMT_U32"\r\n",wkey,"count",wval,self->count);
        OUT_CTTY("%*s %*c\r\n",wkey,"wrap",wval,BOOLCHAR(self->wrap));
    }
}

static char *s_trim_leading(char *src)
{
    char *retval=NULL;
    if(NULL!=src){
        char *ph=src;
        // trim leading whitespace
        while(isspace(*ph) && *ph!='\0')ph++;
        retval=ph;
    }

    return retval;
}

static char *s_trim_trailing(char *src)
{
    char *retval=NULL;
    if(NULL!=src){
        char *pt=src+strlen(src);

        // trim trailing whitespace
        while(pt>src && isspace(*(pt-1)) ){
            *(pt-1)='\0';
            pt--;
        }
        retval=src;
    }

    return retval;
}

static char *s_trim(char *src)
{
    char *retval=NULL;

    if(NULL!=src){
        retval=s_trim_leading(s_trim_trailing(src));
    }

    return retval;
}

//////////////////////////////////////
// lless util implementation
//////////////////////////////////////

int limpl_lless(log_impl_t *impl, lutil_opts_t *opts)
{
    return_code_t retval=ERR_FAILURE;

    XPRINT(0x2,"%s ENTER src[%s] type[%d] opts[%p]\r\n",__func__,opts->src,opts->src_type,opts);
    int serr=0;

    if( (serr=limpl_head(impl))==0){

        enum {HELP=0,WAIT, QUIT, MOVE, PREV, NEXT, HEAD, TAIL, CMD, SEARCH, STATE_COUNT}state;
        byte ctrl=0;

        char ibuf[64]={0};
        uint64_t search_time[2]={0LL,0LL};
        limpl_search_def_t search_s={
            ST_INVALID,
            CTYPE_NONE,OP_INVALID,
            0,
            {{0},{0}}
        },*search=&search_s;
        struct tm tm_iso[2];
        int gmt_ofs=-8;
        char *ip=ibuf;

        // initialize state machine parameters
        bool init=false;
        state=HELP;

        lless_nav_t nav[2]={
            {NAV_NONE,DIR_FLG_FWD|OFS_FLG_REL,LOG_OFS_INVALID,0,false},
            {NAV_NONE,DIR_FLG_FWD|OFS_FLG_REL,LOG_OFS_INVALID,0,false}
        };
        lless_nav_t *nav_cur=&nav[0];//,*nav_save=&nav[1];

        // enter state machine:
        // - compute offset (modified for some states/operations)
        // - output text
        // - get input
        // - update state
        while(state!=QUIT && !g_logutils_interrupt){

            XPRINT(0x10000000,">>>> state [%d/%s]\r\n",state,(state>0&&state<STATE_COUNT?lless_state_names[state]:"INVALID"));
            ////////////////////////////////
            // adjust navigation per state
            if(state==HEAD){
                nav_cur->type=NAV_HEAD;
            }else if(state==TAIL){
                nav_cur->type=NAV_TAIL;
            }else if(state==PREV){
                nav_cur->type=NAV_REL;
                DIR_SET_REV(&nav_cur->flags);
                OFS_SET_REL(&nav_cur->flags);
                nav_cur->count =  1;
            }else if(state==NEXT){
                nav_cur->type=NAV_REL;
                DIR_SET_FWD(&nav_cur->flags);
                OFS_SET_REL(&nav_cur->flags);
                nav_cur->count =  1;
            }else if(state==MOVE){
                // otherwise, use move parameters
                XPRINT(0x10000000,"MOVE fp[%"FMT_U32"]\r\n",limpl_ftell(impl));
            }else if(state==SEARCH){
                nav_cur->type=NAV_REL;
                nav_cur->count =  0;
            }else if(state==HELP){
                OUT_CTTY("            Command Summary\r\n\r\n");
                OUT_CTTY("  Movement  :\r\n");
                OUT_CTTY("    First/Last [ g <    ] / [ G >  ]\r\n");
                OUT_CTTY("    Next/Prev  [ dD<CR> ] / [ uU   ]\r\n");
                OUT_CTTY("    This       [ <SPC>  ]\r\n");
                OUT_CTTY("    Jump +/-   [ j<n>   ] / [ k<n> ]\r\n");
                OUT_CTTY("\r\n");
                OUT_CTTY("  Session   :\r\n");
                OUT_CTTY("    Help [h H ?]   Quit [q Q x X]\r\n");
                OUT_CTTY("\r\n");
                OUT_CTTY("  Command   :\r\n");
                OUT_CTTY("    Enter cmd mode   [ / ]\r\n");
                OUT_CTTY("    Search -\r\n");
                OUT_CTTY("      gmt:<offset_h>\r\n");
                OUT_CTTY("      te:epoch_msec ti:iso1806\r\n");
                OUT_CTTY("      rt:rtid rm:rmid\r\n");
                OUT_CTTY("      mm:mmid\r\n");
                OUT_CTTY("      sn:sequence_n\r\n");
                OUT_CTTY("      cyc:cycle_id\r\n");
                OUT_CTTY("      esn:ensemble_n\r\n");
                OUT_CTTY("      msn:meas_n\r\n");
                OUT_CTTY("      idx:record index\r\n");
                OUT_CTTY("      use: <type><op><x>[,<y>]\r\n");
                OUT_CTTY("      where:\r\n");
                OUT_CTTY("      <type> : search type (te, ti, etc.)\r\n");
                OUT_CTTY("        <op> : operator [==, !=, > ,< , >=, <=, <>, ><]\r\n");
                OUT_CTTY("       <x,y> : value or range (x: low, y:high)\r\n");
                OUT_CTTY("      Next match [n]\r\n");
                OUT_CTTY("      Prev match [N]\r\n");
                OUT_CTTY("    Output -\r\n");
                OUT_CTTY("      out:[pbcrx[x*]hdv]\r\n");
                OUT_CTTY("      dbg:<32-bit hex mask>\r\n");
                OUT_CTTY("\r\n");

                if(!init){
                    init=true;
                    state=HEAD;
                    nav_cur->type=NAV_HEAD;
                }
            }

            ////////////////////////////////
            // Execute navigation
            // Output record if found

            if(state!=WAIT && state!=HELP){
                XPRINT(0x10000000,">>>> NAVIGATION [%d/%s]\r\n",nav_cur->type,(nav_cur->type>0&&nav_cur->type<NAV_COUNT?lless_nav_names[nav_cur->type]:"INVALID"));

                if(g_xprint&0x10000020)
                    s_show_nav(nav_cur);

                uint32_t r_ofs=0;
                int serr = 0;
                // go to new nav
                switch (nav_cur->type) {
                    case NAV_TAIL:
                        limpl_tail(impl);
                        break;
                    case NAV_HEAD:
                        limpl_head(impl);
                        break;
                    case NAV_ABS:
                        OFS_SET_ABS(&nav_cur->flags);
                        impl->seek(impl,nav_cur->flags,nav_cur->start,nav_cur->count,&r_ofs,NULL);
                        break;
                    case NAV_REL:
                        if(state==MOVE || state==NEXT || state==PREV){
                            XPRINT(0x10000000,"NAV_REL fp[%"FMT_U32"]\r\n",limpl_ftell(impl));

                            if(nav_cur->count==0){
                                // restore last output
                                limpl_fseek(impl,impl->cur[CUR_SAVE]);
                            }else if( (serr=impl->seek(impl,nav_cur->flags,LOG_OFS_INVALID,nav_cur->count,&r_ofs,NULL))!=0){
                                // use nav config to seek
                                switch(serr){
                                    case -1:
                                        TRACE();
                                        break;
                                    case -2:
                                        // EOF
                                        if(DIR_IS_FWD(nav_cur->flags)){
                                            OUT_ERR("end of file\r\n");
                                        }else{
                                            OUT_ERR("start of file\r\n");
                                        }
                                        break;
                                    case -3:
                                        // start of file
                                        if(DIR_IS_FWD(nav_cur->flags)){
                                            OUT_ERR("end of file\r\n");
                                        }else{
                                            OUT_ERR("start of file\r\n");
                                        }
                                        break;
                                    case -4:
                                        // IO error
                                        OUT_ERR("io error [%d]\r\n",iow_errno);
                                        break;
                                }
                                state=WAIT;
                            }
                        }else if(state==SEARCH){
                            if(NULL!=search ){
                                if(impl->search(impl, nav_cur->flags, search, &r_ofs)!=0){
                                    // don't show if search failed
                                    state=WAIT;
                                    TRACE();
                                }else{
                                    OUT_CTTY("not found\r\n");
                                }
                            }else{
                                // search not valid
                                state=WAIT;
                            }
                        }
                        break;

                    default:
                        OUT_CTTY("ERR - INVALID NAV TYPE\r\n");
                        break;
                }

                if(state!=WAIT){
                    uint32_t cur_save=limpl_ftell(impl);
                    if(impl->show(impl,5,opts)==0){
                        impl->cur[CUR_SAVE]=cur_save;
                    }
                    OUT_CTTY("\r\n");
                }
                // save nav
                memcpy(&nav[1],&nav[0],sizeof(lless_nav_t));
            }


            ////////////////////////////////
            // Get input (blocks)
            int inchar=0;
            while((inchar=iow_get_char(false))==0 && !g_logutils_interrupt ){
                iow_delay_msec(500);
            }
            ctrl=inchar;
            //                OUT_CTTY(" - ctrl[%02X/%c] intr[%c]\r\n",ctrl,ctrl,BOOLCHAR(g_logutils_interrupt));

            ////////////////////////////////
            // Update state
            state=WAIT;
            nav_cur->count=0;
            DIR_SET_FWD(&nav_cur->flags);
            char *ep=NULL;
            int n_args=0;
            uint32_t dbg_mask=0;
            switch(ctrl){
                case 'j':
                case 'J':
                case 'k':
                case 'K':
                    OUT_CTTY("\b%c",ctrl);
                    ZERO_MEM(ibuf,IOF_SRCH_MAX_BYTES);
                    iow_get_line(ibuf,IOF_SRCH_MAX_BYTES);
                    nav_cur->count=1;
                    nav_cur->type=NAV_REL;
                    OFS_SET_REL(&nav_cur->flags);
                    DIR_SET_FWD(&nav_cur->flags);
                    sscanf(ibuf,"%"FMT_U32"",&nav_cur->count);
                    if(ctrl=='k'||ctrl=='K'){
                        DIR_SET_REV(&nav_cur->flags);
                    }
                    state=MOVE;
                    break;
                case 'U':
                case 'u':
                    nav_cur->count=1;
                    OFS_SET_REL(&nav_cur->flags);
                    DIR_SET_REV(&nav_cur->flags);
                    state=PREV;
                    break;
                case 'D':
                case 'd':
                case '\r':
                case '\n':
                    nav_cur->count=1;
                    DIR_SET_FWD(&nav_cur->flags);
                    OFS_SET_REL(&nav_cur->flags);
                    state=NEXT;
                    break;
                case '<':
                case 'g':
                    state=HEAD;
                    break;
                case '>':
                case 'G':
                    state=TAIL;
                    break;
                case ' ':
                    nav_cur->count=0;
                    DIR_SET_FWD(&nav_cur->flags);
                    OFS_SET_REL(&nav_cur->flags);
                    state=MOVE;
                    break;
                case '/':
                    state=CMD;
                    OUT_CTTY("\b/");
                    ZERO_MEM(ibuf,IOF_SRCH_MAX_BYTES);
                    iow_get_line(ibuf,IOF_SRCH_MAX_BYTES);

                    if((ip=strstr(ibuf,"te"))!=NULL){
                        ep=ip+2;
                        while(*ep!='\0' && (*ep=='<' || *ep=='>' || *ep=='=' || *ep=='!') )ep++;
                        n_args=sscanf(ep,"%"FMT_U64",%"FMT_U64"",&search_time[0],&search_time[1]);
                        if(n_args>0){
                            DIR_SET_FWD(&nav_cur->flags);
                            search->op_id=limpl_cmp_parse_op(ip);
                            search->cmp_type=CTYPE_U64;
                            search->type=ST_TIME;
                            search->val[0].u64=search_time[0];
                            search->val[1].u64=search_time[1];
                            state=SEARCH;
                        }
                    }else if((ip=strstr(ibuf,"ti"))!=NULL){
                        ep=ip+2;
                        while(*ep!='\0' && (*ep=='<' || *ep=='>' || *ep=='=' || *ep=='!') )ep++;
                        n_args=sscanf(ep,"%d-%d-%dT%d:%d:%d,%d-%d-%dT%d:%d:%d",
                                         &tm_iso[0].tm_year, &tm_iso[0].tm_mon,
                                         &tm_iso[0].tm_mday, &tm_iso[0].tm_hour,
                                         &tm_iso[0].tm_min, &tm_iso[0].tm_sec,
                                         &tm_iso[1].tm_year, &tm_iso[1].tm_mon,
                                         &tm_iso[1].tm_mday, &tm_iso[1].tm_hour,
                                         &tm_iso[1].tm_min, &tm_iso[1].tm_sec);
                        if(n_args==6 || n_args==12){

                            tm_iso[0].tm_year-=1900;
                            tm_iso[0].tm_mon-=1;
                            tm_iso[0].tm_hour+=gmt_ofs;

                            tm_iso[1].tm_year-=1900;
                            tm_iso[1].tm_mon-=1;
                            tm_iso[1].tm_hour+=gmt_ofs;

                            search->type=ST_TIME;
                            DIR_SET_FWD(&nav_cur->flags);
                            search->op_id=limpl_cmp_parse_op(ip);
                            search->cmp_type=CTYPE_U64;
                            search->val[0].u64=mktime(&tm_iso[0])*1000ULL;
                            search->val[1].u64=mktime(&tm_iso[1])*1000ULL;
                            OUT_CTTY("search %s -> %"FMT_U64",%"FMT_U64"\r\n",s_trim(ep),search->val[0].u64,search->val[1].u64);
                            state=SEARCH;
                        }
                    }else if((ip=strstr(ibuf,"rt"))!=NULL){
                        ep=ip+2;
                        while(*ep!='\0' && (*ep=='<' || *ep=='>' || *ep=='=' || *ep=='!') )ep++;
                        n_args=sscanf(ep,"%"FMT_U16",%"FMT_U16"",&search->val[0].u16,&search->val[1].u16);
                        if(n_args>0){
                            search->op_id=limpl_cmp_parse_op(ip);
                            search->cmp_type=CTYPE_U16;
                            search->type=ST_RTID;
                            DIR_SET_FWD(&nav_cur->flags);
                            state=SEARCH;
                        }
                    }else if((ip=strstr(ibuf,"rm"))!=NULL){
                        ep=ip+2;
                        while(*ep!='\0' && (*ep=='<' || *ep=='>' || *ep=='=' || *ep=='!') )ep++;
                        n_args=sscanf(ep,"%"FMT_U16",%"FMT_U16"",&search->val[0].u16,&search->val[1].u16);
                        if(n_args>0){
                            search->op_id=limpl_cmp_parse_op(ip);
                            search->cmp_type=CTYPE_U16;
                            search->type=ST_RMID;
                            DIR_SET_FWD(&nav_cur->flags);
                            state=SEARCH;
                        }
                    }else if((ip=strstr(ibuf,"mm"))!=NULL){
                        ep=ip+2;
                        while(*ep!='\0' && (*ep=='<' || *ep=='>' || *ep=='=' || *ep=='!') )ep++;
                        n_args=sscanf(ep,"%"FMT_U16",%"FMT_U16"",&search->val[0].u16,&search->val[1].u16);
                        if(n_args>0){
                            search->op_id=limpl_cmp_parse_op(ip);
                            search->cmp_type=CTYPE_U16;
                            search->type=ST_MMID;
                            DIR_SET_FWD(&nav_cur->flags);
                            state=SEARCH;
                        }
                    }else if((ip=strstr(ibuf,"esn"))!=NULL){
                        ep=ip+3;
                        while(*ep!='\0' && (*ep=='<' || *ep=='>' || *ep=='=' || *ep=='!') )ep++;
                        n_args=sscanf(ep,"%"FMT_U16",%"FMT_U16"",&search->val[0].u16,&search->val[1].u16);
                        if(n_args>0){
                            search->op_id=limpl_cmp_parse_op(ip);
                            search->cmp_type=CTYPE_U16;
                            search->type=ST_ESN;
                            DIR_SET_FWD(&nav_cur->flags);
                            state=SEARCH;
                        }
                    }else if((ip=strstr(ibuf,"msn"))!=NULL){
                        ep=ip+3;
                        while(*ep!='\0' && (*ep=='<' || *ep=='>' || *ep=='=' || *ep=='!') )ep++;
                        n_args=sscanf(ep,"%"FMT_U16",%"FMT_U16"",&search->val[0].u16,&search->val[1].u16);
                        if(n_args>0){
                            search->op_id=limpl_cmp_parse_op(ip);
                            search->cmp_type=CTYPE_U16;
                            search->type=ST_MSN;
                            DIR_SET_FWD(&nav_cur->flags);
                            state=SEARCH;
                        }
                    }else if((ip=strstr(ibuf,"sn"))!=NULL){
                        ep=ip+2;
                        while(*ep!='\0' && (*ep=='<' || *ep=='>' || *ep=='=' || *ep=='!') )ep++;
                        n_args=sscanf(ep,"%"FMT_U32",%"FMT_U32"",&search->val[0].u32,&search->val[1].u32);
                        if(n_args>0){
                            search->op_id=limpl_cmp_parse_op(ip);
                            search->cmp_type=CTYPE_U32;
                            search->type=ST_SEQN;
                            DIR_SET_FWD(&nav_cur->flags);
                            state=SEARCH;
                        }
                    }else if((ip=strstr(ibuf,"cyc"))!=NULL){
                        ep=ip+2;
                        while(*ep!='\0' && (*ep=='<' || *ep=='>' || *ep=='=' || *ep=='!') )ep++;
                        n_args=sscanf(ep,"%"FMT_U32",%"FMT_U32"",&search->val[0].u32,&search->val[1].u32);
                        if(n_args>0){
                            search->op_id=limpl_cmp_parse_op(ip);
                            search->cmp_type=CTYPE_U32;
                            search->type=ST_CYCN;
                            DIR_SET_FWD(&nav_cur->flags);
                            state=SEARCH;
                        }
                    }else if((ip=strstr(ibuf,"idx"))!=NULL){
                        ep=ip+3;
                        while(*ep!='\0' && (*ep=='<' || *ep=='>' || *ep=='=' || *ep=='!') )ep++;
                        n_args=sscanf(ep,"%"FMT_U32",%"FMT_U32"",&search->val[0].u32,&search->val[1].u32);
                        if(n_args>0){
                            search->op_id=limpl_cmp_parse_op(ip);
                            search->cmp_type=CTYPE_U32;
                            search->type=ST_IDX;
                            DIR_SET_FWD(&nav_cur->flags);
                            search->idx=0;
                            limpl_fseek(impl,0);
                            state=SEARCH;
                       }
                    }else if((ip=strstr(ibuf,"gmt:"))!=NULL){
                        if(sscanf(ip+4,"%d",&gmt_ofs)==1){
                            gmt_ofs%=24;
                            OUT_CTTY("gmt_ofs=%d\r\n",gmt_ofs);
                        }else{
                            OUT_CTTY("gmt_ofs invalid [%s] (use int)\r\n",s_trim(ip+4));
                        }
                    }else if((ip=strstr(ibuf,"dbg"))!=NULL){
                        if(sscanf(ip+3,"=%"FMT_X32"",&dbg_mask)==1){
                            SET_XPRINT(dbg_mask);
                        }else if(sscanf(ip+3,"+%"FMT_X32"",&dbg_mask)==1){
                            g_xprint |= dbg_mask;
                        }else if(sscanf(ip+3,"-%"FMT_X32"",&dbg_mask)==1){
                            g_xprint &= ~(dbg_mask);
                        }
                        OUT_CTTY("dbg mask=%"FMT_X32"\r\n",g_xprint);
                    }else if((ip=strstr(ibuf,"out:"))!=NULL){
                        // parse options into temp config
                        lutil_opts_t tmp_cfg={0};
                        tmp_cfg.oflags=QOF_NONE;
                        char **tmp_opts = &ip;
                        limpl_parse_lutil_opts(&tmp_cfg,1,tmp_opts);

                        // if flags set, transfer them to active config
                        if(tmp_cfg.oflags!=QOF_NONE){
                            opts->oflags = tmp_cfg.oflags;
                        }
                        state=WAIT;
                    }
                    // if no valid cmd, return to WAIT
                    if(state==CMD)
                        state=WAIT;
                    break;

                case 'N':
                    DIR_SET_REV(&nav_cur->flags);
                    if(limpl_ftell(impl)==0){
                        limpl_tail(impl);
                    }
                    state=SEARCH;
                    break;
                case 'n':
                    DIR_SET_FWD(&nav_cur->flags);
                    if(limpl_feof(impl)){
                        limpl_head(impl);
                    }
                    state=SEARCH;
                    break;
                case 'Q':
                case 'q':
                case 'X':
                case 'x':
                    if(state==SEARCH){
                        state=WAIT;
                    }else{
                        state=QUIT;
                    }
                    break;
                case 'H':
                case 'h':
                case '?':
                    state=HELP;
                    break;
            }
        }// while (state machine)
        retval = ERR_SUCCESS;
    }else{
        OUT_CTTY("%s - ERR seek ret[%d]\r\n",__func__,serr);
    }
    OUT_CTTY("\r\n");
    return retval;
}

//////////////////////////////////////
// data queue util implementation
//////////////////////////////////////
#ifdef WITH_DQUTIL

static int s_dq_add(log_impl_t *impl,dqutil_opts_t *cfg)
{
    int retval=-1;

    OUT_CTTY("%s - not implemented\r\n",__func__);

    // open dest file (append)

    // go to end of dest file

    // go to source start record

    // read source record

    // format output

    // write to dest

    return retval;
}

static int s_dq_del(log_impl_t *impl,dqutil_opts_t *cfg)
{
    int retval=-1;
    OUT_CTTY("%s - not implemented\r\n",__func__);
    return retval;
}

static int s_dq_move(log_impl_t *impl,dqutil_opts_t *cfg)
{
    int retval=-1;
    OUT_CTTY("%s - not implemented\r\n",__func__);
    return retval;
}

static int s_dq_show(log_impl_t *impl,dqutil_opts_t *cfg)
{
    int retval=-1;
    OUT_CTTY("%s - not implemented\r\n",__func__);
    return retval;
}

static int s_dq_inspect(log_impl_t *impl,dqutil_opts_t *cfg)
{
    int retval=-1;
    OUT_CTTY("%s - not implemented\r\n",__func__);
    return retval;
}

int limpl_dqutil(log_impl_t *impl, dqutil_opts_t *opts)
{
    int retval=-1;
    if(NULL!=impl && NULL!=opts){
        if(opts->cmd==DQC_SHOW){
            retval=s_dq_show(impl, opts);
        }else if(opts->cmd==DQC_ADD){
            retval=s_dq_add(impl, opts);
        }else if(opts->cmd==DQC_DEL){
            retval=s_dq_del(impl, opts);
        }else if(opts->cmd==DQC_MOVE){
            retval=s_dq_move(impl, opts);
        }else if(opts->cmd==DQC_INSPECT){
            retval=s_dq_inspect(impl, opts);
        }// else invalid
    }// invalid arg
    return retval;
}

void dqutil_show_opts(dqutil_opts_t *self)
{
    if(NULL!=self){
        int wkey=12;
        int wval=16;

        OUT_CTTY("%*s %*s\r\n",wkey,"cmd",wval,dqutil_cmd_names[self->cmd]);
        OUT_CTTY("%*s %*c\r\n",wkey,"lastn",wval,BOOLCHAR(self->last_n));
        OUT_CTTY("%*s %*"FMT_U32"\r\n",wkey,"start",wval,self->start);
        OUT_CTTY("%*s %*"FMT_U32"\r\n",wkey,"count",wval,self->count);
        OUT_CTTY("%*s %*"FMT_U32"\r\n",wkey,"index",wval,self->index);
        OUT_CTTY("%*s %*c\r\n",wkey,"idx_rel",wval,BOOLCHAR(self->idx_rel));
        OUT_CTTY("%*s %*s%08"FMT_X32"\r\n",wkey,"oflags",wval-8,"x",self->oflags);
        OUT_CTTY("%*s %*s\r\n",wkey,"src",wval,self->src);
        OUT_CTTY("%*s %*s\r\n",wkey,"dest",wval,self->dest);
        OUT_CTTY("%*s %*s\r\n",wkey,"src_type",wval,lutil_src_names[self->src_type]);
    }
}

int limpl_parse_dqutil_opts(dqutil_opts_t *cfg, int opt_n, char **opts)
{
    int retval=-1;
    if(NULL!=cfg && NULL!=opts){
        char **opt=opts;
        int i=0;
        queue_output_flag tmp_oflags=0x0;
        struct tm tm_iso[2];
        int gmt_ofs=-8;
        int n_args=0;

        limpl_search_def_t *search=&cfg->search;
        while(i<opt_n){
            retval=0;
            XPRINT(0x10000000,"opt[%s]\r\n",*opt);
            char *ap=NULL,*ep=NULL,*ip=NULL;
            uint32_t u32[2]={0,0};
            if(strcmp(*opt,"z")==0 || strcmp(*opt,"Z")==0){
                cfg->src_type=LU_ZBIN;
            }else if(strcmp(*opt,"o")==0 || strcmp(*opt,"O")==0){
                cfg->src_type=LU_OBIN;
            }else if(strcmp(*opt,"h")==0 || strcmp(*opt,"H")==0){
                retval=1;
                break;
            }else if((ip=strstr(*opt,"dbg"))!=NULL || (ip=strstr(*opt,"DBG"))!=NULL){
                if(sscanf(ip+3,"=%"FMT_X32"",&u32[0])==1){
                    SET_XPRINT(u32[0]);
                }else if(sscanf(ip+3,"+%"FMT_X32"",&u32[0])==1){
                    g_xprint |= u32[0];
                }else if(sscanf(ip+3,"-%"FMT_X32"",&u32[0])==1){
                    g_xprint &= ~(u32[0]);
                }else if(sscanf(ip+4,"%"FMT_X32"",&u32[0])==1){
                    SET_XPRINT(u32[0]);
                }
                XPRINT(0xFE,"dbg mask=%"FMT_X32"\r\n",g_xprint);
            }else if( (ap=strstr(*opt,"out:"))!=NULL){
                ap+=4;
                if(strstr(ap,"b")!=NULL || strstr(ap,"B")!=NULL)
                    tmp_oflags|=QOF_BASE85;
                if(strstr(ap,"d")!=NULL || strstr(ap,"D")!=NULL)
                    tmp_oflags|=QOF_DATA|QOF_PRETTY;
                if(strstr(ap,"r")!=NULL || strstr(ap,"R")!=NULL){
                    tmp_oflags|=QOF_RAW;
                }
                if(strstr(ap,"c")!=NULL || strstr(ap,"C")!=NULL){
                    tmp_oflags|=QOF_CSV;
                }
                if(strstr(ap,"h")!=NULL || strstr(ap,"H")!=NULL){
                    tmp_oflags|=QOF_HEADER|QOF_PRETTY;
                }
                if(strstr(ap,"p")!=NULL || strstr(ap,"P")!=NULL){
                    tmp_oflags|=QOF_PRETTY;
                }
                if(strstr(ap,"v")!=NULL || strstr(ap,"V")!=NULL){
                    tmp_oflags|=QOF_VERBOSE;
                }
                if(strstr(ap,"u")!=NULL || strstr(ap,"U")!=NULL){
                    tmp_oflags|=QOF_UUENCODE;
                }
                if(strstr(ap,"x")!=NULL || strstr(ap,"X")!=NULL){
                    if(strstr(ap,"x*")!=NULL || strstr(ap,"X*")!=NULL){
                        tmp_oflags|=QOF_HEX;
                        tmp_oflags|=QOF_PRETTY_HEX;
                    }else if(strstr(ap,"xx")!=NULL || strstr(ap,"XX")!=NULL){
                        tmp_oflags|=QOF_PRETTY_HEX;
                    }else{
                        tmp_oflags|=QOF_HEX;
                    }
                }
            }else if(strcmp(*opt,"add")==0 || strcmp(*opt,"ADD")==0){
                cfg->cmd=DQC_ADD;
            }else if(strcmp(*opt,"del")==0 || strcmp(*opt,"DEL")==0){
                cfg->cmd=DQC_DEL;
            }else if(strcmp(*opt,"move")==0 || strcmp(*opt,"MOVE")==0){
                cfg->cmd=DQC_MOVE;
            }else if(strcmp(*opt,"show")==0 || strcmp(*opt,"SHOW")==0){
                cfg->cmd=DQC_SHOW;
            }else if(strcmp(*opt,"inspect")==0 || strcmp(*opt,"INSPECT")==0){
                cfg->cmd=DQC_INSPECT;
            }else if(sscanf(*opt,"s%"FMT_U32"",&u32[0])==1 ||
                     sscanf(*opt,"S%"FMT_U32"",&u32[0])==1){
                cfg->start=u32[0];
            }else if(sscanf(*opt,"c%"FMT_U32"",&u32[0])==1 ||
                     sscanf(*opt,"C%"FMT_U32"",&u32[0])==1){
                cfg->count=u32[0];
            }else if(sscanf(*opt,"l%"FMT_U32"",&u32[0])==1 ||
                     sscanf(*opt,"L%"FMT_U32"",&u32[0])==1){
                cfg->count=u32[0];
                cfg->last_n=true;
            }else if( (ip=strstr(*opt,"ti"))!=NULL || (ip=strstr(*opt,"TI"))!=NULL){
                ep=ip+2;
                while(*ep!='\0' && (*ep=='<' || *ep=='>' || *ep=='=' || *ep=='!') )ep++;
                n_args=sscanf(ep,"%d-%d-%dT%d:%d:%d,%d-%d-%dT%d:%d:%d",
                              &tm_iso[0].tm_year, &tm_iso[0].tm_mon,
                              &tm_iso[0].tm_mday, &tm_iso[0].tm_hour,
                              &tm_iso[0].tm_min, &tm_iso[0].tm_sec,
                              &tm_iso[1].tm_year, &tm_iso[1].tm_mon,
                              &tm_iso[1].tm_mday, &tm_iso[1].tm_hour,
                              &tm_iso[1].tm_min, &tm_iso[1].tm_sec);
                if(n_args==6 || n_args==12){

                    tm_iso[0].tm_year-=1900;
                    tm_iso[0].tm_mon-=1;
                    tm_iso[0].tm_hour+=gmt_ofs;

                    tm_iso[1].tm_year-=1900;
                    tm_iso[1].tm_mon-=1;
                    tm_iso[1].tm_hour+=gmt_ofs;

                    search->type=ST_TIME;
                    search->op_id=limpl_cmp_parse_op(ip);
                    search->cmp_type=CTYPE_U64;
                    search->val[0].u64=mktime(&tm_iso[0])*1000ULL;
                    search->val[1].u64=mktime(&tm_iso[1])*1000ULL;
                    FILT_SET_EN(&cfg->opflags);
                    OUT_CTTY("search %s -> %"FMT_U64",%"FMT_U64"\r\n",s_trim(ep),search->val[0].u64,search->val[1].u64);
                }
            }else if( (ip=strstr(*opt,"te"))!=NULL || (ip=strstr(*opt,"TE"))!=NULL){
                ep=ip+2;
                while(*ep!='\0' && (*ep=='<' || *ep=='>' || *ep=='=' || *ep=='!') )ep++;
                n_args=sscanf(ep,"%"FMT_U64",%"FMT_U64"",&search->val[0].u64,&search->val[1].u64);
                if(n_args>0){
                    search->op_id=limpl_cmp_parse_op(ip);
                    search->cmp_type=CTYPE_U64;
                    search->type=ST_TIME;
                    FILT_SET_EN(&cfg->opflags);
                }
            }else if((ip=strstr(*opt,"rt"))!=NULL || (ip=strstr(*opt,"RT"))!=NULL){
                ep=ip+2;
                while(*ep!='\0' && (*ep=='<' || *ep=='>' || *ep=='=' || *ep=='!') )ep++;
                n_args=sscanf(ep,"%"FMT_U16",%"FMT_U16"",&search->val[0].u16,&search->val[1].u16);
                if(n_args>0){
                    search->op_id=limpl_cmp_parse_op(ip);
                    search->cmp_type=CTYPE_U16;
                    search->type=ST_RTID;
                    FILT_SET_EN(&cfg->opflags);
                }
            }else if((ip=strstr(*opt,"rm"))!=NULL || (ip=strstr(*opt,"RM"))!=NULL){
                ep=ip+2;
                while(*ep!='\0' && (*ep=='<' || *ep=='>' || *ep=='=' || *ep=='!') )ep++;
                n_args=sscanf(ep,"%"FMT_U16",%"FMT_U16"",&search->val[0].u16,&search->val[1].u16);
                if(n_args>0){
                    search->op_id=limpl_cmp_parse_op(ip);
                    search->cmp_type=CTYPE_U16;
                    search->type=ST_RMID;
                    FILT_SET_EN(&cfg->opflags);
                }
            }else if((ip=strstr(*opt,"mm"))!=NULL || (ip=strstr(*opt,"MM"))!=NULL){
                ep=ip+2;
                while(*ep!='\0' && (*ep=='<' || *ep=='>' || *ep=='=' || *ep=='!') )ep++;
                n_args=sscanf(ep,"%"FMT_U16",%"FMT_U16"",&search->val[0].u16,&search->val[1].u16);
                if(n_args>0){
                    search->op_id=limpl_cmp_parse_op(ip);
                    search->cmp_type=CTYPE_U16;
                    search->type=ST_MMID;
                    FILT_SET_EN(&cfg->opflags);
                }
            }else if( (ip=strstr(*opt,"esn"))!=NULL || (ip=strstr(*opt,"ESN"))!=NULL){
                ep=ip+3;
                while(*ep!='\0' && (*ep=='<' || *ep=='>' || *ep=='=' || *ep=='!') )ep++;
                n_args=sscanf(ep,"%"FMT_U32",%"FMT_U32"",&search->val[0].u32,&search->val[1].u32);
                if(n_args>0){
                    search->op_id=limpl_cmp_parse_op(ip);
                    search->cmp_type=CTYPE_U32;
                    search->type=ST_ESN;
                    FILT_SET_EN(&cfg->opflags);
                }
            }else if( (ip=strstr(*opt,"sn"))!=NULL || (ip=strstr(*opt,"SN"))!=NULL){
                ep=ip+2;
                while(*ep!='\0' && (*ep=='<' || *ep=='>' || *ep=='=' || *ep=='!') )ep++;
                n_args=sscanf(ep,"%"FMT_U32",%"FMT_U32"",&search->val[0].u32,&search->val[1].u32);
                if(n_args>0){
                    search->op_id=limpl_cmp_parse_op(ip);
                    search->cmp_type=CTYPE_U32;
                    search->type=ST_SEQN;
                    FILT_SET_EN(&cfg->opflags);
                }
            }else if((ip=strstr(*opt,"idx"))!=NULL || (ip=strstr(*opt,"IX"))!=NULL){
                ep=ip+3;
                while(*ep!='\0' && (*ep=='<' || *ep=='>' || *ep=='=' || *ep=='!') )ep++;
                n_args=sscanf(ep,"%"FMT_U32",%"FMT_U32"",&u32[0],&u32[1]);
                if(n_args>0){
                    if(u32[1]==0){
                        if(u32[0]>0){
                            cfg->start=u32[0];
                            cfg->count=-1;
                        }
                    }else{
                        if(u32[1]>u32[0]){
                            cfg->start=u32[0];
                            cfg->count=u32[1]-u32[0];
                        }
                    }
                }
            }else if(strstr(*opt,"src:")!=NULL || strstr(*opt,"SRC:")!=NULL){
                cfg->src=strstr(*opt,":")+1;
            }else if(strstr(*opt,"dest:")!=NULL || strstr(*opt,"DEST:")!=NULL){
                cfg->dest=strstr(*opt,":")+1;
            }else{
                if(i>0)
                    XPRINT(0xFE,"%s:%d unrecognized option[%s]\r\n",__func__,__LINE__,*opt);
            }
            opt++;
            i++;
        }//while opt
        if(tmp_oflags!=0){
            cfg->oflags=tmp_oflags;
        }
    }// invalid arg
    return retval;
}
#endif //WITH_DQUTIL

//////////////////////////////////////
// lutil implementation
//////////////////////////////////////

void lutil_show_opts(lutil_opts_t *self)
{
    if(NULL!=self){
        int wkey=12;
        int wval=16;
        OUT_CTTY("%*s %*c\r\n",wkey,"lastn",wval,BOOLCHAR(self->last_n));
        OUT_CTTY("%*s %*"FMT_U32"\r\n",wkey,"start",wval,self->start);
        OUT_CTTY("%*s %*"FMT_U32"\r\n",wkey,"count",wval,self->count);
        OUT_CTTY("%*s %*s%08"FMT_X32"\r\n",wkey,"oflags",wval-8,"x",self->oflags);
        OUT_CTTY("%*s %*s%08"FMT_X32"\r\n",wkey,"iflags",wval-8,"x",self->iflags);
        OUT_CTTY("%*s %*s%08"FMT_X32"\r\n",wkey,"opflags",wval-8,"x",self->opflags);
        OUT_CTTY("%*s %*s\r\n",wkey,"src_type",wval,lutil_src_names[self->src_type]);
        OUT_CTTY("%*s %*s\r\n",wkey,"src",wval,self->src);
        limpl_search_def_show(&self->search);
        OUT_CTTY("\r\n");
    }
}

int limpl_parse_lutil_opts(lutil_opts_t *cfg, int opt_n, char **opts)
{
    int retval=-1;
    if(NULL!=cfg && NULL!=opts){
        char **opt=opts;
        int i=0;
        queue_output_flag tmp_oflags=0x0;
        linfo_flags tmp_iflags=0x0;
        struct tm tm_iso[2];
        int gmt_ofs=-8;
        int n_args=0;
        limpl_search_def_t *search=&cfg->search;
//        SET_XPRINT(0xFFFFFFFF);
        while(i<opt_n){
            retval=0;
            char *ap=NULL, *ep=NULL, *ip=NULL;
            uint32_t u32[2]={0,0};

            if(strcmp(*opt,"z")==0 || strcmp(*opt,"Z")==0){
                cfg->src_type=LU_ZBIN;
            }else if(strcmp(*opt,"o")==0 || strcmp(*opt,"O")==0){
                cfg->src_type=LU_OBIN;
            }else if(strcmp(*opt,"h")==0 || strcmp(*opt,"H")==0){
                retval=1;
                break;
            }else if((ip=strstr(*opt,"dbg"))!=NULL || (ip=strstr(*opt,"DBG"))!=NULL){
                if(sscanf(ip+3,"=%"FMT_X32"",&u32[0])==1){
                    SET_XPRINT(u32[0]);
                }else if(sscanf(ip+3,"+%"FMT_X32"",&u32[0])==1){
                    g_xprint |= u32[0];
                }else if(sscanf(ip+3,"-%"FMT_X32"",&u32[0])==1){
                    g_xprint &= ~(u32[0]);
                }else if(sscanf(ip+4,"%"FMT_X32"",&u32[0])==1){
                    SET_XPRINT(u32[0]);
                }
                XPRINT(0xFE,"dbg mask=%"FMT_X32"\r\n",g_xprint);
            }else if(sscanf(*opt,"gmt:%d",&gmt_ofs)==1 || sscanf(*opt,"GMT:%d",&gmt_ofs)==1){
                // set gmt offset (use before te option)
            }else if(strstr(*opt,"src:")!=NULL || strstr(*opt,"SRC:")!=NULL){
#if defined(__PIC24FJ256GA106__) || defined(__PIC24FJ256GA606__) || defined(__PIC24FJ1024GA606__)
                char *kp = strstr(*opt,":$");
                if(NULL!=kp){
                    memset(opt_buf,0,CFG_MEM_STRING_BYTES);
                    config_get(kp+2,opt_buf);
                    cfg->src=opt_buf;
                }else{
                    cfg->src=strstr(*opt,":")+1;
                }
#else
                    cfg->src=strstr(*opt,":")+1;
#endif
            }else if( (ap=strstr(*opt,"out:"))!=NULL || (ap=strstr(*opt,"OUT:"))!=NULL){
                ap+=4;
                char *bp=strstr(ap,",");
                if(NULL!=bp){
                    while(ap<=bp){
                        if(*ap=='h' || *ap=='H'){
                            tmp_oflags|=QOF_HOST;
                        }
                        if(*ap=='c' || *ap=='C'){
                            tmp_oflags|=QOF_CONSOLE;
                        }
                        ap++;
                    }
                }
                if(strstr(ap,"b")!=NULL || strstr(ap,"B")!=NULL)
                    tmp_oflags|=QOF_BASE85;
                if(strstr(ap,"d")!=NULL || strstr(ap,"D")!=NULL)
                    tmp_oflags|=QOF_DATA|QOF_PRETTY;
                if(strstr(ap,"r")!=NULL || strstr(ap,"R")!=NULL){
                    tmp_iflags|=LINF_REC_COUNTER;
                	tmp_oflags|=QOF_RAW;
                }
                if(strstr(ap,"e")!=NULL || strstr(ap,"E")!=NULL)
                    tmp_iflags|=LINF_TIME_POSIX;
                if(strstr(ap,"i")!=NULL || strstr(ap,"I")!=NULL)
                    tmp_iflags|=LINF_TIME_ISO;
                if(strstr(ap,"m")!=NULL || strstr(ap,"M")!=NULL)
                    tmp_oflags|=(QOF_CSVX|QOF_CSV);
               if(strstr(ap,"c")!=NULL || strstr(ap,"C")!=NULL){
                    tmp_iflags|=LINF_FMT_CSV;
                	tmp_oflags|=QOF_CSV;
                }
                if(strstr(ap,"h")!=NULL || strstr(ap,"H")!=NULL){
                    tmp_iflags|=LINF_CSV_HEADER;
                    tmp_oflags|=QOF_HEADER|QOF_PRETTY;
                }
                if(strstr(ap,"p")!=NULL || strstr(ap,"P")!=NULL){
                    tmp_oflags|=QOF_PRETTY;
                    tmp_iflags|=LINF_FMT_PRETTY;
                }
                if(strstr(ap,"v")!=NULL || strstr(ap,"V")!=NULL){
                    tmp_oflags|=QOF_VERBOSE;
                }
                if(strstr(ap,"u")!=NULL || strstr(ap,"U")!=NULL){
                    tmp_oflags|=QOF_UUENCODE;
                }
                if(strstr(ap,"x")!=NULL || strstr(ap,"X")!=NULL){
                    if(strstr(ap,"x*")!=NULL || strstr(ap,"X*")!=NULL){
                        tmp_oflags|=QOF_HEX;
                        tmp_oflags|=QOF_PRETTY_HEX;
                    }else if(strstr(ap,"xx")!=NULL || strstr(ap,"XX")!=NULL){
                        tmp_oflags|=QOF_PRETTY_HEX;
                    }else{
                        tmp_oflags|=QOF_HEX;
                    }
                }
            }else if(sscanf(*opt,"s%"FMT_U32"",&u32[0])==1 ||
                     sscanf(*opt,"S%"FMT_U32"",&u32[0])==1){
                cfg->start=u32[0];
            }else if(sscanf(*opt,"c%"FMT_U32"",&u32[0])==1 ||
                     sscanf(*opt,"C%"FMT_U32"",&u32[0])==1){
                cfg->count=u32[0];
            }else if(sscanf(*opt,"l%"FMT_U32"",&u32[0])==1 ||
                     sscanf(*opt,"L%"FMT_U32"",&u32[0])==1){
                cfg->count=u32[0];
                cfg->last_n=true;
            }else if( (ip=strstr(*opt,"ti"))!=NULL || (ip=strstr(*opt,"TI"))!=NULL){
                ep=ip+2;
                while(*ep!='\0' && (*ep=='<' || *ep=='>' || *ep=='=' || *ep=='!') )ep++;
                n_args=sscanf(ep,"%d-%d-%dT%d:%d:%d,%d-%d-%dT%d:%d:%d",
                              &tm_iso[0].tm_year, &tm_iso[0].tm_mon,
                              &tm_iso[0].tm_mday, &tm_iso[0].tm_hour,
                              &tm_iso[0].tm_min, &tm_iso[0].tm_sec,
                              &tm_iso[1].tm_year, &tm_iso[1].tm_mon,
                              &tm_iso[1].tm_mday, &tm_iso[1].tm_hour,
                              &tm_iso[1].tm_min, &tm_iso[1].tm_sec);
                if(n_args==6 || n_args==12){

                    tm_iso[0].tm_year-=1900;
                    tm_iso[0].tm_mon-=1;
                    tm_iso[0].tm_hour+=gmt_ofs;

                    tm_iso[1].tm_year-=1900;
                    tm_iso[1].tm_mon-=1;
                    tm_iso[1].tm_hour+=gmt_ofs;

                    search->type=ST_TIME;
                    search->op_id=limpl_cmp_parse_op(ip);
                    search->cmp_type=CTYPE_U64;
                    search->val[0].u64=mktime(&tm_iso[0])*1000ULL;
                    search->val[1].u64=mktime(&tm_iso[1])*1000ULL;
                    FILT_SET_EN(&cfg->opflags);
                    XPRINT(0xFE,"search %s -> %"FMT_U64",%"FMT_U64"\r\n",s_trim(ep),search->val[0].u64,search->val[1].u64);
                }
            }else if( (ip=strstr(*opt,"te"))!=NULL || (ip=strstr(*opt,"TE"))!=NULL){
                ep=ip+2;
                while(*ep!='\0' && (*ep=='<' || *ep=='>' || *ep=='=' || *ep=='!') )ep++;
                n_args=sscanf(ep,"%"FMT_U64",%"FMT_U64"",&search->val[0].u64,&search->val[1].u64);
                if(n_args>0){
                    search->op_id=limpl_cmp_parse_op(ip);
                    search->cmp_type=CTYPE_U64;
                    search->type=ST_TIME;
                    FILT_SET_EN(&cfg->opflags);
                }
            }else if((ip=strstr(*opt,"rt"))!=NULL || (ip=strstr(*opt,"RT"))!=NULL){
                ep=ip+2;
                while(*ep!='\0' && (*ep=='<' || *ep=='>' || *ep=='=' || *ep=='!') )ep++;
                n_args=sscanf(ep,"%"FMT_U16",%"FMT_U16"",&search->val[0].u16,&search->val[1].u16);
                if(n_args>0){
                    search->op_id=limpl_cmp_parse_op(ip);
                    search->cmp_type=CTYPE_U16;
                    search->type=ST_RTID;
                    FILT_SET_EN(&cfg->opflags);
                }
            }else if((ip=strstr(*opt,"rm"))!=NULL || (ip=strstr(*opt,"RM"))!=NULL){
                ep=ip+2;
                while(*ep!='\0' && (*ep=='<' || *ep=='>' || *ep=='=' || *ep=='!') )ep++;
                n_args=sscanf(ep,"%"FMT_U16",%"FMT_U16"",&search->val[0].u16,&search->val[1].u16);
                if(n_args>0){
                    search->op_id=limpl_cmp_parse_op(ip);
                    search->cmp_type=CTYPE_U16;
                    search->type=ST_RMID;
                    FILT_SET_EN(&cfg->opflags);
               }
            }else if((ip=strstr(*opt,"mm"))!=NULL || (ip=strstr(*opt,"MM"))!=NULL){
                ep=ip+2;
                while(*ep!='\0' && (*ep=='<' || *ep=='>' || *ep=='=' || *ep=='!') )ep++;
                n_args=sscanf(ep,"%"FMT_U16",%"FMT_U16"",&search->val[0].u16,&search->val[1].u16);
                if(n_args>0){
                    search->op_id=limpl_cmp_parse_op(ip);
                    search->cmp_type=CTYPE_U16;
                    search->type=ST_MMID;
                    FILT_SET_EN(&cfg->opflags);
                }
            }else if( (ip=strstr(*opt,"esn"))!=NULL || (ip=strstr(*opt,"ESN"))!=NULL){
                ep=ip+3;
                while(*ep!='\0' && (*ep=='<' || *ep=='>' || *ep=='=' || *ep=='!') )ep++;
                n_args=sscanf(ep,"%"FMT_U16",%"FMT_U16"",&search->val[0].u16,&search->val[1].u16);
                if(n_args>0){
                    search->op_id=limpl_cmp_parse_op(ip);
                    search->cmp_type=CTYPE_U16;
                    search->type=ST_ESN;
                    FILT_SET_EN(&cfg->opflags);
                }
            }else if( (ip=strstr(*opt,"msn"))!=NULL || (ip=strstr(*opt,"MSN"))!=NULL){
                ep=ip+3;
                while(*ep!='\0' && (*ep=='<' || *ep=='>' || *ep=='=' || *ep=='!') )ep++;
                n_args=sscanf(ep,"%"FMT_U16",%"FMT_U16"",&search->val[0].u16,&search->val[1].u16);
                if(n_args>0){
                    search->op_id=limpl_cmp_parse_op(ip);
                    search->cmp_type=CTYPE_U16;
                    search->type=ST_MSN;
                    FILT_SET_EN(&cfg->opflags);
                }
            }else if( (ip=strstr(*opt,"sn"))!=NULL || (ip=strstr(*opt,"SN"))!=NULL){
                ep=ip+2;
                while(*ep!='\0' && (*ep=='<' || *ep=='>' || *ep=='=' || *ep=='!') )ep++;
                n_args=sscanf(ep,"%"FMT_U32",%"FMT_U32"",&search->val[0].u32,&search->val[1].u32);
                if(n_args>0){
                    search->op_id=limpl_cmp_parse_op(ip);
                    search->cmp_type=CTYPE_U32;
                    search->type=ST_SEQN;
                    FILT_SET_EN(&cfg->opflags);
                }
            }else if( (ip=strstr(*opt,"cn"))!=NULL || (ip=strstr(*opt,"CN"))!=NULL){
                ep=ip+2;
                while(*ep!='\0' && (*ep=='<' || *ep=='>' || *ep=='=' || *ep=='!') )ep++;
                n_args=sscanf(ep,"%"FMT_U32",%"FMT_U32"",&search->val[0].u32,&search->val[1].u32);
                if(n_args>0){
                    search->op_id=limpl_cmp_parse_op(ip);
                    search->cmp_type=CTYPE_U32;
                    search->type=ST_CYCN;
                    FILT_SET_EN(&cfg->opflags);
                }
            }else if((ip=strstr(*opt,"idx"))!=NULL || (ip=strstr(*opt,"IDX"))!=NULL){
                ep=ip+3;
                while(*ep!='\0' && (*ep=='<' || *ep=='>' || *ep=='=' || *ep=='!') )ep++;
                n_args=sscanf(ep,"%"FMT_U32",%"FMT_U32"",&u32[0],&u32[1]);
                if(n_args>0){
                    if(u32[1]==0){
                        if(u32[0]>0){
                            cfg->start=u32[0];
                            cfg->count=-1;
                        }
                    }else{
                        if(u32[1]>u32[0]){
                            cfg->start=u32[0];
                            cfg->count=u32[1]-u32[0];
                        }
                    }
                }
            }else{
                if(i>0){
                    // assume it's a file
                    cfg->src=*opt;
                    s_trim_trailing(cfg->src);
                }
            }
            XPRINT(0xFE,"opt[%s]\r\n",*opt);
            opt++;
            i++;
        }//while opt

        if((tmp_oflags&QOF_DEV_FLAGS)!=0){
            // replace format options
            cfg->oflags &= ~(QOF_DEV_FLAGS);
        }
        if((tmp_oflags&QOF_FMT_FLAGS)!=0){
            // replace format options
            cfg->oflags &= ~(QOF_FMT_FLAGS);
        }
        if(tmp_oflags!=0){
            cfg->oflags|=tmp_oflags;
        }
        // clear overridden options
        if((tmp_iflags&LINF_FMT_FLAGS)!=0){
            cfg->iflags &= ~(LINF_FMT_FLAGS);
        }
        if((tmp_iflags&LINF_TIME_FLAGS)!=0){
            cfg->iflags &= ~(LINF_TIME_FLAGS);
        }
        if(tmp_iflags!=0){
            // set overridden options
            cfg->iflags |= tmp_iflags;
        }
    }// invalid arg
    return retval;
}
//////////////////////////////////////
// zrecord implementation
//////////////////////////////////////


int impl_zbin_traverse(log_impl_t *self, lutil_opflags_t flags, uint32_t count, log_ofs_t *r_ofs, uint32_t *r_count)
{
    int retval=0;

    if(NULL!=self && NULL!=self->fp && NULL!=r_ofs){

        XPRINT(0xF000000FE,"%s: ENTER count[%"FMT_U32"] dir[%c] fp[%"FMT_U32"]\r\n",__func__,count,(DIR_IS_FWD(flags)?'F':'R'),iow_ftell(self->fp));

        uint32_t i=0;
        uint32_t cur_ofs=iow_ftell(self->fp);

        uint32_t sync_bytes=0;
        uint32_t rec_start=(count==0?limpl_ftell(self):0);

        for(i=0;i<count;i++){

            sync_bytes=0;

            if(self->sync(self,flags,&cur_ofs,&sync_bytes)==0){

                rec_start=cur_ofs;

            }else{

                // check for error conditions
                if(iow_feof(self->fp)){
                    // end of file
                    iow_clearerr(self->fp);
                    retval=-2;
                }else if(iow_ftell(self->fp)==0UL){
                    // start of file
                    retval=-3;
                }else if(iow_ferror(self->fp)){
                    // IO/fsys error
                    iow_clearerr(self->fp);
                    retval=-4;
                }

                if(retval!=0){
                    break;
                }
            }


        }// for count

        if(NULL!=r_count)*r_count=i;
        if(i==count){
            *r_ofs=cur_ofs;
            retval=0;
        }

        XPRINT(0xF000000FE,"%s: EXIT  ret[%d] cur_ofs[%"FMT_U32"] i[%"FMT_U32"] r_count[%"FMT_U32"] r_ofs[%"FMT_U32"] fp[%"FMT_U32"]\r\n",__func__,retval,cur_ofs, i,(NULL==r_count?-1:*r_count),*r_ofs,iow_ftell(self->fp));
    }// invalid arg
    return retval;
}

int impl_zbin_read(log_impl_t *self, byte *dest, uint32_t len, uint32_t count, uint32_t *r_count)
{
    int retval=-1;

    if(NULL!=self && NULL!=dest){

       size_t br=0;
        byte buf[LI_ZREC_SIZEN(6)];
        licor_zrecord_t *rec=(licor_zrecord_t *)buf;
        uint32_t dest_rem=len;
        uint32_t count_rem=count;
        byte *dp=dest;
        while(dest_rem>0 && count_rem>0){

            ZERO_MEM(buf,LI_ZREC_SIZEN(6));
            size_t read_len=LI_ZREC_HDR_LEN;
            // read header
           if((br=iow_fread(self->fp,buf,read_len)) == read_len){
                // validate header
                if(rec->hdr.sync!=LI_ZREC_SYNC){
                    licor_logrechdr_show(&rec->hdr, true, 5);
                    break;
                }

               br=0;
                // get data len
                read_len=LI_ZREC_DATA_LEN(rec);
                // read datalen
               if((br=iow_fread(self->fp,(byte *)LI_ZREC_PMDATA(rec),read_len))==read_len){

                    if(read_len<=dest_rem){
                        memcpy(dp,rec,LI_ZREC_SIZE(rec));
                        dp+=read_len;
                        dest_rem-=read_len;
                        count_rem--;
                    }else{
                        // out of space
                        OUT_CTTY("ERR (space) sz[%"FMT_SZ"] rem[%"FMT_U32"] \r\n",read_len,dest_rem);
                        break;
                    }
                }else{
                    // data read error
                    XTRACE(0xE);
                    break;
                }
            }else{
                // hdr read error
                XTRACE(0xE);
                break;
            }
        }//while

        if(NULL!=r_count){
            *r_count = (count-count_rem);
        }
        if(count_rem<=0){
            retval=0;
        }
    }
    return retval;
}

int impl_zbin_compare(byte *src, limpl_search_def_t *search)
{
    int retval=-1;

    if(NULL!=src && NULL!=search){
        licor_zrecord_t *rec = (licor_zrecord_t *)src;
        cmp_val_t vset[3]={{0},{0},{0}};

        if(search->type==ST_TIME){
            vset[0].u64=search->val[0].u64;
            vset[1].u64=search->val[1].u64;
            vset[2].u64=rec->hdr.timestamp;
        }else if(search->type==ST_RTID){
            vset[0].u16=search->val[0].u16;
            vset[1].u16=search->val[1].u16;
            vset[2].u16=rec->hdr.type_id;
        }else if(search->type==ST_RMID){
            vset[0].u16=search->val[0].u16;
            vset[1].u16=search->val[1].u16;
            vset[2].u16=rec->hdr.meas_id;
        }else if(search->type==ST_SEQN){
            vset[0].u32=search->val[0].u32;
            vset[1].u32=search->val[1].u32;
            vset[2].u32=rec->hdr.seq_n;
        }else if(search->type==ST_CYCN){
            vset[0].u32=search->val[0].u32;
            vset[1].u32=search->val[1].u32;
            vset[2].u32=rec->hdr.cycle_id;
        }else{
            // unimplemented
            XTRACE(0xFE);
        }
        if(search->cmp_type!=CTYPE_NONE){
            XPRINT(0xFE,"op[%s] cmp_type[%d] stype[%d] vset{%08llX,%08llX,%08llX,}\r\n",lutil_cmp_ops[search->op_id],search->cmp_type,search->type,vset[0].u64,vset[0].u64,vset[0].u64);
            retval = s_cmp_type(search->op_id,search->cmp_type,vset);
        }
    }// invalid arg

    return retval;
}

int impl_zbin_bshow(log_impl_t *self, byte *src, int indent, lutil_opts_t *opts)
{
    int retval=-1;

    if(NULL!=self && NULL!=opts){
        licor_zrecord_outs((licor_zrecord_t *)src, opts->oflags);
        retval=0;
    }
    return retval;
}

int impl_zbin_show(log_impl_t *self, int indent, lutil_opts_t *opts)
{
    int retval=-1;

    if(NULL!=self && NULL!=opts){

        XPRINT(0x40000000,"%s ENTER fp[%"FMT_U32"]\r\n",__func__,iow_ftell(self->fp));

        byte buf[self->buf_len];
        uint32_t r_count=0;
        log_ofs_t r_ofs=0;

        ZERO_MEM(buf,self->buf_len);

         if(self->read(self, buf, self->buf_len, 1, &r_count)==0){

             retval=self->bshow(self,buf,indent,opts);

            // seek next (should be there)
            // skips cruft between records (e.g. CRLF)
             retval=limpl_next(self,&r_ofs);

        }else{
            // read error
            XTRACE(0xE);
        }
        XPRINT(0x40000000,"%s EXIT fp[%"FMT_U32"] r_ofs[%"FMT_U32"] ret[%d]\r\n",__func__,iow_ftell(self->fp),r_ofs,retval);
    }// invalid arg
    return retval;
}

int impl_zbin_inspect(log_impl_t *self, int indent, lutil_opts_t *opts)
{
    int retval=-1;

    if(NULL!=self){

        XPRINT(0xFE,"oflags[%08"FMT_FLAG"]\r\n",opts->iflags);

        linfo_stats_t stats_s={0}, *r_stats=&stats_s;

        linfo_stats_t *pstats=r_stats;
        if(NULL==r_stats){
            // use local instance (NULL instanced passed in)
            linfo_stats_t stats_s;
            pstats=&stats_s;
        }
        memset(pstats,0,sizeof(linfo_stats_t));
        pstats->fsize=0;
        pstats->time_bin[2]=0xFFFFFFFFFFFFFFFFULL;
        pstats->seq_bin[2]=0xFFFFFFFFUL;
        pstats->seq_last=-1;
        pstats->time_last=-1;

        uint32_t ofs_test=0;
        if(NULL!=self->fpath){

            pstats->fsize=iow_fsize(self->fp);
            iow_fseek(self->fp,0);

            while(limpl_feof(self)==0){

                int test=0;
                uint32_t r_count=0;

                if( (test=self->sync(self,(DIR_FLG_FWD),&ofs_test,&r_count))==0){

                    if(r_count>0){
                        // mcount == sync chars
                        // extra characters in file
                        pstats->err_bin[E_SYNC]++;
                    }

                    // point to start record
                    limpl_fseek(self,ofs_test);
                    char buf[LI_ZREC_SIZEN(6)];
                    licor_zrecord_t *rec=(licor_zrecord_t *)buf;
                    size_t br=0;
                    size_t read_len=LI_ZREC_HDR_LEN;
                    ZERO_MEM(rec,LI_ZREC_SIZEN(6));

                    if((br=iow_fread(self->fp,rec,read_len)) == read_len){

                        // timestamp, sequence ranges
                        if(rec->hdr.timestamp<=pstats->time_bin[T_RNGSTART])
                            pstats->time_bin[T_RNGSTART]=rec->hdr.timestamp;
                        if(rec->hdr.timestamp>=pstats->time_bin[T_RNGEND])
                            pstats->time_bin[T_RNGEND]=rec->hdr.timestamp;

                        if(rec->hdr.seq_n<=pstats->seq_bin[SEQ_RNGSTART]){
                            pstats->seq_bin[SEQ_RNGSTART]=rec->hdr.seq_n;
                        }
                        if(rec->hdr.seq_n>=pstats->seq_bin[SEQ_RNGEND]){
                            pstats->seq_bin[SEQ_RNGEND]=rec->hdr.seq_n;
                        }

                        if(pstats->record_count==0){
                            pstats->time_bin[T_FIRST]=rec->hdr.timestamp;
                            pstats->time_bin[T_LAST]=rec->hdr.timestamp;
                            pstats->seq_bin[SEQ_FIRST]=rec->hdr.seq_n;
                            pstats->seq_bin[SEQ_LAST]=rec->hdr.seq_n;
                        }else{
                            pstats->time_bin[T_LAST]=rec->hdr.timestamp;
                            pstats->seq_bin[SEQ_LAST]=rec->hdr.seq_n;
                        }

                        // timestamp errors
                        if(rec->hdr.timestamp==0){
                            pstats->time_bin[E_TMZERO]++;
                        }
                        if(pstats->record_count!=0 && (rec->hdr.timestamp<pstats->time_last)){
                            pstats->err_bin[E_TMMONO]++;
                        }
                        pstats->time_last=rec->hdr.timestamp;


                        // seq errors
                        if( (pstats->record_count!=0) && (rec->hdr.seq_n<=pstats->seq_last)){
                            pstats->err_bin[E_SEQMONO]++;
                        }
                        pstats->seq_last=rec->hdr.seq_n;

                        // measurement count errors
                        if(rec->hdr.meas_count<0 || rec->hdr.meas_count>LICOR_MAX_CYCLE_MEASUREMENTS)pstats->err_bin[E_CTINVAL]++;

                        // record len errors
                        if(rec->hdr.len==0 || ((rec->hdr.len%LI_ZREC_MDATA_LEN)!=0) )pstats->err_bin[E_LENINVAL]++;
                        // record type stats, errors
                        switch(rec->hdr.type_id){
                            case RTID_INVALID:
                                pstats->record_bin[R_RTID_INVAL]++;
                                break;
                            case RTID_ZRECv0:
                                pstats->record_bin[R_RTID_ZRECV1]++;
                                break;
                            default:
                                pstats->err_bin[E_RTID]++;
                                break;
                        }
                        switch(rec->hdr.meas_id){
                            case RMID_INVALID:
                                pstats->record_bin[R_RMID_INVAL]++;
                                break;
                            case RMID_NONE:
                                pstats->record_bin[R_RMID_NONE]++;
                                break;
                            case RMID_SAMPLE:
                                pstats->record_bin[R_RMID_SAMPLE]++;
                                break;
                            case RMID_SCAL:
                                pstats->record_bin[R_RMID_SCAL]++;
                                break;
                            case RMID_ZCAL:
                                pstats->record_bin[R_RMID_ZCAL]++;
                                break;
                            default:
                                pstats->err_bin[E_RMID]++;
                                break;
                        }

                        // read the data
                        size_t read_len=LI_ZREC_DATA_LEN(rec);
                        uint32_t rofs = limpl_ftell(self);
                        if((br=iow_fread(self->fp,LI_ZREC_PMDATA(rec),read_len)) == read_len){
                            mdata_Z_t *mdata = LI_ZREC_PMDATA(rec);
                            int j=0;
                            for(j=0;j<rec->hdr.meas_count;j++){
                                switch (mdata[j].meas_id) {
                                    case MMID_NONE:
                                        pstats->record_bin[R_MMID_NONE]++;
                                        break;
                                    case MMID_AIR:
                                        pstats->record_bin[R_MMID_AIR]++;
                                        break;
                                    case MMID_STD:
                                        pstats->record_bin[R_MMID_STD]++;
                                        break;
                                    case MMID_ZERO:
                                        pstats->record_bin[R_MMID_ZERO]++;
                                        break;
                                    case MMID_PRES:
                                        pstats->record_bin[R_MMID_PRES]++;
                                        break;
                                    case MMID_POSTS:
                                        pstats->record_bin[R_MMID_POSTS]++;
                                        break;
                                    case MMID_PREZ:
                                        pstats->record_bin[R_MMID_PREZ]++;
                                        break;
                                    case MMID_POSTZ:
                                        pstats->record_bin[R_MMID_POSTZ]++;
                                        break;
                                    case MMID_PREB:
                                        pstats->record_bin[R_MMID_PREB]++;
                                        break;
                                    case MMID_POSTB:
                                        pstats->record_bin[R_MMID_POSTB]++;
                                        break;
                                    case MMID_BLINE:
                                        pstats->record_bin[R_MMID_BLINE]++;
                                        break;
                                    case MMID_SAMPLE:
                                        pstats->record_bin[R_MMID_SAMPLE]++;
                                        break;
                                    case MMID_SCAL:
                                        pstats->record_bin[R_MMID_SCAL]++;
                                        break;
                                    case MMID_ZCAL:
                                        pstats->record_bin[R_MMID_ZCAL]++;
                                        break;
                                    default:
                                        pstats->err_bin[E_MMID]++;
                                        break;
                                }
                            }
                        }else{
                            OUT_CTTY("data read err rec[%d] fp[%"FMT_U32" (x%08"FMT_X32")], read[%"FMT_SZ"/%"FMT_SZ"]\r\n",pstats->record_count,rofs,rofs,br,read_len);
                            // else mdata read error
                            pstats->err_bin[E_DATA]++;
                        }

                        if( (opts->iflags&LINF_REC_COUNTER) !=0)
                            if( (pstats->record_count%100) == 0)OUT_CTTY("\b\b\b\b\b\b%6d",pstats->record_count);

                        pstats->record_count++;

                        uint32_t next_ofs=ofs_test+LI_ZREC_SIZEN(rec->hdr.meas_count);
                        //                        OUT_CTTY("rcount[%d] ofs_test[%"FMT_U32"] next_ofs:%"FMT_U32"\r\n",pstats->record_count,ofs_test,next_ofs);
                        iow_fseek(self->fp,next_ofs);

                        // let user interrupt
                        byte brk=0;
                        brk=iow_get_char(false);
                        if(brk=='\r' || brk=='\n' || brk==CTRL_D)break;

                    }else{
                        // else read error
                        XTRACE(0xF);
                    }
                }else{
                    pstats->err_bin[E_SYNC]++;
                    if(test<0)break;
                    OUT_ERR("ERR - fread test[%d] fp[%"FMT_U32"] feof[%d] ferr[%d]\r\n",test,limpl_ftell(self), limpl_feof(self), limpl_ferr(self));
                }
            }// end while

            iow_fclose(self->fp);

            if( (opts->iflags&LINF_REC_COUNTER) !=0)
                OUT_CTTY("\b\b\b\b\b\b%6d",pstats->record_count);

            // sum errors
            int i=0;
            for(i=0;i<EBIN_COUNT;i++){
                pstats->error_count+=pstats->err_bin[i];
            }

            // output summary
            if( ((opts->iflags&LINF_FMT_PRETTY)!=0) ||
               ((opts->iflags&LINF_FMT_FLAGS)==0)){
                OUT_CTTY("\r\nLog Summary [%s]\r\n",self->fpath);
                OUT_CTTY("file len         : %15"FMT_U32"\r\n",pstats->fsize);
                OUT_CTTY("record count     : %15d\r\n",pstats->record_count);
                char *tstr = striso8601(NULL,pstats->time_bin[T_FIRST],ISO8601_PREC_MSEC);
                OUT_CTTY("time first       : %15"FMT_U64"/%s\r\n",pstats->time_bin[T_FIRST],tstr);
                tstr = striso8601(NULL,pstats->time_bin[T_LAST],ISO8601_PREC_MSEC);
                OUT_CTTY("time last        : %15"FMT_U64"/%s\r\n",pstats->time_bin[T_LAST],tstr);
                tstr = striso8601(NULL,pstats->time_bin[T_RNGSTART],ISO8601_PREC_MSEC);
                OUT_CTTY("time range start : %15"FMT_U64"/%s\r\n",pstats->time_bin[T_RNGSTART],tstr);
                tstr = striso8601(NULL,pstats->time_bin[T_RNGEND],ISO8601_PREC_MSEC);
                OUT_CTTY("time range end   : %15"FMT_U64"/%s\r\n",pstats->time_bin[T_RNGEND],tstr);
                OUT_CTTY("seq first        : %15"FMT_U32"\r\n",pstats->seq_bin[SEQ_FIRST]);
                OUT_CTTY("seq last         : %15"FMT_U32"\r\n",pstats->seq_bin[SEQ_LAST]);
                OUT_CTTY("seq range start  : %15"FMT_U32"\r\n",pstats->seq_bin[SEQ_RNGSTART]);
                OUT_CTTY("seq range end    : %15"FMT_U32"\r\n",pstats->seq_bin[SEQ_RNGEND]);
                OUT_CTTY("Error Summary    :\r\n");
                OUT_CTTY("error count      : %15d\r\n",pstats->error_count);
                for(i=0;i<EBIN_COUNT;i++){
                    OUT_CTTY("  %14s  %10s%6d\r\n",linfo_ebin_names[i],"",pstats->err_bin[i]);
                }
                OUT_CTTY("Record Summary:\r\n");
                for(i=0;i<RBIN_COUNT;i++){
                    OUT_CTTY("  %14s  %10s%6d\r\n",linfo_rbin_names[i],"",pstats->record_bin[i]);
                }
            }

            if( (opts->iflags&LINF_FMT_CSV)!=0 ){
                if( (opts->iflags&LINF_CSV_HEADER)!=0){
                    OUT_CTTY("file_len,rec_count,tfirst,tlast,tmin,tmax,");
                    OUT_CTTY("seq_first,seq_last,seq_min,seq_max,err_count,");
                    OUT_CTTY("E_SYNC,E_TMZERO,E_TMMONO,E_CTINVAL,E_SEQINVAL,");
                    OUT_CTTY("E_LENINVAL,E_RTID,E_RMID,RMID_INVAL,RMID_NONE,");
                    OUT_CTTY("RMID_SAMPLE,RMID_SCAL,RMID_ZCAL,RTID_INVAL,RTID_ZRECV1\r\n");
                }
                OUT_CTTY("%"FMT_U32",",pstats->fsize);
                OUT_CTTY("%d,",pstats->record_count);

                if( (opts->iflags&LINF_TIME_ISO) !=0){
                    char *tstr = striso8601(NULL,pstats->time_bin[T_FIRST],ISO8601_PREC_MSEC);
                    OUT_CTTY("%s,",tstr);
                    tstr = striso8601(NULL,pstats->time_bin[T_LAST],ISO8601_PREC_MSEC);
                    OUT_CTTY("%s,",tstr);
                    tstr = striso8601(NULL,pstats->time_bin[T_RNGSTART],ISO8601_PREC_MSEC);
                    OUT_CTTY("%s,",tstr);
                    tstr = striso8601(NULL,pstats->time_bin[T_RNGEND],ISO8601_PREC_MSEC);
                    OUT_CTTY("%s,",tstr);
                }else{
                    OUT_CTTY("%"FMT_U64",",pstats->time_bin[T_FIRST]);
                    OUT_CTTY("%"FMT_U64",",pstats->time_bin[T_LAST]);
                    OUT_CTTY("%"FMT_U64",",pstats->time_bin[T_RNGSTART]);
                    OUT_CTTY("%"FMT_U64",",pstats->time_bin[T_RNGEND]);
                }

                OUT_CTTY("%"FMT_U32",",pstats->seq_bin[SEQ_FIRST]);
                OUT_CTTY("%"FMT_U32",",pstats->seq_bin[SEQ_LAST]);
                OUT_CTTY("%"FMT_U32",",pstats->seq_bin[SEQ_RNGSTART]);
                OUT_CTTY("%"FMT_U32",",pstats->seq_bin[SEQ_RNGEND]);
                OUT_CTTY("%d,",pstats->error_count);
                for(i=0;i<EBIN_COUNT;i++){
                    OUT_CTTY("%d,",pstats->err_bin[i]);
                }
                for(i=0;i<RBIN_COUNT;i++){
                    OUT_CTTY("%d%s",pstats->record_bin[i],(i<(RBIN_COUNT-1)?",":"\r\n"));
                }
            }
            retval=ERR_SUCCESS;

        }

    }// else invalid arg
    return retval;
}

//////////////////////////////////////
// orecord implementation
//////////////////////////////////////

int impl_obin_traverse(log_impl_t *self, lutil_opflags_t flags, uint32_t count,  uint32_t *r_ofs, uint32_t *r_count)
{
    int retval=-1;

    if(NULL!=self && NULL!=self->fp && NULL!=r_ofs){
        XPRINT(0xF000000FE,"%s: ENTER count[%"FMT_U32"] dir[%c] fp[%"FMT_U32"]\r\n",__func__,count,(DIR_IS_FWD(flags)?'F':'R'),iow_ftell(self->fp));

        uint32_t i=0;
        uint32_t cur_ofs=iow_ftell(self->fp);
        uint32_t sync_bytes=0;
        uint32_t rec_start=(count==0?limpl_ftell(self):0);

        // traverse counts syncs
        // returned offset is same as start if already synced
        for(i=0;i<count;i++){

            sync_bytes=0;

            if(self->sync(self,flags,&cur_ofs,&sync_bytes)==0){
                rec_start=cur_ofs;
            }else{

                // check for error conditions
                if(iow_feof(self->fp)){
                    // end of file
                    iow_clearerr(self->fp);
                    retval=-2;
                }else if(iow_ftell(self->fp)==0UL){
                    // start of file
                    retval=-3;
                }else if(iow_ferror(self->fp)){
                    // IO/fsys error
                    iow_clearerr(self->fp);
                    retval=-4;
                }

                if(retval!=0){
                    TRACE();
                    break;
                }
            }
        }// for count

        if(NULL!=r_count)*r_count=i;
        if(i==count){
            *r_ofs=rec_start;//cur_ofs;
            retval=0;
        }

        XPRINT(0xF000000FE,"%s: EXIT  ret[%d] cur_ofs[%"FMT_U32"] i[%"FMT_U32"] r_count[%"FMT_U32"] r_ofs[%"FMT_U32"] fp[%"FMT_U32"]\r\n",__func__,retval,cur_ofs, i,(NULL==r_count?-1:*r_count),*r_ofs,iow_ftell(self->fp));
    }
    return retval;
}

static int s_cmp_type(cmp_op_id op, cmp_type_t cmp_type, cmp_val_t *vset)
{
    int retval=-1;
    XPRINT(0xF000000FE,"%s: ENTRY op[%s] cmp_type[%s]\r\n",__func__,lutil_cmp_names[op],lutil_cmp_type_names[cmp_type]);
    XPRINT(0xF000000FE,"V2[%"FMT_U32"] V0[%"FMT_U32"] V1[%"FMT_U32"]\r\n",vset[2].u32,vset[0].u32,vset[1].u32);

    switch (cmp_type) {
        case CTYPE_U64:
            if(op==OP_EQ){
                if(vset[2].u64 == vset[0].u64)
                retval=0;
            }else if(op==OP_NE){
                if(vset[2].u64 != vset[0].u64)
                    retval=0;
            }else if(op==OP_GT){
                if(vset[2].u64 > vset[0].u64)
                    retval=0;
            }else if(op==OP_LT){
                if(vset[2].u64< vset[0].u64)
                    retval=0;
            }else if(op==OP_GE){
                if(vset[2].u64 >= vset[0].u64)
                    retval=0;
            }else if(op==OP_LE){
                if(vset[2].u64 <= vset[0].u64)
                    retval=0;
            }else if(op==OP_RI){
                if(vset[2].u64 >= vset[0].u64 &&
                   vset[2].u64 <= vset[1].u64)
                    retval=0;
            }else if(op==OP_RO){
                if(vset[2].u64 < vset[0].u64 ||
                   vset[2].u64 > vset[1].u64)
                    retval=0;
            }
            break;
        case CTYPE_U32:
            if(op==OP_EQ){
                if(vset[2].u32 == vset[0].u32)
                retval=0;
            }else if(op==OP_NE){
                if(vset[2].u32 != vset[0].u32)
                    retval=0;
            }else if(op==OP_GT){
                if(vset[2].u32 > vset[0].u32)
                    retval=0;
            }else if(op==OP_LT){
                if(vset[2].u32< vset[0].u32)
                    retval=0;
            }else if(op==OP_GE){
                if(vset[2].u32 >= vset[0].u32)
                    retval=0;
            }else if(op==OP_LE){
                if(vset[2].u32 <= vset[0].u32)
                    retval=0;
            }else if(op==OP_RI){
                if(vset[2].u32 >= vset[0].u32 &&
                   vset[2].u32 <= vset[1].u32)
                    retval=0;
            }else if(op==OP_RO){
                if(vset[2].u32 < vset[0].u32 ||
                   vset[2].u32 > vset[1].u32)
                    retval=0;
            }
            break;
        case CTYPE_U16:
            if(op==OP_EQ){
                if(vset[2].u16 == vset[0].u16)
                retval=0;
            }else if(op==OP_NE){
                if(vset[2].u16 != vset[0].u16)
                    retval=0;
            }else if(op==OP_GT){
                if(vset[2].u16 > vset[0].u16)
                    retval=0;
            }else if(op==OP_LT){
                if(vset[2].u16< vset[0].u16)
                    retval=0;
            }else if(op==OP_GE){
                if(vset[2].u16 >= vset[0].u16)
                    retval=0;
            }else if(op==OP_LE){
                if(vset[2].u16 <= vset[0].u16)
                    retval=0;
            }else if(op==OP_RI){
                if(vset[2].u16 >= vset[0].u16 &&
                   vset[2].u16 <= vset[1].u16)
                    retval=0;
            }else if(op==OP_RO){
                if(vset[2].u16 < vset[0].u16 ||
                   vset[2].u16 > vset[1].u16)
                    retval=0;
            }
            break;

        default:
            break;
    }
    if(retval==0)XPRINT(0x10000000,"%s: MATCHED\r\n",__func__);
    return retval;
}

cmp_op_id limpl_cmp_parse_op(char *expr)
{
    cmp_op_id retval=OP_INVALID;

    // test order is significant
    if(strstr(expr,"==")!=NULL){
        retval=OP_EQ;
    }else if(strstr(expr,"!=")!=NULL){
        retval=OP_NE;
    }else if(strstr(expr,">=")!=NULL){
        retval=OP_GE;
    }else if(strstr(expr,"<=")!=NULL){
        retval=OP_LE;
    }else if(strstr(expr,"<>")!=NULL){
        retval=OP_RO;
    }else if(strstr(expr,"><")!=NULL){
        retval=OP_RI;
    }else if(strstr(expr,">")!=NULL){
        retval=OP_GT;
    }else if(strstr(expr,"<")!=NULL){
        retval=OP_LT;
    }

    return retval;
}

int impl_obin_compare(byte *src, limpl_search_def_t *search)
{
    int retval=-1;

    if(NULL!=src && NULL!=search){

        licor_orecord_t *rec = (licor_orecord_t *)src;
        cmp_val_t vset[3]={{0},{0},{0}};

        if(search->type==ST_TIME){
            vset[0].u64=search->val[0].u64;
            vset[1].u64=search->val[1].u64;
            vset[2].u64=rec->timestamp;
        }else if(search->type==ST_RMID){
            uint16_t rmid = ((rec->id>>8)&0xff00);

            vset[0].u16=search->val[0].u16;
            vset[1].u16=search->val[1].u16;
            vset[2].u16=rmid;
        }else if(search->type==ST_MMID){
            uint16_t mmid = (rec->id&0x00FF);

            vset[0].u16=search->val[0].u16;
            vset[1].u16=search->val[1].u16;
            vset[2].u16=mmid;
        }else if(search->type==ST_XID){
            vset[0].u16=search->val[0].u16;
            vset[1].u16=search->val[1].u16;
            vset[2].u16=rec->id;
        }else if(search->type==ST_SEQN){
            vset[0].u32=search->val[0].u32;
            vset[1].u32=search->val[1].u32;
            vset[2].u32=rec->seq_n;
        }else if(search->type==ST_CYCN){
            vset[0].u32=search->val[0].u32;
            vset[1].u32=search->val[1].u32;
            vset[2].u32=rec->cycle_id;
        }else if(search->type==ST_ESN){
            vset[0].u16=search->val[0].u16;
            vset[1].u16=search->val[1].u16;
            vset[2].u16=rec->ens_n;
        }else if(search->type==ST_MSN){
            vset[0].u16=search->val[0].u16;
            vset[1].u16=search->val[1].u16;
            vset[2].u16=rec->meas_n;
        }else{
            // unimplemented
            XTRACE(0xFE);
        }
        if(search->cmp_type!=CTYPE_NONE){
            XPRINT(0xFE,"op[%s] cmp_type[%d] stype[%d] vset{%08llX,%08llX,%08llX,}\r\n",lutil_cmp_ops[search->op_id],search->cmp_type,search->type,vset[0].u64,vset[0].u64,vset[0].u64);
        	retval = s_cmp_type(search->op_id,search->cmp_type,vset);
        }
    }else{TRACE();}

    return retval;
}

int impl_obin_read(log_impl_t *self, byte *dest, uint32_t len, uint32_t count, uint32_t *r_count)
{
    int retval=-1;

    if(NULL!=self && NULL!=dest){
        XPRINT(0xFE,"%s: ENTER len[%"FMT_U32"] count[%"FMT_U32"] fp[%"FMT_U32"]\r\n",__func__,len,count,limpl_ftell(self));

        size_t br=0;
        byte buf[LI_OREC_SIZE];
        licor_orecord_t *rec=(licor_orecord_t *)buf;
        uint32_t dest_rem=len;
        uint32_t count_rem=count;
        byte *dp=dest;
        while(dest_rem>0 && count_rem>0){

            ZERO_MEM(buf,LI_OREC_SIZE);
            size_t read_len=LI_OREC_SIZE;
            // read header
//            OUT_CTTY("%s:%d fp[%"FMT_U32"] drem[%"FMT_U32"] crem[%"FMT_U32"]\r\n",__func__,__LINE__,limpl_ftell(self),dest_rem,count_rem);

            if((br=iow_fread(self->fp,buf,read_len)) == read_len){
                // validate header
                if(rec->sync==LI_OREC_SYNC){
                    if(read_len<=dest_rem){
                        memcpy(dp,rec,LI_OREC_SIZE);
                        dp+=read_len;
                        dest_rem-=read_len;
                        count_rem--;
                    }else{
                        // out of space
                        OUT_CTTY("ERR (space) sz[%"FMT_SZ"] rem[%"FMT_U32"] \r\n",read_len,dest_rem);
                        break;
                    }
                    break;
                }else{
                    TRACE();
                }
            }else{
                // read error
                // TODO: sync and continue
                XTRACE(0xE);
                break;
            }
        }//while

        if(NULL!=r_count){
            *r_count = (count-count_rem);
        }
        if(count_rem<=0){
            retval=0;
        }
        XPRINT(0xFE,"%s: EXIT len[%"FMT_U32"] r_count[%"FMT_U32"] fp[%"FMT_U32"] ret[%d]\r\n",__func__,len,(r_count!=NULL?*r_count:-1),limpl_ftell(self),retval);
    }
    return retval;
}

int impl_obin_bshow(log_impl_t *self, byte *src, int indent, lutil_opts_t *opts)
{
    int retval=-1;

    if(NULL!=self && NULL!=opts){
        licor_orecord_outs((licor_orecord_t *)src, opts->oflags);
        retval=0;
    }
    return retval;
}

int impl_obin_show(log_impl_t *self, int indent, lutil_opts_t *opts)
{
    int retval=-1;
    if(NULL!=self){

        XPRINT(0x40000000,"%s ENTER fp[%"FMT_U32"]\r\n",__func__,iow_ftell(self->fp));


        byte buf[self->buf_len];
        uint32_t r_count=0;
        log_ofs_t r_ofs=0;

        ZERO_MEM(buf,self->buf_len);

        if(self->read(self, buf, self->buf_len, 1, &r_count)==0){

            retval=self->bshow(self, buf, indent, opts);

            // seek next (should be there)
            // skips cruft between records (e.g. CRLF)
            limpl_next(self,&r_ofs);

        }else{
            // read error
            XTRACE(0xE);
         }
        XPRINT(0x40000000,"%s EXIT fp[%"FMT_U32"] r_ofs[%"FMT_U32"] ret[%d]\r\n",__func__,iow_ftell(self->fp),r_ofs,retval);
    }// invalid arg
    return retval;
}

int impl_obin_inspect(log_impl_t *self, int indent, lutil_opts_t *opts)
{
    int retval=-1;

    if(NULL!=self){

        XPRINT(0xFE,"oflags[%08"FMT_FLAG"]\r\n",opts->iflags);

        linfo_stats_t stats_s={0}, *r_stats=&stats_s;

        linfo_stats_t *pstats=r_stats;
        if(NULL==r_stats){
            // use local instance (NULL instanced passed in)
            linfo_stats_t stats_s;
            pstats=&stats_s;
        }
        memset(pstats,0,sizeof(linfo_stats_t));
        pstats->fsize=0;
        pstats->time_bin[2]=0xFFFFFFFFFFFFFFFFULL;
        pstats->seq_bin[2]=0xFFFFFFFFUL;
        pstats->seq_last=-1;
        pstats->time_last=-1;

        uint32_t ofs_test=0;
        if(NULL!=self->fpath){

            pstats->fsize=iow_fsize(self->fp);
            iow_fseek(self->fp,0);

uint32_t r_count=0;
            while(limpl_feof(self)==0){

                int test=0;
                r_count=0;

                if( (test=self->sync(self,(DIR_FLG_FWD),&ofs_test,&r_count))==0){

                    if(r_count>0){
                        // r_count == sync chars
                        // extra characters in file
                        OUT_ERR("rc[%"FMT_U32"] sz[%zu]\r\n",r_count,LI_OREC_SIZE);
                        pstats->err_bin[E_SYNC]++;
                    }

                    // point to start record
                    limpl_fseek(self,ofs_test);
                    char buf[LI_OREC_SIZE];
                    licor_orecord_t *rec=(licor_orecord_t *)buf;
                    size_t br=0;
                    size_t read_len=LI_OREC_SIZE;
                    ZERO_MEM(rec,LI_OREC_SIZE);

                    if((br=iow_fread(self->fp,rec,read_len)) == read_len){

                        // timestamp, sequence ranges
                        if(rec->timestamp<=pstats->time_bin[T_RNGSTART])
                            pstats->time_bin[T_RNGSTART]=rec->timestamp;
                        if(rec->timestamp>=pstats->time_bin[T_RNGEND])
                            pstats->time_bin[T_RNGEND]=rec->timestamp;

                        if(rec->seq_n<=pstats->seq_bin[SEQ_RNGSTART]){
                            pstats->seq_bin[SEQ_RNGSTART]=rec->seq_n;
                        }
                        if(rec->seq_n>=pstats->seq_bin[SEQ_RNGEND]){
                            pstats->seq_bin[SEQ_RNGEND]=rec->seq_n;
                        }

                        if(pstats->record_count==0){
                            pstats->time_bin[T_FIRST]=rec->timestamp;
                            pstats->time_bin[T_LAST]=rec->timestamp;
                            pstats->seq_bin[SEQ_FIRST]=rec->seq_n;
                            pstats->seq_bin[SEQ_LAST]=rec->seq_n;
                        }else{
                            pstats->time_bin[T_LAST]=rec->timestamp;
                            pstats->seq_bin[SEQ_LAST]=rec->seq_n;
                        }

                        // timestamp errors
                        if(rec->timestamp==0){
                            pstats->time_bin[E_TMZERO]++;
                        }
                        if(pstats->record_count!=0 && (rec->timestamp<pstats->time_last)){
                            pstats->err_bin[E_TMMONO]++;
                        }
                        pstats->time_last=rec->timestamp;


                        // seq errors
                        if( (pstats->record_count!=0) && (rec->seq_n<=pstats->seq_last)){
                            pstats->err_bin[E_SEQMONO]++;
                        }
                        pstats->seq_last=rec->seq_n;

                        // measurement count errors
                        if(rec->ens_n>=rec->ens_len || rec->ens_len==0){
                            pstats->err_bin[E_CTINVAL]++;
                        }

                        // record len errors
//                        if(rec->len==0 || ((rec->len%LI_ZREC_MDATA_LEN)!=0) ){
//                            pstats->err_bin[E_LENINVAL]++;
//                        }

                        // record type stats, errors
//                        switch(rec->type_id){
//                            case RTID_INVALID:
//                                pstats->record_bin[R_RTID_INVAL]++;
//                                break;
//                            case RTID_ZRECv0:
//                                pstats->record_bin[R_RTID_ZRECV1]++;
//                                break;
//                            default:
//                                pstats->err_bin[E_RTID]++;
//                                break;
//                        }

                        uint16_t rmid= ((rec->id&0xFF00)>>8);
                        uint16_t mmid= (rec->id&0x00FF);

                        switch(rmid){
                            case RMID_INVALID:
                            case RMID_BINVALID:
                                pstats->record_bin[R_RMID_INVAL]++;
                                break;
                            case RMID_NONE:
                                pstats->record_bin[R_RMID_NONE]++;
                                break;
                            case RMID_SAMPLE:
                                pstats->record_bin[R_RMID_SAMPLE]++;
                                break;
                            case RMID_SCAL:
                                pstats->record_bin[R_RMID_SCAL]++;
                                break;
                            case RMID_ZCAL:
                                pstats->record_bin[R_RMID_ZCAL]++;
                                break;
                            default:
                                pstats->err_bin[E_RMID]++;
                                break;
                        }

                        switch(mmid){
                            case MMID_NONE:
                                pstats->record_bin[R_MMID_NONE]++;
                                break;
                            case MMID_AIR:
                                pstats->record_bin[R_MMID_AIR]++;
                                break;
                            case MMID_STD:
                                pstats->record_bin[R_MMID_STD]++;
                                break;
                            case MMID_ZERO:
                                pstats->record_bin[R_MMID_ZERO]++;
                                break;
                            case MMID_PRES:
                                pstats->record_bin[R_MMID_PRES]++;
                                break;
                            case MMID_POSTS:
                                pstats->record_bin[R_MMID_POSTS]++;
                                break;
                            case MMID_PREZ:
                                pstats->record_bin[R_MMID_PREZ]++;
                                break;
                            case MMID_POSTZ:
                                pstats->record_bin[R_MMID_POSTZ]++;
                                break;
                            case MMID_PREB:
                                pstats->record_bin[R_MMID_PREB]++;
                                break;
                            case MMID_POSTB:
                                pstats->record_bin[R_MMID_POSTB]++;
                                break;
                            case MMID_BLINE:
                                pstats->record_bin[R_MMID_BLINE]++;
                                break;
                            case MMID_SAMPLE:
                                pstats->record_bin[R_MMID_SAMPLE]++;
                                break;
                            case MMID_SCAL:
                                pstats->record_bin[R_MMID_SCAL]++;
                                break;
                            case MMID_ZCAL:
                                pstats->record_bin[R_MMID_ZCAL]++;
                                break;
                            default:
                                pstats->err_bin[E_MMID]++;
                                break;
                        }

                        // TODO: check MMID, period

                        if( (opts->iflags&LINF_REC_COUNTER) !=0)
                            if( (pstats->record_count%100) == 0)OUT_CTTY("\b\b\b\b\b\b%6d",pstats->record_count);

                        pstats->record_count++;

                        uint32_t next_ofs=ofs_test+LI_OREC_SIZE;
                        //                        OUT_CTTY("rcount[%d] ofs_test[%"FMT_U32"] next_ofs:%"FMT_U32"\r\n",pstats->record_count,ofs_test,next_ofs);
                        iow_fseek(self->fp,next_ofs);

                        // let user interrupt
                        byte brk=0;
                        brk=iow_get_char(false);
                        if(brk=='\r' || brk=='\n' || brk==CTRL_D)break;

                    }else{
                        // else read error
                        XTRACE(0xF);
                    }
                }else{
                    pstats->err_bin[E_SYNC]++;
                    if(test<0)break;
                    OUT_ERR("ERR - fread test[%d] fp[%"FMT_U32"] feof[%d] ferr[%d]\r\n",test,limpl_ftell(self), limpl_feof(self), limpl_ferr(self));
                }
            }// end while

            iow_fclose(self->fp);

            if( (opts->iflags&LINF_REC_COUNTER) !=0)
                OUT_CTTY("\b\b\b\b\b\b%6d",pstats->record_count);

            // sum errors
            int i=0;
            for(i=0;i<EBIN_COUNT;i++){
                pstats->error_count+=pstats->err_bin[i];
            }

            // output summary
            if( ((opts->iflags&LINF_FMT_PRETTY)!=0) ||
               ((opts->iflags&LINF_FMT_FLAGS)==0)){
                OUT_CTTY("\r\nLog Summary [%s]\r\n",self->fpath);
                OUT_CTTY("file len         : %15"FMT_U32"\r\n",pstats->fsize);
                OUT_CTTY("record count     : %15d\r\n",pstats->record_count);
                char *tstr = striso8601(NULL,pstats->time_bin[T_FIRST],ISO8601_PREC_MSEC);
                OUT_CTTY("time first       : %15"FMT_U64"/%s\r\n",pstats->time_bin[T_FIRST],tstr);
                tstr = striso8601(NULL,pstats->time_bin[T_LAST],ISO8601_PREC_MSEC);
                OUT_CTTY("time last        : %15"FMT_U64"/%s\r\n",pstats->time_bin[T_LAST],tstr);
                tstr = striso8601(NULL,pstats->time_bin[T_RNGSTART],ISO8601_PREC_MSEC);
                OUT_CTTY("time range start : %15"FMT_U64"/%s\r\n",pstats->time_bin[T_RNGSTART],tstr);
                tstr = striso8601(NULL,pstats->time_bin[T_RNGEND],ISO8601_PREC_MSEC);
                OUT_CTTY("time range end   : %15"FMT_U64"/%s\r\n",pstats->time_bin[T_RNGEND],tstr);
                OUT_CTTY("seq first        : %15"FMT_U32"\r\n",pstats->seq_bin[SEQ_FIRST]);
                OUT_CTTY("seq last         : %15"FMT_U32"\r\n",pstats->seq_bin[SEQ_LAST]);
                OUT_CTTY("seq range start  : %15"FMT_U32"\r\n",pstats->seq_bin[SEQ_RNGSTART]);
                OUT_CTTY("seq range end    : %15"FMT_U32"\r\n",pstats->seq_bin[SEQ_RNGEND]);
                OUT_CTTY("Error Summary    :\r\n");
                OUT_CTTY("error count      : %15d\r\n",pstats->error_count);
                for(i=0;i<EBIN_COUNT;i++){
                    OUT_CTTY("  %14s  %10s%6d\r\n",linfo_ebin_names[i],"",pstats->err_bin[i]);
                }
                OUT_CTTY("Record Summary:\r\n");
                for(i=0;i<RBIN_COUNT;i++){
                    OUT_CTTY("  %14s  %10s%6d\r\n",linfo_rbin_names[i],"",pstats->record_bin[i]);
                }
            }

            if( (opts->iflags&LINF_FMT_CSV)!=0 ){
                if( (opts->iflags&LINF_CSV_HEADER)!=0){
                    OUT_CTTY("file_len,rec_count,tfirst,tlast,tmin,tmax,");
                    OUT_CTTY("seq_first,seq_last,seq_min,seq_max,err_count,");
                    OUT_CTTY("E_SYNC,E_TMZERO,E_TMMONO,E_CTINVAL,E_SEQINVAL,");
                    OUT_CTTY("E_LENINVAL,E_RTID,E_RMID,RMID_INVAL,RMID_NONE,");
                    OUT_CTTY("RMID_SAMPLE,RMID_SCAL,RMID_ZCAL,RTID_INVAL,RTID_ZRECV1\r\n");
                }
                OUT_CTTY("%"FMT_U32",",pstats->fsize);
                OUT_CTTY("%d,",pstats->record_count);

                if( (opts->iflags&LINF_TIME_ISO) !=0){
                    char *tstr = striso8601(NULL,pstats->time_bin[T_FIRST],ISO8601_PREC_MSEC);
                    OUT_CTTY("%s,",tstr);
                    tstr = striso8601(NULL,pstats->time_bin[T_LAST],ISO8601_PREC_MSEC);
                    OUT_CTTY("%s,",tstr);
                    tstr = striso8601(NULL,pstats->time_bin[T_RNGSTART],ISO8601_PREC_MSEC);
                    OUT_CTTY("%s,",tstr);
                    tstr = striso8601(NULL,pstats->time_bin[T_RNGEND],ISO8601_PREC_MSEC);
                    OUT_CTTY("%s,",tstr);
                }else{
                    OUT_CTTY("%"FMT_U64",",pstats->time_bin[T_FIRST]);
                    OUT_CTTY("%"FMT_U64",",pstats->time_bin[T_LAST]);
                    OUT_CTTY("%"FMT_U64",",pstats->time_bin[T_RNGSTART]);
                    OUT_CTTY("%"FMT_U64",",pstats->time_bin[T_RNGEND]);
                }

                OUT_CTTY("%"FMT_U32",",pstats->seq_bin[SEQ_FIRST]);
                OUT_CTTY("%"FMT_U32",",pstats->seq_bin[SEQ_LAST]);
                OUT_CTTY("%"FMT_U32",",pstats->seq_bin[SEQ_RNGSTART]);
                OUT_CTTY("%"FMT_U32",",pstats->seq_bin[SEQ_RNGEND]);
                OUT_CTTY("%d,",pstats->error_count);
                for(i=0;i<EBIN_COUNT;i++){
                    OUT_CTTY("%d,",pstats->err_bin[i]);
                }
                for(i=0;i<RBIN_COUNT;i++){
                    OUT_CTTY("%d%s",pstats->record_bin[i],(i<(RBIN_COUNT-1)?",":"\r\n"));
                }
            }
            retval=ERR_SUCCESS;

        }

    }// else invalid arg
    return retval;
}

//////////////////////////////////////
// text (line) implementation
//////////////////////////////////////

