FileDocCategorySizeDatePackage
TestSipHeader2.javaAPI DocphoneME MR2 API (J2ME)43580Wed May 02 18:00:40 BST 2007javax.microedition.sip

TestSipHeader2.java

/*
 *   
 *
 * Copyright  1990-2007 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation.
 * 
 * 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 version 2 for more details (a copy is
 * included at /legal/license.txt).
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions.
 */

package javax.microedition.sip;

import com.sun.midp.i3test.TestCase;
import com.sun.midp.io.j2me.storage.RandomAccessStream;
import com.sun.midp.io.j2me.storage.File;

import javax.microedition.io.Connector;
import java.io.IOException;
import java.io.OutputStream;

/**
 * Tests for the SipHeader class.
 *
 * We assume that for a header like
 * <pre>
 * Accept-Language: da, en-gb;q=0.8, en;q=0.7
 * </pre>
 * 3 name-headerValue pairs will be generated by the implementation.
 */
class THeader {

    /*
     * structured internal representation
     * Contact: "Mr. Watson" <sip:watson@worcester.bell-telephone.com>;
     *             q=0.7;expires=3600
     *  {{"Contact"},
     *   {": "},
     *   {"\"Mr. Watson\" <sip:watson@worcester.bell-telephone.com>"},
     *   {";"},
     *   {"q","=","0.7"},
     *   {";"},
     *   {"expires=3600",{"\r\n"}
     * };
     *  name       
     *  delim  
     *  value    
     *  delim 
     *  param           
     *  delim  param
     *
     *  hline =  name  delim  value *( delim param )
     *  param = pname pdelim pvalue
     */
    String[][]guts;
    
    THeader(String[][] guts0) {
        guts = acopy(guts0);
    }

    THeader(THeader p) {
        guts = acopy(p.guts);
    }
    
    // auxiliary tools, generic stuff
    public static String concata(String[] a) {
        String res = "";
        for (int i = 0; i < a.length; i++) {
            res = res + a[i];
        }
	return res;
    }

    public static String concata(String[][]a, int i0, int im) {
        String res = "";
        if (a.length < im) {
	    im = a.length;
	}
        for (int i = i0; i < im; i++) {
            res = res + concata(a[i]);
        }
	return res;
    }

    public static String[] acopy(String[] g) {
        String[] res = new String[g.length];
        for (int i = 0; i < g.length; i++) {
            res[i] = new String(g[i]);
        }
        return res;
    }

    public static String[][] acopy(String[][] g) {
        String[][] res = new String[g.length][];
        for (int i = 0; i < g.length; i++) {
            res[i] = acopy(g[i]);
        }
        return res;
    }

    public static String[][] acopyWithoutDelimAndPrm(String[][] g, int no) {
        String[][] res = new String[g.length-2][];
        if (0 <= no && no < g.length) {
            for (int i = 0, j = 0; i < g.length; i++) {
                if (i != no && i != no - 1) {
                    res[j++] = acopy(g[i]);
                }
            }
            return res;
        } else {
            return acopy(g);
        }
    }

    public static String[][] acopyWithDelimAndPrm(String[][] g, 
						  String[]delim, 
						  String[]prm) {
        String[][] res = new String[g.length+2][];
        int i, j;
        for (i = 0, j = 0; i < g.length-1; i++) {
            res[j++] = acopy(g[i]);
        }
        res[j++] = acopy(delim);
        res[j++] = acopy(prm);
        res[j++] = acopy(g[i++]);
        return res;
    }

    // read access functions
    // note: even positions for data, odd for delimiters
    public String getName() {
        return guts[0][0];
    }

    public String getValue() {
        return guts[2][0];
    }

    public String getHeaderValue() {
        return concata(guts, 2, guts.length-1);
    }

    public String toString() {
        return concata(guts, 0, guts.length);
    }

    public String getPrmNam(int i) {
        /*
	 * myout.println("index: " + i +" => " + ((i + 2) * 2
	 *    +" guts.length: "+guts.length));
	 */
        if ((i + 2) * 2 >= guts.length)
	    return null;
        return guts[(i + 2) * 2][0];
    }

    public String getPrmVal(int i) {
        return guts[(i + 2) * 2][2];
    }

    public String[] getPrmWhole(int i) {
        return guts[(i + 2) *2];
    }

    public String[] getParameterNames() {
        String[] res = new String [guts.length / 2 - 2];
        for (int i = 0; i < res.length; i++) {
            res[i] = getPrmNam(i);
        }
        return res;
    }

    public int findPrmNam(String name) {

        for (int i = 0, imax = guts.length / 2 - 2; i < imax; i++) {
            if (name.equals(getPrmNam(i))) {
                return i;
            }
        }
        return -1;
    }
    
    // write access functions
    // note: even positions for data, odd for delimiters
    public void setName(String n) {
        guts[0][0] = n;
    }

    public void setValue(String v) {
        guts[2][0] = v;
    }

    public void setPrmNam(int i, String n) {
        guts[(i + 2) * 2][0] = n;
    }

    public void setPrmVal(int i, String v) {
        guts[(i + 2) *2][2] = v;
    }

    public void setPrmWhole(int i, String[] p) {
        guts[(i + 2) *2] = p;
    }

    public void rmvPrm(int i) {
        guts = acopyWithoutDelimAndPrm(guts, (i + 2) * 2);
    }

    public void appendPrmWhole(String[] prm) {
        guts = acopyWithDelimAndPrm(guts, 
				    new String[] 
	    { TestSipHeader2.makeDelimiterFor(this) },
				    prm);
    }

    public void setOrAddPrmWhole(String[] prm) {
        int prmN = findPrmNam(prm[0]);
        if (-1 != prmN) {
	    // myout.println("setOrAddPrmWhole: set");
            setPrmWhole(prmN, prm);
        } else {
	    // myout.println("setOrAddPrmWhole: append");
            appendPrmWhole(prm);
        }
    }
}

class BlockedTestException extends Exception {
    public BlockedTestException() { };
}

class NothingToDoInTestException extends Exception {
    public NothingToDoInTestException() { };
}

abstract class TestSipHeader2_constants extends TestCase {
    /** this one should never appear in the report */
    public static final char NOTRUN = '-';
    /** failure happened before the test was run */
    public static final char BLOCKED = 'b';
    /** test failed: wrong data */
    public static final char FAILED = 'f';
    /** test failed because it caused an exception */
    public static final char EXCEPTION = 'X';
    /** there has been nothing to do: not bad */
    public static final char NOTHINGTODO = 'o';
    /** success */
    public static final char SUCCESS = 's';

    final int nTests = 6;
    final int nSubTests = 31; // not more that # of bits
    final int exceptionAndNoSubTests = nSubTests - 1;
}

public class TestSipHeader2 extends TestSipHeader2_constants {
    /** used as: testResult[firstHeaderN][secondHeaderN][testN] */
    int testN;
    /** used as: testResult[firstHeaderN][secondHeaderN][testN] */
    int firstHeaderN;
    /** used as: testResult[firstHeaderN][secondHeaderN][testN] */
    int secondHeaderN;
    /** used as: testResult[firstHeaderN][secondHeaderN][testN] */
    char[][][] testResult;
    /**
     * a bit vertor. used as: 
     *    failureReason[firstHeaderN][secondHeaderN][testN]
     */
    int [][][] failureReason;
    /** array of test names */
    String[] testName = new String[nTests];
    /** array of subtest names (that is, read access test names) */
    private String[] subTestName = new String[nSubTests];
    /**
     * contains data on which we perform tests.
     */
    String[][][] headerSample = new String[][][]
    {
        {{"Accept"}, {": "}, {"application/sdp"}, {"\r\n"}}, 
        {{"Accept"}, {": "}, {"application/sdp"}, {";"}, {"level", "=", "1"},
	 {"\r\n"}}, 
        {{"Accept"}, {": "}, {"text/html"}, {"\r\n"}}, 
        {{"Accept-Encoding"}, {": "}, {"gzip"}, {"\r\n"}}, 
        {{"Accept-Encoding"}, {": "}, {"identity"}, {"\r\n"}}, 
        {{"Accept-Language"}, {": "}, {"da"}, {"\r\n"}}, 
        {{"Accept-Language"}, {": "}, {"en-gb;q=0.8"}, {"\r\n"}}, 
        {{"Accept-Language"}, {": "}, {"en;q=0.7"}, {"\r\n"}}, 
        {{"Alert-Info"}, {": "}, {"<http://www.example.com/sounds/moo.wav>"},
	 {"\r\n"}}, 
        {{"Alert-Info"}, {": "},
	 {"<http://www.freetones.org/vasya-pupkin/imperial.mp3>"}, {"\r\n"}}, 
        {{"Allow"}, {": "}, {"INVITE"}, {"\r\n"}}, 
        {{"Allow"}, {": "}, {"ACK"}, {"\r\n"}}, 
        {{"Authentication-Info"}, {": "}, 
	 {"nextnonce", "=", "\"47364c23432d2e131a5fb210812c\""}, {"\r\n"}}, 
        {{"Authentication-Info"}, {": "}, {"rspauth", "=", "\"1234567890\""},
	 {"\r\n"}}, 
        {{"Authorization"}, {": "}, 
	 {"Digest"}, {" "}, 
	 {"username=\"Alice\""}, {", "}, 
	 {"realm", "=", "\"atlanta.com\""}, {", \n       "}, 
	 {"nonce", "=", "\"84a4cc6f3082121f32b42a2187831a9e\""},
	 {", \n       "}, 
	 {"response", "=", "\"7587245234b3434cc3412213e5f113a5432\""},
	 {"\r\n"}}, 
        {{"Call-ID"}, {": "}, {"a84b4c76e66710@pc33.atlanta.com"}, {"\r\n"}}, 
        {{"Call-ID"}, {": "},
	 {"f81d4fae-7dec-11d0-a765-00a0c91e6bf6@foo.bar.com"}, {"\r\n"}}, 
        {{"Call-Info"}, {": "}, {"<http://wwww.example.com/alice/photo.jpg>"},
	 {" ;"}, {"purpose", "=", "icon"}, {"\r\n"}}, 
        {{"Call-Info"}, {": "}, {"<http://www1.example.com/alice/>"}, {" ;"},
	 {"purpose", "=", "info"}, {"\r\n"}}, 
        {{"Contact"}, {": "}, 
	 {"\"Mr. Watson\" <sip:watson@worcester.bell-telephone.com>"}, 
	 {";"}, 
	 {"q", "=", "0.7"}, {";"}, 
	 {"expires", "=", "3600"}, {"\r\n"}}, 
        {{"Contact"}, {": "}, 
	 {"<sip:watson@worcester.bell-telephone.com>"}, {";"}, 
	 {"q", "=", "0.7"}, {";"}, 
	 {"expires", "=", "3600"}, {"\r\n"}}, 
        {{"Contact"}, {": "},  {"<sip:alice@pc33.atlanta.com>"}, {"\r\n"}}, 
        {{"CONTACT"}, {": "},
	 {"sip:user@host?Subject=foo&Call-Info=<http://www.foo.com>"}, 
	 {"\r\n"}}, 
        {{"CONTACT"}, {": "}, {"<sip:alice@atlanta.com>"}, {";"},
	 {"ExPiReS", "=", "3600"}, {"\r\n"}}, 
        {{"Content-Disposition"}, {": "}, {"session"}, {"\r\n"}}, 
        {{"Content-Disposition"}, {": "}, {"icon"}, {"\r\n"}}, 
        {{"Content-Disposition"}, {": "}, {"session"}, {";"}, 
	 {"handling=optional"}, {"\r\n"}}, 
        {{"Content-Encoding"}, {": "}, {"gzip"}, {"\r\n"}}, 
        {{"Content-Encoding"}, {": "}, {"compress"}, {"\r\n"}}, 
        {{"Content-Encoding"}, {": "}, {"identity"}, {"\r\n"}}, 
        {{"Content-Encoding"}, {": "}, {"deflate"}, {"\r\n"}}, 
        {{"Content-Language"}, {": "}, {"fr"}, {"\r\n"}}, 
        {{"Content-Language"}, {": "}, {"ru"}, {"\r\n"}}, 
        {{"Content-Length"}, {": "},  {"142"}, {"\r\n"}}, 
        {{"Content-Length"}, {": "},  {"0"}, {"\r\n"}}, 
        {{"Content-Type"}, {": "},  {"application/sdp"}, {"\r\n"}}, 
        {{"Content-Type"}, {": "},  {"image/gif"}, {"\r\n"}}, 
	// ?? TODO: is space a delimiter?
        {{"CSeq"}, {": "}, {"314159 INVITE"}, {"\r\n"}}, 
        {{"CSeq"}, {": "}, {"63104 OPTIONS"}, {"\r\n"}}, 
        {{"Date"}, {": "}, {"Sat, 13 Nov 2010 23:29:00 GMT"}, {"\r\n"}}, 
        {{"Date"}, {": "}, {"Thu, 9 Jun 2005 17:32:21 GMT"}, {"\r\n"}}, 
        {{"Error-Info"}, {": "}, {"<sip:not-in-service-recording@atlanta.com>"},
	 {"\r\n"}}, 
        {{"Error-Info"}, {": "}, {"<sip:abc@def.gh>"}, {"\r\n"}}, 
        {{"Expires"}, {": "}, {"5"}, {"\r\n"}}, 
        {{"Expires"}, {": "}, {"7200"}, {"\r\n"}}, 
        {{"From"}, {": "},  {"Bob <sip:bob@biloxi.com>"}, {";"},
	 {"tag", "=", "a6c85cf"}, {"\r\n"}}, 
        {{"From"}, {": "},  {"Alice <sip:alice@atlanta.com>"}, {";"},
	 {"tag", "=", "1928301774"}, {"\r\n"}}, 
        {{"From"}, {": "}, {"sip:+12125551212@phone2net.com"}, {";"},
	 {"tag=887s"}, {"\r\n"}}, 
        {{"From"}, {": "}, {"Anonymous <sip:c8oqz84zk7z@privacy.org>"},
	 {";"}, {"tag", "=", "hyh8"}, {"\r\n"}}, 
        {{"In-Reply-To"}, {": "},  {"17320@shanghai.chinatel.cn"}, {"\r\n"}}, 
        {{"In-Reply-To"}, {": "},  {"70710@saturn.bell-tel.com"}, {"\r\n"}}, 
        {{"Max-Forwards"},  {": "}, {"70"}, {"\r\n"}}, 
        {{"Max-Forwards"},  {": "}, {"65"}, {"\r\n"}}, 
        {{"Min-Expires"}, {": "}, {"60"}, {"\r\n"}}, 
        {{"Min-Expires"}, {": "}, {"82"}, {"\r\n"}}, 
        {{"Organization"}, {": "}, {" Boxes by Bob"}, {"\r\n"}}, 
        {{"Organization"}, {": "}, {" gov.nist, whatever it could mean..."},
	 {"\r\n"}}, 
        {{"Priority"}, {": "}, {"emergency"}, {"\r\n"}}, 
        {{"Priority"}, {": "}, {"non-urgent"}, {"\r\n"}}, 
        {{"Proxy-Authenticate"}, {": "},  {"Digest"}, {" "}, 
            {"realm=\"atlanta.com\""}, {", "}, 
            {"domain=\"sip:ss1.carrier.com\""}, {", "}, 
            {"qop=\"auth\""}, {", "}, 
            {"nonce=\"e84f1cce41e6cbe5aea9c8e88d35a\""}, {", "}, 
            {"opaque=\"\""}, {", "}, 
            {"stale=FALSE"}, {", "}, 
            {"algorithm=MD5"}, {"\r\n"}}, 
        //
        {{"Proxy-Authorization"}, {": "},  {"Digest"}, {" "}, 
            {"username=\"Alice\""}, {", "}, 
            {"realm=\"otlonto.com\""}, {", "}, 
            {"nonce=\"c60f3082ee1212b402a21831ae\""}, {", "}, 
            {"response=\"245f23415f11432b3434341c022\""}, {"\r\n"}}, 

        {{"Proxy-Require"}, {": "}, {"foo"}, {"\r\n"}}, 
        {{"Proxy-Require"}, {": "}, {"bar"} 
	 // , {"; "}, {"par", "=", "val"}
        }, 
        {{"Record-Route"}, {": "}, {"<sip:p4.domain.com;lr>"}, {"\r\n"}}, 
        {{"Record-Route"}, {": "}, {"<sip:p3.middle.com>"}, {"\r\n"}}, 
        {{"Reply-To"}, {": "}, {"Boob <sip:boob@beloxi.com>"}, {"\r\n"}}, 
        {{"Reply-To"}, {": "},  {"Todd <sip:todd@biloxi.com>"}, {"\r\n"}}, 
        {{"Require"}, {": "}, {"100rel"}, {"\r\n"}}, 
        {{"Require"}, {": "}, {"glitchware-purse"}, {"\r\n"}}, 
        {{"Retry-After"}, {": "}, {"18000"}, {";"}, 
            {"duration", "=", "3600"}, {"\r\n"}}, 
        {{"Retry-After"}, {": "}, {"120"}, {" (I'm in a meeting)"}, {"\r\n"}}, 
        {{"Route"}, {": "},  {"<sip:alice@atlanta.com>"}, {"\r\n"}}, 
        {{"Route"}, {": "},  {"<sip:UserB@there.com;maddr=ss2.wcom.com>"},
	 {"\r\n"}}, 
        {{"Server"}, {": "}, {"HomeServer v2"}, {"\r\n"}}, 
        {{"Server"}, {": "}, {"GlitchWare Ink Server v.1.0"}, {"\r\n"}}, 
//        {{"Route"}, {": "}, 
//            {"<sip:alice@atlanta.com>"}, {", "}, 
//            {"<sip:bob@biloxi.com>"}, {", \n             "}, 
//            {"<sip:carol@chicago.com>"}, {"\r\n"}}, 
        {{"Subject"}, {": "},
	 {"I know you're there, pick up the phone and talk to me!"}, {"\r\n"}},
        {{"Subject"}, {": "}, {"Weekend plans"}, {"\r\n"}}, 
        {{"Supported"}, {": "}, {"100rel"}, {"\r\n"}}, 
        {{"Supported"}, {": "}, {"ggg"}, {"\r\n"}}, 
        {{"Timestamp"}, {": "}, {"54"}, {"\r\n"}}, 
        {{"Timestamp"}, {": "}, {"102"}, {"\r\n"}}, 
        {{"To"}, {": "},  {"Bob <sip:bob@biloxi.com>"}, {"\r\n"}}, 
        {{"To"}, {": "},  {"Todd <sip:todd@biloxi.com>"}, {";"},
	 {"tag", "=", "a6c85cf"}, {"\r\n"}}, 
        {{"Unsupported"}, {": "}, {"100rel"}, {"\r\n"}}, 
        {{"Unsupported"}, {": "}, {"mumble-dumble"}, {"\r\n"}}, 
        {{"User-Agent"}, {": "}, {"Softphone Beta1.5"}, {"\r\n"}}, 
        {{"User-Agent"}, {": "}, {"yes-kia car talk"}, {"\r\n"}}, 
        {{"Via"}, {": "}, 
            {"SIP/2.0/UDP pc33.atlanta.com"}, {";"}, 
            {"branch", "=", "z9hG4bKhjhs8ass877"}, {"\r\n"}}, 
        {{"Via"}, {": "}, 
            {"SIP/2.0/UDP bigbox3.site3.atlanta.com"}, {"\n         ;"}, 
            {"branch", "=", "z9hG4bK77ef4c2312983.1"}, {";"},
	 {"received", "=", "192.0.2.2"}, {"\r\n"}}, 
        {{"Warning"}, {": "}, {"370 devnull \"Choose a bigger pipe\""}, 
	 {"\r\n"}}, 
        {{"Warning"}, {": "},
	 {"300 isi.edu \"Incompatible network protocol\""}, 
	 {"\r\n"}}, 
        {{"WWW-Authenticate"}, {": "},  {"Digest"}, {" "}, 
	 {"realm=\"atlanta.com\""}, {", "}, 
	 {"domain=\"sip:boxesbybob.com\""}, {", "}, 
	 {"qop=\"auth\""}, {", "}, 
	 {"nonce=\"f84f1cec41e6cbe5aea9c8e88d359\""}, {", "}, 
	 {"opaque=\"\""}, {", "}, 
	 {"stale=FALSE"}, {", "}, 
	 {"algorithm=MD5"}, {"\r\n"}}, 
        // Extension headers
        {{"Asd"}, {": "},  {"fghjkl"}, {"; "}, {"qwe", "=", "rty"}, {"; "},
	 {"zxc", "=", "vbn"}, {"\r\n"}}, 
        {{"Zxcv"}, {": "}, {"bnm"}, {"\r\n"}}, 
        {{"Qwe"}, {": "}, {"rty"}, {"; "}, {"ui", "=", "op89"}, {"; "},
	 {"zx", "=", "cvb"}, {"; "}, {"nm", "=", "jhg-kl-09"}, {"\r\n"}}, 
	// */
    };

    // auxiliary tools, specific stuff
    public static String makeDelimiterFor(THeader h) {
        String headerName = h.getName();
        String res = "; ";
        if (headerName == "WWW-Authenticate"
	   || headerName == "Proxy-Authenticate"
	   || headerName == "Proxy-Authorization"
	   || headerName == "Authorization") {
            res = ", ";  
	    // TODO: IIRC the 1st delimiter is space while the 2nd one is comma
        }
        return res;
    }

    /**
     * modify the string so that it looks good in the log (replace
     * CR/LF by 'R' and 'N')
     * @param s the string to be print-encoded
     * @return the modified string
     */
    static String prtEncode(String s) {
        return s.replace('\r', 'R').replace('\n','N');
    }

    /**
     * extract the test name from the class name. The class name has
     * the form ...blah..blah...Tester_XYZ, where XYZ is the test name.
     * @param s class name
     * @return test name
     */
    static String className2testName(String s) {
        return s.substring(s.lastIndexOf('_') + 1); 
	// substring index = 0 if -1 is returned
    }

    public void linebreak() {
        // if (verbose)
        myout.println("=== 1st, 2nd:"+firstHeaderN+", "+secondHeaderN
		      +" ====================================================");
    }

    /**
     * Print the test subtitle
     *
     * @param s descriptive text
     * @param t the first THeader that contains data that get used and,
     *  probably, modified
     * @param n the second THeader that serves as a source of data that 
     * replace data taken from the first THeader
     */
    /*
     * public void testing(String s, THeader t, THeader n)q {
     *    myout.println("### testing " + s +" '" + prtEncode(t.toString())
     *            + "' '" + prtEncode(n.toString()) + "'");
     * }
     */

    /**
     * If got and expected do not match, set
     * testResult[firstHeaderN][secondHeaderN][testN] to FAILED
     *
     * @param subTestN subtest number
     * @param got the obtained string (gets compared to <code>expected</code>)
     * @param expected the expected string
     * @param shortDescr
     * @param moreDescr some text for human readability
     */
    public void expect(int subTestN, 
		       String got,
		       String expected, 
		       String shortDescr, 
		       String moreDescr) {
        if (null == subTestName[subTestN]) {
            subTestName[subTestN] = shortDescr;
        }
        assertEquals("[" + shortDescr + "]" + "{" + moreDescr+ "}", expected, got);
        if (got == null ? expected == null : got.equals(expected)) {
	    //            myout.println("passed: "+subTestN+" "+description);
        } else {
	    //  myout.println( "*** error at subtest "
	    //                 + subTestN + " " + description +
	    //                 " \nexpected: '" + expected +
	    //                 "'\nobtained: '" + got + "'");
            testResult[firstHeaderN][secondHeaderN][testN] = FAILED;
            recordFailureReason(subTestN);
        }
    }

    /**
     * remembers which sub test has failed
     * @param subTestNum
     */
    void recordFailureReason(int subTestNum) {
        failureReason[firstHeaderN][secondHeaderN][testN] |= 1 << subTestNum;
	/*
	 * if (null == failureReason[firstHeaderN][secondHeaderN][testN]) {
	 *    failureReason[firstHeaderN][secondHeaderN][testN] += 
	 *              testName[testN];
	 *  } else {
	 *    failureReason[firstHeaderN][secondHeaderN][testN] += 
	 *        " " + testName[testN];
	 *  }
	 */
    }

    /**
     * convert a failure reason represented as a bit vector to a
     * human-readable string with names of failed tests.
     * @param fr failure reason
     * @return
     */
    String failureReasonToString(int fr) {
        String res = "";
        for (int i = 0; i < nSubTests; i++) {
            if (0 != (fr & (1 << i))) {
                if (res.equals("")) {
                    res = subTestName[i];
                } else {
                    res += " " + subTestName[i];
                }
            }
        }
        if ("".equals(res)) 
	    res = "OK";
        // if (fr != 0) res += " " + Integer.toHexString(fr);
        return res;
    }

    /**
     * an auxiliary function.
     * if ctl is -1 (specifies the whole range), return ifrange, else
     * return ifexact.
     * @param ctl
     * @param ifrange
     * @param ifexact
     * @return
     */
    private int choose(int ctl, int ifrange, int ifexact) {
        return ctl == -1 ? ifrange : ifexact;
    }

    /**
     * return failure reasons encountered while running the specified test(s).
     * @param fhn first header number, -1 for the whole range
     * @param shn second header number, -1 for the whole range
     * @param tn test number, -1 for the whole range
     * @return
     */
    int getFailureReasons(int fhn, int shn, int tn) {
        // System.out.println("getFailureReasons(" + fhn + ", " 
	// + shn + ", " + tn + ")");
       
        int cumulativeFailureReason = 0;
        int nHeaderSamples = headerSample.length;
        for (int firstHeaderN = choose(fhn, 0, fhn), 
		 firstHeaderNBound = choose(fhn, nHeaderSamples, fhn + 1);
            firstHeaderN < firstHeaderNBound;
            firstHeaderN++) {
            for (int secondHeaderN = choose(shn, 0, shn), 
		     secondHeaderNBound = choose(shn, nHeaderSamples, shn + 1);
                secondHeaderN < secondHeaderNBound;
                secondHeaderN++) {
                for (int testN = choose(tn, 0, tn), 
			 testNBound = choose(tn, nTests, tn + 1);
		     testN < testNBound;
		     testN++) {
		    /*
		     * System.out.print("indices: " + firstHeaderN + " " 
		     *    + secondHeaderN + " " + testN);
		     * System.out.print(" bounds : " + firstHeaderNBound + " " 
		     *    + secondHeaderNBound + " " + testNBound);
		     * System.out.print(" reason:" 
		     *    + failureReason[firstHeaderN][secondHeaderN][testN]
		     *    + "\n");
		     */
                    cumulativeFailureReason |= 
			failureReason[firstHeaderN][secondHeaderN][testN];
                }
            }
        }
        return cumulativeFailureReason;
    }

    /**
     * Throw a BlockedTestException if the test has failed at the
     * pre-requisite stage. Why: we may discover a failure before
     * we actually start the test, and we want such cases to be
     * marked separately.
     * Reads testResult[firstHeaderN][secondHeaderN][testN].
     * @throws BlockedTestException
     */
    public void failureMeansBlocked() throws BlockedTestException {
        // myout.println("failureMeansBlocked():");
        if (testResult[firstHeaderN][secondHeaderN][testN] != NOTRUN) {
            // myout.println("failureMeansBlocked() yes!!!");
            // testResult[firstHeaderN][secondHeaderN][testN] = BLOCKED;
            throw new BlockedTestException();
        }
    }

    /**
     * same as expect, but for String arrays
     * @param subTestN
     * @param got
     * @param expected
     * @param shortDescr
     * @param moreDescr
     */
    public void expecta(int subTestN, String[] got, String[] expected, 
			String shortDescr, String moreDescr) {
        // myout.println("areEqual(got, expected) = " 
	//    + areEqual(got, expected));
        if (null == subTestName[subTestN]) {
            subTestName[subTestN] = shortDescr;
        }

        if (!areEqual(got, expected)) {
            /*
	     * DEBUG:
	     * String msg = "*** error at subtest " + subTestN
	     * + " " + shortDescr + moreDescr
	     * + " \nexpected arr: ";
	     * msg += stringizeStrArr(expected);
	     * msg += "\nobtained arr: ";
	     * msg += stringizeStrArr(got);
	     * fail(msg);
	     */
	    fail(shortDescr);
        } else {
            assertTrue(shortDescr + moreDescr, true);
	    //  myout.println("passed: "+subTestN+" "+description);
        }
    }

    /**
     * compare two string arrays
     * @param a
     * @param b
     * @return true if they are equal
     */
    static boolean areEqual(String[] a, String[] b) {
        if (a == b) {
            return true;
        }
        if (a == null || b == null) {
            return false;
        }
        if (a.length != b.length) {
            return false;
        }
        for (int i = 0, len = a.length; i < len; i++) {
            if (!a[i].equals(b[i])) {
                return false;
            }
        }
        return true;
    }

    /**
     * print a string array to Syatem.out
     * @param a
     */
    void printStrArr(String[] a) {
        myout.print("[" + a.length + "]" + "{");
        for (int i = 0, len = a.length; i < len; i++) {
            myout.print("'" + a[i] + "' ");
        }
        myout.print("}");
    }

    /**
     * convert a string array to string
     * @param a
     */
    static String stringizeStrArr(String[] a) {
        String res = "";
        res = "[" + a.length + "]" + "{";
        for (int i = 0, len = a.length; i < len; i++) {
            res += "'" + a[i] + "' ";
        }
        res += "}";
        return res;
    }

    /**
     * print a string array to Syatem.out, and perform a carriage return
     * @param a
     */
    void printlnStrArr(String[] a) {
        printStrArr(a);
        myout.print("\n");
    }

    /**
     * Compare data in the SipHeader h to the data in the THeader t,
     * and modify testResult[firstHeaderN][secondHeaderN][testN] accordingly.
     * Calls expect().
     *
     * This function is used first to verify the read access functions,
     * and then to verify the results of write access functions.
     * @param h
     * @param t
     * @param descr
     */
    public void verifyOutputs(SipHeader h, THeader t, String descr) {
	/*
	 *  String args = " SipH='" + prtEncode(h.toString())
	 *      + "' TH='" + prtEncode(t.toString()) + "'";
	 */
        String args = " '" + prtEncode(t.toString()) + "'"; 
        try {
            expect(1, h.getName(), t.getName(), "getName", args); 
	} catch (Throwable th) { 
            expect(1, "**EXCEPTION**", t.getName(), "getName", args);
        }
        try {
            expect(2, h.getHeaderValue(), t.getHeaderValue(), "getHeaderValue",
		   args);
        } catch (Throwable th) {
            expect(2, "**EXCEPTION**", t.getHeaderValue(), "getHeaderValue", 
		   args);
        }
        try {
            expect(3, h.getValue(), t.getValue(), "getValue", args);
        } catch (Throwable th) {
            expect(3, "**EXCEPTION**", t.getValue(), "getValue", args);
        }
        try {
            expect(4, h.toString(), t.toString(), "toString", args);
        } catch (Throwable th) {
            expect(4, "**EXCEPTION**", t.toString(), "toString", args);
        }
        try {
            expecta(5, h.getParameterNames(), t.getParameterNames(),
		    "getParameterNames", args);
        } catch (Throwable th) {
            expecta(5, new String[] {"**EXCEPTION**"}, t.getParameterNames(),
		    "getParameterNames", args);
        }
        for (int i = 0; i < 9; i++) {
	    // myout.println("getParameterNames i:" + i);
            if (t.getPrmNam(i) != null) {
                try {
                    expect(10 + i, h.getParameter(t.getPrmNam(i)),
			   t.getPrmVal(i), "getParameter#" + i, args);
                } catch (Throwable th) {
                    expect(10 + i, "**EXCEPTION**",
			   t.getPrmVal(i), "getParameter#" + i, args);
                }
            }
        }
	//	expect(1,h., t.);
	//	expect(1,h., t.);
	//	expect(1,h., t.);
    }
    
    /**
     * Common functionality for write function tester classes.
     */
    abstract class Tester {
        /**
         * a constructor that does nothing
         * (esp. in view of that there isn't any data)
         */
        public Tester() { }

        /**
         * diagnostic actions to be performed when an exception happens
         * @param t
         * @param name
         */
        void onExc(Throwable t, String name) {
            myout.println("\n***{{ exception while testing " 
			  + name + "\n" 
			  + t + "\n}}***");
            t.printStackTrace();
            recordFailureReason(exceptionAndNoSubTests);
            if (null == subTestName[exceptionAndNoSubTests]) {
                subTestName[exceptionAndNoSubTests] = "EXCEPTION";
            }
        }

        /**
         * The framework function. Calls run() that gets overridden
         * in the derived classes.
         * @param t the first THeader that contains data that get used
	 *           and, probably, modified
         * @param n the second THeader that serves as a source of data
	 *           that replace data taken from the first THeader
         * @param descr0 test description: which arguments
         */
        void test(THeader t, THeader n, String descr0) {
            testResult[firstHeaderN][secondHeaderN][testN] = NOTRUN;
            if (testName[testN] == null) {
                testName[testN] = className2testName(this.getClass().getName());
            }
            String name = testName[testN];
            String descr = name + " " + descr0;
            declare(descr);
            // testing(name, t, n);
            try {
                doTest(t, n, name + " " + descr);
                /*
		 * myout.println("%%% successful run: "
		 *    + testResult[firstHeaderN][secondHeaderN][testN]);
		 */
                if (testResult[firstHeaderN][secondHeaderN][testN] == NOTRUN) {
		    // still '-'; not 'f', and in fact no exception
		    // has been there
                    testResult[firstHeaderN][secondHeaderN][testN] = SUCCESS;
                }
            } catch (BlockedTestException bte) {
                testResult[firstHeaderN][secondHeaderN][testN] = BLOCKED;
                /*
		 * myout.println("%%% blocked test: " 
		 *    + testResult[firstHeaderN][secondHeaderN][testN]);
		 */
                fail(descr + "blocked");
            } catch (NothingToDoInTestException ntdite) {
                testResult[firstHeaderN][secondHeaderN][testN] = NOTHINGTODO;
                /*
		 * myout.println("%%% nothing to do: "
		 *    + testResult[firstHeaderN][secondHeaderN][testN]);
		 */
                assertTrue(descr + "nothing to do", true);
            } catch (Throwable tt) {
                testResult[firstHeaderN][secondHeaderN][testN] = EXCEPTION;
                /*
		 * myout.println("%%% unsuccessful run: "
		 *    + testResult[firstHeaderN][secondHeaderN][testN]);
		 */
                fail(descr + "exception");
                onExc(tt, name);
            }
        }

        /**
         * run the test within the test() framework function.
         * @param t the first THeader that contains data that get used and,
	 *  probably, modified
         * @param n the second THeader that serves as a source of data that
	 * replace data taken from the first THeader
         * @param descr
         * @throws BlockedTestException
         */
        abstract void doTest(THeader t, THeader n, String descr) 
	    throws BlockedTestException, NothingToDoInTestException;
    }
    
    class Tester_construct extends Tester {
        public Tester_construct() { }
	
        void doTest(THeader t, THeader n, String descr) 
	    throws BlockedTestException {
	    /*
	     * myout.println("Creating # " + t.getName() + " # " 
	     *     + t.getHeaderValue() + " #"); 
	     */
	    SipHeader s = new SipHeader(t.getName(), t.getHeaderValue());
            verifyOutputs(s, t, descr);
        }
    }

    class Tester_constructWithPrm extends Tester_construct {
        String[] param1 = {"xprm1", "=", "Value1"};
        String[] param2 = {"xprm2-pi", "=", "3.141592653589793238"};
        String[] param3 = {"xprm3-text", "=",
			   "\"And his sister's weird, she drives a lorry\""};

        public Tester_constructWithPrm() { }

        void test(THeader t, THeader n, String descr) {
            THeader tt = new THeader(t);
            tt.appendPrmWhole(param1);
            tt.appendPrmWhole(param2);
            tt.appendPrmWhole(param3);
            super.test(tt, n, descr);
        }
    }
    
    class Tester_setName extends Tester {
        public Tester_setName() { }
	
        void doTest(THeader t, THeader n, String descr) 
	    throws BlockedTestException {
	    /*
	     * myout.println("Test " + testN + " Creating # " + t.getName() 
	     *   + " # " + t.getHeaderValue() + " #"); 
	     */
	    SipHeader s = new SipHeader(t.getName(), t.getHeaderValue());
            verifyOutputs(s, t, descr);
            failureMeansBlocked();

            THeader m = new THeader(t);
            m.setName(n.getName());

            s.setName(n.getName());
            verifyOutputs(s, m, descr);
        }
    }

    class Tester_setValue extends Tester {
        public Tester_setValue() { }

        void doTest(THeader t, THeader n, String descr)
	    throws BlockedTestException {
            SipHeader s = new SipHeader(t.getName(), t.getHeaderValue());
            verifyOutputs(s, t, descr);
            failureMeansBlocked();

            THeader m = new THeader(t);
	    /*
	     * myout.println("s,m before: $" + prtEncode(s.toString()) + "$" 
	     *    + prtEncode(m.toString()) + "$"); 
	     * myout.println("setValue: $"
	     *    + n.getValue() + "$"); 
	     */
	    m.setValue(n.getValue());
            s.setValue(n.getValue());

	    /*
	     *  myout.println("s,m after : $" + prtEncode(s.toString())
	     *     + "$" + prtEncode(m.toString()) + "$");
	     */
            verifyOutputs(s, m, descr);
        }
    }

    class Tester_removeParameter extends Tester {
        public Tester_removeParameter() { }

        void doTest(THeader t, THeader n, String descr)
	    throws BlockedTestException, NothingToDoInTestException {
            String[] prm = n.getParameterNames();
	    // printStrArr(prm);
            if (0 == prm.length) {
                // myout.println("nothing to do");
                throw new NothingToDoInTestException();
            }

            for (int i = 0; i < prm.length; i++) {
                SipHeader s = new SipHeader(t.getName(), t.getHeaderValue());
                THeader m = new THeader(t);
                verifyOutputs(s, m, descr);
                failureMeansBlocked();

		//  myout.println("s,m before: $" + s + "$" + m + "$");
                m.rmvPrm(i);
                s.removeParameter(t.getPrmNam(i));

		// myout.println("s,m after : $" + s + "$" + m + "$");
                verifyOutputs(s, m, descr);
                if (testResult[firstHeaderN][secondHeaderN][testN] != NOTRUN) {
		    //   myout.println("s,m already wrong, exiting test");
                    break;
                }
            }
        }
    }

    class Tester_setParameter extends Tester {
        public Tester_setParameter() { }

        void doTest(THeader t, THeader n, String descr)
	    throws BlockedTestException, NothingToDoInTestException {
            String[] prm = n.getParameterNames();
	    // printStrArr(prm);
            if (0 == prm.length) {
                // myout.println("nothing to do");
                throw new NothingToDoInTestException();
            }
            for (int i = 0; i < prm.length; i++) {
                SipHeader s = new SipHeader(t.getName(), t.getHeaderValue());
                THeader m = new THeader(t);
                verifyOutputs(s, m, descr);
                failureMeansBlocked();

		// myout.println("s,m before: !" + s + "!" + m + "!");
                m.setOrAddPrmWhole(n.getPrmWhole(i));
                s.setParameter(n.getPrmNam(i), n.getPrmVal(i));

		// myout.println("s,m after : !"+s+"!"+m+"!");
                verifyOutputs(s, m, descr);
                if (testResult[firstHeaderN][secondHeaderN][testN] != NOTRUN) {
		    //  myout.println("s,m already wrong, exiting test");
                    break;
                }
            }
        }
    }

    /**
     * Run all tests with the arguments t and n
     * @param t specifies the header that gets constructed and modified
     * @param n a header that serves as a data source for modifications
     * @param descr text description of arguments t and n
     */
    public void verifyAll(THeader t, THeader n, String descr) {
        testN = 0;
        new Tester_construct().test(t, n, descr);        testN++;
        new Tester_constructWithPrm().test(t, n, descr); testN++;
        new Tester_setName().test(t, n, descr);          testN++;
        new Tester_setValue().test(t, n, descr);         testN++;
        new Tester_removeParameter().test(t, n, descr);  testN++;
        new Tester_setParameter().test(t, n, descr);     testN++;
        if (testN != nTests) {
	    throw new RuntimeException("mismatch in # of tests");
	}
    }

    /**
     * Body of the test 1.
     *
     * Test constructors of SipAddress class.
     */
    void Test1() {
        for (int i = 0; i < headerSample.length; i++) {
            for (int j = 0; j < headerSample.length; j++) {
                firstHeaderN = i;
                secondHeaderN = j;
                linebreak();
                THeader t = new THeader(headerSample[i]);
                THeader n = new THeader(headerSample[j]);
		/*
		 * DEBUG:
		 * String argsDescr = "\nwithHdr: '"
		 *  + prtEncode(n.toString()) 
		 *  + "' (" + i + ")\ndataSrc: '"
		 *  + prtEncode(t.toString()) 
		 *  + "' (" + j +")\n";
		 */
		String argsDescr = "test1";
                verifyAll(t, n, argsDescr);
            }
        }
    }
    /*
     *    void Test2() {
     *        RandomAccessStream ras  = new RandomAccessStream();
     *        try {
     *            ras.connect("test.log",Connector.WRITE);
     *            OutputStream os=ras.openOutputStream();
     *            os.write("testing log".getBytes());
     *            os.close();
     *            ras.disconnect();
     *        } catch (IOException e) {
     *            e.printStackTrace();
     *        }
     *    }
     */

    MyOut myout = new MyOut();

    class MyOut {
        RandomAccessStream ras;
        OutputStream os;
        public static final String fileName = "funcmap.txt";

        void init() {
            ras = new RandomAccessStream();
            try {
                File fs = new File();
                if (fs.exists(fileName)) {
                    fs.delete(fileName);
                }
            } catch (IOException e) {
                System.err.print("problems when trying to delete old log file: "
				 + fileName);
            }
            try {
                ras.connect(fileName, Connector.WRITE);
                os = ras.openOutputStream();
            } catch (IOException e) {
                System.err.print("could not open log");
            }
        }

        void windup() {
            try {
                os.close();
                ras.disconnect();
            } catch (IOException e) {
                System.err.print("could not close log");
            }
        }

        void print(String s) {
            try {
                os.write(s.getBytes());
            } catch (IOException e) {
                System.err.print("could not write to log");
            }
        }

        void print(char c) {
            try {
                os.write(c);
            } catch (IOException e) {
                System.err.print("could not write to log");
            }
        }

        void println(String s)  {
            print(s+"\n");
        }

        void println() {
            print("\n");
        }
    }

    /**
     * print the "functionality coverage map" indicating what tests
     * fail on what data.
     */
    void printResults() {
        myout.print("\n");
        for (int k = 0; k < nTests; k++) {
            myout.print("---- test " + k + " -- " + testName[k] + " ---- \n");
            for (int i = 0; i < headerSample.length; i++) {
                myout.print("" + (i / 10 % 10));
            }
            myout.println();
            for (int i = 0; i < headerSample.length; i++) {
                myout.print("" + (i % 10));
            }
            myout.println();
            for (int i = 0; i < headerSample.length; i++) {
                for (int j = 0; j < headerSample.length; j++) {
                    myout.print(testResult[i][j][k]);
                }
                myout.print(" (" + i + ")" 
			    + prtEncode(new THeader(headerSample[i]).toString())
			    + "\n");
            }
            myout.print("\n");
        }
        myout.print("---- used headers ---- \n");
        for (int i = 0; i < headerSample.length; i++) {
            for (int j = 0; j < i; j++) {
                myout.print(" ");
            }
            myout.print("" + i +": ");
            myout.print(prtEncode(new THeader(headerSample[i]).toString()));
            myout.print("\n");
        }
        myout.print("\n");
    }

    void printAnalysis() {
        try {
            myout.print("---- per-sample analysis ---- \n");
            for (int sn = 0; sn < headerSample.length; sn++) {
                myout.println("\n" + sn + ") " + headerSample[sn][0][0]);
                myout.println(new THeader(headerSample[sn]).toString());
                for (int tn = 0; tn < nTests; tn++) {
                    myout.print(testName[tn] + ": ");
                    myout.println(failureReasonToString(getFailureReasons(sn,
									  -1,
									  tn)));
                }
            }
        } catch (Throwable e) {
            System.out.println("in printAnalysis():");
            e.printStackTrace();
        }

	/*
	 * myout.print("---- per-class analysis ---- \n");
	 * for (int sampleNFrom=0, sampleNUpto=0; s
	 *     ampleNFrom < headerSample.length;
	 *     sampleNFrom = sampleNUpto) {
	 *     myout.println(headerSample[sampleNFrom][0][0]);
	 *  for (sampleNUpto = sampleNFrom; 
	 *       sampleNUpto<headerSample.length
         *       && headerSample[sampleNFrom][0][0]
	 *      .equals(headerSample[sampleNUpto][0][0]);
         *      sampleNUpto++) {
	 *  }
         *  for (int tn = 0; tn < nTests; tn++) {
         *      myout.print(testName[tn]+": ");
	 *  }
	 *  for (int k = sampleNFrom; k < sampleNUpto; k++) {
	 *  }
	 * }
	 */
    }

    /**
     * Tests execute
     */
    public void runTests() {
	// declare("Constructor SipAddress test");
        testResult = new char[headerSample.length][headerSample.length][nTests];
        failureReason = 
	    new int[headerSample.length][headerSample.length][nTests];
        myout.init();
	Test1();
        printResults();
        printAnalysis();
        myout.windup();
    }
}