Full Code of cnlohr/cnping for AI

master ce49c2d85240 cached
31 files
63.9 KB
21.4k tokens
53 symbols
1 requests
Download .txt
Repository: cnlohr/cnping
Branch: master
Commit: ce49c2d85240
Files: 31
Total size: 63.9 KB

Directory structure:
gitextract_6s2_y85m/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       └── build.yml
├── .gitignore
├── .gitmodules
├── .travis.yml
├── LICENSE
├── LICENSE.BSD-3-Clause
├── LICENSE.MIT
├── Makefile
├── README.md
├── cnping.1
├── cnping.c
├── debian/
│   ├── changelog
│   ├── control
│   ├── copyright
│   ├── install
│   ├── postinst
│   ├── rules
│   └── source/
│       └── format
├── error_handling.h
├── freedesktop/
│   ├── com.github.cntools.cnping.desktop
│   └── com.github.cntools.cnping.metainfo.xml
├── httping.c
├── httping.h
├── ping.c
├── ping.h
├── resolve.c
├── resolve.h
├── resources.rc
├── searchnet.c
└── uac.manifest

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "monthly"



================================================
FILE: .github/workflows/build.yml
================================================
name: Build cnping

on:
  push:
  pull_request:
  workflow_dispatch:

jobs:
  Build-for-Linux:
    runs-on: ubuntu-latest
      
    steps:
    - uses: actions/checkout@v6
      with:
        submodules: recursive
    - name: Install dependencies
      run: |
        sudo apt-get update
        sudo apt-get install -y \
           debhelper-compat \
           libglvnd-dev \
           libx11-dev \
           libxext-dev \
           libxinerama-dev \
           mesa-common-dev \
           make \
           build-essential \
           binutils-mingw-w64-i686 \
           gcc-mingw-w64-i686 \
           g++-mingw-w64-i686 \
           devscripts \
           appstream \
           imagemagick \
           desktop-file-utils

    - name: Build
      run: make cnping cnping.exe
        
    - uses: actions/upload-artifact@v7
      with:
        name: cnping-exe
        path: "*.exe"

    - name: Build deb
      run: debuild --no-sign
      
    - uses: actions/upload-artifact@v7
      with:
        name: cnping-debian
        path: |
          /home/runner/work/cnping/*.deb
          /home/runner/work/cnping/*.ddeb
          
    - name: Validate AppStream data
      run: appstreamcli validate freedesktop/com.github.cntools.cnping.metainfo.xml
    - name: Validate desktop file
      run: desktop-file-validate freedesktop/com.github.cntools.cnping.desktop
      
#  Build-RPM-for-Fedora:
#    runs-on: ubuntu-latest
#    container: dreua/ ...
#    steps:
#    - name: RPM Build
#      run: /cnping/cnping-build
#    - uses: actions/upload-artifact@v7
#      with:
#        name: cnping-fedora-rpm
#        path: /github/home/rpmbuild/**/*.rpm


================================================
FILE: .gitignore
================================================
*.o
*.exe
cnping
cnping-mousey
searchnet
tags

debian/.debhelper
debian/cnping
debian/cnping.substvars
debian/debhelper-build-stamp
debian/files


================================================
FILE: .gitmodules
================================================
[submodule "rawdraw"]
	path = rawdraw
	url = https://github.com/cntools/rawdraw


================================================
FILE: .travis.yml
================================================
language: c
dist: bionic

before_install:
- sudo apt-get -qq update
- sudo apt-get install -y make libxinerama-dev libxext-dev libx11-dev build-essential
  binutils-mingw-w64-i686 gcc-mingw-w64-i686 g++-mingw-w64-i686 mesa-common-dev libglvnd-dev
script: make cnping cnping.exe
  
deploy:
  provider: releases
  api_key:
    secure: "iGexfXEEUlID8jiIFaly4RcWBS1iFiT2jTtwLEExz0tBMxZuvpvS+MfZMtXt3YTI1NsbY/B/1tfO2CueEfIWW/d8mmCxf1E/VerfxA1j0NgRc3br6L1nFxbkwEiHtsHO5sYf4QGlqgzipfW2u/cp4nO8pme4jGStMTSZxyTSJPAuWT+4qiyDeay4QGLkigplW9570xKVgejX0hBm7zuw7R8QdnRvIftyvvbQiRqnLoYPPDc1PSOxLfdfNTAoUikbMuOkiN1XI3s3mytTYKCiUa3Zxj1BS35AEPCxaQoOr0iyY+jBFZrwdvPOTAD8VsAGRzjvQ0NldnuIZlXJnFAvtwjxRIIS2rPaLqC79lphaw2j0564tDY0p+j1MhVBt1wjAkRg/Dh2R1SzcO3baxJH6MHKCGS1lUxkUgW0kBoKaIw/DzfnXHx5EsJPbyLeksyLf8XzYCTPMaVJEQK30b0TM5OOEcwCrtAjwX9IiIXAgZiGyw7E4CuKWU+RfZRee8IvhF0qs5DIn0yiCmvYIMYZvX9rL6mm1Wn1Hb7+o4bxHzMmuFmo9HJfgYOzoIhBmr3Q1DIbH/UHcqeFWrP/Dz47easv3GRSbnXcbyh8G0d7mFheO+9JdAnoGBzr1Ig5Eeq0Q1ppjP7jCQuPJQ9EP3uJHStQlIVJse95PZD+TyP8LW4="
  file: 
    - cnping.exe
  skip_cleanup: true
  on:
    tags: true
    repo: cntools/cnping
    branch: master
  draft: true


================================================
FILE: LICENSE
================================================
//Rawdraw and CNPing may be licensed feely under the MIT-x11
//or NewBSD Licenses.  You choose!



================================================
FILE: LICENSE.BSD-3-Clause
================================================
BSD 3-Clause License

Copyright (c) 2018 Charles Lohr

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

    (1) Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer. 

    (2) Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in
    the documentation and/or other materials provided with the
    distribution.  
    
    (3)The name of the author may not be used to
    endorse or promote products derived from this software without
    specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE. 


================================================
FILE: LICENSE.MIT
================================================
MIT License

Copyright (c) 2018 Charles Lohr

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: Makefile
================================================
CFLAGS ?= -s -Os -I/opt/X11/include -Wall
LDFLAGS ?= -s -L/opt/X11/lib/
CC? = gcc

ICONSPATH = freedesktop/icons/hicolor/
APPNAME = com.github.cntools.cnping

all : cnping

#CFLAGS := $(CFLAGS) -DCNFGOGL
#LDFLAGS := $(LDFLAGS) -lGL

clean :
	rm -rf *.o *~ cnping cnping.exe cnping_mac searchnet
	rm -rf rawdraw/*.o


# Windows

#MINGW32 := /usr/bin/i686-w64-mingw32-
MINGW32 ?= i686-w64-mingw32-

# If you don't need admin privileges
ADMINFLAGS := $(ADMINFLAGS) -DWIN_USE_NO_ADMIN_PING

# Add git version to CFLAGS
GIT_VERSION := "$(shell git describe --abbrev=4 --dirty --always --tags)"
CFLAGS += -DVERSION=\"$(GIT_VERSION)\"

cnping-wingdi.exe : cnping.c ping.c httping.c resources.o
	$(MINGW32)gcc -g -fno-ident -mwindows -m32 $(CFLAGS) -o $@ $^  -lgdi32 -lws2_32 -s -D_WIN32_WINNT=0x0600 -DWIN32 -liphlpapi -DMINGW_BUILD $(ADMINFLAGS)

cnping.exe : cnping.c ping.c httping.c resolve.c resources.o
	$(MINGW32)gcc -g -fno-ident -mwindows -m32 -DCNFGOGL $(CFLAGS) -o $@ $^  -lgdi32 -lws2_32 -s -D_WIN32_WINNT=0x0600 -DWIN32 -liphlpapi -lopengl32 -DMINGW_BUILD $(ADMINFLAGS)

resources.o : resources.rc
	$(MINGW32)windres resources.rc -o resources.o $(ADMINFLAGS)


# Unix

cnping : cnping.c ping.c httping.c resolve.c
	$(CC) $(CFLAGS) -o $@ $^ -lX11 -lm -lpthread -lGL $(LDFLAGS)

cnping_ogl : cnping.c ping.c httping.c resolve.c
	$(CC) $(CFLAGS) -o $@ $^ -DCNFGOGL $(CFLAGS) -lX11 -lm -lpthread $(LDFLAGS) -lGL

cnping_mac : cnping.c ping.c httping.c resolve.c
	$(CC) -o cnping $^ -x objective-c -framework Cocoa -framework QuartzCore -lm -lpthread -DOSX

searchnet : ping.c searchnet.c resolve.c
	$(CC) $(CFLAGS) -o $@ $^ -lpthread

linuxinstall : cnping
	rm -f /usr/local/bin/cnping
	cp cnping /usr/local/bin/
	cp -r $(ICONSPATH) /usr/local/share/icons
	cp freedesktop/${APPNAME}.desktop /usr/local/share/applications
	cp freedesktop/${APPNAME}.metainfo.xml /usr/local/share/metainfo
	setcap cap_net_raw+ep /usr/local/bin/cnping
#	sudo chmod +t /usr/local/bin/cnping  #One option - set the stuid bit.
#	sudo install cnping /usr/local/bin/  #Another option - using install.

# minimal linux install, may be useful for development
minlinuxinstall : cnping
	sudo rm -f /usr/local/bin/cnping
	sudo cp cnping /usr/local/bin/
	sudo setcap cap_net_raw+ep /usr/local/bin/cnping



# this target requires imagemagick
updateicons : ${ICONSPATH}scalable/apps/${APPNAME}.svg
	convert $^ -resize 16x16 ${ICONSPATH}16x16/apps/${APPNAME}.png
	convert $^ -resize 32x32 ${ICONSPATH}32x32/apps/${APPNAME}.png
	convert $^ -resize 48x48 ${ICONSPATH}48x48/apps/${APPNAME}.png
	convert $^ -resize 256x256 ${ICONSPATH}256x256/apps/${APPNAME}.png
	convert $^ -resize 1024x1024 ${ICONSPATH}1024x1024/apps/${APPNAME}.png

# after creating the ico file use GIMP to compress it:
# Image-> Mode -> Indexed...
# Choose "Generate optimum palette"
# Maximum number of colors: 3 (may change if the icon changes)
# "Convert"
# File -> Export As
# Check "Compressed (PNG)" in every resolution
# "Export"
cnping.ico: ${ICONSPATH}scalable/apps/${APPNAME}.svg
	convert $^ -density 300 -define icon:auto-resize=256,64,16 -background none $@



================================================
FILE: README.md
================================================
cnping
======

Minimal Graphical IPV4 Ping/HTTP Ping Tool.  (also comes with searchnet, like nmap but smaller and simpler).  It uses rawdraw so it is OS independent.
```
Usage: cnping [host] [period] [extra size] [y-axis scaling] [window title]

	 [host]                 -- domain or IP address of ICMP ping target, or http://[host] i.e. http://google.com
	 [period]               -- period in seconds (optional), default 0.02
	 [extra size]           -- ping packet extra size (above 12), optional, default = 0
	 [const y-axis scaling] -- use a fixed scaling factor instead of auto scaling (optional)
	 [window title]         -- the title of the window (optional)
```
<IMG SRC=https://i.imgur.com/Yj5coKN.gif>

If an http host is listed, the default request is ```HEAD /favicon.ico HTTP/1.1``` since this is usually a very fast, easy operation for the server.  If a specific file or uri is requested, that will be requested instead, i.e. http://github.com/cnlohr will request ```HEAD /cnlohr HTTP/1.1```.

If a regular hostname is requested instead, ICMP (regular ping) will be used.

This allows cnping to be operated in environments where ICMP is prohibited by local computer or network policies.

## Installation

### Ubuntu

```
sudo apt install libxinerama-dev libxext-dev libx11-dev build-essential mesa-common-dev libglvnd-dev
git clone https://github.com/cntools/cnping.git
cd cnping/
git submodule update --init --recursive
make linuxinstall
```

'linuxinstall' builds the tool, copies it to your /usr/local/bin folder, and sets the cap_net_raw capability allowing it to create raw sockets without being root.

```
sudo cp cnping /usr/local/bin/
sudo setcap cap_net_raw+ep /usr/local/bin/cnping
```

Note that if only http pinging is requested, you do not need cap_net_raw or root access.

### Windows

A Windows-exe can be cross compiled on Linux, just install the necessary dependencies and compile it:

```
sudo apt install binutils-mingw-w64-i686 gcc-mingw-w64-i686 g++-mingw-w64-i686
make cnping.exe
```

### Other distributions

See our Wiki page for information on more distributions, e.g. Arch, Void, Gentoo or Fedora: [Binary packages and distributions](https://github.com/cntools/cnping/wiki/Binary-packages-and-distributions)


================================================
FILE: cnping.1
================================================
.TH CNPING 1 "Dec 2020" "version 1.0" "User Manuals"
.SH "NAME"
cnping \- Minimal Graphical IPv4 Ping/HTTP Ping Tool
.SH "SYNOPSIS"
.B cnping cnping [host] [period] [extra size] [y-axis scaling] [window title]
.SH "DESCRIPTION"
.B cnping
is a minimal graphical real time IPv4 ping tool written in C.
It can send pings via ICMP (regular ping) or HTTP which is useful 
in case ICMP is prohibited. Responses are displayed as vertical bars 
in a graphical window. Red bars indicate a response was not (yet) 
received, white bars indicate the response was received with a height 
relative to the round trip time. Additional statistics are displayed 
as an overlay.
.SH "OPTIONS"
All options can be passed either positional or as flags.
.IP -h
domain, IP address of ping target for ICMP or http host, i.e. http://google.com

.IP -p
period in seconds (optional), default 0.02 

.IP -s
ping packet extra size (above 12), optional, default = 0 

.IP -y
use a fixed scaling factor instead of auto scaling (optional)

.IP -t
the title of the window (optional)

.SH "GUI"
.IP q
quit

.IP m
toggle histogram mode

.IP f
toggle histogram frame mode

.IP c
reset histogram data

.SH "AUTHOR"
Charles Lohr <TODO Email>
.SH COPYRIGHT
Copyright \(co 2018-2020 Charles Lohr
.br
.SH LICENSE
Rawdraw and CNPing may be licensed feely under the MIT-x11
or NewBSD Licenses. You choose!



================================================
FILE: cnping.c
================================================
//Copyright (c) 2011-2019 <>< Charles Lohr - Under the MIT/x11 or NewBSD License you choose.

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <errno.h>
#include <string.h>
#if defined( WINDOWS ) || defined( WIN32 )
#ifdef _MSC_VER
#define strdup _strdup
#endif
#include <windows.h>
#else
  #ifdef __FreeBSD__
    #include <sys/types.h>
    #include <netinet/in.h>
  #endif
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <netdb.h>
#endif

#ifndef CNFGOGL
#define CNFGOGL
#endif

#define CNFG_IMPLEMENTATION
#include "rawdraw/os_generic.h"
#include "rawdraw/CNFG.h"
#include "ping.h"
#include "error_handling.h"
#include "httping.h"

// #### Cross-plattform debugging ####
// Windows does not print to Console, use DebugView from SysInternals to
// see the output. (Setup: Computer -> Connect Local; Capture -> Capture Win32)
// Warning: Debugging on Windows can slow cnping down and lead to wrong measurements!
//#define DEBUG
#ifdef DEBUG
	char msgbuf[1024];
	#ifdef WIN32
		#define debug(...) \
			snprintf(msgbuf, sizeof(msgbuf), __VA_ARGS__); \
			OutputDebugString(msgbuf);
	#else
		#define debug(...) printf(__VA_ARGS__);
	#endif
#else
	// Let the compiler parse it to catch errors. Compiler will optimize away.
	#define debug(...) \
			do { if (0) fprintf(stderr, __VA_ARGS__); } while (0);
#endif

unsigned frames = 0;
unsigned long iframeno = 0;
short screenx, screeny;
const char * pinghost;
float GuiYScaleFactor;
int GuiYscaleFactorIsConstant;
double globmaxtime, globmintime = 1e20;
double globinterval, globlast;
uint64_t globalrx;
uint64_t globallost;
// Ping Data. Will be overwritten with random bytes when !DEBUG
uint8_t pattern[8] = {0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF};

#define PINGCYCLEWIDTH 8192
#define TIMEOUT 4

double PingSendTimes[PINGCYCLEWIDTH];
double PingRecvTimes[PINGCYCLEWIDTH];
int current_cycle = 0;

int ExtraPingSize;
int in_histogram_mode, in_frame_mode = 1;
void HandleGotPacket( int seqno, int timeout );

#if defined( WINDOWS ) || defined( WIN32 )
WSADATA wsaData;
#endif


#define MAX_HISTO_MARKS (TIMEOUT*10000)
uint64_t hist_counts[MAX_HISTO_MARKS];

void HandleNewPacket( int seqno )
{
	double Now = OGGetAbsoluteTime();
	PingSendTimes[seqno] = Now;
	PingRecvTimes[seqno] = 0;
	static int timeoutmark;

	while( Now - PingSendTimes[timeoutmark] > TIMEOUT )
	{
		if( PingRecvTimes[timeoutmark] < PingSendTimes[timeoutmark] )
		{
			HandleGotPacket( timeoutmark, 1 );
		}
		timeoutmark++;
		if( timeoutmark >= PINGCYCLEWIDTH ) timeoutmark = 0;
	}
}

void HandleGotPacket( int seqno, int timeout )
{
	double Now = OGGetAbsoluteTime();

	if( timeout )
	{
		if( PingRecvTimes[seqno] < -0.5 ) return;

		globallost++;
		PingRecvTimes[seqno] = -1;
		hist_counts[MAX_HISTO_MARKS-1]++;
		return;
	}

	if( PingRecvTimes[seqno] >= PingSendTimes[seqno] ) return;
	if( PingSendTimes[seqno] < 1 )  return;
	if( Now - PingSendTimes[seqno] > TIMEOUT ) return;

	PingRecvTimes[seqno] = OGGetAbsoluteTime();
	double Delta = PingRecvTimes[seqno] - PingSendTimes[seqno];
	if( Delta > globmaxtime ) { globmaxtime = Delta; }
	if( Delta < globmintime ) { globmintime = Delta; }
	int slot = Delta * 10000;
	if( slot >= MAX_HISTO_MARKS ) slot = MAX_HISTO_MARKS-1;
	if( slot < 0 ) slot = 0;
	hist_counts[slot]++;

	if( globlast > 0.5 )
	{
		if( Now - globlast > globinterval ) globinterval = Now - globlast;
	}
	globlast = Now;
	globalrx++;
}


void HTTPingCallbackStart( int seqno )
{
	current_cycle = seqno;
	HandleNewPacket( seqno );
}

void HTTPingCallbackGot( int seqno )
{
	HandleGotPacket( seqno, 0 );
}

void display(uint8_t *buf, int bytes)
{
	int reqid = (buf[0] << 24) | (buf[1]<<16) | (buf[2]<<8) | (buf[3]);
	debug("Received ping: reqid=%d\n", reqid);
	reqid &= (PINGCYCLEWIDTH-1);
	if( memcmp( buf+4, pattern, sizeof(pattern) ) != 0 ) return;
	debug("Memcmp OK, checked %ld bytes, first values being %x %x %x %x\n",
		  (long int) sizeof(pattern), pattern[0], pattern[1], pattern[2], pattern[3])
	HandleGotPacket( reqid, 0 );
}

int load_ping_packet( uint8_t * buffer, int bufflen )
{
	buffer[0] = current_cycle >> 24;
	buffer[1] = current_cycle >> 16;
	buffer[2] = current_cycle >> 8;
	buffer[3] = current_cycle >> 0;

	memcpy( buffer+4, pattern, sizeof(pattern) );

	if( ping_failed_to_send )
	{
		PingSendTimes[(current_cycle+PINGCYCLEWIDTH-1)&(PINGCYCLEWIDTH-1)] = 0; //Unset ping send.
	}

	HandleNewPacket( current_cycle&(PINGCYCLEWIDTH-1) );

	current_cycle++;

	return 12 + ExtraPingSize;
}

void * PingListen( void * r )
{
	listener();
	ERRM( "Fault on listen.\n" );
	exit( -2 );
}

void * PingSend( void * r )
{
	do_pinger( );
	ERRM( "Fault on ping.\n" );
	exit( -1 );
}




void HandleKey( int keycode, int bDown )
{
	switch( keycode )
	{

#if defined( WIN32 ) || defined( WINDOWS )
		case 'r':
		{
			char   lpFilename[1024];
			char   lpDirectory[1024];
			GetCurrentDirectory( 1023, lpDirectory );
			GetModuleFileNameA( GetModuleHandle(0), lpFilename, 1023 );

			CreateProcessA( lpFilename, GetCommandLine(), 0, 0, 1, 0, 0, lpDirectory, 0, 0 );
			exit( 0 );
			break;
		}
#endif
		case 'f':
			if( bDown ) in_frame_mode = !in_frame_mode;
			if( !in_frame_mode ) in_histogram_mode = 1;
			break;
		case 'm': 
			if( bDown ) in_histogram_mode = !in_histogram_mode;
			if( !in_histogram_mode ) in_frame_mode = 1;
			break;
		case 'c':
			memset( hist_counts, 0, sizeof( hist_counts ) );
			globmaxtime = 0;
			globmintime = 1e20;
			globinterval = 0;
			globlast = 0;
			globalrx = 0;
			globallost = 0;
			break;
		case 'q':
			exit(0);
			break;

	}
}
void HandleButton( int x, int y, int button, int bDown ){}
void HandleMotion( int x, int y, int mask ){}
void HandleDestroy() { exit(0); }


double GetWindMaxPingTime( void )
{
	int i;
	double maxtime = 0;

	for( i = 0; i < screenx; i++ )
	{
		int index = ((current_cycle - i - 1) + PINGCYCLEWIDTH) & (PINGCYCLEWIDTH-1);
		double st = PingSendTimes[index];
		double rt = PingRecvTimes[index];

		double dt = 0;

		if( rt > st )
		{
			dt = rt - st;
			dt *= 1000;
			if( dt > maxtime ) maxtime = dt;
		}
	}

	return maxtime;
}

void DrawMainText( const char * stbuf )
{
	int x, y;
	CNFGColor( 0x000000ff );
	for( x = -1; x < 2; x++ ) for( y = -1; y < 2; y++ )
	{
		CNFGPenX = 10+x; CNFGPenY = 10+y;
		CNFGDrawText( stbuf, 2 );
	}
	CNFGColor( 0xffffffff );
	CNFGPenX = 10; CNFGPenY = 10;
	CNFGDrawText( stbuf, 2 );
}

void DrawFrameHistogram()
{
	int i;
//	double Now = OGGetAbsoluteTime();
	const int colwid = 50;
	int categories = (screenx-50)/colwid;
	int maxpingslot = ( globmaxtime*10000.0 );
	int minpingslot = ( globmintime*10000.0 );
	int slots = maxpingslot-minpingslot;

	if( categories <= 2 )
	{
		goto nodata;
	}
	else
	{
		int skips = ( (slots)/categories ) + 1;
		int slotsmax = maxpingslot / skips + 1;
		int slotsmin = minpingslot / skips;
		slots = slotsmax - slotsmin;
		if( slots <= 0 ) goto nodata;

		uint64_t samples[slots+2];
		int      ssmsMIN[slots+2];
		int      ssmsMAX[slots+2];
		int samp = minpingslot - 1;

		if( slots <= 1 ) goto nodata;

		memset( samples, 0, sizeof( samples ) );
		if( samp < 0 ) samp = 0;

		uint64_t highestchart = 0;
		int tslot = 0;
		for( i = slotsmin; i <= slotsmax; i++ )
		{
			int j;
			uint64_t total = 0;
			ssmsMIN[tslot] = samp;
			for( j = 0; j < skips; j++ )
			{
				total += hist_counts[samp++];
			}

			ssmsMAX[tslot] = samp;
			if( total > highestchart ) highestchart = total;
			samples[tslot++] = total;
		}

		if( highestchart <= 0 )
		{
			goto nodata;
		}

		int rslots = 0;
		for( i = 0; i < slots+1; i++ )
		{
			if( samples[i] ) rslots = i;
		}
		rslots++;

		for( i = 0; i < rslots; i++ )
		{
			CNFGColor( 0x33cc33ff );
			int top = 30;
			uint64_t samps = samples[i];
			int bottom = screeny - 50;
			int height = samps?(samps * (bottom-top) / highestchart + 1):0;
			int startx = (i+1) * (screenx-50) / rslots;
			int endx = (i+2) * (screenx-50) / rslots;

			if( !in_frame_mode )
			{
				CNFGTackRectangle( startx, bottom-height, endx, bottom + 1 );
				CNFGColor( 0x000000ff );
			}
			else
			{
				CNFGColor( 0x8080ffff );
			}
			CNFGTackSegment( startx, bottom+1, endx, bottom+1 );

			CNFGTackSegment( startx, bottom-height, startx, bottom+1 );
			CNFGTackSegment( endx,   bottom-height, endx,   bottom+1 );

			CNFGTackSegment( startx, bottom-height, endx, bottom-height );
			char stbuf[1024];
			int log10 = 1;
			int64_t ll = samps;
			while( ll >= 10 )
			{
				ll /= 10;
				log10++;
			}

			if( !in_frame_mode )
			{
				CNFGColor( 0xffffffff );
			}
			else
			{
				CNFGColor( 0x8080ffff );
			}


			CNFGPenX = startx + (8-log10) * 4; CNFGPenY = bottom+3;
#ifdef WIN32
			sprintf( stbuf, "%I64u", samps );
#else
			sprintf( stbuf, "%lu", samps );
#endif
			CNFGDrawText( stbuf, 2 );

			CNFGPenX = startx; CNFGPenY = bottom+14;
			sprintf( stbuf, "%5.1fms\n%5.1fms", ssmsMIN[i]/10.0, ssmsMAX[i]/10.0 );
			CNFGDrawText( stbuf, 2 );
		}
		char stt[1024];
#ifdef WIN32
		snprintf( stt, 1024, "Host: %s\nHistorical max  %9.2fms\nBiggest interval%9.2fms\nHistorical packet loss %I64u/%I64u = %6.3f%%",
#else
		snprintf( stt, 1024, "Host: %s\nHistorical max  %9.2fms\nBiggest interval%9.2fms\nHistorical packet loss %lu/%lu = %6.3f%%",
#endif
			pinghost, globmaxtime*1000.0, globinterval*1000.0, globallost, globalrx, globallost*100.0/(globalrx+globallost) );
		if( !in_frame_mode )
			DrawMainText( stt );
		return;
	}
nodata:
	DrawMainText( "No data.\n" );
	return;
}


void DrawFrame( void )
{
	int i;

	double now = OGGetAbsoluteTime();

	double totaltime = 0;
	int totalcountok = 0;
	int totalcountloss = 0;
	double mintime = 10000;
	double maxtime = 0;
	double stddev = 0;
	double last = -1;
	double loss = 100.00;
	double windmaxtime = GetWindMaxPingTime();

	for( i = 0; i < screenx; i++ )
	{
		int index = ((current_cycle - i - 1) + PINGCYCLEWIDTH) & (PINGCYCLEWIDTH-1);
		double st = PingSendTimes[index];
		double rt = PingRecvTimes[index];

		double dt = 0;

		if( rt > st ) // ping received
		{
			CNFGColor( 0xffffffff );
			dt = rt - st;
			dt *= 1000;
			totaltime += dt;
			if( dt < mintime ) mintime = dt;
			if( dt > maxtime ) maxtime = dt;
			totalcountok++;
			if( last < 0)
				last = dt;
		}
		else if (st != 0) // ping sent but not received
		{
			CNFGColor( 0xff0000ff );
			dt = now - st;
			dt *= 1000;
			if( i > 5 ) totalcountloss++; //Get a freebie on the first 5.
		}
		else // no ping sent for this point in time (after startup)
		{
			CNFGColor( 0x000000ff );
			dt = 99 * 1000; // assume 99s to fill screen black
		}

		if (!GuiYscaleFactorIsConstant)
		{
			GuiYScaleFactor =  (screeny - 50) / windmaxtime;
		}

		int h = dt*GuiYScaleFactor;
		int top = screeny - h;
		if( top < 0 ) top = 0;
		CNFGTackSegment( i, screeny-1, i, top );
	}

	double avg = totaltime / totalcountok;
	loss = (double) totalcountloss / (totalcountok + totalcountloss) * 100;

	for( i = 0; i < screenx; i++ )
	{
		int index = ((current_cycle - i - 1) + PINGCYCLEWIDTH) & (PINGCYCLEWIDTH-1);
		double st = PingSendTimes[index];
		double rt = PingRecvTimes[index];

		double dt = 0;
		if( rt > st )
		{
			dt = rt - st;
			dt *= 1000;
			stddev += (dt-avg)*(dt-avg);
		}
	}

	stddev /= totalcountok;

	stddev = sqrt(stddev);

	int avg_gui    = avg*GuiYScaleFactor;
	int stddev_gui = stddev*GuiYScaleFactor;

	CNFGColor( 0x00ff00ff );


	int l = avg_gui;
	CNFGTackSegment( 0, screeny-l, screenx, screeny - l );
	l = (avg_gui) + (stddev_gui);
	CNFGTackSegment( 0, screeny-l, screenx, screeny - l );
	l = (avg_gui) - (stddev_gui);
	CNFGTackSegment( 0, screeny-l, screenx, screeny - l );

	char stbuf[2048];
	char * sptr = &stbuf[0];

	sptr += sprintf( sptr, 
		"Last:%6.2f ms    Host: %s\n"
		"Min :%6.2f ms\n"
		"Max :%6.2f ms    Historical max:   %5.2f ms\n"
		"Avg :%6.2f ms    Biggest interval: %5.2f ms\n"
#ifdef WIN32
		"Std :%6.2f ms    Historical loss:  %I64u/%I64u %5.3f%%\n"
#else
		"Std :%6.2f ms    Historical loss:  %lu/%lu %5.3f%%\n"
#endif
		"Loss:%6.1f %%", last, pinghost, mintime, maxtime, globmaxtime*1000, avg, globinterval*1000.0, stddev,
		globallost, globalrx+globallost, globallost*100.0f/(globalrx+globallost), loss );

	DrawMainText( stbuf );
	OGUSleep( 1000 );
}

#ifdef WIN32

const char * glargv[10];
int glargc = 0;

int RegString( int write, char * data, DWORD len )
{
	HKEY hKey;
	if( RegCreateKeyExA(HKEY_CURRENT_USER, "Software\\cnping", 0, NULL,	   
		REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hKey, NULL) == ERROR_SUCCESS)
	{
		if( write )
		{
			RegSetValueExA( hKey, "history", 0, REG_SZ, (uint8_t*)data, len );
			return 0;
		}
		else
		{
			DWORD type;
			if( RegGetValueA( hKey, "", "history", 0x02, &type, data, &len ) == ERROR_SUCCESS )
			{	
				return 0;
			}
			return -16;
		}

		RegCloseKey( hKey );
	}
	else
	{
		return -15;
	}
}

INT_PTR CALLBACK TextEntry( HWND   hwndDlg, UINT   uMsg, WPARAM wParam, LPARAM lParam )
{

	switch( uMsg )
	{
	case WM_INITDIALOG:
		SetDlgItemText(hwndDlg, 4, "0.02");
		SetDlgItemText(hwndDlg, 5, "0" );

		char data[1024];
		if( !RegString( 0, data, sizeof( data ) ) )
		{
			SetDlgItemText(hwndDlg, 3, data);
		}

		return 0;
	case WM_COMMAND:
		switch( wParam>>24 )
		{
			case 4: case 3: return 0; //keyboard input
			case 1: case 2: return 0; //focus changed.
			case 0:
			{
				int id = wParam & 0xffffff;
				if( id == 8 || id == 2 )
				{
					exit( -1 );
				}

				char Address[128]; GetDlgItemText(hwndDlg, 3, Address, sizeof(Address));
				char Period[128];  GetDlgItemText(hwndDlg, 4, Period, sizeof(Period));
				char Extra[128];   GetDlgItemText(hwndDlg, 5, Extra, sizeof(Extra));
				char Scaling[128]; GetDlgItemText(hwndDlg, 6, Scaling, sizeof(Scaling));
			
				if( strlen( Address ) )
				{
					RegString( 1, Address, strlen( Address ) );

					glargc = 2;
					glargv[1] = strdup( Address );
					if( strlen( Period ) )
					{
						glargc = 3;
						glargv[2] = strdup( Period );
						if( strlen( Extra ) )
						{
							glargc = 4;
							glargv[3] = strdup( Extra );
							if( strlen( Scaling ) )
							{
								glargc = 5;
								glargv[4] = strdup( Scaling );
							}
						}
					}
				}
				EndDialog(hwndDlg, 0);
				return 0; //User pressed enter.
			}
		}
/*		return 0;
	case WM_CTLCOLORBTN:
		//printf( "ctr %p %p %p\n", uMsg, wParam, lParam );
		//return 0;
	case 32: case 512: case 132: case 24: case 70:
	case 127: case 783: case 28: case 134: case 6: case 7:
	case 8: case 312: case 15: case 71: case 133: case 307:
	case 20: case 310: case 33:
		return 0;
*/
	}
	return 0;
}
#endif
int main( int argc, const char ** argv )
{
	char title[1024];
	int i;
	double ThisTime;
	double LastFPSTime = OGGetAbsoluteTime();
	double LastFrameTime = OGGetAbsoluteTime();
	double SecToWait;
	double frameperiodseconds;
	const char * device = NULL;

#ifdef WIN32
	ShowWindow (GetConsoleWindow(), SW_HIDE);
#endif

	srand( (uintmax_t)(OGGetAbsoluteTime()*100000) );
	#ifndef DEBUG
	for( i = 0; i < sizeof( pattern ); i++ )
	{
		pattern[i] = rand();
	}
	#endif
	CNFGBGColor = 0x000080ff;
#ifdef WIN32
	if( argc < 2 )
	{
		DialogBox(0, "IPDialog", 0, TextEntry );
		argc = glargc;
		argv = glargv;
	}
#endif

	pingperiodseconds = 0.02;
	ExtraPingSize = 0;
	title[0] = 0;
	GuiYScaleFactor = 0;

	//We need to process all the unmarked parameters.
	int argcunmarked = 1;
	int displayhelp = 0;

	for( i = 1; i < argc; i++ )
	{
		const char * thisargv = argv[i];
		if( thisargv[0] == '-' )
		{
			int np = ++i;
			if( np >= argc )
			{
				displayhelp = 1;
				break;
			}
			const char * nextargv = argv[np];
			//Parameter-based field.
			switch( thisargv[1] )
			{
				case 'h': pinghost = nextargv; break;
				case 'p': pingperiodseconds = atof( nextargv ); break;
				case 's': ExtraPingSize = atoi( nextargv ); break;
				case 'y': GuiYScaleFactor = atof( nextargv ); break;
				case 't': sprintf(title, "%s", nextargv); break;
				case 'm': in_histogram_mode = 1; break;
				case 'I': device = nextargv; break;
				default: displayhelp = 1; break;
			}
		}
		else
		{
			//Unmarked fields
			switch( argcunmarked++ )
			{
				case 1: pinghost = thisargv; break;
				case 2: pingperiodseconds = atof( thisargv ); break;
				case 3: ExtraPingSize = atoi( thisargv ); break;
				case 4: GuiYScaleFactor = atof( thisargv ); break;
				case 5: sprintf(title, "%s", thisargv ); break;
				default: displayhelp = 1;
			}
		}
	}

	if( title[0] == 0 )
	{
		sprintf( title, "%s - cnping "VERSION, pinghost );
	}

	if( GuiYScaleFactor > 0 )
	{
		GuiYscaleFactorIsConstant = 1;
	}

	if( !pinghost )
	{
		displayhelp = 1;
	}

	if( displayhelp )
	{
		ERRM( "cnping "VERSION" Usage: cnping [host] [period] [extra size] [y-axis scaling] [window title]\n"
			"   (-h) [host]                 -- domain, IP address of ping target for ICMP or http host, i.e. http://google.com\n"
			"   (-p) [period]               -- period in seconds (optional), default 0.02 \n"
			"   (-s) [extra size]           -- ping packet extra size (above 12), optional, default = 0 \n"
			"   (-y) [const y-axis scaling] -- use a fixed scaling factor instead of auto scaling (optional)\n"
			"   (-t) [window title]         -- the title of the window (optional)\n"
			"   (-I) [interface]            -- Sets source interface (i.e. eth0)\n");
		return -1;
	}

#if defined( WIN32 ) || defined( WINDOWS )
	if(device)
	{
		ERRM("Error: Device option is not implemented on your platform. PRs welcome.\n");
		exit( -1 );
	}
	
	if( WSAStartup(MAKEWORD(2,2), &wsaData) )
	{
		ERRM( "Fault in WSAStartup\n" );
		exit( -2 );
	}
	CNFGSetup( title, 320, 155 );
#else
	CNFGSetupWMClass( title, 320, 155, "cnping", "cnping" );
#endif
 

	if( memcmp( pinghost, "http://", 7 ) == 0 )
	{
		StartHTTPing( pinghost+7, pingperiodseconds, device );
	}
	else
	{
		char* protoEnd = strstr( pinghost, "://" );
		if ( protoEnd )
		{
			int protoSize = protoEnd - pinghost;
			char protoBuffer[protoSize + 1];
			memcpy( protoBuffer, pinghost, protoSize );
			protoBuffer[protoSize] = '\0';
			ERRM( "Protocol \"%s\" is not supported\n", protoBuffer );
			exit( -1 );
		}

		ping_setup( pinghost, device );
		OGCreateThread( PingSend, 0 );
		OGCreateThread( PingListen, 0 );
	}


	frameperiodseconds = fmin(.2, fmax(.03, pingperiodseconds));

	while(1)
	{
		iframeno++;
		CNFGHandleInput();

		CNFGClearFrame();
		CNFGColor( 0xffffffff );
		CNFGGetDimensions( &screenx, &screeny );

		if( in_frame_mode )
		{
			DrawFrame();
		}

		if( in_histogram_mode )
		{
			DrawFrameHistogram();
		}

		CNFGPenX = 100; CNFGPenY = 100;
		CNFGColor( 0xff0000ff );
		CNFGDrawText( errbuffer, 3 );


		frames++;
		CNFGSwapBuffers();

		ThisTime = OGGetAbsoluteTime();
		if( ThisTime > LastFPSTime + 1 )
		{
			frames = 0;
			LastFPSTime+=1;
		}

		SecToWait = frameperiodseconds - ( ThisTime - LastFrameTime );
		LastFrameTime += frameperiodseconds;
		//printf("iframeno = %d; SecToWait = %f; pingperiodseconds = %f; frameperiodseconds = %f \n", iframeno, SecToWait, pingperiodseconds, frameperiodseconds);
		if( SecToWait > 0 )
			OGUSleep( (int)( SecToWait * 1000000 ) );
	}

	return(0);
}



================================================
FILE: debian/changelog
================================================
cnping (1.0.0) unstable; urgency=medium

  * Initial Release.

 -- Charles Lohr <lohr85@gmail.com>  Sun, 04 Dec 2022 14:05:43 +0100


================================================
FILE: debian/control
================================================
Source: cnping
Section: net
Priority: optional
Maintainer: Charles Lohr <lohr85@gmail.com>
Build-Depends: debhelper-compat (= 12),
               libglvnd-dev,
               libx11-dev,
               libxext-dev,
               libxinerama-dev,
               mesa-common-dev
Standards-Version: 4.6.1
Homepage: https://github.com/cntools/cnping/
VCS-Browser: https://github.com/cntools/cnping/
VCS-Git: https://github.com/cntools/cnping.git

Package: cnping
Architecture: any
Depends: libcap2-bin, ${misc:Depends}, ${shlibs:Depends}
Description: Minimal Graphical IP Ping Tool
 cnping is a minimal graphical real time IP ping tool written in C.
 It can send pings via ICMP (regular ping) or HTTP which is useful in case ICMP
 is prohibited. Responses are displayed as vertical bars in a graphical window.
 Red bars indicate a response was not (yet) received, white bars indicate the
 response was received with a height relative to the round trip time.
 Additional statistics are displayed as an overlay.
 .
 cnping uses rawdraw so it is OS independent and very light weight.


================================================
FILE: debian/copyright
================================================
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: cnping
Upstream-Contact: Charles Lohr <lohr85@gmail.com>
Source: https://github.com/cntools/cnping/

Files: *
Copyright: 2022 Charles Lohr <lohr85@gmail.com>
License: BSD-3-Clause or MIT

Files: debian/*
Copyright: 2022 Charles Lohr <lohr85@gmail.com>
License: GPL-3.0+

License: GPL-3.0+
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
 .
 This package is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 .
 You should have received a copy of the GNU General Public License
 along with this program. If not, see <https://www.gnu.org/licenses/>.
 .
 On Debian systems, the complete text of the GNU General
 Public License version 3 can be found in "/usr/share/common-licenses/GPL-3".

License: BSD-3-Clause
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are
 met:

     (1) Redistributions of source code must retain the above copyright
     notice, this list of conditions and the following disclaimer.

     (2) Redistributions in binary form must reproduce the above copyright
     notice, this list of conditions and the following disclaimer in
     the documentation and/or other materials provided with the
     distribution.

     (3)The name of the author may not be used to
     endorse or promote products derived from this software without
     specific prior written permission.

 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 POSSIBILITY OF SUCH DAMAGE.


License: MIT
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in all
 copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.


================================================
FILE: debian/install
================================================
cnping /usr/bin/
freedesktop/com.github.cntools.cnping.desktop /usr/share/applications/
freedesktop/com.github.cntools.cnping.metainfo.xml /usr/share/metainfo/
freedesktop/icons/hicolor /usr/share/icons


================================================
FILE: debian/postinst
================================================
#!/bin/sh

set -e

PROGRAM=$(dpkg-divert --truename /usr/bin/cnping)

if [ "$1" = configure ]; then
    # If we have setcap installed, try setting cap_net_raw+ep,
    # which allows us to install our binaries without the setuid
    # bit.
    if command -v setcap > /dev/null; then
        if setcap cap_net_raw+ep $PROGRAM; then
            chmod u-s $PROGRAM
        else
            echo "Setcap failed on $PROGRAM, falling back to setuid" >&2
            chmod u+s $PROGRAM
        fi
    else
        echo "Setcap is not installed, falling back to setuid" >&2
        chmod u+s $PROGRAM
    fi
fi

#DEBHELPER#

exit 0


================================================
FILE: debian/rules
================================================
#!/usr/bin/make -f

%:
	dh $@


================================================
FILE: debian/source/format
================================================
3.0 (native)


================================================
FILE: error_handling.h
================================================
#ifndef _ERROR_HANDLING
#define _ERROR_HANDLING

extern char errbuffer[1024];

#ifdef WIN32

#ifndef _MSC_VER
#define ERRM(x...) { sprintf( errbuffer, x ); MessageBox( 0, errbuffer, "cnping", 0 ); }
#define ERRMB(x...) { sprintf( errbuffer, x ); }
#else
#define ERRM(...) { sprintf( errbuffer, __VA_ARGS__ ); MessageBox( 0, errbuffer, "cnping", 0 ); }
#define ERRMB(...) { sprintf( errbuffer, __VA_ARGS__ ); }
#endif

#else

#define ERRM(x...) { fprintf( stderr, x ); }
#define ERRMB(x...) { sprintf( errbuffer, x);  fprintf( stderr, x ); }

#endif


#endif


================================================
FILE: freedesktop/com.github.cntools.cnping.desktop
================================================
[Desktop Entry]
Version=1.0
Name=cnping
Comment=Minimal Graphical IP Ping/HTTP Ping Tool
Type=Application
Icon=com.github.cntools.cnping
Categories=Network;
Keywords=ping;
StartupWMClass=cnping

# cnping is meant to be started from command line, let's give users a hint.
Exec=bash -c "cnping ; bash"
Terminal=true



================================================
FILE: freedesktop/com.github.cntools.cnping.metainfo.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
  <id>com.github.cntools.cnping</id>
  <name>cnping</name>
  <summary>Minimal Graphical IPv4 Ping/HTTP Ping Tool</summary>
  <launchable type="desktop-id">com.github.cntools.cnping.desktop</launchable>
  <metadata_license>CC0-1.0</metadata_license>
  <content_rating type="oars-1.1"/>
  <description>
    <p>
        cnping is a minimal graphical real time IPv4 ping tool written in C.
It can send pings via ICMP (regular ping) or HTTP which is useful 
in case ICMP is prohibited. Responses are displayed as vertical bars 
in a graphical window. Red bars indicate a response was not (yet) 
received, white bars indicate the response was received with a height 
relative to the round trip time. Additional statistics are displayed 
as an overlay.
    </p>
    <p>
        cnping uses rawdraw so it is OS independent and very light weight.
    </p>
  </description>
  <url type="homepage">https://github.com/cntools/cnping</url>
  <screenshots>
    <screenshot type="default">
      <image>https://i.imgur.com/Yj5coKN.gif</image>
      <caption/>
    </screenshot>
  </screenshots>
  <releases><!-- add new versions on top -->
    <release date="2019-10-04" version="1.0.0"/>
  </releases>
  <project_license>MIT or BSD-3-Clause</project_license>
  <url type="bugtracker">https://github.com/cntools/cnping/issues</url>
</component>


================================================
FILE: httping.c
================================================
#include "httping.h"
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "resolve.h"

#ifndef TCC
#include <unistd.h>
#include <sys/types.h>
#else
#include "tccheader.h"
#endif

#if defined( WIN32 ) || defined( WINDOWS )
	#ifdef TCC
	#else
	#include <ws2tcpip.h>
	#endif
	#define SOL_TCP IPPROTO_TCP
#else
	#include <sys/socket.h>
	#include <netinet/in.h>
	#include <netinet/tcp.h>
	#include <netdb.h>
#endif

#include "rawdraw/os_generic.h"
#include "error_handling.h"

#ifndef MSG_NOSIGNAL
	#define MSG_NOSIGNAL 0
#endif

#define HTTPTIMEOUT 3.0

//Callbacks (when started/received)
void HTTPingCallbackStart( int seqno );
void HTTPingCallbackGot( int seqno );

//Don't dynamically allocate resources here, since execution may be stopped arbitrarily.
void DoHTTPing( const char * addy, double minperiod, int * seqnoptr, volatile double * timeouttime, int * socketptr, volatile int * getting_host_by_name, const char * device)
{
#if defined(WIN32) || defined(WINDOWS)
	(void) device; // option is not available for windows. Suppress unused warning.

	WSADATA wsaData;
	int r =	WSAStartup(MAKEWORD(2,2), &wsaData);
	if( r )
	{
		ERRM( "Fault in WSAStartup\n" );
		exit( -2 );
	}
#endif
	struct sockaddr_in6 serveraddr;
	socklen_t serveraddr_len;
	int serverresolve;
	int httpsock;
	int addylen = strlen(addy);
	char hostname[addylen+1];
	memcpy( hostname, addy, addylen + 1 );
	char * eportmarker = strchr( hostname, ':' );
	char * eurl = strchr( hostname, '/' );

	int portno = 80;

	if( eportmarker )
	{
		portno = atoi( eportmarker+1 );
		*eportmarker = 0;
	}
	else
	{
		if( eurl )
			*eurl = 0;
	}

	/* gethostbyname: get the server's DNS entry */
	serveraddr_len = sizeof(serveraddr);
	*getting_host_by_name = 1;
	serverresolve = resolveName((struct sockaddr*) &serveraddr, &serveraddr_len, hostname, AF_UNSPEC);
	*getting_host_by_name = 0;

	if (serverresolve != 1) {
		ERRMB("ERROR, no such host as \"%s\"\n", hostname);
		goto fail;
	}

	/* build the server's Internet address */
	serveraddr.sin6_port = htons(portno);

reconnect:
	*socketptr = httpsock = socket(serveraddr.sin6_family, SOCK_STREAM, 0);
	if (httpsock < 0)
	{
		ERRMB( "Error opening socket\n" );
		return;
	}

	int sockVal = 1;
	// using char* for sockVal for windows
	if (setsockopt(httpsock, SOL_TCP, TCP_NODELAY, (char*) &sockVal, 4) != 0)
	{
		ERRM( "Error: Failed to set TCP_NODELAY\n");
		// not a critical error, we can continue
	}

#if !defined( WIN32 ) && !defined( WINDOWS )
	if(device)
	{
		if( setsockopt(httpsock, SOL_SOCKET, SO_BINDTODEVICE, device, strlen(device) +1) != 0)
		{
			ERRM("Error: Failed to set Device option.\n");
			exit( -1 );
		}
	}
#endif // not windows

	/* connect: create a connection with the server */
	if (connect(httpsock, (struct sockaddr*)&serveraddr, serveraddr_len) < 0)
	{
		ERRMB( "%s: ERROR connecting: %s (%d)\n", hostname, strerror(errno), errno );
		goto fail;
	}

#ifdef __APPLE__
	int opt = 1;
	setsockopt(httpsock, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
#endif

	errbuffer[0] = 0;

	while( 1 )
	{
		char buf[8192];

		int n = sprintf( buf, "HEAD %s HTTP/1.1\r\nConnection: keep-alive\r\nHost: %s\r\n\r\n", eurl?eurl:"/favicon.ico", hostname );

		(*seqnoptr) ++;
		HTTPingCallbackStart( *seqnoptr );

		int rs = send( httpsock, buf, n, MSG_NOSIGNAL );
		double starttime = *timeouttime = OGGetAbsoluteTime();
		int breakout = 0;
		if( rs != n ) breakout = 1;
		int endstate = 0;
		while( !breakout )
		{
#ifdef WIN32
			n = recv( httpsock, buf, sizeof(buf)-1, 0);
#else
			n = recv( httpsock, buf, sizeof(buf)-1, MSG_PEEK);
			if( n > 0 ) n = read( httpsock, buf, sizeof(buf)-1);
			else if( n == 0 ) break; //FIN received
#endif

			
			if( n < 0 ) return;
			
			int i;
			for( i = 0; i < n; i++ )
			{
				char c = buf[i];
				switch( endstate )
				{
				case 0: if( c == '\r' ) endstate++; break;
				case 1: if( c == '\n' ) endstate++; else endstate = 0; break;
				case 2: if( c == '\r' ) endstate++; else endstate = 0; break;
				case 3: if( c == '\n' && i == n-1) breakout = 1; else endstate = 0; break;
				}
			}
		}
		*timeouttime = OGGetAbsoluteTime();

		HTTPingCallbackGot( *seqnoptr );

		double delay_time = minperiod - (*timeouttime - starttime);
		if( delay_time > 0 )
			usleep( (int)(delay_time * 1000000) );
		if( !breakout ) {
#ifdef WIN32
			closesocket( httpsock );
#else
			close( httpsock );
#endif
			goto reconnect;
		}
	}
fail:
	return;
}


struct HTTPPingLaunch
{
	const char * addy;
	double minperiod;
	const char * device;

	volatile double timeout_time;
	volatile int failed;
	int seqno;
	int socket;
	volatile int getting_host_by_name;
};

static void * DeployPing( void * v )
{
	struct HTTPPingLaunch *hpl = (struct HTTPPingLaunch*)v;
	hpl->socket = 0;
	hpl->getting_host_by_name = 0;
	DoHTTPing( hpl->addy, hpl->minperiod, &hpl->seqno, &hpl->timeout_time, &hpl->socket, &hpl->getting_host_by_name, hpl->device );
	hpl->failed = 1;
	return 0;
}


static void * PingRunner( void * v )
{
	struct HTTPPingLaunch *hpl = (struct HTTPPingLaunch*)v;
	hpl->seqno = 0;
	while( 1 )
	{
		hpl->timeout_time = OGGetAbsoluteTime();
		og_thread_t thd = OGCreateThread( DeployPing, hpl );
		while( 1 )
		{
			double Now = OGGetAbsoluteTime();
			double usl = hpl->timeout_time - Now + HTTPTIMEOUT;
			if( usl > 0 ) usleep( (int)(usl*1000000 + 1000));
			else usleep( 10000 );

			if( hpl->timeout_time + HTTPTIMEOUT < Now && !hpl->getting_host_by_name ) //Can't terminate in the middle of a gethostbyname operation otherwise bad things can happen.
			{
				if( hpl->socket )
				{
#ifdef WIN32
					closesocket( hpl->socket );
#else
					close( hpl->socket );
#endif
				}

				OGCancelThread( thd );
				break;
			}
		}
	}
	return 0;
}

int StartHTTPing( const char * addy, double minperiod, const char * device)
{
	struct HTTPPingLaunch *hpl = malloc( sizeof( struct HTTPPingLaunch ) );
	hpl->addy = addy;
	hpl->minperiod = minperiod;
	hpl->device = device;
	OGCreateThread( PingRunner, hpl );
	return 0;
}





================================================
FILE: httping.h
================================================
#ifndef _HTTPING_H
#define _HTTPING_H

//Callbacks (when started/received)
void HTTPingCallbackStart( int seqno );
void HTTPingCallbackGot( int seqno );

//addy should be google.com/blah or something like that.  Do not include prefixing http://.  Port numbers OK.
int StartHTTPing( const char * addy, double minperiod, const char * device);

#endif



================================================
FILE: ping.c
================================================
//Copyright 2017 <>< C. Lohr, under the MIT/x11 License
//Rewritten from Sean Walton and Macmillan Publishers.
//Most of it was rewritten but the header was never updated.
//Now I finished the job.

#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "ping.h"
#include "error_handling.h"
#include "resolve.h"

#ifdef TCC
#include "tccheader.h"
#endif

int ping_failed_to_send;
float pingperiodseconds;
int precise_ping;
struct sockaddr_in6 psaddr;
socklen_t psaddr_len;
int using_regular_ping;

#ifdef WIN_USE_NO_ADMIN_PING

#ifdef TCC
	//...
#else
#include <inaddr.h>
#include <ws2tcpip.h>
#include <ipexport.h>
#include <icmpapi.h>
#include <windows.h>
#endif

#include "rawdraw/os_generic.h"



#define MAX_PING_SIZE 16384
#define PINGTHREADS 100

#if !defined( MINGW_BUILD ) && !defined( TCC )
	#pragma comment(lib, "Ws2_32.lib")
	#pragma comment(lib, "iphlpapi.lib")
#endif

static og_sema_t s_disp;
static og_sema_t s_ping;

void ping_setup(const char * strhost, const char * device)
{
	// resolve host
	psaddr_len = sizeof(psaddr);
	if( strhost )
		resolveName((struct sockaddr*) &psaddr, &psaddr_len, strhost, AF_INET); // only resolve ipv4 on windows
	else
		psaddr.sin6_family = AF_INET;

	s_disp = OGCreateSema();
	s_ping = OGCreateSema();
	//This function is executed first.
}

void listener()
{
	static uint8_t listth;
	if( listth ) return;
	listth = 1;

	OGUnlockSema( s_disp );
	//Normally needs to call display(buf + 28, bytes - 28 ); on successful ping.
	//This function is executed as a thread after setup.
	//Really, we just use the s_disp semaphore to make sure we only launch disp's at correct times.

	while(1) { OGSleep( 100000 ); }
	return;
}

static HANDLE pinghandles[PINGTHREADS];

static void * pingerthread( void * v )
{
	uint8_t ping_payload[MAX_PING_SIZE];

	HANDLE ih = *((HANDLE*)v);

	int timeout_ms = pingperiodseconds * (PINGTHREADS-1) * 1000;
	while(1)
	{
		OGLockSema( s_ping );
		int rl = load_ping_packet( ping_payload, sizeof( ping_payload ) );
		struct repl_t
		{
			ICMP_ECHO_REPLY rply;
			uint8_t err_data[16384];
		} repl;

		DWORD res = IcmpSendEcho( ih,
			((struct sockaddr_in*) &psaddr)->sin_addr.s_addr, ping_payload, rl,
			0, &repl, sizeof( repl ),
			timeout_ms );
		int err;
		if( !res ) err = GetLastError();
		OGLockSema( s_disp );

		if( !res )
		{
			if( err == 11050 )
			{
				printf( "GENERAL FAILURE\n" );
			}
			else
			{
				printf( "ERROR: %d\n", err );
			}
		}
		if( res )
		{
			display( repl.rply.Data, rl );
		}
		OGUnlockSema( s_disp );
	}
	return 0;
}

void singleping(struct sockaddr *addr, socklen_t addr_len )
{
	int i;
	(void) addr;
	(void) addr_len;

	if( psaddr.sin6_family != AF_INET )
	{
		// ipv6 ICMP Ping is not supported on windows
		ERRM( "ERROR: ipv6 ICMP Ping is not supported on windows\n" );
		exit( -1 );
	}

	static int did_init_threads = 0;
	if( !did_init_threads )
	{
		did_init_threads = 1;
		//Launch pinger threads
		for( i = 0; i < PINGTHREADS; i++ )
		{
			HANDLE ih = pinghandles[i] = IcmpCreateFile();
			if( ih == INVALID_HANDLE_VALUE )
			{
				ERRM( "Cannot create ICMP thread %d\n", i );
				exit( 0 );
			}

			OGCreateThread( pingerthread, &pinghandles[i] );
		}
	}
	//This function is executed as a thread after setup.

	if( i >= PINGTHREADS-1 ) i = 0;
	else i++;
	OGUnlockSema( s_ping );
}

void ping(struct sockaddr *addr, socklen_t addr_len )
{
	int i;
	(void) addr;
	(void) addr_len;

	using_regular_ping = 1;

	if( psaddr.sin6_family != AF_INET )
	{
		// ipv6 ICMP Ping is not supported on windows
		ERRM( "ERROR: ipv6 ICMP Ping is not supported on windows\n" );
		exit( -1 );
	}

	//Launch pinger threads
	for( i = 0; i < PINGTHREADS; i++ )
	{
		HANDLE ih = pinghandles[i] = IcmpCreateFile();
		if( ih == INVALID_HANDLE_VALUE )
		{
			ERRM( "Cannot create ICMP thread %d\n", i );
			exit( 0 );
		}

		OGCreateThread( pingerthread, &pinghandles[i] );
	}
	//This function is executed as a thread after setup.

	while(1)
	{
		if( i >= PINGTHREADS-1 ) i = 0;
		else i++;
		OGUnlockSema( s_ping );
		OGUSleep( (int)(pingperiodseconds * 1000000) );
	}
}



#else // ! WIN_USE_NO_ADMIN_PING

//The normal way to do it - only problem is Windows needs admin privs.


#ifdef WIN32
	#include <winsock2.h>
	#define SOL_IP		0
	#define F_SETFL		4
	#define ICMP_ECHO	8
	#define IP_TTL		2
	#define O_NONBLOCK   04000
#ifndef MINGW_BUILD
	#pragma comment(lib, "Ws2_32.lib")
#endif
	#include <windows.h>
	#include <winsock2.h>
	#include <ws2tcpip.h>
	#include <stdint.h>
#else // ! WIN32
	#ifdef __FreeBSD__
		#include <netinet/in.h>
	#endif
	#include <unistd.h>
	#include <sys/socket.h>
	#include <resolv.h>
	#include <netdb.h>
	#if defined(__APPLE__) || defined(__FreeBSD__)
		#ifndef SOL_IP
			#define SOL_IP IPPROTO_IP
		#endif
	#endif
	#include <netinet/ip.h>
	#include <netinet/ip_icmp.h>
	#include <netinet/icmp6.h>
#endif

#include "rawdraw/os_generic.h"

#if defined WIN32 || defined __APPLE__
struct icmphdr
{
	uint8_t		type;
	uint8_t		code;
	uint16_t	checksum;
	union
	{
		struct
		{
			uint16_t	id;
			uint16_t	sequence;
		} echo;
		uint32_t	gateway;
		struct
		{
			uint16_t	__unused;
			uint16_t	mtu;
		} frag;
	} un;
};
#endif


#define PACKETSIZE	65536

struct packet
{
#ifdef __FreeBSD__
	struct icmp hdr;
	unsigned char msg[PACKETSIZE-sizeof(struct icmp)];
#else
	struct icmphdr hdr;
	unsigned char msg[PACKETSIZE-sizeof(struct icmphdr)];
#endif
};

int sd;
int pid=-1;

uint16_t checksum( const unsigned char * start, uint16_t len )
{
	uint16_t i;
	const uint16_t * wptr = (uint16_t*) start;
	uint32_t csum = 0;
	for (i=1;i<len;i+=2)
		csum += (uint32_t)(*(wptr++));
	if( len & 1 )  //See if there's an odd number of bytes?
		csum += *(uint8_t*)wptr;
	if (csum>>16)
		csum = (csum & 0xFFFF)+(csum >> 16);
	//csum = (csum>>8) | ((csum&0xff)<<8);
	return ~csum;
}

// setsockopt TTL to 255
void setTTL(int sock)
{
	const int val=255;

	assert(psaddr.sin6_family == AF_INET || psaddr.sin6_family == AF_INET6);

	if ( setsockopt(sd, (psaddr.sin6_family == AF_INET) ? SOL_IP : SOL_IPV6, IP_TTL, &val, sizeof(val)) != 0)
	{
		ERRM("Error: Failed to set TTL option.  Are you root?  Or can do sock_raw sockets?\n");
		exit( -1 );
	}
}

// 0 = failed, 1 = this is a ICMP Response
int isICMPResponse(unsigned char* buf, int bytes)
{
	assert(psaddr.sin6_family == AF_INET || psaddr.sin6_family == AF_INET6);

	if( bytes == -1 ) return 0;

	if( psaddr.sin6_family == AF_INET ) // ipv4 compare
	{
		if( buf[9] != IPPROTO_ICMP ) return 0;
		if( buf[20] !=  ICMP_ECHOREPLY ) return 0;

	}
	else if( psaddr.sin6_family == AF_INET6 ) // ipv6 compare
	{
		if( buf[0] != ICMP6_ECHO_REPLY ) return 0;
	}

	return 1;
}

int createSocket()
{
	if( psaddr.sin6_family == AF_INET )
	{
		return socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);
	}
	else if( psaddr.sin6_family == AF_INET6 )
	{
		return socket(PF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
	}

	// invalid af_family
	return -1;
}

void listener()
{
#ifndef WIN32
	int sd = createSocket();

	setTTL(sd);
#endif

	struct sockaddr_in6 addr;
	unsigned char buf[66000];
#ifdef WIN32
	SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
#endif
	for (;;)
	{
		socklen_t addrlenval=sizeof(addr);
		int bytes;

#ifdef WIN32
		WSAPOLLFD fda[1];
		fda[0].fd = sd;
		fda[0].events = POLLIN;
		WSAPoll(fda, 1, 10);
#endif
		keep_retry_quick:

		bytes = recvfrom(sd, buf, sizeof(buf), 0, (struct sockaddr*)&addr, &addrlenval );
		if( !isICMPResponse(buf, bytes) ) continue;

		// compare the sender
		if( using_regular_ping && memcmp(&addr, &psaddr, addrlenval) != 0 ) continue;

		// sizeof(packet.hdr) + 20
		int offset = 0;
		if(addr.sin6_family == AF_INET) // ipv4
		{
#ifdef __FreeBSD__
			offset = 48;
#else
			offset = 28;
#endif
		}
		else // ipv6
		{
			offset = 8;
		}

		if ( bytes > 0 )
			display(buf + offset, bytes - offset );
		else
		{
			ERRM("Error: recvfrom failed");
		}

		goto keep_retry_quick;
	}

	ERRM( "Fault on listen().\n" );
	exit( 0 );
}

void singleping(struct sockaddr *addr, socklen_t addr_len )
{
	int cnt=1;

#ifdef WIN32
	{
		//Setup windows socket for nonblocking io.
		unsigned long iMode = 1;
		ioctlsocket(sd, FIONBIO, &iMode);
	}
#else
	if ( fcntl(sd, F_SETFL, O_NONBLOCK) != 0 )
		ERRM("Warning: Request nonblocking I/O failed.");
#endif

	double stime = OGGetAbsoluteTime();

	struct packet pckt;

	{
		int rsize = load_ping_packet( pckt.msg, sizeof( pckt.msg ) );
		memset( &pckt.hdr, 0, sizeof( pckt.hdr ) ); //This needs to be here, but I don't know why, since I think the struct is fully populated.
#ifdef __FreeBSD__
		pckt.hdr.icmp_code = 0;
		pckt.hdr.icmp_type = ICMP_ECHO;
		pckt.hdr.icmp_id = pid;
		pckt.hdr.icmp_seq = cnt++;
		pckt.hdr.icmp_cksum = checksum((const unsigned char *)&pckt, sizeof( pckt.hdr ) + rsize );
#else
		pckt.hdr.code = 0;
		pckt.hdr.type = (psaddr.sin6_family == AF_INET) ? ICMP_ECHO : ICMP6_ECHO_REQUEST;
		pckt.hdr.un.echo.id = pid;
		pckt.hdr.un.echo.sequence = cnt++;
		pckt.hdr.checksum = checksum((const unsigned char *)&pckt, sizeof( pckt.hdr ) + rsize );
#endif

		int sr = sendto(sd, (char*)&pckt, sizeof( pckt.hdr ) + rsize , 0, addr, addr_len);

		if( sr <= 0 )
		{
			ping_failed_to_send = 1;
			if( using_regular_ping )
			{
				ERRMB("Ping send failed:\n%s (%d)\n", strerror(errno), errno);
			}
		}
		else
		{
			ping_failed_to_send = 0;
		}
	} 
}

void ping(struct sockaddr *addr, socklen_t addr_len )
{
	int cnt=1;

	using_regular_ping = 1;
#ifdef WIN32
	{
		//Setup windows socket for nonblocking io.
		unsigned long iMode = 1;
		ioctlsocket(sd, FIONBIO, &iMode);
	}
#else
	if ( fcntl(sd, F_SETFL, O_NONBLOCK) != 0 )
		ERRM("Warning: Request nonblocking I/O failed.");
#endif

	double stime = OGGetAbsoluteTime();

	struct packet pckt;
	do
	{
		int rsize = load_ping_packet( pckt.msg, sizeof( pckt.msg ) );
		memset( &pckt.hdr, 0, sizeof( pckt.hdr ) ); //This needs to be here, but I don't know why, since I think the struct is fully populated.
#ifdef __FreeBSD__
		pckt.hdr.icmp_code = 0;
		pckt.hdr.icmp_type = ICMP_ECHO;
		pckt.hdr.icmp_id = pid;
		pckt.hdr.icmp_seq = cnt++;
		pckt.hdr.icmp_cksum = checksum((const unsigned char *)&pckt, sizeof( pckt.hdr ) + rsize );
#else
		pckt.hdr.code = 0;
		pckt.hdr.type = (psaddr.sin6_family == AF_INET) ? ICMP_ECHO : ICMP6_ECHO_REQUEST;
		pckt.hdr.un.echo.id = pid;
		pckt.hdr.un.echo.sequence = cnt++;
		pckt.hdr.checksum = checksum((const unsigned char *)&pckt, sizeof( pckt.hdr ) + rsize );
#endif

		int sr = sendto(sd, (char*)&pckt, sizeof( pckt.hdr ) + rsize , 0, addr, addr_len);

		if( sr <= 0 )
		{
			ping_failed_to_send = 1;
			if( using_regular_ping )
			{
				ERRMB("Ping send failed:\n%s (%d)\n", strerror(errno), errno);
			}
		}
		else
		{
			ping_failed_to_send = 0;
		}

		if( precise_ping )
		{
			double ctime;
			do
			{
				ctime = OGGetAbsoluteTime();
				if( pingperiodseconds >= 1000 ) stime = ctime;
			} while( ctime < stime + pingperiodseconds );
			stime += pingperiodseconds;
		}
		else
		{
			if( pingperiodseconds > 0 )
			{
				uint32_t dlw = 1000000.0*pingperiodseconds;
				OGUSleep( dlw );
			}
		}
	} 	while( pingperiodseconds >= 0 );
	//close( sd ); //Hacky, we don't close here because SD doesn't come from here, rather from ping_setup.  We may want to run this multiple times.
}

void ping_setup(const char * strhost, const char * device)
{
	pid = getpid();

#ifdef WIN32
	WSADATA wsaData;
	int r =	WSAStartup(MAKEWORD(2,2), &wsaData);
	if( r )
	{
		ERRM( "Fault in WSAStartup\n" );
		exit( -2 );
	}
#endif


#ifdef WIN32
	sd = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, 0, 0, WSA_FLAG_OVERLAPPED);
	{
		int lttl = 0xff;
		if (setsockopt(sd, IPPROTO_IP, IP_TTL, (const char*)&lttl, sizeof(lttl)) == SOCKET_ERROR)
		{
			printf( "Warning: No IP_TTL.\n" );
		}
	}
#else
	// resolve host
	psaddr_len = sizeof(psaddr);

	if( strhost )
		resolveName((struct sockaddr*) &psaddr, &psaddr_len, strhost, AF_UNSPEC);
	else
		psaddr.sin6_family = AF_INET;

	sd = createSocket();

	setTTL(sd);

	if(device)
	{
		if( setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, device, strlen(device) +1) != 0)
		{
			ERRM("Error: Failed to set Device option.  Are you root?  Or can do sock_raw sockets?\n");
			exit( -1 );
		}
	}

#endif
	if ( sd < 0 )
	{
		ERRM("Error: Could not create raw socket\n");
		exit(0);
	}

}

#endif // WIN_USE_NO_ADMIN_PING


void do_pinger( )
{
	ping((struct sockaddr*) &psaddr, psaddr_len );
}

// used by the ERRMB makro from error_handling.h
char errbuffer[1024];



================================================
FILE: ping.h
================================================
#ifndef _PING_H
#define _PING_H

#include <stdint.h>
#ifdef WIN32
	typedef int socklen_t;
	struct sockaddr;
#else
	#include <sys/socket.h>
#endif

unsigned short checksum(const unsigned char *b, uint16_t len);

//Callback (when received)
void display(uint8_t *buf, int bytes);

//Callback (before sending)
//return value = # of bytes to send in ping message.
int load_ping_packet( uint8_t  * buffer, int buffersize );

void listener();
void ping(struct sockaddr *addr, socklen_t addr_len );
void do_pinger( );

void singleping(struct sockaddr *addr, socklen_t addr_len ); // If using this, must call ping_setup( 0, 0); to begin.

//If pingperiodseconds = -1, run ping/do_pinger once and exit.
extern float pingperiodseconds;
extern int precise_ping; //if 0, use minimal CPU, but ping send-outs are only approximate, if 1, spinlock until precise time for ping is hit.
extern int ping_failed_to_send;
void ping_setup(const char * strhost, const char * device);


#endif



================================================
FILE: resolve.c
================================================
#include "resolve.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "error_handling.h"

#ifdef WIN32
	#include <ws2tcpip.h>
#else // !WIN32
	#include <arpa/inet.h> // inet_pton (parsing ipv4 and ipv6 notation)

	#include <sys/types.h>
	#include <sys/socket.h>
	#include <netdb.h>
#endif

int resolveName(struct sockaddr* addr, socklen_t* addr_len, const char* hostname, int family)
{
	int result;
	struct addrinfo* res = NULL;

	// zero addr
	memset(addr, 0, *addr_len);

	// try to parse ipv4
	if(family == AF_UNSPEC || family == AF_INET) {
		result = inet_pton(AF_INET, hostname, &((struct sockaddr_in*) addr)->sin_addr);
		if(result == 1)
		{
			addr->sa_family = AF_INET;
			*addr_len = sizeof(struct sockaddr_in);
			return 1;
		}
	}

	// try to parse ipv6
	if(family == AF_UNSPEC || family == AF_INET6) {
		result = inet_pton(AF_INET6, hostname, &((struct sockaddr_in6*) addr)->sin6_addr);
		if(result == 1)
		{
			addr->sa_family = AF_INET6;
			*addr_len = sizeof(struct sockaddr_in6);
			return 1;
		}
	}

	// try to resolve DNS
	struct addrinfo hints;
	memset(&hints, 0, sizeof(struct addrinfo));
	hints.ai_family = family; // AF_UNSPEC, AF_INET, AF_INET6
	hints.ai_socktype = 0; // 0 = any, SOCK_STREAM, SOCK_DGRAM
	hints.ai_protocol = 0; // 0 = any
	hints.ai_flags = 0; // no flags

	result = getaddrinfo(hostname, NULL, &hints, &res);
	if( result != 0)
	{
		ERRM( "Error: cannot resolve hostname %s: %s\n", hostname, gai_strerror(result) );
		exit( -1 );
	}

	if(res->ai_addrlen > *addr_len)
	{
		// error - this should not happen
		ERRM( "Error addr is to short. required length: %d, available length: %d", res->ai_addrlen, *addr_len );
		freeaddrinfo(res);
		exit( -1 );
	}
	memcpy(addr, res->ai_addr, res->ai_addrlen);
	*addr_len = res->ai_addrlen;

	freeaddrinfo(res);
	return 1;
}


================================================
FILE: resolve.h
================================================
#ifndef _RESOLVE_H
#define _RESOLVE_H

#ifdef WIN32
	typedef int socklen_t;
	struct sockaddr;
#else
	#include <sys/socket.h>
#endif

// try to parse hostname
//  * as dot notation (1.1.1.1)
//  * as ipv6 notation (abcd:ef00::1)
//  * as hostname (resolve DNS)
// returns 1 on success
// family can be used to force ipv4 or ipv6. Use AF_UNSPEC (allow both), AF_INET or AF_INET6 to force ipv4 or ipv6
int resolveName(struct sockaddr* addr, socklen_t* addr_len, const char* hostname, int family);

#endif


================================================
FILE: resources.rc
================================================
#ifndef WIN_USE_NO_ADMIN_PING
1 24 "uac.manifest"
#endif

#include <windows.h>

LANGUAGE 9, SUBLANG_DEFAULT
IPDialog DIALOG 10, 10, 340, 100
STYLE WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
CAPTION "cnping"
BEGIN
    LTEXT           "Usage: cnping [host] [period] [extra size] [y-axis scaling]",                        0, 90, 0, 300, 20, SS_LEFT
    LTEXT           "[host]                 -- domain or IP address of icmp ping, or http://host",        0, 90, 15, 300, 20, SS_LEFT
    LTEXT           "[period]               -- period in seconds (optional), default is 0.02",            0, 90, 30, 300, 20, SS_LEFT
    LTEXT           "[extra size]           -- ping packet extra size (above 12), optional, default = 0", 0, 90, 45, 300, 10, SS_LEFT
    LTEXT           "[const y-axis scaling] -- use a fixed scaling factor instead of auto scaling",       0, 90, 60, 300, 10, SS_LEFT
    EDITTEXT        3, 1, 15, 88, 12
    EDITTEXT        4, 1, 30, 88, 12
    EDITTEXT        5, 1, 45, 88, 12
    EDITTEXT        6, 1, 60, 88, 12
    PUSHBUTTON      "&Ping", 7, 25, 80, 100, 10
    PUSHBUTTON      "&Close", 8, 175, 80, 100, 10
END

MAIN_ICON ICON "cnping.ico"


================================================
FILE: searchnet.c
================================================
#include <stdio.h>
#include "ping.h"
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdint.h>
#include <stdlib.h>
#include "resolve.h"
#include "rawdraw/os_generic.h"

uint32_t my_random_key;
uint8_t send_id[4];

extern float pingperiodseconds;

void * PingListen( void * r )
{
	listener();
	printf( "Fault on listen.\n" );
	exit( -2 );
}

void display(uint8_t *buf, int bytes)
{
	uint32_t reqid = ((uint32_t)buf[0+1] << 24) | (buf[1+1]<<16) | (buf[2+1]<<8) | (buf[3+1]);

	if( reqid != my_random_key ) return;

	printf( "%d.%d.%d.%d\n", buf[4+1], buf[5+1], buf[6+1], buf[7+1] );
}

int load_ping_packet( uint8_t * buffer, int bufflen )
{
	buffer[0+1] = my_random_key >> 24;
	buffer[1+1] = my_random_key >> 16;
	buffer[2+1] = my_random_key >> 8;
	buffer[3+1] = my_random_key >> 0;

	buffer[4+1] = send_id[0];
	buffer[5+1] = send_id[1];
	buffer[6+1] = send_id[2];
	buffer[7+1] = send_id[3];

	return 12;
}


int main( int argc, char ** argv )
{
	uint32_t offset;
	int mask;
	in_addr_t base;
	char dispip[32];
	float speed;

	ping_setup( 0, 0);
	OGCreateThread( PingListen, 0 );
	srand( ((int)(OGGetAbsoluteTime()*10000)) );
	my_random_key = rand();

	if( argc != 4 )
	{
		fprintf( stderr, "Usage: [searchnet IP net] [mask (single #, i.e. 24)] [speed (in seconds per attempt)]\n" );
		return -1;
	}

	base = ntohl(inet_addr( argv[1] ));
	mask = 1<<(32-atoi(argv[2]));
	speed = atof(argv[3]);

	base &= ~(mask-1);
	printf( "Base: %08x / Mask: %08x\n", base, mask );
	for( offset = 0; offset < mask; offset++ )
	{
		uint32_t cur = base + offset;
		sprintf( dispip, "%d.%d.%d.%d", (cur>>24)&0xff, (cur>>16)&0xff, (cur>>8)&0xff, (cur)&0xff );
		send_id[0] = (cur>>24)&0xff;
		send_id[1] = (cur>>16)&0xff;
		send_id[2] = (cur>>8)&0xff;
		send_id[3] = (cur)&0xff;
//		printf( "Pinging: %s\n", dispip );
		pingperiodseconds = -1;

		struct sockaddr_in6 psaddr;
		socklen_t psaddr_len = sizeof(psaddr);
		resolveName((struct sockaddr*) &psaddr, &psaddr_len, dispip, AF_UNSPEC);
		singleping((struct sockaddr*) &psaddr, psaddr_len );

		OGUSleep( (int)(speed * 1000000) );
	}

	return 0;
}



================================================
FILE: uac.manifest
================================================
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<!-- This is no longer needed if you compile with WIN_USE_NO_ADMIN_PING.  It is only required for raw packet access -->
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity version="1.0.0.0" processorArchitecture="X86" name="cnping" type="win32"/>
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
    <security>
      <requestedPrivileges>
        <requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
      </requestedPrivileges>
    </security>
  </trustInfo>
</assembly>
Download .txt
gitextract_6s2_y85m/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       └── build.yml
├── .gitignore
├── .gitmodules
├── .travis.yml
├── LICENSE
├── LICENSE.BSD-3-Clause
├── LICENSE.MIT
├── Makefile
├── README.md
├── cnping.1
├── cnping.c
├── debian/
│   ├── changelog
│   ├── control
│   ├── copyright
│   ├── install
│   ├── postinst
│   ├── rules
│   └── source/
│       └── format
├── error_handling.h
├── freedesktop/
│   ├── com.github.cntools.cnping.desktop
│   └── com.github.cntools.cnping.metainfo.xml
├── httping.c
├── httping.h
├── ping.c
├── ping.h
├── resolve.c
├── resolve.h
├── resources.rc
├── searchnet.c
└── uac.manifest
Download .txt
SYMBOL INDEX (53 symbols across 7 files)

FILE: cnping.c
  function HandleNewPacket (line 89) | void HandleNewPacket( int seqno )
  function HandleGotPacket (line 107) | void HandleGotPacket( int seqno, int timeout )
  function HTTPingCallbackStart (line 143) | void HTTPingCallbackStart( int seqno )
  function HTTPingCallbackGot (line 149) | void HTTPingCallbackGot( int seqno )
  function display (line 154) | void display(uint8_t *buf, int bytes)
  function load_ping_packet (line 165) | int load_ping_packet( uint8_t * buffer, int bufflen )
  function HandleKey (line 203) | void HandleKey( int keycode, int bDown )
  function HandleButton (line 244) | void HandleButton( int x, int y, int button, int bDown ){}
  function HandleMotion (line 245) | void HandleMotion( int x, int y, int mask ){}
  function HandleDestroy (line 246) | void HandleDestroy() { exit(0); }
  function GetWindMaxPingTime (line 249) | double GetWindMaxPingTime( void )
  function DrawMainText (line 273) | void DrawMainText( const char * stbuf )
  function DrawFrameHistogram (line 287) | void DrawFrameHistogram()
  function DrawFrame (line 421) | void DrawFrame( void )
  function RegString (line 541) | int RegString( int write, char * data, DWORD len )
  function INT_PTR (line 570) | INT_PTR CALLBACK TextEntry( HWND   hwndDlg, UINT   uMsg, WPARAM wParam, ...
  function main (line 644) | int main( int argc, const char ** argv )

FILE: httping.c
  function DoHTTPing (line 43) | void DoHTTPing( const char * addy, double minperiod, int * seqnoptr, vol...
  type HTTPPingLaunch (line 195) | struct HTTPPingLaunch
  type HTTPPingLaunch (line 210) | struct HTTPPingLaunch
  type HTTPPingLaunch (line 210) | struct HTTPPingLaunch
  type HTTPPingLaunch (line 221) | struct HTTPPingLaunch
  type HTTPPingLaunch (line 221) | struct HTTPPingLaunch
  function StartHTTPing (line 253) | int StartHTTPing( const char * addy, double minperiod, const char * device)

FILE: ping.c
  type sockaddr_in6 (line 23) | struct sockaddr_in6
  function ping_setup (line 54) | void ping_setup(const char * strhost, const char * device)
  function listener (line 68) | void listener()
  type repl_t (line 96) | struct repl_t
  type sockaddr_in (line 103) | struct sockaddr_in
  function singleping (line 130) | void singleping(struct sockaddr *addr, socklen_t addr_len )
  function ping (line 167) | void ping(struct sockaddr *addr, socklen_t addr_len )
  type icmphdr (line 247) | struct icmphdr
  type packet (line 272) | struct packet
  function checksum (line 286) | uint16_t checksum( const unsigned char * start, uint16_t len )
  function setTTL (line 302) | void setTTL(int sock)
  function isICMPResponse (line 316) | int isICMPResponse(unsigned char* buf, int bytes)
  function createSocket (line 336) | int createSocket()
  function listener (line 351) | void listener()
  function singleping (line 412) | void singleping(struct sockaddr *addr, socklen_t addr_len )
  function ping (line 465) | void ping(struct sockaddr *addr, socklen_t addr_len )
  function ping_setup (line 539) | void ping_setup(const char * strhost, const char * device)
  function do_pinger (line 597) | void do_pinger( )

FILE: ping.h
  type socklen_t (line 6) | typedef int socklen_t;
  type sockaddr (line 7) | struct sockaddr
  type sockaddr (line 22) | struct sockaddr
  type sockaddr (line 25) | struct sockaddr

FILE: resolve.c
  function resolveName (line 18) | int resolveName(struct sockaddr* addr, socklen_t* addr_len, const char* ...

FILE: resolve.h
  type socklen_t (line 5) | typedef int socklen_t;
  type sockaddr (line 6) | struct sockaddr
  type sockaddr (line 17) | struct sockaddr

FILE: searchnet.c
  function display (line 23) | void display(uint8_t *buf, int bytes)
  function load_ping_packet (line 32) | int load_ping_packet( uint8_t * buffer, int bufflen )
  function main (line 48) | int main( int argc, char ** argv )
Condensed preview — 31 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (72K chars).
[
  {
    "path": ".github/dependabot.yml",
    "chars": 120,
    "preview": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"monthly\"\n\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "chars": 1663,
    "preview": "name: Build cnping\n\non:\n  push:\n  pull_request:\n  workflow_dispatch:\n\njobs:\n  Build-for-Linux:\n    runs-on: ubuntu-lates"
  },
  {
    "path": ".gitignore",
    "chars": 145,
    "preview": "*.o\n*.exe\ncnping\ncnping-mousey\nsearchnet\ntags\n\ndebian/.debhelper\ndebian/cnping\ndebian/cnping.substvars\ndebian/debhelper-"
  },
  {
    "path": ".gitmodules",
    "chars": 80,
    "preview": "[submodule \"rawdraw\"]\n\tpath = rawdraw\n\turl = https://github.com/cntools/rawdraw\n"
  },
  {
    "path": ".travis.yml",
    "chars": 1146,
    "preview": "language: c\ndist: bionic\n\nbefore_install:\n- sudo apt-get -qq update\n- sudo apt-get install -y make libxinerama-dev libxe"
  },
  {
    "path": "LICENSE",
    "chars": 97,
    "preview": "//Rawdraw and CNPing may be licensed feely under the MIT-x11\n//or NewBSD Licenses.  You choose!\n\n"
  },
  {
    "path": "LICENSE.BSD-3-Clause",
    "chars": 1429,
    "preview": "BSD 3-Clause License\n\nCopyright (c) 2018 Charles Lohr\n\nRedistribution and use in source and binary forms, with or withou"
  },
  {
    "path": "LICENSE.MIT",
    "chars": 1069,
    "preview": "MIT License\n\nCopyright (c) 2018 Charles Lohr\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
  },
  {
    "path": "Makefile",
    "chars": 3108,
    "preview": "CFLAGS ?= -s -Os -I/opt/X11/include -Wall\nLDFLAGS ?= -s -L/opt/X11/lib/\nCC? = gcc\n\nICONSPATH = freedesktop/icons/hicolor"
  },
  {
    "path": "README.md",
    "chars": 2247,
    "preview": "cnping\n======\n\nMinimal Graphical IPV4 Ping/HTTP Ping Tool.  (also comes with searchnet, like nmap but smaller and simple"
  },
  {
    "path": "cnping.1",
    "chars": 1363,
    "preview": ".TH CNPING 1 \"Dec 2020\" \"version 1.0\" \"User Manuals\"\n.SH \"NAME\"\ncnping \\- Minimal Graphical IPv4 Ping/HTTP Ping Tool\n.SH"
  },
  {
    "path": "cnping.c",
    "chars": 19047,
    "preview": "//Copyright (c) 2011-2019 <>< Charles Lohr - Under the MIT/x11 or NewBSD License you choose.\n\n#include <stdio.h>\n#includ"
  },
  {
    "path": "debian/changelog",
    "chars": 132,
    "preview": "cnping (1.0.0) unstable; urgency=medium\n\n  * Initial Release.\n\n -- Charles Lohr <lohr85@gmail.com>  Sun, 04 Dec 2022 14:"
  },
  {
    "path": "debian/control",
    "chars": 1078,
    "preview": "Source: cnping\nSection: net\nPriority: optional\nMaintainer: Charles Lohr <lohr85@gmail.com>\nBuild-Depends: debhelper-comp"
  },
  {
    "path": "debian/copyright",
    "chars": 3608,
    "preview": "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\nUpstream-Name: cnping\nUpstream-Contact: Charl"
  },
  {
    "path": "debian/install",
    "chars": 203,
    "preview": "cnping /usr/bin/\nfreedesktop/com.github.cntools.cnping.desktop /usr/share/applications/\nfreedesktop/com.github.cntools.c"
  },
  {
    "path": "debian/postinst",
    "chars": 623,
    "preview": "#!/bin/sh\n\nset -e\n\nPROGRAM=$(dpkg-divert --truename /usr/bin/cnping)\n\nif [ \"$1\" = configure ]; then\n    # If we have set"
  },
  {
    "path": "debian/rules",
    "chars": 30,
    "preview": "#!/usr/bin/make -f\n\n%:\n\tdh $@\n"
  },
  {
    "path": "debian/source/format",
    "chars": 13,
    "preview": "3.0 (native)\n"
  },
  {
    "path": "error_handling.h",
    "chars": 558,
    "preview": "#ifndef _ERROR_HANDLING\n#define _ERROR_HANDLING\n\nextern char errbuffer[1024];\n\n#ifdef WIN32\n\n#ifndef _MSC_VER\n#define ER"
  },
  {
    "path": "freedesktop/com.github.cntools.cnping.desktop",
    "chars": 315,
    "preview": "[Desktop Entry]\nVersion=1.0\nName=cnping\nComment=Minimal Graphical IP Ping/HTTP Ping Tool\nType=Application\nIcon=com.githu"
  },
  {
    "path": "freedesktop/com.github.cntools.cnping.metainfo.xml",
    "chars": 1408,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<component type=\"desktop-application\">\n  <id>com.github.cntools.cnping</id>\n  <na"
  },
  {
    "path": "httping.c",
    "chars": 6022,
    "preview": "#include \"httping.h\"\n#include <errno.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#"
  },
  {
    "path": "httping.h",
    "chars": 350,
    "preview": "#ifndef _HTTPING_H\n#define _HTTPING_H\n\n//Callbacks (when started/received)\nvoid HTTPingCallbackStart( int seqno );\nvoid "
  },
  {
    "path": "ping.c",
    "chars": 12469,
    "preview": "//Copyright 2017 <>< C. Lohr, under the MIT/x11 License\n//Rewritten from Sean Walton and Macmillan Publishers.\n//Most of"
  },
  {
    "path": "ping.h",
    "chars": 969,
    "preview": "#ifndef _PING_H\n#define _PING_H\n\n#include <stdint.h>\n#ifdef WIN32\n\ttypedef int socklen_t;\n\tstruct sockaddr;\n#else\n\t#incl"
  },
  {
    "path": "resolve.c",
    "chars": 1820,
    "preview": "#include \"resolve.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include \"error_handling.h\"\n\n#ifdef WIN"
  },
  {
    "path": "resolve.h",
    "chars": 502,
    "preview": "#ifndef _RESOLVE_H\n#define _RESOLVE_H\n\n#ifdef WIN32\n\ttypedef int socklen_t;\n\tstruct sockaddr;\n#else\n\t#include <sys/socke"
  },
  {
    "path": "resources.rc",
    "chars": 1158,
    "preview": "#ifndef WIN_USE_NO_ADMIN_PING\n1 24 \"uac.manifest\"\n#endif\n\n#include <windows.h>\n\nLANGUAGE 9, SUBLANG_DEFAULT\nIPDialog DIA"
  },
  {
    "path": "searchnet.c",
    "chars": 2114,
    "preview": "#include <stdio.h>\n#include \"ping.h\"\n#include <sys/socket.h>\n#include <netinet/in.h>\n#include <arpa/inet.h>\n#include <st"
  },
  {
    "path": "uac.manifest",
    "chars": 596,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>\n<!-- This is no longer needed if you compile with WIN_USE_NO_ADM"
  }
]

About this extraction

This page contains the full source code of the cnlohr/cnping GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 31 files (63.9 KB), approximately 21.4k tokens, and a symbol index with 53 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!