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 on I/O error
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 on I/O error
079     * @see ISOPackager
080     */
081    public VAPChannel (ISOPackager p, ServerSocket serverSocket) 
082        throws IOException
083    {
084        super(p, serverSocket);
085    }
086    /**
087     * Sets the source identifier carried in the BASE1 header.
088     *
089     * @param srcid source id
090     */
091    public void setSrcId (String srcid) {
092        this.srcid = srcid;
093    }
094    /**
095     * Returns the configured source identifier.
096     *
097     * @return source id
098     */
099    public String getSrcId () {
100        return srcid;
101    }
102    /**
103     * Sets the destination identifier carried in the BASE1 header.
104     *
105     * @param dstid destination id
106     */
107    public void setDstId (String dstid) {
108        this.dstid = dstid;
109    }
110    /**
111     * Returns the configured destination identifier.
112     *
113     * @return destination id
114     */
115    public String getDstId () {
116        return dstid;
117    }
118    /**
119     * The default header for VAPChannel is BASE1Header
120     */
121    protected ISOHeader getDynamicHeader (byte[] image) {
122        return new BASE1Header(image);
123    }
124        
125    /**
126     * This method reads in a Base 1 Header.
127     *
128     * @param hLen requested header length (ignored — the actual length is read from the wire)
129     * @return the header bytes read from the wire
130     * @throws IOException on I/O error
131     */
132    protected byte[] readHeader(int hLen)
133        throws IOException {
134        // Read the first byte of the header (header length)
135        int b = serverIn.read();
136        int bytesRead = b;
137
138        if (b != -1)
139        {
140            // Create am array to read the header into
141            byte bytes[] = new byte[b];
142            // Stick the first byte in
143            bytes[0] = (byte) b;
144            // and read the rest of the header
145            serverIn.readFully(bytes, 1, b - 1);
146
147            // Is there another header following on
148            if ((bytes[1] & 0x80) == 0x80)
149            {
150                b = serverIn.read();
151                bytesRead += b;
152
153                // Create an array big enough for both headers
154                byte tmp[] = new byte[bytesRead];
155                                
156                // Copy in the original
157                System.arraycopy(bytes, 0, tmp, 0, bytes.length);
158
159                // And this one
160                tmp[bytes.length] = (byte) b;
161                serverIn.readFully(tmp, bytes.length + 1, b - 1);
162                bytes = tmp;
163            }
164            return bytes;
165        }
166        else
167        {
168            throw new IOException("Error reading header");
169        }
170    }
171        
172    protected void sendMessageLength(int len) throws IOException {
173        serverOut.write (len >> 8);
174        serverOut.write (len);
175        serverOut.write (0);
176        serverOut.write (0);
177    }
178    /**
179     * @param   m   the message
180     * @param   len already packed message len (to avoid re-pack)
181     * @exception IOException on I/O error
182     */
183    protected void sendMessageHeader(ISOMsg m, int len) 
184        throws IOException
185    {
186        ISOHeader h = !isOverrideHeader() && m.getHeader() != null ?
187                m.getISOHeader() :
188                new BASE1Header (srcid, dstid, headerFormat);
189
190        if (h instanceof BASE1Header)
191            ((BASE1Header)h).setLen(len);
192
193        serverOut.write(h.pack());
194    }
195    protected int getMessageLength() throws IOException, ISOException {
196        int l = 0;
197        byte[] b = new byte[4];
198        // ignore VAP polls (0 message length)
199        while (l == 0) {
200            serverIn.readFully(b,0,4);
201            l = ((int)b[0] &0xFF) << 8 | (int)b[1] &0xFF;
202
203            if (replyKeepAlive && l == 0) {
204                try {
205                    serverOutLock.lock();
206                    serverOut.write(b);
207                    serverOut.flush();
208                    if (debugPoll)
209                        Logger.log(new LogEvent(this, "poll"));
210                } finally {
211                    serverOutLock.unlock();
212                }
213            }
214        }
215        return l;
216    }
217
218    protected int getHeaderLength() {
219        return BASE1Header.LENGTH;
220    }
221        
222    protected boolean isRejected(byte[] b) {
223        BASE1Header h = new BASE1Header(b);
224        return h.isRejected() || h.getHLen() != BASE1Header.LENGTH;
225    }
226
227    protected boolean shouldIgnore (byte[] b) {
228        if (b != null) {
229            BASE1Header h = new BASE1Header(b);
230            return h.getFormat() > 2;
231        }
232        return false;
233    }
234
235
236    /**
237     * sends an ISOMsg over the TCP/IP session.
238     *
239     * swap source/destination addresses in BASE1Header if
240     * a reply message is detected.<br>
241     * Sending an incoming message is seen as a reply.
242     *
243     * @param m the Message to be sent
244     * @exception IOException on I/O error
245     * @exception ISOException on pack/unpack error
246     * @see ISOChannel#send
247     */
248    public void send (ISOMsg m) throws IOException, ISOException
249    {
250        if (m.isIncoming() && m.getHeader() != null && swapDirection) {
251            m.getISOHeader().swapDirection();
252        }
253        super.send(m);
254    }
255
256    public void setConfiguration (Configuration cfg)
257        throws ConfigurationException 
258    {
259        super.setConfiguration (cfg);
260        srcid = cfg.get ("srcid", "000000");
261        dstid = cfg.get ("dstid", "000000");
262        debugPoll = cfg.getBoolean("debug-poll", false);
263        headerFormat = cfg.getInt("header-format", 2);
264        replyKeepAlive = cfg.getBoolean("reply-keepalive", true);
265        swapDirection = cfg.getBoolean("swap-direction", true);
266    }
267}