001/*
002 * jPOS Project [http://jpos.org]
003 * Copyright (C) 2000-2026 jPOS Software SRL
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jpos.iso.channel;
020
021import org.jpos.core.Configuration;
022import org.jpos.core.ConfigurationException;
023import org.jpos.iso.*;
024import org.jpos.iso.header.BASE1Header;
025import org.jpos.util.LogEvent;
026import org.jpos.util.Logger;
027
028import java.io.IOException;
029import java.net.ServerSocket;
030
031/**
032 * ISOChannel implementation - VISA's VAP framing
033 *
034 * @author apr@cs.com.uy
035 * @version $Id$
036 * @see ISOMsg
037 * @see ISOException
038 * @see ISOChannel
039 */
040@SuppressWarnings("unused")
041public class VAPChannel extends BaseChannel {
042    String srcid = "000000";
043    String dstid = "000000";
044    boolean debugPoll;
045    int headerFormat = 2;
046    private boolean replyKeepAlive = true;
047    private boolean swapDirection = false;
048
049    /**
050     * Public constructor (used by Class.forName("...").newInstance())
051     */
052    public VAPChannel () {
053        super();
054    }
055    /**
056     * Construct client ISOChannel
057     * @param host  server TCP Address
058     * @param port  server port number
059     * @param p     an ISOPackager (should be ISO87BPackager)
060     * @see org.jpos.iso.packager.ISO87BPackager
061     */
062    public VAPChannel (String host, int port, ISOPackager p) {
063        super(host, port, p);
064    }
065    /**
066     * Construct server ISOChannel
067     * @param p     an ISOPackager (should be ISO87BPackager)
068     * @exception IOException
069     * @see org.jpos.iso.packager.ISO87BPackager
070     */
071    public VAPChannel (ISOPackager p) throws IOException {
072        super(p);
073    }
074    /**
075     * constructs a server ISOChannel associated with a Server Socket
076     * @param p     an ISOPackager
077     * @param serverSocket where to accept a connection
078     * @exception IOException
079     * @see ISOPackager
080     */
081    public VAPChannel (ISOPackager p, ServerSocket serverSocket) 
082        throws IOException
083    {
084        super(p, serverSocket);
085    }
086    public void setSrcId (String srcid) {
087        this.srcid = srcid;
088    }
089    public String getSrcId () {
090        return srcid;
091    }
092    public void setDstId (String dstid) {
093        this.dstid = dstid;
094    }
095    public String getDstId () {
096        return dstid;
097    }
098    /**
099     * The default header for VAPChannel is BASE1Header
100     */
101    protected ISOHeader getDynamicHeader (byte[] image) {
102        return new BASE1Header(image);
103    }
104        
105    /**
106     * This method reads in a Base 1 Header.
107     *
108     * @param hLen
109     * @throws IOException
110     */
111    protected byte[] readHeader(int hLen)
112        throws IOException {
113        // Read the first byte of the header (header length)
114        int b = serverIn.read();
115        int bytesRead = b;
116
117        if (b != -1)
118        {
119            // Create am array to read the header into
120            byte bytes[] = new byte[b];
121            // Stick the first byte in
122            bytes[0] = (byte) b;
123            // and read the rest of the header
124            serverIn.readFully(bytes, 1, b - 1);
125
126            // Is there another header following on
127            if ((bytes[1] & 0x80) == 0x80)
128            {
129                b = serverIn.read();
130                bytesRead += b;
131
132                // Create an array big enough for both headers
133                byte tmp[] = new byte[bytesRead];
134                                
135                // Copy in the original
136                System.arraycopy(bytes, 0, tmp, 0, bytes.length);
137
138                // And this one
139                tmp[bytes.length] = (byte) b;
140                serverIn.readFully(tmp, bytes.length + 1, b - 1);
141                bytes = tmp;
142            }
143            return bytes;
144        }
145        else
146        {
147            throw new IOException("Error reading header");
148        }
149    }
150        
151    protected void sendMessageLength(int len) throws IOException {
152        serverOut.write (len >> 8);
153        serverOut.write (len);
154        serverOut.write (0);
155        serverOut.write (0);
156    }
157    /**
158     * @param   m   the message
159     * @param   len already packed message len (to avoid re-pack)
160     * @exception IOException
161     */
162    protected void sendMessageHeader(ISOMsg m, int len) 
163        throws IOException
164    {
165        ISOHeader h = !isOverrideHeader() && m.getHeader() != null ?
166                m.getISOHeader() :
167                new BASE1Header (srcid, dstid, headerFormat);
168
169        if (h instanceof BASE1Header)
170            ((BASE1Header)h).setLen(len);
171
172        serverOut.write(h.pack());
173    }
174    protected int getMessageLength() throws IOException, ISOException {
175        int l = 0;
176        byte[] b = new byte[4];
177        // ignore VAP polls (0 message length)
178        while (l == 0) {
179            serverIn.readFully(b,0,4);
180            l = ((int)b[0] &0xFF) << 8 | (int)b[1] &0xFF;
181
182            if (replyKeepAlive && l == 0) {
183                try {
184                    serverOutLock.lock();
185                    serverOut.write(b);
186                    serverOut.flush();
187                    if (debugPoll)
188                        Logger.log(new LogEvent(this, "poll"));
189                } finally {
190                    serverOutLock.unlock();
191                }
192            }
193        }
194        return l;
195    }
196
197    protected int getHeaderLength() {
198        return BASE1Header.LENGTH;
199    }
200        
201    protected boolean isRejected(byte[] b) {
202        BASE1Header h = new BASE1Header(b);
203        return h.isRejected() || h.getHLen() != BASE1Header.LENGTH;
204    }
205
206    protected boolean shouldIgnore (byte[] b) {
207        if (b != null) {
208            BASE1Header h = new BASE1Header(b);
209            return h.getFormat() > 2;
210        }
211        return false;
212    }
213
214
215    /**
216     * sends an ISOMsg over the TCP/IP session.
217     *
218     * swap source/destination addresses in BASE1Header if
219     * a reply message is detected.<br>
220     * Sending an incoming message is seen as a reply.
221     *
222     * @param m the Message to be sent
223     * @exception IOException
224     * @exception ISOException
225     * @see ISOChannel#send
226     */
227    public void send (ISOMsg m) throws IOException, ISOException
228    {
229        if (m.isIncoming() && m.getHeader() != null && swapDirection) {
230            m.getISOHeader().swapDirection();
231        }
232        super.send(m);
233    }
234
235    public void setConfiguration (Configuration cfg)
236        throws ConfigurationException 
237    {
238        super.setConfiguration (cfg);
239        srcid = cfg.get ("srcid", "000000");
240        dstid = cfg.get ("dstid", "000000");
241        debugPoll = cfg.getBoolean("debug-poll", false);
242        headerFormat = cfg.getInt("header-format", 2);
243        replyKeepAlive = cfg.getBoolean("reply-keepalive", true);
244        swapDirection = cfg.getBoolean("swap-direction", true);
245    }
246}