package hep.io.root.daemon;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author Tony Johnson
*/
public class RootProtocol
{
public final static int defaultPort = 1094;
//---- ROOTD message opcodes (2000 - 2099)
private final static int kROOTD_USER = 2000; //user id follows
private final static int kROOTD_PASS = 2001; //passwd follows
private final static int kROOTD_AUTH = 2002; //authorization status (to client)
private final static int kROOTD_FSTAT = 2003; //filename follows
private final static int kROOTD_OPEN = 2004; //filename follows + mode
private final static int kROOTD_PUT = 2005; //offset, number of bytes and buffer
private final static int kROOTD_GET = 2006; //offset, number of bytes
private final static int kROOTD_FLUSH = 2007; //flush file
private final static int kROOTD_CLOSE = 2008; //close file
private final static int kROOTD_STAT = 2009; //return rootd statistics
private final static int kROOTD_ACK = 2010; //acknowledgement (all OK)
private final static int kROOTD_ERR = 2011; //error code and message follow
private final static int kROOTD_PROTOCOL = 2012; //returns rootd protocol
private final static int kROOTD_SRPUSER = 2013; //user id for SRP authentication follows
private final static int kROOTD_SRPN = 2014; //SRP n follows
private final static int kROOTD_SRPG = 2015; //SRP g follows
private final static int kROOTD_SRPSALT = 2016; //SRP salt follows
private final static int kROOTD_SRPA = 2017; //SRP a follows
private final static int kROOTD_SRPB = 2018; //SRP b follows
private final static int kROOTD_SRPRESPONSE = 2019; //SRP final response
private final static int kROOTD_PUTFILE = 2020; //store file
private final static int kROOTD_GETFILE = 2021; //retrieve file
private final static int kROOTD_CHDIR = 2022; //change directory
private final static int kROOTD_MKDIR = 2023; //make directory
private final static int kROOTD_RMDIR = 2024; //delete directory
private final static int kROOTD_LSDIR = 2025; //list directory
private final static int kROOTD_PWD = 2026; //pwd
private final static int kROOTD_MV = 2027; //rename file
private final static int kROOTD_RM = 2028; //delete file
private final static int kROOTD_CHMOD = 2029; //change permission
private final static int kROOTD_KRB5 = 2030; //krb5 authentication follows
private final static int kROOTD_PROTOCOL2 = 2031; //client proto follows, returns rootd proto
private final static int kROOTD_BYE = 2032; //terminate rootd
private final static int kROOTD_GLOBUS = 2033; //Globus authetication follows
private final static int kROOTD_CLEANUP = 2034; //Cleanup things
private final static int kROOTD_SSH = 2035; //SSH-like authentication follows
private final static int kROOTD_RFIO = 2036; //RFIO-like authentication follows
private final static int kROOTD_NEGOTIA = 2037; //Negotiation follows
private final static int kROOTD_RSAKEY = 2038; //RSA public key exchange
private final static int kROOTD_ENCRYPT = 2039; //An encrypted message follows
private int bufferSize = 8096;
private static int MAXGETSIZE = -1;
private static Logger logger = Logger.getLogger("hep.io.root.daemon");
static
{
if (System.getProperty("debugRootDaemon")!= null)
{
logger.setLevel(Level.FINE);
ConsoleHandler handler = new ConsoleHandler();
handler.setLevel(Level.FINE);
logger.addHandler(handler);
}
}
private Socket socket;
private Message message;
private Response response;
/** Creates a new instance of RootProtocol */
public RootProtocol(String host, int port, String auth, String username, String password) throws IOException
{
if (port == -1) port = defaultPort;
logger.fine("Opening rootd connection to: "+host+":"+port);
socket = new Socket(host,port);
socket.setSoTimeout(Integer.getInteger("root.timeout",10000).intValue());
OutputStream out = socket.getOutputStream();
DataOutputStream data = new DataOutputStream(out);
logger.fine("Sending welcome");
data.writeInt(0);
data.writeInt(0);
data.writeInt(0);
data.flush();
message = new Message(data);
InputStream in = socket.getInputStream();
DataInputStream dataIn = new DataInputStream(in);
response = new Response(dataIn);
message.send(kROOTD_PROTOCOL);
int rc = response.read();
if (response.dataAsInt() < 9) throw new IOException("Unexpected response: "+rc+" "+response.dataAsInt());
message.send(kROOTD_PROTOCOL2,"9");
if (response.dataAsInt() < 9) throw new IOException("Unexpected response: "+rc+" "+response.dataAsInt());
rc = response.read();
String user = "9999 -1 5 "+username.length()+" "+username;
message.send(kROOTD_USER,user);
rc = response.read();
message.send(kROOTD_PASS,password,true);
do
{
rc = response.read();
}
while (rc != kROOTD_AUTH);
}
public int getBufferSize()
{
return this.bufferSize;
}
public void setBufferSize(int bufferSize)
{
this.bufferSize = bufferSize;
}
public String[] fstat() throws IOException
{
message.send(kROOTD_FSTAT);
int rc = response.read();
return response.dataAsString().split("\\s+");
}
public void open(String file) throws IOException
{
message.send(kROOTD_OPEN,file+" r");
int rc = response.read();
}
public DaemonInputStream openStream(long size)
{
return new RootStream(size);
}
public String[] ls(String dir) throws IOException
{
message.send(kROOTD_LSDIR,dir);
StringBuffer result = new StringBuffer();
for (;;)
{
int rc = response.read();
result.append(response.dataAsString());
if (rc == kROOTD_LSDIR) break;
}
return result.toString().split("\n");
}
public String pwd() throws IOException
{
message.send(kROOTD_PWD);
int rc = response.read();
return response.dataAsString();
}
private static class Message
{
private DataOutputStream data;
Message(DataOutputStream data)
{
this.data = data;
}
void send(int message) throws IOException
{
data.writeInt(4);
data.writeInt(message);
data.flush();
}
void send(int message, String extra) throws IOException
{
send(message,extra,false);
}
void send(int message, String extra, boolean invert) throws IOException
{
logger.fine("->"+message+" "+extra);
byte[] bytes = extra.getBytes();
if (invert)
{
for (int i=0; i<bytes.length; i++)
{
bytes[i] = (byte) ~bytes[i];
}
}
data.writeInt(4+bytes.length+1);
data.writeInt(message);
data.write(bytes);
data.write(0);
data.flush();
}
}
private static class Response
{
private DataInputStream in;
private int code;
private int dataLength;
private byte[] data;
Response(DataInputStream in)
{
this.in = in;
}
int read() throws IOException
{
dataLength = in.readInt() - 4;
code = in.readInt();
logger.fine("<-"+code+" ("+dataLength+") ");
if (data == null || dataLength>data.length) data = new byte[dataLength];
for (int i=0; i<dataLength; i++)
{
data[i] = in.readByte();
}
if (code == kROOTD_ERR) throw new RootdException(this);
return code;
}
int read(byte[] values, int offset, int size) throws IOException
{
read();
int n = size;
while (n>0)
{
int k = in.read(values,offset,n);
if (k<0) throw new IOException("Unexpected end of input");
n -= k;
offset += k;
}
return size;
}
int dataAsInt()
{
int l = data[0] << 24;
l += data[1] << 16;
l += data[2] << 8;
l += data[3];
return l;
}
String dataAsString()
{
int l = dataLength;
for (int i=0; i<l; i++)
{
if (data[i] == 0)
{
l = i;
break;
}
}
return new String(data,0,l);
}
}
private static class RootdException extends IOException
{
RootdException(Response response)
{
super("Root Deamon exception: "+response.dataAsInt());
}
}
private class RootStream extends DaemonInputStream
{
private byte[] buffer = new byte[bufferSize];
private int bpos = 0;
private int blen = 0;
private long fsize;
RootStream(long fsize)
{
this.fsize = fsize;
}
long getSize()
{
return fsize;
}
public int read() throws IOException
{
if (bpos >= blen)
{
if (!fillBuffer()) return -1;
}
int i = buffer[bpos++];
if (i < 0) i += 256;
return i;
}
public void close() throws IOException
{
message.send(kROOTD_CLOSE);
//response.read();
socket.close();
}
public int read(byte[] values, int offset, int size) throws IOException
{
if (bpos >= blen)
{
long position = this.position+bpos;
if (position >= fsize) return -1;
long n = Math.min(fsize-position,size);
if (MAXGETSIZE > 0 && n > MAXGETSIZE) n = MAXGETSIZE;
String where = position+" "+n;
message.send(kROOTD_GET,where);
int l = response.read(values,offset,(int) n);
this.position += l;
return l;
}
else
{
int l = Math.min(size,blen-bpos);
System.arraycopy(buffer, bpos, values, offset, l);
bpos += l;
return l;
}
}
public long skip(long skip) throws IOException
{
setPosition(getPosition()+skip);
return skip;
}
public void setPosition(long pos)
{
if (pos>position && pos<position+blen)
{
bpos = (int) (pos-position);
}
else
{
blen = 0;
bpos = 0;
super.setPosition(pos);
}
}
public int available() throws IOException
{
return blen - bpos;
}
private boolean fillBuffer() throws IOException
{
position += bpos;
bpos = 0;
long n = Math.min(fsize-position,buffer.length);
if (n <= 0) return false;
if (MAXGETSIZE > 0 && n > MAXGETSIZE) n = MAXGETSIZE;
String where = position+" "+n;
message.send(kROOTD_GET,where);
blen = response.read(buffer,0,(int) n);
return true;
}
public long getPosition()
{
return position + bpos;
}
}
public static void main(String[] args) throws IOException
{
RootProtocol rp = new RootProtocol("london.jaws.com",-1,"anonymous","tonyj@slac",null);
System.out.println(rp.pwd());
String[] result = rp.ls("/bin/ls -F");
for (int i=0; i<result.length; i++) System.out.println(result[i]);
}
}