/*
* 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;
/*
* Changelog:
* 2002-10-24 creation.
* 2009-03-22 Object oriented version
*
*
* TODO: use a source port as TID.
* timeout and resend or cancel connection.
*
*/
import util.Amd;
import util.Timer;
/**
* Tftp.java: A simple TFTP Server. see rfc1350.
*/
public class Tftp implements UdpHandler {
public static final int PORT = 69;
public static final int IDLE = 0;
public static final int RRQ = 1;
public static final int WRQ = 2;
public static final int DAT = 3;
public static final int ACK = 4;
public static final int ERR = 5;
/**
* Set to true to simulate some lost packets
*/
public static final boolean SIM_ERR = false;
static final int errorInterval = 71;
private int state;
protected int fn;
private int endBlock;
private int block;
private int last_block;
private int simerr;
private int block_out;
private int timeout;
private int time;
private int srcIp, dstIp, dstPort;
private LinkLayer ipLink;
private Ejip ejip;
public Tftp(Ejip ejipRef) {
ejip = ejipRef;
tftpInit();
}
public void tftpInit() {
state = IDLE;
last_block = 0;
block = 0;
fn = 0;
block_out = 0;
timeout = 0;
}
/** drop the packet end reset state */
public void discard(Packet p) {
p.len = 0;
tftpInit();
}
public void onTheFly(int block) {
block_out = block;
time = 4;
timeout = Timer.getTimeoutSec(time);
}
public void loop() {
if (block_out != 0) {
if (Timer.timeout(timeout)) {
resend();
}
}
}
private void resend() {
time <<= 1;
if (time > 64) {
if (Logging.LOG)
Logging.wr("TFTP give up");
tftpInit();
return;
}
if (Logging.LOG)
Logging.wr("TFTP resend ");
if (Logging.LOG)
Logging.intVal(block_out);
// retransmit DATA
timeout = Timer.getTimeoutSec(time);
Packet p = ejip.getFreePacket(ipLink);
if (p == null) { // got no free buffer!
if (Logging.LOG)
Logging.wr('!');
if (Logging.LOG)
Logging.wr('b');
return;
}
p.buf[Udp.DATA] = (DAT << 16) + block_out;
if (block_out == endBlock) {
p.len = Udp.DATA * 4 + 4; // last block is zero length
} else {
read(p.buf, block_out);
p.len = Udp.DATA * 4 + 4 + 512;
}
Udp.build(p, srcIp, dstIp, dstPort);
}
/**
* handle the TFTP packets.
*
* filename is fixed length (2): 'ix' internal memory (read only) 'f0'..'f8'
* flash sector (64 KB)
*
*/
public void request(Packet p) {
int i;
int[] buf = p.buf;
if (Logging.LOG)
Logging.wr('F');
if (Logging.LOG)
Logging.hexVal(buf[Udp.DATA]);
int op = buf[Udp.DATA] >>> 16;
if (SIM_ERR) {
++simerr;
if (simerr % errorInterval == 0) {
if (Logging.LOG) {
Logging.wr(" tftp dropped");
Logging.lf();
}
ejip.returnPacket(p); // mark packet free return;
return;
}
}
if (op == RRQ) {
state = RRQ;
fn = buf[Udp.DATA] & 0xffff;
i = fn >> 8;
if (i == 'i') {
endBlock = 2 + 1; // (256*4)/512
} else if (i == 'f') {
endBlock = 128 + 1; // 64K/512
} else {
endBlock = 1 + 1;
}
block = 1;
buf[Udp.DATA] = (DAT << 16) + block;
read(buf, block);
p.len = Udp.DATA * 4 + 4 + 512;
onTheFly(block);
} else if (op == ACK) {
i = (buf[Udp.DATA] & 0xffff); // get block number
if (i < block) {
// a ACK for an already sent package
// drop it
ejip.returnPacket(p); // mark packet free
return;
}
block = i + 1; // use one higher then last acked block
if (block > endBlock) { // ACK of last block
discard(p);
} else {
buf[Udp.DATA] = (DAT << 16) + block;
if (block == endBlock) {
p.len = Udp.DATA * 4 + 4; // last block is zero length
} else {
read(buf, block);
p.len = Udp.DATA * 4 + 4 + 512;
}
onTheFly(block);
if (SIM_ERR) {
++simerr;
if (simerr % errorInterval == 0) {
if (Logging.LOG) {
Logging.wr(" simulate wrong data on read ");
Logging.lf();
}
buf[Udp.DATA+13] = 0x12345678;
}
}
}
} else if (op == WRQ) {
state = WRQ;
fn = buf[Udp.DATA] & 0xffff;
block = 1;
buf[Udp.DATA] = (ACK << 16);
p.len = Udp.DATA * 4 + 4;
} else if (op == DAT) {
i = (buf[Udp.DATA] & 0xffff); // get block number
if (state == IDLE) {
if (i == last_block) {
// ACK of last data block got lost,
// but we received the data and finished programming
buf[Udp.DATA] = (ACK << 16) + last_block; // just ack it
p.len = Udp.DATA * 4 + 4; // we have already received it
// before
}
} else if (state != WRQ) {
discard(p);
} else if (block != i) { // not the expected block
// is it a second write with the old block number?
if (block - 1 == i) {
buf[Udp.DATA] = (ACK << 16) + i; // just ack it
p.len = Udp.DATA * 4 + 4; // we have already received it
// before
} else {
p.len = 0; // else just discard packet
}
} else {
// if (Logging.LOG) Logging.wr('a');
// if (Logging.LOG) Logging.intVal(block);
if (p.len > Udp.DATA * 4 + 4) {
write(buf, block);
}
buf[Udp.DATA] = (ACK << 16) + block;
boolean last = p.len != Udp.DATA * 4 + 4 + 512;
p.len = Udp.DATA * 4 + 4;
++block;
if (last) { // end of write
eof(block - 1);
tftpInit();
// remember very last written block
last_block = block - 1;
}
}
} else {
p.len = 0;
tftpInit();
if (Logging.LOG)
Logging.wr("error ");
}
if (p.len == 0) {
ejip.returnPacket(p); // mark packet free
} else {
reply(p);
}
}
// TODO: insert reply back to request or split in smaller methods
public void reply(Packet p) {
if (SIM_ERR) {
++simerr;
if (simerr % errorInterval == 0) {
if (Logging.LOG) {
Logging.wr("reply dropped ");
Logging.lf();
}
ejip.returnPacket(p); // mark packet free
return;
}
}
int[] buf = p.buf;
if (Logging.LOG)
Logging.wr("tftp reply: ");
if (Logging.LOG)
Logging.hexVal(buf[Udp.DATA] & 0xffff);
// generate a reply with IP src/dst exchanged
dstPort = buf[Udp.HEAD] >>> 16;
srcIp = buf[4];
dstIp = buf[3];
ipLink = p.getLinkLayer();
Udp.build(p, srcIp, dstIp, dstPort);
}
/**
* Invoked on each received block. Program the Flash in default
* implementation.
*/
protected void write(int[] buf, int block) {
int i;
int base;
block--;
i = fn >> 8;
Timer.wd(); // toggle for each block?
if (i == 'f') { // program flash
base = (block << 9);
base += ((fn & 0xff) - '0') << 16; // 64 KB sector
if ((base & 0xffff) == 0) {
Amd.erase(base);
}
progloop(base, buf);
}
}
/**
* Invoked at end of write
*
* @param i
* block number of last block
*/
protected void eof(int i) {
// nothing to do in default implementation
}
private void progloop(int base, int[] buf) {
int i, w;
for (i = 1; i < 129; ++i) {
w = buf[Udp.DATA + i];
Amd.program(base, w >>> 24);
Amd.program(base + 1, w >>> 16);
Amd.program(base + 2, w >>> 8);
Amd.program(base + 3, w);
base += 4;
}
}
/**
* read internal memory or flash.
*/
public void read(int[] buf, int block) {
int i, j, k;
int base;
block--;
i = fn >> 8;
if (i == 'i') { // read internal memory
base = block << 7;
for (i = 0; i < 128; ++i) { // @WCA loop<=128
buf[Udp.DATA + 1 + i] = com.jopdesign.sys.Native.rdIntMem(base
+ i);
}
} else if (i == 'f') { // read flash
base = (block << 9) + 0x80000; // add offset because we use
// Native.rdMem!
base += ((fn & 0xff) - '0') << 16; // 64 KB sector
k = 0;
for (i = 0; i < 128; ++i) { // @WCA loop<=128
synchronized (this) {
for (j = 0; j < 4; ++j) { // @WCA loop<=4
k <<= 8;
k += com.jopdesign.sys.Native
.rdMem(base + (i << 2) + j);
}
}
buf[Udp.DATA + 1 + i] = k;
}
} else { // read nothing
for (i = 0; i < 128; ++i) { // @WCA loop<=128
buf[Udp.DATA + 1 + i] = 0;
}
}
}
}