Xiao-Shan Gao

249 papers A* 16A 4B 24C 4Misc 4Journal 171Unranked 23
YearRankTypeTitle / Venue / Authors
2026 J jnl
CoRR
Ruichen Qiu, Yichuan Cao, Junqi Liu, Dakai Guo, Xiao-Shan Gao, Lihong Zhi, Ruyong Feng
2026 J jnl
CoRR
Yifan Zhu, Yibo Miao, Yinpeng Dong, Xiao-Shan Gao
2025 J jnl
CoRR
Ruichen Qiu, Jiajun Tan, Jiayue Pu, Honglin Wang, Xiao-Shan Gao, Fei Sun
2025 J jnl
CoRR
Lijia Yu, Xiao-Shan Gao, Lijun Zhang
2025 J jnl
Math. Comput.
Qiao-Long Huang, Xiao-Shan Gao
2025 J jnl
Neural Networks
Yihan Wang, Shuang Liu, Xiao-Shan Gao
2025 J jnl
CoRR
Lijia Yu, Yibo Miao, Yifan Zhu, Xiao-Shan Gao, Lijun Zhang
2025 A* conf
ICLR
Lijia Yu, Yibo Miao, Yifan Zhu, Xiao-Shan Gao, Lijun Zhang
2025 J jnl
Trans. Mach. Learn. Res.
Yihan Wang, Yiwei Lu, Guojun Zhang, Franziska Boenisch, Adam Dziedzic, Yaoliang Yu, Xiao-Shan Gao
2025 A* conf
AAAI
Ruichen Qiu, Yibo Miao, Shiwen Wang, Yifan Zhu, Lijia Yu, Xiao-Shan Gao
2025 A* conf
ICLR
Shuang Liu, Yihan Wang, Yifan Zhu, Yibo Miao, Xiao-Shan Gao
2025 J jnl
CoRR
Shuang Liu, Yihan Wang, Yifan Zhu, Yibo Miao, Xiao-Shan Gao
2025 J jnl
CoRR
Yifan Zhu, Lijia Yu, Xiao-Shan Gao
2025 J jnl
IEEE Trans. Inf. Theory
Laigang Guo, Raymond W. Yeung, Xiao-Shan Gao
2025 J jnl
CoRR
Yichuan Cao, Yibo Miao, Xiao-Shan Gao, Yinpeng Dong
2024 J jnl
CoRR
Yihan Wang, Yiwei Lu, Guojun Zhang, Franziska Boenisch, Adam Dziedzic, Yaoliang Yu, Xiao-Shan Gao
2024 J jnl
CoRR
Yihan Wang, Yiwei Lu, Xiao-Shan Gao, Gautam Kamath, Yaoliang Yu
2024 J jnl
CoRR
Yihan Wang, Shuang Liu, Xiao-Shan Gao
2024 A* conf
AAAI
Yifan Zhu, Lijia Yu, Xiao-Shan Gao
2024 A* conf
NeurIPS
Yihan Wang, Yifan Zhu, Xiao-Shan Gao
2024 J jnl
CoRR
Yihan Wang, Yifan Zhu, Xiao-Shan Gao
2024 A* conf
ICML
Shuyu Cheng, Yibo Miao, Yinpeng Dong, Xiao Yang, Xiao-Shan Gao, Jun Zhu
2024 J jnl
CoRR
Shuyu Cheng, Yibo Miao, Yinpeng Dong, Xiao Yang, Xiao-Shan Gao, Jun Zhu
2024 A* conf
AAAI
Shuang Liu, Yihan Wang, Xiao-Shan Gao
2024 J jnl
CoRR
Shuang Liu, Yihan Wang, Xiao-Shan Gao
2024 J jnl
CoRR
Lijia Yu, Xiao-Shan Gao, Lijun Zhang, Yibo Miao
2024 A* conf
NeurIPS
Lijia Yu, Xiao-Shan Gao, Lijun Zhang, Yibo Miao
2024 A* conf
ICML
Lijia Yu, Shuang Liu, Yibo Miao, Xiao-Shan Gao, Lijun Zhang
2024 J jnl
CoRR
Lijia Yu, Shuang Liu, Yibo Miao, Xiao-Shan Gao, Lijun Zhang
2024 A* conf
NeurIPS
Yibo Miao, Yinpeng Dong, Jinlai Zhang, Lijia Yu, Xiao Yang, Xiao-Shan Gao
2024 A* conf
ICLR
Lijia Yu, Xiao-Shan Gao, Lijun Zhang
2024 J jnl
CoRR
Ruichen Qiu, Yibo Miao, Shiwen Wang, Lijia Yu, Yifan Zhu, Xiao-Shan Gao
2024 B conf
ISIT
Laigang Guo, Raymond W. Yeung, Xiao-Shan Gao
2024 J jnl
CoRR
Laigang Guo, Raymond W. Yeung, Xiao-Shan Gao
2024 J jnl
J. Symb. Comput.
Qiao-Long Huang, Ke Ye, Xiao-Shan Gao
2024 A* conf
NeurIPS
Yibo Miao, Yifan Zhu, Lijia Yu, Jun Zhu, Xiao-Shan Gao, Yinpeng Dong
2024 J jnl
CoRR
Yibo Miao, Yifan Zhu, Yinpeng Dong, Lijia Yu, Jun Zhu, Xiao-Shan Gao
2024 A* conf
ICML
Yifan Zhu, Yibo Miao, Yinpeng Dong, Xiao-Shan Gao
2024 J jnl
CoRR
Yifan Zhu, Yibo Miao, Yinpeng Dong, Xiao-Shan Gao
2023 A* conf
ICML
Lijia Yu, Yihan Wang, Xiao-Shan Gao
2023 J jnl
J. Comput. Des. Eng.
Qin Wu, Chun-Ming Yuan, Li-Yong Shen, Shitao He, Xiao-Shan Gao
2023 J jnl
CoRR
Yifan Zhu, Lijia Yu, Xiao-Shan Gao
2023 J jnl
J. Syst. Sci. Complex.
Lijia Yu, Xiao-Shan Gao
2023 B conf
ISSAC
Qiao-Long Huang, Xiao-Shan Gao
2023 J jnl
IEEE Trans. Inf. Theory
Laigang Guo, Raymond W. Yeung, Xiao-Shan Gao
2023 conf
ICONIP (8)
Yihan Wang, Lijia Yu, Xiao-Shan Gao
2023 J jnl
CoRR
Yihan Wang, Lijia Yu, Xiao-Shan Gao
2022 J jnl
CoRR
Xiao-Shan Gao, Shuang Liu, Lijia Yu
2022 J jnl
CoRR
Lijia Yu, Yihan Wang, Xiao-Shan Gao
2022 J jnl
CoRR
Qiao-Long Huang, Xiao-Shan Gao
2022 A* conf
NeurIPS
Yibo Miao, Yinpeng Dong, Jun Zhu, Xiao-Shan Gao
2022 J jnl
CoRR
Yibo Miao, Yinpeng Dong, Jun Zhu, Xiao-Shan Gao
2022 J jnl
Entropy
Laigang Guo, Chun-Ming Yuan, Xiao-Shan Gao
2022 J jnl
J. Comput. Des. Eng.
Hong-Yu Ma, Chun-Ming Yuan, Li-Yong Shen, Xiao-Shan Gao
2022 B conf
ISIT
Laigang Guo, Raymond W. Yeung, Xiao-Shan Gao
2022 J jnl
CoRR
Laigang Guo, Raymond W. Yeung, Xiao-Shan Gao
2022 J jnl
J. Syst. Sci. Complex.
Yu-Ao Chen, Xiao-Shan Gao
2022 J jnl
CoRR
Qiao-Long Huang, Ke Ye, Xiao-Shan Gao
2021 J jnl
CoRR
Laigang Guo, Chun-Ming Yuan, Xiao-Shan Gao
2021 J jnl
Entropy
Laigang Guo, Chun-Ming Yuan, Xiao-Shan Gao
2021 J jnl
CoRR
Lijia Yu, Xiao-Shan Gao
2021 J jnl
CoRR
Chen Zhao, Xiao-Shan Gao
2021 J jnl
Quantum
Chen Zhao, Xiao-Shan Gao
2021 B conf
ISIT
Laigang Guo, Chun-Ming Yuan, Xiao-Shan Gao
2021 B conf
ISSAC
Xiao-Shan Gao
2021 J jnl
Quantum Mach. Intell.
Chen Zhao, Xiao-Shan Gao
2021 J jnl
CoRR
Lijia Yu, Xiao-Shan Gao
2021 J jnl
Math. Comput. Sci.
Youren Hu, Xiao-Shan Gao
2020 J jnl
J. Symb. Comput.
Qiao-Long Huang, Xiao-Shan Gao
2020 J jnl
CoRR
Lijia Yu, Xiao-Shan Gao
2020 J jnl
CoRR
Laigang Guo, Chun-Ming Yuan, Xiao-Shan Gao
2020 J jnl
CoRR
Laigang Guo, Chun-Ming Yuan, Xiao-Shan Gao
2019 J jnl
Theor. Comput. Sci.
Rui-Juan Jing, Chun-Ming Yuan, Xiao-Shan Gao
2019 J jnl
J. Syst. Sci. Complex.
Xiao-Shan Gao, Hongbo Li, Dongming Wang
2019 J jnl
CoRR
Chen Zhao, Xiao-Shan Gao
2019 B conf
CASC
Qiao-Long Huang, Xiao-Shan Gao
2019 J jnl
J. Syst. Sci. Complex.
Youren Hu, Xiao-Shan Gao
2019 J jnl
CoRR
Youren Hu, Xiao-Shan Gao
2018 J jnl
J. Symb. Comput.
Xiao-Shan Gao
2018 J jnl
IACR Cryptol. ePrint Arch.
Yu-Ao Chen, Xiao-Shan Gao
2018 J jnl
CoRR
Yu-Ao Chen, Xiao-Shan Gao, Chun-Ming Yuan
2017 J jnl
J. Syst. Sci. Complex.
Wei Zhu, Xiao-Shan Gao
2017 J jnl
Robotica
Ming-Yong Zhao, Xiao-Shan Gao, Qiang Zhang
2017 J jnl
J. Symb. Comput.
Xiao-Shan Gao, Zhang Huang, Chun-Ming Yuan
2017 B conf
CASC
Youren Hu, Xiao-Shan Gao
2017 J jnl
CoRR
Yu-Ao Chen, Xiao-Shan Gao
2017 B conf
ISSAC
Yu-Ao Chen, Xiao-Shan Gao
2017 J jnl
CoRR
Qiao-Long Huang, Xiao-Shan Gao
2017 J jnl
J. Syst. Sci. Complex.
Xiao-Shan Gao, Jie Chen, Jun Shao, Shouyang Wang
2017 J jnl
CoRR
Yu-Ao Chen, Xiao-Shan Gao
2017 J jnl
CoRR
Qiao-Long Huang, Xiao-Shan Gao
2017 J jnl
CoRR
Qiao-Long Huang, Xiao-Shan Gao
2017 B conf
CASC
Qiao-Long Huang, Xiao-Shan Gao
2017 J jnl
CoRR
Qiao-Long Huang, Xiao-Shan Gao
2017 conf
MACIS
Qiao-Long Huang, Xiao-Shan Gao
2017 J jnl
CoRR
Qiao-Long Huang, Xiao-Shan Gao
2017 J jnl
J. Syst. Sci. Complex.
Xiao-Shan Gao, Zhang Huang, Jie Wang, Chun-Ming Yuan
2017 J jnl
ACM Commun. Comput. Algebra
Xiao-Shan Gao
2016 J jnl
CoRR
Rui-Juan Jing, Chun-Ming Yuan, Xiao-Shan Gao
2016 J jnl
CoRR
Xiao-Shan Gao, Zhang Huang, Chun-Ming Yuan
2016 conf
ICIA
Qiang Zhang, Xiao-Shan Gao
2016 B ed.
ISSAC
Sergei A. Abramov, Eugene V. Zima, Xiao-Shan Gao
2016 J jnl
Sci. China Inf. Sci.
Xiao-Shan Gao, Zhenyu Huang
2016 J jnl
Robotica
Qiang Zhang, Shurong Li, Jian-Xin Guo, Xiao-Shan Gao
2016 J jnl
CoRR
Xiao-Shan Gao, Zhang Huang, Jie Wang, Chun-Ming Yuan
2015 J jnl
CoRR
Wei Zhu, Xiao-Shan Gao
2015 J jnl
ACM Commun. Comput. Algebra
Xiao-Shan Gao
2015 J jnl
Comput. Aided Des.
Zhengyuan Yang, Liyong Shen, Chun-Ming Yuan, Xiao-Shan Gao
2015 B conf
CASC
Kai Jin, Jin-San Cheng, Xiao-Shan Gao
2015 J jnl
Found. Comput. Math.
Wei Li, Chun-Ming Yuan, Xiao-Shan Gao
2015 J jnl
J. Symb. Comput.
Wei Li, Chun-Ming Yuan, Xiao-Shan Gao
2015 J jnl
J. Syst. Sci. Complex.
Jian-Xin Guo, Qiang Zhang, Xiao-Shan Gao, Hongbo Li
2015 J jnl
Int. J. Comput. Commun. Control
Qiang Zhang, Shurong Li, Jian-Xin Guo, Xiao-Shan Gao
2014 J jnl
CoRR
Xiao-Shan Gao, Zhang Huang, Chun-Ming Yuan
2014 J jnl
J. Syst. Sci. Complex.
Jin-San Cheng, Xiao-Shan Gao
2013 J jnl
Comput. Aided Des.
Jian-Xin Guo, Ke Zhang, Qiang Zhang, Xiao-Shan Gao
2013 C conf
ACC
Qiang Zhang, Shurong Li, Xiao-Shan Gao
2013 B conf
ISSAC
Wei Li, Chun-Ming Yuan, Xiao-Shan Gao
2013 J jnl
J. Syst. Sci. Complex.
Jian-Xin Guo, Qiang Zhang, Xiao-Shan Gao
2012 J jnl
J. Symb. Comput.
Xiao-Shan Gao, Deepak Kapur
2012 J jnl
CoRR
Liyong Shen, Chun-Ming Yuan, Xiao-Shan Gao
2012 J jnl
CoRR
Jin-San Cheng, Kai Jin, Xiao-Shan Gao, Daniel Lazard
2012 J jnl
Comput. Aided Geom. Des.
Liyong Shen, Chun-Ming Yuan, Xiao-Shan Gao
2012 J jnl
J. Symb. Comput.
Xiao-Shan Gao, Zhenyu Huang
2012 J jnl
Math. Comput. Sci.
Hongbo Li, Xiao-Shan Gao, Lixian Zhang, Ruiyong Sun
2012 J jnl
Comput. Aided Geom. Des.
Xiao-Shan Gao, Christoph M. Hoffmann, Robert Joan-Arinyo
2012 J jnl
CoRR
Zhi-Yong Zhang, Chun-Ming Yuan, Xiao-Shan Gao
2012 conf
ASCM
Zhi-Yong Zhang, Chun-Ming Yuan, Xiao-Shan Gao
2012 J jnl
J. Syst. Sci. Complex.
Shang-Wei Zhao, Ruyong Feng, Xiao-Shan Gao
2012 J jnl
J. Symb. Comput.
Xiao-Shan Gao, Deepak Kapur
2012 J jnl
J. Symb. Comput.
Jin-San Cheng, Xiao-Shan Gao, Leilei Guo
2012 J jnl
CoRR
Wei Li, Chun-Ming Yuan, Xiao-Shan Gao
2012 J jnl
ACM Commun. Comput. Algebra
Wei Li, Chun-Ming Yuan, Xiao-Shan Gao
2012 J jnl
Comput. Geom.
Xiao-Shan Gao, Robert Joan-Arinyo, Dominique Michelucci
2011 J jnl
Sci. China Inf. Sci.
Mei Zhang, Wei Yan, Chun-Ming Yuan, Dingkang Wang, Xiao-Shan Gao
2011 J jnl
CoRR
Wei Li, Xiao-Shan Gao
2011 J jnl
CoRR
Jin-San Cheng, Xiao-Shan Gao
2011 J jnl
J. Syst. Sci. Complex.
Liyong Shen, Eng-Wee Chionh, Xiao-Shan Gao, Jia Li
2011 J jnl
CoRR
Jin-San Cheng, Xiao-Shan Gao, Leilei Guo
2011 J jnl
CoRR
Wei Li, Chun-Ming Yuan, Xiao-Shan Gao
2011 B conf
ISSAC
Wei Li, Xiao-Shan Gao, Chun-Ming Yuan
2010 conf
IPCV
Li Han, Xiao-Shan Gao
2010 J jnl
CoRR
Xiao-Shan Gao, Zhenyu Huang
2010 J jnl
CoRR
Xiao-Shan Gao, Wei Li, Chun-Ming Yuan
2010 J jnl
CoRR
Shang-Wei Zhao, Ruyong Feng, Xiao-Shan Gao
2010 J jnl
IACR Cryptol. ePrint Arch.
Shang-Wei Zhao, Ruyong Feng, Xiao-Shan Gao
2010 J jnl
J. Autom. Reason.
Zheng Ye, Shang-Ching Chou, Xiao-Shan Gao
2010 J jnl
J. Autom. Reason.
Zheng Ye, Shang-Ching Chou, Xiao-Shan Gao
2009 J jnl
J. Symb. Comput.
Xiao-Shan Gao, Yong Luo, Chun-Ming Yuan
2009 J jnl
J. Syst. Sci. Complex.
Chun-Ming Yuan, Xiao-Shan Gao
2009 B conf
CASC
Jin-San Cheng, Xiao-Shan Gao, Jia Li
2009 J jnl
CoRR
Jin-San Cheng, Xiao-Shan Gao, Jia Li
2009 conf
CAD/Graphics
Li Han, Xiao-Shan Gao, Hongbo Li, Lixian Zhang, Jie Liu
2009 J jnl
J. Symb. Comput.
Xiao-Shan Gao, Joris van der Hoeven, Chun-Ming Yuan, Gui-Lin Zhang
2009 J jnl
J. Symb. Comput.
Jin-San Cheng, Xiao-Shan Gao, Chee-Keng Yap
2009 J jnl
J. Symb. Comput.
Mingbo Zhang, Xiao-Shan Gao
2009 J jnl
IACR Cryptol. ePrint Arch.
Xiao-Shan Gao, Zhenyu Huang
2009 J jnl
Theor. Comput. Sci.
Shang-Wei Zhao, Xiao-Shan Gao
2009 B conf
ISSAC
Jin-San Cheng, Xiao-Shan Gao, Jia Li
2009 Misc conf
SAC
Jin-San Cheng, Xiao-Shan Gao, Jia Li
2008 J jnl
ACM Commun. Comput. Algebra
Xiao-Shan Gao, Zhenyu Huang
2008 J jnl
J. Syst. Sci. Complex.
Fengjuan Chai, Xiao-Shan Gao, Chun-Ming Yuan
2008 J jnl
IEEE Intell. Syst.
Xiao-Shan Gao, Dantong Ouyang, Ji-gui Sun, Sanjiang Li, Tian-shun Yao, Ruzhan Lu, Chun-Yi Shi, Zhangang Han, Jue Wang, Cun-gen Cao, Ruqian Lu
2008 conf
Automated Deduction in Geometry
Zheng Ye, Shang-Ching Chou, Xiao-Shan Gao
2008 J jnl
Appl. Algebra Eng. Commun. Comput.
Xiao-Shan Gao, Mingbo Zhang
2008 J jnl
J. Comput. Sci. Technol.
Jia Li, Liyong Shen, Xiao-Shan Gao
2008 J jnl
J. Symb. Comput.
Ruyong Feng, Xiao-Shan Gao, Zhenyu Huang
2008 Misc conf
SAC
Xiao-Shan Gao, Dominique Michelucci, Pascal Schreck
2007 conf
ASCM
Jia Li, Xiao-Shan Gao
2007 J jnl
J. Heuristics
Xinchao Zhao, Xiao-Shan Gao
2007 B conf
ISSAC
Jin-San Cheng, Xiao-Shan Gao, Chee-Keng Yap
2007 J jnl
Appl. Math. Comput.
Xinchao Zhao, Xiao-Shan Gao, Ze-Chun Hu
2007 J jnl
Frontiers Comput. Sci. China
Wenjun Wu, Xiao-Shan Gao
2007 conf
CAD/Graphics
Jia Li, Liyong Shen, Xiao-Shan Gao
2007 conf
ASCM
Gui-Lin Zhang, Xiao-Shan Gao
2006 J jnl
Comput. Aided Des.
Xiao-Shan Gao, Qiang Lin, Gui-Fang Zhang
2006 J jnl
J. Symb. Comput.
Ruyong Feng, Xiao-Shan Gao
2006 J jnl
J. Comput. Sci. Technol.
Wen-tsun Wu, Xiao-Shan Gao
2006 conf
Challenges in Symbolic Computation Software
Xiao-Shan Gao, Mingbo Zhang
2006 Misc conf
SAC
Xiao-Shan Gao, Dominique Michelucci, Pascal Schreck
2006 J jnl
Int. J. Comput. Geom. Appl.
Xiao-Shan Gao, Dominique Michelucci
2006 J jnl
Comput. Aided Geom. Des.
Eng-Wee Chionh, Xiao-Shan Gao, Liyong Shen
2006 J jnl
J. Math. Imaging Vis.
Xiao-Shan Gao, Jianliang Tang
2006 J jnl
Vis. Comput.
Ming Li, Xiao-Shan Gao, Shang-Ching Chou
2006 B conf
ISSAC
Xiao-Shan Gao, Chun-Ming Yuan
2006 Misc conf
SAC
Gui-Fang Zhang, Xiao-Shan Gao
2006 J jnl
J. Syst. Sci. Complex.
Jia Li, Xiao-Shan Gao
2006 J jnl
Int. J. Comput. Geom. Appl.
Gui-Fang Zhang, Xiao-Shan Gao
2006 J jnl
ACM Commun. Comput. Algebra
Xiao-Shan Gao
2005 J jnl
SIGSAM Bull.
Xiao-Shan Gao, Gui-Fang Zhang
2005 B conf
ISSAC
J. M. Aroca, J. Cano, Ruyong Feng, Xiao-Shan Gao
2005 conf
IMA Conference on the Mathematics of Surfaces
Jin-San Cheng, Xiao-Shan Gao, Ming Li
2005 J jnl
IEEE Trans. Robotics
Xiao-Shan Gao, Deli Lei, Qizheng Liao, Gui-Fang Zhang
2005 J jnl
J. Comput. Sci. Technol.
Ming Li, Xiao-Shan Gao, Jin-San Cheng
2005 J jnl
SIGSAM Bull.
Jin-San Cheng, Xiao-Shan Gao, Ming Li
2004 C conf
SNPD
Xinchao Zhao, Xiao-Shan Gao
2004 J jnl
Discret. Appl. Math.
Jimin Wang, Xiao-Shan Gao
2004 B conf
ISSAC
Xiao-Shan Gao, Mingbo Zhang
2004 J jnl
J. Symb. Comput.
Arjeh M. Cohen, Xiao-Shan Gao, Nobuki Takayama
2004 conf
Automated Deduction in Geometry
Gui-Fang Zhang, Xiao-Shan Gao
2004 conf
IWMM/GIAE
Ruyong Feng, Xiao-Shan Gao
2004 C conf
GMP
Xiao-Shan Gao, Ming Li
2004 B conf
ISSAC
Ruyong Feng, Xiao-Shan Gao
2004 J jnl
Comput. Aided Geom. Des.
Xiao-Shan Gao, Ming Li
2004 J jnl
Comput. Aided Des.
Xiao-Shan Gao, Christoph M. Hoffmann, Wei-Qiang Yang
2003 conf
Shape Modeling International
Xiao-Shan Gao, Gui-Fang Zhang
2003 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Xiao-Shan Gao, Xiaorong Hou, Jianliang Tang, Hang-Fei Cheng
2003 conf
Symposium on Solid Modeling and Applications
Xiao-Shan Gao, Gui-Fang Zhang
2003 J jnl
J. Symb. Comput.
Xiao-Shan Gao
2003 J jnl
SIGSAM Bull.
Ruyong Feng, Xiao-Shan Gao
2002 C conf
GMP
Xiao-Shan Gao, Ming Li
2002 J jnl
Comput. Aided Des.
Xiao-Shan Gao, Kun Jiang, Chang-Cai Zhu
2002 conf
Automated Deduction in Geometry
Xiao-Shan Gao, Qiang Lin
2002 conf
Symposium on Solid Modeling and Applications
Xiao-Shan Gao, Christoph M. Hoffmann, Wei-Qiang Yang
2001 ch.
Handbook of Automated Reasoning
Shang-Ching Chou, Xiao-Shan Gao
2001 J jnl
Sci. China Ser. F Inf. Sci.
Xiao-Shan Gao, Lei-Dong Huang, Kun Jiang
2001 J jnl
J. Comput. Sci. Technol.
Xiao-Shan Gao, Hangfei Chen
2000 J jnl
J. Autom. Reason.
Shang-Ching Chou, Xiao-Shan Gao, Jing-Zhong Zhang
2000 conf
Automated Deduction in Geometry
Xiao-Shan Gao, Lei-Dong Huang, Kun Jiang
1999 ed.
Automated Deduction in Geometry
Xiao-Shan Gao, Dongming Wang, Lu Yang
1999 J jnl
J. Comput. Sci. Technol.
Xiao-Shan Gao, Changcai Zhu
1999 J jnl
Comput. Aided Des.
Jian-Xin Ge, Shang-Ching Chou, Xiao-Shan Gao
1998 conf
Automated Deduction in Geometry
Xiao-Shan Gao
1998 J jnl
Comput. Aided Des.
Xiao-Shan Gao, Shang-Ching Chou
1998 J jnl
Comput. Aided Des.
Xiao-Shan Gao, Shang-Ching Chou
1996 A conf
CADE
Shang-Ching Chou, Xiao-Shan Gao, Jing-Zhong Zhang
1996 J jnl
J. Autom. Reason.
Shang-Ching Chou, Xiao-Shan Gao
1996 J jnl
J. Autom. Reason.
Shang-Ching Chou, Xiao-Shan Gao, Jing-Zhong Zhang
1996 conf
Automated Deduction in Geometry
Lu Yang, Xiao-Shan Gao, Shang-Ching Chou, Jing-Zhong Zhang
1995 J jnl
Ann. Math. Artif. Intell.
Jing-Zhong Zhang, Shang-Ching Chou, Xiao-Shan Gao
1995 J jnl
J. Autom. Reason.
Shang-Ching Chou, Xiao-Shan Gao, Jing-Zhong Zhang
1994 book
Machine proofs in geometry - automated production of readable proofs for geometry theorems.
Shang-Ching Chou, Xiao-Shan Gao, Jing-Zhong Zhang
1994 A conf
CADE
Nicholas Freitag McPhee, Shang-Ching Chou, Xiao-Shan Gao
1993 J jnl
J. Symb. Comput.
Xiao-Shan Gao, Shang-Ching Chou
1993 B conf
ISSAC
Shang-Ching Chou, Xiao-Shan Gao, Jing-Zhong Zhang
1993 A* conf
LICS
Shang-Ching Chou, Xiao-Shan Gao, Jing-Zhong Zhang
1993 J jnl
J. Autom. Reason.
Shang-Ching Chou, Xiao-Shan Gao
1993 J jnl
J. Autom. Reason.
Shang-Ching Chou, Xiao-Shan Gao
1992 conf
IWAR
Xiao-Shan Gao
1992 conf
IWAR
Shang-Ching Chou, Xiao-Shan Gao
1992 J jnl
J. Symb. Comput.
Xiao-Shan Gao, Shang-Ching Chou
1992 J jnl
Appl. Algebra Eng. Commun. Comput.
Xiao-Shan Gao, Shang-Ching Chou
1992 A conf
CADE
Shang-Ching Chou, Xiao-Shan Gao
1992 B conf
ISSAC
Xiao-Shan Gao, Shang-Ching Chou
1991 B conf
ISSAC
Xiao-Shan Gao, Shang-Ching Chou
1991 J jnl
Int. J. Comput. Geom. Appl.
Xiao-Shan Gao, Shang-Ching Chou
1990 B conf
ISSAC
Shang-Ching Chou, Xiao-Shan Gao
1990 A conf
CADE
Shang-Ching Chou, Xiao-Shan Gao
1990 J jnl
J. Autom. Reason.
Xiao-Shan Gao
redb/extractors/ioc_extractor/standalone_ioc_extractor.py
← Index redb/extractors/ioc_extractor/standalone_ioc_extractor.py python
#!/usr/bin/env python3
"""
Standalone IOC Extractor for REDB Analysis Pipeline

This is a single-file IOC extraction script that can be called from the
analysis backend after Binary Ninja processing completes.

Dependencies:
    - clickhouse-connect (pip install clickhouse-connect)
    - python-dotenv (pip install python-dotenv)
    - Python 3.9+

Usage:
    # Extract IOCs for a single sample (uses .env for ClickHouse connection)
    python standalone_ioc_extractor.py --sha256 <hash>

    # Specify custom .env file
    python standalone_ioc_extractor.py --sha256 <hash> --env-file /path/to/.env

    # Override connection settings
    python standalone_ioc_extractor.py --sha256 <hash> --host ch.example.com --port 8123

    # Force re-extraction (delete existing IOCs first)
    python standalone_ioc_extractor.py --sha256 <hash> --force

    # Programmatic usage:
    from standalone_ioc_extractor import IOCExtractor
    extractor = IOCExtractor()  # Uses .env by default
    ioc_count = extractor.extract_for_sample("sha256_hash_here")

Required files:
    - iana_tlds.txt: Place in same directory as this script, or specify with --tld-file
    - .env file with CLICKHOUSE_* variables (or pass connection params directly)

Environment variables (from .env):
    CLICKHOUSE_HOST (default: localhost)
    CLICKHOUSE_PORT (default: 8123)
    CLICKHOUSE_USER (default: default)
    CLICKHOUSE_PASSWORD (default: empty)
    CLICKHOUSE_DATABASE (default: redb)

Based on IOC scraper patterns from Synapse (https://github.com/vertexproject/synapse)
"""

import os
import re
import argparse
import ipaddress
import logging
from pathlib import Path
from datetime import datetime
from dataclasses import dataclass
from enum import Enum
from typing import List, Set, Tuple, Generator, Optional

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [%(levelname)s] %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger(__name__)


# =============================================================================
# Enums and Data Classes
# =============================================================================

class IOCType(str, Enum):
    """IOC types matching ClickHouse Enum8 values"""
    IPV4 = 'ipv4'
    IPV6 = 'ipv6'
    FQDN = 'fqdn'
    URL = 'url'
    EMAIL = 'email'
    SERVER = 'server'
    HASH_MD5 = 'hash_md5'
    HASH_SHA1 = 'hash_sha1'
    HASH_SHA256 = 'hash_sha256'
    CVE = 'cve'
    CWE = 'cwe'
    CPE = 'cpe'
    CRYPTO_BTC = 'crypto_btc'
    CRYPTO_ETH = 'crypto_eth'
    CRYPTO_XRP = 'crypto_xrp'
    CRYPTO_BCH = 'crypto_bch'
    CRYPTO_ADA = 'crypto_ada'
    CRYPTO_SUBSTRATE = 'crypto_substrate'
    PATH_LINUX = 'path_linux'
    PATH_WINDOWS = 'path_windows'
    REGISTRY_KEY = 'registry_key'
    ONION = 'onion'


class SourceType(str, Enum):
    """Source types matching ClickHouse Enum8 values"""
    DECOMPILED_FUNCTION = 'decompiled_function'
    DISASSEMBLED_FUNCTION = 'disassembled_function'
    STRING = 'string'
    # Universal text-based artefact surfaces (JS today; PowerShell, Python,
    # email, extracted PDF/Office text in the future). The frontend reads
    # `redb_basic_properties.filetype_magika` to render the right per-format
    # view, the same translation pattern APK uses for `decompiled_function`.
    TEXT_RAW = 'text_raw'
    TEXT_NORMALIZED = 'text_normalized'


@dataclass
class ExtractedIOC:
    """Represents an extracted IOC"""
    ioc_type: IOCType
    ioc_value: str
    source_type: SourceType
    source_identifier: str


# =============================================================================
# TLD Loading
# =============================================================================

def load_tld_list(tld_file: Optional[Path] = None) -> str:
    """Load TLD list and build regex alternation pattern."""
    if tld_file is None:
        tld_file = Path(__file__).parent / 'iana_tlds.txt'

    tlds = []
    try:
        with open(tld_file, 'r') as f:
            for line in f:
                line = line.strip().lower()
                if not line or line.startswith('#'):
                    continue
                tlds.append(line)
    except FileNotFoundError:
        logger.warning(f"TLD file not found at {tld_file}, using fallback list")
        tlds = ['com', 'net', 'org', 'edu', 'gov', 'mil', 'io', 'co', 'info',
                'biz', 'me', 'tv', 'cc', 'uk', 'de', 'fr', 'ru', 'cn', 'jp',
                'br', 'au', 'in', 'link', 'taxi']

    # Add special TLDs used in malware
    tlds.extend(['onion', 'bit', 'bazar'])
    tlds.sort(key=len, reverse=True)

    return '(?:' + '|'.join(re.escape(tld) for tld in tlds) + ')'


# =============================================================================
# Regex Patterns
# =============================================================================

# IPv4
IPV4_OCTET = r'(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'
IPV4_PATTERN = re.compile(
    rf'(?<![0-9.])({IPV4_OCTET}\.{IPV4_OCTET}\.{IPV4_OCTET}\.{IPV4_OCTET})(?![0-9.])',
    re.ASCII
)

# IPv6
IPV6_PATTERN = re.compile(
    r'(?<![0-9a-fA-F:])('
    r'(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|'
    r'(?:[0-9a-fA-F]{1,4}:){1,7}:|'
    r'(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|'
    r'(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|'
    r'(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|'
    r'(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|'
    r'(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|'
    r'[0-9a-fA-F]{1,4}:(?::[0-9a-fA-F]{1,4}){1,6}|'
    r':(?::[0-9a-fA-F]{1,4}){1,7}|'
    r'::(?:[fF]{4}:)?(?:' + IPV4_OCTET + r'\.){3}' + IPV4_OCTET + r'|'
    r'(?:[0-9a-fA-F]{1,4}:){1,4}:(?:' + IPV4_OCTET + r'\.){3}' + IPV4_OCTET +
    r')(?![0-9a-fA-F:])',
    re.ASCII
)

# URL
URL_SCHEMES = r'(?:https?|ftp|ftps|sftp|file|smb|ssh|telnet|ldap|ldaps)'
URL_PATTERN = re.compile(
    rf'({URL_SCHEMES}://[^\s<>\"\'\)\]\}},;]+)',
    re.IGNORECASE
)

# Defanging patterns
DEFANG_PATTERNS = [
    (re.compile(r'hxxps?://', re.IGNORECASE), lambda m: m.group().lower().replace('xx', 'tt')),
    (re.compile(r'\[\.?\]'), '.'),
    (re.compile(r'\[\s*dot\s*\]', re.IGNORECASE), '.'),
    (re.compile(r'\[\s*at\s*\]', re.IGNORECASE), '@'),
    (re.compile(r'\(\.\)'), '.'),
    (re.compile(r'\[:\]'), ':'),
]

# Email
EMAIL_PATTERN = re.compile(
    r'(?<![a-zA-Z0-9._%+-])([a-zA-Z0-9._%+-]{1,64}@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})(?![a-zA-Z0-9_])',
    re.ASCII
)

# Server (IP:port)
SERVER_PATTERN = re.compile(
    rf'({IPV4_OCTET}\.{IPV4_OCTET}\.{IPV4_OCTET}\.{IPV4_OCTET}):(\d{{1,5}})',
    re.ASCII
)

# .onion
ONION_PATTERN = re.compile(
    r'(?<![a-zA-Z0-9.-])([a-z2-7]{16}(?:[a-z2-7]{40})?\.onion)(?![a-zA-Z0-9.-])',
    re.IGNORECASE
)

# Hashes
MD5_PATTERN = re.compile(r'(?<![A-Za-z0-9])([a-fA-F0-9]{32})(?![A-Za-z0-9])', re.ASCII)
SHA1_PATTERN = re.compile(r'(?<![A-Za-z0-9])([a-fA-F0-9]{40})(?![A-Za-z0-9])', re.ASCII)
SHA256_PATTERN = re.compile(r'(?<![A-Za-z0-9])([a-fA-F0-9]{64})(?![A-Za-z0-9])', re.ASCII)

# CVE/CWE/CPE
CVE_PATTERN = re.compile(
    r'(?i)(CVE[-\u2010\u2011\u2012\u2013\u2014\u2015](?:19|20)\d{2}[-\u2010\u2011\u2012\u2013\u2014\u2015]\d{4,7})',
    re.UNICODE
)
CWE_PATTERN = re.compile(r'(?i)(CWE-\d{1,8})')
CPE_PATTERN = re.compile(
    r'(cpe:2\.3:[aho\*\-](?::[a-zA-Z0-9\*\-._~%!$&\'()+,;=]*){10})',
    re.IGNORECASE
)

# Cryptocurrency
BASE58_CHARSET = r'a-zA-HJ-NP-Z0-9'
BECH32_CHARSET = r'qpzry9x8gf2tvdw0s3jn54khce6mua7l'

BTC_P2PKH_PATTERN = re.compile(rf'(?<![A-Za-z0-9])([1][{BASE58_CHARSET}]{{25,39}})(?![A-Za-z0-9])')
BTC_P2SH_PATTERN = re.compile(rf'(?<![A-Za-z0-9])(3[{BASE58_CHARSET}]{{33}})(?![A-Za-z0-9])')
BTC_BECH32_PATTERN = re.compile(
    rf'(?<![A-Za-z0-9])((bc|bcrt|tb)1[{BECH32_CHARSET}]{{3,71}})(?![A-Za-z0-9])',
    re.IGNORECASE
)
ETH_PATTERN = re.compile(r'(?<![A-Za-z0-9])(0x[A-Fa-f0-9]{40})(?![A-Fa-f0-9])')
XRP_PATTERN = re.compile(rf'(?<![A-Za-z0-9])([xr][{BASE58_CHARSET}]{{25,46}})(?![A-Za-z0-9])')
BCH_PATTERN = re.compile(
    rf'(?<![A-Za-z0-9])((bitcoincash|bchtest):[{BECH32_CHARSET}]{{42}})(?![A-Za-z0-9])',
    re.IGNORECASE
)
ADA_SHELLEY_PATTERN = re.compile(
    rf'(?<![A-Za-z0-9])(addr1[{BECH32_CHARSET}]{{53,}})(?![A-Za-z0-9])',
    re.IGNORECASE
)
ADA_BYRON_PATTERN = re.compile(
    rf'(?<![A-Za-z0-9])((DdzFF|Ae2td)[{BASE58_CHARSET}]{{54,99}})(?![A-Za-z0-9])'
)
SUBSTRATE_PATTERN = re.compile(
    rf'(?<![A-Za-z0-9])([1a-z][{BASE58_CHARSET}]{{46,47}})(?![A-Za-z0-9])'
)

# File paths
LINUX_PATH_PATTERN = re.compile(
    r'(?<![a-zA-Z0-9/])(/(?:bin|boot|dev|etc|home|lib|lib64|media|mnt|opt|proc|root|run|sbin|srv|sys|tmp|usr|var)/[^\s<>\"\'\)\]\}},;]{1,500})(?![a-zA-Z0-9/])'
)

# Windows path / registry key — both share separator and component grammar.
# Separator accepts 1 or 2 backslashes so paths embedded in source-code string
# literals (where `\` is escaped to `\\` — JS, JSON, PowerShell, etc.) match
# the same as runtime-form paths. `*` is permitted in components to capture
# wildcard patterns common in malware (e.g. C:\Users\*\AppData\Local\Temp).
#
# Two-tier component grammar:
#   _WIN_FIRST — strict (no whitespace, quote, separator, reserved-char) —
#     used as the first char of every segment so segments can't start with
#     a space or a quote that would let a path drift into prose
#   _WIN_INNER — permissive (allows internal whitespace) — used in the body
#     of *delimited* segments only (those followed by `\` or `\\`), so
#     `Program Files` matches between separators
# The final segment uses _WIN_FIRST throughout, so a path that runs into
# free text stops at the first whitespace instead of slurping the rest of
# the line. (e.g. "c:\users\admin\desktop and stuff" → "c:\users\admin\desktop".)
_WIN_SEP = r'\\{1,2}'
_WIN_FIRST = r"[^\\<>\"'`\?\|\s]"
# `_WIN_INNER` also excludes `:` to prevent two adjacent paths in free text
# from collapsing into one match — e.g. "From C:\one to D:\two" must not
# slurp ` to D:` into the body of segment 1. Colon-in-segment is rare in
# practice (only NTFS alternate data streams use it: `C:\file.txt:stream`).
_WIN_INNER = r"[^\\<>\"'`\?\|:]"
WINDOWS_PATH_PATTERN = re.compile(
    rf'(?<![a-zA-Z0-9\\])'
    rf'([a-zA-Z]:{_WIN_SEP}'
    rf'(?:{_WIN_FIRST}{_WIN_INNER}*{_WIN_SEP})*'
    rf'(?:{_WIN_FIRST}+)?'
    rf')(?![a-zA-Z0-9\\])',
    re.IGNORECASE
)
_REG_HIVE = (
    r'(?:HK(?:LM|CU|CR|U|CC|PD)'
    r'|HKEY_(?:LOCAL_MACHINE|CURRENT_USER|CLASSES_ROOT|USERS|CURRENT_CONFIG|PERFORMANCE_DATA))'
)
REGISTRY_KEY_PATTERN = re.compile(
    rf'(?<![A-Za-z0-9\\])'
    rf'({_REG_HIVE}{_WIN_SEP}'
    rf'(?:{_WIN_FIRST}{_WIN_INNER}*{_WIN_SEP})*'
    rf'{_WIN_FIRST}+'
    rf')(?![A-Za-z0-9\\])',
    re.IGNORECASE
)

# Exclusions
EXCLUDED_IPS = {
    '0.0.0.0', '127.0.0.1', '255.255.255.255',
    '1.0.0.0', '1.0.0.1', '1.1.1.1',
    '8.8.8.8', '8.8.4.4',
}
EXCLUDED_DOMAINS = {
    'example.com', 'example.org', 'example.net',
    'localhost', 'localhost.localdomain',
    'test.com', 'test.local',
}

# JS-context FQDN false-positive blocklists.
#
# When the scraper is constructed with `js_context=True`, FQDN candidates whose
# leftmost segment is in JS_FP_SLDS or whose TLD is in JS_FP_TLDS are rejected.
# Both lists target patterns that arise from JS object/property access syntax
# (`this.foo.bar`, `process.id`, `lib.so`, `Function.name`, ...) where the
# segment chain accidentally shape-matches a hostname. Without this filter
# typical samples produce ~80% FQDN false positives because new gTLDs include
# common JS property-name suffixes (`.name`, `.id`, `.so`, `.post`,
# `.services`, ...).
#
# Curated specifically for JS — APK suppresses FQDN entirely (smali class
# names produce orders-of-magnitude more dotted identifiers than JS source).
# Protocol prefixes (ftp, smtp, imap, pop3, http, https) are *not* in the SLD
# list so legitimate C2 hostnames like `ftp.evil.example.com` survive.
JS_FP_TLDS = frozenset({
    'name', 'id', 'so', 'post', 'services', 'tools', 'support',
    'today', 'email', 'page', 'site', 'click', 'link', 'tech',
    'systems', 'network',
})
JS_FP_SLDS = frozenset({
    # JS keywords / pseudo-globals
    'this', 'self', 'super', 'arguments', 'globalthis',
    # Common host objects
    'window', 'document', 'console', 'navigator', 'location', 'history',
    'screen', 'event',
    # Node / runtime globals
    'process', 'proc', 'module', 'exports', 'require', 'global', 'buffer',
    # Built-in constructors / prototypes that get used as property roots
    'function', 'func', 'object', 'array', 'string', 'number', 'boolean',
    'symbol', 'promise', 'map', 'set', 'date', 'regexp', 'error', 'json',
    'math', 'reflect', 'proxy', 'class', 'interface',
    # Framework / popular library roots
    'vue', 'react', 'angular', 'firebase', 'jquery', 'lodash', 'axios',
    'socket', 'express', 'fastify', 'koa', 'next', 'nuxt', 'svelte',
    'redux', 'mobx',
    # Browser-extension surfaces
    'chrome', 'browser', 'firefox', 'safari',
    # Generic code-shape identifiers that show up in malware-analysis text
    'system', 'lib', 'api', 'app', 'component', 'controller', 'service',
    'handler', 'manager', 'factory', 'builder', 'view', 'model', 'config',
    'option', 'param', 'arg', 'data', 'result', 'value', 'key', 'index',
    'count', 'size', 'length', 'type', 'kind', 'target', 'payload',
    'exploit', 'malware', 'sandbox', 'vm', 'thread', 'task', 'job',
    'worker', 'session', 'request', 'response', 'callback', 'listener',
    'observer', 'subscriber',
})


# =============================================================================
# IOC Scraper
# =============================================================================

class IOCScraper:
    """Scrapes IOCs from text content."""

    def __init__(
        self,
        tld_file: Optional[Path] = None,
        suppress_types: Optional[Set[IOCType]] = None,
        js_context: bool = False,
    ):
        self.tld_pattern = load_tld_list(tld_file)
        self.suppress_types: Set[IOCType] = suppress_types or set()
        # When True, FQDN validation additionally rejects candidates whose
        # leftmost segment is in JS_FP_SLDS or whose TLD is in JS_FP_TLDS, to
        # filter false positives from JS object-access syntax. Defaults to
        # False so non-JS callers behave identically to before.
        self.js_context: bool = js_context
        self.fqdn_pattern = re.compile(
            rf'(?<![a-zA-Z0-9_./:@\\-])([a-zA-Z0-9](?:[a-zA-Z0-9-]{{0,61}}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{{0,61}}[a-zA-Z0-9])?)*\.{self.tld_pattern})(?![a-zA-Z0-9_-])',
            re.IGNORECASE
        )

    def _defang_text(self, text: str) -> str:
        for pattern, replacement in DEFANG_PATTERNS:
            if callable(replacement):
                text = pattern.sub(replacement, text)
            else:
                text = pattern.sub(replacement, text)
        return text

    def _validate_ipv4(self, ip: str) -> bool:
        try:
            addr = ipaddress.IPv4Address(ip)
            if ip in EXCLUDED_IPS:
                return False
            if addr.is_private or addr.is_loopback or addr.is_reserved:
                return False
            return True
        except (ipaddress.AddressValueError, ValueError):
            return False

    def _validate_ipv6(self, ip: str) -> bool:
        try:
            addr = ipaddress.IPv6Address(ip)
            if addr.is_private or addr.is_loopback or addr.is_reserved:
                return False
            return True
        except (ipaddress.AddressValueError, ValueError):
            return False

    def _validate_hash(self, hash_value: str) -> bool:
        if len(set(hash_value.lower())) <= 2:
            return False
        if hash_value.lower() in {'0' * len(hash_value), 'f' * len(hash_value)}:
            return False
        return True

    def _validate_fqdn(self, fqdn: str) -> bool:
        fqdn_lower = fqdn.lower()
        if fqdn_lower in EXCLUDED_DOMAINS:
            return False
        parts = fqdn_lower.split('.')
        if all(part.isdigit() for part in parts[:-1]):
            return False
        # Reject FQDNs where the second-level domain is less than 3 characters
        # e.g. a.com, ab.com are rejected; abc.com and x.abc.com are accepted
        if len(parts) >= 2 and len(parts[-2]) < 3:
            return False
        # JS-context filter: reject candidates that are almost certainly JS
        # object-access syntax (`this.foo.bar`, `process.id`, `lib.so`,
        # `Component.name`) rather than real hostnames. The TLD check fires on
        # gTLDs that double as common JS property suffixes; the leftmost-
        # segment check fires on JS keywords / framework roots. Either one
        # alone is sufficient to reject. See JS_FP_TLDS / JS_FP_SLDS for the
        # rationale and the curated lists.
        if self.js_context:
            if parts[-1] in JS_FP_TLDS:
                return False
            if len(parts) >= 2 and parts[0] in JS_FP_SLDS:
                return False
        return True

    def _normalize_cve(self, cve: str) -> str:
        cve = re.sub(r'[\u2010\u2011\u2012\u2013\u2014\u2015]', '-', cve)
        return cve.upper()

    def scrape(
        self,
        text: str,
        source_type: SourceType,
        source_identifier: str
    ) -> Generator[ExtractedIOC, None, None]:
        """Scrape IOCs from text."""
        if not text:
            return

        text = self._defang_text(text)
        seen: Set[Tuple[IOCType, str]] = set()

        def emit(ioc_type: IOCType, value: str) -> Generator[ExtractedIOC, None, None]:
            key = (ioc_type, value.lower())
            if key not in seen:
                seen.add(key)
                yield ExtractedIOC(
                    ioc_type=ioc_type,
                    ioc_value=value,
                    source_type=source_type,
                    source_identifier=source_identifier
                )

        # URLs
        for match in URL_PATTERN.finditer(text):
            url = match.group(1).rstrip('.,;:')
            yield from emit(IOCType.URL, url)

        # Servers (IP:port)
        for match in SERVER_PATTERN.finditer(text):
            ip = match.group(1)
            port = int(match.group(2))
            if self._validate_ipv4(ip) and 1 <= port <= 65535:
                yield from emit(IOCType.SERVER, f"{ip}:{port}")

        # IPv4
        for match in IPV4_PATTERN.finditer(text):
            ip = match.group(1)
            if self._validate_ipv4(ip):
                yield from emit(IOCType.IPV4, ip)

        # IPv6
        for match in IPV6_PATTERN.finditer(text):
            ip = match.group(1)
            if self._validate_ipv6(ip):
                yield from emit(IOCType.IPV6, ip)

        # .onion
        for match in ONION_PATTERN.finditer(text):
            onion = match.group(1).lower()
            yield from emit(IOCType.ONION, onion)

        # FQDNs
        if IOCType.FQDN not in self.suppress_types:
            for match in self.fqdn_pattern.finditer(text):
                fqdn = match.group(1).lower()
                if self._validate_fqdn(fqdn) and not fqdn.endswith('.onion'):
                    yield from emit(IOCType.FQDN, fqdn)

        # Emails
        for match in EMAIL_PATTERN.finditer(text):
            email = match.group(1).lower()
            yield from emit(IOCType.EMAIL, email)

        # Hashes (SHA256 first)
        for match in SHA256_PATTERN.finditer(text):
            hash_val = match.group(1).lower()
            if self._validate_hash(hash_val):
                yield from emit(IOCType.HASH_SHA256, hash_val)

        for match in SHA1_PATTERN.finditer(text):
            hash_val = match.group(1).lower()
            if self._validate_hash(hash_val) and (IOCType.HASH_SHA256, hash_val) not in seen:
                yield from emit(IOCType.HASH_SHA1, hash_val)

        for match in MD5_PATTERN.finditer(text):
            hash_val = match.group(1).lower()
            if self._validate_hash(hash_val):
                if not any((IOCType.HASH_SHA1, h) in seen or (IOCType.HASH_SHA256, h) in seen
                           for h in [hash_val]):
                    yield from emit(IOCType.HASH_MD5, hash_val)

        # CVE/CWE/CPE
        for match in CVE_PATTERN.finditer(text):
            cve = self._normalize_cve(match.group(1))
            yield from emit(IOCType.CVE, cve)

        for match in CWE_PATTERN.finditer(text):
            cwe = match.group(1).upper()
            yield from emit(IOCType.CWE, cwe)

        for match in CPE_PATTERN.finditer(text):
            cpe = match.group(1).lower()
            yield from emit(IOCType.CPE, cpe)

        # Cryptocurrency
        for match in BTC_BECH32_PATTERN.finditer(text):
            addr = match.group(1).lower()
            yield from emit(IOCType.CRYPTO_BTC, addr)

        for match in BTC_P2PKH_PATTERN.finditer(text):
            yield from emit(IOCType.CRYPTO_BTC, match.group(1))

        for match in BTC_P2SH_PATTERN.finditer(text):
            yield from emit(IOCType.CRYPTO_BTC, match.group(1))

        for match in ETH_PATTERN.finditer(text):
            yield from emit(IOCType.CRYPTO_ETH, match.group(1).lower())

        for match in XRP_PATTERN.finditer(text):
            yield from emit(IOCType.CRYPTO_XRP, match.group(1))

        for match in BCH_PATTERN.finditer(text):
            yield from emit(IOCType.CRYPTO_BCH, match.group(1).lower())

        for match in ADA_SHELLEY_PATTERN.finditer(text):
            yield from emit(IOCType.CRYPTO_ADA, match.group(1).lower())

        for match in ADA_BYRON_PATTERN.finditer(text):
            yield from emit(IOCType.CRYPTO_ADA, match.group(1))

        for match in SUBSTRATE_PATTERN.finditer(text):
            addr = match.group(1)
            if any(c.isdigit() for c in addr) and len(addr) in (47, 48):
                yield from emit(IOCType.CRYPTO_SUBSTRATE, addr)

        # File paths
        for match in LINUX_PATH_PATTERN.finditer(text):
            path = match.group(1).rstrip('.,;:')
            yield from emit(IOCType.PATH_LINUX, path)

        for match in WINDOWS_PATH_PATTERN.finditer(text):
            # Normalise doubled backslashes (source-escaped form) to single so
            # `C:\\Users\\Public` and `C:\Users\Public` collapse to one IOC.
            path = match.group(1).rstrip('.,;:').replace('\\\\', '\\')
            yield from emit(IOCType.PATH_WINDOWS, path)

        # Windows registry keys — same normalisation as paths
        for match in REGISTRY_KEY_PATTERN.finditer(text):
            key = match.group(1).rstrip('.,;:').replace('\\\\', '\\')
            yield from emit(IOCType.REGISTRY_KEY, key)


# =============================================================================
# IOC Extractor (ClickHouse integration)
# =============================================================================

def load_env(env_file: Optional[Path] = None):
    """Load environment variables from .env file."""
    try:
        from dotenv import load_dotenv
    except ImportError:
        logger.warning("python-dotenv not installed, using environment variables only")
        return

    if env_file:
        load_dotenv(env_file)
    else:
        # Try common locations
        for path in [Path(".env"), Path(__file__).parent.parent.parent.parent / ".env"]:
            if path.exists():
                load_dotenv(path)
                logger.debug(f"Loaded environment from {path}")
                break


class IOCExtractor:
    """Extracts IOCs from ClickHouse data and stores results."""

    def __init__(
        self,
        host: Optional[str] = None,
        port: Optional[int] = None,
        user: Optional[str] = None,
        password: Optional[str] = None,
        database: Optional[str] = None,
        batch_size: int = 1000,
        tld_file: Optional[Path] = None,
        env_file: Optional[Path] = None,
        client=None
    ):
        """
        Initialize IOC Extractor.

        Connection parameters can be passed directly or loaded from environment:
            CLICKHOUSE_HOST, CLICKHOUSE_PORT, CLICKHOUSE_USER,
            CLICKHOUSE_PASSWORD, CLICKHOUSE_DATABASE

        Args:
            host: ClickHouse host (default: from env or localhost)
            port: ClickHouse port (default: from env or 8123)
            user: ClickHouse user (default: from env or default)
            password: ClickHouse password (default: from env or empty)
            database: ClickHouse database (default: from env or redb)
            batch_size: Batch size for processing
            tld_file: Path to TLD list file
            env_file: Path to .env file (default: auto-detect)
            client: Existing ClickHouse client (if provided, connection params are ignored)
        """
        # If existing client provided, use it directly
        if client is not None:
            self.client = client
            self.database = database or os.environ.get("CLICKHOUSE_DATABASE", "redb")
            logger.info(f"Using existing ClickHouse client, database={self.database}")
        else:
            # Create new connection
            try:
                import clickhouse_connect
            except ImportError:
                raise ImportError("clickhouse-connect is required. Install with: pip install clickhouse-connect")

            # Load .env file
            load_env(env_file)

            # Use passed values or fall back to environment variables
            host = host or os.environ.get("CLICKHOUSE_HOST", "localhost")
            port = port or int(os.environ.get("CLICKHOUSE_PORT", "8123"))
            user = user or os.environ.get("CLICKHOUSE_USER", "default")
            password = password if password is not None else os.environ.get("CLICKHOUSE_PASSWORD", "")
            database = database or os.environ.get("CLICKHOUSE_DATABASE", "redb")

            logger.info(f"Connecting to ClickHouse at {host}:{port}, database={database}")

            self.client = clickhouse_connect.get_client(
                host=host,
                port=port,
                username=user,
                password=password,
                database=database
            )
            self.database = database

        self.batch_size = batch_size
        self.scraper = IOCScraper(tld_file)
        self._insert_buffer: List[dict] = []

    def has_existing_iocs(self, sha256: str) -> bool:
        """Check if sample already has IOCs extracted."""
        result = self.client.query(
            f"SELECT 1 FROM {self.database}.redb_iocs WHERE sha256 = %(sha256)s LIMIT 1",
            parameters={"sha256": sha256}
        )
        return len(result.result_rows) > 0

    def delete_iocs_for_sample(self, sha256: str):
        """Delete existing IOCs for a sample."""
        self.client.command(
            f"ALTER TABLE {self.database}.redb_iocs DELETE WHERE sha256 = %(sha256)s",
            parameters={"sha256": sha256}
        )
        logger.info(f"Deleted existing IOCs for {sha256[:16]}...")

    def extract_for_sample(self, sha256: str, force: bool = False) -> int:
        """
        Extract IOCs for a single sample.

        Args:
            sha256: Sample SHA256 hash
            force: Delete existing IOCs and re-extract

        Returns:
            Number of IOCs extracted
        """
        if force:
            self.delete_iocs_for_sample(sha256)
        elif self.has_existing_iocs(sha256):
            logger.info(f"Skipping {sha256[:16]}... (already has IOCs)")
            return 0

        total_iocs = 0

        # Extract from strings
        strings_count = self._extract_from_strings(sha256)
        total_iocs += strings_count

        # Extract from decompiled functions
        functions_count = self._extract_from_decompiled(sha256)
        total_iocs += functions_count

        # Flush remaining buffer
        self._flush_buffer()

        logger.info(f"Extracted {total_iocs} IOCs for {sha256[:16]}... (strings: {strings_count}, functions: {functions_count})")
        return total_iocs

    def _extract_from_strings(self, sha256: str) -> int:
        """Extract IOCs from sample's strings."""
        count = 0
        offset = 0

        while True:
            result = self.client.query(
                f"""
                SELECT string, string_offset
                FROM {self.database}.code_binja_strings_by_binary
                WHERE sha256 = %(sha256)s
                ORDER BY string_offset
                LIMIT %(limit)s OFFSET %(offset)s
                """,
                parameters={"sha256": sha256, "limit": self.batch_size, "offset": offset}
            )

            if not result.result_rows:
                break

            for row in result.result_rows:
                string_value, string_offset = row
                if isinstance(string_value, bytes):
                    string_value = string_value.decode('utf-8', errors='replace')

                for ioc in self.scraper.scrape(string_value, SourceType.STRING, str(string_offset)):
                    self._buffer_insert(sha256, ioc)
                    count += 1

            offset += len(result.result_rows)

        return count

    def _extract_from_decompiled(self, sha256: str) -> int:
        """Extract IOCs from sample's decompiled functions."""
        count = 0

        # Get function hashes for this sample
        refs_result = self.client.query(
            f"""
            SELECT decompiled_function_hash
            FROM {self.database}.code_binja_decompiled_functions_references
            WHERE sha256 = %(sha256)s
            """,
            parameters={"sha256": sha256}
        )

        if not refs_result.result_rows:
            return 0

        function_hashes = [row[0] for row in refs_result.result_rows]
        if isinstance(function_hashes[0], bytes):
            function_hashes = [h.decode('utf-8') for h in function_hashes]

        # Process in batches
        for i in range(0, len(function_hashes), self.batch_size):
            batch_hashes = function_hashes[i:i + self.batch_size]

            # Get function content, excluding LIBRARY and THUNK types
            content_result = self.client.query(
                f"""
                SELECT decompiled_function_hash, decompiled_function
                FROM {self.database}.code_binja_decompiled_functions_content
                WHERE decompiled_function_hash IN %(hashes)s
                  AND function_type NOT IN ('LIBRARY', 'THUNK')
                """,
                parameters={"hashes": batch_hashes}
            )

            for row in content_result.result_rows:
                func_hash, func_content = row
                if isinstance(func_hash, bytes):
                    func_hash = func_hash.decode('utf-8')
                if isinstance(func_content, bytes):
                    func_content = func_content.decode('utf-8', errors='replace')

                for ioc in self.scraper.scrape(func_content, SourceType.DECOMPILED_FUNCTION, func_hash):
                    self._buffer_insert(sha256, ioc)
                    count += 1

        return count

    def _buffer_insert(self, sha256: str, ioc: ExtractedIOC):
        """Buffer an IOC for batch insert."""
        self._insert_buffer.append({
            "sha256": sha256,
            "ioc_type": ioc.ioc_type.value,
            "ioc_value": ioc.ioc_value,
            "source_type": ioc.source_type.value,
            "source_identifier": ioc.source_identifier,
            "extracted_at": datetime.utcnow()
        })

        if len(self._insert_buffer) >= self.batch_size:
            self._flush_buffer()

    def _flush_buffer(self):
        """Flush buffered IOCs to ClickHouse."""
        if not self._insert_buffer:
            return

        columns = ["sha256", "ioc_type", "ioc_value", "source_type", "source_identifier", "extracted_at"]
        data = [[row[col] for col in columns] for row in self._insert_buffer]

        self.client.insert(
            f"{self.database}.redb_iocs",
            data,
            column_names=columns
        )

        self._insert_buffer = []


# =============================================================================
# CLI
# =============================================================================

def main():
    parser = argparse.ArgumentParser(
        description="Extract IOCs from ClickHouse sample data",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
    # Extract IOCs for a sample (uses .env for connection)
    python standalone_ioc_extractor.py --sha256 abc123...

    # Force re-extraction
    python standalone_ioc_extractor.py --sha256 abc123... --force

    # Override connection settings from .env
    python standalone_ioc_extractor.py --sha256 abc123... --host ch.example.com

    # Use custom .env file
    python standalone_ioc_extractor.py --sha256 abc123... --env-file /path/to/.env
        """
    )
    parser.add_argument("--sha256", required=True, help="Sample SHA256 hash")
    parser.add_argument("--env-file", type=Path, help="Path to .env file (default: auto-detect)")
    parser.add_argument("--host", help="ClickHouse host (default: from .env or localhost)")
    parser.add_argument("--port", type=int, help="ClickHouse HTTP port (default: from .env or 8123)")
    parser.add_argument("--user", help="ClickHouse user (default: from .env or default)")
    parser.add_argument("--password", help="ClickHouse password (default: from .env or empty)")
    parser.add_argument("--database", help="ClickHouse database (default: from .env or redb)")
    parser.add_argument("--force", action="store_true", help="Force re-extraction (delete existing IOCs)")
    parser.add_argument("--tld-file", type=Path, help="Path to TLD list file (default: iana_tlds.txt in script directory)")
    parser.add_argument("--batch-size", type=int, default=1000, help="Batch size for processing (default: 1000)")

    args = parser.parse_args()

    extractor = IOCExtractor(
        host=args.host,
        port=args.port,
        user=args.user,
        password=args.password,
        database=args.database,
        batch_size=args.batch_size,
        tld_file=args.tld_file,
        env_file=args.env_file
    )

    ioc_count = extractor.extract_for_sample(args.sha256, force=args.force)
    print(f"Extracted {ioc_count} IOCs for {args.sha256[:16]}...")


if __name__ == "__main__":
    main()