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}