无我

让内心永远燃烧着伟大的光明的精神之火!
灵活的思考,严谨的实现
豪迈的气魄、顽强的意志和周全的思考

eSNACC对OCTET STRING 的编码和解码

本文剖析asn-octs.h/c,从源代码来学习eSNACC对OCTET STRING的编码和解码。

eSNACC对字节串OCTET STRING的处理与上一篇描述的比特串的方法类似,而且字节串的处理更加简单。所以在上一篇的基础上,我们专门分析上一篇中对连接型串解码时没有展开讲的函数,也作为上一篇的补充。上一篇可以参见eSNACC对BIT STRING的编码和解码

 

先看看eSNACC对字节串的表示方法:

typedef struct AsnOcts
{
  unsigned 
long octetLen;
  
char            *octs;
}
 AsnOcts;

可以看到与比特串很类似,唯一不同的是octetLen的类型,对字节串的是unsigned long;而比特串的是int。为什么要这样设计呢?因为长度肯定不可能是负数,所以设计成unsigned的是我们认为合理的。而对比特串的想法,我不太清楚,这其实也可以说是对那个模块的另一个设计问题。

octetLen代表该字节串的长度,但不包含串末尾的null字节。

octs是指向字节串的指针,分配的字节串长度为octetLen+1,在解码时会主动给末尾设为null。千万要注意:这个char*与比特串的char*有很大的不同:字节串的char*是一个字符指针,其指向的是一个由null终结的字符串。而比特串的就是一串比特位,最末尾也没有null。

 

好了,看字节串的编码和解码,我们发现他的操作很简单,只是一个内存拷贝的过程,编码的时候也不需要专门来存len信息,也更不需要像比特串那样做若干操作来处理字节填充、未使用位数计算等等。所以解码的时候也不需要判断这些。仅仅一个要记住的是:len是不包括字节串末尾的null字符的,只是在解码时多分配了一个字节,主动使其为null结束。

 

上面这些都很简单,我想本文就主要分析对连接型字节串的解码过程。这是两个静态函数,不够在分析这些代码之前,我觉得应该下说明一下他的设计方法:

由于连接型串就是有多个字节串嵌套构造而成的。也就是

连接串 => 连接串 + 原生串

这样的话,在解码时,势必产生很多夹杂其中的原生串碎片。如果让用户来管理这些碎片是很麻烦的事,我们更喜欢把一个连接串也存为一串整体的内存中,就如同操作一个原生串一样。正是基于这个原因,eSNACC就为我们做好封装,他自己判断串类型,然后做不同的解码,最后都是返回给用户一块连续的内存。

我们先看看用于管理串碎片的方案:结构体加宏

typedef struct StrStkElmt
{
    
char *str;
    unsigned 
long len;
}
 StrStkElmt;

typedef 
struct StrStk
{
    StrStkElmt 
*stk; /* ptr to array of SSElmts with 'size' elmts */
    unsigned 
long initialNumElmts;
    unsigned 
long numElmts;  /* total # of elements in str stk */
    unsigned 
long growElmts; /* # elmts to increase size by when nec */
    unsigned 
long nextFreeElmt; /* index of next free element */
    unsigned 
long totalByteLen; /* octet len of string stored in stk */
}
 StrStk;

extern StrStk strStkG;
/*
 * initializes stk (Allocates if nec.)
 * once stk is enlarged, it doesn't shrink
 
*/

#define RESET_STR_STK()\
{\
    strStkG.nextFreeElmt 
= 0;\
    strStkG.totalByteLen 
= 0;\
    
if (strStkG.stk == NULL){\
       strStkG.stk 
= (StrStkElmt*) malloc ((strStkG.initialNumElmts) *sizeof (StrStkElmt));\
       strStkG.numElmts 
= strStkG.initialNumElmts;}
\
}


/*
 * add a char*,len pair to top of stack.
 * grows stack if necessary using realloc (!)
 
*/

#define PUSH_STR(strPtr, strsLen, env)\
{\
    
if (strStkG.nextFreeElmt >= strStkG.numElmts)\
    
{\
       strStkG.stk 
= (StrStkElmt*) realloc (strStkG.stk, (strStkG.numElmts + strStkG.growElmts) *sizeof (StrStkElmt));\
       strStkG.numElmts 
+= strStkG.growElmts;\
    }
\
    strStkG.totalByteLen 
+= strsLen;\
    strStkG.stk[strStkG.nextFreeElmt].str 
= strPtr;\
    strStkG.stk[strStkG.nextFreeElmt].len 
= strsLen;\
    strStkG.nextFreeElmt
++;\
}

/*
 * Set up size values for the stack that is used for merging constructed
 * octet or bit string into single strings.
 * ****  Call this before decoding anything. *****
 * Note: you don't have to call this if the default values
 * for initialStkSizeG and stkGrowSizeG are acceptable
 
*/

#define SetupConsBitsOctsStringStk (initialNumberOfElmts, numberOfElmtsToGrowBy)\
{\
    strStkG.initialNumElmts 
= initialNumberOfElmts; \
    strStkG.growElmts 
= numberOfElmtsToGrowBy;\
}

可以看到用于管理每一块比特串或字节串碎片的结构StrStkElmt与我们定义的AsnBits/AsnOcts非常类似:基本就是定义字段的顺序不同。

而结构体StrStk就是用来管理若干碎片的,使得我们在解码时不需要处理这些烦人的片段,他内部将这些都解码好并且最后拷贝到一个整体的内存块中返回,真是功德无限呀!我们就来认识一下这个活佛吧:

StrStkElmt *stk:指向碎片串的指针。

unsigned long initialNumElmts:首次分配StrStkElmt的数目。
unsigned long numElmts:stk中拥有的总的StrStkElmt数。
unsigned long growElmts:当首次分配的initialNumElmts不够用,而需要再次分配时,默认的增长数。
unsigned long nextFreeElmt:stk中可用存放StrStkElmt的序号。
unsigned long totalByteLen:stk存放的串的总字节数。

然后声明了一个变量strStkG,具体定义在实现文件中。后面就是定义了几个宏来操作这个变量:

RESET_STR_STK:用于给stk分配内存,并且初始化numElmts值。

PUSH_STR:将char*,len对加到stk串中。如果空间不够会自动增长。

SetupConsBitsOctsStringStk:如果你对他提供的initialNumElmts和growElmts不满意,请在做任何解码操作之前调用这个宏来定义自己的需求。

 

对于这种方案,有几点说明是:

1、一旦stk扩容了,那么就无法缩水。

2、如果由于数量不够而需要增长,这可能会导致内存的重分配和拷贝,一定程度影响性能。

在该模块的实现文件中只有结构体变量strStkG的定义:

/* global for use by AsnBits and AsnOcts */
StrStk strStkG 
= { NULL, 12806400 };

也就是strStkG的初始值为:StrStkElmt指针为空,默认会分配的128个指针空间。因为当前还没分配内存,所以总数为0.当不够用时一次增长64个。然后后面两个量都初始化为0.

 

好了,有了对这个辅助体的全面的认识,那么最上面的两个解码函数也就迎刃而解了。

先看解码连续字节串的入口:

/*
 * Decodes a seq of universally tagged octets strings until either EOC is
 * encountered or the given len is decoded.  Merges them into a single
 * string. puts a NULL terminator on the string but does not include
 * this in the length.
 
*/

static void
BDecConsAsnOcts PARAMS ((b, len, result, bytesDecoded, env),
    GenBuf 
*b _AND_
    AsnLen len _AND_
    AsnOcts 
*result _AND_
    AsnLen 
*bytesDecoded _AND_
    jmp_buf env)
{
    
char *bufCurr;
    unsigned 
long curr;

    RESET_STR_STK();

    
/*
     * decode each piece of the octet string, puting
     * an entry in the octet string stack for each
     
*/

    FillOctetStringStk (b, len, bytesDecoded, env);

    result
->octetLen = strStkG.totalByteLen;

    
/* alloc str for all octs pieces with extra byte for null terminator */
    bufCurr 
= result->octs = Asn1Alloc (strStkG.totalByteLen +1);
    CheckAsn1Alloc (result
->octs, env);

    
/* copy octet str pieces into single blk */
    
for (curr = 0; curr < strStkG.nextFreeElmt; curr++)
    
{
        memcpy (bufCurr, strStkG.stk[curr].str, strStkG.stk[curr].len);
        bufCurr 
+= strStkG.stk[curr].len;
    }


    
/* add null terminator - this is not included in the str's len */
    
*bufCurr = '\0';

}
  /* BDecConsAsnOcts */

首先函数注释说明了解码一串连接字节串,直到指定长度或者遇到EOC。最后把这些分散的碎片整合到一个字符串中。与比特串不同的是,会在串最后添加null字符。

逻辑上,首先调用RESET_STR_STK()给完成strStkG的指针内存分配。然后调用FillOctetStringStk来完成真正的碎片解码,该函数完成操作后,各个碎片都可以通过strStkG的成员变量来访问了。正如我们看到的,他分配了一个strStkG.totalByteLen +1的空间,多1是为了存放null。然后遍历每一个有效的指针,将值拷贝到分配这一整块内存中。最后在末尾附上null。

FillOctetStringStk具体定义如下:

/*
 * Used for decoding constructed OCTET STRING values into
 * a contiguous local rep.
 * fills string stack with references to the pieces of a
 * construced octet string
 
*/

static void
FillOctetStringStk PARAMS ((b, elmtLen0, bytesDecoded, env),
    GenBuf 
*b _AND_
    AsnLen elmtLen0 _AND_
    AsnLen 
*bytesDecoded _AND_
    jmp_buf env)
{
    unsigned 
long refdLen;
    unsigned 
long totalRefdLen;
    
char *strPtr;
    unsigned 
long totalElmtsLen1 = 0;
    unsigned 
long tagId1;
    unsigned 
long elmtLen1;

    
for (; (totalElmtsLen1 < elmtLen0) || (elmtLen0 == INDEFINITE_LEN); )
    
{
        tagId1 
= BDecTag (b, &totalElmtsLen1, env);

        
if ((tagId1 == EOC_TAG_ID) && (elmtLen0 == INDEFINITE_LEN))
        
{
            BDEC_2ND_EOC_OCTET (b, 
&totalElmtsLen1, env);
            
break;
        }


        elmtLen1 
= BDecLen (b, &totalElmtsLen1, env);
        
if (tagId1 == MAKE_TAG_ID (UNIV, PRIM, OCTETSTRING_TAG_CODE))
        
{
            
/*
             * primitive part of string, put references to piece (s) in
             * str stack
             
*/

            totalRefdLen 
= 0;
            refdLen 
= elmtLen1;
            
while (1)
            
{
                strPtr 
= (char *)BufGetSeg (b, &refdLen);

                PUSH_STR (strPtr, refdLen, env);
                totalRefdLen 
+= refdLen;
                
if (totalRefdLen == elmtLen1)
                    
break/* exit this while loop */

                
if (refdLen == 0/* end of data */
                
{
                    Asn1Error (
"BDecConsOctetString: ERROR - attempt to decode past end of data\n");
                    longjmp (env, 
-18);
                }

                refdLen 
= elmtLen1 - totalRefdLen;
            }

            totalElmtsLen1 
+= elmtLen1;
        }



        
else if (tagId1 == MAKE_TAG_ID (UNIV, CONS, OCTETSTRING_TAG_CODE))
        
{
            
/*
             * constructed octets string embedding in this constructed
             * octet string. decode it.
             
*/

            FillOctetStringStk (b, elmtLen1, 
&totalElmtsLen1, env);
        }

        
else  /* wrong tag */
        
{
            Asn1Error (
"BDecConsOctetString: ERROR - decoded non-OCTET STRING tag inside a constructed OCTET STRING\n");
            longjmp (env, 
-19);
        }

    }
 /* end of for */

    (
*bytesDecoded) += totalElmtsLen1;

}
  /* FillOctetStringStk */

在FillOctetStringStk中,判断当前串碎片是什么类型,如果是原生类型,就直接分配内存存放缓冲区的内容;如果还是连接类型,那就递归调用本函数,否则报错。

 

好了,字节串编码解码的分析就到此了。

posted on 2012-04-23 16:22 Tim 阅读(3220) 评论(0)  编辑 收藏 引用 所属分类: eSNACC学习


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


<2009年9月>
303112345
6789101112
13141516171819
20212223242526
27282930123
45678910

导航

统计

公告

本博客原创文章,欢迎转载和交流。不过请注明以下信息:
作者:TimWu
邮箱:timfly@yeah.net
来源:www.cppblog.com/Tim
感谢您对我的支持!

留言簿(9)

随笔分类(173)

IT

Life

搜索

积分与排名

最新随笔

最新评论

阅读排行榜