struct sparse_file *sparse_file_import_auto(int fd, bool crc, bool verbose)
{
	struct sparse_file *s;
	int64_t len;
	int ret;

	s = sparse_file_import(fd, verbose, crc);
	if (s) {
		return s;
	}

	len = lseek64(fd, 0, SEEK_END);
	if (len < 0) {
		return NULL;
	}

	lseek64(fd, 0, SEEK_SET);

	s = sparse_file_new(4096, len);
	if (!s) {
		return NULL;
	}

	ret = sparse_file_read_normal(s, fd);
	if (ret < 0) {
		sparse_file_destroy(s);
		return NULL;
	}

	return s;
}
int main(int argc, char *argv[])
{
	int in;
	int out;
	int i;
	int ret;
	struct sparse_file *s;

	if (argc < 3) {
		usage();
		exit(-1);
	}

	out = open(argv[argc - 1], O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0664);
	if (out < 0) {
		fprintf(stderr, "Cannot open output file %s\n", argv[argc - 1]);
		exit(-1);
	}

	for (i = 1; i < argc - 1; i++) {
		if (strcmp(argv[i], "-") == 0) {
			in = STDIN_FILENO;
		} else {
			in = open(argv[i], O_RDONLY | O_BINARY);
			if (in < 0) {
				fprintf(stderr, "Cannot open input file %s\n", argv[i]);
				exit(-1);
			}
		}

		s = sparse_file_import(in, true, false);
		if (!s) {
			fprintf(stderr, "Failed to read sparse file\n");
			exit(-1);
		}

		lseek(out, SEEK_SET, 0);

		ret = sparse_file_write(s, out, false, false, false);
		if (ret < 0) {
			fprintf(stderr, "Cannot write output file\n");
			exit(-1);
		}
		sparse_file_destroy(s);
		close(in);
	}

	close(out);

	exit(0);
}
bool generate_verity_tree(const std::string& data_filename,
                          const std::string& verity_filename,
                          HashTreeBuilder* builder,
                          const std::vector<unsigned char>& salt_content,
                          size_t block_size, bool sparse, bool verbose) {
  android::base::unique_fd data_fd(open(data_filename.c_str(), O_RDONLY));
  if (data_fd == -1) {
    PLOG(ERROR) << "failed to open " << data_filename;
    return false;
  }

  struct sparse_file* file;
  if (sparse) {
    file = sparse_file_import(data_fd, false, false);
  } else {
    file = sparse_file_import_auto(data_fd, false, verbose);
  }

  if (!file) {
    LOG(ERROR) << "failed to read file " << data_filename;
    return false;
  }

  int64_t len = sparse_file_len(file, false, false);
  if (len % block_size != 0) {
    LOG(ERROR) << "file size " << len << " is not a multiple of " << block_size
               << " byte";
    return false;
  }

  // Initialize the builder to compute the hash tree.
  if (!builder->Initialize(len, salt_content)) {
    LOG(ERROR) << "Failed to initialize HashTreeBuilder";
    return false;
  }

  auto hash_callback = [](void* priv, const void* data, size_t len) {
    auto sparse_hasher = static_cast<HashTreeBuilder*>(priv);
    return sparse_hasher->Update(static_cast<const unsigned char*>(data), len)
               ? 0
               : 1;
  };
  sparse_file_callback(file, false, false, hash_callback, builder);
  sparse_file_destroy(file);

  if (!builder->BuildHashTree()) {
    return false;
  }

  return builder->WriteHashTreeToFile(verity_filename);
}
bool handle_command(Socket *client, std::string cmd, std::vector<std::string> args) {
    const char *trampfile = "/data/misc/fastbootd/mid.bin";

    if (cmd == "getvar") {
        if (args[0] == "max-download-size") {
            return send_reply(client, "OKAY", "%d", kMaxDownloadSize);
        } else if (args[0] == "partition-type") {
            for (size_t i = 0; i < sizeof(part_info) / sizeof(part_info[0]); i++) {
                if (args[1] == part_info[i].name) {
                    return send_reply(client, "OKAY", part_info[i].type);
                }
            }
        } else if (args[0] == "product") {
            char property[PROPERTY_VALUE_MAX];
            property_get("ro.product.board", property, "");
            return send_reply(client, "OKAY", property);
        } else if (args[0] == "serialno") {
            char property[PROPERTY_VALUE_MAX];
            property_get("ro.serialno", property, "");
            return send_reply(client, "OKAY", property);
        } else if (args[0] == "version-bootloader") {
            return send_reply(client, "OKAY", "0.1");
        }
        return send_reply(client, "OKAY", "");
    } else if (cmd == "download") {
        uint32_t size = strtol(args[0].c_str(), 0, 16);
        send_reply(client, "DATA", "%08x", size);

        int fd = open(trampfile, O_WRONLY | O_CREAT | O_TRUNC, 0600);
        if (fd < 0) {
            send_reply(client, "FAIL", "fail to create trampoline file to store data!");
            return false;
        }

        while (size > 0) {
            char buffer[4096];
            ssize_t read = client->Receive(buffer, 8, 0);
            if (read != 8) {
                send_reply(client, "FAIL", "fail to receive data!");
                close(fd);
                return false;
            }
            size_t length = ExtractMessageLength(buffer);
            do {
                read = client->Receive(buffer, std::min(length, sizeof(buffer)), 0);
                if (read < 0) {
                    close(fd);
                    return false;
                }

                write(fd, buffer, read);

                length -= read;
                size -= read;
            } while (length > 0);
        }

        close(fd);

        return send_reply(client, "OKAY", "");
    } else if (cmd == "flash") {
        std::unique_ptr<char, int (*)(const char *)> tmpfile((char *)trampfile, unlink);
        int fd = open(tmpfile.get(), O_RDONLY);
        if (fd < 0) {
            send_reply(client, "FAIL", "please run download command first!");
            return false;
        }

        const char *devname = NULL;
        const char *partname = NULL;
        for (size_t i = 0; i < sizeof(part_info) / sizeof(part_info[0]); i++) {
            if (args[0] == part_info[i].name) {
                devname = part_info[i].device;
                partname = part_info[i].name;
                break;
            }
        }

        if (devname == NULL) {
            close(fd);
            send_reply(client, "FAIL", "partition: %s does not exist!", args[0].c_str());
            return false;
        }

        if (!strcmp("boot", partname)) {
            close(fd);
            return handle_command_flash_boot_partition(client, tmpfile.get(), devname);
        }

        int fddev = open(devname, O_WRONLY | O_CREAT, 0600);
        if (fddev < 0) {
            close(fd);
            send_reply(client, "FAIL", "failed to open partition: %s", args[0].c_str());
            return false;
        }

        struct sparse_file *s = sparse_file_import(fd, true, false);
        if (!s) {
            close(fd);
            close(fddev);

            send_reply(client, "FAIL", "failed to read sparse file!");
            return false;
        }

        sparse_file_write(s, fddev, false, false, false);
        sparse_file_destroy(s);

        close(fd);
        close(fddev);

        sync();

        return send_reply(client, "OKAY", "");
    } else if (cmd == "erase") {
        const char *devname = NULL;
        for (size_t i = 0; i < sizeof(part_info) / sizeof(part_info[0]); i++) {
            if (args[0] == part_info[i].name) {
                devname = part_info[i].device;
                break;
            }
        }

        if (devname == NULL) {
            send_reply(client, "FAIL", "partition: %s does not exist!", args[0].c_str());
            return false;
        }

        uint64_t devsize = 0;
        int fd = open(devname, O_RDONLY);
        ioctl(fd, BLKGETSIZE64, &devsize);

        const uint64_t blksize = 64 * 1024;
        const uint64_t numblk = (devsize + blksize - 1) / blksize;
        const uint64_t updsize = (numblk / 10) * blksize;
        for (uint64_t offset = 0; offset < devsize; offset += updsize) {
            uint64_t realsize = std::min(updsize, devsize - offset);
            const char *argv[] = {
                "/system/bin/dd",
                "if=/dev/zero",
                android::base::StringPrintf("of=%s", devname).c_str(),
                android::base::StringPrintf("seek=%lld", offset).c_str(),
                android::base::StringPrintf("bs=%lld", realsize).c_str(),
                "count=1",
            };
            int status;

            android_fork_execvp(sizeof(argv) / sizeof(argv[0]), (char **)argv, &status, true, true);
            send_reply(client, "INFO", android::base::StringPrintf("erase %s: %3lld/100",
                    devname, (offset + realsize) * 100 / devsize).c_str());
        }

        return send_reply(client, "OKAY", "");
    } else if (cmd == "continue") {
        android::base::WriteStringToFile("5", "/sys/module/bcm2709/parameters/reboot_part");
        android_reboot(ANDROID_RB_RESTART, 0, NULL);
//        while (true) { pause(); }
        return send_reply(client, "OKAY", "");
    } else if (cmd == "reboot" || cmd == "reboot-bootloader") {
        android::base::WriteStringToFile("0", "/sys/module/bcm2709/parameters/reboot_part");
        android_reboot(ANDROID_RB_RESTART, 0, NULL);
//        while (true) { pause(); }
        return send_reply(client, "OKAY", "");
    }

    return send_reply(client, "FAIL", "unknown command: %s", cmd.c_str());
}
int main(int argc, char **argv)
{
    char *data_filename;
    char *verity_filename;
    unsigned char *salt = NULL;
    size_t salt_size = 0;
    bool sparse = false;
    size_t block_size = 4096;
    uint64_t calculate_size = 0;
    bool verbose = false;

    while (1) {
        const static struct option long_options[] = {
            {"salt-str", required_argument, 0, 'a'},
            {"salt-hex", required_argument, 0, 'A'},
            {"help", no_argument, 0, 'h'},
            {"sparse", no_argument, 0, 'S'},
            {"verity-size", required_argument, 0, 's'},
            {"verbose", no_argument, 0, 'v'},
            {NULL, 0, 0, 0}
        };
        int c = getopt_long(argc, argv, "a:A:hSs:v", long_options, NULL);
        if (c < 0) {
            break;
        }

        switch (c) {
        case 'a':
            salt_size = strlen(optarg);
            salt = new unsigned char[salt_size]();
            if (salt == NULL) {
                FATAL("failed to allocate memory for salt\n");
            }
            memcpy(salt, optarg, salt_size);
            break;
        case 'A': {
                BIGNUM *bn = NULL;
                if(!BN_hex2bn(&bn, optarg)) {
                    FATAL("failed to convert salt from hex\n");
                }
                salt_size = BN_num_bytes(bn);
                salt = new unsigned char[salt_size]();
                if (salt == NULL) {
                    FATAL("failed to allocate memory for salt\n");
                }
                if((size_t)BN_bn2bin(bn, salt) != salt_size) {
                    FATAL("failed to convert salt to bytes\n");
                }
            }
            break;
        case 'h':
            usage();
            return 1;
        case 'S':
            sparse = true;
            break;
        case 's': {
                char* endptr;
                errno = 0;
                unsigned long long int inSize = strtoull(optarg, &endptr, 0);
                if (optarg[0] == '\0' || *endptr != '\0' ||
                        (errno == ERANGE && inSize == ULLONG_MAX)) {
                    FATAL("invalid value of verity-size\n");
                }
                if (inSize > UINT64_MAX) {
                    FATAL("invalid value of verity-size\n");
                }
                calculate_size = (uint64_t)inSize;
            }
            break;
        case 'v':
            verbose = true;
            break;
        case '?':
            usage();
            return 1;
        default:
            abort();
        }
    }

    argc -= optind;
    argv += optind;

    const EVP_MD *md = EVP_sha256();
    if (!md) {
        FATAL("failed to get digest\n");
    }

    size_t hash_size = EVP_MD_size(md);
    assert(hash_size * 2 < block_size);

    if (!salt || !salt_size) {
        salt_size = hash_size;
        salt = new unsigned char[salt_size];
        if (salt == NULL) {
            FATAL("failed to allocate memory for salt\n");
        }

        int random_fd = open("/dev/urandom", O_RDONLY);
        if (random_fd < 0) {
            FATAL("failed to open /dev/urandom\n");
        }

        ssize_t ret = read(random_fd, salt, salt_size);
        if (ret != (ssize_t)salt_size) {
            FATAL("failed to read %zu bytes from /dev/urandom: %zd %d\n", salt_size, ret, errno);
        }
        close(random_fd);
    }

    if (calculate_size) {
        if (argc != 0) {
            usage();
            return 1;
        }
        size_t verity_blocks = 0;
        size_t level_blocks;
        int levels = 0;
        do {
            level_blocks = verity_tree_blocks(calculate_size, block_size, hash_size, levels);
            levels++;
            verity_blocks += level_blocks;
        } while (level_blocks > 1);

        printf("%" PRIu64 "\n", (uint64_t)verity_blocks * block_size);
        return 0;
    }

    if (argc != 2) {
        usage();
        return 1;
    }

    data_filename = argv[0];
    verity_filename = argv[1];

    int fd = open(data_filename, O_RDONLY);
    if (fd < 0) {
        FATAL("failed to open %s\n", data_filename);
    }

    struct sparse_file *file;
    if (sparse) {
        file = sparse_file_import(fd, false, false);
    } else {
        file = sparse_file_import_auto(fd, false, verbose);
    }

    if (!file) {
        FATAL("failed to read file %s\n", data_filename);
    }

    int64_t len = sparse_file_len(file, false, false);
    if (len % block_size != 0) {
        FATAL("file size %" PRIu64 " is not a multiple of %zu bytes\n",
                len, block_size);
    }

    int levels = 0;
    size_t verity_blocks = 0;
    size_t level_blocks;

    do {
        level_blocks = verity_tree_blocks(len, block_size, hash_size, levels);
        levels++;
        verity_blocks += level_blocks;
    } while (level_blocks > 1);

    unsigned char *verity_tree = new unsigned char[verity_blocks * block_size]();
    unsigned char **verity_tree_levels = new unsigned char *[levels + 1]();
    size_t *verity_tree_level_blocks = new size_t[levels]();
    if (verity_tree == NULL || verity_tree_levels == NULL || verity_tree_level_blocks == NULL) {
        FATAL("failed to allocate memory for verity tree\n");
    }

    unsigned char *ptr = verity_tree;
    for (int i = levels - 1; i >= 0; i--) {
        verity_tree_levels[i] = ptr;
        verity_tree_level_blocks[i] = verity_tree_blocks(len, block_size, hash_size, i);
        ptr += verity_tree_level_blocks[i] * block_size;
    }
    assert(ptr == verity_tree + verity_blocks * block_size);
    assert(verity_tree_level_blocks[levels - 1] == 1);

    unsigned char zero_block_hash[hash_size];
    unsigned char zero_block[block_size];
    memset(zero_block, 0, block_size);
    hash_block(md, zero_block, block_size, salt, salt_size, zero_block_hash, NULL);

    unsigned char root_hash[hash_size];
    verity_tree_levels[levels] = root_hash;

    struct sparse_hash_ctx ctx;
    ctx.hashes = verity_tree_levels[0];
    ctx.salt = salt;
    ctx.salt_size = salt_size;
    ctx.hash_size = hash_size;
    ctx.block_size = block_size;
    ctx.zero_block_hash = zero_block_hash;
    ctx.md = md;

    sparse_file_callback(file, false, false, hash_chunk, &ctx);

    sparse_file_destroy(file);
    close(fd);

    for (int i = 0; i < levels; i++) {
        size_t out_size;
        hash_blocks(md,
                verity_tree_levels[i], verity_tree_level_blocks[i] * block_size,
                verity_tree_levels[i + 1], &out_size,
                salt, salt_size, block_size);
          if (i < levels - 1) {
              assert(div_round_up(out_size, block_size) == verity_tree_level_blocks[i + 1]);
          } else {
              assert(out_size == hash_size);
          }
    }

    for (size_t i = 0; i < hash_size; i++) {
        printf("%02x", root_hash[i]);
    }
    printf(" ");
    for (size_t i = 0; i < salt_size; i++) {
        printf("%02x", salt[i]);
    }
    printf("\n");

    fd = open(verity_filename, O_WRONLY|O_CREAT, 0666);
    if (fd < 0) {
        FATAL("failed to open output file '%s'\n", verity_filename);
    }
    write(fd, verity_tree, verity_blocks * block_size);
    close(fd);

    delete[] verity_tree_levels;
    delete[] verity_tree_level_blocks;
    delete[] verity_tree;
    delete[] salt;
}