/* * gquic_process.c * * Created on: 2019年4月2日 * Author: root */ #include "gquic_process.h" #include "quic_analysis.h" #include "quic_callback.h" #include #include #include #include #define C2S 0x01 #define S2C 0x02 #define GQUIC_HANDSHAKE_LEN 12+1+1+4+4 #define DIVERSIFICATION_NONCE_LEN 32 extern struct quic_param_t g_quic_param; enum gquic_type { GQUIC=1, UNKNOWN_QUIC_TYPE }; enum gquic_handshake_type { QUIC_Crypto=1, UNKNOWN_HANDSHAKE_TYPE }; int get_quic_tlv(char *start_pos, quic_tlv_t *tlv, int len, int type, int thread_seq) { if(tlv->ptr_value==NULL && len>0) { tlv->ptr_value=(char *)dictator_malloc(thread_seq, len); memset(tlv->ptr_value, 0, len); tlv->length=len; tlv->type=type; memcpy(tlv->ptr_value, start_pos, tlv->length); } return 0; } UINT32 get_stream_id(struct streaminfo *pstream, char* quic_data, UINT8 frame_type, UINT32 *skip_len) { UINT16 data_length=0; UINT32 stream_id=0; UINT8 stream_id_len=0; UINT8 len_data=0,len_stream=0, len_offset=0; len_stream = read_stream_len(frame_type); switch(len_stream) { case 1: stream_id=quic_data[*skip_len]; break; case 2: stream_id=a_pletoh16((void *)quic_data, *skip_len); break; case 3: stream_id=a_pletoh24((void *)quic_data, *skip_len); break; default: stream_id=a_pletoh32((void *)quic_data, *skip_len); break; } *skip_len+=len_stream; // stream ID length if(frame_type&STREAM_D) { data_length=ntohs(a_pletoh16((void *)quic_data, *skip_len)); *skip_len+=2; //data length } len_offset=read_offset_len(frame_type); *skip_len+=len_offset; MESA_handle_runtime_log(g_quic_param.logger, RLOG_LV_DEBUG, "QUIC_FRAME", "stream_id: %u data length: %u offset length: %u addr: %s", stream_id, data_length, len_offset, printaddr(&pstream->addr, pstream->threadnum)); return stream_id; } UINT32 get_packet_number(char* g_data_t, UINT32 offset, UINT8 pkn_len) { switch(pkn_len) { case 1: return g_data_t[offset]; break; case 2: return ntohs(a_pletoh16((void *)g_data_t, offset)); break; case 4: return a_pletoh32((void *)g_data_t, offset); break; default: return a_pletoh48((void *)g_data_t, offset); break; } return 0; } enum GQUIC_VERSION is_gquic_protocol(struct streaminfo *pstream, char *payload, uint32_t payload_len, uint8_t *pub_flags, uint32_t *version, uint32_t *skip_len) { uint32_t is_q064=0; uint64_t connection_id = 0; int connection_id_len = 0; enum GQUIC_VERSION quic_version=GQUIC_UNKNOWN; if(a_canRead(5, payload_len, *skip_len)) { *skip_len+=1; strncpy((char*)&is_q064, payload+(*skip_len), 4); if(ntohl(is_q064)==VER_Q046) { *skip_len += 2; *version=(payload[*skip_len]&0x0f)*10 + (payload[*skip_len+1]&0x0f); *skip_len += 2; MESA_handle_runtime_log(g_quic_param.logger, RLOG_LV_DEBUG, "QUIC_PUB_FLAGS", "version: Q0%u addr: %s", *version, printaddr(&pstream->addr, pstream->threadnum)); return GQUIC_Q046; } *skip_len-=1; } *pub_flags=payload[*skip_len]; // public flags *skip_len+=1; if(*pub_flags>PACKET_PUBLIC_FLAGS_MAX) { return quic_version; } connection_id_len=read_conn_id_len(*pub_flags); connection_id=(connection_id_len==0) ? 0 : (a_pntoh64((void *)payload, *skip_len)); *skip_len+=connection_id_len; // CID length if(payload[*skip_len]==PUBLIC_FLAG_VER_FST_BYTE && *pub_flags&PUBLIC_FLAG_VER) { *skip_len += 2; *version=(payload[*skip_len]&0x0f)*10 + (payload[*skip_len+1]&0x0f); *skip_len += 2; quic_version=GQUIC_OTHERS; } if(*pub_flags==PUBLIC_FLAG_NONCE) { *skip_len+=32; //diversification nonce } UINT8 pkt_seq_len=read_seq_num_len(*pub_flags); UINT32 pkt_seq=get_packet_number(payload, *skip_len, pkt_seq_len); *skip_len+=pkt_seq_len; // skip pkt_num if(*pub_flags&PUBLIC_FLAG_VER) { //message authentication hash *skip_len+=MSG_AUTH_HASH_LEN; if(*version>0 && *version<34) { *skip_len+=1; //private flags } } MESA_handle_runtime_log(g_quic_param.logger, RLOG_LV_DEBUG, "QUIC_IDETIFY", "pub_flags: 0X%02X connection_id: %llu version: Q%03u packet number: %u dir(1: C2S;2: S2C): %d addr: %s", *pub_flags, connection_id, *version, pkt_seq, pstream->curdir, printaddr(&pstream->addr, pstream->threadnum)); return quic_version; } void gquic_proc_tag(struct streaminfo *pstream, struct quic_stream *a_quic_stream, UINT16 tag_num, UINT8 direction, void* a_packet, char * quic_data, UINT32 quic_data_len, UINT32 *used_len) { UCHAR return_val; UINT32 tag_type; UINT32 total_tag_len=0, tag_len=0; UINT32 tag_value_start=tag_num*4*2+(*used_len); // skip length of type and offset, type(offset)=szieof(int) UINT32 tag_offset_end=0, pre_tag_offset_end=0; int tag_type_len=4, tag_offset_len=4,num=0; enum quic_interested_region region_mask=QUIC_INTEREST_KEY_MASK; while(tag_num > 0 && a_canRead(tag_type_len+tag_offset_len, quic_data_len, *used_len)) { tag_type=a_pntoh32(quic_data, *used_len); *used_len+=4; tag_offset_end=a_pletoh32(quic_data, *used_len); *used_len+=4; tag_len=tag_offset_end-pre_tag_offset_end; if(tag_len<0 || (tag_offset_end>=quic_data_len) || (tag_len>quic_data_len-tag_value_start)) { return ; } switch(tag_type) { case TAG_PAD: break; case TAG_CCS: //common_cert region_mask=QUIC_COMM_CERT_MASK; get_quic_tlv(quic_data+tag_value_start, &a_quic_stream->common_cert, tag_len, tag_type, pstream->threadnum); break; case TAG_CCRT: //cached_cert region_mask=QUIC_CACHED_CERT_MASK; get_quic_tlv(quic_data+tag_value_start, &a_quic_stream->cached_cert, tag_len, tag_type, pstream->threadnum); break; case TAG_CRT: //cert_chain ????? length need change if(a_quic_stream->cert_chain.length==0) { //tag_value_start += a_quic_stream->cert_chain.length; tag_len=(tag_len>(quic_data_len-tag_value_start+1) ? (quic_data_len-tag_value_start+1) : tag_len); get_quic_tlv(quic_data+tag_value_start, &a_quic_stream->cached_cert, tag_len, tag_type, pstream->threadnum); region_mask=QUIC_CERT_CHAIN_MASK; } break; case TAG_SNI: if(a_quic_stream->st_client_hello.server_name_len==0) { a_quic_stream->st_client_hello.server_name_len=(tag_len>SERVER_NAME_LEN ? SERVER_NAME_LEN : tag_len); memcpy(a_quic_stream->st_client_hello.server_name, quic_data + tag_value_start, tag_len); MESA_handle_runtime_log(g_quic_param.logger, RLOG_LV_DEBUG, "QUIC_SNI", "SNI: %s addr: %s", a_quic_stream->st_client_hello.server_name, printaddr(&pstream->addr, pstream->threadnum)); } break; case TAG_UAID: if(a_quic_stream->st_client_hello.user_agent_len==0) { a_quic_stream->st_client_hello.user_agent_len=(tag_len>USER_AGENT_LEN ? USER_AGENT_LEN : tag_len); memcpy(a_quic_stream->st_client_hello.user_agent, quic_data + tag_value_start, a_quic_stream->st_client_hello.user_agent_len); } break; default: if(direction == 0x01 && num < a_quic_stream->st_client_hello.ext_tag_num) { a_quic_stream->st_client_hello.ext_tags[num]=(quic_tlv_t *)dictator_malloc(pstream->threadnum, sizeof(quic_tlv_t)); get_quic_tlv(quic_data+tag_value_start, a_quic_stream->st_client_hello.ext_tags[num], (tag_len>MAX_TAG_VALUE_LEN-1 ? MAX_TAG_VALUE_LEN-1 : tag_len), tag_type, pstream->threadnum); } else if(num < a_quic_stream->st_server_hello.ext_tag_num) { a_quic_stream->st_server_hello.ext_tags[num]=(quic_tlv_t *)dictator_malloc(pstream->threadnum, sizeof(quic_tlv_t)); get_quic_tlv(quic_data+tag_value_start, a_quic_stream->st_server_hello.ext_tags[num], (tag_len>MAX_TAG_VALUE_LEN-1 ? MAX_TAG_VALUE_LEN-1 : tag_len), tag_type, pstream->threadnum); } num++; break; } tag_num--; tag_value_start+=tag_len; total_tag_len+=tag_len; pre_tag_offset_end=tag_offset_end; return_val=quic_callPlugins(a_quic_stream, pstream, region_mask, pstream->threadnum, a_packet); region_mask=QUIC_INTEREST_KEY_MASK; } *used_len += total_tag_len; return; } int gquic_frame_type_ack(struct quic_stream* a_quic_stream, char * quic_data, UINT32 quic_data_len, UINT8 frame_type, UINT32 *used_len) { UINT8 num_timestamp, num_ranges, num_revived, num_blocks; UINT32 len_largest_observed=0, len_missing_packet=0; *used_len+=1; len_largest_observed = read_largest_observed_len(frame_type); len_missing_packet = read_missing_packet_len(frame_type); //No longer Entropy after Q034 if(a_quic_stream->version < 34) { //Send Entropy *used_len += 1; *used_len += len_largest_observed; *used_len += 2; //ack delay time strncpy((char*)&num_timestamp, quic_data+*used_len,1); *used_len += 1; if(num_timestamp > 0) { *used_len += 1; *used_len += 4; *used_len += (num_timestamp - 1)*(1+2); } if(frame_type & ACK_N) { strncpy((char*)&num_ranges, quic_data+*used_len,1); *used_len += 1; *used_len += num_ranges*(len_missing_packet+1); strncpy((char*)&num_revived, quic_data+*used_len,1); *used_len += 1; //Num Revived x Length Largest Observed *used_len += num_revived*len_largest_observed; } } else { *used_len += len_largest_observed; //Largest Acked *used_len += 2; //Largest Acked Delta Time if(frame_type & ACK_N) //Ack Block { strncpy((char*)&num_blocks, quic_data+*used_len,1); *used_len += 1; } *used_len += len_missing_packet; //First Ack Block Length if(num_blocks) { //Gap to next block *used_len += 1; num_blocks -= 1; *used_len += (num_blocks - 1)*len_missing_packet; } strncpy((char*)&num_timestamp, quic_data+*used_len,1); //Timestamp *used_len += 1; if(num_timestamp > 0) { *used_len += 1; //Delta Largest Acked *used_len += 4; //Time Since Largest Acked //Num Timestamp x (Delta Largest Acked + Time Since Previous Timestamp) *used_len += (num_timestamp - 1)*(1+2); } } return 0; } // return value: Have no meaning, only numerical value int gquic_frame_type_control(struct quic_stream* a_quic_stream, char * quic_data, UINT32 quic_data_len, UINT8 frame_type, uint32_t pkt_num_len, UINT32 *used_len) { UINT16 len_reason; *used_len+=1; switch(frame_type) { case RST_STREAM: *used_len +=4; //stream id *used_len+=8; //Byte Offset *used_len+=4; //Error Code a_quic_stream->fin_flag=QUIC_TRUE; break; case CONNECTION_CLOSE: len_reason = 0; *used_len += 4; //Error Code len_reason = a_pntoh16(quic_data, *used_len); //Reason Phrase Length *used_len += 2; //Reason Phrase,If length remaining == len_reason, it is Connection Close if(get_remaining_len(quic_data_len, *used_len) == len_reason) { return QUIC_DATA; } a_quic_stream->fin_flag=(a_quic_stream->fin_flag==QUIC_FALSE) ? QUIC_HALF_CLOSE : QUIC_TRUE; break; case GOAWAY: len_reason = 0; *used_len += 4; //Error Code *used_len += 4; //Last Good Stream ID len_reason = a_pntoh16(quic_data, *used_len); //Reason Phrase Length *used_len += 2; *used_len += len_reason; //Reason Phrase break; case WINDOW_UPDATE: *used_len += 4; //Stream ID *used_len += 8; //Byte Offset break; case BLOCKED: *used_len += 4; //Stream ID break; case STOP_WAITING: //No longer Entropy after Q034 if(a_quic_stream->version < 34) { *used_len += 1; // Send Entropy } //Least Unacked Delta *used_len += pkt_num_len; break; case PING: //No Payload case PADDING: default: return QUIC_FALSE; break; } return QUIC_TRUE; } int gquic_frame_type_stream(struct streaminfo *pstream, struct quic_stream* a_quic_stream, char *quic_data, UINT32 quic_data_len, UINT8 frame_type, UINT32 *used_len, void* a_packet) { UINT8 return_val; UINT16 tag_num = 0; UINT32 stream_id, message_tag; if(frame_type&STREAM_F) { a_quic_stream->fin_flag=QUIC_TRUE; } stream_id=get_stream_id(pstream, quic_data, frame_type, used_len); if(stream_id==1) //handshake { message_tag = a_pntoh32((void *)quic_data, *used_len); *used_len += 4; tag_num = a_pletoh16(quic_data, *used_len); *used_len += 2; //tag_num *used_len += 2; //padding switch(message_tag) { case CHLO: //MTAG_CHLO; if(a_quic_stream->st_client_hello.ext_tags==NULL) { a_quic_stream->st_client_hello.ext_tags=(quic_tlv_t **)dictator_malloc(pstream->threadnum, tag_num*sizeof(quic_tlv_t*)); a_quic_stream->st_client_hello.ext_tag_num = tag_num; } else { if(a_quic_stream->st_server_hello.ext_tag_num && tag_num>a_quic_stream->st_client_hello.ext_tag_num) { quic_release_exts(pstream->threadnum, a_quic_stream->st_client_hello.ext_tags, a_quic_stream->st_client_hello.ext_tag_num); a_quic_stream->st_client_hello.ext_tags=(quic_tlv_t **)dictator_malloc(pstream->threadnum, tag_num*sizeof(quic_tlv_t*)); a_quic_stream->st_client_hello.ext_tag_num = tag_num; } else { return -1; } } gquic_proc_tag(pstream, a_quic_stream, tag_num, C2S, a_packet, quic_data, quic_data_len, used_len); if(a_quic_stream->st_client_hello.server_name_len>0 || a_quic_stream->st_client_hello.user_agent_len>0) { quic_callPlugins(a_quic_stream, pstream, QUIC_CLIENT_HELLO_MASK, pstream->threadnum, a_packet); } break; case SHLO: //MTAG_SHLO; if(a_quic_stream->st_server_hello.ext_tags==NULL) { return -2; } a_quic_stream->st_server_hello.ext_tags=(quic_tlv_t **)dictator_malloc(pstream->threadnum, tag_num*sizeof(quic_tlv_t*)); a_quic_stream->st_server_hello.ext_tag_num=tag_num; gquic_proc_tag(pstream, a_quic_stream, tag_num, S2C, a_packet, quic_data, quic_data_len, used_len); if(a_quic_stream->st_server_hello.ext_tags != NULL) { quic_callPlugins(a_quic_stream, pstream, QUIC_SERVER_HELLO_MASK, pstream->threadnum, a_packet); } break; case REJ: //MTAG_REJ; if(a_quic_stream->st_server_hello.ext_tags!=NULL) { return -3; } a_quic_stream->st_server_hello.ext_tags=(quic_tlv_t **)dictator_malloc(pstream->threadnum, tag_num*sizeof(quic_tlv_t*)); a_quic_stream->st_server_hello.ext_tag_num=tag_num; gquic_proc_tag(pstream, a_quic_stream, tag_num, S2C, a_packet, quic_data, quic_data_len, used_len); if(a_quic_stream->st_server_hello.ext_tags != NULL) { quic_callPlugins(a_quic_stream, pstream, QUIC_SERVER_HELLO_MASK, pstream->threadnum, a_packet); } break; default: break; } } return 0; } //frame type->stream->offset->data length void gquic_proc_unencrypt(struct streaminfo *pstream, struct quic_stream* a_quic_stream, uint32_t pkt_num_len, void* a_packet, char * quic_data, UINT32 quic_data_len, UINT32 *used_len) { UINT8 frame_type; UINT32 ret=0; while(*used_lenfin_flag=QUIC_FALSE; // is_handshake_pkt don't identify QUIC; but function set QUIC_TRUE if(ret!=QUIC_TRUE) { return ret; } } else { if(frame_type&STREAM) { if(frame_type&STREAM_D) { *used_len+=2; } *used_len+=1; *used_len+=read_stream_len(frame_type); *used_len+=read_offset_len(frame_type); if((quic_data_len-*used_len)<=4) { return QUIC_FALSE; } strncpy((char*)&message_tag, quic_data+*used_len, 4); if(ntohl(message_tag)==CHLO || ntohl(message_tag)==SHLO || ntohl(message_tag)==REJ) { return QUIC_TRUE; } } else if(frame_type&ACK) { gquic_frame_type_ack(a_quic_stream, quic_data, quic_data_len, frame_type, used_len); } else { *used_len +=1; return QUIC_FALSE; } } } return QUIC_FALSE; } UINT8 parse_gquic_Q046(struct streaminfo *pstream, void* a_packet, char * payload, uint32_t payload_len, uint32_t *used_len, struct quic_stream* a_quic_stream, uint8_t pub_flags) { uint8_t frame_type; uint16_t tag_num=0; uint32_t stream_id, message_tag; if(!a_canRead(25, payload_len, *used_len)) { return QUIC_RETURN_DROPME; } *used_len += 25; while(*used_len < payload_len) { a_readUInt8(&frame_type, payload, payload_len, used_len); if(frame_type & STREAM) { stream_id=get_stream_id(pstream, payload, frame_type, used_len); if(stream_id==1) { message_tag = a_pntoh32((void *)payload, *used_len); *used_len += 4; tag_num = a_pletoh16(payload, *used_len); *used_len += 2; //tag_num *used_len += 2; //padding switch(message_tag) { case CHLO: //MTAG_CHLO; if(a_quic_stream->st_client_hello.ext_tags==NULL && tag_num>0) { a_quic_stream->st_client_hello.ext_tags=(quic_tlv_t **)dictator_malloc(pstream->threadnum, tag_num*sizeof(quic_tlv_t*)); a_quic_stream->st_client_hello.ext_tag_num = tag_num; } gquic_proc_tag(pstream, a_quic_stream, tag_num, C2S, a_packet, payload, payload_len, used_len); if(a_quic_stream->st_client_hello.server_name_len > 0 || a_quic_stream->st_client_hello.user_agent_len > 0) { quic_callPlugins(a_quic_stream, pstream, QUIC_CLIENT_HELLO_MASK, pstream->threadnum, a_packet); } break; default: break; } } } } return QUIC_RETURN_NORM; } UINT8 parse_gquic(struct streaminfo *pstream, void* a_packet, char * payload, uint32_t payload_len, uint32_t *used_len, struct quic_stream* a_quic_stream, uint8_t pub_flags) { int is_handshake=0; uint32_t pkt_num_len = 0; uint32_t skip_len=0; pkt_num_len=read_seq_num_len(pub_flags); //version 协商 if(!(PUBLIC_FLAG_VER&pub_flags!=0) && a_quic_stream->version && !a_quic_stream->version_cfm) { a_quic_stream->version_cfm = QUIC_TRUE; quic_callPlugins(a_quic_stream, pstream, QUIC_VERSION_MASK, pstream->threadnum, a_packet); MESA_handle_runtime_log(g_quic_param.logger, RLOG_LV_DEBUG, "QUIC_VERSION", "version: %u addr: %s", a_quic_stream->version, printaddr(&pstream->addr, pstream->threadnum)); } skip_len=*used_len; is_handshake=is_handshake_pkt(a_quic_stream, pkt_num_len, payload, payload_len, &skip_len); // just identify switch(is_handshake) { case QUIC_TRUE: //handshake a_quic_stream->is_quic_stream = QUIC_TRUE; gquic_proc_unencrypt(pstream, a_quic_stream, pkt_num_len, a_packet, payload, payload_len, used_len); break; case QUIC_DATA: //ack or special stream a_quic_stream->is_quic_stream = QUIC_TRUE; break; default: //gquic data or not gquic packet if(!a_quic_stream->is_quic_stream) { return QUIC_RETURN_DROPME; } quic_callPlugins(a_quic_stream, pstream, QUIC_APPLICATION_DATA_MASK, pstream->threadnum, a_packet); break; } return QUIC_RETURN_NORM; } //cid->version->nounce->pkt num->ahn hash(12) UINT8 gquic_process(struct streaminfo *pstream, struct quic_stream* a_quic_stream, int thread_seq, void* a_packet) { uint8_t pub_flags = 0; uint32_t used_len = 0; int ret=QUIC_RETURN_DROPME; enum GQUIC_VERSION is_gquic=GQUIC_UNKNOWN; struct udpdetail *udp_detail=(struct udpdetail *)pstream->pdetail; if(!a_quic_stream->is_quic_stream && udp_detail->datalen<=GQUIC_HEADER_LEN) { MESA_handle_runtime_log(g_quic_param.logger, RLOG_LV_DEBUG, "QUIC", "This is not quic (!a_quic_stream->is_quic_stream)=%d, or packet length is litter udp_detail->datalen<=GQUIC_HEADER_LEN(%d<=%d) addr: %s", !a_quic_stream->is_quic_stream, udp_detail->datalen, GQUIC_HEADER_LEN, printaddr(&pstream->addr, thread_seq)); return QUIC_RETURN_DROPME; } if(udp_detail->pdata==NULL || udp_detail->datalen<=0) { return QUIC_RETURN_NORM; } is_gquic=is_gquic_protocol(pstream, (char *)udp_detail->pdata, udp_detail->datalen, &pub_flags, &a_quic_stream->version, &used_len); if(is_gquic!=GQUIC_UNKNOWN) { a_quic_stream->is_quic_stream=QUIC_TRUE; quic_callPlugins(a_quic_stream, pstream, QUIC_VERSION_MASK, thread_seq, a_packet); switch(is_gquic) { case GQUIC_OTHERS: ret=parse_gquic(pstream, a_packet, (char *)udp_detail->pdata, udp_detail->datalen, &used_len, a_quic_stream, pub_flags); break; case GQUIC_Q046: ret=parse_gquic_Q046(pstream, a_packet, (char *)udp_detail->pdata, udp_detail->datalen, &used_len, a_quic_stream, pub_flags); break; default: break; } } if(a_quic_stream->is_quic_stream==QUIC_TRUE) { return QUIC_RETURN_NORM; } return ret; } UINT32 read_offset_len(UINT8 frame_type) { switch((frame_type&STREAM_OOO)>>2) { case 0: return 0; break; case 1: return 2; break; case 2: return 3; break; case 3: return 4; break; case 4: return 5; break; case 5: return 6; break; case 6: return 7; break; case 7: return 8; break; default: break; } return 0; } UINT32 read_stream_len(UINT8 frame_type) { UINT32 stream_len = 0; switch(frame_type&STREAM_SS) { case STREAM_ID_1BYTE: stream_len = 1; break; case STREAM_ID_2BYTE: stream_len = 2; break; case STREAM_ID_3BYTE: stream_len = 3; break; case STREAM_ID_4BYTE: stream_len = 4; break; default: break; } return stream_len; } int read_conn_id_len(UINT8 flags) { switch (flags&BYTE_CNTID_8) { case BYTE_CNTID_8: return 8; case BYTE_CNTID_0: return 0; default: return 0; // modify by liuxueli 20200522 //return -1; } } UINT32 read_seq_num_len(UINT8 flags) { switch (flags & PKT_NUM_6) { case PKT_NUM_6: return 6; case PKT_NUM_4: return 4; case PKT_NUM_2: return 2; case PKT_NUM_1: return 1; default: break; } return 1; } UINT32 read_largest_observed_len(UINT8 frame_type) { switch((frame_type & ACK_LL) >> 2) { case 0: return 1; break; case 1: return 2; break; case 2: return 4; break; case 3: return 6; break; default: break; } return 1; } UINT32 read_missing_packet_len(UINT8 frame_type) { switch(frame_type & ACK_MM) { case 0: return 1; break; case 1: return 2; break; case 2: return 4; break; case 3: return 6; break; default: break; } return 1; }