/* packet-wav-new.c
 * Routines for wav file dissection
 * Copyright 2011, andrewl/crackmes.de
 *
 * This version simplifies plugin writing by dropping the ability to
 * search on header fields and such - all fields are added textually
 * under a "dummy" registered header field and tree. All fields and items
 * are of type NONE: we do all display formatting ourselves via the *_format()
 * functions.
 *
 * 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 2 of the License, or
 * (at your option) any later version.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "config.h"

#include <glib.h>

#include <epan/packet.h>
#include <epan/prefs.h>
#include <epan/etypes.h>

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

/*****************************************************************************
 * globals
 *****************************************************************************/

// int identifier of protocol returned via proto_register_protocol()
static int proto_wav = -1;
// int identifier of header field assigned via proto_register_field_array()
static int hf_RiffHeader = -1; /* riff header and subfields */
/* dummy field */
static gint hf_dummy = -1;
static hf_register_info hf_dummy_array[] = 
         {{ &hf_dummy, {".", ".", FT_NONE, BASE_NONE, 0, 0, 0, HFILL }}};
/* dummy tree type */
static gint ett_dummy = -1;
static gint *ett_dummy_array[] = { &ett_dummy };

/*****************************************************************************
 * wav structs
 *****************************************************************************/
/* made after reading:
   https://ccrma.stanford.edu/courses/422/projects/WaveFormat/ */
typedef struct _RiffHeader {
    guint32 ChunkID;
    guint32 ChunkSize;
    guint32 Format;
} RiffHeader;

typedef struct _FmtChunk {
    guint32 Subchunk1ID;
    guint32 Subchunk1Size;
    guint16 AudioFormat;
    guint16 NumChannels;
    guint32 SampleRate;
    guint32 ByteRate;
    guint16 BlockAlign;
    guint16 BitsPerSample;
} FmtChunk;

typedef struct _DataChunk {
    guint32 Subchunk2ID;
    guint32 Subchunk2Size;
    guint8 *data; /* struct reader will allocate this */
} DataChunk;

/*****************************************************************************
 * pretty-print of defined types
 *****************************************************************************/

char * AudioFormat_to_string(int format)
{
    switch(format) {
        case 1:
            return "PCM";
        default:
            return "UNKNOWN";
    }
}
/*****************************************************************************
 * struct readers slurp data off tvb into a handy struct
 *****************************************************************************/
void read_RiffHeader(tvbuff_t *tvb, int offset, RiffHeader *rh)
{
    int ostart = offset;

    rh->ChunkID = tvb_get_ntohl(tvb, offset);
    offset += sizeof(rh->ChunkID);
    rh->ChunkSize = tvb_get_letohl(tvb, offset);
    offset += sizeof(rh->ChunkSize);
    rh->Format = tvb_get_letohl(tvb, offset);
    offset += sizeof(rh->Format);
    
    if((offset - ostart) != sizeof(RiffHeader)) {
        printf("ERROR: bad struct translation at %s:%s\n", __FILE__, __LINE__);
    }
}

void read_FmtChunk(tvbuff_t *tvb, int offset, FmtChunk *rh)
{
    int ostart = offset;

    rh->Subchunk1ID = tvb_get_ntohl(tvb, offset);
    offset += sizeof(rh->Subchunk1ID);
    rh->Subchunk1Size = tvb_get_letohl(tvb, offset);
    offset += sizeof(rh->Subchunk1Size);
    rh->AudioFormat = tvb_get_letohs(tvb, offset);
    offset += sizeof(rh->AudioFormat);
    rh->NumChannels = tvb_get_letohs(tvb, offset);
    offset += sizeof(rh->NumChannels);
    rh->SampleRate = tvb_get_letohl(tvb, offset);
    offset += sizeof(rh->SampleRate);
    rh->ByteRate = tvb_get_letohl(tvb, offset);
    offset += sizeof(rh->ByteRate);
    rh->BlockAlign = tvb_get_letohs(tvb, offset);
    offset += sizeof(rh->BlockAlign);
    rh->BitsPerSample = tvb_get_letohs(tvb, offset);
    offset += sizeof(rh->BitsPerSample);
           
    if((offset - ostart) != sizeof(FmtChunk)) {
        printf("ERROR: bad struct translation at %s:%s\n", __FILE__, __LINE__);
    }
}

void read_DataChunk(tvbuff_t *tvb, int offset, DataChunk *dc)
{
    int i, ostart = offset;

    dc->Subchunk2ID = tvb_get_ntohl(tvb, offset);
    offset += sizeof(dc->Subchunk2ID);
    dc->Subchunk2Size = tvb_get_letohl(tvb, offset);
    offset += sizeof(dc->Subchunk2Size);
    /* alloc, read the variable part */
    /* want to use ep_alloc() here, but size may be too big */
    /* eg: emem.c:732: failed assertion "size<((10 * 1024 * 1024)>>2)" */
    dc->data = g_malloc(dc->Subchunk2Size);
    tvb_memcpy(tvb, dc->data, offset, dc->Subchunk2Size);

    // no sanity check here, variable sized struct
}

/*****************************************************************************
 * consumers (read and build a section of the proto tree)
 *****************************************************************************/

int consume_RiffHeader(tvbuff_t *tvb, int offset, proto_tree *tree)
{
    /* misc variables */
    int ostart;

    /* read in struct */
    RiffHeader rh;
    read_RiffHeader(tvb, offset, &rh);

    /* set up item and tree */
    proto_item *item;
    proto_tree *subtree;
    item = proto_tree_add_none_format(tree, hf_dummy, tvb, offset, -1, 
        "RIFF Header");
    subtree = proto_item_add_subtree(item, ett_dummy);

    /* add every struct field */
    proto_tree_add_none_format(subtree, hf_dummy, tvb, offset, 
        sizeof(rh.ChunkID), "ChunkID: %c%c%c%c", rh.ChunkID >> 24, 
        (rh.ChunkID >> 16) & 0xFF, (rh.ChunkID >> 8) & 0xFF, rh.ChunkID & 0xFF);
    offset += sizeof(rh.ChunkID);

    proto_tree_add_none_format(subtree, hf_dummy, tvb, offset, 
        sizeof(rh.ChunkSize), "ChunkSize: 0x%08X (%d)", rh.ChunkSize, rh.ChunkSize);
    offset += sizeof(rh.ChunkSize);

    proto_tree_add_none_format(subtree, hf_dummy, tvb, offset, 
        sizeof(rh.Format), "Format: 0x%08X (%d)", rh.Format, rh.Format);
    offset += sizeof(rh.Format);

    /* set len */
    proto_item_set_len(item, sizeof(rh));

    return sizeof(rh);
}

int consume_FmtChunk(tvbuff_t *tvb, int offset, proto_tree *tree)
{
    /* misc variables */
    int ostart = offset;

    /* read in struct */
    FmtChunk fc;
    read_FmtChunk(tvb, offset, &fc);

    /* set up item and tree */
    proto_item *item;
    proto_tree *subtree;
    item = proto_tree_add_none_format(tree, hf_dummy, tvb, offset, -1, 
        "Format Subchunk");
    subtree = proto_item_add_subtree(item, ett_dummy);

    /* add every struct field */
    proto_tree_add_none_format(subtree, hf_dummy, tvb, offset, 
        sizeof(fc.Subchunk1ID), "Subchunk1ID: %c%c%c%c", fc.Subchunk1ID >> 24, 
        (fc.Subchunk1ID >> 16) & 0xFF, (fc.Subchunk1ID >> 8) & 0xFF, fc.Subchunk1ID & 0xFF);

    offset += sizeof(fc.Subchunk1ID);

    proto_tree_add_none_format(subtree, hf_dummy, tvb, offset, 
        sizeof(fc.Subchunk1Size), "Subchunk1Size: 0x%08X (%d)", fc.Subchunk1Size, fc.Subchunk1Size);
    offset += sizeof(fc.Subchunk1Size);

    proto_tree_add_none_format(subtree, hf_dummy, tvb, offset, 
        sizeof(fc.AudioFormat), "AudioFormat: 0x%08X (%d) (%s)", fc.AudioFormat, 
        fc.AudioFormat, AudioFormat_to_string(fc.AudioFormat));
    offset += sizeof(fc.AudioFormat);

    proto_tree_add_none_format(subtree, hf_dummy, tvb, offset, 
        sizeof(fc.NumChannels), "NumChannels: 0x%08X (%d)", fc.NumChannels, fc.NumChannels);
    offset += sizeof(fc.NumChannels);

    proto_tree_add_none_format(subtree, hf_dummy, tvb, offset, 
        sizeof(fc.SampleRate), "SampleRate: 0x%08X (%d)", fc.SampleRate, fc.SampleRate);
    offset += sizeof(fc.SampleRate);

    proto_tree_add_none_format(subtree, hf_dummy, tvb, offset, 
        sizeof(fc.ByteRate), "ByteRate: 0x%08X (%d)", fc.ByteRate, fc.ByteRate);
    offset += sizeof(fc.ByteRate);

    proto_tree_add_none_format(subtree, hf_dummy, tvb, offset, 
        sizeof(fc.BlockAlign), "BlockAlign: 0x%08X (%d)", fc.BlockAlign, fc.BlockAlign);
    offset += sizeof(fc.BlockAlign);

    proto_tree_add_none_format(subtree, hf_dummy, tvb, offset, 
        sizeof(fc.BitsPerSample), "BitsPerSample: 0x%08X (%d)", fc.BitsPerSample, fc.BitsPerSample);
    offset += sizeof(fc.BitsPerSample);

    /* set len */
    proto_item_set_len(item, offset-ostart);

    return offset-ostart;
}

int consume_DataChunk(tvbuff_t *tvb, int offset, proto_tree *tree,
        /* extra data needed for proper DataChunk parsing */ FmtChunk *fc)
{
    /* misc variables */
    int i, j, ostart = offset;

    /* read in struct */
    DataChunk dc;
    memset(&dc, 0, sizeof(dc));
    read_DataChunk(tvb, offset, &dc);

    /* set up item and tree */
    proto_item *item, *item2;
    proto_tree *subtree, *subtree2;
    item = proto_tree_add_none_format(tree, hf_dummy, tvb, offset, -1, 
        "Data Subchunk");
    subtree = proto_item_add_subtree(item, ett_dummy);

    proto_tree_add_none_format(subtree, hf_dummy, tvb, offset, 
        sizeof(dc.Subchunk2ID), "Subchunk2ID: %c%c%c%c", dc.Subchunk2ID >> 24, 
        (dc.Subchunk2ID >> 16) & 0xFF, (dc.Subchunk2ID >> 8) & 0xFF, dc.Subchunk2ID & 0xFF);
    offset += sizeof(dc.Subchunk2ID);

    proto_tree_add_none_format(subtree, hf_dummy, tvb, offset, 
        sizeof(dc.Subchunk2Size), "Subchunk2Size: 0x%08X (%d)", dc.Subchunk2Size, dc.Subchunk2Size);
    offset += sizeof(dc.Subchunk2Size);

    if(fc->NumChannels > 2) {
        printf("ERROR: not equipped to handle more than 2 channels!\n");
    }

    /* calculations */
    int BytesPerSample = fc->BitsPerSample / 8;
    int BytesPerSampleTuple = fc->NumChannels * BytesPerSample;
    int nSampleTuples = dc.Subchunk2Size / BytesPerSampleTuple;

    /* loop over each sample tuple */
    for(i=0; i<nSampleTuples; ++i) {
            item2 = proto_tree_add_none_format(subtree, hf_dummy, tvb,
                            offset, BytesPerSample * fc->NumChannels, "sample tuple 0x%X (%d))", i);
            
            subtree2 = proto_item_add_subtree(item2, ett_dummy);
            
            for(j=0; j<fc->NumChannels; ++j) {
                proto_tree_add_none_format(subtree2, hf_dummy, tvb,
                    offset, BytesPerSample, "channel %d sample", j);

                offset += BytesPerSample;
            }
    }

    if(dc.data) {
        g_free(dc.data);
    }

    /* go back and set len */
    proto_item_set_len(item, offset-ostart);

    return offset-ostart;
}

/*****************************************************************************
 * main dissect code
 *****************************************************************************/
static int
dissect_wav(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
    /* Set up structures needed to add the protocol subtree and manage it */
	proto_item *item_root;
	proto_tree *tree_wav;
    gint offset = 0;
    gint ostart, oend;
    gint i;

    // check "RIFF" header
    if(tvb_get_bits32(tvb, 0, 32, 0) != 0x52494646) {
        return 0;
    }

	col_set_str(pinfo->cinfo, COL_PROTOCOL, "N/A");
	col_set_str(pinfo->cinfo, COL_INFO, "WAV File");

    /* summary mode or detail mode? */
	if (tree) {
		item_root = proto_tree_add_item(tree, proto_wav, tvb, 0, -1, 0);
		tree_wav = proto_item_add_subtree(item_root, ett_dummy);

        offset = 0;

        /* consume Riff */
        offset += consume_RiffHeader(tvb, offset, tree_wav);

        /* read (for future variable referral), consume Format Chunk */
        FmtChunk fc;
        read_FmtChunk(tvb, offset, &fc); 
        offset += consume_FmtChunk(tvb, offset, tree_wav);
    
        /* consume Data Chunk */
        consume_DataChunk(tvb, offset, tree_wav, /* extra info */ &fc);
	}
	
    return tvb_length(tvb);
}

void proto_register_wav(void)
{
    // register protocol
	proto_wav = proto_register_protocol("WAV File","wav", "wav_file_filter");

    // register fields (associated with protocol)
	proto_register_field_array(proto_wav, hf_dummy_array, array_length(hf_dummy_array));

    // register subtrees
	proto_register_subtree_array(ett_dummy_array, array_length(ett_dummy_array));
}


void proto_reg_handoff_wav(void)
{
	static gboolean initialized = FALSE;
    static dissector_handle_t h_dissector;

	if (!initialized) {
		h_dissector = new_create_dissector_handle(dissect_wav, proto_wav);

        /*  register us as a dissector at frame-level
            frame-level name/pattern gleaned from here:
            http://www.wireshark.org/lists/wireshark-dev/200707/msg00230.html */
        dissector_add("wtap_encap", WTAP_ENCAP_USER0, h_dissector);

		initialized = TRUE;
	} 
}

/*****************************************************************************
 * plugin interface (just version and two chunks)
 *****************************************************************************/

const gchar version[] = "0.0.0";

void plugin_register (void)
{
    proto_register_wav();
}

void plugin_reg_handoff(void)
{
    proto_reg_handoff_wav();
}


