Package jnode.protocol.binkp.connector

Source Code of jnode.protocol.binkp.connector.BinkpAbstractConnector

/*
* Licensed to the jNode FTN Platform Develpoment Team (jNode Team)
* under one or more contributor license agreements.
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership. 
* The jNode Team licenses this file to you under the
* Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package jnode.protocol.binkp.connector;

import static jnode.protocol.binkp.BinkpProtocolTools.createMessage;
import static jnode.protocol.binkp.BinkpProtocolTools.forwardToTossing;
import static jnode.protocol.binkp.BinkpProtocolTools.getAuthPassword;
import static jnode.protocol.binkp.BinkpProtocolTools.getString;
import static jnode.protocol.binkp.BinkpProtocolTools.messageEquals;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import jnode.core.ConcurrentDateFormatAccess;
import jnode.dto.Link;
import jnode.dto.LinkOption;
import jnode.event.ConnectionEndEvent;
import jnode.event.Notifier;
import jnode.ftn.FtnTools;
import jnode.ftn.types.FtnAddress;
import jnode.logger.Logger;
import jnode.main.MainHandler;
import jnode.main.SystemInfo;
import jnode.main.threads.PollQueue;
import jnode.main.threads.ThreadPool;
import jnode.main.threads.TosserQueue;
import jnode.ndl.NodelistScanner;
import jnode.protocol.binkp.exceprion.ConnectionEndException;
import jnode.protocol.binkp.types.BinkpCommand;
import jnode.protocol.binkp.types.BinkpFrame;
import jnode.protocol.io.Message;

/**
* Абстрактный binkp через любой протокол
*
* @author kreon
*
*/
public abstract class BinkpAbstractConnector implements Runnable {
  static final Logger logger = Logger.getLogger(BinkpAbstractConnector.class);

  private static final ConcurrentDateFormatAccess format = new ConcurrentDateFormatAccess(
      "EEE, dd MMM yyyy HH:mm:ss Z", Locale.US);
  private static final Pattern cramPattern = Pattern
      .compile("^CRAM-([-A-Z0-9]+)-([a-f0-9]+)$");
  private static final int STATE_GREET = 0;
  protected static final int STATE_ERROR = 1;
  protected static final int STATE_END = 2;
  private static final int STATE_ADDR = 3;
  private static final int STATE_AUTH = 4;
  private static final int STATE_TRANSFER = 5;
  private static final String BINKP_NETWORK_NAME = "binkp.network";
  private static final String BINKP_MAX_MEM = "binkp.maxmem";
  private static final String BINKP_TEMP = "binkp.temp";
  private static final String BINKP_SIZE = "binkp.size";
  private static final String BINKP_TIMEOUT = "binkp.timeout";
  protected static Integer staticMemMaxSize = null;
  protected static Integer staticBufMaxSize = null;
  protected static File staticTempDirectory = null;
  protected static String staticNetworkName = null;
  protected static Long staticMaxTimeout = null;

  protected static void init() {
    if (staticTempDirectory == null) {
      staticTempDirectory = new File(MainHandler.getCurrentInstance()
          .getProperty(BINKP_TEMP,
              System.getProperty("java.io.tmpdir")));
    }
    if (staticNetworkName == null) {
      staticNetworkName = MainHandler.getCurrentInstance().getProperty(
          BINKP_NETWORK_NAME, "fidonet");
    }
    if (staticMemMaxSize == null) {
      staticMemMaxSize = MainHandler.getCurrentInstance()
          .getIntegerProperty(BINKP_MAX_MEM, 10485760);
    }
    if (staticBufMaxSize == null) {
      staticBufMaxSize = MainHandler.getCurrentInstance()
          .getIntegerProperty(BINKP_SIZE, 32767);
      if (staticBufMaxSize > 32767) {
        staticBufMaxSize = 32767;
      }
    }
    if (staticMaxTimeout == null) {
      staticMaxTimeout = (long) MainHandler.getCurrentInstance()
          .getIntegerProperty(BINKP_TIMEOUT, 30);
      staticMaxTimeout *= 1000;
    }
  }

  protected int connectionState = STATE_GREET;
  protected List<FtnAddress> foreignAddress = new ArrayList<>();
  private List<FtnAddress> ourAddress = new ArrayList<>();
  protected Link foreignLink;
  private boolean secure = false;
  protected boolean clientConnection = true;
  private String cramAlgo = null;
  private String cramText = null;
  protected boolean binkp1_0 = true;
  protected ArrayList<Message> messages = new ArrayList<>();
  protected InputStream currentInputStream;
  protected int messages_index = 0;
  // protected transferringMessage = null;
  private Message receivingMessage = null;
  private File currentFile;
  protected OutputStream currentOS;
  private long receivingBytesLeft;
  protected boolean flag_leob = false;
  protected boolean flag_reob = false;
  protected int sent_bytes = 0;
  protected int recv_bytes = 0;
  protected int total_sent_bytes = 0;
  protected int total_recv_bytes = 0;
  protected int total_sent_files = 0;
  protected int total_recv_files = 0;
  protected long lastTimeout;

  protected LinkedList<BinkpFrame> frames = new LinkedList<>();
  private long time = 0;

  public abstract void run();

  public BinkpAbstractConnector(String protocolAddress) throws IOException {
    init();
    this.clientConnection = true;
    logger.l3("Created " + getClass().getSimpleName()
        + " client connection to " + protocolAddress);
  }

  public BinkpAbstractConnector() throws IOException {
    init();
    this.clientConnection = false;
    logger.l3("Created " + getClass().getSimpleName()
        + " server connection");
  }

  protected void error(String text) {
    frames.clear();
    frames.addLast(new BinkpFrame(BinkpCommand.M_ERR, text));
    logger.l2("Local error: " + text);
    connectionState = STATE_ERROR;
  }

  protected void proccessFrame(BinkpFrame frame) {
    if (time == 0) {
      time = new Date().getTime();
    }
    addTimeout(); // it's ok :-)
    if (frame.getCommand() != null) {
      switch (frame.getCommand()) {
      case M_NUL:
        m_null(frame.getArg());
        break;
      case M_ADR:
        m_adr(frame.getArg());
        break;
      case M_PWD:
        m_pwd(frame.getArg());
        break;
      case M_OK:
        m_ok(frame.getArg());
        break;
      case M_ERR:
        rerror("Remote told: " + frame.getArg());
        break;

      case M_EOB:
        m_eob();
        break;

      case M_FILE:
        m_file(frame.getArg());
        break;

      case M_GOT:
        m_got(frame.getArg());
        break;

      case M_GET:
        m_get(frame.getArg());
        break;

      case M_SKIP:
        m_skip(frame.getArg());
        break;

      case M_BSY:
        m_bsy(frame.getArg());

      default:
        break;
      }
    } else {
      if (receivingMessage != null) {
        if (receivingBytesLeft > 0) {
          byte[] data = frame.getBytes();
          int len = data.length - 2;
          try {
            if (receivingBytesLeft >= len) {
              currentOS.write(data, 2, len);
              receivingBytesLeft -= len;
            } else {
              currentOS.write(data, 2, (int) receivingBytesLeft);
              receivingBytesLeft = 0;
            }
            recv_bytes += len;
            total_recv_bytes += len;
          } catch (IOException e) {
            frames.addLast(new BinkpFrame(BinkpCommand.M_SKIP,
                getString(receivingMessage)));
            receivingMessage = null;
            receivingBytesLeft = 0;
          }
        } else {
          logger.l4("Unknown data frame " + frame);
        }
        if (receivingBytesLeft == 0) {
          try {
            currentOS.close();
          } catch (IOException e) {
          }
          int ret = forwardToTossing(receivingMessage, currentFile,
              currentOS);
          frames.addLast(new BinkpFrame(
              (ret == 0) ? BinkpCommand.M_GOT
                  : BinkpCommand.M_SKIP,
              getString(receivingMessage)));
          logger.l3(String.format("Received file: %s (%d)",
              receivingMessage.getMessageName(),
              receivingMessage.getMessageLength()));
          total_recv_files++;
          receivingMessage = null;
          receivingBytesLeft = 0;
          currentFile = null;
          currentOS = null;

        }
      } else {
        logger.l4("Unknown data frame: " + frame);
      }
    }

  }

  private void m_bsy(String arg) {
    logger.l3("Remote is busy: " + arg);
    connectionState = STATE_END;
    finish("m_bsy");
  }

  private void rerror(String string) {
    logger.l2("Remote error: " + string);
    connectionState = STATE_ERROR;
  }

  private void m_eob() {
    flag_reob = true;
    checkEOB();
  }

  private void m_skip(String arg) {
    Message found = null;
    for (Message message : messages) {
      if (messageEquals(message, arg)) {
        logger.l3(String.format("Skip file: %s (%d)",
            message.getMessageName(), message.getMessageLength()));
        found = message;
        break;
      }
    }
    if (found != null) {
      messages.remove(found);
    } else {
      logger.l3("M_GOT for file we haven't sent: " + arg);
    }

  }

  private void m_get(String arg) {
    for (Message message : messages) {
      if (messageEquals(message, arg)) {
        int skip = Integer.valueOf(arg.split(" ")[3]);
        sendMessage(message, skip);
        logger.l4("M_GET for file " + arg);
        break;
      }
    }
  }

  private void m_got(String arg) {
    Message found = null;
    for (Message message : messages) {
      if (messageEquals(message, arg)) {
        logger.l3(String.format("Sent file: %s (%d)",
            message.getMessageName(), message.getMessageLength()));
        found = message;
        break;
      }
    }
    if (found != null) {
      total_sent_files++;
      found.delete();
      messages.remove(found);
    } else {
      logger.l3("M_GOT for file we haven't sent: " + arg);
    }
  }

  private void m_file(String arg) {
    receivingMessage = createMessage(arg, secure);
    long free_space = new File(FtnTools.getInbound()).getFreeSpace();
    if (receivingMessage.getMessageLength() > free_space) {
      frames.addLast(new BinkpFrame(BinkpCommand.M_SKIP,
          getString(receivingMessage)));
      receivingMessage = null;
      logger.l1("No enogth free space in inbound for receiving file");
    }
    if (!arg.split(" ")[3].equals("0")) {
      frames.addLast(new BinkpFrame(BinkpCommand.M_GET, getString(
          receivingMessage, 0)));
    } else {
      receivingBytesLeft = receivingMessage.getMessageLength();
      try {
        currentFile = File.createTempFile("temp", "jnode",
            staticTempDirectory);
        free_space = currentFile.getFreeSpace();
        if (receivingMessage.getMessageLength() > free_space) {
          logger.l1("No enogth free space in tmp for receiving file");
          currentFile.delete();
          throw new IOException();
        }
        currentOS = new FileOutputStream(currentFile);
      } catch (IOException e) {
        currentFile = null;
        if (receivingMessage.getMessageLength() < staticMemMaxSize) {
          currentOS = new ByteArrayOutputStream(
              (int) receivingMessage.getMessageLength());
        } else {
          frames.addLast(new BinkpFrame(BinkpCommand.M_SKIP,
              getString(receivingMessage)));
          receivingMessage = null;
          receivingBytesLeft = 0;
        }
      }
      if (receivingMessage != null) {
        logger.l3(String.format("Receiving file: %s (%d)",
            receivingMessage.getMessageName(),
            receivingMessage.getMessageLength()));
      }
    }

  }

  private void m_ok(String arg) {
    if (connectionState != STATE_AUTH) {
      error("We weren't waiting for M_OK");
    }
    String text = ((secure) ? "(S) Secure" : "(U) Unsecure")
        + " connection with "
        + ((secure) ? foreignLink.getLinkAddress() : foreignAddress
            .get(0));
    logger.l3(text);
    connectionState = STATE_TRANSFER;
  }

  /**
   * Обработка входящего M_PWD
   *
   * @param arg
   */
  private void m_pwd(String arg) {
    if (connectionState != STATE_AUTH) {
      error("We weren't waiting for M_PWD");
    }
    boolean valid = (!secure || checkPassword(arg));
    String text;
    if (secure) {
      text = "(S) Secure  connection with "
          + foreignLink.getLinkAddress();
    } else {
      text = "(U) Unsecure connection with " + foreignAddress.get(0);
    }
    if (valid) {
      logger.l3(text);
      frames.addLast(new BinkpFrame(BinkpCommand.M_OK, text));
      connectionState = STATE_TRANSFER;
    } else {
      error("Invalid password");
      connectionState = STATE_ERROR;
    }
  }

  private boolean checkPassword(String arg) {
    String password = foreignLink.getProtocolPassword();
    if (password.equals(arg)) {
      return true;
    }
    password = getAuthPassword(foreignLink, secure, cramAlgo, cramText);
    if (password.endsWith(arg)) {
      return true;
    }
    return false;
  }

  /**
   * Обработка входящего M_ADR
   *
   * @param arg
   */
  private void m_adr(String arg) {
    if (connectionState != STATE_ADDR) {
      error("We weren't waiting for M_ADR");
    }
    for (String addr : arg.replace("^[ ]*", "").split(" ")) {
      try {
        FtnAddress a = new FtnAddress(addr);
        Link link = FtnTools.getLinkByFtnAddress(a);
        boolean nodelist = NodelistScanner.getInstance().isExists(a) != null;
        if (link != null || nodelist) {
          foreignAddress.add(a);
        }
      } catch (NumberFormatException e) {
        logger.l4("Invalid address " + addr);
      }
    }

    if (foreignAddress.isEmpty()) {
      error("No valid address specified");
      return;
    }
    Link link = FtnTools.getLinkByFtnAddress(foreignAddress);
    if (link != null) {
      String ourAka = FtnTools.getOptionString(link,
          LinkOption.STRING_OUR_AKA);
      if (ourAka != null) {
        try {
          FtnAddress addr = new FtnAddress(ourAka);
          if (ourAddress.contains(addr)) {
            ourAddress.clear();
            ourAddress.add(addr);
          }
        } catch (NumberFormatException e) {
        }
      }
      foreignLink = link;
      secure = true;
    } else {
      boolean nodelist = false;
      for (FtnAddress a : foreignAddress) {
        if (NodelistScanner.getInstance().isExists(a) != null) {
          nodelist = true;
          break;
        }
      }
      if (!nodelist) {
        error("No one address you specified exists in Nodelist");
        return;
      }
    }
    for (FtnAddress addr : foreignAddress) {
      if (!PollQueue.getSelf().isActive(addr)) {
        PollQueue.getSelf().start(addr);
      } else {
        busy("Already connected with " + addr.toString());
      }
    }
    if (clientConnection) {
      frames.addLast(new BinkpFrame(BinkpCommand.M_PWD, getAuthPassword(
          foreignLink, secure, cramAlgo, cramText)));
    } else {
      sendAddrs();
    }
    connectionState = STATE_AUTH;

  }

  protected void busy(String string) {
    frames.clear();
    frames.addLast(new BinkpFrame(BinkpCommand.M_BSY, string));
    connectionState = STATE_END;
    logger.l3("Local busy: " + string);
  }

  private void sendAddrs() {
    StringBuilder sb = new StringBuilder();
    boolean flag = true;
    for (FtnAddress a : ourAddress) {
      if (flag) {
        flag = false;
      } else {
        sb.append(" ");
      }
      sb.append(a.toString() + "@" + staticNetworkName);
    }
    frames.addLast(new BinkpFrame(BinkpCommand.M_ADR, sb.toString()));
  }

  private void m_null(String arg) {
    logger.l4("M_NULL " + arg);
    String[] args = arg.split(" ");
    if (args[0].equals("OPT")) {
      for (int i = 1; i < args.length; i++) {
        Matcher md = cramPattern.matcher(args[i]);
        if (md.matches()) {
          String[] algos = md.group(1).split("/");
          for (String algo : algos) {
            try {
              MessageDigest.getInstance(algo);
              cramText = md.group(2);
              cramAlgo = md.group(1);
              logger.l4("Remote requires MD-mode (" + algo + ")");
              break;
            } catch (NoSuchAlgorithmException e) {
              logger.l2("fail algo ", e);
              logger.l2("Remote requires MD-mode for unknown algo");
            }
          }
        }
      }
    } else if (args[0].equals("VER")) {
      if (arg.matches("^.* binkp/1\\.1$")) {
        binkp1_0 = false;
        logger.l4("Protocol version 1.1");
      } else {
        binkp1_0 = true;
        logger.l4("Protocol version 1.0");
      }
    }

  }

  protected void checkForMessages() {
    checkTimeout();
    if (connectionState != STATE_TRANSFER) {
      return;
    }
    if (flag_leob) {
      return;
    }
    if (messages.size() > 0) {
      BinkpFrame frame = readFrame();
      if (frame != null) {
        frames.addLast(frame);
      }
      return;
    }
    for (FtnAddress a : foreignAddress) {
      messages.addAll(TosserQueue.getInstanse().getMessages(a));
    }
    if (messages.isEmpty()) {
      if (!flag_leob) {
        flag_leob = true;
        frames.addLast(new BinkpFrame(BinkpCommand.M_EOB));
        checkEOB();
      }
    } else {
      messages_index = 0;
      startNextFile();
    }
  }

  protected void finish(String reason) {
    time = new Date().getTime() - time;
    logger.l4("Finishing: " + reason);
    for (FtnAddress addr : foreignAddress) {
      PollQueue.getSelf().end(addr);
    }
    throw new ConnectionEndException();
  }

  protected void greet() {
    // check if busy
    if (ThreadPool.isBusy()) {
      busy("Too much connections");
      finish("From greet()");
    }
    addTimeout();
    SystemInfo info = MainHandler.getCurrentInstance().getInfo();
    ourAddress.addAll(info.getAddressList());
    frames.addLast(new BinkpFrame(BinkpCommand.M_NUL, "SYS "
        + info.getStationName()));
    frames.addLast(new BinkpFrame(BinkpCommand.M_NUL, "ZYZ "
        + info.getSysop()));
    frames.addLast(new BinkpFrame(BinkpCommand.M_NUL, "LOC "
        + info.getLocation()));
    frames.addLast(new BinkpFrame(BinkpCommand.M_NUL, "NDL "
        + info.getNDL()));
    frames.addLast(new BinkpFrame(BinkpCommand.M_NUL, "VER "
        + MainHandler.getVersion() + " binkp/1.1"));
    frames.addLast(new BinkpFrame(BinkpCommand.M_NUL, "TIME "
        + format.format(new Date())));

    connectionState = STATE_ADDR;
    if (clientConnection) {
      sendAddrs();
    } else {
      MessageDigest md;
      try {
        md = MessageDigest.getInstance("MD5");
        md.update(String.format("%d%d", System.currentTimeMillis(),
            System.nanoTime()).getBytes());
        byte[] digest = md.digest();
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < 16; i++) {
          builder.append(String.format("%02x", digest[i]));
        }
        cramText = builder.toString();
        cramAlgo = "MD5";
        frames.addLast(new BinkpFrame(BinkpCommand.M_NUL, String
            .format("OPT CRAM-MD5-%s", cramText)));

      } catch (NoSuchAlgorithmException e) {
        cramText = null;
      }
    }
  }

  protected boolean isConnected() {
    checkTimeout();
    return !((frames.isEmpty() && connectionState == STATE_END) || connectionState == STATE_ERROR);
  }

  protected BinkpFrame readFrame() {
    if (currentInputStream != null) {
      try {
        byte[] buf = new byte[staticBufMaxSize];
        int n = currentInputStream.read(buf);
        sent_bytes += n;
        if (n > 0) {
          sent_bytes += n;
          total_sent_bytes += n;
          addTimeout();
          return new BinkpFrame(buf, n);
        } else {
          currentInputStream.close();
          currentInputStream = null;
          logger.l5("received EOF on current IO");
          messages_index++;
          if (startNextFile()) {
            return readFrame();
          }
        }
      } catch (IOException e) {
        error("Error reading file");
      }
    }
    return null;
  }

  protected boolean startNextFile() {
    logger.l5("startNextFile()");
    try {
      Message nextMessage = messages.get(messages_index);
      sendMessage(nextMessage, 0);
      return true;
    } catch (IndexOutOfBoundsException e) {
      return false;
    }
  }

  protected void sendMessage(Message message, int skip) {
    frames.addLast(new BinkpFrame(BinkpCommand.M_FILE, getString(message,
        skip)));
    logger.l3(String.format("Sending file: %s (%d)",
        message.getMessageName(), message.getMessageLength()));
    try {
      message.getInputStream().skip(skip);
      if (currentInputStream != null) {
        currentInputStream.close();
        currentInputStream = null;
      }
      currentInputStream = message.getInputStream();
    } catch (IOException e) {
      error("IOException");
    }

  }

  private void checkTimeout() {
    long last = new Date().getTime();
    if (last - lastTimeout > staticMaxTimeout) {
      connectionState = STATE_ERROR;
      finish("Connection timeout");
    }
  }

  private void addTimeout() {
    lastTimeout = new Date().getTime();
  }

  protected void checkEOB() {
    checkTimeout();
    if (connectionState == STATE_END || connectionState == STATE_ERROR) {
      finish("connectionState = END|ERROR");
    }
    if (flag_leob && flag_reob) {
      if (sent_bytes + recv_bytes == 0 || binkp1_0) {
        connectionState = STATE_END;
      } else {
        logger.l5("Binkp/1.1 : reset state");
        flag_leob = false;
        flag_reob = false;
        sent_bytes = 0;
        recv_bytes = 0;
      }
    }
  }

  protected void done() {
    try {
      if (currentOS != null) {
        currentOS.close();
      }
      for (Message message : messages) {
        if (message.getInputStream() != null) {
          message.getInputStream().close();
        }
      }
    } catch (IOException e2) {
      logger.l2("Error while closing key", e2);
    }
    ConnectionEndEvent event = null;
    if (!foreignAddress.isEmpty()) {
      for (FtnAddress addr : foreignAddress) {
        PollQueue.getSelf().end(addr);
      }
      time /= 1000;
      long scps = (time > 0) ? total_sent_bytes / time : total_sent_bytes;
      long rcps = (time > 0) ? total_recv_bytes / time : total_recv_bytes;

      String address = (foreignLink != null) ? foreignLink
          .getLinkAddress() : foreignAddress.get(0).toString();
      logger.l2(String.format(
          "Done: %s %s, %s, S/R: %d/%d (%d/%d bytes) (%d/%d cps)",
          (clientConnection) ? "to" : "from", address,
          (connectionState == STATE_END) ? "OK" : "ERROR",
          total_sent_files, total_recv_files, total_sent_bytes,
          total_recv_bytes, scps, rcps));
      event = new ConnectionEndEvent(new FtnAddress(address),
          !clientConnection, (connectionState == STATE_END),
          total_recv_bytes, total_sent_bytes);
    } else {
      event = new ConnectionEndEvent(clientConnection, false);
      logger.l3("Connection ended as unknown");
    }
    Notifier.INSTANSE.notify(event);
  }

}
TOP

Related Classes of jnode.protocol.binkp.connector.BinkpAbstractConnector

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.