• 如何解析SIP报文


    SIP协议是一个文本协议,比如下面是话机注册的首次REGISTER请求:

    REGISTER sip:10.32.26.25:5070;transport=tcp SIP/2.0
    Via: SIP/2.0/TCP 10.32.26.25:51696;rport;branch=z9hG4bKPj8d4db68b24754f539dbf3b563a44fe55;alias
    Max-Forwards: 70
    From: jimmy<sip:1000@10.32.26.25>;tag=89aefb1f3fc0413283a453eda5407f60
    To: <sip:1000@10.32.26.25>
    Call-ID: 1e7af0e67a5044658fc7f6716d329642
    CSeq: 36850 REGISTER
    User-Agent: MicroSIP/3.20.3
    Supported: outbound, path
    Contact: <sip:1000@10.32.26.25:51696;transport=TCP;ob>;reg-id=1;+sip.instance="<urn:uuid:00000000-0000-0000-0000-000011058e7e>"
    Expires: 300
    Allow: PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS
    Content-Length:  0
    

    技术上讲,完全可以逐行按String解析,白手起家,拆解出其中的内容,但是这样做一来有些原始,二来也未必高效,幸好社区里已经类似的开源项目:pkts ,借助这个开源项目,可以很方便的把上述内容快速解析出来,示例代码如下:

    先添加pom依赖(目前最新是3.0.11-SNAPSHOT)

    <dependency>
        <groupId>io.pkts</groupId>
        <artifactId>pkts-sip</artifactId>
        <version>3.0.11-SNAPSHOT</version>
    </dependency>
    

    然后就可以解析了:

    @Test
        public void testParseRegister() throws IOException {
            StringBuilder register = new StringBuilder("REGISTER sip:10.32.26.25:5070;transport=tcp SIP/2.0
    " +
                    "Via: SIP/2.0/TCP 10.32.26.25:51696;rport;branch=z9hG4bKPj8d4db68b24754f539dbf3b563a44fe55;alias
    " +
                    "Max-Forwards: 70
    " +
                    "From: <sip:1000@10.32.26.25>;tag=89aefb1f3fc0413283a453eda5407f60
    " +
                    "To: jimmy<sip:1000@10.32.26.25>
    " +
                    "Call-ID: 1e7af0e67a5044658fc7f6716d329642
    " +
                    "CSeq: 36850 REGISTER
    " +
                    "User-Agent: MicroSIP/3.20.3
    " +
                    "Supported: outbound, path
    " +
                    "Contact: <sip:1000@10.32.26.25:51696;transport=TCP;ob>;reg-id=1;+sip.instance="<urn:uuid:00000000-0000-0000-0000-000011058e7e>"
    " +
                    "Expires: 300
    " +
                    "Allow: PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS
    " +
                    "Content-Length:  0
    ");
    
            SipMessage msgMessage = SipParser.frame(Buffers.wrap(register.toString()));
    
            if (msgMessage.isRegisterRequest()) {
                System.out.println("This is a REGISTER request");
            }
    
            Buffer method = msgMessage.getMethod();
            System.out.println("方法:" + method + "
    ");
            Buffer initialLine = msgMessage.getInitialLine();
            System.out.println("第一行:" + initialLine + "
    ");
    
            List<ViaHeader> viaHeaders = msgMessage.getViaHeaders();
            System.out.println("via:");
            for (ViaHeader viaHeader : viaHeaders) {
                System.out.println("host:" + viaHeader.getHost() + ",branch:" + viaHeader.getBranch() + ",alias:" + viaHeader.getParameter("alias"));
            }
    
            MaxForwardsHeader maxForwards = msgMessage.getMaxForwards();
            System.out.println("
    maxForwards:" + maxForwards.getMaxForwards());
    
            FromHeader fromHeader = msgMessage.getFromHeader();
            System.out.println("
    from-tag:" + fromHeader.getTag());
    
            ToHeader toHeader = msgMessage.getToHeader();
            System.out.println("
    to:" + toHeader.getAddress().getDisplayName());
    
            CallIdHeader callIDHeader = msgMessage.getCallIDHeader();
            System.out.println("
    callId:" + callIDHeader.getCallId());
    
            CSeqHeader cSeqHeader = msgMessage.getCSeqHeader();
            System.out.println("
    cSeq:" + cSeqHeader.getSeqNumber());
    
            Optional<SipHeader> userAgentHeader = msgMessage.getHeader("User-Agent");
            System.out.println("
    userAgent value:" + userAgentHeader.get().getValue());
    
            Optional<SipHeader> supported = msgMessage.getHeader("Supported");
            System.out.println("
    supported name:" + supported.get().getName());
    
            ContactHeader contactHeader = msgMessage.getContactHeader();
            System.out.println("
    contact reg-id:" + contactHeader.getParameter("reg-id"));
    
            ExpiresHeader expiresHeader = msgMessage.getExpiresHeader();
            System.out.println("
    expires:" + expiresHeader.getExpires());
    
            Optional<SipHeader> allowHeader = msgMessage.getHeader("Allow");
            System.out.println("
    allow:" + allowHeader.get().getValue());
    
            int contentLength = msgMessage.getContentLength();
            System.out.println("
    contentLength:" + contentLength);
    
        }
    

    输出如下:

    This is a REGISTER request
    方法:REGISTER
    
    第一行:REGISTER sip:10.32.26.25:5070;transport=tcp SIP/2.0
    
    via:
    host:10.32.26.25,branch:z9hG4bKPj8d4db68b24754f539dbf3b563a44fe55,alias:null
    
    maxForwards:70
    
    from-tag:89aefb1f3fc0413283a453eda5407f60
    
    to:jimmy
    
    callId:1e7af0e67a5044658fc7f6716d329642
    
    cSeq:36850
    
    userAgent value:MicroSIP/3.20.3
    
    supported name:Supported
    
    contact reg-id:1
    
    expires:300
    
    allow:PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS
    
    contentLength:0
    

    pkts-sip的解析非常高效,其主要设计思路借鉴了netty的buffer,自定义类似的buffer结构,内部有 readerIndex、writerIndex、markedReaderIndex、lowerBoundary、upperBoundary几个标识,可以快速读取或写入。

    最常用的ByteBuffer内部数据存储于byte[]数组,值类型的变量直接在堆外内存区分配,无需JVM来GC。 

    SIP中常见的各种Header解析,pkts-sip已经做了实现,类图如下:

    一个完整的SIP报文,正如最开始的解析示例代码,最终会被解析成SipMessage,根据该报文是Request还是Response,又派生出2个子类:

    SipMessage中的核心部分,就是各种SIpHeader实例。

    除了解析,pkts-sip还可以组装各种SIP报文,仍然以开头这段REGISTER为例,如果服务端收到这个注册请求,可以方便的组装Response进行回应:

        @Test
        public void testBuildRegisterResponse() throws IOException {
            StringBuilder register = new StringBuilder("REGISTER sip:10.32.26.25:5070;transport=tcp SIP/2.0
    " +
                    "Via: SIP/2.0/TCP 10.32.26.25:51696;rport;branch=z9hG4bKPj8d4db68b24754f539dbf3b563a44fe55;alias
    " +
                    "Max-Forwards: 70
    " +
                    "From: <sip:1000@10.32.26.25>;tag=89aefb1f3fc0413283a453eda5407f60
    " +
                    "To: jimmy<sip:1000@10.32.26.25>
    " +
                    "Call-ID: 1e7af0e67a5044658fc7f6716d329642
    " +
                    "CSeq: 36850 REGISTER
    " +
                    "User-Agent: MicroSIP/3.20.3
    " +
                    "Supported: outbound, path
    " +
                    "Contact: <sip:1000@10.32.26.25:51696;transport=TCP;ob>;reg-id=1;+sip.instance="<urn:uuid:00000000-0000-0000-0000-000011058e7e>"
    " +
                    "Expires: 300
    " +
                    "Allow: PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS
    " +
                    "Content-Length:  0
    ");
            SipMessage msgMessage = SipParser.frame(Buffers.wrap(register.toString()));
    
            SipResponse sipResponse = msgMessage.createResponse(401)
                    .withHeader(SipHeader.create("User-Agent", "FreeSWITCH-mod_sofia/1.6.18+git~20170612T211449Z~6e79667c0a~64bit"))
                    .withHeader(SipHeader.create("Allow", "INVITE, ACK, BYE, CANCEL, OPTIONS, MESSAGE, INFO, UPDATE, REGISTER, REFER, NOTIFY, PUBLISH, SUBSCRIBE"))
                    .withHeader(SipHeader.create("Supported", "timer, path, replaces"))
                    .withHeader(SipHeader.create("WWW-Authenticate", "Digest realm="10.32.26.25", nonce="bee3366b-cf59-476e-bc5e-334e0d65b386", algorithm=MD5, qop="auth""))
                    .withHeader(new ContentLengthHeader.Builder(0).build())
                    .build();
    
            System.out.println(sipResponse);
    
        }
    

    输出如下:

    SIP/2.0 401 Unauthorized
    Call-ID: 1e7af0e67a5044658fc7f6716d329642
    CSeq: 36850 REGISTER
    WWW-Authenticate: Digest realm="10.32.26.25", nonce="bee3366b-cf59-476e-bc5e-334e0d65b386", algorithm=MD5, qop="auth"
    User-Agent: FreeSWITCH-mod_sofia/1.6.18+git~20170612T211449Z~6e79667c0a~64bit
    To: jimmy<sip:1000@10.32.26.25>
    From: <sip:1000@10.32.26.25>;tag=89aefb1f3fc0413283a453eda5407f60
    Content-Length: 0
    Supported: timer, path, replaces
    Via: SIP/2.0/TCP 10.32.26.25:51696;rport;branch=z9hG4bKPj8d4db68b24754f539dbf3b563a44fe55;alias
    Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, MESSAGE, INFO, UPDATE, REGISTER, REFER, NOTIFY, PUBLISH, SUBSCRIBE
    

    可能有细心的同学发现了,最终输出的报文,每行的出现顺序好象有点怪,比如Content-Length:0,是在最后添加进去的,但却是在中间出现。可以看下io.pkts.packet.sip.impl.SipMessageBuilder#build的源码:

    597行这里,finalHeaders是一个HashMap,众所周知HashMap是不能保证顺序的,对顺序十分在意的同学,可以换成LinkedHashMap,另外从代码可以看出,viaHeaders是放在常规Headers之后组装的,一般我们习惯于把Via放在最开始,大家可以把这2段代码的位置互换一下。

    改完之后,再跑一下代码:

    SIP/2.0 401 Unauthorized
    Via: SIP/2.0/TCP 10.32.26.25:51696;rport;branch=z9hG4bKPj8d4db68b24754f539dbf3b563a44fe55;alias
    From: <sip:1000@10.32.26.25>;tag=89aefb1f3fc0413283a453eda5407f60
    To: jimmy<sip:1000@10.32.26.25>
    CSeq: 36850 REGISTER
    Call-ID: 1e7af0e67a5044658fc7f6716d329642
    User-Agent: FreeSWITCH-mod_sofia/1.6.18+git~20170612T211449Z~6e79667c0a~64bit
    Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, MESSAGE, INFO, UPDATE, REGISTER, REFER, NOTIFY, PUBLISH, SUBSCRIBE
    Supported: timer, path, replaces
    WWW-Authenticate: Digest realm="10.32.26.25", nonce="bee3366b-cf59-476e-bc5e-334e0d65b386", algorithm=MD5, qop="auth"
    Content-Length: 0
    

    看上去顺眼多了,此外从源代码可以看到,ptks-sip在构造各种Header时,大量使用了Builder设计模式(比如下图中的FromHeader.Builder),可以方便的用withXXX(...),得到一个XXXBuilder实例,最后调用build()方法生成想要的XXXHeader实例。

    最后来谈下如何扩展ptks未支持的Header,一般情况下,如果ptks不支持的Header,比如:

    WWW-Authenticate: Digest realm="10.32.26.25", nonce="bee3366b-cf59-476e-bc5e-334e0d65b386", algorithm=MD5, qop="auth"
    

    解析后,会生成默认的SipHeaderImpl实例列表,参考下图:

    这样在使用时,并不方便,最好是希望能看FromHeader类似,只生成1个特定的WWWAuthenticateHeader实例,并且能类似getRealm()、getNonce()...得到相关的属性值。ptks-sip的readme里,告诉了大家扩展的步骤,我把主要部分列了下:

    1、先定义一个XXXHeader的接口,比如:WWWAuthenticateHeader

    2、XXXHeader接口里,实现static frame()方法(注:jdk 1.8开始,接口可以添加方法实现)

    3、XXXHeader接口里,定义copy()方法

    4、SipHeader接口中添加isXXX()以及toXXX()方法

    5、XXXHeader接口里,定义ensure()方法,并返回this

    6、实现XXXHeader,定义一个XXXHeaderImpl类,核心的解析工作,就放在这个类的frame方法中完成

    7、SipParser类中,添加XXXHeader的注册信息

    8、单元测试

    按这个步骤,先来定义一个WWWAuthenticateHeader

    package io.pkts.packet.sip.header;
    
    import io.pkts.buffer.Buffer;
    import io.pkts.buffer.Buffers;
    import io.pkts.packet.sip.SipParseException;
    import io.pkts.packet.sip.header.impl.WWWAuthenticateHeaderImpl;
    
    
    public interface WWWAuthenticateHeader extends SipHeader {
    
        Buffer NAME = Buffers.wrap("WWW-Authenticate");
    
        Buffer getRealm();
    
        Buffer getNonce();
    
        Buffer getAlgorithm();
    
        Buffer getQop();
        
        static WWWAuthenticateHeader frame(final Buffer buffer) throws SipParseException {
            try {
                return new WWWAuthenticateHeader.Builder(buffer).build();
            } catch (final Exception e) {
                throw new SipParseException(0, "Unable to frame the WWWAuthenticate header due to IOException", e);
            }
        }
    
        @Override
        default WWWAuthenticateHeader toWWWAuthenticateHeader() {
            return this;
        }
    
    
        class Builder implements SipHeader.Builder<WWWAuthenticateHeader> {
            private Buffer value;
    
            private Buffer realm;
            private Buffer nonce;
            private Buffer algorithm;
            private Buffer qop;
    
            public Builder() {
    
            }
    
            public Builder(Buffer value) {
                this.value = value;
            }
    
            @Override
            public WWWAuthenticateHeader.Builder withValue(Buffer value) {
                this.value = value;
                return this;
            }
    
            public WWWAuthenticateHeader.Builder withRealm(Buffer realm) {
                this.realm = realm;
                return this;
            }
    
            public WWWAuthenticateHeader.Builder withNonce(Buffer nonce) {
                this.nonce = nonce;
                return this;
            }
    
            public WWWAuthenticateHeader.Builder withAlgorithm(Buffer algorithm) {
                this.algorithm = algorithm;
                return this;
            }
    
            public WWWAuthenticateHeader.Builder withQop(Buffer qop) {
                this.qop = qop;
                return this;
            }
    
            @Override
            public WWWAuthenticateHeader build() throws SipParseException {
                if (value == null &&
                        (this.realm == null && this.nonce == null)) {
                    throw new SipParseException("You must specify the [value] or [realm/nonce] of the WWWAuthenticate-Header");
                }
    
                if (this.value != null) {
                    return new WWWAuthenticateHeaderImpl(value);
                } else {
                    return new WWWAuthenticateHeaderImpl(realm, nonce, algorithm, qop);
                }
            }
        }
    
    }
    

    SipHeader里添加

        default boolean isWWWAuthenticateHeader() {
            //WWW-Authenticate
            final Buffer m = getName();
            try {
                if (m.getReadableBytes() == 16) {
                    return (m.getByte(0) == 'W' || m.getByte(0) == 'w') &&
                            (m.getByte(1) == 'W' || m.getByte(1) == 'w') &&
                            (m.getByte(2) == 'W' || m.getByte(2) == 'w') &&
                            m.getByte(3) == '-' &&
                            (m.getByte(4) == 'A' || m.getByte(4) == 'a') &&
                            (m.getByte(5) == 'U' || m.getByte(5) == 'u') &&
                            (m.getByte(6) == 'T' || m.getByte(6) == 't') &&
                            (m.getByte(7) == 'H' || m.getByte(7) == 'h') &&
                            (m.getByte(8) == 'E' || m.getByte(8) == 'e') &&
                            (m.getByte(9) == 'N' || m.getByte(9) == 'n') &&
                            (m.getByte(10) == 'T' || m.getByte(10) == 't') &&
                            (m.getByte(11) == 'I' || m.getByte(11) == 'i') &&
                            (m.getByte(12) == 'C' || m.getByte(12) == 'c') &&
                            (m.getByte(13) == 'A' || m.getByte(13) == 'a') &&
                            (m.getByte(14) == 'T' || m.getByte(14) == 't') &&
                            (m.getByte(15) == 'E' || m.getByte(15) == 'e');
                }
            } catch (final IOException e) {
                throw new SipParseException(0, UNABLE_TO_PARSE_OUT_THE_HEADER_NAME_DUE_TO_UNDERLYING_IO_EXCEPTION, e);
            }
            return false;
        }
    
        default WWWAuthenticateHeader toWWWAuthenticateHeader() {
            throw new ClassCastException(CANNOT_CAST_HEADER_OF_TYPE + getClass().getName()
                    + " to type " + WWWAuthenticateHeader.class.getName());
        }
    

    然后再来WWWAuthenticateHeaderImpl

    package io.pkts.packet.sip.header.impl;
    
    import io.pkts.buffer.Buffer;
    import io.pkts.buffer.Buffers;
    import io.pkts.packet.sip.SipParseException;
    import io.pkts.packet.sip.header.WWWAuthenticateHeader;
    import io.pkts.packet.sip.impl.SipParser;
    
    import java.util.LinkedHashMap;
    import java.util.Map;
    
    public class WWWAuthenticateHeaderImpl extends SipHeaderImpl implements WWWAuthenticateHeader {
    
    
        private Map<Buffer, Buffer> paramMap = new LinkedHashMap<>();
    
        private Buffer realm;
        private Buffer nonce;
        private Buffer algorithm;
        private Buffer qop;
    
    
        /**
         * @param value
         */
        public WWWAuthenticateHeaderImpl(Buffer value) {
            super(WWWAuthenticateHeader.NAME, value);
    
            Buffer original = value.clone();
            Buffer params = null;
            if (original.hasReadableBytes()) {
                params = original.slice("Digest ".length(), original.getUpperBoundary());
            }
    
            final byte[] VALUE_END_1 = Buffers.wrap("", ").getArray();
            final byte[] VALUE_END_2 = Buffers.wrap(", ").getArray();
    
            //WWW-Authenticate: Digest realm="10.32.26.25",
            // nonce="bee3366b-cf59-476e-bc5e-334e0d65b386",
            // algorithm=MD5,
            // qop="auth"
    
            try {
                // 思路:
                // 1 遇到[=]号是key结束,遇到[,]或[", ]或[
    ]是value结束
                // 2 每次遇"="或”,”标识lastMarkIndex
                int lastMarkIndex = params.getReaderIndex();
                boolean inKey = true;
                Buffer latestKey = Buffers.EMPTY_BUFFER, latestValue;
                while (params.hasReadableBytes() && params.getReaderIndex() <= params.getUpperBoundary()) {
                    if (inKey && SipParser.isNext(params, SipParser.EQ)) {
                        //遇到[=]认为key结束
                        latestKey = params.slice(lastMarkIndex, params.getReaderIndex());
                        params.setReaderIndex(params.getReaderIndex() + 1);
                        if (SipParser.isNext(params, SipParser.DQUOT)) {
                            //跳过[="]等号后的第1个双引号
                            params.setReaderIndex(params.getReaderIndex() + 1);
                            inKey = false;
                        }
                        lastMarkIndex = params.getReaderIndex();
                    } else if (params.getReadableBytes() == 1 ||
                            SipParser.isNext(params, VALUE_END_1) ||
                            SipParser.isNext(params, VALUE_END_2)) {
                        //遇到[", ]或[, ]视为value结束
                        if (params.getReadableBytes() == 1 && params.peekByte() != SipParser.DQUOT) {
                            latestValue = params.slice(lastMarkIndex, params.getReaderIndex() + 1);
                        } else {
                            latestValue = params.slice(lastMarkIndex, params.getReaderIndex());
                        }
    
                        paramMap.put(latestKey, latestValue);
    
                        if (params.getReadableBytes() == 1) {
                            params.setReaderIndex(params.getReaderIndex() + 1);
                        } else if (SipParser.isNext(params, VALUE_END_1)) {
                            params.setReaderIndex(params.getReaderIndex() + VALUE_END_1.length);
                        } else if (SipParser.isNext(params, VALUE_END_2)) {
                            params.setReaderIndex(params.getReaderIndex() + VALUE_END_2.length);
                        }
    
                        lastMarkIndex = params.getReaderIndex();
    
                        inKey = true;
                    } else {
                        params.setReaderIndex(params.getReaderIndex() + 1);
                    }
                }
            } catch (Exception e) {
                throw new SipParseException(NAME + " parse error, " + e.getCause());
            }
        }
    
    
        public WWWAuthenticateHeaderImpl(Buffer realm, Buffer nonce, Buffer algorithm, Buffer qop) {
            super(WWWAuthenticateHeader.NAME, Buffers.EMPTY_BUFFER);
            this.realm = realm;
            this.nonce = nonce;
            this.algorithm = algorithm;
            this.qop = qop;
        }
    
        @Override
        public Buffer getValue() {
            Buffer value = super.getValue();
            if (value != null && value != Buffers.EMPTY_BUFFER) {
                return value;
            }
            StringBuilder sb = new StringBuilder("Digest realm="" + this.getRealm() + "", nonce="" + this.getNonce() + """);
            if (this.getAlgorithm() != null) {
                sb.append(", algorithm=" + this.getAlgorithm());
            }
            if (this.getQop() != null) {
                sb.append(", qop="" + this.getQop() + """);
            }
            value = Buffers.wrap(sb.toString());
            return value;
        }
    
        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder(NAME.toString());
            sb.append(": Digest realm="" + this.getRealm() + "", nonce="" + this.getNonce() + """);
            if (this.getAlgorithm() != null) {
                sb.append(", algorithm=" + this.getAlgorithm());
            }
            if (this.getQop() != null) {
                sb.append(", qop="" + this.getQop() + """);
            }
            return sb.toString();
        }
    
    
        @Override
        public WWWAuthenticateHeader.Builder copy() {
            return new WWWAuthenticateHeader.Builder(getValue());
        }
    
        @Override
        public WWWAuthenticateHeader ensure() {
            return this;
        }
    
        @Override
        public WWWAuthenticateHeader clone() {
            final Buffer value = getValue();
            return new WWWAuthenticateHeaderImpl(value.clone());
        }
    
        @Override
        public Buffer getRealm() {
            if (realm != null) {
                return realm;
            }
            realm = paramMap.get(Buffers.wrap("realm"));
            return realm;
        }
    
        @Override
        public Buffer getNonce() {
            if (nonce != null) {
                return nonce;
            }
            nonce = paramMap.get(Buffers.wrap("nonce"));
            return nonce;
        }
    
        @Override
        public Buffer getAlgorithm() {
            if (algorithm != null) {
                return algorithm;
            }
            algorithm = paramMap.get(Buffers.wrap("algorithm"));
            return algorithm;
        }
    
        @Override
        public Buffer getQop() {
            if (qop != null) {
                return qop;
            }
            qop = paramMap.get(Buffers.wrap("qop"));
            return qop;
        }
    }
    

    SipParser里新增注册

        static {
            framers.put(CallIdHeader.NAME, header -> CallIdHeader.frame(header.getValue()));
            framers.put(CallIdHeader.COMPACT_NAME, header -> CallIdHeader.frameCompact(header.getValue()));
    
            ...
    
            framers.put(ViaHeader.NAME, header -> ViaHeader.frame(header.getValue()));
            framers.put(ViaHeader.COMPACT_NAME, header -> ViaHeader.frame(header.getValue()));
    
            //新增WWWAuthenticateHeader注册
            framers.put(WWWAuthenticateHeader.NAME, header -> WWWAuthenticateHeader.frame(header.getValue()));
        }
    

    frame方法里,也要新增判断:

        public static SipMessage frame(final Buffer buffer) throws IOException {
    
            ...
    
            // Move along as long as we actually can consume an header and
            ...
            SipHeader contactHeader = null;
            SipHeader wwwAuthenticateHeader = null;
            ...
    
            while (consumeCRLF(buffer) != 2 && (headerName = SipParser.nextHeaderName(buffer)) != null) {
                final List<Buffer> values = readHeaderValues(headerName, buffer).values;
                for (final Buffer value : values) {
                    header = new SipHeaderImpl(headerName, value);
                    // The headers that are most commonly used will be fully
                    // parsed just because no stack can really function without
                    // looking into these headers.
                    if (header.isContentLengthHeader()) {
                        final ContentLengthHeader l = header.ensure().toContentLengthHeader();
                        contentLength = l.getContentLength();
                        header = l;
                    } 
                    ...
                    } else if (recordRouteHeader == null && header.isRecordRouteHeader()) {
                        header = header.ensure();
                        recordRouteHeader = header;
                    } else if (wwwAuthenticateHeader == null && header.isWWWAuthenticateHeader()) {
                        header = header.ensure();
                        wwwAuthenticateHeader = header;
                    }
    
    
                    ...
        }                    
    

    另外有1个小坑,readme里没提到,类似

    WWW-Authenticate: Digest realm="10.32.26.25", nonce="bee3366b-cf59-476e-bc5e-334e0d65b386", algorithm=MD5, qop="auth"
    

    这种header解析时,还要修改SipParser里的isHeaderAllowingMultipleValues方法

        private static boolean isHeaderAllowingMultipleValues(final Buffer headerName) {
            final int size = headerName.getReadableBytes();
            if (size == 7) {
                return !isSubjectHeader(headerName);
            } else if (size == 5) {
                return !isAllowHeader(headerName);
            } else if (size == 4) {
                return !isDateHeader(headerName);
            } else if (size == 1) {
                return !isAllowEventsHeaderShort(headerName);
            } else if (size == 12) {
                return !isAllowEventsHeader(headerName);
            } else if (size == 16) {
              # 新增判断,防止被解析成多行
                return !isWWWAuthenticateHeader(headerName);
            }
            return true;
        }
    

    为了方便判断Buffer接下来几个位置是否为指定字符,SipParser里的isNext也做了扩展

        public static boolean isNext(final Buffer buffer, final byte[] bytes) throws IOException {
            boolean hasReadableBytes = buffer.hasReadableBytes();
            if (!hasReadableBytes) {
                return false;
            }
            int readableBytes = buffer.getReadableBytes();
            int length = bytes.length;
            if (readableBytes < length) {
                return false;
            }
            boolean match = true;
            for (int i = 0; i < length; i++) {
                int readIndex = buffer.getReaderIndex() + i;
                byte aByte = buffer.getByte(readIndex);
                if (aByte != bytes[i]) {
                    match = false;
                    break;
                }
            }
            return match;
        }
    

    还可以在ImmutableSipMessage类中添加以下方法,这样用起来更顺手

        @Override
        public WWWAuthenticateHeader getWWWAuthenticateHeader() throws SipParseException{
            final SipHeader header = findHeader(WWWAuthenticateHeader.NAME.toString());
            return header != null ? header.ensure().toWWWAuthenticateHeader() : null;
        }
    

    这些做完后,再来跑先前的测试

    从上图可以看到,realm oncealgorithmqop这些属性已经正确提取出来了,最后可以再测试下Builder

    package io.pkts.packet.sip.header.impl;
    
    import io.pkts.buffer.Buffer;
    import io.pkts.buffer.Buffers;
    import io.pkts.packet.sip.SipParseException;
    import io.pkts.packet.sip.header.ViaHeader;
    import io.pkts.packet.sip.header.WWWAuthenticateHeader;
    import org.junit.Test;
    
    import static org.hamcrest.CoreMatchers.is;
    import static org.hamcrest.CoreMatchers.nullValue;
    import static org.junit.Assert.*;
    
    
    public class WWWAuthenticateHeaderImplTest {
    
        @Test
        public void testBuild1() throws Exception {
            final WWWAuthenticateHeader wwwAuthenticateHeader = new WWWAuthenticateHeader.Builder()
                    .withAlgorithm(Buffers.wrap("MD5"))
                    .withNonce(Buffers.wrap("bee3366b-cf59-476e-bc5e-334e0d65b386"))
                    .withQop(Buffers.wrap("auth"))
                    .withRealm(Buffers.wrap("10.32.26.25"))
                    .build();
    
            assertEquals(wwwAuthenticateHeader.getAlgorithm(), Buffers.wrap("MD5"));
            assertEquals(wwwAuthenticateHeader.getNonce(), Buffers.wrap("bee3366b-cf59-476e-bc5e-334e0d65b386"));
            assertEquals(wwwAuthenticateHeader.getQop(), Buffers.wrap("auth"));
            assertEquals(wwwAuthenticateHeader.getRealm(), Buffers.wrap("10.32.26.25"));
    
            Buffer value = Buffers.wrap("Digest realm="10.32.26.25", nonce="bee3366b-cf59-476e-bc5e-334e0d65b386", algorithm=MD5, qop="auth"");
            assertTrue(wwwAuthenticateHeader.getValue().equalsIgnoreCase(value));
        }
    
        @Test
        public void testBuild2() throws Exception {
            final WWWAuthenticateHeader wwwAuthenticateHeader = new WWWAuthenticateHeader.Builder()
                    .withNonce(Buffers.wrap("bee3366b-cf59-476e-bc5e-334e0d65b386"))
                    .withRealm(Buffers.wrap("10.32.26.25"))
                    .build();
    
            assertEquals(wwwAuthenticateHeader.getAlgorithm(), null);
            assertEquals(wwwAuthenticateHeader.getNonce(), Buffers.wrap("bee3366b-cf59-476e-bc5e-334e0d65b386"));
            assertEquals(wwwAuthenticateHeader.getQop(), null);
            assertEquals(wwwAuthenticateHeader.getRealm(), Buffers.wrap("10.32.26.25"));
    
            Buffer value = Buffers.wrap("Digest realm="10.32.26.25", nonce="bee3366b-cf59-476e-bc5e-334e0d65b386"");
            assertTrue(wwwAuthenticateHeader.getValue().equalsIgnoreCase(value));
        }
    
        @Test
        public void testFrame1() throws Exception {
            Buffer value = Buffers.wrap("Digest realm="10.32.26.25", nonce="bee3366b-cf59-476e-bc5e-334e0d65b386", algorithm=MD5, qop="auth"");
            final WWWAuthenticateHeader wwwAuthenticateHeader = new WWWAuthenticateHeaderImpl(value);
            assertEquals(wwwAuthenticateHeader.getAlgorithm(), Buffers.wrap("MD5"));
            assertEquals(wwwAuthenticateHeader.getNonce(), Buffers.wrap("bee3366b-cf59-476e-bc5e-334e0d65b386"));
            assertEquals(wwwAuthenticateHeader.getQop(), Buffers.wrap("auth"));
            assertEquals(wwwAuthenticateHeader.getRealm(), Buffers.wrap("10.32.26.25"));
        }
    
        @Test
        public void testFrame2() throws Exception {
            Buffer realm = Buffers.wrap("10.32.26.25");
            Buffer nonce = Buffers.wrap("bee3366b-cf59-476e-bc5e-334e0d65b386");
            final WWWAuthenticateHeader wwwAuthenticateHeader = new WWWAuthenticateHeaderImpl(realm, nonce, null, null);
    
            assertEquals(wwwAuthenticateHeader.getAlgorithm(), null);
            assertEquals(wwwAuthenticateHeader.getNonce(), Buffers.wrap("bee3366b-cf59-476e-bc5e-334e0d65b386"));
            assertEquals(wwwAuthenticateHeader.getQop(), null);
            assertEquals(wwwAuthenticateHeader.getRealm(), Buffers.wrap("10.32.26.25"));
    
            Buffer value = Buffers.wrap("Digest realm="10.32.26.25", nonce="bee3366b-cf59-476e-bc5e-334e0d65b386"");
            assertTrue(wwwAuthenticateHeader.getValue().equalsIgnoreCase(value));
        }
    
    
    }
    

    以上代码,均已提交到 https://github.com/yjmyzz/pkts/tree/master/pkts-sip,供大家参考

    作者:菩提树下的杨过
    出处:http://yjmyzz.cnblogs.com
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    关于selenium中的三种等待方式与EC模块的知识
    re.findall用法
    链表及链表的逆置
    通过两个队列实现一个栈
    flask与Django框架的区别
    http协议与https协议
    Maven系列教材 (四)- 通过命令行创建Maven风格的Java项目
    Maven系列教材 (三)- 仓库概念,下载与配置
    Maven系列教材 (二)- 下载与配置Maven
    Maven系列教材 (一)- 什么是Maven
  • 原文地址:https://www.cnblogs.com/yjmyzz/p/parse-sip-using-pkts.html
Copyright © 2020-2023  润新知