www.pudn.com > ejip.zip > Ppp.java


/* 
 * Copyright (c) Martin Schoeberl, martin@jopdesign.com 
 * All rights reserved. 
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions 
 * are met: 
 * 1. Redistributions of source code must retain the above copyright 
 *    notice, this list of conditions and the following disclaimer. 
 * 2. Redistributions in binary form must reproduce the above copyright 
 *    notice, this list of conditions and the following disclaimer in the 
 *    documentation and/or other materials provided with the distribution. 
 * 3. All advertising materials mentioning features or use of this software 
 *    must display the following acknowledgement: 
 *	This product includes software developed by Martin Schoeberl 
 * 
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
 * SUCH DAMAGE. 
 * 
 */ 
 
package ejip; 
 
/** 
*	Ppp.java 
* 
*	communicate with jopbb via serial line. 
*/ 
 
import util.*; 
 
/** 
*	Ppp driver. 
*/ 
 
public class Ppp extends LinkLayer { 
 
	private static final int MAX_BUF = 1500+4;		// 1500 is PPP information field 
/** 
*	period for thread in ms. 
*/ 
	private static final int PERIOD = 10; 
 
 
	private static final int IP = 0x0021;			// Internet Protocol packet 
	private static final int IPCP = 0x8021;			// Internet Protocol Configuration Protocol packet 
	private static final int CCP = 0x80fd;			// Compression Configuration Protocol packet 
	private static final int LCP = 0xc021;			// Link Configuration Protocol packet 
	private static final int PAP = 0xc023;			// Password Authentication Protocol packet 
 
	private static final int REQ = 1;				// Request options list 
	private static final int ACK = 2;				// Acknowledge options list 
	private static final int NAK = 3;				// Not acknowledge options list 
	private static final int REJ = 4;				// Reject options list 
	private static final int TERM = 5;				// Termination 
 
	private static final int NEG_SEND= 3000/PERIOD;	// Period of send negotiation 
	private static final int IP_SEND= 10000/PERIOD;	// Send timout for ip for reconnect 
 
/** 
*	receive buffer 
*/ 
	private static int[] rbuf; 
/** 
*	send buffer 
*/ 
	private static int[] sbuf; 
/** 
*	bytes received. 
*/ 
	private static int cnt; 
/** 
*	mark escape sequence. 
*/ 
	private static boolean esc; 
/** 
*	a ppp packet is in the receive buffer. 
*/ 
	private static boolean ready; 
/** 
*	bytes to be sent. 0 means txFree 
*/ 
	private static int scnt; 
/** 
*	allready sent bytes. 
*/ 
	private static int sent; 
 
/** 
*	flag (0x7e, ~) received 
*/ 
	private static boolean flag; 
/** 
*	number for lcp id 
*/ 
	private static int lcpId; 
/** 
*	state machine 
*/ 
	private static int state; 
/** 
*	reject counter 
*/ 
	private static int rejCnt; 
	private static final int MAX_REJ = 5; 
 
/** 
*	remote ip address. 
*/ 
	public static int ipRemote; 
/** 
*	ip address. 
*/ 
	public static int ip; 
 
/** 
*	The one and only reference to this object. 
*/ 
	private static Ppp single; 
 
/** 
*	private constructor. The singleton object is created in init(). 
*/ 
	private Ppp() { 
		super(PERIOD);						// thats the period. 
	} 
 
/** 
*	allocate buffer, start serial buffer and slip Thread. 
*/ 
	public static void init() { 
 
		if (single != null) return;			// allready called init() 
 
		rbuf = new int[MAX_BUF]; 
		sbuf = new int[MAX_BUF]; 
		cnt = 0; 
		esc = false; 
		ready = false; 
		flag = false; 
		scnt = 0; 
		sent = 0; 
 
		lcpId = 0x11; 
		ip = 0; 
		ipRemote = 0; 
 
		initStr(); 
 
		Serial.init();						// start serial buffer thread 
 
		single = new Ppp(); 
		single.start();						// kick off the thread 
	} 
 
/** 
*	main loop. 
*/ 
	public void run() { 
 
		connect(); 
	} 
 
	/** 
	* windoz PPP. 
	*/ 
	private static int[] client; 
	private static int[] ver; 
 
	private static int[] ok; 
	private static int[] connect; 
	private static int[] ath; 
	private static int[] pin; 
	private static int[] con; 
	private static int[] dial; 
	private static int[] flow; 
 
	private static void initStr() { 
 
		int[] s1 = { 'C', 'L', 'I', 'E', 'N', 'T' }; 
		client = s1; 
		int[] s2 = { 'V', 'E', 'R' }; 
		ver = s2; 
 
 
 
		int [] s3 = { 'O', 'K' }; 
		int [] s4 = { 'E', 'C', 'T' }; 
		int [] s5 = { '|', '+', '+', '+', '|', 'A', 'T', 'H', '\r' }; 
		// int [] s6 = { 'A', 'T', '+', 'C', 'P', 'I', 'N', '=', '9', '1', '7', '4','\r'  }; 
		int [] s6 = { 'A', 'T', '+', 'C', 'P', 'I', 'N', '=', '5', '6', '4', '4','\r'  }; 
 
		ok = s3; 
		connect = s4; 
		ath = s5; 
		pin = s6; 
 
		initStr2(); 
	} 
 
	private static void initStr2() { 
 
		// int [] s7 = { 'A', 'T', '+', 'C', 'G', 'D', 'C', 'O', 'N', 'T', '=', '1', ',', '"', 'I', 'P', '"', ',', '"', 'w', 'e', 'b', '.', 'o', 'n', 'e', '.', 'a', 't', '"', '\r' }; 
		int [] s7 = { 'A', 'T', '+', 'C', 'G', 'D', 'C', 'O', 'N', 'T', '=', '1', ',', '"', 'I', 'P', '"', ',', '"', 'A', '1', '.', 'n', 'e', 't', '"', '\r' }; 
		int [] s8 = { 'A', 'T', 'D', '*', '9', '9', '*', '*', '*', '1', '#', '\r' }; 
		int [] s9 = { 'A', 'T', '\\', 'Q', '3', '\r' }; 
 
		con = s7; 
		dial = s8; 
		flow = s9; 
		 
	} 
 
	/** 
	*	wait seconds 
	*/ 
	void waitSec(int t) { 
 
		// t *= 1000/PERIOD; 
 
		for (int i=0; ii) return false; 
Dbg.wr('\''); 
		for (i=0; i0; --timer) { 
 
			waitForNextPeriod(); 
			dropIp(); 
 
			for (int i = Serial.rxCnt(); i>0; --i) { 
 
				int val = Serial.rd(); 
Dbg.wr(val); 
				if (val == rcv[ptr]) { 
					++ptr; 
					if (ptr==len) { 
Dbg.wr('\n'); 
						waitSec(1); 
						return true;			// we're done 
					} 
				} else { 
					ptr = 0;					// reset match pointer 
				} 
			} 
		} 
Dbg.wr('?'); 
Dbg.wr('\n'); 
 
		return false;							// timeout expired 
	} 
 
	/** 
	*	do the modem stuff till CONNECT 
	*/ 
 
	void modemInit() { 
 
		for (;;) { 
 
			if (sendWait(ath, ok, 3)) { 
				if (sendWait(flow, ok, 3)) { 
					if (!sendWait(con, ok, 2)) {	// when ERROR PIN is not set 
						sendWait(pin, ok, 30); 
						if (!sendWait(con, ok, 20)) { 
							continue;				// something really strange happend! 
						} 
					} 
					if (sendWait(dial, connect, 10)) { 
						break; 
					} 
				} 
			} 
 
			waitSec(1); 
		} 
 
		state = MODEM_OK; 
		timer = 0; 
		lcpAck = false; 
		ipcpAck = false; 
	} 
 
	/** 
	*	cancel connection. 
	*/ 
	void modemHangUp() { 
 
		ip = 0;								// stop sending ip data 
		state = INIT; 
		rejCnt = 0; 
		// flush buffer 
		for (int i = Serial.rxCnt(); i>0; --i) { 
			Serial.rd(); 
		} 
		for (;;) { 
			if (sendWait(ath, ok, 3)) { 
				break; 
			} 
			waitSec(3); 
		} 
	} 
	 
	private static final int INIT = 0; 
	private static final int MODEM_OK = 1; 
	private static final int LCP_SENT = 2; 
	private static final int LCP_OK = 3; 
	private static final int PAP_SENT = 4; 
	private static final int PAP_OK = 5; 
	private static final int IPCP_SENT = 6; 
	private static final int IPCP_SENT2 = 7; 
	private static final int IPCP_OK = 8; 
	private static final int CONNECTED = 9; 
 
	private static int timer;							// negotion send and ip-restart timer 
	private static boolean lcpAck; 
	private static boolean ipcpAck; 
 
	/** 
	*	establish a connetcion. 
	*/ 
	void connect() { 
 
		timer = 0; 
		state = INIT; 
		rejCnt = 0; 
		lcpAck = false; 
		boolean ipcpAck = false; 
 
		// 
		//	start the modem 
		// 
		modemInit(); 
 
		// 
		//	now LCP negotiation 
		// 
 
		for (;;) { 
			waitForNextPeriod(); 
			loop(); 
 
			if (state==MODEM_OK) { 
			} 
 
			if (rejCnt > MAX_REJ) { 
				modemHangUp();		// start over 
				modemInit(); 
			} 
 
			if (ready && scnt==0) {				// one packet is read and send buffer is free 
 
				yield();						// time for a thread switch 
 
dbgCon(); 
 
				int prot = (rbuf[2]<<8)+rbuf[3]; 
				int code = rbuf[4]; 
 
				if (prot == LCP) { 
 
					if (code == REQ) { 
						if (checkOptions(LCP)) { 
							lcpAck = true;; 
						} else { 
							++rejCnt; 
						} 
					} else if (code==ACK && rbuf[5]==lcpId) { 
						state = LCP_OK; 
					} else if (code==TERM) { 
						modemHangUp();		// start over 
						modemInit(); 
					} 
 
				} else if (prot == PAP) { 
 
					if (rbuf[4]==ACK && rbuf[5]==lcpId) { 
						state = PAP_OK; 
					} 
 
				} else if (prot == IPCP) { 
 
					if (code == REQ) { 
						if (checkOptions(IPCP)) { 
							ipcpAck = true;; 
						} 
					} else if (code == NAK) {	// with this NAK we will get our IP address 
						ip= (rbuf[10]<<24) + (rbuf[11]<<16) + 
							(rbuf[12]<<8) + rbuf[13]; 
						dbgIp(ip); 
						makeIPCP(); 
						state = IPCP_SENT2; 
					} else if (code == ACK) { 
						state = IPCP_OK; 
						// nothing more to do ? 
						state = CONNECTED; 
Dbg.wr('C'); 
Dbg.wr('\n'); 
					} 
 
// 
//	des is net guat: kommt nur hierher, wenn der Sendbuffer frei ist!!! 
// 
				} else if (prot == IP) {		// we finally got an ip packet :-) 
 
					readIp(); 
 
				} 
 
				cnt = 0; 
				ready = false; 
			} 
 
			doSend(); 
		} 
	} 
 
	void doSend() { 
 
		if (state==CONNECTED) {			// send waiting ip packets 
			if (scnt==0) {				// transmit buffer is free 
				timer = 0; 
				// 
				// get a ready to send packet with source from this driver. 
				// 
				Packet p = Packet.getPacket(single, Packet.SND, Packet.ALLOC); 
				if (p!=null) { 
					sendIp(p);			// send one packet 
				} 
			} else {					// increment sendTimer; 
				timer++; 
				if (timer>IP_SEND) { 
					modemHangUp();		// start over 
					modemInit(); 
				} 
			} 
		} else {						// do the negotiation stuff 
			dropIp(); 
			++timer; 
			if (timer>NEG_SEND) { 
/* 
Dbg.intVal(state); 
if (lcpAck) Dbg.wr('t'); else Dbg.wr('f'); 
*/ 
				if (scnt==0) {			// once every two seconds send a REQ 
					if (state == MODEM_OK) { 
						makeLCP(); 
						state = LCP_SENT; 
					} else if (state == LCP_OK && lcpAck) { 
						makePAP(); 
						state = PAP_SENT; 
//					} else if (state == PAP_OK && ipcpAck) { // wait for remote ipcp and ACK first on Linux 
					} else if (state>=PAP_OK && state'); 
for (int i=0; i>>24); 
Dbg.intVal((ip>>>16)&0xff); 
Dbg.intVal((ip>>>8)&0xff); 
Dbg.intVal(ip&0xff); 
Dbg.wr('\n'); 
} 
 
	/** 
	*	generate a PPP (negotiation) request. 
	*/ 
 
	void makeLCP() { 
 
		lcpId = 0x22; 
Dbg.wr('L'); 
Dbg.intVal(lcpId); 
Dbg.wr('\n'); 
 
		sbuf[0] = 0xff; 
		sbuf[1] = 0x03; 
		//	REQ LCP options 2, 7, 8 
		sbuf[2] = LCP>>8; 
		sbuf[3] = LCP&0xff; 
		sbuf[4] = REQ; 
		sbuf[5] = lcpId; 
		sbuf[6] = 0; 
		sbuf[7] = 18-4;		// length including code, id and length field 
		sbuf[8] = 0x02;		// async-map 
		sbuf[9] = 0x06; 
		sbuf[10] = 0x00; 
		sbuf[11] = 0x0a; 
// sbuf[11] = 0x00;			// one does not like this 
		sbuf[12] = 0x00; 
		sbuf[13] = 0x00; 
		sbuf[14] = 0x07;	// protocol field compression 
		sbuf[15] = 0x02; 
		sbuf[16] = 0x08;	// addr., contr. field compression 
		sbuf[17] = 0x02; 
 
		checksum(18); 
	} 
 
	void makePAP() { 
 
		lcpId = 0x33; 
Dbg.wr('P'); 
Dbg.intVal(lcpId); 
Dbg.wr('\n'); 
 
/* compression 
		sbuf[0] = 0xff; 
		sbuf[1] = 0x03; 
*/ 
		sbuf[0] = PAP>>8; 
		sbuf[1] = PAP&0xff; 
		sbuf[2] = REQ; 
		sbuf[3] = lcpId; 
		sbuf[4] = 0; 
 
/* A1.net 
*/ 
		sbuf[5] = 24-2;		// length including code, id and length field 
		sbuf[6] = 13;		// length of user id 
		sbuf[7] = 'p'; 
		sbuf[8] = 'p'; 
		sbuf[9] = 'p'; 
		sbuf[10] = '@'; 
		sbuf[11] = 'A'; 
		sbuf[12] = '1'; 
		sbuf[13] = 'p'; 
		sbuf[14] = 'l'; 
		sbuf[15] = 'u'; 
		sbuf[16] = 's'; 
		sbuf[17] = '.'; 
		sbuf[18] = 'a'; 
		sbuf[19] = 't'; 
		sbuf[20] = 3;		// length of password 
		sbuf[21] = 'p'; 
		sbuf[22] = 'p'; 
		sbuf[23] = 'p'; 
 
		checksum(24); 
		// checksum(23); 
 
 
/* ONE 
		sbuf[7] = 30-4;		// length including code, id and length field 
		sbuf[8] = 14;		// length of user id 
		sbuf[9] = '+'; 
		sbuf[10] = '4'; 
		sbuf[11] = '3'; 
		sbuf[12] = '6'; 
		sbuf[13] = '9'; 
		sbuf[14] = '9'; 
		sbuf[15] = '1'; 
		sbuf[16] = '9'; 
		sbuf[17] = '5'; 
		sbuf[18] = '2'; 
		sbuf[19] = '0'; 
		sbuf[20] = '2'; 
		sbuf[21] = '2'; 
		sbuf[22] = '0'; 
		sbuf[23] = 6;		// length of password 
		sbuf[24] = 'N'; 
		sbuf[25] = '6'; 
		sbuf[26] = 'J'; 
		sbuf[27] = '8'; 
		sbuf[28] = 'N'; 
		sbuf[29] = '4'; 
 
		checksum(30); 
*/ 
 
	} 
 
	void makeIPCP() { 
 
		lcpId = 0x44; 
Dbg.wr('I'); 
Dbg.intVal(lcpId); 
Dbg.wr('\n'); 
 
/* compression 
		sbuf[0] = 0xff; 
		sbuf[1] = 0x03; 
*/ 
		sbuf[0] = IPCP>>8; 
		sbuf[1] = IPCP&0xff; 
		sbuf[2] = REQ; 
		sbuf[3] = lcpId; 
		sbuf[4] = 0; 
		sbuf[5] = 14-4;		// length including code, id and length field 
		sbuf[6] = 0x03;		// ip-address 0.0.0.0 
		sbuf[7] = 0x06; 
		sbuf[8] = ip>>>24; 
		sbuf[9] = (ip>>16)&0xff; 
		sbuf[10] = (ip>>8)&0xff; 
		sbuf[11] = ip&0xff; 
 
		// checksum(14); 
		checksum(12); 
	} 
 
	/** 
	*	process a LCP, IPCP request 
	*/ 
	boolean checkOptions(int type) { 
 
		int i; 
		int len = (rbuf[6]<<8) + rbuf[7] - 4;		// including code, id and lentgh 
 
		int ptr = 8; 
 
Dbg.wr('R'); 
Dbg.wr(' '); 
		int resp = ACK; 
 
		for (i=0; i 0) { 
			int opt = rbuf[ptr]; 
Dbg.intVal(opt); 
			if (type==LCP && opt==3) {				// auth. protocol 
				if ((rbuf[ptr+2]<<8) + rbuf[ptr+3] != PAP) { 
					resp = REJ; 
Dbg.wr('!'); 
Dbg.wr('P'); 
Dbg.wr(' '); 
				} 
			} else if (type==IPCP) { 
				if (opt==2) {						// IP-Compression 
					resp = REJ; 
				} else if (opt==3) {				// IP-address 
					ipRemote = (rbuf[ptr+2]<<24) + (rbuf[ptr+3]<<16) + 
						(rbuf[ptr+4]<<8) + rbuf[ptr+5]; 
Dbg.hexVal(ipRemote); 
dbgIp(ipRemote); 
				} 
			} 
			// } else if (opt==xx} 
			if (resp==REJ) { 
				int optlen = rbuf[ptr+1]; 
				slen = 4 + optlen; 
				for (i=0; i>>8; 
		sbuf[7] = slen&0xff; 
Dbg.wr('\n'); 
 
		checksum(slen+4); 
 
		return resp == ACK; 
	} 
 
/** 
*	get a Packet buffer and copy from receive buffer. 
*/ 
	void readIp() { 
 
		int i, j, k; 
 
		Packet p = Packet.getPacket(Packet.FREE, Packet.ALLOC, single); 
		if (p==null) { 
Dbg.wr('!'); 
			return;							// try again later 
		}									// buf blocks receive buffer :-<  
 
		int[] pb = p.buf; 
 
		cnt -= 6;							// minus ppp header and checksum 
 
		rbuf[cnt+4] = 0; 
		rbuf[cnt+4+1] = 0; 
		rbuf[cnt+4+2] = 0; 
 
		// copy buffer 
		k = 0; 
		for (i=0; i>>2] = k; 
		} 
 
		p.len = cnt; 
 
// Dbg.wr('s'); 
// Dbg.intVal(cnt); 
/* 
dbgIp(pb[3]); 
dbgIp(pb[4]); 
for (i=0; i<(cnt+4)>>2; ++i) Dbg.hexVal(pb[i]); 
Dbg.wr('\n'); 
*/ 
		cnt = 0; 
		ready = false; 
 
		p.setStatus(Packet.RCV);		// inform upper layer 
	} 
 
 
/** 
*	copy packet to send buffer. 
*/ 
	void sendIp(Packet p) { 
 
		int i, k; 
		int[] pb = p.buf; 
 
// Dbg.wr('S'); 
// Dbg.intVal(p.len); 
 
		sbuf[0] = 0xff; 
		sbuf[1] = 0x03; 
		sbuf[2] = IP>>8; 
		sbuf[3] = IP&0xff; 
 
		int slen = p.len; 
		sent = 0; 
		for (i=0; i>>2]; 
			sbuf[i+4] = k>>>24; 
			sbuf[i+4+1] = (k>>>16)&0xff; 
			sbuf[i+4+2] = (k>>>8)&0xff; 
			sbuf[i+4+3] = k&0xff; 
		} 
		p.setStatus(Packet.FREE);		// mark packet free 
 
		checksum(slen+4); 
	} 
 
/* warum geht das nicht !!!!! 
	private void loop() { 
*/ 
/** 
*	read from serial buffer and build a ppp packet. 
*	send a packet if one is in our send buffer. 
*/ 
	boolean loop() { 
 
		int i; 
		boolean ret = false; 
 
		i = Serial.rxCnt(); 
		if (i!=0 && !ready) { 
			ret = true; 
			rcv(i); 
		} 
		if (scnt!=0) { 
			i = Serial.txFreeCnt(); 
			if (i>2) {	 
				snd(i); 
			} 
		} 
 
		return ret; 
	} 
 
/** 
*	copy from send buffer to serial buffer with flags and escapes. 
*/ 
	void snd(int free) { 
 
		int i; 
 
		if (sent==0) { 
			Serial.wr('~'); 
			--free; 
		} 
 
		for (i=sent; free>1 && i= LCP_OK) { 			// hard code async map 
	if (c=='~' || c=='}' || c==17 || c==19) { // 0x000a0000 async map 
		Serial.wr('}'); 
		Serial.wr(c ^ ' '); 
		free -= 2; 
	} else { 
		Serial.wr(c); 
		--free; 
	} 
} else { 
*/ 
			if (c=='~' || c=='}' || c<0x20) {			// c<0x20 could be omitted after LCP async map 
				Serial.wr('}'); 
				Serial.wr(c ^ ' '); 
				free -= 2; 
			} else { 
				Serial.wr(c); 
				--free; 
			} 
/* 
} 
*/ 
		} 
		sent = i; 
 
		if (sent==scnt && free!=0) { 
			Serial.wr('~'); 
			scnt = 0; 
			sent = 0; 
		} 
	} 
 
	private static boolean escape; 
	private static int fcs; 
/** 
*	copy from serial buffer to receive buffer. 
*	calc CRC on the fly. 
*/ 
	void rcv(int len) { 
 
		int i; 
 
		if (cnt==0) fcs = 0xffff; 
 
		// get all bytes from serial buffer 
		for (i=0; i>8); 
 
		} 
/* 
Dbg.wr('r'); 
Dbg.intVal(cnt); 
Dbg.wr('\n'); 
*/ 
	} 
 
/** 
*	calculate CRC of byte c for checksum 
*/ 
	int check(int c) { 
 
		c &= 0xff; 
		for (int i=0; i<8; ++i) { 
			if ((c&1) != 0) { 
				c >>= 1;	 
				c ^= 0x8408; 
			} else { 
				c >>= 1; 
			} 
		} 
		return c; 
	} 
 
/** 
*	calculate CRC for send packet and mark it ready to send. 
*/ 
	void checksum(int len) { 
 
		int k, j, i; 
		int fcs = 0xffff; 
 
		for (i=0; i>= 1;	 
					j ^= 0x8408; 
				} else { 
					j >>= 1; 
				} 
			} 
			fcs = j ^ (fcs>>8); 
		} 
		fcs = fcs ^ 0xffff; 
		sbuf[len] = fcs & 0xff;		// LSB first ! 
		sbuf[len+1] = fcs >> 8; 
		scnt = len+2; 
 
if (state!=CONNECTED) { 
Dbg.wr('<'); 
for (i=0; i