/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed 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.
 */

#include "perfetto/ext/base/base64.h"

#include "perfetto/ext/base/string_view.h"
#include "perfetto/ext/base/utils.h"
#include "test/gtest_and_gmock.h"

namespace perfetto {
namespace base {
namespace {

struct TestPattern {
  size_t decoded_len;
  const char* decoded;
  const char* encoded;
};

TestPattern kPatterns[] = {

    // Basic bit patterns;
    // values obtained with "echo -n '...' | uuencode -m test"

    {1, "\000", "AA=="},
    {1, "\001", "AQ=="},
    {1, "\002", "Ag=="},
    {1, "\004", "BA=="},
    {1, "\010", "CA=="},
    {1, "\020", "EA=="},
    {1, "\040", "IA=="},
    {1, "\100", "QA=="},
    {1, "\200", "gA=="},

    {1, "\377", "/w=="},
    {1, "\376", "/g=="},
    {1, "\375", "/Q=="},
    {1, "\373", "+w=="},
    {1, "\367", "9w=="},
    {1, "\357", "7w=="},
    {1, "\337", "3w=="},
    {1, "\277", "vw=="},
    {1, "\177", "fw=="},
    {2, "\000\000", "AAA="},
    {2, "\000\001", "AAE="},
    {2, "\000\002", "AAI="},
    {2, "\000\004", "AAQ="},
    {2, "\000\010", "AAg="},
    {2, "\000\020", "ABA="},
    {2, "\000\040", "ACA="},
    {2, "\000\100", "AEA="},
    {2, "\000\200", "AIA="},
    {2, "\001\000", "AQA="},
    {2, "\002\000", "AgA="},
    {2, "\004\000", "BAA="},
    {2, "\010\000", "CAA="},
    {2, "\020\000", "EAA="},
    {2, "\040\000", "IAA="},
    {2, "\100\000", "QAA="},
    {2, "\200\000", "gAA="},

    {2, "\377\377", "//8="},
    {2, "\377\376", "//4="},
    {2, "\377\375", "//0="},
    {2, "\377\373", "//s="},
    {2, "\377\367", "//c="},
    {2, "\377\357", "/+8="},
    {2, "\377\337", "/98="},
    {2, "\377\277", "/78="},
    {2, "\377\177", "/38="},
    {2, "\376\377", "/v8="},
    {2, "\375\377", "/f8="},
    {2, "\373\377", "+/8="},
    {2, "\367\377", "9/8="},
    {2, "\357\377", "7/8="},
    {2, "\337\377", "3/8="},
    {2, "\277\377", "v/8="},
    {2, "\177\377", "f/8="},

    {3, "\000\000\000", "AAAA"},
    {3, "\000\000\001", "AAAB"},
    {3, "\000\000\002", "AAAC"},
    {3, "\000\000\004", "AAAE"},
    {3, "\000\000\010", "AAAI"},
    {3, "\000\000\020", "AAAQ"},
    {3, "\000\000\040", "AAAg"},
    {3, "\000\000\100", "AABA"},
    {3, "\000\000\200", "AACA"},
    {3, "\000\001\000", "AAEA"},
    {3, "\000\002\000", "AAIA"},
    {3, "\000\004\000", "AAQA"},
    {3, "\000\010\000", "AAgA"},
    {3, "\000\020\000", "ABAA"},
    {3, "\000\040\000", "ACAA"},
    {3, "\000\100\000", "AEAA"},
    {3, "\000\200\000", "AIAA"},
    {3, "\001\000\000", "AQAA"},
    {3, "\002\000\000", "AgAA"},
    {3, "\004\000\000", "BAAA"},
    {3, "\010\000\000", "CAAA"},
    {3, "\020\000\000", "EAAA"},
    {3, "\040\000\000", "IAAA"},
    {3, "\100\000\000", "QAAA"},
    {3, "\200\000\000", "gAAA"},

    {3, "\377\377\377", "////"},
    {3, "\377\377\376", "///+"},
    {3, "\377\377\375", "///9"},
    {3, "\377\377\373", "///7"},
    {3, "\377\377\367", "///3"},
    {3, "\377\377\357", "///v"},
    {3, "\377\377\337", "///f"},
    {3, "\377\377\277", "//+/"},
    {3, "\377\377\177", "//9/"},
    {3, "\377\376\377", "//7/"},
    {3, "\377\375\377", "//3/"},
    {3, "\377\373\377", "//v/"},
    {3, "\377\367\377", "//f/"},
    {3, "\377\357\377", "/+//"},
    {3, "\377\337\377", "/9//"},
    {3, "\377\277\377", "/7//"},
    {3, "\377\177\377", "/3//"},
    {3, "\376\377\377", "/v//"},
    {3, "\375\377\377", "/f//"},
    {3, "\373\377\377", "+///"},
    {3, "\367\377\377", "9///"},
    {3, "\357\377\377", "7///"},
    {3, "\337\377\377", "3///"},
    {3, "\277\377\377", "v///"},
    {3, "\177\377\377", "f///"},

    // Random numbers: values obtained with
    //
    //  #! /bin/bash
    //  dd bs=$1 count=1 if=/dev/random of=/tmp/bar.random
    //  od -N $1 -t o1 /tmp/bar.random
    //  uuencode -m test < /tmp/bar.random
    //
    // where $1 is the number of bytes (2, 3)

    {2, "\243\361", "o/E="},
    {2, "\024\167", "FHc="},
    {2, "\313\252", "y6o="},
    {2, "\046\041", "JiE="},
    {2, "\145\236", "ZZ4="},
    {2, "\254\325", "rNU="},
    {2, "\061\330", "Mdg="},
    {2, "\245\032", "pRo="},
    {2, "\006\000", "BgA="},
    {2, "\375\131", "/Vk="},
    {2, "\303\210", "w4g="},
    {2, "\040\037", "IB8="},
    {2, "\261\372", "sfo="},
    {2, "\335\014", "3Qw="},
    {2, "\233\217", "m48="},
    {2, "\373\056", "+y4="},
    {2, "\247\232", "p5o="},
    {2, "\107\053", "Rys="},
    {2, "\204\077", "hD8="},
    {2, "\276\211", "vok="},
    {2, "\313\110", "y0g="},
    {2, "\363\376", "8/4="},
    {2, "\251\234", "qZw="},
    {2, "\103\262", "Q7I="},
    {2, "\142\312", "Yso="},
    {2, "\067\211", "N4k="},
    {2, "\220\001", "kAE="},
    {2, "\152\240", "aqA="},
    {2, "\367\061", "9zE="},
    {2, "\133\255", "W60="},
    {2, "\176\035", "fh0="},
    {2, "\032\231", "Gpk="},

    {3, "\013\007\144", "Cwdk"},
    {3, "\030\112\106", "GEpG"},
    {3, "\047\325\046", "J9Um"},
    {3, "\310\160\022", "yHAS"},
    {3, "\131\100\237", "WUCf"},
    {3, "\064\342\134", "NOJc"},
    {3, "\010\177\004", "CH8E"},
    {3, "\345\147\205", "5WeF"},
    {3, "\300\343\360", "wOPw"},
    {3, "\061\240\201", "MaCB"},
    {3, "\225\333\044", "ldsk"},
    {3, "\215\137\352", "jV/q"},
    {3, "\371\147\160", "+Wdw"},
    {3, "\030\320\051", "GNAp"},
    {3, "\044\174\241", "JHyh"},
    {3, "\260\127\037", "sFcf"},
    {3, "\111\045\033", "SSUb"},
    {3, "\202\114\107", "gkxH"},
    {3, "\057\371\042", "L/ki"},
    {3, "\223\247\244", "k6ek"},
    {3, "\047\216\144", "J45k"},
    {3, "\203\070\327", "gzjX"},
    {3, "\247\140\072", "p2A6"},
    {3, "\124\115\116", "VE1O"},
    {3, "\157\162\050", "b3Io"},
    {3, "\357\223\004", "75ME"},
    {3, "\052\117\156", "Kk9u"},
    {3, "\347\154\000", "52wA"},
    {3, "\303\012\142", "wwpi"},
    {3, "\060\035\362", "MB3y"},
    {3, "\130\226\361", "WJbx"},
    {3, "\173\013\071", "ews5"},
    {3, "\336\004\027", "3gQX"},
    {3, "\357\366\234", "7/ac"},
    {3, "\353\304\111", "68RJ"},
    {3, "\024\264\131", "FLRZ"},
    {3, "\075\114\251", "PUyp"},
    {3, "\315\031\225", "zRmV"},
    {3, "\154\201\276", "bIG+"},
    {3, "\200\066\072", "gDY6"},
    {3, "\142\350\267", "Yui3"},
    {3, "\033\000\166", "GwB2"},
    {3, "\210\055\077", "iC0/"},
    {3, "\341\037\124", "4R9U"},
    {3, "\161\103\152", "cUNq"},
    {3, "\270\142\131", "uGJZ"},
    {3, "\337\076\074", "3z48"},
    {3, "\375\106\362", "/Uby"},
    {3, "\227\301\127", "l8FX"},
    {3, "\340\002\234", "4AKc"},
    {3, "\121\064\033", "UTQb"},
    {3, "\157\134\143", "b1xj"},
    {3, "\247\055\327", "py3X"},
    {3, "\340\142\005", "4GIF"},
    {3, "\060\260\143", "MLBj"},
    {3, "\075\203\170", "PYN4"},
    {3, "\143\160\016", "Y3AO"},
    {3, "\313\013\063", "ywsz"},
    {3, "\174\236\135", "fJ5d"},
    {3, "\103\047\026", "QycW"},
    {3, "\365\005\343", "9QXj"},
    {3, "\271\160\223", "uXCT"},
    {3, "\362\255\172", "8q16"},
    {3, "\113\012\015", "SwoN"},

    // various lengths, generated by this python script:
    //
    // from string import lowercase as lc
    // for i in range(27):
    //   print '{ %2d, "%s",%s "%s" },' % (i, lc[:i], ' ' * (26-i),
    //                                     lc[:i].encode('base64').strip())

    {0, "abcdefghijklmnopqrstuvwxyz", ""},
    {1, "abcdefghijklmnopqrstuvwxyz", "YQ=="},
    {2, "abcdefghijklmnopqrstuvwxyz", "YWI="},
    {3, "abcdefghijklmnopqrstuvwxyz", "YWJj"},
    {4, "abcdefghijklmnopqrstuvwxyz", "YWJjZA=="},
    {5, "abcdefghijklmnopqrstuvwxyz", "YWJjZGU="},
    {6, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVm"},
    {7, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZw=="},
    {8, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2g="},
    {9, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hp"},
    {10, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpag=="},
    {11, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpams="},
    {12, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamts"},
    {13, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbQ=="},
    {14, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW4="},
    {15, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5v"},
    {16, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcA=="},
    {17, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHE="},
    {18, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFy"},
    {19, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFycw=="},
    {20, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3Q="},
    {21, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1"},
    {22, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dg=="},
    {23, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnc="},
    {24, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4"},
    {25, "abcdefghijklmnopqrstuvwxy", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eQ=="},
    {26, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo="},
};

TEST(Base64Test, Encode) {
  EXPECT_EQ(Base64Encode(""), "");
  EXPECT_EQ(Base64Encode("f"), "Zg==");
  EXPECT_EQ(Base64Encode("fo"), "Zm8=");
  EXPECT_EQ(Base64Encode("foo"), "Zm9v");
  EXPECT_EQ(Base64Encode("foob"), "Zm9vYg==");
  EXPECT_EQ(Base64Encode("fooba"), "Zm9vYmE=");
  EXPECT_EQ(Base64Encode("foobar"), "Zm9vYmFy");
  EXPECT_EQ(Base64Encode("\xff"), "/w==");
  EXPECT_EQ(Base64Encode("\xff\xfe"), "//4=");
  EXPECT_EQ(Base64Encode("\xff\xfe\xfd"), "//79");
  EXPECT_EQ(Base64Encode("\xff\xfe\xfd\xfc"), "//79/A==");

  for (size_t i = 0; i < ArraySize(kPatterns); ++i) {
    const auto& p = kPatterns[i];
    std::string res = Base64Encode(StringView(p.decoded, p.decoded_len));
    EXPECT_EQ(p.encoded, res);
  }

  // Error cases
  char buf[4];
  EXPECT_EQ(0, Base64Encode("", 0, buf, 0));
  EXPECT_EQ(0, Base64Encode("", 0, buf, 1));
  EXPECT_EQ(-1, Base64Encode("a", 1, buf, 0));
  EXPECT_EQ(-1, Base64Encode("abc", 3, buf, 0));
  EXPECT_EQ(-1, Base64Encode("abc", 3, buf, 1));
  EXPECT_EQ(-1, Base64Encode("abc", 3, buf, 3));
  EXPECT_EQ(4, Base64Encode("abc", 3, buf, 4));
}

TEST(Base64Test, Decode) {
  EXPECT_EQ(Base64Decode(""), "");
  EXPECT_EQ(Base64Decode("Zg=="), "f");
  EXPECT_EQ(Base64Decode("Zg="), "f");
  EXPECT_EQ(Base64Decode("Zg"), "f");
  EXPECT_EQ(Base64Decode("Zm8="), "fo");
  EXPECT_EQ(Base64Decode("Zm8"), "fo");
  EXPECT_EQ(Base64Decode("Zm9v"), "foo");
  EXPECT_EQ(Base64Decode("Zm9vYg=="), "foob");
  EXPECT_EQ(Base64Decode("Zm9vYg="), "foob");
  EXPECT_EQ(Base64Decode("Zm9vYg"), "foob");
  EXPECT_EQ(Base64Decode("Zm9vYmE="), "fooba");
  EXPECT_EQ(Base64Decode("Zm9vYmE"), "fooba");
  EXPECT_EQ(Base64Decode("Zm9vYmFy"), "foobar");
  EXPECT_EQ(Base64Decode("/w=="), "\xff");
  EXPECT_EQ(Base64Decode("/w="), "\xff");
  EXPECT_EQ(Base64Decode("/w"), "\xff");
  EXPECT_EQ(Base64Decode("//4="), "\xff\xfe");
  EXPECT_EQ(Base64Decode("//4"), "\xff\xfe");
  EXPECT_EQ(Base64Decode("//79"), "\xff\xfe\xfd");
  EXPECT_EQ(Base64Decode("//79/A=="), "\xff\xfe\xfd\xfc");
  EXPECT_EQ(Base64Decode("//79/A="), "\xff\xfe\xfd\xfc");
  EXPECT_EQ(Base64Decode("//79/A"), "\xff\xfe\xfd\xfc");

  for (size_t i = 0; i < ArraySize(kPatterns); ++i) {
    const auto& p = kPatterns[i];
    std::optional<std::string> dec = Base64Decode(StringView(p.encoded));
    EXPECT_TRUE(dec.has_value());
    EXPECT_EQ(dec.value(), StringView(p.decoded, p.decoded_len).ToStdString());
  }

  // Error cases:
  EXPECT_EQ(Base64Decode("Z"), std::nullopt);
  EXPECT_EQ(Base64Decode("Zm9vY"), std::nullopt);

  uint8_t buf[4];
  EXPECT_EQ(Base64Decode("", 0, buf, 2), 0);       // Valid, 0 len.
  EXPECT_EQ(Base64Decode("Z", 1, buf, 1), -1);     // Invalid input.
  EXPECT_EQ(Base64Decode("Zg==", 4, buf, 1), -1);  // Not enough dst space.
}

}  // namespace
}  // namespace base
}  // namespace perfetto
