package org.plumelib.bcelutil;

import java.util.Arrays;
import org.apache.bcel.Const;
import org.apache.bcel.classfile.StackMapEntry;
import org.apache.bcel.classfile.StackMapType;
import org.apache.bcel.generic.BranchInstruction;
import org.apache.bcel.generic.CodeExceptionGen;
import org.apache.bcel.generic.IfInstruction;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionFactory;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.InstructionTargeter;
import org.apache.bcel.generic.LOOKUPSWITCH;
import org.apache.bcel.generic.LineNumberGen;
import org.apache.bcel.generic.LocalVariableGen;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.TABLESWITCH;
import org.apache.bcel.verifier.structurals.OperandStack;
import org.checkerframework.checker.nullness.qual.Nullable;

/**
 * This class provides utility methods to maintain and modify a method's InstructionList within a
 * Java class file. It is a subclass of {@link org.plumelib.bcelutil.StackMapUtils} and thus handles
 * all the StackMap side effects of InstructionList modification. It can be thought of as an
 * extension to BCEL.
 *
 *
BCEL ought to automatically build and maintain the StackMapTable in a manner similar to the
 * LineNumberTable and the LocalVariableTable. However, for historical reasons, it does not.
 *
 *
If one wishes to modify a Java class file, you should create a subclass of
 * InstructionListUtils to do the modifications. Then a rough program template for that class would
 * be:
 *
 *

 *   import org.apache.bcel.classfile.*;
 *   import org.apache.bcel.generic.*;
 *
 *  try {
 *    // Parse the bytes of the classfile, die on any errors
 *    ClassParser parser = new ClassParser(new ByteArrayInputStream(classfileBuffer), className);
 *    JavaClass jc = parser.parse();
 *
 *    // Transform the file
 *    modifyClass(jc);
 *
 *  } catch (Throwable e) {
 *    throw new RuntimeException("Unexpected error", e);
 *  }
 *
 *  void modifyClass(JavaClass jc) {
 *    ClassGen cg = new ClassGen(jc);
 *    String classname = cg.getClassName();
 *    //save ConstantPool for use by StackMapUtils
 *    pool = cg.getConstantPool();
 *
 *    for (Method m : cg.getMethods()) {
 *      try {
 *        MethodGen mg = new MethodGen(m, classname, pool);
 *        // Get the instruction list and skip methods with no instructions
 *        InstructionList il = mg.getInstructionList();
 *        if (il == null) {
 *          continue;
 *        }
 *
 *        // Get existing StackMapTable (if present)
 *        set_current_stack_map_table(mg, cg.getMajor());
 *        fix_local_variable_table(mg);
 *
 *        // Create a map of Uninitialized_variable_info offsets to
 *        // InstructionHandles.
 *        build_unitialized_NEW_map(il);
 *
 * This is where you would insert your code to modify the current method (mg).
 * Most often this is done with members of the {@link org.apache.bcel.generic}
 * package.  However, you should use the members of InstrutionListUtils to update
 * the byte code instructions of mg rather than similar methods in the BCEL
 * generic package in order to maintain the integrity of the method's StackMapTable.
 *
 *        // Update the Uninitialized_variable_info offsets before
 *        // we write out the new StackMapTable.
 *        update_uninitialized_NEW_offsets(il);
 *        create_new_stack_map_attribute(mg);
 *
 *        // Update the instruction list
 *        mg.setInstructionList(il);
 *        mg.update();
 *
 *        // Update the max stack
 *        mg.setMaxStack();
 *        mg.setMaxLocals();
 *        mg.update();
 *
 *        remove_local_variable_type_table(mg);
 *
 *        // Update the method in the class
 *        cg.replaceMethod(m, mg.getMethod());
 *
 *      } catch (Throwable t) {
 *        throw new Error("Unexpected error processing " + classname + "." + m.getName(), t);
 *      }
 *    }
 *  }
 *

 *
 * It one only wishes to examine a class file, the use of this class is not necessary. See {@link
 * org.plumelib.bcelutil.BcelUtil} for notes on inspecting a Java class file.
 */
@SuppressWarnings("nullness")
public abstract class InstructionListUtils extends StackMapUtils {

  /**
   * Appends the specified instruction to the end of the specified list. Required because for some
   * reason you can't directly append jump instructions to the list -- but you can create new ones
   * and append them.
   *
   * @param il InstructionList to be modified
   * @param inst Instruction to be appended
   */
  protected final void append_inst(InstructionList il, Instruction inst) {

    // System.out.println ("append_inst: " + inst.getClass().getName());
    if (inst instanceof LOOKUPSWITCH) {
      LOOKUPSWITCH ls = (LOOKUPSWITCH) inst;
      il.append(new LOOKUPSWITCH(ls.getMatchs(), ls.getTargets(), ls.getTarget()));
    } else if (inst instanceof TABLESWITCH) {
      TABLESWITCH ts = (TABLESWITCH) inst;
      il.append(new TABLESWITCH(ts.getMatchs(), ts.getTargets(), ts.getTarget()));
    } else if (inst instanceof IfInstruction) {
      IfInstruction ifi = (IfInstruction) inst;
      il.append(InstructionFactory.createBranchInstruction(inst.getOpcode(), ifi.getTarget()));
    } else {
      il.append(inst);
    }
  }

  /**
   * Inserts an instruction list at the beginning of a method.
   *
   * @param mg MethodGen of method to be modified
   * @param new_il InstructionList holding the new code
   */
  protected final void insert_at_method_start(MethodGen mg, InstructionList new_il) {

    // Ignore methods with no instructions
    InstructionList il = mg.getInstructionList();
    if (il == null) {
      return;
    }
    insert_before_handle(mg, il.getStart(), new_il, false);
  }

  /**
   * Inserts a new instruction list into an existing instruction list just prior to the indicated
   * instruction handle (which must be a member of the existing instruction list). If new_il is
   * null, do nothing.
   *
   * @param mg MethodGen containing the instruction handle
   * @param ih InstructionHandle indicating where to insert new code
   * @param new_il InstructionList holding the new code
   * @param redirect_branches flag indicating if branch targets should be moved from ih to new_il
   */
  protected final void insert_before_handle(
      MethodGen mg,
      InstructionHandle ih,
      @Nullable InstructionList new_il,
      boolean redirect_branches) {

    if (new_il == null) {
      return;
    }

    // Ignore methods with no instructions
    InstructionList il = mg.getInstructionList();
    if (il == null) {
      return;
    }

    boolean at_start = (ih.getPrev() == null);
    new_il.setPositions();
    InstructionHandle new_end = new_il.getEnd();
    int new_length = new_end.getPosition() + new_end.getInstruction().getLength();

    print_stack_map_table("Before insert_inst");
    debug_instrument.log("  insert_inst: %d%n%s%n", new_il.getLength(), new_il);

    // Add the new code in front of the instruction handle.
    InstructionHandle new_start = il.insert(ih, new_il);
    il.setPositions();

    if (redirect_branches) {
      // Move all of the branches from the old instruction to the new start.
      il.redirectBranches(ih, new_start);
    }

    // Move other targets to the new start.
    if (ih.hasTargeters()) {
      for (InstructionTargeter it : ih.getTargeters()) {
        if ((it instanceof LineNumberGen) && redirect_branches) {
          it.updateTarget(ih, new_start);
        } else if (it instanceof LocalVariableGen) {
          LocalVariableGen lvg = (LocalVariableGen) it;
          // If ih is end of local variable range, leave as is.
          // If ih is start of local variable range and we are
          // at the begining of the method go ahead and change
          // start to new_start.  This is to preserve live range
          // for variables that are live for entire method.
          if ((lvg.getStart() == ih) && at_start) {
            it.updateTarget(ih, new_start);
          }
        } else if ((it instanceof CodeExceptionGen) && redirect_branches) {
          CodeExceptionGen exc = (CodeExceptionGen) it;
          if (exc.getStartPC() == ih) exc.updateTarget(ih, new_start);
          // else if (exc.getEndPC() == ih) leave as is
          else if (exc.getHandlerPC() == ih) exc.setHandlerPC(new_start);
          else System.out.printf("Malformed CodeException: %s%n", exc);
        }
      }
    }

    // Need to update stack map for change in length of instruction bytes.
    // If redirect_branches is true then we don't want to change the
    // offset of a stack map that was on the original ih, we want it
    // to 'move' to the new_start.  If we did not redirect the branches
    // then we do want any stack map associated with the original ih
    // to stay there. The routine update_stack_map starts looking for
    // a stack map after the location given as its first argument.
    // Thus we need to subtract one from this location if the
    // redirect_branches flag is false.
    il.setPositions();
    update_stack_map_offset(new_start.getPosition() - (redirect_branches ? 0 : 1), new_length);

    print_il(new_start, "After update_stack_map_offset");

    // We need to see if inserting the additional instructions caused
    // a change in the amount of switch instruction padding bytes.
    modify_stack_maps_for_switches(new_start, il);
  }

  /**
   * Print a BCEL instruction list to the debug_instrument log.
   *
   * @param start start of the instruction list
   * @param label a descriptive string for the instruction list
   */
  private void print_il(InstructionHandle start, String label) {
    if (debug_instrument.enabled()) {
      print_stack_map_table(label);
      InstructionHandle tih = start;
      while (tih != null) {
        debug_instrument.log("inst: %s %n", tih);
        if (tih.hasTargeters()) {
          for (InstructionTargeter it : tih.getTargeters()) {
            debug_instrument.log("targeter: %s %n", it);
          }
        }
        tih = tih.getNext();
      }
    }
  }

  /**
   * Convenience function to build an instruction list.
   *
   * @param instructions a variable number of BCEL instructions
   * @return an InstructionList
   */
  protected final InstructionList build_il(Instruction... instructions) {
    InstructionList il = new InstructionList();
    for (Instruction inst : instructions) {
      append_inst(il, inst);
    }
    return il;
  }

  /**
   * Delete instruction(s) from start_ih thru end_ih in an instruction list. start_ih may be the
   * first instruction of the list, but end_ih must not be the last instruction of the list.
   * start_ih may be equal to end_ih. There must not be any targeters on any of the instructions to
   * be deleted except for start_ih. Those targeters will be moved to the first instruction
   * following end_ih.
   *
   * @param mg MethodGen containing the instruction handles
   * @param start_ih InstructionHandle indicating first instruction to be deleted
   * @param end_ih InstructionHandle indicating last instruction to be deleted
   */
  protected final void delete_instructions(
      MethodGen mg, InstructionHandle start_ih, InstructionHandle end_ih) {
    InstructionList il = mg.getInstructionList();
    InstructionHandle new_start = end_ih.getNext();
    if (new_start == null) {
      throw new RuntimeException("Cannot delete last instruction.");
    }

    il.setPositions();
    int size_deletion = start_ih.getPosition() - new_start.getPosition();

    // Move all of the branches from the first instruction to the new start
    il.redirectBranches(start_ih, new_start);

    // Move other targeters to the new start.
    if (start_ih.hasTargeters()) {
      for (InstructionTargeter it : start_ih.getTargeters()) {
        if (it instanceof LineNumberGen) {
          it.updateTarget(start_ih, new_start);
        } else if (it instanceof LocalVariableGen) {
          it.updateTarget(start_ih, new_start);
        } else if (it instanceof CodeExceptionGen) {
          CodeExceptionGen exc = (CodeExceptionGen) it;
          if (exc.getStartPC() == start_ih) exc.updateTarget(start_ih, new_start);
          else if (exc.getEndPC() == start_ih) exc.updateTarget(start_ih, new_start);
          else if (exc.getHandlerPC() == start_ih) exc.setHandlerPC(new_start);
          else System.out.printf("Malformed CodeException: %s%n", exc);
        } else {
          System.out.printf("unexpected target %s%n", it);
        }
      }
    }

    // Remove the old handle(s).  There should not be any targeters left.
    try {
      il.delete(start_ih, end_ih);
    } catch (Exception e) {
      throw new Error("Can't delete instruction list", e);
    }
    // Need to update instruction positions due to delete above.
    il.setPositions();

    // Update stack map to account for deleted instructions.
    update_stack_map_offset(new_start.getPosition(), size_deletion);

    // Check to see if the deletion caused any changes
    // in the amount of switch instruction padding bytes.
    // If so, we may need to update the corresponding stackmap.
    modify_stack_maps_for_switches(new_start, il);
  }

  /**
   * Compute the StackMapTypes of the live variables of the current method at a specific location
   * within the method. There may be gaps ("Bogus" or non-live slots) so we can't just count the
   * number of live variables, we must find the max index of all the live variables.
   *
   * @param mg MethodGen for the current method
   * @param location the code location to be evaluated
   * @return an array of StackMapType describing the live locals at location
   */
  protected final StackMapType[] calculate_live_local_types(MethodGen mg, int location) {
    int max_local_index = -1;
    StackMapType[] local_map_types = new StackMapType[mg.getMaxLocals()];
    Arrays.fill(local_map_types, new StackMapType(Const.ITEM_Bogus, -1, pool.getConstantPool()));
    for (LocalVariableGen lv : mg.getLocalVariables()) {
      if (location >= lv.getStart().getPosition()) {
        if (lv.getLiveToEnd() || location < lv.getEnd().getPosition()) {
          int i = lv.getIndex();
          local_map_types[i] = generate_StackMapType_from_Type(lv.getType());
          max_local_index = Math.max(max_local_index, i);
        }
      }
    }
    return Arrays.copyOf(local_map_types, max_local_index + 1);
  }

  /**
   * Compute the StackMapTypes of the items on the execution stack as described by the OperandStack
   * argument.
   *
   * @param stack an OperandStack object
   * @return an array of StackMapType describing the stack contents
   */
  protected final StackMapType[] calculate_live_stack_types(OperandStack stack) {
    int ss = stack.size();
    StackMapType[] stack_map_types = new StackMapType[ss];
    for (int ii = 0; ii < ss; ii++) {
      stack_map_types[ii] = generate_StackMapType_from_Type(stack.peek(ss - ii - 1));
    }
    return stack_map_types;
  }

  /**
   * Replace instruction ih in list il with the instructions in new_il. If new_il is null, do
   * nothing.
   *
   * @param mg MethodGen containing the instruction handle
   * @param il InstructionList containing ih
   * @param ih InstructionHandle indicating where to insert new code
   * @param new_il InstructionList holding the new code
   */
  protected final void replace_instructions(
      MethodGen mg, InstructionList il, InstructionHandle ih, @Nullable InstructionList new_il) {

    if (new_il == null) {
      return;
    }

    InstructionHandle new_end;
    InstructionHandle new_start;
    int old_length = ih.getInstruction().getLength();

    new_il.setPositions();
    InstructionHandle end = new_il.getEnd();
    int new_length = end.getPosition() + end.getInstruction().getLength();

    debug_instrument.log("  replace_inst: %s %d%n%s%n", ih, new_il.getLength(), new_il);
    print_il(ih, "Before replace_inst");

    // If there is only one new instruction, just replace it in the handle
    if (new_il.getLength() == 1) {
      ih.setInstruction(new_il.getEnd().getInstruction());
      if (old_length == new_length) {
        // no possible changes downstream, so we can exit now
        return;
      }
      print_stack_map_table("replace_inst_with_single_inst B");
      il.setPositions();
      new_end = ih;
      // Update stack map for change in length of instruction bytes.
      update_stack_map_offset(ih.getPosition(), (new_length - old_length));

      // We need to see if inserting the additional instructions caused
      // a change in the amount of switch instruction padding bytes.
      // If so, we may need to update the corresponding stackmap.
      modify_stack_maps_for_switches(new_end, il);
    } else {
      print_stack_map_table("replace_inst_with_inst_list B");
      // We are inserting more than one instruction.
      // Get the start and end handles of the new instruction list.
      new_end = new_il.getEnd();
      new_start = il.insert(ih, new_il);
      il.setPositions();

      // Just in case there is a switch instruction in new_il we need
      // to recalculate new_length as the padding may have changed.
      new_length = new_end.getNext().getPosition() - new_start.getPosition();

      // Move all of the branches from the old instruction to the new start
      il.redirectBranches(ih, new_start);

      print_il(new_end, "replace_inst #1");

      // Move other targets to the new instuctions.
      if (ih.hasTargeters()) {
        for (InstructionTargeter it : ih.getTargeters()) {
          if (it instanceof LineNumberGen) {
            it.updateTarget(ih, new_start);
          } else if (it instanceof LocalVariableGen) {
            it.updateTarget(ih, new_end);
          } else if (it instanceof CodeExceptionGen) {
            CodeExceptionGen exc = (CodeExceptionGen) it;
            if (exc.getStartPC() == ih) exc.updateTarget(ih, new_start);
            else if (exc.getEndPC() == ih) exc.updateTarget(ih, new_end);
            else if (exc.getHandlerPC() == ih) exc.setHandlerPC(new_start);
            else System.out.printf("Malformed CodeException: %s%n", exc);
          } else {
            System.out.printf("unexpected target %s%n", it);
          }
        }
      }

      print_il(new_end, "replace_inst #2");

      // Remove the old handle.  There should be no targeters left to it.
      try {
        il.delete(ih);
      } catch (Exception e) {
        System.out.printf("Can't delete instruction: %s at %s%n", mg.getClassName(), mg.getName());
        throw new Error("Can't delete instruction", e);
      }
      // Need to update instruction address due to delete above.
      il.setPositions();

      print_il(new_end, "replace_inst #3");

      if (needStackMap) {
        // Before we look for branches in the inserted code we need
        // to update any existing stack maps for locations in the old
        // code that are after the inserted code.
        update_stack_map_offset(new_start.getPosition(), (new_length - old_length));

        // We need to see if inserting the additional instructions caused
        // a change in the amount of switch instruction padding bytes.
        // If so, we may need to update the corresponding stackmap.
        modify_stack_maps_for_switches(new_end, il);
        print_stack_map_table("replace_inst_with_inst_list C");

        // Look for branches within the new il; i.e., both the source
        // and target must be within the new il.  If we find any, the
        // target will need a stack map entry.
        // This situation is caused by a call to "instrument_object_call".
        InstructionHandle nih = new_start;
        int target_count = 0;
        int target_offsets[] = new int[2]; // see note below for why '2'

        // Any targeters on the first instruction will be from 'outside'
        // the new il so we start with the second instruction. (We already
        // know there is more than one instruction in the new il.)
        nih = nih.getNext();

        // We assume there is more code after the new il insertion point
        // so this getNext will not fail.
        new_end = new_end.getNext();
        while (nih != new_end) {
          if (nih.hasTargeters()) {
            for (InstructionTargeter it : nih.getTargeters()) {
              if (it instanceof BranchInstruction) {
                target_offsets[target_count++] = nih.getPosition();
                debug_instrument.log("New branch target: %s %n", nih);
              }
            }
          }
          nih = nih.getNext();
        }

        print_il(new_end, "replace_inst #4");

        if (target_count != 0) {
          // Currently, target_count is always 2; but code is
          // written to allow more.
          int cur_loc = new_start.getPosition();
          int orig_size = stack_map_table.length;
          StackMapEntry[] new_stack_map_table = new StackMapEntry[orig_size + target_count];

          // Calculate the operand stack value(s) for revised code.
          mg.setMaxStack();
          StackTypes stack_types = bcel_calc_stack_types(mg);
          OperandStack stack;

          // Find last stack map entry prior to first new branch target;
          // returns -1 if there isn't one. Also sets running_offset and number_active_locals.
          // The '+1' below means new_index points to the first stack map entry after our
          // inserted code.  There may not be one; in which case new_index == orig_size.
          int new_index = find_stack_map_index_before(target_offsets[0]) + 1;

          // The Java compiler can 'simplfy' the generated class file by not
          // inserting a stack map entry every time a local is defined if
          // that stack map entry is not needed as a branch target.  Thus,
          // there can be more live locals than defined by the stack maps.
          // This leads to serious complications if we wish to insert instrumentation
          // code, that contains internal branches and hence needs stack
          // map entries, into a section of code with 'extra' live locals.
          // If we don't include these extra locals in our inserted stack
          // map entries and they are subsequently referenced, that would
          // cause a verification error.  But if we do include them the locals
          // state might not be correct when execution reaches the first
          // stack map entry after our inserted code and that
          // would also cause a verification error. Dicey stuff.

          // I think one possibllity would be to insert a nop instruction
          // with a stack map of CHOP right after the last use of an 'extra'
          // local prior to the next stack map entry. An interesting alternative
          // might be, right as we start to process a method, make a pass over
          // the byte codes and add any 'missing' stack maps. This would
          // probably be too heavy as most instrumentation does not contain branches.
          // Not sure which of these two methods is 'easier' at this point.

          // I'm going to try a simplification.  If there are 'extra' locals
          // and we need to generate a FULL stack map - then go through and
          // make all subsequent StackMaps FULL as well.

          // The inserted code has pushed an object reference on the stack.
          // The StackMap(s) we create as targets of an internal branch
          // must account for this item.  Normally, we would use
          // SAME_LOCALS_1_STACK_ITEM_FRAME for this case, but there is a
          // possibility that the compiler has already allocated extra local items
          // that it plans to identify in a subsequent StackMap APPEND entry.

          // First, lets calculate the number and types of the live locals.
          StackMapType[] local_map_types = calculate_live_local_types(mg, cur_loc);
          int local_map_index = local_map_types.length;

          // local_map_index now contains the number of live locals.
          // number_active_locals has been calculated from the existing StackMap.
          // If these two are equal, we should be ok.
          int number_extra_locals = local_map_index - number_active_locals;
          // lets do a sanity check
          assert number_extra_locals >= 0
              : "invalid extra locals count: " + number_active_locals + ", " + local_map_index;

          // Copy any existing stack maps prior to inserted code.
          System.arraycopy(stack_map_table, 0, new_stack_map_table, 0, new_index);

          boolean need_full_maps = false;
          for (int i = 0; i < target_count; i++) {
            stack = stack_types.get(target_offsets[i]);
            debug_instrument.log("stack: %s %n", stack);

            if (number_extra_locals == 0 && stack.size() == 1 && !need_full_maps) {
              // the simple case
              StackMapType stack_map_type0 = generate_StackMapType_from_Type(stack.peek(0));
              StackMapType[] stack_map_types0 = {stack_map_type0};
              new_stack_map_table[new_index + i] =
                  new StackMapEntry(
                      Const.SAME_LOCALS_1_STACK_ITEM_FRAME,
                      0, // byte_code_offset set below
                      null,
                      stack_map_types0,
                      pool.getConstantPool());
            } else {
              // need a FULL_FRAME stack map entry
              need_full_maps = true;
              new_stack_map_table[new_index + i] =
                  new StackMapEntry(
                      Const.FULL_FRAME,
                      0, // byte_code_offset set below
                      calculate_live_local_types(mg, target_offsets[i]),
                      calculate_live_stack_types(stack),
                      pool.getConstantPool());
            }
            // now set the offset from the previous Stack Map entry to our new one.
            new_stack_map_table[new_index + i].updateByteCodeOffset(
                target_offsets[i] - (running_offset + 1));
            running_offset = target_offsets[i];
          }

          // now copy remaining 'old' stack maps
          int remainder = orig_size - new_index;
          if (remainder > 0) {
            // before we copy, we need to update first map after insert
            l1:
            while (nih != null) {
              if (nih.hasTargeters()) {
                for (InstructionTargeter it : nih.getTargeters()) {
                  if (it instanceof BranchInstruction) {
                    stack_map_table[new_index].updateByteCodeOffset(
                        nih.getPosition()
                            - target_offsets[target_count - 1]
                            - 1
                            - stack_map_table[new_index].getByteCodeOffset());
                    break l1;
                  } else if (it instanceof CodeExceptionGen) {
                    CodeExceptionGen exc = (CodeExceptionGen) it;
                    if (exc.getHandlerPC() == nih) {
                      stack_map_table[new_index].updateByteCodeOffset(
                          nih.getPosition()
                              - target_offsets[target_count - 1]
                              - 1
                              - stack_map_table[new_index].getByteCodeOffset());
                      break l1;
                    }
                  }
                }
              }
              nih = nih.getNext();
            }

            // Now we can copy the remaining stack map entries.
            if (need_full_maps) {
              // Must convert all remaining stack map entries to FULL.
              while (remainder > 0) {
                int stack_map_offset = stack_map_table[new_index].getByteCodeOffset();
                running_offset = running_offset + stack_map_offset + 1;
                stack = stack_types.get(running_offset);
                // System.out.printf("running_offset: %d, stack: %s%n", running_offset, stack);
                new_stack_map_table[new_index + target_count] =
                    new StackMapEntry(
                        Const.FULL_FRAME,
                        stack_map_offset,
                        calculate_live_local_types(mg, running_offset),
                        calculate_live_stack_types(stack),
                        pool.getConstantPool());
                new_index++;
                remainder--;
              }
            } else {
              System.arraycopy(
                  stack_map_table,
                  new_index,
                  new_stack_map_table,
                  new_index + target_count,
                  remainder);
            }
          }
          stack_map_table = new_stack_map_table;
        }
      }
    }

    debug_instrument.log("%n");
    print_il(new_end, "replace_inst #5");
  }
}


package com.davfx.ninio.core;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.io.LittleEndianDataInputStream;

public final class RawTcpdumpReader implements TcpdumpReader {
	private static final Logger LOGGER = LoggerFactory.getLogger(RawTcpdumpReader.class);

	private static final int MAX_SIZE = 100 * 1024;

	private final boolean any;

	public RawTcpdumpReader(boolean any) {
		this.any = any;
	}

	@Override
	public Iterable<String> tcpdumpOptions() {
		return Arrays.asList("-w", "-", "-U");
	}

	/*%%%
	private static String macToString(byte[] mac) {
		StringBuilder sb = new StringBuilder(18);
	    for (byte b : mac) {
	        if (sb.length() > 0)
	            sb.append(':');
	        sb.append(String.format("%02x", b));
	    }
	    return sb.toString();
	}
	*/

	@Override
	public void read(InputStream input, Handler handler) throws IOException {
		@SuppressWarnings("resource")
		LittleEndianDataInputStream in = new LittleEndianDataInputStream(input);
		LOGGER.debug("Reading tcpdump stream");
		long header = in.readInt() & 0xFFFFFFFF;
		if (header != 0xA1B2C3D4) {
			throw new IOException("Bad header: 0x" + Long.toHexString(header));
		}
		int majorVersionNumber = in.readUnsignedShort();
		int minorVersionNumber = in.readUnsignedShort();
		long zone = in.readInt() & 0xFFFFFFFF;
		@SuppressWarnings("unused")
		long timestampAccuracy = in.readInt() & 0xFFFFFFFF;
		long maxCaptureLength = in.readInt() & 0xFFFFFFFF;
		long dataLinkType = in.readInt() & 0xFFFFFFFF;
		LOGGER.debug("Tcpdump header recognized (version = {}/{}, zone = {}, maxCaptureLength = {}, dataLinkType = {})", majorVersionNumber, minorVersionNumber, zone, maxCaptureLength, dataLinkType);

		/*
		https://wiki.wireshark.org/Development/LibpcapFileFormat

		typedef struct pcap_hdr_s {
			guint32 magic_number;   /* magic number * /
			guint16 version_major;  /* major version number * /
			guint16 version_minor;  /* minor version number * /
			gint32  thiszone;       /* GMT to local correction * /
			guint32 sigfigs;        /* accuracy of timestamps * /
			guint32 snaplen;        /* max length of captured packets, in octets * /
			guint32 network;        /* data link type * /
		} pcap_hdr_t;
		*/

		while (true) {
			try {
				//%% LOGGER.debug("Tcpdump loop, step 1");
				/*
				typedef struct pcaprec_hdr_s {
					guint32 ts_sec;         /* timestamp seconds * /
					guint32 ts_usec;        /* timestamp microseconds * /
					guint32 incl_len;       /* number of octets of packet saved in file * /
					guint32 orig_len;       /* actual length of packet * /
				} pcaprec_hdr_t;
				*/
				long timestampSeconds = in.readInt() & 0xFFFFFFFF;
				long timestampMicroSeconds = in.readInt() & 0xFFFFFFFF;
				@SuppressWarnings("unused")
				int savedLength = in.readInt();
				int actualLength = in.readInt();

				double timestamp = ((double) timestampSeconds) + (((double) timestampMicroSeconds) / 1_000_000d); // sec, usec

				byte[] destinationMac = new byte[6];
				byte[] sourceMac = new byte[6];
				in.readFully(destinationMac);
				in.readFully(sourceMac);

				//%% System.out.println("mac0="+macToString(destinationMac));
				//%% System.out.println("mac1="+macToString(sourceMac));

				//IPV4 UNICAST: 0800
				//IPV6 UNICAST: 86dd
				//IPV4 MULTICAST: 00000800
				//IPV6 MULTICAST: 000086dd
				byte[] unused = new byte[any ? 4 : 2];
				in.readFully(unused);

				LOGGER.trace("Tcpdump packet ({} bytes)", actualLength);

				int l = actualLength - destinationMac.length - sourceMac.length - unused.length;

				if (l > MAX_SIZE) {
					throw new IOException("Packet too big: " + l);
				}

				byte[] bytes = new byte[l];
				in.readFully(bytes);
				IpPacketReadUtils.read(timestamp, bytes, 0, bytes.length, handler);
			} catch (EOFException eof) {
				break;
			}
		}
	}
}


package com.davfx.ninio.core.v4;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.davfx.ninio.core.dependencies.Dependencies;
import com.davfx.ninio.util.ClassThreadFactory;
import com.davfx.ninio.util.ConfigUtils;
import com.davfx.ninio.util.DateUtils;
import com.google.common.util.concurrent.AtomicDouble;
import com.typesafe.config.Config;

public final class Supervision {
	private static final Logger LOGGER = LoggerFactory.getLogger(Supervision.class);

	private static final Config CONFIG = ConfigUtils.load(new Dependencies()).getConfig(com.davfx.ninio.core.TcpSocket.class.getPackage().getName()); //TODO Set class to Supervision.class
	private static final double SUPERVISION_DISPLAY = ConfigUtils.getDuration(CONFIG, "supervision.tcp.display"); //TODO Change this config key

	private static double floorTime(double now, double period) {
    	double precision = 1000d;
    	long t = (long) (now * precision);
    	long d = (long) (period * precision);
    	return (t - (t % d)) / precision;
	}

	private static final ConcurrentHashMap<String, AtomicDouble> counters = new ConcurrentHashMap<>();
	static {
		double now = DateUtils.now();
		double startDisplay = SUPERVISION_DISPLAY - (now - floorTime(now, SUPERVISION_DISPLAY));

		ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(new ClassThreadFactory(Supervision.class, true));

		executor.scheduleAtFixedRate(new Runnable() {
			@Override
			public void run() {
				for (Map.Entry<String, AtomicDouble> e : counters.entrySet()) {
					double v = e.getValue().getAndSet(0d);
					LOGGER.trace("[Supervision/{}] {}", e.getKey(), v);
				}
			}
		}, (long) (startDisplay * 1000d), (long) (SUPERVISION_DISPLAY * 1000d), TimeUnit.MILLISECONDS);
	}

	public static interface Supervise {
		void set(double value);
	}

	public static Supervise supervise(String counterName) {
		final AtomicDouble counter = new AtomicDouble(0d);
		counters.put(counterName, counter);
		return new Supervise() {
			@Override
			public void set(double value) {
				while (true) {
					double current = counter.get();
					if (current >= value) {
						break;
					}
					if (counter.compareAndSet(current, value)) {
						break;
					}
				}
			}
		};
	}
}


package com.davfx.ninio.ssh;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;

import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.davfx.ninio.core.Address;
import com.davfx.ninio.core.Connection;
import com.google.common.primitives.Ints;

final class UncipheringConnection implements Connection {
	private static final Logger LOGGER = LoggerFactory.getLogger(UncipheringConnection.class);

	private final Connection wrappee;
	private Cipher cipher = null;
	private Mac mac = null;
	private int sequence = 0;

	private ByteBuffer lengthBuffer = ByteBuffer.allocate(Ints.BYTES);
	private int count = 0;

	private ByteBuffer firstBuffer = null;
	private ByteBuffer remainingBuffer = null;
	private ByteBuffer uncipheredFirstBuffer = null;
	private ByteBuffer uncipheredRemainingBuffer = null;
	private ByteBuffer macBuffer = null;
	private int state = 0;

	public UncipheringConnection(Connection wrappee) {
		this.wrappee = wrappee;
	}

    /*
	Initial IV client to server:     HASH (K || H || "A" || session_id)
	Initial IV server to client:     HASH (K || H || "B" || session_id)
	Encryption key client to server: HASH (K || H || "C" || session_id)
	Encryption key server to client: HASH (K || H || "D" || session_id)
	Integrity key client to server:  HASH (K || H || "E" || session_id)
	Integrity key server to client:  HASH (K || H || "F" || session_id)
	*/
	public void init(String encryptionAlgorithm, String cipherAlgorithm, int keyLength, String macAlgorithm, byte[] K, byte[] H, byte[] sessionId) throws GeneralSecurityException {
		MessageDigest sha = MessageDigest.getInstance("SHA-1");

		sha.reset();
		sha.update(new SshPacketBuilder().writeMpInt(K).append(H).writeByte('B').append(sessionId).finish());
		byte[] iv = sha.digest();

		sha.reset();
		sha.update(new SshPacketBuilder().writeMpInt(K).append(H).writeByte('D').append(sessionId).finish());
		byte[] cipherKey = sha.digest();

		try {
			cipher = Cipher.getInstance(encryptionAlgorithm + "/" + cipherAlgorithm + "/NoPadding");

			iv = CipherUtils.expandKey(K, H, iv, sha, cipher.getBlockSize());
			cipherKey = CipherUtils.expandKey(K, H, cipherKey, sha, keyLength);

			iv = CipherUtils.shrinkKey(iv, cipher.getBlockSize());
			cipherKey = CipherUtils.shrinkKey(cipherKey, keyLength);

			cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(cipherKey, encryptionAlgorithm), new IvParameterSpec(iv));

			sha.reset();
			sha.update(new SshPacketBuilder().writeMpInt(K).append(H).writeByte('F').append(sessionId).finish());
			byte[] macKey = sha.digest();

			mac = Mac.getInstance(macAlgorithm);

			macKey = CipherUtils.expandKey(K, H, macKey, sha, mac.getMacLength());
			macKey = CipherUtils.shrinkKey(macKey, mac.getMacLength());

			mac.init(new SecretKeySpec(macKey, macAlgorithm));

			firstBuffer = ByteBuffer.allocate(cipher.getBlockSize());
			macBuffer = ByteBuffer.allocate(mac.getMacLength());
		} catch (GeneralSecurityException e) {
			cipher = null;
			mac = null;
			throw e;
		}
	}

	@Override
	public void received(Address address, ByteBuffer b) {
		if ((cipher == null) || (mac == null)) {
			int p = b.position();
			while (b.hasRemaining()) {
				if (count == 0) {
					ByteBufferUtils.transfer(b, lengthBuffer);
					if (lengthBuffer.position() == lengthBuffer.capacity()) {
						lengthBuffer.flip();
						count = lengthBuffer.getInt();
						lengthBuffer.clear();
						sequence++;
					}
				}
				if (count > 0) {
					int c = Math.min(count, b.remaining());
					b.position(b.position() + c);
					count -= c;
				}
			}
			b.position(p);
			wrappee.received(address, b);
			return;
		}

		while (b.hasRemaining()) {
			if (state == 0) {
				ByteBufferUtils.transfer(b, firstBuffer);
				if (firstBuffer.position() == firstBuffer.capacity()) {
					firstBuffer.flip();

					uncipheredFirstBuffer = ByteBuffer.allocate(firstBuffer.remaining());
					try {
						cipher.update(firstBuffer, uncipheredFirstBuffer);
					} catch (ShortBufferException e) {
						throw new RuntimeException(e);
					}
					uncipheredFirstBuffer.flip();
					firstBuffer.clear();
					int firstLength = uncipheredFirstBuffer.getInt() + 4;
					remainingBuffer = ByteBuffer.allocate(firstLength - uncipheredFirstBuffer.capacity());
					uncipheredFirstBuffer.clear();
					state = 1;
				}
			}

			if (state == 1) {
				ByteBufferUtils.transfer(b, remainingBuffer);
				if (remainingBuffer.position() == remainingBuffer.capacity()) {
					remainingBuffer.flip();

					uncipheredRemainingBuffer = ByteBuffer.allocate(remainingBuffer.remaining());
					try {
						cipher.update(remainingBuffer, uncipheredRemainingBuffer);
					} catch (ShortBufferException e) {
						throw new RuntimeException(e);
					}
					uncipheredRemainingBuffer.flip();
					remainingBuffer = null;
					state = 2;
				}
			}

			if (state == 2) {
				ByteBufferUtils.transfer(b, macBuffer);
				if (macBuffer.position() == macBuffer.capacity()) {
					macBuffer.flip();

					ByteBuffer sequenceBuffer = ByteBuffer.allocate(Ints.BYTES);
					sequenceBuffer.putInt(sequence);
					sequence++;
					sequenceBuffer.flip();

					mac.reset();
					mac.update(sequenceBuffer);
					int p = uncipheredFirstBuffer.position();
					mac.update(uncipheredFirstBuffer);
					uncipheredFirstBuffer.position(p);
					p = uncipheredRemainingBuffer.position();
					mac.update(uncipheredRemainingBuffer);
					uncipheredRemainingBuffer.position(p);

					byte[] calculatedMac = new byte[mac.getMacLength()];
					try {
						mac.doFinal(calculatedMac, 0);
					} catch (ShortBufferException e) {
						throw new RuntimeException(e);
					} catch (IllegalStateException e) {
						throw new RuntimeException(e);
					}

					boolean valid = ByteBuffer.wrap(calculatedMac).equals(macBuffer);

					state = 0;

					ByteBuffer fbb = uncipheredFirstBuffer;
					ByteBuffer rbb = uncipheredRemainingBuffer;
					uncipheredFirstBuffer = null;
					uncipheredRemainingBuffer = null;
					macBuffer.clear();

					if (!valid) {
						LOGGER.error("Invalid MAC");
						//%%% connector.close();
						//%%% closing.closed();
					} else {
						wrappee.received(address, fbb);
						wrappee.received(address, rbb);
					}
				}
			}
		}
	}

	@Override
	public void closed() {
		wrappee.closed();
	}

	@Override
	public void failed(IOException ioe) {
		wrappee.failed(ioe);
	}

	@Override
	public void connected(Address address) {
		wrappee.connected(address);
	}
}


package com.davfx.ninio.http.service;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.davfx.ninio.core.Address;
import com.davfx.ninio.core.Nop;
import com.davfx.ninio.core.SendCallback;
import com.davfx.ninio.http.HttpContentReceiver;
import com.davfx.ninio.http.HttpContentSender;
import com.davfx.ninio.http.HttpHeaderKey;
import com.davfx.ninio.http.HttpListening;
import com.davfx.ninio.http.HttpListeningHandler;
import com.davfx.ninio.http.HttpMessage;
import com.davfx.ninio.http.HttpRequest;
import com.davfx.ninio.http.HttpResponse;
import com.davfx.ninio.http.HttpSpecification;
import com.davfx.ninio.http.HttpStatus;
import com.davfx.ninio.http.dependencies.Dependencies;
import com.davfx.ninio.http.service.HttpController.Http;
import com.davfx.ninio.http.service.HttpController.HttpAsyncOutput;
import com.davfx.ninio.util.ConfigUtils;
import com.davfx.ninio.util.SerialExecutor;
import com.google.common.base.Charsets;
import com.google.common.base.Optional;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
import com.google.common.net.HttpHeaders;
import com.typesafe.config.Config;

public final class HttpService implements HttpListeningHandler {

	private static final Logger LOGGER = LoggerFactory.getLogger(HttpService.class);

	private static final Config CONFIG = ConfigUtils.load(new Dependencies()).getConfig(HttpListening.class.getPackage().getName());
	private static final int DEFAULT_THREADS = CONFIG.getInt("service.threads");
	private static final int STREAMING_BUFFER_SIZE = CONFIG.getBytes("service.stream.buffer").intValue();
	private static final long SENDING_LIMIT = CONFIG.getBytes("service.produce.limit").longValue();

	public static interface Builder {
		/*%%
		interface HandlerBuilder {
			HandlerBuilder limit(double streamingRate);
		}
		*/

		Builder threading(int threads);
		Builder register(HttpServiceHandler handler);
		HttpService build();
	}

	/*%%
	private static final class HandlerElement {
		public HttpServiceHandler handler;
		public double streamingRate = Double.NaN;
	}
	*/

	public static Builder builder() {
		return new Builder() {
			private final ImmutableList.Builder<HttpServiceHandler> handlers = ImmutableList.builder();
			private int threads = DEFAULT_THREADS;

			@Override
			public Builder threading(int threads) {
				this.threads = threads;
				return this;
			}

			@Override
			public Builder register(HttpServiceHandler handler) {
				//%% final HandlerElement h = new HandlerElement();
				//%% h.handler = handler;
				handlers.add(handler);
				/*%%
				return new HandlerBuilder() {
					@Override
					public HandlerBuilder limit(double streamingRate) {
						h.streamingRate = streamingRate;
						return this;
					}
				};
				*/
				return this;
			}

			@Override
			public HttpService build() {
				return new HttpService(threads, handlers.build());
			}
		};
	}

	private final Executor[] executors;
	private final AtomicInteger loop = new AtomicInteger(0);
	private final ImmutableList<HttpServiceHandler> handlers;

	private HttpService(int threads, ImmutableList<HttpServiceHandler> handlers) {
		this.handlers = handlers;
		executors = new Executor[threads];
		for (int i = 0; i < executors.length; i++) {
			executors[i] = new SerialExecutor(HttpService.class);
		}
	}

	private void execute(Runnable r) {
		executors[Math.abs(loop.getAndIncrement()) % executors.length].execute(r);
	}

	@Override
	public void connected(Address address) {
	}

	@Override
	public void closed() {
		LOGGER.info("Service closed");
	}

	@Override
	public void failed(IOException ioe) {
		LOGGER.error("Service failed", ioe);
	}

	@Override
	public HttpContentReceiver handle(final HttpRequest request, final HttpResponseSender responseHandler) {
		return new HttpContentReceiver() {

			private final ByteBufferHandlerInputStream post = new ByteBufferHandlerInputStream();
			private HttpController.Http http = null;
			private final Object lock = new Object();
			private boolean closed = false;

			{
				execute(new Runnable() {
					@Override
					public void run() {
						HttpController.Http r = Http.notFound();

						for (HttpServiceHandler h : handlers) {
							try {
								r = h.handle(new HttpServiceRequest(request), new HttpPost() {
									private String string = null;
									private ImmutableMultimap<String, Optional<String>> parameters = null;

									@Override
									public synchronized InputStream stream() {
										if (string != null) {
											return null;
										}
										return post;
									}
									@Override
									public synchronized String toString() {
										if (string != null) {
											return string;
										}
										Charset charset = null;
										for (String h : request.headers.get(HttpHeaderKey.CONTENT_TYPE)) {
											charset = HttpContentType.getContentType(h);
										}
										if (charset == null) {
											charset = Charsets.UTF_8;
										}
										int l = post.waitFor();
										LOGGER.trace("Post size: {} bytes", l);
										byte[] b = new byte[l];
										try {
											try (DataInputStream in = new DataInputStream(post)) {
												in.readFully(b, 0, b.length);
											}
										} catch (IOException ioe) {
											LOGGER.error("Could not read post", ioe);
											return null;
										}
										string = new String(b, charset);
										return string;
									}
									@Override
									public synchronized ImmutableMultimap<String, Optional<String>> parameters() {
										if (parameters != null) {
											return parameters;
										}
										parameters = HttpRequest.parameters(HttpSpecification.PARAMETERS_START + toString());
										return parameters;
									}
								});

								if (r != null) {
									//%% streamingRate = h.streamingRate;
									break;
								}
							} catch (Exception e) {
								LOGGER.error("Http service error", e);
								r = Http.internalServerError();
								break;
							}
						}

						synchronized (lock) {
							if (closed) {
								if (r.stream != null) {
									try {
										r.stream.close();
									} catch (IOException ce) {
									}
								}
								return;
							}
							http = r;
							lock.notifyAll();
						}
					}
				});
			}

			@Override
			public void received(ByteBuffer buffer) {
				post.receiver.received(buffer);
			}

			@Override
			public void ended() {
				post.receiver.ended();
				execute(new Runnable() {
					@Override
					public void run() {
						synchronized (lock) {
							while (http == null) {
								if (closed) {
									return;
								}
								try {
									lock.wait();
								} catch (InterruptedException ie) {
								}
							}
						}

						if (http.async != null) {
							http.async.produce(new HttpAsyncOutput() {

								private int status = HttpStatus.OK;
								private String reason = HttpMessage.OK;
								private Multimap<String, String> headers = HashMultimap.create();
								private HttpContentSender sender = null;
								private long sending = 0L;

								private void send() {
									if (sender != null) {
										return;
									}
									if (!headers.containsKey(HttpHeaderKey.CONTENT_TYPE)) {
										headers.put(HttpHeaderKey.CONTENT_TYPE, HttpContentType.plainText());
									}
									sender = responseHandler.send(new HttpResponse(status, reason, ImmutableMultimap.copyOf(headers)));
								}

								@Override
								public synchronized void finish() {
									send();
									sender.finish();
								}

								@Override
								public synchronized void cancel() {
									send();
									sender.cancel();
								}

								@Override
								public synchronized HttpAsyncOutput ok() {
									status = HttpStatus.OK;
									reason = HttpMessage.OK;
									return this;
								}
								@Override
								public synchronized HttpAsyncOutput notFound() {
									status = HttpStatus.NOT_FOUND;
									reason = HttpMessage.NOT_FOUND;
									return this;
								}
								@Override
								public synchronized HttpAsyncOutput internalServerError() {
									status = HttpStatus.INTERNAL_SERVER_ERROR;
									reason = HttpMessage.INTERNAL_SERVER_ERROR;
									return this;
								}
								@Override
								public synchronized HttpAsyncOutput badRequest() {
									status = HttpStatus.BAD_REQUEST;
									reason = HttpMessage.BAD_REQUEST;
									return this;
								}

								@Override
								public synchronized HttpAsyncOutput header(String key, String value) {
									headers.put(key, value);
									return this;
								}
								@Override
								public HttpAsyncOutput contentType(String contentType) {
									return header(HttpHeaderKey.CONTENT_TYPE, contentType);
								}
								@Override
								public HttpAsyncOutput contentLength(long contentLength) {
									return header(HttpHeaderKey.CONTENT_LENGTH, String.valueOf(contentLength));
								}

								private synchronized void isSent(long size) {
									sending -= size;
									notifyAll();
								}

								@Override
								public synchronized HttpAsyncOutput produce(final ByteBuffer buffer) {
									send();

									while (sending > SENDING_LIMIT) {
										try {
											wait();
										} catch (InterruptedException ie) {
										}
									}

									final long size = buffer.remaining();
									sending += size;
									sender.send(buffer, new SendCallback() {
										@Override
										public void failed(IOException e) {
											isSent(size);
										}
										@Override
										public void sent() {
											isSent(size);
										}
									});
									return this;
								}

								@Override
								public HttpAsyncOutput produce(String buffer) {
									return produce(ByteBuffer.wrap(buffer.getBytes(Charsets.UTF_8)));
								}
							});

						} else {

							ImmutableMultimap.Builder<String, String> headers = ImmutableMultimap.builder();
							for (Map.Entry<String, Collection<String>> h : http.headers.asMap().entrySet()) {
								String key = h.getKey();
								if ((key.equals(HttpHeaders.CONTENT_TYPE)) || (key.equals(HttpHeaders.CONTENT_LENGTH)) || (key.equals(HttpHeaders.CONTENT_ENCODING))) {
									String last = null;
									for (String v : h.getValue()) {
										last = v;
									}
									if (last != null) {
										headers.put(key, last);
									}
								} else {
									for (String v : h.getValue()) {
										headers.put(key, v);
									}
								}
							}

							final HttpContentSender sender = responseHandler.send(new HttpResponse(http.status, http.reason, headers.build()));

							if (http.stream == null) {
								if (http.content != null) {
									sender.send(ByteBuffer.wrap(http.content.getBytes(Charsets.UTF_8)), new Nop());
								}
								sender.finish();
							} else {
								final InputStream stream = http.stream;

								SendCallback sendCallback = new SendCallback() {
									@Override
									public void sent() {
										byte[] b = new byte[STREAMING_BUFFER_SIZE];
										int l;
										try {
											l = stream.read(b);
										} catch (IOException ee) {
											LOGGER.error("Could not read stream", ee);
											try {
												stream.close();
											} catch (IOException ce) {
											}
											sender.cancel();
											return;
										}
										if (l <= 0) {
											try {
												stream.close();
											} catch (IOException ce) {
											}
											sender.finish();
										} else {
											sender.send(ByteBuffer.wrap(b, 0, l), this);
										}
									}

									@Override
									public void failed(IOException ioe) {
										LOGGER.error("Could not send stream", ioe);
									}
								};

								sendCallback.sent();
							}
						}
					}
				});
			}
		};
	}
}


package com.davfx.ninio.http;

import java.util.Deque;
import java.util.LinkedList;
import java.util.List;

import com.google.common.base.Optional;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;

public final class HttpRequest {

	public final HttpRequestAddress address;
	public final HttpMethod method;
	public final String path;
	public final ImmutableMultimap<String, String> headers;

	public HttpRequest(HttpRequestAddress address, HttpMethod method, String path, ImmutableMultimap<String, String> headers) {
		this.address = address;
		this.method = method;
		this.path = path;
		this.headers = headers;
	}
	public HttpRequest(HttpRequestAddress address, HttpMethod method, String path) {
		this(address, method, path, ImmutableMultimap.<String, String>of());
	}

	@Override
	public String toString() {
		return "[address=" + address + ", method=" + method + ", path=" + path + ", headers=" + headers + "]";
	}

	/*%%
	public static interface ResolveCallback extends Failing {
		void resolved(HttpRequest request);
	}

	public static void resolve(DnsConnecter dns, String url, ResolveCallback callback) {
		resolve(dns, url, HttpMethod.GET, ImmutableMultimap.<String, String>of(), callback);
	}
	public static void resolve(DnsConnecter dns, String url, final HttpMethod method, final ImmutableMultimap<String, String> headers, final ResolveCallback callback) {
		final UrlUtils.ParsedUrl parsedUrl = UrlUtils.parse(url);

		dns.request().resolve(parsedUrl.host, null).receive(new DnsReceiver() {
			@Override
			public void failed(IOException e) {
				callback.failed(e);
			}

			@Override
			public void received(byte[] ip) {
				callback.resolved(new HttpRequest(new Address(ip, parsedUrl.port), parsedUrl.secure, method, parsedUrl.path, UrlUtils.merge(headers, parsedUrl.headers)));
			}
		});
	}
	*/

	/*%%
	public static HttpRequest of(String url) {
		return of(url, HttpMethod.GET, ImmutableMultimap.<String, String>of());
	}
	public static HttpRequest of(String url, HttpMethod method, ImmutableMultimap<String, String> headers) {
		String protocol;
		boolean secure;
		int defaultPort;
		if (url.startsWith(HttpSpecification.PROTOCOL)) {
			protocol = HttpSpecification.PROTOCOL;
			secure = false;
			defaultPort = HttpSpecification.DEFAULT_PORT;
		} else if (url.startsWith(HttpSpecification.SECURE_PROTOCOL)) {
			protocol = HttpSpecification.SECURE_PROTOCOL;
			secure = true;
			defaultPort = HttpSpecification.DEFAULT_SECURE_PORT;
		} else {
			throw new IllegalArgumentException("URL must starts with " + HttpSpecification.PROTOCOL + " or " + HttpSpecification.SECURE_PROTOCOL);
		}

		int i = url.indexOf(HttpSpecification.PATH_SEPARATOR, protocol.length());
		if (i < 0) {
			String a = url.substring(protocol.length());
			int k = a.indexOf(':');
			Address address;
			if (k < 0) {
				address = Address.of(a, defaultPort);
			} else {
				String h = a.substring(0, k);
				int p = Integer.parseInt(a.substring(k + 1));
				address = Address.of(h, p);
			}
			return new HttpRequest(address, secure, method, String.valueOf(HttpSpecification.PATH_SEPARATOR), headers);
		} else {
			String a = url.substring(protocol.length(), i);
			int k = a.indexOf(':');
			Address address;
			if (k < 0) {
				address = Address.of(a, defaultPort);
			} else {
				String h = a.substring(0, k);
				int p = Integer.parseInt(a.substring(k + 1));
				address = Address.of(h, p);
			}
			return new HttpRequest(address, secure, method, url.substring(i), headers);
		}
	}
	*/

	public static ImmutableList<String> path(String path) {
		int i = path.indexOf(HttpSpecification.PARAMETERS_START);
		if (i < 0) {
			i = path.indexOf(HttpSpecification.HASH_SEPARATOR);
		} else {
			int j = path.indexOf(HttpSpecification.HASH_SEPARATOR);
			if ((j >= 0) && (j < i)) {
				i = j;
			}
		}

		if (i < 0) {
			i = path.length();
		}

		String p = path.substring(0, i);
		if (p.charAt(0) != HttpSpecification.PATH_SEPARATOR) {
			throw new IllegalArgumentException("Path must start with '" + HttpSpecification.PATH_SEPARATOR + "': " + p);
		}
		String s = p.substring(1);
		Deque<String> l = new LinkedList<>();
		for (String k : Splitter.on(HttpSpecification.PATH_SEPARATOR).splitToList(s)) {
			if (k.isEmpty()) {
				continue;
			}
			if (k.equals(".")) {
				continue;
			}
			if (k.equals("..")) {
				if (!l.isEmpty()) {
					l.removeLast();
				}
				continue;
			}
			l.add(k);
		}
		return ImmutableList.copyOf(l);
	}

	public static ImmutableMultimap<String, Optional<String>> parameters(String path) {
		int i = path.indexOf(HttpSpecification.PARAMETERS_START);
		if (i < 0) {
			return ImmutableMultimap.of();
		} else {
			int j = path.indexOf(HttpSpecification.HASH_SEPARATOR);
			String s;
			if (j < 0) {
				s = path.substring(i + 1);
			} else {
				s = path.substring(i + 1, j);
			}
			ImmutableMultimap.Builder<String, Optional<String>> m = ImmutableMultimap.builder();
			for (String kv : Splitter.on(HttpSpecification.PARAMETERS_SEPARATOR).splitToList(s)) {
				List<String> l = Splitter.on(HttpSpecification.PARAMETER_KEY_VALUE_SEPARATOR).splitToList(kv);
				if (!l.isEmpty()) {
					if (l.size() == 1) {
						m.put(UrlUtils.decode(l.get(0)), Optional.<String>absent());
					} else {
						m.put(UrlUtils.decode(l.get(0)), Optional.of(UrlUtils.decode(l.get(1))));
					}
				}
			}
			return m.build();
		}
	}
}


package com.davfx.ninio.core;

import java.io.IOException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.spi.SelectorProvider;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.davfx.ninio.core.dependencies.Dependencies;
import com.davfx.ninio.util.ClassThreadFactory;
import com.davfx.ninio.util.ConfigUtils;
import com.typesafe.config.Config;

final class InternalQueue implements Queue, AutoCloseable {
	private static final Logger LOGGER = LoggerFactory.getLogger(InternalQueue.class);

	private static final Config CONFIG = ConfigUtils.load(new Dependencies()).getConfig(TcpSocket.class.getPackage().getName());
	private static final double WAIT_ON_ERROR = ConfigUtils.getDuration(CONFIG, "queue.waitOnError");
	private static final double WAIT_ON_CLOSE = ConfigUtils.getDuration(CONFIG, "queue.waitOnClose");

	private final Selector selector;
	private final ConcurrentLinkedQueue<Runnable> toRun = new ConcurrentLinkedQueue<Runnable>(); // Using LinkedBlockingQueue my prevent OutOfMemory errors but may DEADLOCK

	public InternalQueue(final NinioPriority priority) {
		try {
			selector = SelectorProvider.provider().openSelector();
		} catch (IOException ioe) {
			LOGGER.error("Could not create selector", ioe);
			throw new RuntimeException(ioe);
		}

		Thread t = new ClassThreadFactory(InternalQueue.class).newThread(new Runnable() {
			@Override
			public void run() {
				while (true) {
					try {
						try {
							selector.select();
						} catch (ClosedSelectorException ce) {
							return;
						}
						Set<SelectionKey> s;
						try {
							s = selector.selectedKeys();
						} catch (ClosedSelectorException ce) {
							return;
						}
						if (s != null) {
							for (SelectionKey key : s) {
								try {
									((SelectionKeyVisitor) key.attachment()).visit(key);
								} catch (Throwable e) {
									LOGGER.error("[{}] Error in event handling", priority, e);
								}
							}
							s.clear();
						}
					} catch (Throwable e) {
						LOGGER.error("[{}] Error in selector ", priority, e);
						try {
							Thread.sleep((long) (WAIT_ON_ERROR * 1000d));
						} catch (InterruptedException ie) {
						}
					}

					while (!toRun.isEmpty()) {
						Runnable r = toRun.poll();
						try {
							r.run();
						} catch (Throwable e) {
							LOGGER.error("[{}] Error in running task", priority, e);
						}
					}
				}
			}
		});

		if (priority == NinioPriority.HIGH) {
			t.setPriority(Thread.MAX_PRIORITY);
		}
		t.setDaemon(true);
		t.start();
	}

	@Override
	public void execute(Runnable command) {
		toRun.add(command);
		selector.wakeup();
	}

	@Override
	public SelectionKey register(SelectableChannel channel) throws ClosedChannelException {
		return channel.register(selector, 0);
	}

	/*%%
	@Override
	public void waitFor() {
		Wait w = new Wait();
		execute(w);
		w.waitFor();
	}
	*/

	@Override
	public void close() {
		Thread t = new ClassThreadFactory(InternalQueue.class).newThread(new Runnable() {
			@Override
			public void run() {
				try {
					Thread.sleep((long) (WAIT_ON_CLOSE * 1000d));
				} catch (InterruptedException ie) {
				}
				try {
					selector.close();
				} catch (IOException e) {
				}
				selector.wakeup();
			}
		});

		t.setDaemon(true);
		t.start();
	}
}


package com.davfx.ninio.http;

import java.io.IOException;
import java.nio.ByteBuffer;

import com.davfx.ninio.core.Failing;

final class ContentLengthReader implements HttpContentReceiver {

	private final Failing failing;
	private final HttpContentReceiver wrappee;

	private final long contentLength;
	private long countRead = 0L;

	private boolean ended = false;

	public ContentLengthReader(long contentLength, Failing failing, HttpContentReceiver wrappee) {
		this.failing = failing;
		this.wrappee = wrappee;
		this.contentLength = contentLength;
	}

	// MUST sync-ly consume buffer
	@Override
	public void received(ByteBuffer buffer) {
		if (ended) {
			throw new IllegalStateException();
		}

		if ((countRead + buffer.remaining()) > contentLength) {
			ByteBuffer b = buffer.duplicate();
			int l = (int) (contentLength - countRead);
			b.limit(b.position() + l);
			buffer.position(buffer.position() + l);
			buffer = b;
		}

		countRead += buffer.remaining();
		if (buffer.hasRemaining()) {
			wrappee.received(buffer);
		}
		if (countRead == contentLength) {
			ended = true;
			wrappee.ended();
		}
	}

	@Override
	public void ended() {
		if (ended) {
			throw new IllegalStateException();
		}
		ended = true;
		failing.failed(new IOException("Connection closed prematurely"));
	}
}


package com.davfx.ninio.core.v4;

import java.io.IOException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.spi.SelectorProvider;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.davfx.ninio.core.dependencies.Dependencies;
import com.davfx.ninio.util.ClassThreadFactory;
import com.davfx.ninio.util.ConfigUtils;
import com.typesafe.config.Config;

public final class Ninio { // implements AutoCloseable {

	private static final Logger LOGGER = LoggerFactory.getLogger(Ninio.class);

	private static final Config CONFIG = ConfigUtils.load(new Dependencies()).getConfig(com.davfx.ninio.core.Ninio.class.getPackage().getName());
	private static final int THREADING = CONFIG.getInt("threading");
	private static final double WAIT_ON_ERROR = ConfigUtils.getDuration(CONFIG, "queue.waitOnError");

	private static final class Queues {
		private final NinioPriority priority;
		private final Object lock;
		// private boolean closed = false;
		private Selector selector = null;
		private ConcurrentLinkedQueue<Runnable> toRun = null;
		private int references = 0;

		private static final class InnerQueue implements Queue {
			private final Selector currentSelector;
			private final ConcurrentLinkedQueue<Runnable> currentToRun;
			private final Runnable finalization;
			public InnerQueue(Selector currentSelector, ConcurrentLinkedQueue<Runnable> currentToRun, Runnable finalization) {
				this.currentSelector = currentSelector;
				this.currentToRun = currentToRun;
				this.finalization = finalization;
			}
			@Override
			public SelectionKey register(SelectableChannel channel) throws ClosedChannelException {
				return channel.register(currentSelector, 0);
			}
			@Override
			public void execute(Runnable command) {
				currentToRun.add(command);
				currentSelector.wakeup();
			}
			@Override
			public void close() {
				finalization.run();
			}
		}

		public Queues(Object lock, NinioPriority priority) {
			this.lock = lock;
			this.priority = priority;
		}

		/*
		public void close() {
			Selector currentSelector;
			synchronized (lock) {
				if (closed) {
					return;
				}
				closed = true;
				currentSelector = selector;
			}
			if (currentSelector != null) {
				try {
					currentSelector.close();
				} catch (IOException e) {
				}
				currentSelector.wakeup();
			}
		}
		*/

		public Queue open() {
			Selector currentSelector;
			ConcurrentLinkedQueue<Runnable> currentToRun;

			Runnable finalization = new Runnable() {
				private boolean called = false;
				@Override
				public void run() {
					synchronized (lock) {
						if (called) {
							return;
						}
						/*
						if (closed) {
							return;
						}
						*/
						called = true;
						references--;
						if (references == 0) {
							LOGGER.debug("Queue not used anymore, closing the related thread");
							try {
								selector.close();
							} catch (IOException e) {
							}
							selector = null;
							toRun = null;
							references = 0;
						}
					}
				}
			};

			synchronized (lock) {
				/*
				if (closed) {
					return new Queue() {
						@Override
						public SelectionKey register(SelectableChannel channel) throws ClosedChannelException {
							throw new ClosedChannelException();
						}
						@Override
						public void execute(Runnable command) {
						}
						@Override
						public void close() {
						}
					};
				}
				*/

				if (selector != null) {
					references++;
					return new InnerQueue(selector, toRun, finalization);
				}

				LOGGER.debug("Starting a new queue thread");

				toRun = null;
				references = 0;

				try {
					selector = SelectorProvider.provider().openSelector();
				} catch (IOException ioe) {
					return null;
				}
				toRun = new ConcurrentLinkedQueue<Runnable>();
				references++;

				currentSelector = selector;
				currentToRun = toRun;
			}

			Thread t = new ClassThreadFactory(Queues.class).newThread(new Runnable() {
				@Override
				public void run() {
					while (true) {
						try {
							try {
								currentSelector.select();
							} catch (ClosedSelectorException ce) {
								return;
							}
							Set<SelectionKey> s;
							try {
								s = currentSelector.selectedKeys();
							} catch (ClosedSelectorException ce) {
								return;
							}
							if (s != null) {
								for (SelectionKey key : s) {
									try {
										((SelectionKeyVisitor) key.attachment()).visit(key);
									} catch (Throwable e) {
										LOGGER.error("Error in event handling", e);
									}
								}
								s.clear();
							}
						} catch (Throwable e) {
							LOGGER.error("Error in selector ", e);
							try {
								Thread.sleep((long) (WAIT_ON_ERROR * 1000d));
							} catch (InterruptedException ie) {
							}
						}

						while (!currentToRun.isEmpty()) {
							Runnable r = currentToRun.poll();
							try {
								r.run();
							} catch (Throwable e) {
								LOGGER.error("Error in running task", e);
							}
						}
					}
				}
			});

			if (priority == NinioPriority.HIGH) {
				t.setPriority(Thread.MAX_PRIORITY);
			}
			t.setDaemon(true);
			t.start();

			return new InnerQueue(currentSelector, currentToRun, finalization);
		}
	}

	private final Object lock = new Object();
	private final Queues[][] internalQueues;
	private int index = 0;

	public Ninio() {
		NinioPriority[] priorities = NinioPriority.values();
		internalQueues = new Queues[priorities.length][];
		for (int i = 0; i < internalQueues.length; i++) {
			NinioPriority priority = priorities[i];
			internalQueues[i] = new Queues[THREADING];
			for (int j = 0; j < internalQueues[i].length; j++) {
				internalQueues[i][j] = new Queues(lock, priority);
			}
		}
	}

	/*
	@Override
	public void close() {
		for (Queues[] q : internalQueues) {
			for (Queues internalQueue : q) {
				internalQueue.close();
			}
		}
	}
	*/

	public Queue register(NinioPriority priority) {
		int i;
		synchronized (lock) {
			i = index;
			index++;
			if (index == THREADING) {
				index = 0;
			}
		}
		return internalQueues[priority.ordinal()][i].open();
	}
}


package com.davfx.ninio.snmp;

import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;

import com.google.common.io.BaseEncoding;

public final class BerReader {
	private final ByteBuffer buffer;
	private final Deque<Integer> toReadLengthPositions = new LinkedList<Integer>();

	public BerReader(ByteBuffer buffer) {
		this.buffer = buffer;
	}

	private int doReadType(ByteBuffer buffer) throws IOException {
		int b = buffer.get() & 0xFF;
		if ((b & BerConstants.ASN_BIT8) == BerConstants.ASN_BIT8) {
			throw new IOException("No such object");
		}
		return b;
	}

	// Strict reading

	// checked
	private static int doReadLength(ByteBuffer buffer) throws IOException {
		int lengthbyte = buffer.get() & 0xFF;

		if ((lengthbyte & BerConstants.ASN_BIT8) == BerConstants.ASN_BIT8) {
			lengthbyte &= ~BerConstants.ASN_BIT8;
			if (lengthbyte == 0) {
				throw new IOException("Indefinite lengths are not supported");
			}
			if (lengthbyte > 4) {
				throw new IOException("Data length > 4 bytes are not supported");
			}
			int length = 0;
			for (int i = 0; i < lengthbyte; i++) {
				length <<= 8;
				length |= buffer.get() & 0xFF;
			}
			if (length < 0) {
				throw new IOException("SNMP does not support data lengths > 2^31");
			}
			return length;
		} else { // Short asnlength
			return lengthbyte;
		}
	}

	private static int doReadInteger(ByteBuffer buffer, int length) throws IOException {
		long value = 0L;
		for (int i = 0; i < length; i++) {
			int b = buffer.get() & 0xFF;
			if ((i == 0) && ((b & 0x80) == 0x80)) {
				value = 0xFFFFFFFF; // Negative, in two's complement form
			}
			value <<= 8;
			value |= b;
		}

		return (int) value;
	}

	//TODO Check the OPAQUE UINT64 format
	private static long doReadLong(ByteBuffer buffer, int length) throws IOException {
		long value = 0L;
		for (int i = 0; i < length; i++) {
			int b = buffer.get() & 0xFF;
			if ((i == 0) && ((b & 0x80) == 0x80)) {
				value = 0xFFFFFFFFL; // Negative, in two's complement form
			}
			value <<= 8;
			value |= b;
		}

		return value;
	}

	private static float doReadFloat(ByteBuffer buffer, int length) throws IOException {
		return buffer.getFloat();
	}

	//TODO Check the OPAQUE DOUBLE format
	private static double doReadDouble(ByteBuffer buffer, int length) throws IOException {
		return buffer.getDouble();
	}

	private static BigInteger doReadUnsignedLongInteger(ByteBuffer buffer, int length) throws IOException {
		byte[] b = new byte[length];
		buffer.get(b);

		return new BigInteger(b);

		/*
		long value = 0;
		for (int i = 0; i < length; i++) {
			long b = buffer.get() & 0xFFL;
			value <<= 8;
			value |= b;
		}
		return value;
		*/
	}

	public int readInteger() throws IOException {
		int type = doReadType(buffer);
		if (type != BerConstants.INTEGER) {
			throw new IOException("Wrong ASN.1 type. Not an integer: " + type);
		}
		int length = doReadLength(buffer);
		return doReadInteger(buffer, length);
	}

	private static long[] doReadOid(ByteBuffer buffer, int length) throws IOException {
		List<Long> values = new LinkedList<>();

		if (length == 0) {
			throw new IOException("Invalid OID");
		}

		{
			int b = buffer.get() & 0xFF;
			values.add((long) (b / 40));
			values.add((long) (b % 40));
		}

		length--;

		long value = 0L;
		while (length > 0) {
			int b = buffer.get() & 0xFF;

			value <<= 7;
			value |= (b & ~0x80);

			if ((b & 0x80) == 0) {
				values.add(value);
				value = 0L;
			}

			length--;
		}

		long[] v = new long[values.size()];
		int i = 0;
		for (long val : values) {
			v[i] = val;
			i++;
		}
		return v;
	}

	public Oid readOid() throws IOException {
		int type = doReadType(buffer);
		if (type != BerConstants.OID) {
			throw new IOException("Wrong type. Not an OID: " + type);
		}

		int length = doReadLength(buffer);
		return new Oid(doReadOid(buffer, length));
	}

	private static ByteBuffer doReadString(ByteBuffer buffer, int length) throws IOException {
		ByteBuffer b = ByteBuffer.wrap(buffer.array(), buffer.position(), length);
		buffer.position(buffer.position() + length);
		return b;
	}
	public ByteBuffer readBytes() throws IOException {
		int type = doReadType(buffer);
		if (type != BerConstants.OCTETSTRING) {
			throw new IOException("Wrong ASN.1 type. Not a string: " + type);
		}

		int length = doReadLength(buffer);
		return doReadString(buffer, length);
	}

	public void readNull() throws IOException {
		int type = doReadType(buffer);
		if (type != BerConstants.NULL) {
			throw new IOException("Wrong ASN.1 type. Not a null: " + type);
		}
		doReadLength(buffer);
	}

	public int beginReadSequence() throws IOException {
		int type = buffer.get() & 0xFF;
		if ((type & BerConstants.ASN_CONSTRUCTOR) != BerConstants.ASN_CONSTRUCTOR) {
			throw new IOException("Wrong ASN.1 type. Not a sequence: " + type);
		}
		int length = doReadLength(buffer);
		toReadLengthPositions.addFirst(buffer.position() + length);
		return type;
	}

	public void endReadSequence() throws IOException {
		int position = toReadLengthPositions.removeFirst();
		if (position != buffer.position()) {
			throw new IOException("Bad sequence: " + position + "!=" + buffer.position());
		}
	}

	public boolean hasRemainingInSequence() {
		int position = toReadLengthPositions.getFirst();
		return (buffer.position() < position);
	}

	public String readValue() throws IOException {
		return doReadValue(buffer, false);
	}

	private static String doReadValue(ByteBuffer buffer, boolean opaque) throws IOException {
	// public OidValue readOidValue() throws IOException {
		int type = buffer.get() & 0xFF;

		if ((type & BerConstants.ASN_BIT8) == BerConstants.ASN_BIT8) {
			if (!opaque) {
				int l = doReadLength(buffer);
				doReadString(buffer, l);
				return null;
			}
			type = buffer.get() & 0xFF; // OPAQUE wrapped type
		} else if (opaque) {
			int l = doReadLength(buffer);
			doReadString(buffer, l);
			return null;
		}

		int length = doReadLength(buffer);

		if (type == BerConstants.INTEGER) {
			return String.valueOf(doReadInteger(buffer, length));
			// return new OidValue(OidValue.Type.NUMBER, doReadInteger(length));
		}

		if (type == BerConstants.TIMETICKS) {
			return String.valueOf(doReadUnsignedLongInteger(buffer, length));
			// return new OidValue(OidValue.Type.TIME, doReadUnsignedLongInteger(length));
		}

		if ((type == BerConstants.COUNTER32) || (type == BerConstants.GAUGE32) || (type == BerConstants.COUNTER64) || (type == BerConstants.UNSIGNEDINTEGER32)) {
			return String.valueOf(doReadUnsignedLongInteger(buffer, length));
			// return new OidValue(OidValue.Type.NUMBER, doReadUnsignedLongInteger(length));
		}

		if (type == BerConstants.NULL) {
			if (length != 0) {
				throw new IOException("Invalid Null encoding, length is not zero");
			}
			return null;
			// return new OidValue(OidValue.Type.NULL);
		}

		if (type == BerConstants.OID) {
			return new Oid(doReadOid(buffer, length)).toString();
			// return new OidValue(OidValue.Type.OID, doReadOid(length));
		}

		if (type == BerConstants.IPADDRESS) {
			return asIpString(doReadString(buffer, length));
			// return new OidValue(OidValue.Type.IPADDRESS, doReadString(length));
		}

		if (type == BerConstants.OCTETSTRING) {
			return string(doReadString(buffer, length));
			// return new OidValue(OidValue.Type.STRING, doReadString(length));
		}

		if (type == BerConstants.OPAQUE) {
			ByteBuffer wrapped = doReadString(buffer, length);
			return doReadValue(wrapped, true);
			//%% return new OidValue(OidValue.Type.STRING, doReadString(length));
		}

		if (type == BerConstants.OPAQUE_FLOAT) {
			return String.valueOf(doReadFloat(buffer, length));
		}
		if (type == BerConstants.OPAQUE_DOUBLE) {
			return String.valueOf(doReadDouble(buffer, length));
		}
		if (type == BerConstants.OPAQUE_INTEGER64) {
			return String.valueOf(doReadLong(buffer, length));
		}
		if (type == BerConstants.OPAQUE_UNSIGNEDINTEGER64) {
			return String.valueOf(doReadUnsignedLongInteger(buffer, length));
		}

		// if ((type == BerConstants.ASN_BITSTRING) || (type ==
		// BerConstants.OPAQUE) || (type == BerConstants.NSAPADDRESS)) {
		return string(doReadString(buffer, length));
		// return new OidValue(OidValue.Type.OTHER, doReadString(length));
		// }
	}

	private static String asIpString(ByteBuffer bb) {
		ByteBuffer bytes = bb.duplicate();
		if (bytes.remaining() == 4) {
			StringBuilder b = new StringBuilder();
			while (bytes.hasRemaining()) {
				int k = bytes.get() & 0xFF;
				if (b.length() > 0) {
					b.append('.');
				}
				b.append(String.valueOf(k));
			}
			return b.toString();
		}

		StringBuilder b = new StringBuilder();
		while (bytes.hasRemaining()) {
			int k = ((bytes.get() & 0xFF) << 8) | (bytes.get() & 0xFF);
			String s = Integer.toHexString(k);
			if (b.length() > 0) {
				b.append(':');
			}
			b.append(s);
		}
		return b.toString();
	}

	private static String string(ByteBuffer bb) {
		ByteBuffer bytes = bb.duplicate();
		while (bytes.hasRemaining()) {
			int c = bytes.get() & 0xFF;
			if (((c < 32) && (c != 10) && (c != 13)) || (c >= 127)) {
				return BaseEncoding.base16().encode(bb.array(), bb.position(), bb.remaining());
			}
		}
		return BerPacketUtils.string(bb);
	}
}


package com.davfx.ninio.http;

import java.io.IOException;
import java.nio.ByteBuffer;

import com.davfx.ninio.core.Failing;

final class ChunkedReader implements HttpContentReceiver {

	private final Failing failing;
	private final HttpContentReceiver wrappee;

	private final LineReader lineReader = new LineReader();
	private boolean chunkHeaderRead = false;
	private int chunkLength;
	private int chunkCountRead;

	private boolean ended = false;

	public ChunkedReader(Failing failing, HttpContentReceiver wrappee) {
		this.failing = failing;
		this.wrappee = wrappee;
	}

	// MUST sync-ly consume buffer
	@Override
	public void received(ByteBuffer buffer) {
		if (ended) {
			throw new IllegalStateException();
		}
		while (true) {

			while (!chunkHeaderRead) {
				String line = lineReader.handle(buffer);
				if (line == null) {
					return;
				}
				try {
					chunkLength = Integer.parseInt(line, 16);
				} catch (NumberFormatException e) {
					ended = true;
					failing.failed(new IOException("Invalid chunk size: " + line));
					return;
				}
				chunkHeaderRead = true;
				chunkCountRead = 0;
			}

			if (chunkHeaderRead) {
				if (chunkCountRead < chunkLength) {
					if (!buffer.hasRemaining()) {
						return;
					}

					long toRead = chunkLength - chunkCountRead;

			    	ByteBuffer d = buffer.duplicate();
			    	if (toRead >= 0) {
						if (d.remaining() > toRead) {
							d.limit((int) (buffer.position() + toRead));
						}
			    	}

			    	chunkCountRead += d.remaining();
					buffer.position((int) (buffer.position() + d.remaining()));

					wrappee.received(d);
				}

				if (chunkCountRead == chunkLength) {
					while (chunkHeaderRead) {
						String line = lineReader.handle(buffer);
						if (line == null) {
							return;
						}
						if (!line.isEmpty()) {
							ended = true;
							failing.failed(new IOException("Invalid chunk footer"));
							return;
						}
						chunkHeaderRead = false;
					}

					if (chunkLength == 0) {
						ended = true;
						wrappee.ended();
						return;
					}
				}
			}

		}
	}

	@Override
	public void ended() {
		if (ended) {
			throw new IllegalStateException();
		}
		ended = true;
		failing.failed(new IOException("Connection closed prematurely"));
	}
}


package com.davfx.ninio.snmp;

import java.nio.ByteBuffer;

public final class Version3PacketBuilder {
/*
	private static interface Value {
		BerPacket ber();
	}

	private static final class OidValue {
		public final Oid oid;
		public final Value value;
		public OidValue(Oid oid, Value value) {
			this.oid = oid;
			this.value = value;
		}
	}
*/

	private final ByteBuffer buffer;

	private static final class AuthBerPacket implements BerPacket {
		private static final int LENGTH = 12;
		private final ByteBuffer lengthBuffer = BerPacketUtils.lengthBuffer(LENGTH);
		private int position = -1;
		public AuthBerPacket() {
		}

		@Override
		public void write(ByteBuffer buffer) {
			BerPacketUtils.writeHeader(BerConstants.OCTETSTRING, lengthBuffer, buffer);
			position = buffer.position();
			buffer.put(new byte[LENGTH]);
		}
		@Override
		public ByteBuffer lengthBuffer() {
			return lengthBuffer;
		}
		@Override
		public int length() {
			return LENGTH;
		}
	}

	private static final class PrivacyBerPacket implements BerPacket {
		private static final int LENGTH = 8;
		private final ByteBuffer lengthBuffer = BerPacketUtils.lengthBuffer(LENGTH);
		private int position = -1;
		public PrivacyBerPacket() {
		}

		@Override
		public void write(ByteBuffer buffer) {
			BerPacketUtils.writeHeader(BerConstants.OCTETSTRING, lengthBuffer, buffer);
			position = buffer.position();
			buffer.put(new byte[LENGTH]);
		}
		@Override
		public ByteBuffer lengthBuffer() {
			return lengthBuffer;
		}
		@Override
		public int length() {
			return LENGTH;
		}
	}

	private Version3PacketBuilder(AuthRemoteEngine authEngine, int requestId, int type, int bulkLength, Oid oid) {
		authEngine.renewTime();

		boolean encrypt = false;
		int securityFlags = 0x0;
		if (authEngine.getId() != null) {
			if (authEngine.getAuthLogin() != null) {
				securityFlags |= BerConstants.VERSION_3_AUTH_FLAG;
				if (authEngine.usePriv()) {
					securityFlags |= BerConstants.VERSION_3_PRIV_FLAG;
					encrypt = true;
				}
			}
		}
		securityFlags |= BerConstants.VERSION_3_REPORTABLE_FLAG;

		SequenceBerPacket root = new SequenceBerPacket(BerConstants.SEQUENCE)
			.add(new IntegerBerPacket(BerConstants.VERSION_3))
			.add(new SequenceBerPacket(BerConstants.SEQUENCE)
				.add(new IntegerBerPacket(authEngine.incPacketNumber())) // Packet number
				.add(new IntegerBerPacket(65535)) // Max packet size
				.add(new BytesBerPacket(ByteBuffer.wrap(new byte[] { (byte) securityFlags })))
				.add(new IntegerBerPacket(BerConstants.VERSION_3_USM_SECURITY_MODEL)));

		AuthBerPacket auth = new AuthBerPacket();
		PrivacyBerPacket priv = new PrivacyBerPacket();

		root.add(new BytesSequenceBerPacket(new SequenceBerPacket(BerConstants.SEQUENCE)
				.add(new BytesBerPacket((authEngine.getId() == null) ? ByteBuffer.allocate(0) : ByteBuffer.wrap(authEngine.getId())))
				.add(new IntegerBerPacket(authEngine.getBootCount()))
				.add(new IntegerBerPacket(authEngine.getTime()))
				.add(new BytesBerPacket(BerPacketUtils.bytes(authEngine.getAuthLogin())))
				.add(auth)
				.add(priv)
			));

		SequenceBerPacket seq = new SequenceBerPacket(BerConstants.SEQUENCE);
		seq.add(new OidBerPacket(oid)).add(new NullBerPacket());

		BerPacket pduPacket = new SequenceBerPacket(BerConstants.SEQUENCE)
			.add(new BytesBerPacket((authEngine.getId() != null) ? ByteBuffer.wrap(authEngine.getId()) : ByteBuffer.allocate(0)))
			.add(new BytesBerPacket((authEngine.authRemoteSpecification.contextName != null) ? BerPacketUtils.bytes(authEngine.authRemoteSpecification.contextName) : ByteBuffer.allocate(0)))
			.add(new SequenceBerPacket(type)
				.add(new IntegerBerPacket(requestId))
				.add(new IntegerBerPacket(0))
				.add(new IntegerBerPacket(bulkLength))
				.add(new SequenceBerPacket(BerConstants.SEQUENCE)
					.add(seq)));

		if (encrypt) {
			ByteBuffer decryptedBuffer = ByteBuffer.allocate(BerPacketUtils.typeAndLengthBufferLength(pduPacket.lengthBuffer()) + pduPacket.length());
			pduPacket.write(decryptedBuffer);
			decryptedBuffer.flip();
			root.add(new BytesBerPacket(authEngine.encrypt(decryptedBuffer)));
		} else {
			root.add(pduPacket);
		}

		buffer = ByteBuffer.allocate(BerPacketUtils.typeAndLengthBufferLength(root.lengthBuffer()) + root.length());
		root.write(buffer);
		buffer.flip();

		if (encrypt) {
			writeInside(buffer, priv.position, authEngine.getEncryptionParameters());
		}

		writeInside(buffer, auth.position, authEngine.hash(buffer));
	}

	private static void writeInside(ByteBuffer buffer, int position, byte[] bytes) {
		int p = buffer.position();
		buffer.position(position);
		buffer.put(bytes);
		buffer.position(p);
	}

/*
	private static Iterable<OidValue> single(Oid oid) {
		List<OidValue> l = new LinkedList<>();
		l.add(new OidValue(oid, new Value() {
			@Override
			public BerPacket ber() {
				return new NullBerPacket();
			}
		}));
		return l;
	}
*/

	public static Version3PacketBuilder getBulk(AuthRemoteEngine authEngine, int requestId, Oid oid, int bulkLength) {
		return new Version3PacketBuilder(authEngine, requestId, BerConstants.GETBULK, bulkLength, oid);
	}
	public static Version3PacketBuilder get(AuthRemoteEngine authEngine, int requestId, Oid oid) {
		return new Version3PacketBuilder(authEngine, requestId, BerConstants.GET, 0, oid);
	}
	public static Version3PacketBuilder getNext(AuthRemoteEngine authEngine, int requestId, Oid oid) {
		return new Version3PacketBuilder(authEngine, requestId, BerConstants.GETNEXT, 0, oid);
	}

/*
	public static Version3PacketBuilder trap(AuthRemoteEngine authEngine, int requestId, final Oid trapOid, Iterable<SnmpResult> oidValues) {
		List<OidValue> l = new LinkedList<>();
		l.add(new OidValue(BerConstants.TIMESTAMP_OID, new Value() {
			@Override
			public BerPacket ber() {
				return new IntegerBerPacket((int) (System.currentTimeMillis() / 10L));
			}
		}));
		l.add(new OidValue(BerConstants.TRAP_OID, new Value() {
			@Override
			public BerPacket ber() {
				return new OidBerPacket(trapOid);
			}
		}));
		for (final SnmpResult oidValue : oidValues) {
			l.add(new OidValue(oidValue.oid, new Value() {
				@Override
				public BerPacket ber() {
					return new BytesBerPacket(BerPacketUtils.bytes(oidValue.value));
				}
			}));
		}
		return new Version3PacketBuilder(authEngine, requestId, BerConstants.TRAP, 0, l);
	}
*/

	public ByteBuffer getBuffer() {
		return buffer;
	}
}


package com.davfx.ninio.string;

import java.io.IOException;
import java.io.Reader;
import java.util.LinkedList;
import java.util.List;

import com.google.common.collect.Lists;

public final class Extractor {
	private String[] from = new String[0];
	private String[] to = new String[0];
	private Reader on = null;
	private final String found;
	private StringBuilder accumulator = null;
	private final List<StringBuilder> accumulators;

	private boolean ignoreWhitespaces = false;

	public Extractor() {
		this(null, new LinkedList<StringBuilder>());
	}

	private Extractor(String found, List<StringBuilder> accumulators) {
		this.found = found;
		this.accumulators = accumulators;
	}

	public Extractor ignoreWhitespaces(boolean ignoreWhitespaces) {
		this.ignoreWhitespaces = ignoreWhitespaces;
		return this;
	}

	public Extractor from(String... from) {
		this.from = from;
		return this;
	}

	public Extractor to(String... to) {
		this.to = to;
		return this;
	}

	public Extractor on(Reader on) {
		this.on = on;
		return this;
	}

	// Do not call twice
	public Extractor accumulate() {
		accumulator = new StringBuilder();
		accumulators.add(accumulator);
		return this;
	}

	public String found() {
		return found;
	}

	public String accumulated() {
		return accumulator.toString();
	}

	public String contents() throws IOException {
		accumulate();
		extract();
		return accumulated();
	}

	/*%%%%%%%
	public String contents() throws IOException {
		StringBuilder b = null;
		if (from.length == 0) {
			b = new StringBuilder();
		}

		int[] iFrom = new int[from.length]; // Filled with zeros
		int[] iTo = new int[to.length]; // Filled with zeros
		while (true) {
			int k = on.read();
			if (k < 0) {
				if (b == null) {
					return null;
				}
				if (to.length == 0) {
					return b.toString();
				}
				return null;
			}

			char c = (char) k;

			if (b != null) {
				b.append(c);
			}

			if (b == null) {
				for (int u = 0; u < iFrom.length; u++) {
					if (c == from[u].charAt(iFrom[u])) {
						iFrom[u]++;
						if (iFrom[u] == from[u].length()) {
							b = new StringBuilder();
							break;
						}
					} else if (iFrom[u] > 0) {
						iFrom[u] = 0;
						if (c == from[u].charAt(iFrom[u])) {
							iFrom[u]++;
							if (iFrom[u] == from[u].length()) {
								b = new StringBuilder();
								break;
							}
						}
					}
				}
			}

			for (int u = 0; u < iTo.length; u++) {
				if (c == to[u].charAt(iTo[u])) {
					iTo[u]++;
					if (iTo[u] == to[u].length()) {
						if (b == null) {
							return null;
						}
						b.delete(b.length() - to[u].length(), b.length());
						return b.toString();
					}
				} else if (iTo[u] > 0) {
					iTo[u] = 0;
					if (c == to[u].charAt(iTo[u])) {
						iTo[u]++;
						if (iTo[u] == to[u].length()) {
							if (b == null) {
								return null;
							}
							b.delete(b.length() - to[u].length(), b.length());
							return b.toString();
						}
					}
				}
			}
		}
	}*/

	public Extractor extract() throws IOException {
		//%%%%% LOGGER.debug("---> to = {}", Arrays.asList(to));

		int[] iFrom = new int[from.length]; // Filled with zeros
		int[] iTo = new int[to.length]; // Filled with zeros
		while (true) {
			int k = on.read();
			if (k < 0) {
				return null;
			}

			char c = (char) k;

			for (StringBuilder b : accumulators) {
				b.append(c);
			}

			if (ignoreWhitespaces && Character.isWhitespace(c)) {
				continue;
			}

			for (int u = 0; u < iFrom.length; u++) {
				if (c == from[u].charAt(iFrom[u])) {
					iFrom[u]++;
					if (iFrom[u] == from[u].length()) {
						return new Extractor(from[u], Lists.newLinkedList(accumulators)).on(on).to(to).ignoreWhitespaces(ignoreWhitespaces);
					}
				} else if (iFrom[u] > 0) {
					iFrom[u] = 0;
					if (c == from[u].charAt(iFrom[u])) {
						iFrom[u]++;
						if (iFrom[u] == from[u].length()) {
							return new Extractor(from[u], Lists.newLinkedList(accumulators)).on(on).to(to).ignoreWhitespaces(ignoreWhitespaces);
						}
					}
				}
			}

			for (int u = 0; u < iTo.length; u++) {
				if (c == to[u].charAt(iTo[u])) {
					iTo[u]++;
					if (iTo[u] == to[u].length()) {
						if (accumulator != null) {
							accumulator.delete(accumulator.length() - to[u].length(), accumulator.length());
						}
						return null;
					}
				} else if (iTo[u] > 0) {
					iTo[u] = 0;
					if (c == to[u].charAt(iTo[u])) {
						iTo[u]++;
						if (iTo[u] == to[u].length()) {
							if (accumulator != null) {
								accumulator.delete(accumulator.length() - to[u].length(), accumulator.length());
							}
							return null;
						}
					}
				}
			}
		}
	}
}


package com.davfx.ninio.ssh;

import java.security.MessageDigest;

final class CipherUtils {
	private CipherUtils() {
	}

	  /*
	   * RFC 4253  7.2. Output from Key Exchange
	   * If the key length needed is longer than the output of the HASH, the
	   * key is extended by computing HASH of the concatenation of K and H and
	   * the entire key so far, and appending the resulting bytes (as many as
	   * HASH generates) to the key.  This process is repeated until enough
	   * key material is available; the key is taken from the beginning of
	   * this value.  In other words:
	   *   K1 = HASH(K || H || X || session_id)   (X is e.g., "A")
	   *   K2 = HASH(K || H || K1)
	   *   K3 = HASH(K || H || K1 || K2)
	   *   ...
	   *   key = K1 || K2 || K3 || ...
	   */
	public static byte[] expandKey(byte[] K, byte[] H, byte[] key, MessageDigest hash, int requiredLength) {
		byte[] result = key;
		while (result.length < requiredLength) {
			SshPacketBuilder buf = new SshPacketBuilder();
			buf.writeMpInt(K);
			buf.append(H);
			buf.append(result);
			hash.update(buf.finish());
			byte[] d = hash.digest();

			byte[] tmp = new byte[result.length + d.length];
			System.arraycopy(result, 0, tmp, 0, result.length);
			System.arraycopy(d, 0, tmp, result.length, d.length);
			result = tmp;
		}
		return result;
	}

	public static byte[] shrinkKey(byte[] key, int requiredLength) {
		if (key.length > requiredLength) {
			byte[] tmp = new byte[requiredLength];
			System.arraycopy(key, 0, tmp, 0, tmp.length);
			key = tmp;
		}
		return key;
	}

}