Kamin Whitehouse

131 papers A* 7A 11B 8Misc 28Journal 25Unranked 49
YearRankTypeTitle / Venue / Authors
2021 J jnl
Proc. ACM Interact. Mob. Wearable Ubiquitous Technol.
Elahe Soltanaghaei, Adwait Dongare, Akarsh Prabhakara, Swarun Kumar, Anthony G. Rowe, Kamin Whitehouse
2020 J jnl
ACM Trans. Cyber Phys. Syst.
Sirajum Munir, Hao-Tsung Yang, Shan Lin, S. M. Shahriar Nirjon, Lin Chen, Enamul Hoque, John A. Stankovic, Kamin Whitehouse
2019 A* conf
WWW
Yiling Jia, Nipun Batra, Hongning Wang, Kamin Whitehouse
2019 A conf
CIKM
Yiling Jia, Nipun Batra, Hongning Wang, Kamin Whitehouse
2019 J jnl
CoRR
Yiling Jia, Nipun Batra, Hongning Wang, Kamin Whitehouse
2019 A* conf
MobiCom
Elahe Soltanaghaei, Mahmoud Elnaggar, Katie Kleeman, Kamin Whitehouse, Cody H. Fleming
2019 A conf
RTAS
Avinash Kalyanaraman, Elahe Soltanaghaei, Kamin Whitehouse
2019 conf
BuildSys
Dezhi Hong, Renqin Cai, Hongning Wang, Kamin Whitehouse
2018 J jnl
Commun. ACM
Luca Mottola, Kamin Whitehouse
2018 A conf
MobiSys
Elahe Soltanaghaei, Avinash Kalyanaraman, Kamin Whitehouse
2018 conf
BuildSys
Jason Koh, Dezhi Hong, Rajesh K. Gupta, Kamin Whitehouse, Hongning Wang, Yuvraj Agarwal
2018 A* conf
AAAI
Nipun Batra, Yiling Jia, Hongning Wang, Kamin Whitehouse
2017 J jnl
ACM Trans. Sens. Networks
Erin Griffiths, Avinash Kalyanaraman, Juhi Ranjan, Kamin Whitehouse
2017 J jnl
GetMobile Mob. Comput. Commun.
Luca Mottola, Kamin Whitehouse
2017 J jnl
Proc. ACM Interact. Mob. Wearable Ubiquitous Technol.
Avinash Kalyanaraman, Dezhi Hong, Elahe Soltanaghaei, Kamin Whitehouse
2017 A conf
AISTATS
Dezhi Hong, Quanquan Gu, Kamin Whitehouse
2017 A* conf
AAAI
Nipun Batra, Hongning Wang, Amarjeet Singh, Kamin Whitehouse
2017 conf
WPA@MobiSys
Elahe Soltanaghaei, Avinash Kalyanaraman, Kamin Whitehouse
2017 A* conf
MobiCom
Elahe Soltanaghaei, Avinash Kalyanaraman, Kamin Whitehouse
2017 A conf
MobiSys
Elahe Soltanaghaei, Avinash Kalyanaraman, Kamin Whitehouse
2017 B conf
EWSN
Mikhail Afanasov, Luca Mottola, Kamin Whitehouse
2017 J jnl
Proc. ACM Interact. Mob. Wearable Ubiquitous Technol.
Erin Griffiths, Salah Assana, Kamin Whitehouse
2017 ed.
BuildSys
Kamin Whitehouse, Prabal Dutta, Hae Young Noh
2017 B conf
SECON
Hao-Tsung Yang, Kin Sum Liu, Jie Gao, Shan Lin, Sirajum Munir, Kamin Whitehouse, John A. Stankovic
2017 B conf
EWSN
Erin Griffiths, Kamin Whitehouse
2016 conf
UbiComp Adjunct
Juhi Ranjan, Kamin Whitehouse
2016 conf
BuildSys
Bharathan Balaji, Arka Aloke Bhattacharya, Gabriel Fierro, Jingkun Gao, Joshua Gluck, Dezhi Hong, Aslak Johansen, Jason Koh, Joern Ploennigs, Yuvraj Agarwal, Mario Berges, David E. Culler, Rajesh K. Gupta, Mikkel Baun Kjærgaard, Mani B. Srivastava, Kamin Whitehouse
2016 A* conf
KDD
Nipun Batra, Amarjeet Singh, Kamin Whitehouse
2016 Misc conf
UbiComp
Rayoung Yang, Devika Pisharoty, Soodeh Montazeri, Kamin Whitehouse, Mark W. Newman
2016 J jnl
GetMobile Mob. Comput. Commun.
Luca Mottola, Kamin Whitehouse
2016 conf
BuildSys
Bharathan Balaji, Arka Aloke Bhattacharya, Gabe Fierro, Jingkun Gao, Joshua Gluck, Dezhi Hong, Aslak Johansen, Jason Koh, Joern Ploennigs, Yuvraj Agarwal, Mario Berges, David E. Culler, Rajesh Gupta, Mikkel Baun Kjærgaard, Mani B. Srivastava, Kamin Whitehouse
2016 A conf
MobiSys
Endri Bregu, Nicola Casamassima, Daniel Cantoni, Luca Mottola, Kamin Whitehouse
2016 conf
UbiComp Adjunct
Juhi Ranjan, Kamin Whitehouse
2016 B conf
DCOSS
Avinash Kalyanaraman, Erin Griffiths, Kamin Whitehouse
2016 conf
BuildSys
Elahe Soltanaghaei, Kamin Whitehouse
2015 conf
UbiComp/ISWC Adjunct
Avinash Kalyanaraman, Kamin Whitehouse
2015 conf
BuildSys
Arka Aloke Bhattacharya, Dezhi Hong, David E. Culler, Jorge Ortiz, Kamin Whitehouse, Eugene Wu
2015 conf
UbiComp/ISWC Adjunct
Avinash Kalyanaraman, Juhi Ranjan, Kamin Whitehouse
2015 A conf
CIKM
Dezhi Hong, Hongning Wang, Kamin Whitehouse
2015 J jnl
Ad Hoc Networks
Behnam Dezfouli, Marjan Radi, Kamin Whitehouse, Shukor Abd Razak, Hwee Pink Tan
2015 J jnl
CoRR
Nipun Batra, Rishi Baijal, Amarjeet Singh, Kamin Whitehouse
2015 conf
IPSN
Yong Sun, Md Anindya Prodhan, Erin Griffiths, Kamin Whitehouse
2015 conf
BuildSys
Nipun Batra, Amarjeet Singh, Kamin Whitehouse
2015 J jnl
CoRR
Nipun Batra, Amarjeet Singh, Kamin Whitehouse
2015 Misc conf
UbiComp
Juhi Ranjan, Kamin Whitehouse
2015 conf
HBU
Juhi Ranjan, Kamin Whitehouse
2015 J jnl
CoRR
Dezhi Hong, Jorge Ortiz, Arka Aloke Bhattacharya, Kamin Whitehouse
2015 conf
BuildSys
Dezhi Hong, Hongning Wang, Jorge Ortiz, Kamin Whitehouse
2015 conf
BuildSys
Devika Pisharoty, Rayoung Yang, Mark W. Newman, Kamin Whitehouse
2015 J jnl
ACM Trans. Sens. Networks
Shan Lin, Gang Zhou, Mo'taz Al-Hami, Kamin Whitehouse, Yafeng Wu, John A. Stankovic, Tian He, Xiaobing Wu, Hengchang Liu
2014 J jnl
IEEE Trans. Mob. Comput.
Hengchang Liu, Zhiheng Xie, Jingyuan Li, Shan Lin, David J. Siu, Pan Hui, Kamin Whitehouse, John A. Stankovic
2014 Misc conf
SenSys
Arka Aloke Bhattacharya, David E. Culler, Dezhi Hong, Kamin Whitehouse, Jorge Ortiz
2014 Misc conf
AMIA
Avinash Kalyanaraman, Blaine Reeder, Kamin Whitehouse
2014 J jnl
CoRR
Nipun Batra, Manoj Gulati, Puneet Jain, Kamin Whitehouse, Amarjeet Singh
2014 conf
BuildSys
Nipun Batra, Manoj Gulati, Puneet Jain, Kamin Whitehouse, Amarjeet Singh
2014 conf
BuildSys
Juhi Ranjan, Erin Griffiths, Kamin Whitehouse
2014 conf
BuildSys
Juhi Ranjan, Kamin Whitehouse
2014 J jnl
Comput. Networks
Behnam Dezfouli, Marjan Radi, Shukor Abd Razak, Kamin Whitehouse, Kamalrulnizam Abu Bakar, Hwee-Pink Tan
2014 B conf
DCOSS
Jiakang Lu, Kamin Whitehouse
2014 conf
BuildSys
John Feminella, Devika Pisharoty, Kamin Whitehouse
2014 J jnl
ACM Trans. Sens. Networks
Jiakang Lu, Yamina Taskin Shams, Kamin Whitehouse
2014 Misc conf
SenSys
Luca Mottola, Mattia Moretta, Kamin Whitehouse, Carlo Ghezzi
2014 Misc conf
AMIA
Blaine Reeder, Kamin Whitehouse
2014 conf
BuildSys
Arka Aloke Bhattacharya, David E. Culler, Dezhi Hong, Kamin Whitehouse, Jorge Ortiz
2013 Misc conf
UbiComp
Juhi Ranjan, Yu Yao, Kamin Whitehouse
2013 conf
BuildSys@SenSys
Andrew Frye, Michel Goraczko, Jie Liu, Md Anindya Prodhan, Kamin Whitehouse
2013 J jnl
Sustain. Comput. Informatics Syst.
Tamim Sookoor, Brian Holben, Kamin Whitehouse
2013 conf
IPSN
Vijay Srinivasan, John A. Stankovic, Kamin Whitehouse
2013 conf
ICCPS
Tamim I. Sookoor, Kamin Whitehouse
2013 Misc ed.
SenSys
Chiara Petrioli, Landon P. Cox, Kamin Whitehouse
2013 conf
BuildSys@SenSys
Dezhi Hong, Jorge Ortiz, Kamin Whitehouse, David E. Culler
2013 Misc conf
SenSys
Luca Mottola, Kamin Whitehouse, Carlo Ghezzi
2012 Misc conf
UbiComp
Krasimira Kapitanova, Enamul Hoque, John A. Stankovic, Kamin Whitehouse, Sang Hyuk Son
2012 J jnl
Proc. IEEE
Benton H. Calhoun, John C. Lach, John A. Stankovic, David D. Wentzloff, Kamin Whitehouse, Adam T. Barth, Jonathan K. Brown, Qiang Li, Seunghyun Oh, Nathan E. Roberts, Yanqing Zhang
2012 Misc conf
SenSys
Timothy W. Hnat, Erin Griffiths, Raymond Dawson, Kamin Whitehouse
2012 conf
IGCC
Tamim I. Sookoor, Brian Holben, Kamin Whitehouse
2012 conf
BuildSys@SenSys
Md Anindya Prodhan, Kamin Whitehouse
2012 J jnl
SIGBED Rev.
Virginia Smith, Tamim I. Sookoor, Kamin Whitehouse
2012 conf
Pervasive
Jiakang Lu, Kamin Whitehouse
2012 conf
IPSN
Jiakang Lu, Kamin Whitehouse
2012 ed.
IPSN
Feng Zhao, Andreas Terzis, Kamin Whitehouse
2012 J jnl
IEEE Des. Test Comput.
Kamin Whitehouse, Juhi Ranjan, Jiakang Lu, Tamim I. Sookoor, Mehdi Saadat, Carrie Meinberg Burke, Galen Staengl, Anselmo Canfora, Hossein Haj-Hariri
2012 Misc conf
UbiComp
Juhi Ranjan, Yu Yao, Erin Griffiths, Kamin Whitehouse
2011 B conf
PIMRC
Hengchang Liu, Zhiheng Xie, Jingyuan Li, Kamin Whitehouse, John A. Stankovic, Shan Lin, David J. Siu
2011 conf
SESENA@ICSE
Wei Le, Jing Yang, Mary Lou Soffa, Kamin Whitehouse
2011 conf
HotCloud
Jie Liu, Michel Goraczko, Sean James, Christian Belady, Jiakang Lu, Kamin Whitehouse
2011 Misc conf
SenSys
Timothy W. Hnat, Vijay Srinivasan, Jiakang Lu, Tamim I. Sookoor, Raymond Dawson, John A. Stankovic, Kamin Whitehouse
2011 conf
BuildSys@SenSys
Vijay Srinivasan, John A. Stankovic, Kamin Whitehouse
2011 B ed.
EWSN
Pedro José Marrón, Kamin Whitehouse
2010 conf
SESENA@ICSE
Timothy W. Hnat, Tamim I. Sookoor, Pieter Hooimeijer, Westley Weimer, Kamin Whitehouse
2010 conf
INSS
Timothy W. Hnat, Kamin Whitehouse
2010 conf
IPSN
Sirajum Munir, Shan Lin, Enamul Hoque, S. M. Shahriar Nirjon, John A. Stankovic, Kamin Whitehouse
2010 A conf
MobiSys
Hengchang Liu, Jingyuan Li, Zhiheng Xie, Shan Lin, Kamin Whitehouse, John A. Stankovic, David J. Siu
2010 conf
INSS
S. M. Shahriar Nirjon, John A. Stankovic, Kamin Whitehouse
2010 conf
INSS
Hengchang Liu, Aravind Srinivasan, Kamin Whitehouse, John A. Stankovic
2010 conf
INSS
Jared Alexander, Matthew Bocknek, Kamin Whitehouse
2010 conf
IPSN
Yafeng Wu, Krasimira Kapitanova, Jingyuan Li, John A. Stankovic, Sang Hyuk Son, Kamin Whitehouse
2010 Misc conf
SenSys
Jiakang Lu, Tamim I. Sookoor, Vijay Srinivasan, Ge Gao, Brian Holben, John A. Stankovic, Eric Field, Kamin Whitehouse
2010 conf
SESENA@ICSE
Michael Okola, Kamin Whitehouse
2010 conf
Pervasive
Vijay Srinivasan, John A. Stankovic, Kamin Whitehouse
2010 conf
BuildSys@SenSys
Jiakang Lu, Dagnachew Birru, Kamin Whitehouse
2009 A* conf
INFOCOM
Jiakang Lu, Kamin Whitehouse
2009 Misc conf
SenSys
S. M. Shahriar Nirjon, John A. Stankovic, Kamin Whitehouse
2009 Misc conf
SenSys
Timothy W. Hnat, Tamim I. Sookoor, Kamin Whitehouse
2009 Misc conf
SenSys
Tamim I. Sookoor, Timothy W. Hnat, Pieter Hooimeijer, Westley Weimer, Kamin Whitehouse
2009 Misc conf
SenSys
Jingyuan Li, Yafeng Wu, Krasimira Kapitanova, John A. Stankovic, Kamin Whitehouse, Sang Hyuk Son
2009 Misc conf
SenSys
Nithya Ramanathan, Thomas Schoellhammer, Eddie Kohler, Kamin Whitehouse, Thomas C. Harmon, Deborah Estrin
2009 A conf
RTSS
Shan Lin, Gang Zhou, Kamin Whitehouse, Yafeng Wu, John A. Stankovic, Tian He
2008 J jnl
SIGBED Rev.
Vijay Srinivasan, John A. Stankovic, Kamin Whitehouse
2008 Misc conf
SenSys
Shan Lin, Gang Zhou, Yafeng Wu, Kamin Whitehouse, John A. Stankovic, Tian He
2008 Misc conf
SenSys
Qing Cao, Tarek F. Abdelzaher, John A. Stankovic, Kamin Whitehouse, Liqian Luo
2008 Misc conf
SenSys
Jiakang Lu, Kamin Whitehouse
2008 Misc conf
SenSys
Timothy W. Hnat, Tamim I. Sookoor, Pieter Hooimeijer, Westley Weimer, Kamin Whitehouse
2008 conf
IPSN
Robert F. Dickerson, Jiakang Lu, Jingyuan Li, Billy Chantree, Jian Lu, John A. Stankovic, Kamin Whitehouse
2008 A conf
ICDCS
Jingbin Zhang, Gang Zhou, Sang Hyuk Son, John A. Stankovic, Kamin Whitehouse
2008 Misc conf
SenSys
Tamim I. Sookoor, Timothy W. Hnat, Kamin Whitehouse
2008 Misc conf
UbiComp
Vijay Srinivasan, John A. Stankovic, Kamin Whitehouse
2008 conf
IOT
Robert F. Dickerson, Jiakang Lu, Jian Lu, Kamin Whitehouse
2007 J jnl
ACM SIGMOBILE Mob. Comput. Commun. Rev.
Kamin Whitehouse, Chris Karlof, David E. Culler
2007 Misc conf
SenSys
Jing Yang, Mary Lou Soffa, Leo Selavo, Kamin Whitehouse
2007 Misc conf
SenSys
Jing Yang, Mary Lou Soffa, Kamin Whitehouse
2006 conf
IPSN
Kamin Whitehouse, David E. Culler
2006 conf
IPSN
Kamin Whitehouse, Gilman Tolle, Jay Taneja, Cory Sharp, Sukun Kim, Jaein Jeong, Jonathan Hui, Prabal Dutta, David E. Culler
2006 B conf
EWSN
Kamin Whitehouse, Feng Zhao, Jie Liu
2006 conf
IPSN
Prabal Dutta, Jonathan Hui, Jaein Jeong, Sukun Kim, Cory Sharp, Jay Taneja, Gilman Tolle, Kamin Whitehouse, David E. Culler
2005 Misc conf
SenSys
Kamin Whitehouse, Feng Zhao, Jie Liu
2005 conf
IPSN
Kamin Whitehouse, Chris Karlof, Alec Woo, Fred Jiang, David E. Culler
2005 ch.
Ambient Intelligence
Philip Alexander Levis, Samuel Madden, Joseph Polastre, Robert Szewczyk, Kamin Whitehouse, Alec Woo, David Gay, Jason L. Hill, Matt Welsh, Eric A. Brewer, David E. Culler
2004 A conf
MobiSys
Kamin Whitehouse, Cory Sharp, David E. Culler, Eric A. Brewer
2003 J jnl
Mob. Networks Appl.
Kamin Whitehouse, David E. Culler
2002 conf
WSNA
Kamin Whitehouse, David E. Culler
redb/queries.py
← Index redb/queries.py python
"""
Database query functions for REDB.

Contains all functions that query ClickHouse for sample metadata,
deduplication checks, and catalog lookups.
"""
import os
import ast
import clickhouse_connect
from typing import List, Optional, Dict

from redb import settings
from redb.s3_utils import generate_s3_key_from_hash


def get_supported_formats(magika_filter: Optional[str] = None) -> List[str]:
    """
    Get supported file formats from SUPPORTED_FORMATS env variable or magika_filter override.
    Expected format: SUPPORTED_FORMATS=['pebin', 'elf']

    Args:
        magika_filter: Optional single format to filter by (overrides env var)

    Returns:
        List of supported format strings, defaults to ['pebin'] if not set
    """
    # If magika_filter is provided, use it as the only format
    if magika_filter:
        return [magika_filter]

    formats_str = os.getenv('SUPPORTED_FORMATS', "['pebin']")
    try:
        formats = ast.literal_eval(formats_str)
        if isinstance(formats, list) and all(isinstance(f, str) for f in formats):
            return formats
        else:
            print(f"[WARNING] SUPPORTED_FORMATS must be a list of strings, got: {formats_str}")
            return ['pebin']
    except (ValueError, SyntaxError) as e:
        print(f"[WARNING] Failed to parse SUPPORTED_FORMATS '{formats_str}': {e}")
        return ['pebin']


def get_db_catalog_connection():
    """Create and return a ClickHouse client for catalog queries"""
    return clickhouse_connect.get_client(
        host=os.getenv("DB_HOST"),
        port=int(os.getenv("DB_PORT", "8123")),
        verify=os.getenv("DB_ENFORCE_SSL", "False").lower() == "true",
        username=os.getenv("DB_USER"),
        password=os.getenv("DB_PASSWORD"),
        database=os.getenv("DB_NAME")
    )


def fetch_s3_objects_by_repository(repository: Optional[str], index_prefix: str, decompile: bool, notes: Optional[str] = None, magika_filter: Optional[str] = None, yara_scan: bool = False, force: bool = False) -> List[Dict]:
    """
    Query the ClickHouse repository_upload_sessions table to get samples.
    S3 bucket comes from env var, S3 key is derived from sha256.

    Args:
        repository: Repository name (e.g., "bazaar", "malshare"). If None or "all-repos", queries all repositories.
        index_prefix: Table prefix for checking existing samples
        decompile: Whether we're in decompile mode (affects which table to check for existing)
        notes: Optional filter for notes field
        magika_filter: Optional single filetype to filter by (overrides SUPPORTED_FORMATS)
        yara_scan: Whether we're in YARA-only mode (skips "already processed" check)

    Returns:
        List of dicts with sha256, s3_bucket, s3_key for each sample
    """
    try:
        client = get_db_catalog_connection()
        s3_bucket = os.getenv('S3_BUCKET')

        if not s3_bucket:
            print("[ERROR] S3_BUCKET environment variable is required")
            return []

        # Get supported formats from env or magika_filter override
        supported_formats = get_supported_formats(magika_filter)
        print(f"[INFO] Querying for supported formats: {supported_formats}")

        # Build query - repository filter is optional
        # Join with catalog_samples to get first_seen date
        if repository and repository != "all-repos":
            query = """
                SELECT DISTINCT rus.sha256, rus.filetype_magika, cs.first_seen
                FROM repository_upload_sessions rus
                LEFT JOIN catalog_samples cs ON rus.sha256 = cs.sha256
                WHERE rus.repository = %(repo)s
                  AND rus.filetype_magika IN %(formats)s
            """
            params = {"repo": repository, "formats": supported_formats}
        else:
            # Query all repositories
            query = """
                SELECT DISTINCT rus.sha256, rus.filetype_magika, cs.first_seen
                FROM repository_upload_sessions rus
                LEFT JOIN catalog_samples cs ON rus.sha256 = cs.sha256
                WHERE rus.filetype_magika IN %(formats)s
            """
            params = {"formats": supported_formats}

        if notes:
            query += " AND rus.notes LIKE %(notes)s"
            params["notes"] = f"%{notes}%"

        # Execute query and convert to list of dictionaries
        result = client.query(query, parameters=params)

        # Build rows with s3_bucket and s3_key derived from sha256
        rows = []
        for row in result.result_rows:
            sha256 = row[0]
            # Handle binary string if needed
            if isinstance(sha256, bytes):
                sha256 = sha256.decode('utf-8')

            rows.append({
                'sha256': sha256,
                's3_bucket': s3_bucket,
                's3_key': generate_s3_key_from_hash(sha256),
                'filetype_magika': row[1],
                'first_seen': row[2]  # From catalog_samples (None if not found)
            })

        print(f"[DEBUG] Found {len(rows)} objects in repository_upload_sessions for repository {repository}")

        # Extract SHA256 hashes from the results for bulk checking
        sha256_list = [row.get('sha256') for row in rows if row.get('sha256')]

        if sha256_list and not force:
            # Check which hashes are already in the database
            existing_hashes = is_in_db_bulk(sha256_list, index_prefix, decompile, yara_scan, magika_filter)
            print(f"[INFO] Found {len(existing_hashes)} objects already in database")

            # Filter out rows with existing hashes
            rows = [row for row in rows if row.get('sha256') not in existing_hashes]
            print(f"[INFO] After filtering, {len(rows)} objects remain to be processed")
        elif force:
            print(f"[INFO] Force mode: skipping deduplication check, processing all {len(sha256_list)} objects")

        # Randomize the order of rows before returning
        import random
        random.shuffle(rows)

        return rows
    except Exception as e:
        print(f"[ERROR] Failed to query repository_upload_sessions: {e}")
        return []
    finally:
        if 'client' in locals():
            client.close()


def fetch_s3_objects_by_date_range(
    index_prefix: str,
    decompile: bool,
    start_date: str,
    end_date: str,
    repository: Optional[str] = None,
    notes: Optional[str] = None,
    magika_filter: Optional[str] = None,
    yara_scan: bool = False,
    force: bool = False,
    analyzed: bool = False
) -> List[Dict]:
    """
    Query samples from catalog_samples by first_seen date range, filtered to only include
    samples that exist in repository_upload_sessions (bulk/repo uploads only).

    Args:
        index_prefix: Table prefix for checking existing samples
        decompile: Whether we're in decompile mode
        start_date: Start date (inclusive) in YYYY-MM-DD format
        end_date: End date (exclusive) in YYYY-MM-DD format
        repository: Optional repository filter
        notes: Optional notes filter
        magika_filter: Optional single filetype to filter by (overrides SUPPORTED_FORMATS)
        yara_scan: Whether we're in YARA-only mode (skips "already processed" check)
        analyzed: Filter to only samples already in basic_properties (cross-database join)

    Returns:
        List of dicts with sha256, s3_bucket, s3_key for each sample
    """
    try:
        client = get_db_catalog_connection()
        s3_bucket = os.getenv('S3_BUCKET')

        if not s3_bucket:
            print("[ERROR] S3_BUCKET environment variable is required")
            return []

        # Get supported formats from env or magika_filter override
        supported_formats = get_supported_formats(magika_filter)
        print(f"[INFO] Querying for supported formats: {supported_formats}")

        # Join catalog_samples with repository_upload_sessions to:
        # 1. Filter by first_seen date from catalog_samples
        # 2. Only include samples that exist in repository_upload_sessions (not user uploads)
        # 3. Optionally filter to only already-analyzed samples (in basic_properties)
        analyzed_join = ""
        if analyzed:
            basic_table = f"{index_prefix}_basic_properties"
            analyzed_join = f"INNER JOIN {basic_table} bp ON cs.sha256 = bp.sha256"
            print(f"[INFO] Filtering to already-analyzed samples in {basic_table}")

        query = f"""
            SELECT DISTINCT cs.sha256, rus.filetype_magika, cs.first_seen
            FROM catalog_samples cs
            INNER JOIN repository_upload_sessions rus ON cs.sha256 = rus.sha256
            {analyzed_join}
            WHERE cs.first_seen >= %(start_date)s
              AND cs.first_seen < %(end_date)s
              AND rus.filetype_magika IN %(formats)s
        """
        params = {
            "start_date": start_date,
            "end_date": end_date,
            "formats": supported_formats
        }

        if repository:
            query += " AND rus.repository = %(repo)s"
            params["repo"] = repository

        if notes:
            query += " AND rus.notes LIKE %(notes)s"
            params["notes"] = f"%{notes}%"

        # Execute query
        result = client.query(query, parameters=params)

        # Build rows with s3_bucket and s3_key derived from sha256
        rows = []
        for row in result.result_rows:
            sha256 = row[0]
            # Handle binary string if needed
            if isinstance(sha256, bytes):
                sha256 = sha256.decode('utf-8')

            rows.append({
                'sha256': sha256,
                's3_bucket': s3_bucket,
                's3_key': generate_s3_key_from_hash(sha256),
                'filetype_magika': row[1],
                'first_seen': row[2]  # From catalog_samples
            })

        date_info = f"from {start_date} to {end_date}"
        repo_info = f" for repository {repository}" if repository else ""
        print(f"[DEBUG] Found {len(rows)} objects in catalog_samples {date_info}{repo_info}")

        # Extract SHA256 hashes from the results for bulk checking
        sha256_list = [row.get('sha256') for row in rows if row.get('sha256')]

        if sha256_list and not force:
            # Check which hashes are already in the database
            existing_hashes = is_in_db_bulk(sha256_list, index_prefix, decompile, yara_scan, magika_filter)
            print(f"[INFO] Found {len(existing_hashes)} objects already in database")

            # Filter out rows with existing hashes
            rows = [row for row in rows if row.get('sha256') not in existing_hashes]
            print(f"[INFO] After filtering, {len(rows)} objects remain to be processed")
        elif force:
            print(f"[INFO] Force mode: skipping deduplication check, processing all {len(sha256_list)} objects")

        # Randomize the order of rows before returning
        import random
        random.shuffle(rows)

        return rows
    except Exception as e:
        print(f"[ERROR] Failed to query catalog_samples by date range: {e}")
        return []
    finally:
        if 'client' in locals():
            client.close()


def fetch_analyzed_samples(
    index_prefix: str,
    decompile: bool,
    magika_filter: Optional[str] = None,
    yara_scan: bool = False,
    force: bool = False,
    rerun: bool = False
) -> List[Dict]:
    """
    Query samples from basic_properties that have already been analyzed.
    Useful for reprocessing with decompilation or specific modules.

    Args:
        index_prefix: Table prefix for ClickHouse
        decompile: Whether we're in decompile mode (affects filtering)
        magika_filter: Optional single filetype to filter by (overrides SUPPORTED_FORMATS)
        yara_scan: Whether we're in YARA-only mode (skips decompile filtering)
        force: Skip all deduplication checks when True
        rerun: Query disassembled table directly (only already-disassembled samples)

    Returns:
        List of dicts with sha256, s3_bucket, s3_key for each analyzed sample
    """
    try:
        client = settings.create_clickhouse_client()
        s3_bucket = os.getenv('S3_BUCKET')

        if not s3_bucket:
            print("[ERROR] S3_BUCKET environment variable is required")
            return []

        # Rerun mode: query directly from disassembled_functions_references
        # instead of basic_properties. This targets only samples that already
        # went through binja successfully.
        if rerun:
            disassembled_table = _get_code_dedup_table(magika_filter)
            basic_table = f"{index_prefix}_basic_properties"

            # Join with basic_properties to get filetype_magika and apply format filters
            conditions = []
            params = {}

            if magika_filter:
                conditions.append("bp.filetype_magika = %(magika)s")
                params["magika"] = magika_filter
            else:
                supported_formats = get_supported_formats()
                conditions.append("bp.filetype_magika IN %(formats)s")
                params["formats"] = supported_formats

            query = (
                f"SELECT DISTINCT d.sha256, bp.filetype_magika "
                f"FROM {disassembled_table} d FINAL "
                f"INNER JOIN {basic_table} bp FINAL ON d.sha256 = bp.sha256"
            )
            if conditions:
                query += " WHERE " + " AND ".join(conditions)

            result = client.query(query, parameters=params)

            rows = []
            for row in result.result_rows:
                sha256 = row[0]
                if isinstance(sha256, bytes):
                    sha256 = sha256.decode('utf-8')
                rows.append({
                    'sha256': sha256,
                    's3_bucket': s3_bucket,
                    's3_key': generate_s3_key_from_hash(sha256),
                    'filetype_magika': row[1]
                })

            print(f"[INFO] Rerun mode: found {len(rows)} already-disassembled samples in {disassembled_table}")

            import random
            random.shuffle(rows)
            return rows

        basic_table = f"{index_prefix}_basic_properties"

        # Build query
        conditions = []
        params = {}

        if magika_filter:
            conditions.append("filetype_magika = %(magika)s")
            params["magika"] = magika_filter
        else:
            supported_formats = get_supported_formats()
            conditions.append("filetype_magika IN %(formats)s")
            params["formats"] = supported_formats

        query = f"SELECT DISTINCT sha256, filetype_magika FROM {basic_table} FINAL"
        if conditions:
            query += " WHERE " + " AND ".join(conditions)

        result = client.query(query, parameters=params)

        rows = []
        for row in result.result_rows:
            sha256 = row[0]
            if isinstance(sha256, bytes):
                sha256 = sha256.decode('utf-8')
            rows.append({
                'sha256': sha256,
                's3_bucket': s3_bucket,
                's3_key': generate_s3_key_from_hash(sha256),
                'filetype_magika': row[1]
            })

        print(f"[DEBUG] Found {len(rows)} analyzed samples in {basic_table}")

        # In YARA-only mode, filter out already-scanned samples (unless force)
        if yara_scan and not force:
            sha256_list = [r['sha256'] for r in rows]
            if sha256_list:
                already_scanned = _check_yara_matches_bulk(sha256_list)
                print(f"[INFO] Found {len(already_scanned)} already YARA-scanned samples")
                rows = [r for r in rows if r['sha256'].lower() not in already_scanned]
                print(f"[INFO] After filtering, {len(rows)} samples remain for YARA scanning")
        # In decompile mode, filter out already-disassembled samples (unless force)
        # We check disassembly (not decompilation) because disassembly is the ground truth:
        # disassembly always succeeds, decompilation may not, so a missing decompile
        # entry doesn't mean the sample wasn't analyzed.
        elif decompile and not force:
            sha256_list = [r['sha256'] for r in rows]
            if sha256_list:
                disassembled_table = _get_code_dedup_table(magika_filter)
                batch_size = 3900

                already_disassembled = set()
                for i in range(0, len(sha256_list), batch_size):
                    batch = sha256_list[i:i+batch_size]
                    placeholders = "','".join(batch)
                    check_query = f"SELECT DISTINCT sha256 FROM {disassembled_table} FINAL WHERE sha256 IN ('{placeholders}')"
                    check_result = client.query(check_query)
                    for check_row in check_result.result_rows:
                        hash_value = check_row[0]
                        if isinstance(hash_value, bytes):
                            hash_value = hash_value.decode('utf-8')
                        already_disassembled.add(hash_value)

                print(f"[INFO] Found {len(already_disassembled)} already-disassembled samples")
                rows = [r for r in rows if r['sha256'] not in already_disassembled]
                print(f"[INFO] After filtering, {len(rows)} samples remain for decompilation")
        elif force:
            print(f"[INFO] Force mode: returning all {len(rows)} analyzed samples")

        # Randomize the order of rows before returning
        import random
        random.shuffle(rows)

        return rows
    except Exception as e:
        print(f"[ERROR] Failed to query analyzed samples: {e}")
        return []
    finally:
        if 'client' in locals():
            client.close()


def is_in_db(sha256, index_prefix, client=None):
    """
    Check if a sample with given SHA256 already exists in the database

    Args:
        sha256: File's SHA256 hash
        index_prefix: Table prefix for ClickHouse
        client: Optional ClickHouse client instance

    Returns:
        bool: True if file exists, False otherwise
    """
    table = f"{index_prefix}_basic_properties"
    close_client = False

    try:
        if client is None:
            client = settings.create_clickhouse_client()
            close_client = True

        # No need for FINAL when checking existence with LIMIT 1
        # Any version of the row proves the sample exists
        query = f"SELECT 1 FROM {table} WHERE sha256 = %(sha256)s LIMIT 1"
        result = client.query(query, parameters={"sha256": sha256})

        return len(result.result_rows) > 0
    except Exception as e:
        print(f"[ERROR] Failed to check if file is in DB: {e}")
        return False
    finally:
        if close_client and client:
            client.close()


def _get_code_dedup_table(magika_filter: Optional[str] = None) -> str:
    """
    Return the code-analysis table used for deduplication based on filetype.

    APK samples are disassembled into code_apk_smali_methods_references;
    everything else uses {CLICKHOUSE_CODE_PREFIX}_disassembled_functions_references.
    """
    if magika_filter == "apk":
        return "code_apk_smali_methods_references"
    code_prefix = os.getenv("CLICKHOUSE_CODE_PREFIX", "code_binja")
    return f"{code_prefix}_disassembled_functions_references"


def is_in_code_db(sha256, client=None, filetype=None):
    """
    Check if a sample with given SHA256 has already been disassembled.

    Args:
        sha256: File's SHA256 hash
        client: Optional ClickHouse client instance
        filetype: Magika filetype label (e.g. 'apk') to pick the right code table

    Returns:
        bool: True if file has been disassembled, False otherwise
    """
    table = _get_code_dedup_table(filetype)
    close_client = False

    try:
        if client is None:
            client = settings.create_clickhouse_client()
            close_client = True

        query = f"SELECT 1 FROM {table} WHERE sha256 = %(sha256)s LIMIT 1"
        result = client.query(query, parameters={"sha256": sha256})

        return len(result.result_rows) > 0
    except Exception as e:
        print(f"[ERROR] Failed to check if file is in code DB: {e}")
        return False
    finally:
        if close_client and client:
            client.close()


def _check_yara_matches_bulk(sha256_list):
    """
    Check which samples have already been YARA-scanned by querying yara_matches.

    The yara_matches table uses FixedString(32) binary sha256, so we convert
    hex strings with unhex().

    Args:
        sha256_list: List of hex SHA256 strings to check

    Returns:
        set: Set of hex SHA256 hashes that already have YARA matches
    """
    # unhex('64hexchars') adds 9 chars overhead per entry vs plain '64hexchars'.
    # 3900 works for plain strings (~67 chars each = 261K) but overflows
    # max_query_size (262144) with unhex() wrapping (~73 chars each = 285K).
    # 3500 × 73 = 255K stays safely under the limit.
    batch_size = 3500
    already_scanned = set()

    try:
        client = settings.create_clickhouse_client()

        total_batches = (len(sha256_list) + batch_size - 1) // batch_size
        for batch_num, i in enumerate(range(0, len(sha256_list), batch_size), 1):
            batch = sha256_list[i:i+batch_size]
            unhex_list = ",".join(f"unhex('{h}')" for h in batch)
            query = f"SELECT DISTINCT hex(sha256) FROM yara_matches FINAL WHERE sha256 IN ({unhex_list})"
            result = client.query(query)
            for row in result.result_rows:
                hash_value = row[0]
                if isinstance(hash_value, bytes):
                    hash_value = hash_value.decode('utf-8')
                already_scanned.add(hash_value.lower())

            if batch_num % 100 == 0 or batch_num == total_batches:
                print(f"[INFO] YARA dedup progress: batch {batch_num}/{total_batches}, found {len(already_scanned)} so far")

        print(f"[INFO] YARA dedup: found {len(already_scanned)} already-scanned samples")
        return already_scanned

    except Exception as e:
        raise RuntimeError(f"YARA dedup query failed — aborting to prevent reprocessing all samples: {e}")
    finally:
        if 'client' in locals():
            client.close()


def is_in_db_bulk(sha256_list, index_prefix, decompile, yara_scan=False, magika_filter=None):
    """
    Check which samples from a list of SHA256 hashes should be skipped.

    Args:
        sha256_list: List of SHA256 hashes to check
        index_prefix: Table prefix for ClickHouse
        decompile: Whether we're in decompile mode
        yara_scan: Whether we're in YARA-only mode (checks yara_matches table)
        magika_filter: Magika filetype label (e.g. 'apk') to pick the right code table

    Returns:
        set: Set of SHA256 hashes that should be skipped
    """
    if not sha256_list:
        return set()

    # YARA-only mode: check yara_matches table for already-scanned samples
    if yara_scan:
        return _check_yara_matches_bulk(sha256_list)

    basic_table = f"{index_prefix}_basic_properties"
    batch_size = 3900

    try:
        client = settings.create_clickhouse_client()

        # Step 1: Check basic_properties (required for both modes)
        in_basic_properties = set()
        for i in range(0, len(sha256_list), batch_size):
            batch = sha256_list[i:i+batch_size]
            placeholders = "','".join(batch)
            query = f"SELECT DISTINCT sha256 FROM {basic_table} FINAL WHERE sha256 IN ('{placeholders}')"
            result = client.query(query)
            for row in result.result_rows:
                hash_value = row[0]
                if isinstance(hash_value, bytes):
                    hash_value = hash_value.decode('utf-8')
                in_basic_properties.add(hash_value)

        if not decompile:
            # Analysis mode: skip samples already in basic_properties
            return in_basic_properties

        # Decompile mode: skip samples NOT in basic_properties + already decompiled
        not_in_basic = set(sha256_list) - in_basic_properties

        if not in_basic_properties:
            return set(sha256_list)  # None ready for decompilation

        # Step 2: Check disassembled table for samples that ARE in basic_properties
        # Disassembly is the ground truth for code analysis (it always succeeds,
        # unlike decompilation which may fail).
        disassembled_table = _get_code_dedup_table(magika_filter)

        already_disassembled = set()
        samples_to_check = list(in_basic_properties)
        for i in range(0, len(samples_to_check), batch_size):
            batch = samples_to_check[i:i+batch_size]
            placeholders = "','".join(batch)
            query = f"SELECT DISTINCT sha256 FROM {disassembled_table} FINAL WHERE sha256 IN ('{placeholders}')"
            result = client.query(query)
            for row in result.result_rows:
                hash_value = row[0]
                if isinstance(hash_value, bytes):
                    hash_value = hash_value.decode('utf-8')
                already_disassembled.add(hash_value)

        return not_in_basic | already_disassembled

    except Exception as e:
        print(f"[ERROR] Failed to check hashes in DB: {e}")
        return set()
    finally:
        if 'client' in locals():
            client.close()