/* packet-p25cai.c
 * Routines for APCO Project 25 Common Air Interface dissection
 * Copyright 2008, Michael Ossmann <mike@ossmann.com>
 *
 * $Id$
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * 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.
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

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

#define FRAME_SYNC_MAGIC 0x5575F5FF77FF

/* function prototypes */
void proto_reg_handoff_p25cai(void);
void dissect_voice(tvbuff_t *tvb, proto_tree *tree, int offset);
void dissect_lc(tvbuff_t *tvb, proto_tree *tree);
void dissect_es(tvbuff_t *tvb, proto_tree *tree);
void dissect_pdu(tvbuff_t *tvb, packet_info *pinfo, int offset, proto_tree *tree);
void dissect_pdu_response(tvbuff_t *tvb, packet_info *pinfo, int offset, proto_tree *tree, guint8 *header, int num_blocks);
void dissect_pdu_unconfirmed(tvbuff_t *tvb, packet_info *pinfo, int offset, proto_tree *tree, guint8 *header, int num_blocks);
void dissect_pdu_confirmed(tvbuff_t *tvb, packet_info *pinfo, int offset, proto_tree *tree, guint8 *header, int num_blocks);
void dissect_pdu_ambt(tvbuff_t *tvb, packet_info *pinfo, int offset, proto_tree *tree, guint8 *header, int num_blocks);
tvbuff_t* extract_status_symbols(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int *outbound);
tvbuff_t* build_hdu_tvb(tvbuff_t *tvb, packet_info *pinfo, int offset);
tvbuff_t* build_tsdu_tvb(tvbuff_t *tvb, packet_info *pinfo, int offset);
tvbuff_t* build_term_lc_tvb(tvbuff_t *tvb, packet_info *pinfo, int offset);
tvbuff_t* build_ldu_lsd_tvb(tvbuff_t *tvb, packet_info *pinfo, int offset);
tvbuff_t* build_ldu_lc_tvb(tvbuff_t *tvb, packet_info *pinfo, int offset);
tvbuff_t* build_ldu_es_tvb(tvbuff_t *tvb, packet_info *pinfo, int offset);
void data_deinterleave(tvbuff_t *tvb, guint8 *deinterleaved, int bit_offset);
void trellis_1_2_decode(guint8 *encoded, guint8 *decoded, int offset);
void trellis_3_4_decode(guint8 *encoded, guint8 *decoded, int offset);
guint8 golay_18_6_8_decode(guint32 codeword);
guint16 golay_24_12_8_decode(guint32 codeword);
guint8 hamming_10_6_3_decode(guint32 codeword);
void cyclic_16_8_5_decode(guint32 codeword, guint8 *decoded);
void rs_24_12_13_decode(guint8 *codeword, guint8 *decoded);
void rs_24_16_9_decode(guint8 *codeword, guint8 *decoded);
void rs_36_20_17_decode(guint8 *codeword, guint8 *decoded);
int find_min(guint8 list[], int len);
int count_bits(unsigned int n);

/* Initialize the protocol and registered fields */
static int proto_p25cai = -1;
static int hf_p25cai_fs = -1;
static int hf_p25cai_nid = -1;
static int hf_p25cai_nac = -1;
static int hf_p25cai_duid = -1;
static int hf_p25cai_hdu = -1;
static int hf_p25cai_tsbk = -1;
static int hf_p25cai_pdu = -1;
static int hf_p25cai_ldu1 = -1;
static int hf_p25cai_ldu2 = -1;
static int hf_p25cai_mi = -1;
static int hf_p25cai_mfid = -1;
static int hf_p25cai_algid = -1;
static int hf_p25cai_kid = -1;
static int hf_p25cai_tgid = -1;
static int hf_p25cai_ss_parent = -1;
static int hf_p25cai_ss = -1;
static int hf_p25cai_lc = -1;
static int hf_p25cai_lcf = -1;
static int hf_p25cai_lbf = -1;
static int hf_p25cai_ptbf = -1;
static int hf_p25cai_isp_opcode = -1;
static int hf_p25cai_osp_opcode = -1;
static int hf_p25cai_unknown_opcode = -1;
static int hf_p25cai_args = -1;
static int hf_p25cai_crc = -1;
static int hf_p25cai_imbe = -1;
static int hf_p25cai_lsd = -1;
static int hf_p25cai_es = -1;
static int hf_p25cai_an = -1;
static int hf_p25cai_io = -1;
static int hf_p25cai_pdu_format = -1;
static int hf_p25cai_sapid = -1;
static int hf_p25cai_llid = -1;
static int hf_p25cai_fmf = -1;
static int hf_p25cai_btf = -1;
static int hf_p25cai_poc = -1;
static int hf_p25cai_syn = -1;
static int hf_p25cai_ns = -1;
static int hf_p25cai_fsnf = -1;
static int hf_p25cai_dho = -1;
static int hf_p25cai_db = -1;
static int hf_p25cai_dbsn = -1;
static int hf_p25cai_crc9 = -1;
static int hf_p25cai_ud = -1;
static int hf_p25cai_packet_crc = -1;
static int hf_p25cai_class = -1;
static int hf_p25cai_type = -1;
static int hf_p25cai_status = -1;
static int hf_p25cai_x = -1;
static int hf_p25cai_sllid = -1;

/* Field values */
static const value_string data_unit_ids[] = {
	{ 0x0, "Header Data Unit" },
	{ 0x3, "Terminator without Link Control" },
	{ 0x5, "Logical Link Data Unit 1" },
	{ 0x7, "Trunking Signaling Data Unit" },
	{ 0xA, "Logical Link Data Unit 2" },
	{ 0xC, "Packet Data Unit" },
	{ 0xF, "Terminator with Link Control" },
	{ 0, NULL }
};

static const value_string network_access_codes[] = {
	{ 0x293, "Default NAC" },
	{ 0xF7E, "Receiver to open on any NAC" },
	{ 0xF7F, "Repeater to receive and retransmit any NAC" },
	{ 0, NULL }
};

static const value_string manufacturer_ids[] = {
	/* from http://ftp.tiaonline.org/TR-8/TR815/Public/MFID_Assignments_080723.doc */
	{ 0x00, "Standard MFID (pre-2001)" },
	{ 0x01, "Standard MFID (post-2001)" },
	{ 0x09, "Aselsan Inc." },
	{ 0x10, "Relm / BK Radio" },
	{ 0x18, "EADS Public Safety Inc." },
	{ 0x20, "Cycomm" },
	{ 0x28, "Efratom Time and Frequency Products, Inc" },
	{ 0x30, "Com-Net Ericsson" },
	{ 0x34, "Etherstack" },
	{ 0x38, "Datron" },
	{ 0x40, "Icom" },
	{ 0x48, "Garmin" },
	{ 0x50, "GTE" },
	{ 0x55, "IFR Systems" },
	{ 0x5A, "INIT Innovations in Transportation, Inc" },
	{ 0x60, "GEC-Marconi" },
	{ 0x64, "Harris Corp." },
	{ 0x68, "Kenwood Communications" },
	{ 0x70, "Glenayre Electronics" },
	{ 0x74, "Japan Radio Co." },
	{ 0x78, "Kokusai" },
	{ 0x7C, "Maxon" },
	{ 0x80, "Midland" },
	{ 0x86, "Daniels Electronics Ltd." },
	{ 0x90, "Motorola" },
	{ 0xA0, "Thales" },
	{ 0xA4, "M/A-COM" },
	{ 0xB0, "Raytheon" },
	{ 0xC0, "SEA" },
	{ 0xC8, "Securicor" },
	{ 0xD0, "ADI" },
	{ 0xD8, "Tait Electronics" },
	{ 0xE0, "Teletec" },
	{ 0xF0, "Transcrypt International" },
	{ 0xF8, "Vertex Standard" },
	{ 0xFC, "Zetron, Inc" },
	{ 0, NULL }
};

/* TODO: use link_control_opcodes instead */
static const value_string link_control_formats[] = {
	{ 0x00, "Group Call Format" },
	{ 0x03, "Individual Call Format" },
	{ 0x80, "Encrypted Group Call Format" },
	{ 0x83, "Encrypted Individual Call Format" },
	{ 0, NULL }
};

static const value_string link_control_opcodes[] = {
	/* from AABF */
	{ 0x00, "Group Voice Channel User (LCGVR)" }, /* LC_GRP_V_CH_USR */
	{ 0x01, "Reserved" },
	{ 0x02, "Group Voice Channel Update (LCGVU)" }, /* LC_GRP_V_CH_UPDT */
	{ 0x03, "Unit to Unit Voice Channel User (LCUVR)" }, /* LC_UU_V_CH_USR */
	{ 0x04, "Group Voice Channel Update - Explicit (LCGVUX)" }, /* LC_GRP_CH_UPDT_EXP */
	{ 0x05, "Unit to Unit Answer Request (LCUAQ)" }, /* LC_UU_ANS_REQ */
	{ 0x06, "Telephone Interconnect Voice Channel User (LCTVR)" }, /* LC_TELE_INT_V_CH_USR */
	{ 0x07, "Telephone Interconnect Answer Request (LCTAQ)" }, /* LC_TELE_INT_ANS_REQ */
	{ 0x08, "Reserved" },
	{ 0x09, "Reserved" },
	{ 0x0A, "Reserved" },
	{ 0x0B, "Reserved" },
	{ 0x0C, "Reserved" },
	{ 0x0D, "Reserved" },
	{ 0x0E, "Reserved" },
	{ 0x0F, "Call Termination/Cancellation (LCCT)" }, /* LC_CALL_TRM_CAN */
	{ 0x10, "Group Affiliation Query (LCGAQ)" }, /* LC_GRP_AFF_Q */
	{ 0x11, "Unit Registration Command (LCRC)" }, /* LC_U_REG_CMD */
	{ 0x12, "Unit Authentication Command (LCAC)" }, /* LC_AUTH_CMD */
	{ 0x13, "Status Query (LCSQ)" }, /* LC_STS_Q */
	{ 0x14, "Status Update (LCSU)" }, /* LC_STA_UPDT */
	{ 0x15, "Message Update (LCMU)" }, /* LC_MSG_UPDT */
	{ 0x16, "Call Alert (LCCA)" }, /* LC_CALL_ALRT */
	{ 0x17, "Extended Function Command (LCEFC)" }, /* LC_EXT_FNCT_CMD */
	{ 0x18, "Channel Identifier Update (LCCIU)" }, /* LC_CH_ID_UPDT */
	{ 0x19, "Channel Identifier Update - Explicit (LCCIUX)" }, /* LC_CH_ID_UPDT_EXP */
	{ 0x1A, "Reserved" },
	{ 0x1B, "Reserved" },
	{ 0x1C, "Reserved" },
	{ 0x1D, "Reserved" },
	{ 0x1E, "Reserved" },
	{ 0x1F, "Reserved" },
	{ 0x20, "System Service Broadcast (LCSSB)" }, /* LC_SYS_SRV_BCST */
	{ 0x21, "Secondary Control Channel Broadcast (LCSCB)" }, /* LC_SCCB */
	{ 0x22, "Adjacent Site Status Broadcast (LCASB)" }, /* LC_ADJ_STS_BCST */
	{ 0x23, "RFSS Status Broadcast (LCRSB)" }, /* LC_RFSS_STS_BCST */
	{ 0x24, "Network Status Broadcast (LCNSB)" }, /* LC_NET_STS_BCST */
	{ 0x25, "Protection Parameter Broadcast (LCPPB)" }, /* LC_P_PARM_BCST */
	{ 0x26, "Secondary Control Channel Broadcast - Explicit (LCSCBX)" }, /* LC_SCCB_EXP */
	{ 0x27, "Adjacent Site Status Broadcast - Explicit (LCASBX)" }, /* LC_ADJ_STS_BCST_EXP */
	{ 0x28, "RFSS Status Broadcast - Explicit (LCRSBX)" }, /* LC_RFSS_STS_BCST_EXP */
	{ 0x29, "Network Status Broadcast - Explicit (LCNSBX)" }, /* LC_NET_STS_BCST_EXP */
	{ 0x2A, "Reserved" },
	{ 0x2B, "Reserved" },
	{ 0x2C, "Reserved" },
	{ 0x2D, "Reserved" },
	{ 0x2E, "Reserved" },
	{ 0x2F, "Reserved" },
	{ 0x30, "Reserved" },
	{ 0x31, "Reserved" },
	{ 0x32, "Reserved" },
	{ 0x33, "Reserved" },
	{ 0x34, "Reserved" },
	{ 0x35, "Reserved" },
	{ 0x36, "Reserved" },
	{ 0x37, "Reserved" },
	{ 0x38, "Reserved" },
	{ 0x39, "Reserved" },
	{ 0x3A, "Reserved" },
	{ 0x3B, "Reserved" },
	{ 0x3C, "Reserved" },
	{ 0x3D, "Reserved" },
	{ 0x3E, "Reserved" },
	{ 0x3F, "Reserved" },
	{ 0, NULL }
};

static const range_string logical_link_ids[] = {
	{ 0x000000, 0x000000, "No One" },
	{ 0x000001, 0x98967F, "General Use" },
	{ 0x989680, 0xFFFFFE, "Talk Group or Special Purpose Use" },
	{ 0xFFFFFF, 0xFFFFFF, "Everyone" },
	{ 0, 0, NULL }
};

static const value_string talk_group_ids[] = {
	{ 0x0000, "No One" },
	{ 0x0001, "Default Talk Group" },
	{ 0xFFFF, "Everyone" },
	{ 0, NULL }
};

static const value_string key_ids[] = {
	{ 0x0000, "Default Key ID" },
	{ 0, NULL }
};

static const value_string algorithm_ids[] = {
	/* mostly from http://ftp.tiaonline.org/TR-8/TR815/Public/ALGID_Guide_040528.doc */
	/* Type I */
	{ 0x00, "ACCORDION 1.3" },
	{ 0x01, "BATON (Auto Even)" },
	{ 0x02, "FIREFLY Type 1" },
	{ 0x03, "MAYFLY Type 1" },
	{ 0x04, "SAVILLE" },
	{ 0x41, "BATON (Auto Odd)" },
	/* Type III */
	{ 0x80, "Unencrypted message" },
	{ 0x81, "DES-OFB" },
	{ 0x82, "2 key Triple DES" },
	{ 0x83, "3 key Triple DES" },
	{ 0x84, "AES-256" },
	/* Motorola proprietary */
	/* FIXME maybe these should only be interpreted this way if mfid == 0x90 */
	{ 0x9F, "DES-XL" },
	{ 0xA0, "DVI-XL" },
	{ 0xA1, "DVP-XL" },
	{ 0, NULL }
};

static const value_string service_access_points[] = {
	{ 0x00, "Unencrypted User Data" },
	{ 0x01, "Encrypted User Data" },
	{ 0x02, "Circuit Data" },
	{ 0x03, "Circuit Data Control" },
	{ 0x04, "Packet Data" },
	{ 0x05, "Address Resolution Protocol" },
	{ 0x06, "SNDCP Packet Data Control" },
	{ 0x1F, "Extended Address" },
	{ 0x20, "Registration and Authorization" },
	{ 0x21, "Channel Reassignment" },
	{ 0x22, "System Configuration" },
	{ 0x23, "MR Loop-Back" },
	{ 0x24, "MR Statistics" },
	{ 0x25, "MR Out-of-Service" },
	{ 0x26, "MR Paging" },
	{ 0x27, "MR Configuration" },
	{ 0x28, "Unencrypted Key Management Message" },
	{ 0x29, "Encrypted Key Management Message" },
	{ 0x3D, "Trunking Control" },
	{ 0x3F, "Protected Trunking Control" },
	{ 0, NULL }
};

static const value_string emergency_indicators[] = {
	{ 0x0, "Routine, non-emergency condition" },
	{ 0x1, "Emergency condition" },
	{ 0, NULL }
};

static const value_string isp_opcodes[] = {
	/* voice service isp */
	/* TODO: dissect AABC page 43 */
	{ 0x00, "Group Voice Service Request" }, /* GRP_V_REQ */
	{ 0x04, "Unit To Unit Voice Service Request" }, /* UU_V_REQ */
	{ 0x05, "Unit To Unit Answer Response" }, /* UU_ANS_RSP */
	{ 0x08, "Telephone Interconnect Request - Explicit Dialing" }, /* TELE_INT_DIAL_REQ */
	{ 0x09, "Telephone Interconnect Request - Implicit Dialing" }, /* TELE_INT_PSTN_REQ */
	{ 0x0A, "Telephone Interconnect Answer Response" }, /* TELE_INT_ANS_RSP */

	/* data service isp */
	/* TODO: dissect AABC page 69 */
	{ 0x10, "Individual Data Service Request (obsolete)" }, /* IND_DATA_REQ */
	{ 0x11, "Group Data Service Request (obsolete)" }, /* GRP_DATA_REQ */
	{ 0x12, "SNDCP Data Channel Request" }, /* SN-DAT_CHN_REQ */
	{ 0x13, "SNDCP Data Page Response" }, /* SN-DAT_PAGE_RES */
	{ 0x14, "SNDCP Reconnect Request" }, /* SN-REC_REQ */

	/* control and status isp */
	/* TODO: dissect AABC page 86 */
	{ 0x20, "Acknowledge Response - Unit" }, /* ACK_RSP_U */
	{ 0x2E, "Authentication Query" }, /* AUTH_Q */
	{ 0x2F, "Authentication Response" }, /* AUTH_RSP */
	{ 0x1F, "Call Alert Request" }, /* CALL_ALRT_REQ */
	{ 0x23, "Cancel Service Request" }, /* CAN_SRV_REQ */
	{ 0x27, "Emergency Alarm Request" }, /* EMRG_ALRM_REQ */
	{ 0x24, "Extended Function Response" }, /* EXT_FNCT_RSP */
	{ 0x29, "Group Affiliation Query Response" }, /* GRP_AFF_Q_RSP */
	{ 0x28, "Group Affiliation Request" }, /* GRP_AFF_REQ */
	{ 0x32, "Identifier Update Request" }, /* IDEN_UP_REQ */
	{ 0x1C, "Message Update Request" }, /* MSG_UPDT_REQ */
	{ 0x30, "Protection Parameter Request" }, /* P_PARM_REQ */
	{ 0x1A, "Status Query Request" }, /* STS_Q_REQ */
	{ 0x19, "Status Query Response" }, /* STS_Q_RSP */
	{ 0x18, "Status Update Request" }, /* STS_UPDT_REQ */
	{ 0x2C, "Unit Registration Request" }, /* U_REG_REQ */
	{ 0x2B, "De-Registration Request" }, /* U_DE_REG_REQ */
	{ 0x2D, "Location Registration Request" }, /* LOC_REG_REQ */
	{ 0x1D, "Radio Unit Monitor Request" }, /* RAD_MON_REQ */
	{ 0x36, "Roaming Address Request" }, /* ROAM_ADDR_REQ */
	{ 0x37, "Roaming Address Response" }, /* ROAM_ADDR_RSP */

	{ 0, NULL }
};

static const value_string osp_opcodes[] = {
	/* voice service osp */
	/* TODO: dissect AABC page 53 */
	{ 0x00, "Group Voice Channel Grant" }, /* GRP_V_CH_GRANT */
	{ 0x02, "Group Voice Channel Grant Update" }, /* GRP_V_CH_GRANT_UPDT */
	{ 0x03, "Group Voice Channel Grant Update - Explicit" }, /* GRP_V_CH_GRANT_UPDT_EXP */
	{ 0x04, "Unit To Unit Voice Channel Grant" }, /* UU_V_CH_GRANT */
	{ 0x05, "Unit To Unit Answer Request" }, /* UU_ANS_REQ */
	{ 0x06, "Unit To Unit Voice Channel Grant Update" }, /* UU_V_CH_GRANT_UPDT */
	{ 0x08, "Telephone Interconnect Voice Channel Grant" }, /* TELE_INT_CH_GRANT */
	{ 0x09, "Telephone Interconnect Voice Channel Grant Update" }, /* TELE_INT_CH_GRANT_UPDT */
	{ 0x0A, "Telephone Interconnect Answer Request" }, /* TELE_INT_ANS_REQ */

	/* data service osp */
	/* TODO: dissect AABC page 76 */
	{ 0x10, "Individual Data Channel Grant (obsolete)" }, /* IND_DATA_CH_GRANT */
	{ 0x11, "Group Data Channel Grant (obsolete)" }, /* GRP_DATA_CH_GRANT */
	{ 0x12, "Group Data Channel Announcement (obsolete)" }, /* GRP_DATA_CH_ANN */
	{ 0x13, "Group Data Channel Announcement Explicit (obsolete)" }, /* GRP_DATA_CH_ANN_EXP */
	{ 0x14, "SNDCP Data Channel Grant" }, /* SN-DATA_CHN_GNT */
	{ 0x15, "SNDCP Data Page Request" }, /* SN-DATA_PAGE_REQ */
	{ 0x16, "SNDCP Data Channel Announcement - Explicit" }, /* SN-DAT_CHN_ANN_EXP */

	/* control and status osp */
	/* TODO: dissect AABC page 121 */
	{ 0x20, "Acknowledge Response - FNE" }, /* ACK_RSP_FNE */
	{ 0x3C, "Adjacent Status Broadcast" }, /* ADJ_STS_BCST */
	{ 0x2E, "Authentication Command" }, /* AUTH_CMD */
	{ 0x1F, "Call Alert" }, /* CALL_ALRT */
	{ 0x27, "Deny Response" }, /* DENY_RSP */
	{ 0x24, "Extended Function Command" }, /* EXT_FNCT_CMD */
	{ 0x2A, "Group Affiliation Query" }, /* GRP_AFF_Q */
	{ 0x28, "Group Affiliation Response" }, /* GRP_AFF_RSP */
	{ 0x3D, "Identifier Update" }, /* IDEN_UP */
	{ 0x1C, "Message Update" }, /* MSG_UPDT */
	{ 0x3B, "Network Status Broadcast" }, /* NET_STS_BCST */
	{ 0x3E, "Protection Parameter Broadcast" }, /* P_PARM_BCST */
	{ 0x3F, "Protection Parameter Update" }, /* P_PARM_UPDT */
	{ 0x21, "Queued Response" }, /* QUE_RSP */
	{ 0x3A, "RFSS Status Broadcast" }, /* RFSS_STS_BCST */
	{ 0x39, "Secondary Control Channel Broadcast" }, /* SCCB */
	{ 0x1A, "Status Query" }, /* STS_Q */
	{ 0x18, "Status Update" }, /* STS_UPDT */
	{ 0x38, "System Service Broadcast" }, /* SYS_SRV_BCST */
	{ 0x2D, "Unit Registration Command" }, /* U_REG_CMD */
	{ 0x2C, "Unit Registration Response" }, /* U_REG_RSP */
	{ 0x2F, "De-Registration Acknowledge" }, /* U_DE_REG_ACK */
	{ 0x2B, "Location Registration Response" }, /* LOC_REG_RSP */
	{ 0x1D, "Radio Unit Monitor Command" }, /* RAD_MON_CMD */
	{ 0x36, "Roaming Address Command" }, /* ROAM_ADDR_CMD */
	{ 0x37, "Roaming Address Update" }, /* ROAM_ADDR_UPDT */
	{ 0x35, "Time and Date Announcement" }, /* TIME_DATE_ANN */
	{ 0x34, "Identifier Update for VHF/UHF Bands" }, /* IDEN_UP_VU */
	{ 0x29, "Secondary Control Channel Broadcast - Explicit" }, /* SCCB_EXP */

	{ 0, NULL }
};

static const range_string cancel_reason_codes[] = {
	{ 0x00, 0x00, "No Reason Code" },
	{ 0x01, 0x0F, "Reserved" },
	{ 0x10, 0x10, "Terminate Queued Condition" },
	{ 0x11, 0x1F, "Reserved" },
	{ 0x20, 0x20, "Terminate Resource Assignment" },
	{ 0x21, 0x7F, "Reserved" },
	{ 0x80, 0xFF, "User or System Definable" },
	{ 0, 0, NULL }
};

static const range_string deny_response_reason_codes[] = {
	{ 0x00, 0x0F, "Reserved" },
	{ 0x10, 0x10, "The requestion unit is not valid" },
	{ 0x11, 0x11, "The requestion unit is not authorized for this service" },
	{ 0x12, 0x1F, "Reserved" },
	{ 0x20, 0x20, "The target unit is not valid" },
	{ 0x21, 0x21, "The target unit is not authorized for this service" },
	{ 0x22, 0x2E, "Reserved" },
	{ 0x2F, 0x2F, "Target unit has refused this call" },
	{ 0x30, 0x30, "The target group is not valid" },
	{ 0x31, 0x31, "The target group is not authorized for this service" },
	{ 0x32, 0x3F, "Reserved" },
	{ 0x40, 0x40, "Invalid dialing" },
	{ 0x41, 0x41, "Telephone number is not authorized" },
	{ 0x42, 0x42, "PSTN address is not valid" },
	{ 0x43, 0x4F, "Reserved" },
	{ 0x50, 0x50, "Call time-out has occurred" },
	{ 0x51, 0x51, "Landline has terminated this call" },
	{ 0x52, 0x52, "Subscriber unit has terminated this call" },
	{ 0x53, 0x5E, "Reserved" },
	{ 0x5F, 0x5F, "Call has been pre-empted" },
	{ 0x60, 0x60, "Site access denial" },
	{ 0x61, 0xEF, "User or system definable" },
	{ 0xF0, 0xF0, "The call options are not valid for thie service" },
	{ 0xF1, 0xF1, "Protection service option is not valid" },
	{ 0xF2, 0xF2, "Duplex service option is not valid" },
	{ 0xF3, 0xF3, "Circuit or packet mode service option is not valid" },
	{ 0xF4, 0xFE, "User or system definable" },
	{ 0xFF, 0xFF, "The system does not support this service" },
	{ 0, 0, NULL }
};

static const range_string queued_response_reason_codes[] = {
	{ 0x00, 0x0F, "Reserved" },
	{ 0x10, 0x10, "The requesting unit is active in another service" },
	{ 0x11, 0x1F, "Reserved" },
	{ 0x10, 0x10, "The target unit is active in another service" },
	{ 0x21, 0x2E, "Reserved" },
	{ 0x2F, 0x2F, "The target unit has queued the call" },
	{ 0x30, 0x30, "The target group is currently active" },
	{ 0x31, 0x3F, "Reserved" },
	{ 0x40, 0x40, "Channel resources are not currently active" },
	{ 0x41, 0x41, "Telephone resources are not currently active" },
	{ 0x42, 0x42, "Data resources are not currently active" },
	{ 0x43, 0x4F, "Reserved" },
	{ 0x50, 0x50, "Superseding service currently active" },
	{ 0x51, 0x7F, "Reserved" },
	{ 0x80, 0xFF, "User or System Definable" },
	{ 0, 0, NULL }
};

static const value_string pdu_formats[] = {
	{ 0x03, "Response Packet" },
	{ 0x15, "Unconfirmed Data Packet" },
	{ 0x16, "Confirmed Data Packet" },
	{ 0x17, "Alternate Multiple Block Trunking Control Packet" },
	{ 0, NULL }
};

static const true_false_string inbound_outbound = {
	"Outbound",
	"Inbound"
};

static const value_string status_symbols[] = {
	{ 0x0, "Unknown" },
	{ 0x1, "Busy" },
	{ 0x2, "Unknown" },
	{ 0x3, "Idle (start of inbound slot)" },
	{ 0, NULL }
};

/* Initialize the subtree pointers */
static gint ett_p25cai = -1;
static gint ett_ss = -1;
static gint ett_nid = -1;
static gint ett_du = -1;
static gint ett_lc = -1;
static gint ett_es = -1;
static gint ett_db = -1;

/* Code to actually dissect the packets */
static int
dissect_p25cai(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{

/* Set up structures needed to add the protocol subtree and manage it */
	proto_item *ti, *nid_item, *du_item;
	proto_tree *p25cai_tree, *nid_tree, *du_tree;
	int offset, outbound;
	tvbuff_t *extracted_tvb, *du_tvb;
	guint8 duid, last_block, mfid;

/*  If this doesn't look like a P25 CAI frame, give up and return 0 so that
 *  perhaps another dissector can take over.
 */

	/* Check that there's enough data */
	if (tvb_length(tvb) < 14) /* P25 CAI smallest packet size */
		return 0;

	/* Check for correct Frame Sync value
	 *
	 * This is commented out for now so that we can deal with symbol errors.
	 * We could use it if we have error correction working before frames get
	 * passed to wireshark.  Perhaps a more generally useful alternative would
	 * look for a minimum number of matching bits rather than requiring a 100%
	 * match.
	 *
	 * guint64 fs;
	 * fs = tvb_get_ntoh64(tvb, 0) >> 16;
	 * if (fs != FRAME_SYNC_MAGIC)
	 * 	return 0;
	 */

/* Make entries in Protocol column and Info column on summary display */
	if (check_col(pinfo->cinfo, COL_PROTOCOL))
		col_set_str(pinfo->cinfo, COL_PROTOCOL, "P25 CAI");

	/* Clear the Info column first just in case of duid fetching failure. */
	if (check_col(pinfo->cinfo, COL_INFO))
		col_clear(pinfo->cinfo, COL_INFO);

	duid = tvb_get_guint8(tvb, 7) & 0xF;

	if (check_col(pinfo->cinfo, COL_INFO))
		col_set_str(pinfo->cinfo, COL_INFO, val_to_str(duid, data_unit_ids, "Unknown Data Unit (0x%02x)"));

	/* see if we are being asked for details */
	if (tree) {

/* create display subtree for the protocol */
		ti = proto_tree_add_item(tree, proto_p25cai, tvb, 0, -1, FALSE);
		p25cai_tree = proto_item_add_subtree(ti, ett_p25cai);
		offset = 0;

		/* top level P25 CAI tree */
		proto_tree_add_item(p25cai_tree, hf_p25cai_fs, tvb, offset, 6, FALSE);
		offset += 6;

		/* Extract Status Symbols */
		extracted_tvb = extract_status_symbols(tvb, pinfo, p25cai_tree, &outbound);

		/* process extracted_tvb from here on */

		nid_item = proto_tree_add_item(p25cai_tree, hf_p25cai_nid, extracted_tvb, offset, 8, FALSE);

		/* NID subtree */
		nid_tree = proto_item_add_subtree(nid_item, ett_nid);
		proto_tree_add_item(nid_tree, hf_p25cai_nac, extracted_tvb, offset, 2, FALSE);
		proto_tree_add_item(nid_tree, hf_p25cai_duid, extracted_tvb, offset, 2, FALSE);
		offset += 8;

		switch (duid) {
		/* Header Data Unit */
		case 0x0:
			du_tvb = build_hdu_tvb(extracted_tvb, pinfo, offset);
			offset = 0;
			du_item = proto_tree_add_item(p25cai_tree, hf_p25cai_hdu, du_tvb, offset, -1, FALSE);
			du_tree = proto_item_add_subtree(du_item, ett_du);
			proto_tree_add_item(du_tree, hf_p25cai_mi, du_tvb, offset, 9, FALSE);
			offset += 9;
			proto_tree_add_item(du_tree, hf_p25cai_mfid, du_tvb, offset, 1, FALSE);
			offset += 1;
			proto_tree_add_item(du_tree, hf_p25cai_algid, du_tvb, offset, 1, FALSE);
			offset += 1;
			proto_tree_add_item(du_tree, hf_p25cai_kid, du_tvb, offset, 2, FALSE);
			offset += 2;
			proto_tree_add_item(du_tree, hf_p25cai_tgid, du_tvb, offset, 2, FALSE);
			offset += 2;
			break;
		/* Terminator Data Unit without Link Control */
		case 0x3:
			/* nothing left to decode */
			break;
		/* Logical Link Data Unit 1 */
		case 0x5:
			du_item = proto_tree_add_item(p25cai_tree, hf_p25cai_ldu1, extracted_tvb, offset, -1, FALSE);
			du_tree = proto_item_add_subtree(du_item, ett_du);
			dissect_voice(extracted_tvb, du_tree, offset);
			dissect_lc(build_ldu_lc_tvb(extracted_tvb, pinfo, offset), du_tree);
			proto_tree_add_item(du_tree, hf_p25cai_lsd, build_ldu_lsd_tvb(extracted_tvb, pinfo, offset), 0, 2, FALSE);
			break;
		/* Trunking Signaling Data Unit */
		case 0x7:
			du_tvb = build_tsdu_tvb(extracted_tvb, pinfo, offset);
			offset = 0;
			last_block = 0;
			while (last_block == 0) {
				last_block = tvb_get_guint8(du_tvb, offset) >> 7;
				mfid = tvb_get_guint8(du_tvb, offset + 1);
				du_item = proto_tree_add_item(p25cai_tree, hf_p25cai_tsbk, du_tvb, offset, 12, FALSE);
				du_tree = proto_item_add_subtree(du_item, ett_du);
				proto_tree_add_item(du_tree, hf_p25cai_lbf, du_tvb, offset, 1, FALSE);
				proto_tree_add_item(du_tree, hf_p25cai_ptbf, du_tvb, offset, 1, FALSE);
				if (mfid > 1) {
					proto_tree_add_item(du_tree, hf_p25cai_unknown_opcode, du_tvb, offset, 1, FALSE);
				} else if (outbound) {
					proto_tree_add_item(du_tree, hf_p25cai_osp_opcode, du_tvb, offset, 1, FALSE);
				} else {
					proto_tree_add_item(du_tree, hf_p25cai_isp_opcode, du_tvb, offset, 1, FALSE);
				}
				offset += 1;
				proto_tree_add_item(du_tree, hf_p25cai_mfid, du_tvb, offset, 1, FALSE);
				offset += 1;
				proto_tree_add_item(du_tree, hf_p25cai_args, du_tvb, offset, 8, FALSE);
				offset += 8;
				/* TODO: dissect args subtree */
				proto_tree_add_item(du_tree, hf_p25cai_crc, du_tvb, offset, 2, FALSE);
				/* TODO: verify CRC */
				offset += 2;
			}
			break;
		/* Logical Link Data Unit 2 */
		case 0xA:
			du_item = proto_tree_add_item(p25cai_tree, hf_p25cai_ldu2, extracted_tvb, offset, -1, FALSE);
			du_tree = proto_item_add_subtree(du_item, ett_du);
			dissect_voice(extracted_tvb, du_tree, offset);
			dissect_es(build_ldu_es_tvb(extracted_tvb, pinfo, offset), du_tree);
			proto_tree_add_item(du_tree, hf_p25cai_lsd, build_ldu_lsd_tvb(extracted_tvb, pinfo, offset), 0, 2, FALSE);
			break;
		/* Packet Data Unit */
		/* TODO: This case is hasn't been tested. */
		case 0xC:
			dissect_pdu(extracted_tvb, pinfo, offset, p25cai_tree);
			break;
		/* Terminator Data Unit with Link Control */
		case 0xF:
			dissect_lc(build_term_lc_tvb(extracted_tvb, pinfo, offset), p25cai_tree);
			break;
		/* Unknown Data Unit */
		default:
			/* don't know how to decode any more */
			break;
		}

	}

/* Return the amount of data this dissector was able to dissect */
	return tvb_length(tvb);
}

/* Dissect voice frames */
void
dissect_voice(tvbuff_t *tvb, proto_tree *tree, int offset)
{
	DISSECTOR_ASSERT(tvb_length_remaining(tvb, offset) >= 196);


	/* TODO: make these less raw */
	proto_tree_add_item(tree, hf_p25cai_imbe, tvb, offset, 18, FALSE);
	offset += 18;
	proto_tree_add_item(tree, hf_p25cai_imbe, tvb, offset, 18, FALSE);
	offset += 23;
	proto_tree_add_item(tree, hf_p25cai_imbe, tvb, offset, 18, FALSE);
	offset += 23;
	proto_tree_add_item(tree, hf_p25cai_imbe, tvb, offset, 18, FALSE);
	offset += 23;
	proto_tree_add_item(tree, hf_p25cai_imbe, tvb, offset, 18, FALSE);
	offset += 23;
	proto_tree_add_item(tree, hf_p25cai_imbe, tvb, offset, 18, FALSE);
	offset += 23;
	proto_tree_add_item(tree, hf_p25cai_imbe, tvb, offset, 18, FALSE);
	offset += 23;
	proto_tree_add_item(tree, hf_p25cai_imbe, tvb, offset, 18, FALSE);
	offset += 22;
	proto_tree_add_item(tree, hf_p25cai_imbe, tvb, offset, 18, FALSE);
	offset += 18;
}

/* Dissect Link Control */
void
dissect_lc(tvbuff_t *tvb, proto_tree *tree)
{
	proto_tree *lc_tree;
	proto_item *lc_item;
	int offset = 0;

	DISSECTOR_ASSERT(tvb_length_remaining(tvb, offset) >= 9);

	lc_item = proto_tree_add_item(tree, hf_p25cai_lc, tvb, offset, 9, FALSE);
	lc_tree = proto_item_add_subtree(lc_item, ett_lc);
	proto_tree_add_item(lc_tree, hf_p25cai_lcf, tvb, offset, 1, FALSE);
	offset += 1;
	/* TODO: Decode Link Control according to Link Control Format. */
}

/* Dissect Encryption Sync from an LDU2 */
void
dissect_es(tvbuff_t *tvb, proto_tree *tree)
{
	proto_tree *es_tree;
	proto_item *es_item;
	int offset = 0;

	DISSECTOR_ASSERT(tvb_length_remaining(tvb, offset) >= 12);

	es_item = proto_tree_add_item(tree, hf_p25cai_es, tvb, offset, 12, FALSE);
	es_tree = proto_item_add_subtree(es_item, ett_es);
	proto_tree_add_item(es_tree, hf_p25cai_mi, tvb, offset, 9, FALSE);
	offset += 9;
	proto_tree_add_item(es_tree, hf_p25cai_algid, tvb, offset, 1, FALSE);
	offset += 1;
	proto_tree_add_item(es_tree, hf_p25cai_kid, tvb, offset, 2, FALSE);
}

/* Dissect Packet Data Unit */
void
dissect_pdu(tvbuff_t *tvb, packet_info *pinfo, int offset, proto_tree *tree)
{
	proto_tree *pdu_tree;
	proto_item *pdu_item;
	guint8 *header_buffer, *trellis_buffer, pdu_format;
	int blocks_to_follow;

	pdu_item = proto_tree_add_item(tree, hf_p25cai_pdu, tvb, offset, -1, FALSE);
	pdu_tree = proto_item_add_subtree(pdu_item, ett_du);

	/* Prepare the header portion of the tvb. */
	DISSECTOR_ASSERT(tvb_length_remaining(tvb, offset) >= 25);
	header_buffer = (guint8*)ep_alloc0(12);
	trellis_buffer = (guint8*)ep_alloc0(25);
	data_deinterleave(tvb, trellis_buffer, offset * 8);
	trellis_1_2_decode(trellis_buffer, header_buffer, 0);

	/* Find out how many data blocks we are supposed to have. */
	blocks_to_follow = header_buffer[6] & 0x7F;

	pdu_format = header_buffer[0] & 0x1F;

	switch (pdu_format) {
	/* Response Packet */
	case 0x03:
		dissect_pdu_response(tvb, pinfo, 0, pdu_tree, header_buffer, blocks_to_follow);
		break;
	/* Unconfirmed Data Packet */
	case 0x15:
		dissect_pdu_unconfirmed(tvb, pinfo, 0, pdu_tree, header_buffer, blocks_to_follow);
		break;
	/* Confirmed Data Packet */
	case 0x16:
		dissect_pdu_confirmed(tvb, pinfo, 0, pdu_tree, header_buffer, blocks_to_follow);
		break;
	/* Alternate Multiple Block Trunking (MBT) Control Packet */
	case 0x17:
		dissect_pdu_ambt(tvb, pinfo, 0, pdu_tree, header_buffer, blocks_to_follow);
		break;
	/* Unknown PDU format */
	default:
		break;
	}
}

/* Dissect Response Packet */
void
dissect_pdu_response(tvbuff_t *tvb, packet_info *pinfo, int offset, proto_tree *tree, guint8 *header, int num_blocks)
{
	tvbuff_t *pdu_tvb;
	proto_tree *db_tree;
	proto_item *db_item;
	guint8 *pdu_buffer, *trellis_buffer;
	int i, tvb_bit_offset, pdu_offset, pdu_buffer_length;

	pdu_buffer_length = 12 + num_blocks * 12;
	pdu_buffer = (guint8*)ep_alloc0(pdu_buffer_length);

	/* Our tvb offset doesn't fall on bit boundaries, so we track it by bits
	 * instead of bytes.
	 */
	tvb_bit_offset = 308;
	pdu_offset = 12;

	/* Copy the temporary header onto the pdu_buffer. */
	for (i = 0; i < 12; i++)
		pdu_buffer[i] = header[i];

	/* Add the data blocks to the pdu_buffer. */
	for (i = 0; i < num_blocks; i++) {
		DISSECTOR_ASSERT(tvb_length_remaining(tvb, tvb_bit_offset / 8) >= 25);
		trellis_buffer = (guint8*)ep_alloc0(25);
		data_deinterleave(tvb, trellis_buffer, tvb_bit_offset);
		trellis_1_2_decode(trellis_buffer, pdu_buffer, pdu_offset);
		tvb_bit_offset += 196;
		pdu_offset += 12;
	}

	/* Setup a new tvb buffer with the decoded data. */
	pdu_tvb = tvb_new_real_data(pdu_buffer, pdu_buffer_length, pdu_buffer_length);
	tvb_set_child_real_data_tvbuff(tvb, pdu_tvb);
	add_new_data_source(pinfo, pdu_tvb, "Packet Data Unit");

	/* Dissect the PDU header. */
	proto_tree_add_item(tree, hf_p25cai_io, pdu_tvb, offset, 1, FALSE);
	proto_tree_add_item(tree, hf_p25cai_pdu_format, pdu_tvb, offset, 1, FALSE);
	offset += 1;
	proto_tree_add_item(tree, hf_p25cai_class, pdu_tvb, offset, 1, FALSE);
	proto_tree_add_item(tree, hf_p25cai_type, pdu_tvb, offset, 1, FALSE);
	proto_tree_add_item(tree, hf_p25cai_status, pdu_tvb, offset, 1, FALSE);
	offset += 1;
	proto_tree_add_item(tree, hf_p25cai_mfid, pdu_tvb, offset, 1, FALSE);
	offset += 1;
	proto_tree_add_item(tree, hf_p25cai_llid, pdu_tvb, offset, 3, FALSE);
	offset += 3;
	proto_tree_add_item(tree, hf_p25cai_x, pdu_tvb, offset, 1, FALSE);
	proto_tree_add_item(tree, hf_p25cai_btf, pdu_tvb, offset, 1, FALSE);
	offset += 1;
	proto_tree_add_item(tree, hf_p25cai_sllid, pdu_tvb, offset, 3, FALSE);
	offset += 3;
	proto_tree_add_item(tree, hf_p25cai_crc, pdu_tvb, offset, 2, FALSE);
	offset += 2;

	/* Dissect the data blocks. */
	for (i = 0; i < num_blocks; i++) {
		db_item = proto_tree_add_item(tree, hf_p25cai_db, pdu_tvb, offset, 12, FALSE);
		db_tree = proto_item_add_subtree(db_item, ett_db);
		if (i == num_blocks - 1) {
			/* The last data block includes the packet CRC */
			proto_tree_add_item(db_tree, hf_p25cai_ud, pdu_tvb, offset, 8, FALSE);
			offset += 8;
			proto_tree_add_item(db_tree, hf_p25cai_packet_crc, pdu_tvb, offset, 4, FALSE);
		} else {
			proto_tree_add_item(db_tree, hf_p25cai_ud, pdu_tvb, offset, 12, FALSE);
		}
	}
}

/* Dissect Unonfirmed Data Packet */
void
dissect_pdu_unconfirmed(tvbuff_t *tvb, packet_info *pinfo, int offset, proto_tree *tree, guint8 *header, int num_blocks)
{
	tvbuff_t *pdu_tvb;
	proto_tree *db_tree;
	proto_item *db_item;
	guint8 *pdu_buffer, *trellis_buffer;
	int i, tvb_bit_offset, pdu_offset, pdu_buffer_length;

	pdu_buffer_length = 12 + num_blocks * 12;
	pdu_buffer = (guint8*)ep_alloc0(pdu_buffer_length);

	/* Our tvb offset doesn't fall on bit boundaries, so we track it by bits
	 * instead of bytes.
	 */
	tvb_bit_offset = 308;
	pdu_offset = 12;

	/* Copy the temporary header onto the pdu_buffer. */
	for (i = 0; i < 12; i++)
		pdu_buffer[i] = header[i];

	/* Add the data blocks to the pdu_buffer. */
	for (i = 0; i < num_blocks; i++) {
		DISSECTOR_ASSERT(tvb_length_remaining(tvb, tvb_bit_offset / 8) >= 25);
		trellis_buffer = (guint8*)ep_alloc0(25);
		data_deinterleave(tvb, trellis_buffer, tvb_bit_offset);
		trellis_1_2_decode(trellis_buffer, pdu_buffer, pdu_offset);
		tvb_bit_offset += 196;
		pdu_offset += 12;
	}

	/* Setup a new tvb buffer with the decoded data. */
	pdu_tvb = tvb_new_real_data(pdu_buffer, pdu_buffer_length, pdu_buffer_length);
	tvb_set_child_real_data_tvbuff(tvb, pdu_tvb);
	add_new_data_source(pinfo, pdu_tvb, "Packet Data Unit");

	/* Dissect the PDU header. */
	proto_tree_add_item(tree, hf_p25cai_an, pdu_tvb, offset, 1, FALSE);
	proto_tree_add_item(tree, hf_p25cai_io, pdu_tvb, offset, 1, FALSE);
	proto_tree_add_item(tree, hf_p25cai_pdu_format, pdu_tvb, offset, 1, FALSE);
	offset += 1;
	proto_tree_add_item(tree, hf_p25cai_sapid, pdu_tvb, offset, 1, FALSE);
	offset += 1;
	proto_tree_add_item(tree, hf_p25cai_mfid, pdu_tvb, offset, 1, FALSE);
	offset += 1;
	proto_tree_add_item(tree, hf_p25cai_llid, pdu_tvb, offset, 3, FALSE);
	offset += 3;
	proto_tree_add_item(tree, hf_p25cai_btf, pdu_tvb, offset, 1, FALSE);
	offset += 1;
	proto_tree_add_item(tree, hf_p25cai_poc, pdu_tvb, offset, 1, FALSE);
	offset += 1;
	/* reserved octet here */
	offset += 1;
	proto_tree_add_item(tree, hf_p25cai_dho, pdu_tvb, offset, 1, FALSE);
	offset += 1;
	proto_tree_add_item(tree, hf_p25cai_crc, pdu_tvb, offset, 2, FALSE);
	offset += 2;

	/* Dissect the data blocks. */
	for (i = 0; i < num_blocks; i++) {
		db_item = proto_tree_add_item(tree, hf_p25cai_db, pdu_tvb, offset, 12, FALSE);
		db_tree = proto_item_add_subtree(db_item, ett_db);
		if (i == num_blocks - 1) {
			/* The last data block includes the packet CRC */
			proto_tree_add_item(db_tree, hf_p25cai_ud, pdu_tvb, offset, 8, FALSE);
			offset += 8;
			proto_tree_add_item(db_tree, hf_p25cai_packet_crc, pdu_tvb, offset, 4, FALSE);
		} else {
			proto_tree_add_item(db_tree, hf_p25cai_ud, pdu_tvb, offset, 12, FALSE);
		}
	}
}

/* Dissect Confirmed Data Packet */
void
dissect_pdu_confirmed(tvbuff_t *tvb, packet_info *pinfo, int offset, proto_tree *tree, guint8 *header, int num_blocks)
{
	tvbuff_t *pdu_tvb;
	proto_tree *db_tree;
	proto_item *db_item;
	guint8 *pdu_buffer, *trellis_buffer;
	int i, tvb_bit_offset, pdu_offset, pdu_buffer_length;

	pdu_buffer_length = 12 + num_blocks * 18;
	pdu_buffer = (guint8*)ep_alloc0(pdu_buffer_length);

	/* Our tvb offset doesn't fall on bit boundaries, so we track it by bits
	 * instead of bytes.
	 */
	tvb_bit_offset = 308;
	pdu_offset = 12;

	/* Copy the temporary header onto the pdu_buffer. */
	for (i = 0; i < 12; i++)
		pdu_buffer[i] = header[i];

	/* Add the data blocks to the pdu_buffer. */
	for (i = 0; i < num_blocks; i++) {
		DISSECTOR_ASSERT(tvb_length_remaining(tvb, tvb_bit_offset / 8) >= 25);
		trellis_buffer = (guint8*)ep_alloc0(25);
		data_deinterleave(tvb, trellis_buffer, tvb_bit_offset);
		trellis_3_4_decode(trellis_buffer, pdu_buffer, pdu_offset);
		tvb_bit_offset += 196;
		pdu_offset += 18;
	}

	/* Setup a new tvb buffer with the decoded data. */
	pdu_tvb = tvb_new_real_data(pdu_buffer, pdu_buffer_length, pdu_buffer_length);
	tvb_set_child_real_data_tvbuff(tvb, pdu_tvb);
	add_new_data_source(pinfo, pdu_tvb, "Packet Data Unit");

	/* Dissect the PDU header. */
	proto_tree_add_item(tree, hf_p25cai_an, pdu_tvb, offset, 1, FALSE);
	proto_tree_add_item(tree, hf_p25cai_io, pdu_tvb, offset, 1, FALSE);
	proto_tree_add_item(tree, hf_p25cai_pdu_format, pdu_tvb, offset, 1, FALSE);
	offset += 1;
	proto_tree_add_item(tree, hf_p25cai_sapid, pdu_tvb, offset, 1, FALSE);
	offset += 1;
	proto_tree_add_item(tree, hf_p25cai_mfid, pdu_tvb, offset, 1, FALSE);
	offset += 1;
	proto_tree_add_item(tree, hf_p25cai_llid, pdu_tvb, offset, 3, FALSE);
	offset += 3;
	proto_tree_add_item(tree, hf_p25cai_fmf, pdu_tvb, offset, 1, FALSE);
	proto_tree_add_item(tree, hf_p25cai_btf, pdu_tvb, offset, 1, FALSE);
	offset += 1;
	proto_tree_add_item(tree, hf_p25cai_poc, pdu_tvb, offset, 1, FALSE);
	offset += 1;
	proto_tree_add_item(tree, hf_p25cai_syn, pdu_tvb, offset, 1, FALSE);
	proto_tree_add_item(tree, hf_p25cai_ns, pdu_tvb, offset, 1, FALSE);
	proto_tree_add_item(tree, hf_p25cai_fsnf, pdu_tvb, offset, 1, FALSE);
	offset += 1;
	proto_tree_add_item(tree, hf_p25cai_dho, pdu_tvb, offset, 1, FALSE);
	offset += 1;
	proto_tree_add_item(tree, hf_p25cai_crc, pdu_tvb, offset, 2, FALSE);
	offset += 2;

	/* Dissect the data blocks. */
	for (i = 0; i < num_blocks; i++) {
		db_item = proto_tree_add_item(tree, hf_p25cai_db, pdu_tvb, offset, 18, FALSE);
		db_tree = proto_item_add_subtree(db_item, ett_db);
		proto_tree_add_item(db_tree, hf_p25cai_dbsn, pdu_tvb, offset, 1, FALSE);
		proto_tree_add_item(db_tree, hf_p25cai_crc9, pdu_tvb, offset, 2, FALSE);
		offset += 2;
		if (i == num_blocks - 1) {
			/* The last data block includes the packet CRC */
			proto_tree_add_item(db_tree, hf_p25cai_ud, pdu_tvb, offset, 12, FALSE);
			offset += 12;
			proto_tree_add_item(db_tree, hf_p25cai_packet_crc, pdu_tvb, offset, 4, FALSE);
		} else {
			proto_tree_add_item(db_tree, hf_p25cai_ud, pdu_tvb, offset, 16, FALSE);
		}
	}
}

/* Dissect Alternate Multiple Block Trunking (MBT) Control Packet */
void
dissect_pdu_ambt(tvbuff_t *tvb, packet_info *pinfo, int offset, proto_tree *tree, guint8 *header, int num_blocks)
{
	tvbuff_t *pdu_tvb;
	proto_tree *db_tree;
	proto_item *db_item;
	guint8 *pdu_buffer, *trellis_buffer, outbound, mfid;
	int i, tvb_bit_offset, pdu_offset, pdu_buffer_length;

	pdu_buffer_length = 12 + num_blocks * 12;
	pdu_buffer = (guint8*)ep_alloc0(pdu_buffer_length);

	/* Our tvb offset doesn't fall on bit boundaries, so we track it by bits
	 * instead of bytes.
	 */
	tvb_bit_offset = 308;
	pdu_offset = 12;

	/* Copy the temporary header onto the pdu_buffer. */
	for (i = 0; i < 12; i++)
		pdu_buffer[i] = header[i];

	/* Add the data blocks to the pdu_buffer. */
	for (i = 0; i < num_blocks; i++) {
		DISSECTOR_ASSERT(tvb_length_remaining(tvb, tvb_bit_offset / 8) >= 25);
		trellis_buffer = (guint8*)ep_alloc0(25);
		data_deinterleave(tvb, trellis_buffer, tvb_bit_offset);
		trellis_1_2_decode(trellis_buffer, pdu_buffer, pdu_offset);
		tvb_bit_offset += 196;
		pdu_offset += 12;
	}

	/* Setup a new tvb buffer with the decoded data. */
	pdu_tvb = tvb_new_real_data(pdu_buffer, pdu_buffer_length, pdu_buffer_length);
	tvb_set_child_real_data_tvbuff(tvb, pdu_tvb);
	add_new_data_source(pinfo, pdu_tvb, "Packet Data Unit");

	/* Dissect the PDU header. */
	proto_tree_add_item(tree, hf_p25cai_an, pdu_tvb, offset, 1, FALSE);
	outbound = (tvb_get_guint8(pdu_tvb, offset) >> 5) & 0x01;
	proto_tree_add_item(tree, hf_p25cai_io, pdu_tvb, offset, 1, FALSE);
	proto_tree_add_item(tree, hf_p25cai_pdu_format, pdu_tvb, offset, 1, FALSE);
	offset += 1;
	proto_tree_add_item(tree, hf_p25cai_sapid, pdu_tvb, offset, 1, FALSE);
	offset += 1;
	mfid = tvb_get_guint8(pdu_tvb, offset);
	proto_tree_add_item(tree, hf_p25cai_mfid, pdu_tvb, offset, 1, FALSE);
	offset += 1;
	proto_tree_add_item(tree, hf_p25cai_llid, pdu_tvb, offset, 3, FALSE);
	offset += 3;
	proto_tree_add_item(tree, hf_p25cai_btf, pdu_tvb, offset, 1, FALSE);
	offset += 1;
	if (mfid > 1) {
		proto_tree_add_item(tree, hf_p25cai_unknown_opcode, pdu_tvb, offset, 1, FALSE);
	} else if (outbound) {
		proto_tree_add_item(tree, hf_p25cai_osp_opcode, pdu_tvb, offset, 1, FALSE);
	} else {
		proto_tree_add_item(tree, hf_p25cai_isp_opcode, pdu_tvb, offset, 1, FALSE);
	}
	offset += 1;
	/* TODO: 2 octets defined by trunking messages */
	offset += 2;
	proto_tree_add_item(tree, hf_p25cai_crc, pdu_tvb, offset, 2, FALSE);
	offset += 2;

	/* Dissect the data blocks. */
	for (i = 0; i < num_blocks; i++) {
		db_item = proto_tree_add_item(tree, hf_p25cai_db, pdu_tvb, offset, 12, FALSE);
		db_tree = proto_item_add_subtree(db_item, ett_db);
		if (i == num_blocks - 1) {
			/* The last data block includes the packet CRC */
			/* really "MBT data" not "user data" */
			proto_tree_add_item(db_tree, hf_p25cai_ud, pdu_tvb, offset, 8, FALSE);
			offset += 8;
			proto_tree_add_item(db_tree, hf_p25cai_packet_crc, pdu_tvb, offset, 4, FALSE);
		} else {
			proto_tree_add_item(db_tree, hf_p25cai_ud, pdu_tvb, offset, 12, FALSE);
		}
	}
}

/* Extract status symbols, display them, and return frame without status symbols */
tvbuff_t*
extract_status_symbols(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int *outbound)
{
	proto_item *ss_parent_item;
	proto_tree *ss_tree;
	int i, j, raw_length, extracted_length;
	guint8 *extracted_buffer;
	tvbuff_t *extracted_tvb;

	raw_length = tvb_length(tvb);
	extracted_length = raw_length - (raw_length / 36);
	*outbound = 0;

	/* Create buffer to become new tvb. */
	extracted_buffer = (guint8*)ep_alloc0(extracted_length);

	/* Create status symbol subtree. */
	ss_parent_item = proto_tree_add_item(tree, hf_p25cai_ss_parent, tvb, 0, -1, FALSE);
	ss_tree = proto_item_add_subtree(ss_parent_item, ett_ss);

	/* Go through frame one dibit at a time. */
	for (i = 0, j = 0; i < raw_length * 4; i++) {
		if (i % 36 == 35) {
			/* After every 35 dibits is a status symbol. */
			proto_tree_add_item(ss_tree, hf_p25cai_ss, tvb, i/4, 1, FALSE);
			/* Check to see if the status symbol is odd. */
			if (tvb_get_guint8(tvb, i/4) & 0x1) {
				/* Flag as outbound (only outbound frames should have odd status symbols).
				 * This may not be a very reliable means of determining the direction, but
				 * I haven't found anything better.
				 */
				*outbound |= 1;
			}
		} else {
			/* Extract frame bits from between status symbols. */
			/* I'm sure there is a more efficient way to do this. */
			extracted_buffer[j/4] |= tvb_get_bits8(tvb, i * 2, 2) << (6 - (j % 4) * 2);
			j++;
		}
	}

	/* Setup a new tvb buffer with the extracted data. */
	extracted_tvb = tvb_new_real_data(extracted_buffer, extracted_length, extracted_length);
	tvb_set_child_real_data_tvbuff(tvb, extracted_tvb);
	add_new_data_source(pinfo, extracted_tvb, "P25 CAI");

	return extracted_tvb;
}

/* Build Header Data Unit tvb. */
tvbuff_t*
build_hdu_tvb(tvbuff_t *tvb, packet_info *pinfo, int offset)
{
	guint8 *hdu_buffer, *rs_codeword;
	guint8 rs_code_byte, high_byte, low_byte;
	guint32 golay_codeword;
	int i, j;
	tvbuff_t *hdu_tvb;

	DISSECTOR_ASSERT(tvb_length_remaining(tvb, offset) >= 81);

	rs_codeword = (guint8*)ep_alloc0(27);
	hdu_buffer = (guint8*)ep_alloc0(15);

	/* Each 18 bits is a Golay codeword. */
	for (i = offset * 8, j = 0; j < 216; i += 18, j += 6) {

		/* Take 18 bits from the tvb, adjusting for byte boundaries. */
		golay_codeword = (tvb_get_ntohl(tvb, i / 8) >> (14 - i % 8)) & 0x3FFFF;
		rs_code_byte = golay_18_6_8_decode(golay_codeword) << 2;

		/* Stuff high bits into one byte of the new buffer. */
		high_byte = rs_code_byte >> (j % 8);
		rs_codeword[j / 8] |= high_byte;

		/* Stuff low bits into the next unless beyond end of buffer. */
		if (j < 210) {
			low_byte = rs_code_byte << (8 - j % 8);
			rs_codeword[j / 8 + 1] |= low_byte;
		}
	}

	rs_36_20_17_decode(rs_codeword, hdu_buffer);

	/* Setup a new tvb buffer with the decoded data. */
	hdu_tvb = tvb_new_real_data(hdu_buffer, 15, 15);
	tvb_set_child_real_data_tvbuff(tvb, hdu_tvb);
	add_new_data_source(pinfo, hdu_tvb, "data units");

	return hdu_tvb;
}

/* Build Low Speed Data tvb from LDU */
tvbuff_t*
build_ldu_lsd_tvb(tvbuff_t *tvb, packet_info *pinfo, int offset)
{
	guint8 *lsd_buffer;
	tvbuff_t *lsd_tvb;

	/* we were passed the offset to the beginning of the LDU */
	offset += 174;

	DISSECTOR_ASSERT(tvb_length_remaining(tvb, offset) >= 4);

	lsd_buffer = (guint8*)ep_alloc0(2);

	cyclic_16_8_5_decode(tvb_get_ntohl(tvb, offset), lsd_buffer);

	/* Setup a new tvb buffer with the decoded data. */
	lsd_tvb = tvb_new_real_data(lsd_buffer, 2, 2);
	tvb_set_child_real_data_tvbuff(tvb, lsd_tvb);
	add_new_data_source(pinfo, lsd_tvb, "Low Speed Data");

	return lsd_tvb;
}

/* Build Link Control tvb from LDU1 */
tvbuff_t*
build_ldu_lc_tvb(tvbuff_t *tvb, packet_info *pinfo, int offset)
{
	guint8 *lc_buffer, *rs_codeword;
	guint8 rs_code_byte, high_byte, low_byte;
	guint32 hamming_codeword;
	int i, j, r, t;
	tvbuff_t *lc_tvb;

	/* we were passed the offset to the beginning of the LDU */
	offset += 36;

	DISSECTOR_ASSERT(tvb_length_remaining(tvb, offset) >= 147);

	rs_codeword = (guint8*)ep_alloc0(18);
	lc_buffer = (guint8*)ep_alloc0(9);

	/* step through tvb bits to find 10 bit hamming codewords */
	for (i = offset * 8, r = 0; r < 144; i += 184) {
		for (j = 0; j < 40; j += 10, r += 6) {
			/* t = tvb bit index
			 * r = reed-solomon codeword bit index
			 */
			t = i + j;
			hamming_codeword = (tvb_get_ntohl(tvb, t / 8) >> (22 - t % 8)) & 0x3FF;
			rs_code_byte = hamming_10_6_3_decode(hamming_codeword) << 2;

			/* Stuff high bits into one byte of the new buffer. */
			high_byte = rs_code_byte >> (r % 8);
			rs_codeword[r / 8] |= high_byte;

			/* Stuff low bits into the next unless beyond end of buffer. */
			if (r < 144) {
				low_byte = rs_code_byte << (8 - r % 8);
				rs_codeword[r / 8 + 1] |= low_byte;
			}
		}
	}

	rs_24_12_13_decode(rs_codeword, lc_buffer);

	/* Setup a new tvb buffer with the decoded data. */
	lc_tvb = tvb_new_real_data(lc_buffer, 9, 9);
	tvb_set_child_real_data_tvbuff(tvb, lc_tvb);
	add_new_data_source(pinfo, lc_tvb, "Link Control");

	return lc_tvb;
}

/* Build Encryption Sync tvb LDU2 */
tvbuff_t*
build_ldu_es_tvb(tvbuff_t *tvb, packet_info *pinfo, int offset)
{
	guint8 *es_buffer, *rs_codeword;
	guint8 rs_code_byte, high_byte, low_byte;
	guint32 hamming_codeword;
	int i, j, r, t;
	tvbuff_t *es_tvb;

	/* we were passed the offset to the beginning of the LDU */
	offset += 36;

	DISSECTOR_ASSERT(tvb_length_remaining(tvb, offset) >= 147);

	rs_codeword = (guint8*)ep_alloc0(18);
	es_buffer = (guint8*)ep_alloc0(12);

	/* step through tvb bits to find 10 bit hamming codewords */
	for (i = offset * 8, r = 0; r < 144; i += 184) {
		for (j = 0; j < 40; j += 10, r += 6) {
			/* t = tvb bit index
			 * r = reed-solomon codeword bit index
			 */
			t = i + j;
			hamming_codeword = (tvb_get_ntohl(tvb, t / 8) >> (22 - t % 8)) & 0x3FF;
			rs_code_byte = hamming_10_6_3_decode(hamming_codeword) << 2;

			/* Stuff high bits into one byte of the new buffer. */
			high_byte = rs_code_byte >> (r % 8);
			rs_codeword[r / 8] |= high_byte;

			/* Stuff low bits into the next unless beyond end of buffer. */
			if (r < 144) {
				low_byte = rs_code_byte << (8 - r % 8);
				rs_codeword[r / 8 + 1] |= low_byte;
			}
		}
	}

	rs_24_16_9_decode(rs_codeword, es_buffer);

	/* Setup a new tvb buffer with the decoded data. */
	es_tvb = tvb_new_real_data(es_buffer, 12, 12);
	tvb_set_child_real_data_tvbuff(tvb, es_tvb);
	add_new_data_source(pinfo, es_tvb, "Encryption Sync");

	return es_tvb;
}

/* Build Trunking Signaling Block tvb. */
tvbuff_t*
build_tsdu_tvb(tvbuff_t *tvb, packet_info *pinfo, int offset)
{
	guint8 *tsdu_buffer, *trellis_buffer;
	guint8 last_block;
	int tsdu_offset, tvb_bit_offset;
	tvbuff_t *tsdu_tvb;

	/* From here on, our tvb offset may not fall on bit boundaries, so we track
	 * it by bits instead of bytes.
	 */
	tvb_bit_offset = offset * 8;
	tsdu_offset = 0;
	last_block = 0;

	tsdu_buffer = (guint8*)ep_alloc0(36); /* 12 times maximum number of TSBKs (3) */

	while (last_block == 0) {
		DISSECTOR_ASSERT(tvb_length_remaining(tvb, tvb_bit_offset / 8) >= 25);
		trellis_buffer = (guint8*)ep_alloc0(25);
		data_deinterleave(tvb, trellis_buffer, tvb_bit_offset);
		trellis_1_2_decode(trellis_buffer, tsdu_buffer, tsdu_offset);
		tvb_bit_offset += 196;
		last_block = tsdu_buffer[tsdu_offset] >> 7;
		tsdu_offset += 12;
	}

	/* Setup a new tvb buffer with the decoded data. */
	tsdu_tvb = tvb_new_real_data(tsdu_buffer, tsdu_offset, tsdu_offset);
	tvb_set_child_real_data_tvbuff(tvb, tsdu_tvb);
	add_new_data_source(pinfo, tsdu_tvb, "data units");

	return tsdu_tvb;
}

/* Build Link Control tvb from Terminator */
tvbuff_t*
build_term_lc_tvb(tvbuff_t *tvb, packet_info *pinfo, int offset)
{
	guint8 *lc_buffer, *rs_codeword;
	guint16 rs_code_chunk;
	guint8 high_byte, low_byte;
	guint32 golay_codeword;
	int i, j;
	tvbuff_t *lc_tvb;

	DISSECTOR_ASSERT(tvb_length_remaining(tvb, offset) >= 36);

	rs_codeword = (guint8*)ep_alloc0(18);
	lc_buffer = (guint8*)ep_alloc0(9);

	/* Each 24 bits is a Golay codeword. */
	for (i = offset * 8, j = 0; j < 144; i += 24, j += 12) {

		/* Take 24 bits from the tvb, adjusting for byte boundaries. */
		golay_codeword = (tvb_get_ntohl(tvb, i / 8) >> (8 - i % 8)) & 0xFFFFFF;
		rs_code_chunk = golay_24_12_8_decode(golay_codeword) << 4;

		/* Stuff high bits into one byte of the new buffer. */
		high_byte = rs_code_chunk >> (j % 8);
		rs_codeword[j / 8] |= high_byte;

		/* Stuff low bits into the next. */
		low_byte = rs_code_chunk << (8 - j % 8);
		rs_codeword[j / 8 + 1] |= low_byte;
	}

	rs_24_12_13_decode(rs_codeword, lc_buffer);

	/* Setup a new tvb buffer with the decoded data. */
	lc_tvb = tvb_new_real_data(lc_buffer, 9, 9);
	tvb_set_child_real_data_tvbuff(tvb, lc_tvb);
	add_new_data_source(pinfo, lc_tvb, "data units");

	return lc_tvb;
}

/* Deinterleave data block.  Assumes output buffer is already zeroed. */
void
data_deinterleave(tvbuff_t *tvb, guint8 *deinterleaved, int bit_offset)
{
	int d, i, j, t;
	int steps[] = {0, 52, 100, 148};

	/* step through input nibbles to copy to output */
	for (i = bit_offset, d = 0; i < 45 + bit_offset; i += 4) {
		for (j = 0; j < 4; j++, d += 4) {
			/* t = tvb bit index
			 * d = deinterleaved bit index
			 */
			t = i + steps[j];
			deinterleaved[d / 8] |= ((tvb_get_guint8(tvb, t / 8) << (t % 8)) & 0xF0) >> (d % 8);
		}
	}
	t = bit_offset + 48;
	deinterleaved[24] |= ((tvb_get_guint8(tvb, t / 8) << (t % 8)) & 0xF0);
}

/* 1/2 rate trellis decoder.  Assumes output buffer is already zeroed. */
void
trellis_1_2_decode(guint8 *encoded, guint8 *decoded, int offset)
{
	int i, j;
	int state = 0;
	guint8 codeword;

	/* Hamming distance */
	guint8 hd[4];

	/* state transition table, including constellation to dibit pair mapping */
	guint8 next_words[4][4] = {
		{0x2, 0xC, 0x1, 0xF},
		{0xE, 0x0, 0xD, 0x3},
		{0x9, 0x7, 0xA, 0x4},
		{0x5, 0xB, 0x6, 0x8}
	};

	/* step through 4 bit codewords in input */
	for (i = 0; i < 196; i += 4) {
		codeword = (encoded[i / 8] >> (4 - (i % 8))) & 0xF;
		/* try each codeword in a row of the state transition table */
		for (j = 0; j < 4; j++) {
			/* find Hamming distance for candidate */
			hd[j] = count_bits(codeword ^ next_words[state][j]);
		}
		/* find the dibit that matches the most codeword bits (minimum Hamming distance) */
		state = find_min(hd, 4);
		/* error if minimum can't be found */
		DISSECTOR_ASSERT(state != -1);
		/* It also might be nice to report a condition where the minimum is
		 * non-zero, i.e. an error has been corrected.  It probably shouldn't
		 * be a permanent failure, though.
		 *
		 * DISSECTOR_ASSERT(hd[state] == 0);
		 */

		/* append dibit onto output buffer */
		if (i < 192)
			decoded[(i / 16) + offset] |= state << (6 - (i / 2) % 8);
	}
}

/* 3/4 rate trellis decoder.  Assumes output buffer is already zeroed. */
void
trellis_3_4_decode(guint8 *encoded, guint8 *decoded, int offset)
{
	int i, j;
	int state = 0;
	guint8 codeword;

	/* Hamming distance */
	guint8 hd[8];

	/* state transition table, including constellation to dibit pair mapping */
	guint8 next_words[8][8] = {
		{0x2, 0xD, 0xE, 0x1, 0x7, 0x8, 0xB, 0x4},
		{0xE, 0x1, 0x7, 0x8, 0xB, 0x4, 0x2, 0xD},
		{0xA, 0x5, 0x6, 0x9, 0xF, 0x0, 0x3, 0xC},
		{0x6, 0x9, 0xF, 0x0, 0x3, 0xC, 0xA, 0x5},
		{0xF, 0x0, 0x3, 0xC, 0xA, 0x5, 0x6, 0x9},
		{0x3, 0xC, 0xA, 0x5, 0x6, 0x9, 0xF, 0x0},
		{0x7, 0x8, 0xB, 0x4, 0x2, 0xD, 0xE, 0x1},
		{0xB, 0x4, 0x2, 0xD, 0xE, 0x1, 0x7, 0x8}
	};

	/* step through 4 bit codewords in input */
	for (i = 0; i < 196; i += 4) {
		codeword = (encoded[i / 8] >> (4 - (i % 8))) & 0xF;
		/* try each codeword in a row of the state transition table */
		for (j = 0; j < 8; j++) {
			/* find Hamming distance for candidate */
			hd[j] = count_bits(codeword ^ next_words[state][j]);
		}
		/* find the dibit that matches the most codeword bits (minimum Hamming distance) */
		state = find_min(hd, 8);
		/* error if minimum can't be found */
		DISSECTOR_ASSERT(state != -1);
		/* It also might be nice to report a condition where the minimum is
		 * non-zero, i.e. an error has been corrected.  It probably shouldn't
		 * be a permanent failure, though.
		 *
		 * DISSECTOR_ASSERT(hd[state] == 0);
		 */

		/* append tribit onto output buffer */
		if (i < 192)
			/* FIXME adapt from 1/2 to 3/4 rate */
			decoded[(i / 16) + offset] |= state << (6 - (i / 2) % 8);
	}
}

/* Error correction decoders
 *
 * TODO:  For now these are fake.  They pull out the original bits without
 * actually doing any error correction or detection.
 */

/* fake (18,6,8) shortened Golay decoder, no error correction */
/* TODO: make less fake */
guint8
golay_18_6_8_decode(guint32 codeword)
{
	return (codeword >> 12) & 0x3F;
}

/* fake (24,12,8) extended Golay decoder, no error correction */
/* TODO: make less fake */
guint16
golay_24_12_8_decode(guint32 codeword)
{
	return (codeword >> 12) & 0xFFF;
}

/* fake (10,6,3) shortened Hamming decoder, no error correction */
/* TODO: make less fake */
guint8
hamming_10_6_3_decode(guint32 codeword)
{
	return (codeword >> 4) & 0x3F;
}

/* fake (16,8,5) shortened cyclic decoder, no error correction */
/* TODO: make less fake */
void
cyclic_16_8_5_decode(guint32 codeword, guint8 *decoded)
{
	/* Take the first byte of each 16 bit word. */
	decoded[0] = codeword >> 24;
	decoded[1] = codeword >> 8;
}

/* fake (24,12,13) Reed-Solomon decoder, no error correction */
/* TODO: make less fake */
void
rs_24_12_13_decode(guint8 *codeword, guint8 *decoded)
{
	int i;

	/* Just grab the first 9 bytes (12 sets of six bits) */
	for (i = 0; i < 9; i++)
		decoded[i] = codeword[i];
}

/* fake (24,16,9) Reed-Solomon decoder, no error correction */
/* TODO: make less fake */
void
rs_24_16_9_decode(guint8 *codeword, guint8 *decoded)
{
	int i;

	/* Just grab the first 12 bytes (16 sets of six bits) */
	for (i = 0; i < 12; i++)
		decoded[i] = codeword[i];
}

/* fake (36,20,17) Reed-Solomon decoder, no error correction */
/* TODO: make less fake */
void
rs_36_20_17_decode(guint8 *codeword, guint8 *decoded)
{
	int i;

	/* Just grab the first 15 bytes (20 sets of six bits) */
	for (i = 0; i < 15; i++)
		decoded[i] = codeword[i];
}

/* Register the protocol with Wireshark */
void
proto_register_p25cai(void)
{

/* Setup list of fields */
	static hf_register_info hf[] = {
		{ &hf_p25cai_fs,
			{ "Frame Synchronization", "p25cai.fs",
			FT_BYTES, BASE_HEX, NULL, 0x0,
			NULL, HFILL }
		},
		{ &hf_p25cai_nid,
			{ "Network ID Codeword", "p25cai.nid",
			FT_UINT64, BASE_HEX, NULL, 0x0,
			NULL, HFILL }
		},
		{ &hf_p25cai_nac,
			{ "Network Access Code", "p25cai.nac",
			FT_UINT16, BASE_HEX, VALS(network_access_codes), 0xFFF0,
			NULL, HFILL }
		},
		{ &hf_p25cai_duid,
			{ "Data Unit ID", "p25cai.duid",
			FT_UINT16, BASE_HEX, VALS(data_unit_ids), 0x000F,
			NULL, HFILL }
		},
		{ &hf_p25cai_hdu,
			{ "Header Data Unit", "p25cai.hdu",
			FT_NONE, BASE_NONE, NULL, 0x0,
			NULL, HFILL }
		},
		{ &hf_p25cai_tsbk,
			{ "Trunking Signaling Block", "p25cai.tsbk",
			FT_NONE, BASE_NONE, NULL, 0x0,
			NULL, HFILL }
		},
		{ &hf_p25cai_pdu,
			{ "Packet Data Unit", "p25cai.pdu",
			FT_NONE, BASE_NONE, NULL, 0x0,
			NULL, HFILL }
		},
		{ &hf_p25cai_ldu1,
			{ "Logical Link Data Unit 1", "p25cai.ldu1",
			FT_NONE, BASE_NONE, NULL, 0x0,
			NULL, HFILL }
		},
		{ &hf_p25cai_ldu2,
			{ "Logical Link Data Unit 2", "p25cai.ldu2",
			FT_NONE, BASE_NONE, NULL, 0x0,
			NULL, HFILL }
		},
		{ &hf_p25cai_mi,
			{ "Message Indicator", "p25cai.mi",
			FT_BYTES, BASE_HEX, NULL, 0x0,
			NULL, HFILL }
		},
		{ &hf_p25cai_mfid,
			{ "Manufacturer's ID", "p25cai.mfid",
			FT_UINT8, BASE_HEX, VALS(manufacturer_ids), 0x0,
			NULL, HFILL }
		},
		{ &hf_p25cai_algid,
			{ "Algorithm ID", "p25cai.algid",
			FT_UINT8, BASE_HEX, VALS(algorithm_ids), 0x0,
			NULL, HFILL }
		},
		{ &hf_p25cai_kid,
			{ "Key ID", "p25cai.kid",
			FT_UINT16, BASE_HEX, VALS(key_ids), 0x0,
			NULL, HFILL }
		},
		{ &hf_p25cai_tgid,
			{ "Talk Group ID", "p25cai.tgid",
			FT_UINT16, BASE_HEX, VALS(talk_group_ids), 0x0,
			NULL, HFILL }
		},
		{ &hf_p25cai_ss_parent,
			{ "Status Symbols", "p25cai.ss_parent",
			FT_NONE, BASE_NONE, NULL, 0x0,
			NULL, HFILL }
		},
		{ &hf_p25cai_ss,
			{ "Status Symbol", "p25cai.ss",
			FT_UINT8, BASE_HEX, VALS(status_symbols), 0x3,
			NULL, HFILL }
		},
		{ &hf_p25cai_lc,
			{ "Link Control", "p25cai.lc",
			FT_NONE, BASE_NONE, NULL, 0x0,
			NULL, HFILL }
		},
		{ &hf_p25cai_lcf,
			{ "Link Control Format", "p25cai.lcf",
			FT_UINT8, BASE_HEX, VALS(link_control_formats), 0x0,
			NULL, HFILL }
		},
		{ &hf_p25cai_lbf,
			{ "Last Block Flag", "p25cai.lbf",
			FT_BOOLEAN, BASE_NONE, NULL, 0x80,
			NULL, HFILL }
		},
		{ &hf_p25cai_ptbf,
			{ "Protected Trunking Block Flag", "p25cai.ptbf",
			FT_BOOLEAN, BASE_NONE, NULL, 0x40,
			NULL, HFILL }
		},
		{ &hf_p25cai_isp_opcode,
			{ "Opcode", "p25cai.isp.opcode",
			FT_UINT8, BASE_HEX, VALS(isp_opcodes), 0x3F,
			NULL, HFILL }
		},
		{ &hf_p25cai_osp_opcode,
			{ "Opcode", "p25cai.osp.opcode",
			FT_UINT8, BASE_HEX, VALS(osp_opcodes), 0x3F,
			NULL, HFILL }
		},
		{ &hf_p25cai_unknown_opcode,
			{ "Unknown Opcode (non-standard MFID)", "p25cai.unknown.opcode",
			FT_UINT8, BASE_HEX, NULL, 0x3F,
			NULL, HFILL }
		},
		{ &hf_p25cai_args,
			{ "Arguments", "p25cai.args",
			FT_UINT64, BASE_HEX, NULL, 0x0,
			NULL, HFILL }
		},
		{ &hf_p25cai_crc,
			{ "CRC", "p25cai.crc",
			FT_UINT16, BASE_HEX, NULL, 0x0,
			NULL, HFILL }
		},
		{ &hf_p25cai_imbe,
			{ "Raw IMBE Frame", "p25cai.imbe",
			FT_BYTES, BASE_HEX, NULL, 0x0,
			NULL, HFILL }
		},
		{ &hf_p25cai_lsd,
			{ "Low Speed Data", "p25cai.lsd",
			FT_UINT16, BASE_HEX, NULL, 0x0,
			NULL, HFILL }
		},
		{ &hf_p25cai_es,
			{ "Encryption Sync", "p25cai.es",
			FT_NONE, BASE_NONE, NULL, 0x0,
			NULL, HFILL }
		},
		{ &hf_p25cai_an,
			{ "A/N", "p25cai.an",
			FT_BOOLEAN, BASE_NONE, NULL, 0x40,
			NULL, HFILL }
		},
		{ &hf_p25cai_io,
			{ "I/O", "p25cai.io",
			FT_BOOLEAN, BASE_NONE, TFS(&inbound_outbound), 0x20,
			NULL, HFILL }
		},
		{ &hf_p25cai_pdu_format,
			{ "Format", "p25cai.pdu.format",
			FT_UINT8, BASE_HEX, VALS(pdu_formats), 0x1F,
			NULL, HFILL }
		},
		{ &hf_p25cai_sapid,
			{ "SAP ID", "p25cai.sapid",
			FT_UINT8, BASE_HEX, VALS(service_access_points), 0x3F,
			NULL, HFILL }
		},
		{ &hf_p25cai_llid,
			{ "Logical Link ID", "p25cai.llid",
			FT_UINT24, BASE_HEX, NULL, 0x0,
			NULL, HFILL }
		},
		{ &hf_p25cai_fmf,
			{ "Full Message Flag", "p25cai.fmf",
			FT_BOOLEAN, BASE_NONE, NULL, 0x80,
			NULL, HFILL }
		},
		{ &hf_p25cai_btf,
			{ "Blocks to Follow", "p25cai.btf",
			FT_UINT8, BASE_DEC, NULL, 0x7F,
			NULL, HFILL }
		},
		{ &hf_p25cai_poc,
			{ "Pad Octet Count", "p25cai.poc",
			FT_UINT8, BASE_HEX, NULL, 0x1F,
			NULL, HFILL }
		},
		{ &hf_p25cai_syn,
			{ "Syn", "p25cai.syn",
			FT_BOOLEAN, BASE_NONE, NULL, 0x80,
			NULL, HFILL }
		},
		{ &hf_p25cai_ns,
			{ "N(S)", "p25cai.ns",
			FT_UINT8, BASE_HEX, NULL, 0x70,
			NULL, HFILL }
		},
		{ &hf_p25cai_fsnf,
			{ "Fragment Sequence Number Field", "p25cai.fsnf",
			FT_UINT8, BASE_HEX, NULL, 0x0F,
			NULL, HFILL }
		},
		{ &hf_p25cai_dho,
			{ "Data Header Offset", "p25cai.dho",
			FT_UINT8, BASE_HEX, NULL, 0x3F,
			NULL, HFILL }
		},
		{ &hf_p25cai_db,
			{ "Data Block", "p25cai.db",
			FT_NONE, BASE_NONE, NULL, 0x0,
			NULL, HFILL }
		},
		{ &hf_p25cai_dbsn,
			{ "Data Block Serial Number", "p25cai.dbsn",
			FT_UINT8, BASE_HEX, NULL, 0xFE,
			NULL, HFILL }
		},
		{ &hf_p25cai_crc9,
			{ "CRC", "p25cai.crc9",
			FT_UINT16, BASE_HEX, NULL, 0x1FF,
			NULL, HFILL }
		},
		{ &hf_p25cai_ud,
			{ "User Data", "p25cai.ud",
			FT_BYTES, BASE_HEX, NULL, 0x0,
			NULL, HFILL }
		},
		{ &hf_p25cai_packet_crc,
			{ "Packet CRC", "p25cai.packet_crc",
			FT_UINT32, BASE_HEX, NULL, 0x0,
			NULL, HFILL }
		},
		{ &hf_p25cai_class,
			{ "Response Class", "p25cai.class",
			FT_UINT8, BASE_HEX, NULL, 0xA0,
			NULL, HFILL }
		},
		{ &hf_p25cai_type,
			{ "Response Type", "p25cai.type",
			FT_UINT8, BASE_HEX, NULL, 0x38,
			NULL, HFILL }
		},
		{ &hf_p25cai_status,
			{ "Response Status", "p25cai.status",
			FT_UINT8, BASE_HEX, NULL, 0x07,
			NULL, HFILL }
		},
		{ &hf_p25cai_x,
			{ "X", "p25cai.x",
			FT_BOOLEAN, BASE_HEX, NULL, 0x80,
			NULL, HFILL }
		},
		{ &hf_p25cai_sllid,
			{ "Source Logical Link ID", "p25cai.sllid",
			FT_UINT24, BASE_HEX, NULL, 0x0,
			NULL, HFILL }
		}
	};

/* Setup protocol subtree arrays */
	static gint *ett[] = {
		&ett_p25cai,
		&ett_ss,
		&ett_nid,
		&ett_du,
		&ett_lc,
		&ett_es,
		&ett_db
	};

/* Register the protocol name and description */
	proto_p25cai = proto_register_protocol(
		"APCO Project 25 Common Air Interface",		/* full name */
		"P25 CAI",	/* short name */
		"p25cai"	/* abbreviation (e.g. for filters) */
		);

/* Required function calls to register the header fields and subtrees used */
	proto_register_field_array(proto_p25cai, hf, array_length(hf));
	proto_register_subtree_array(ett, array_length(ett));

}

void
proto_reg_handoff_p25cai(void)
{
	static gboolean inited = FALSE;

	if (!inited) {

		dissector_handle_t p25cai_handle;

		/*  Use new_create_dissector_handle() to indicate that dissect_p25cai()
		 *  returns the number of bytes it dissected (or 0 if it thinks the packet
		 *  does not belong to APCO Project 25 Common Air Interface).
		 */
		p25cai_handle = new_create_dissector_handle(dissect_p25cai,	proto_p25cai);
		/* FIXME: Temporarily using a reserved ethertype!  This is a
		 * development shortcut that should be corrected later.  Perhaps we
		 * will apply for a libpcap data link type or use other mechanisms in
		 * the future.  Also, we can always use "decode as" in Wireshark.
		 */
		dissector_add("ethertype", 0xFFFF, p25cai_handle);

		inited = TRUE;
	}
}


/* Utility functions */

/* count the number of 1 bits in an int */
int
count_bits(unsigned int n)
{
    int i = 0;
    for (i = 0; n != 0; i++)
        n &= n - 1;
    return i;
}

/* return the index of the lowest value in a list */
int
find_min(guint8 list[], int len)
{
	int min = list[0];	
	int index = 0;	
	int unique = 1;	
	int i;

	for (i = 1; i < len; i++) {
		if (list[i] < min) {
			min = list[i];
			index = i;
			unique = 1;
		} else if (list[i] == min) {
			unique = 0;
		}
	}
	/* return -1 if a minimum can't be found */
	if (!unique)
		return -1;

	return index;
}
