001package jmri.jmris.srcp; 002 003import java.io.BufferedReader; 004import java.io.DataInputStream; 005import java.io.DataOutputStream; 006import java.io.IOException; 007import java.io.InputStreamReader; 008import java.nio.charset.StandardCharsets; 009 010import jmri.jmris.JmriServer; 011import jmri.jmris.srcp.parser.ParseException; 012import jmri.jmris.srcp.parser.SRCPParser; 013import jmri.jmris.srcp.parser.SRCPVisitor; 014import jmri.jmris.srcp.parser.SimpleNode; 015import jmri.jmris.srcp.parser.TokenMgrError; 016import jmri.util.zeroconf.ZeroConfService; 017import org.slf4j.Logger; 018import org.slf4j.LoggerFactory; 019 020/** 021 * This is an implementation of SRCP for JMRI. 022 * 023 * @author Paul Bender Copyright (C) 2009 024 * 025 */ 026public class JmriSRCPServer extends JmriServer { 027 028 // Create a new server using the default port 029 public JmriSRCPServer() { 030 this(4303); // 4303 is assigned to SRCP by IANA. 031 } 032 033 public JmriSRCPServer(int port){ 034 super(port); 035 } 036 037 // Advertise the service with ZeroConf 038 @Override 039 protected void advertise() { 040 service = ZeroConfService.create("_srcp._tcp.local.", portNo); 041 service.publish(); 042 } 043 044 // Handle communication to a client through inStream and outStream 045 @Override 046 public void handleClient(DataInputStream inStream, DataOutputStream outStream) throws IOException { 047 // Listen for commands from the client until the connection closes 048 SRCPParser parser = null; 049 TimeStampedOutput outputStream = new TimeStampedOutput(outStream); 050 051 // interface components 052 JmriSRCPServiceHandler sh = new JmriSRCPServiceHandler(12345); // need real client port. 053 sh.setPowerServer(new JmriSRCPPowerServer(outputStream)); 054 sh.setTurnoutServer(new JmriSRCPTurnoutServer(inStream, outputStream)); 055 sh.setSensorServer(new JmriSRCPSensorServer(inStream, outputStream)); 056 sh.setProgrammerServer(new JmriSRCPProgrammerServer(outputStream)); 057 sh.setTimeServer(new JmriSRCPTimeServer(outputStream)); 058 sh.setThrottleServer(new JmriSRCPThrottleServer(inStream,outputStream)); 059 060 // Start by sending a welcome message 061 outputStream.write( "SRCP 0.8.3\n\r".getBytes()); 062 063 while (true) { 064 // Read the command from the client 065 066 if (!(sh.getRunMode())) { 067 // we start in handshake mode. 068 if (parser == null) { 069 parser = new SRCPParser(inStream); 070 } 071 try { 072 SimpleNode e = parser.handshakecommand(); 073 SRCPVisitor v = new SRCPVisitor(); 074 e.jjtAccept(v, sh); 075 // for simple tasks, we're letting the visitor 076 // generate the response. If this happens, we 077 // need to send the message out. 078 if (v.getOutputString() != null) { 079 outputStream.write((v.getOutputString() + "\n\r").getBytes()); 080 } 081 } catch (ParseException pe) { 082 log.debug("Parse Exception", pe); 083 jmri.jmris.srcp.parser.Token t = parser.getNextToken(); 084 if (t.kind == jmri.jmris.srcp.parser.SRCPParserConstants.EOF) { 085 // the input ended. 086 if (log.isDebugEnabled()) { 087 log.debug("Closing connection due to close of input stream"); 088 } 089 outputStream.close(); 090 inStream.close(); 091 return; 092 } 093 outputStream.write("425 ERROR not supported\n\r".getBytes()); 094 // recover by consuming tokens in the token stream 095 // until we reach the end of the line. 096 while (t.kind != jmri.jmris.srcp.parser.SRCPParserConstants.EOL) { 097 t = parser.getNextToken(); 098 } 099 } 100 } else if (sh.isCommandMode()) { 101 102 if (parser == null) { 103 parser = new SRCPParser(inStream); 104 } 105 try { 106 SimpleNode e = parser.command(); 107 SRCPVisitor v = new SRCPVisitor(); 108 e.jjtAccept(v, sh); 109 // for simple tasks, we're letting the visitor 110 // generate the response. If this happens, we 111 // need to send the message out. 112 if (v.getOutputString() != null) { 113 outputStream.write((v.getOutputString() + "\n\r").getBytes()); 114 } 115 } catch (ParseException pe) { 116 log.debug("Parse Exception", pe); 117 jmri.jmris.srcp.parser.Token t = parser.getNextToken(); 118 if (t.kind == jmri.jmris.srcp.parser.SRCPParserConstants.EOF) { 119 // the input ended. The parser may have prepared 120 // an output string to return (if the client issued 121 // a "TERM 0 SESSION" request). 122 //if(v.getOutputString()!=null) 123 // TimeStampedOutput.writeTimestamp(outputStream,v.getOutputString()+"\n\r"); 124 // and we can close the connection. 125 log.debug("Closing connection due to close of input stream"); 126 outputStream.close(); 127 inStream.close(); 128 return; 129 } 130 outputStream.write(("425 ERROR not supported\n\r").getBytes()); 131 // recover by consuming tokens in the token stream 132 // until we reach the end of the line. 133 while (t.kind != jmri.jmris.srcp.parser.SRCPParserConstants.EOL) { 134 t = parser.getNextToken(); 135 } 136 } catch (TokenMgrError tme) { 137 log.debug("Token Manager Exception", tme); 138 outputStream.write("410 ERROR unknown command\n\r".getBytes()); 139 } 140 } else if (!sh.isCommandMode()) { 141 BufferedReader d = new BufferedReader(new InputStreamReader(inStream, 142 StandardCharsets.UTF_8)); 143 try { 144 String cmd = d.readLine(); 145 if (cmd != null) { 146 log.debug("Received from client: {}",cmd); 147 // input commands are ignored in INFOMODE. 148 } else { 149 // close the input stream. 150 d.close(); 151 inStream.close(); 152 } 153 } catch (java.io.IOException ioe) { 154 // we don't care if there is an error on input. 155 } 156 } else { 157 outputStream.write("500 ERROR out of resources\n\r".getBytes()); 158 outputStream.close(); 159 inStream.close(); 160 return; 161 } 162 } 163 } 164 165 private static final Logger log = LoggerFactory.getLogger(JmriSRCPServer.class); 166}