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

package org.apache.hadoop.security.token;

import java.io.*;
import java.util.Arrays;

import org.apache.hadoop.HadoopIllegalArgumentException;
import org.apache.hadoop.io.*;
import org.apache.hadoop.security.token.delegation.AbstractDelegationTokenIdentifier;
import org.apache.hadoop.security.token.delegation.TestDelegationToken.TestDelegationTokenIdentifier;
import org.apache.hadoop.security.token.delegation.TestDelegationToken.TestDelegationTokenSecretManager;
import org.junit.Test;

import static org.apache.hadoop.test.LambdaTestUtils.intercept;
import static org.junit.Assert.*;

/** Unit tests for Token */
public class TestToken {

  static boolean isEqual(Object a, Object b) {
    return a == null ? b == null : a.equals(b);
  }

  static boolean checkEqual(Token<TokenIdentifier> a, Token<TokenIdentifier> b) {
    return Arrays.equals(a.getIdentifier(), b.getIdentifier())
        && Arrays.equals(a.getPassword(), b.getPassword())
        && isEqual(a.getKind(), b.getKind())
        && isEqual(a.getService(), b.getService());
  }

  /**
   * Test token serialization
   */
  @Test
  public void testTokenSerialization() throws IOException {
    // Get a token
    Token<TokenIdentifier> sourceToken = new Token<TokenIdentifier>();
    sourceToken.setService(new Text("service"));

    // Write it to an output buffer
    DataOutputBuffer out = new DataOutputBuffer();
    sourceToken.write(out);

    // Read the token back
    DataInputBuffer in = new DataInputBuffer();
    in.reset(out.getData(), out.getLength());
    Token<TokenIdentifier> destToken = new Token<TokenIdentifier>();
    destToken.readFields(in);
    assertTrue(checkEqual(sourceToken, destToken));
  }
  
  private static void checkUrlSafe(String str) throws Exception {
    int len = str.length();
    for(int i=0; i < len; ++i) {
      char ch = str.charAt(i);
      if (ch == '-') continue;
      if (ch == '_') continue;
      if (ch >= '0' && ch <= '9') continue;
      if (ch >= 'A' && ch <= 'Z') continue;
      if (ch >= 'a' && ch <= 'z') continue;
      fail("Encoded string " + str + 
           " has invalid character at position " + i);
    }
  }

  @Test
  public void testEncodeWritable() throws Exception {
    String[] values = new String[]{"", "a", "bb", "ccc", "dddd", "eeeee",
        "ffffff", "ggggggg", "hhhhhhhh", "iiiiiiiii",
        "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLM" +
             "NOPQRSTUVWXYZ01234567890!@#$%^&*()-=_+[]{}|;':,./<>?"};
    Token<AbstractDelegationTokenIdentifier> orig;
    Token<AbstractDelegationTokenIdentifier> copy = new Token<>();
    // ensure that for each string the input and output values match
    for(int i=0; i< values.length; ++i) {
      String val = values[i];
      Token.LOG.info("Input = {}", val);
      orig = new Token<>(val.getBytes(),
          val.getBytes(), new Text(val), new Text(val));
      String encode = orig.encodeToUrlString();
      copy.decodeFromUrlString(encode);
      assertEquals(orig, copy);
      checkUrlSafe(encode);
    }
  }

  /*
   * Test decodeWritable() with null newValue string argument,
   * should throw HadoopIllegalArgumentException.
   */
  @Test
  public void testDecodeWritableArgSanityCheck() throws Exception {
    Token<AbstractDelegationTokenIdentifier> token = new Token<>();
    intercept(HadoopIllegalArgumentException.class,
        () -> token.decodeFromUrlString(null));
  }

  @Test
  public void testDecodeIdentifier() throws IOException {
    TestDelegationTokenSecretManager secretManager =
      new TestDelegationTokenSecretManager(0, 0, 0, 0);
    secretManager.startThreads();
    TestDelegationTokenIdentifier id = new TestDelegationTokenIdentifier(
        new Text("owner"), new Text("renewer"), new Text("realUser"));
    
    Token<TestDelegationTokenIdentifier> token =
        new Token<>(id, secretManager);
    TokenIdentifier idCopy = token.decodeIdentifier();
    
    assertNotSame(id, idCopy);
    assertEquals(id, idCopy);
  }

}
