// Copyright 2019 Google
//
// 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 "Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h"

#include <mach/mach.h>

#include <dlfcn.h>

#include "Crashlytics/Crashlytics/Components/FIRCLSGlobals.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSFeatures.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSFile.h"

#import "Crashlytics/Shared/FIRCLSByteUtility.h"
#import "Crashlytics/Shared/FIRCLSUUID.h"

#import <CommonCrypto/CommonHMAC.h>

void FIRCLSLookupFunctionPointer(void* ptr, void (^block)(const char* name, const char* lib)) {
  Dl_info info;

  if (dladdr(ptr, &info) == 0) {
    block(NULL, NULL);
    return;
  }

  const char* name = "unknown";
  const char* lib = "unknown";

  if (info.dli_sname) {
    name = info.dli_sname;
  }

  if (info.dli_fname) {
    lib = info.dli_fname;
  }

  block(name, lib);
}

uint8_t FIRCLSNybbleFromChar(char c) {
  if (c >= '0' && c <= '9') {
    return c - '0';
  }

  if (c >= 'a' && c <= 'f') {
    return c - 'a' + 10;
  }

  if (c >= 'A' && c <= 'F') {
    return c - 'A' + 10;
  }

  return FIRCLSInvalidCharNybble;
}

bool FIRCLSReadMemory(vm_address_t src, void* dest, size_t len) {
  if (!FIRCLSIsValidPointer(src)) {
    return false;
  }

  vm_size_t readSize = len;

  return vm_read_overwrite(mach_task_self(), src, len, (pointer_t)dest, &readSize) == KERN_SUCCESS;
}

bool FIRCLSReadString(vm_address_t src, char** dest, size_t maxlen) {
  char c;
  vm_address_t address;

  if (!dest) {
    return false;
  }

  // Walk the entire string.  Not certain this is perfect...
  for (address = src; address < src + maxlen; ++address) {
    if (!FIRCLSReadMemory(address, &c, 1)) {
      return false;
    }

    if (c == 0) {
      break;
    }
  }

  *dest = (char*)src;

  return true;
}

const char* FIRCLSDupString(const char* string) {
#if CLS_MEMORY_PROTECTION_ENABLED
  char* buffer;
  size_t length;

  if (!string) {
    return NULL;
  }

  length = strlen(string);
  buffer = FIRCLSAllocatorSafeAllocate(_firclsContext.allocator, length + 1, CLS_READONLY);

  memcpy(buffer, string, length);

  buffer[length] = 0;  // null-terminate

  return buffer;
#else
  return strdup(string);
#endif
}

void FIRCLSDispatchAfter(float timeInSeconds, dispatch_queue_t queue, dispatch_block_t block) {
  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeInSeconds * NSEC_PER_SEC)), queue,
                 block);
}

bool FIRCLSUnlinkIfExists(const char* path) {
  if (unlink(path) != 0) {
    if (errno != ENOENT) {
      return false;
    }
  }

  return true;
}

NSString* FIRCLSNormalizeUUID(NSString* value) {
  return [[value stringByReplacingOccurrencesOfString:@"-" withString:@""] lowercaseString];
}

NSString* FIRCLSGenerateNormalizedUUID(void) {
  return FIRCLSNormalizeUUID(FIRCLSGenerateUUID());
}

// Redacts a UUID wrapped in parenthesis from a char* using strchr, which is async safe.
// Ex.
//   "foo (bar) (45D62CC2-CFB5-4E33-AB61-B0684627F1B6) baz"
// becomes
//   "foo (bar) (********-****-****-****-************) baz"
void FIRCLSRedactUUID(char* value) {
  if (value == NULL) {
    return;
  }
  char* openParen = value;
  // find the index of the first paren
  while ((openParen = strchr(openParen, '(')) != NULL) {
    // find index of the matching close paren
    const char* closeParen = strchr(openParen, ')');
    if (closeParen == NULL) {
      break;
    }
    // if the distance between them is 37, traverse the characters
    // and replace anything that is not a '-' with '*'
    if (closeParen - openParen == 37) {
      for (int i = 1; i < 37; ++i) {
        if (*(openParen + i) != '-') {
          *(openParen + i) = '*';
        }
      }
      break;
    }
    openParen++;
  }
}

NSString* FIRCLSNSDataToNSString(NSData* data) {
  NSString* string;
  char* buffer;
  size_t size;
  NSUInteger length;

  // we need 2 hex char for every byte of data, plus one more spot for a
  // null terminator
  length = [data length];
  size = (length * 2) + 1;
  buffer = malloc(sizeof(char) * size);

  if (!buffer) {
    FIRCLSErrorLog(@"Unable to malloc in FIRCLSNSDataToNSString");
    return nil;
  }

  FIRCLSSafeHexToString([data bytes], length, buffer);

  string = [NSString stringWithUTF8String:buffer];

  free(buffer);

  return string;
}

void FIRCLSAddOperationAfter(float timeInSeconds, NSOperationQueue* queue, void (^block)(void)) {
  dispatch_queue_t afterQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  FIRCLSDispatchAfter(timeInSeconds, afterQueue, ^{
    [queue addOperationWithBlock:block];
  });
}

#if DEBUG
void FIRCLSPrintAUUID(const uint8_t* value) {
  CFUUIDRef uuid = CFUUIDCreateFromUUIDBytes(kCFAllocatorDefault, *(CFUUIDBytes*)value);

  NSString* string = CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, uuid));

  CFRelease(uuid);

  FIRCLSDebugLog(@"%@", [[string stringByReplacingOccurrencesOfString:@"-"
                                                           withString:@""] lowercaseString]);
}
#endif
