Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package tcping for openSUSE:Factory checked 
in at 2023-11-13 22:21:42
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/tcping (Old)
 and      /work/SRC/openSUSE:Factory/.tcping.new.17445 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "tcping"

Mon Nov 13 22:21:42 2023 rev:5 rq:1125281 version:2.4.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/tcping/tcping.changes    2023-08-28 
17:18:32.059802103 +0200
+++ /work/SRC/openSUSE:Factory/.tcping.new.17445/tcping.changes 2023-11-13 
22:25:22.501452983 +0100
@@ -1,0 +2,17 @@
+Sun Nov 12 20:34:02 UTC 2023 - Dirk Müller <dmuel...@suse.com>
+
+- update to 2.4.0:
+  * new feature: add `-i` to specify the interval between sending
+    probes.
+  * new feature: add `-I` to specify the source interface to use
+    for sending probes.
+  * new feature: add `-t` to specify a custom timeout for probes.
+  * new feature: add `--db` to specify the path and file name to
+    store tcping output to sqlite database. e.g. `--db
+    /tmp/tcping.db`.
+  * fix: add `rtt` to JSON output
+  * refactor: remove unnecessary custom types
+  * refactor: memory align `structs`
+  * refactor: Debian packaging instructions
+
+-------------------------------------------------------------------

Old:
----
  tcping-2.0.0.tar.gz

New:
----
  tcping-2.4.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ tcping.spec ++++++
--- /var/tmp/diff_new_pack.Rq2kns/_old  2023-11-13 22:25:23.185478168 +0100
+++ /var/tmp/diff_new_pack.Rq2kns/_new  2023-11-13 22:25:23.185478168 +0100
@@ -18,7 +18,7 @@
 
 
 Name:           tcping
-Version:        2.0.0
+Version:        2.4.0
 Release:        0
 Summary:        A ping program for TCP ports
 License:        MIT
@@ -29,7 +29,6 @@
 Source1:        vendor.tar.gz
 BuildRequires:  go >= 1.18
 BuildRequires:  golang-packaging
-%{go_provides}
 
 %description
 TCPing will send TCP probes to an IP address or a hostname specified
@@ -60,11 +59,10 @@
 %autosetup -p 1 -a 1
 
 %build
-%{goprep} github.com/pouriyajamshidi/tcping
-%{gobuild} -mod=vendor .
+go build -mod=vendor -buildmode=pie -trimpath -o tcping
 
 %install
-%{goinstall}
+install -m 755 -D tcping %{buildroot}/%{_bindir}/tcping
 
 %files
 %license LICENSE

++++++ _service ++++++
--- /var/tmp/diff_new_pack.Rq2kns/_old  2023-11-13 22:25:23.209479051 +0100
+++ /var/tmp/diff_new_pack.Rq2kns/_new  2023-11-13 22:25:23.213479199 +0100
@@ -1,9 +1,9 @@
 <services>
-  <service name="download_files" mode="disabled" />
-  <service mode="disabled" name="set_version">
+  <service name="download_files" mode="manual" />
+  <service mode="manual" name="set_version">
     <param name="basename">tcping</param>
   </service>
-  <service name="go_modules" mode="disabled">
+  <service name="go_modules" mode="manual">
   </service>
 </services>
 

++++++ tcping-2.0.0.tar.gz -> tcping-2.4.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tcping-2.0.0/.github/workflows/container-publish.yml 
new/tcping-2.4.0/.github/workflows/container-publish.yml
--- old/tcping-2.0.0/.github/workflows/container-publish.yml    2023-08-05 
15:50:50.000000000 +0200
+++ new/tcping-2.4.0/.github/workflows/container-publish.yml    2023-09-10 
16:07:16.000000000 +0200
@@ -37,14 +37,14 @@
       - name: Extract Version
         id: version_step
         run: |
-          echo "##[set-output 
name=version;]VERSION=${GITHUB_REF#$"refs/tags/v"}"
-          echo "##[set-output 
name=version_tag;]$GITHUB_REPOSITORY:${GITHUB_REF#$"refs/tags/v"}"
-          echo "##[set-output name=latest_tag;]$GITHUB_REPOSITORY:latest"
+          echo "version=${GITHUB_REF#"refs/tags/v"}" >> $GITHUB_ENV
+          echo "version_tag=${GITHUB_REPOSITORY}:${GITHUB_REF#"refs/tags/v"}" 
>> $GITHUB_ENV
+          echo "latest_tag=${GITHUB_REPOSITORY}:latest" >> $GITHUB_ENV
       - name: Print Version
         run: |
-          echo ${{steps.version_step.outputs.version}}
-          echo ${{steps.version_step.outputs.version_tag}}
-          echo ${{steps.version_step.outputs.latest_tag}}
+          echo ${{ env.version }}
+          echo ${{ env.version_tag }}
+          echo ${{ env.latest_tag }}
 
       - name: Checkout repository
         uses: actions/checkout@v3
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tcping-2.0.0/.gitignore new/tcping-2.4.0/.gitignore
--- old/tcping-2.0.0/.gitignore 2023-08-05 15:50:50.000000000 +0200
+++ new/tcping-2.4.0/.gitignore 2023-09-10 16:07:16.000000000 +0200
@@ -2,7 +2,9 @@
 executables/
 .idea/
 tcping
+tcping.exe
 .DS_Store
 .vscode
 *.code-workspace
 patches/
+*.db
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tcping-2.0.0/CHANGELOG.md 
new/tcping-2.4.0/CHANGELOG.md
--- old/tcping-2.0.0/CHANGELOG.md       2023-08-05 15:50:50.000000000 +0200
+++ new/tcping-2.4.0/CHANGELOG.md       2023-09-10 16:07:16.000000000 +0200
@@ -1,5 +1,38 @@
 # Changelog
 
+## v2.4.0 - 2023-09-10
+
+- new feature: add `-i` to specify the interval between sending probes. Thanks 
to @luca-patrignani
+- new feature: add `-I` to specify the source interface to use for sending 
probes. Thanks to @wizsk
+- new feature: add `-t` to specify a custom timeout for probes. Thanks to 
@luca-patrignani
+- new feature: add `--db` to specify the path and file name to store tcping 
output to sqlite database. e.g. `--db /tmp/tcping.db`. Thanks to @wizsk
+- fix: add `rtt` to JSON output
+- fix: CI warning thanks to @wizsk
+- refactor: remove unnecessary custom types
+- refactor: memory align `structs`
+- refactor: Debian packaging instructions
+
+## v2.0.0 - 2023-08-05
+
+- new feature: add `-c` or count flag to exit **TCPING** after a certain 
amount of probes specified by user thanks to @ravsii
+- new feature: add **BSD** support
+- new feature: add **Debian** package to make **TCPING** `apt installable`
+- fix: packet loss `NaN` when program terminated too quickly thanks to @ravsii
+- fix: random IP address selector index out of range bug
+- fix: display format of IPv4 embedded in IPv6 addresses
+- fix: time report bug. Everything is now accurate
+- fix: Enter key detection for Windows machines
+- refactor: complete overhaul of time calculation. **TCPING** now is hack-free 
when it comes to time handling thanks to @ravsii
+- refactor: memory align `structs`
+- refactor: improve code readability
+- refactor: refactor `stats struct` and extract user input to a separate 
`struct`
+- refactor: Enter key detection logic
+- refactor: name resolution handling. The maximum allowed time to wait for DNS 
response is now 2 seconds
+- refactor: and unify exit points thanks to @ravsii
+- tests: add more test special thanks to @ravsii
+- enhancement: add dependabot
+- docs: improve documentation
+
 ## v1.22.1 - 2023-5-14
 
 - new feature: implement JSON output thanks to @ravsii
Binary files old/tcping-2.0.0/Images/gifs/tcping.gif and 
new/tcping-2.4.0/Images/gifs/tcping.gif differ
Binary files old/tcping-2.0.0/Images/gifs/tcping_json_pretty.gif and 
new/tcping-2.4.0/Images/gifs/tcping_json_pretty.gif differ
Binary files old/tcping-2.0.0/Images/gifs/tcping_resolve.gif and 
new/tcping-2.4.0/Images/gifs/tcping_resolve.gif differ
Binary files old/tcping-2.0.0/Images/old/tcping.gif and 
new/tcping-2.4.0/Images/old/tcping.gif differ
Binary files old/tcping-2.0.0/Images/old/tcpingDockerDemo.gif and 
new/tcping-2.4.0/Images/old/tcpingDockerDemo.gif differ
Binary files old/tcping-2.0.0/Images/old/tcpingrflag.gif and 
new/tcping-2.4.0/Images/old/tcpingrflag.gif differ
Binary files old/tcping-2.0.0/Images/old/windowsVersion.png and 
new/tcping-2.4.0/Images/old/windowsVersion.png differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tcping-2.0.0/Images/tapes/tcping.tape 
new/tcping-2.4.0/Images/tapes/tcping.tape
--- old/tcping-2.0.0/Images/tapes/tcping.tape   2023-08-05 15:50:50.000000000 
+0200
+++ new/tcping-2.4.0/Images/tapes/tcping.tape   2023-09-10 16:07:16.000000000 
+0200
@@ -3,7 +3,7 @@
 Set Width 1300
 Set Height 800
 
-Type "./tcping example.com 80" 
+Type "tcping example.com 80" 
 Sleep 500ms  Enter
 
 Sleep 10s Ctrl+C
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tcping-2.0.0/Images/tapes/tcping_json_pretty.tape 
new/tcping-2.4.0/Images/tapes/tcping_json_pretty.tape
--- old/tcping-2.0.0/Images/tapes/tcping_json_pretty.tape       2023-08-05 
15:50:50.000000000 +0200
+++ new/tcping-2.4.0/Images/tapes/tcping_json_pretty.tape       2023-09-10 
16:07:16.000000000 +0200
@@ -3,7 +3,7 @@
 Set Width 1300
 Set Height 1000
 
-Type "./tcping example.com 80 -j --pretty" 
+Type "tcping example.com 80 -j --pretty" 
 Sleep 500ms  Enter
 
 Sleep 10s Ctrl+C
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tcping-2.0.0/Images/tapes/tcping_resolve.tape 
new/tcping-2.4.0/Images/tapes/tcping_resolve.tape
--- old/tcping-2.0.0/Images/tapes/tcping_resolve.tape   2023-08-05 
15:50:50.000000000 +0200
+++ new/tcping-2.4.0/Images/tapes/tcping_resolve.tape   2023-09-10 
16:07:16.000000000 +0200
@@ -3,7 +3,7 @@
 Set Width 1300
 Set Height 800
 
-Type "./tcping example.com 90 -r 3" Sleep 500ms  Enter
+Type "tcping example.com 90 -r 3" Sleep 500ms  Enter
 
 Sleep 15s Ctrl+C
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tcping-2.0.0/LICENSE new/tcping-2.4.0/LICENSE
--- old/tcping-2.0.0/LICENSE    2023-08-05 15:50:50.000000000 +0200
+++ new/tcping-2.4.0/LICENSE    2023-09-10 16:07:16.000000000 +0200
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2023 pouriya jamshidi
+Copyright (c) 2021-2023 pouriya jamshidi
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tcping-2.0.0/Makefile new/tcping-2.4.0/Makefile
--- old/tcping-2.0.0/Makefile   2023-08-05 15:50:50.000000000 +0200
+++ new/tcping-2.4.0/Makefile   2023-09-10 16:07:16.000000000 +0200
@@ -1,16 +1,15 @@
 EXEC_DIR = executables/
 TAPE_DIR = Images/tapes/
 GIFS_DIR = Images/gifs/
-SOURCE_FILES = $(tcping.go statsprinter.go)
 PACKAGE_NAME = tcping
-VERSION = 1.22.1
+VERSION = 2.0.0
 ARCHITECTURE = amd64
 DEB_PACKAGE_DIR = $(EXEC_DIR)/debian
 DEBIAN_DIR = $(DEB_PACKAGE_DIR)/DEBIAN
 CONTROL_FILE = $(DEBIAN_DIR)/control
 EXECUTABLE_PATH = $(EXEC_DIR)/tcping
 TARGET_EXECUTABLE_PATH = $(DEB_PACKAGE_DIR)/usr/bin/
-PACKAGE = $(PACKAGE_NAME)-$(VERSION)_$(ARCHITECTURE).deb
+PACKAGE = $(PACKAGE_NAME)_$(ARCHITECTURE).deb
 MAINTAINER = https://github.com/pouriyajamshidi
 DESCRIPTION = Ping TCP ports using tcping. Inspired by Linux's ping utility. 
Written in Go
 
@@ -29,7 +28,7 @@
        @mkdir -p $(TARGET_EXECUTABLE_PATH)
        
        @echo "[+] Building the Linux version"
-       @go build -ldflags "-s -w" -o $(EXEC_DIR)tcping $(SOURCE_FILES)
+       @go build -ldflags "-s -w" -o $(EXEC_DIR)tcping
 
        @echo "[+] Packaging the Linux version"
        @tar -czvf $(EXEC_DIR)tcping_Linux.tar.gz -C $(EXEC_DIR) tcping > 
/dev/null
@@ -40,7 +39,7 @@
 
        @echo
        @echo "[+] Building the static Linux version"
-       @env GOOS=linux CGO_ENABLED=0 go build -ldflags "-s -w" -o 
$(EXEC_DIR)tcping $(SOURCE_FILES)
+       @env GOOS=linux CGO_ENABLED=0 go build -ldflags "-s -w" -o 
$(EXEC_DIR)tcping
 
        @echo "[+] Packaging the static Linux version"
        @tar -czvf $(EXEC_DIR)tcping_Linux_static.tar.gz -C $(EXEC_DIR) tcping 
> /dev/null
@@ -50,21 +49,20 @@
        @echo "[+] Building the Debian package"
        @cp $(EXECUTABLE_PATH) $(TARGET_EXECUTABLE_PATH)
 
-       @echo "[+] Creating control file"
+       @echo "[+] Creating the Debian control file"
        @echo "Package: $(PACKAGE_NAME)" > $(CONTROL_FILE)
        @echo "Version: $(VERSION)" >> $(CONTROL_FILE)
        @echo "Section: custom" >> $(CONTROL_FILE)
        @echo "Priority: optional" >> $(CONTROL_FILE)
        @echo "Architecture: amd64" >> $(CONTROL_FILE)
        @echo "Essential: no" >> $(CONTROL_FILE)
-       @echo "Installed-Size: 2048" >> $(CONTROL_FILE)
        @echo "Maintainer: $(MAINTAINER)" >> $(CONTROL_FILE)
        @echo "Description: $(DESCRIPTION)" >> $(CONTROL_FILE)
 
-       @echo "[+] Building package"
+       @echo "[+] Building the Debian package"
        @dpkg-deb --build $(DEB_PACKAGE_DIR)
 
-       @echo "[+] Renaming package"
+       @echo "[+] Renaming the Debian package"
        @mv $(DEB_PACKAGE_DIR).deb $(EXEC_DIR)/$(PACKAGE)
 
        @echo "[+] Removing the static Linux binary"
@@ -72,7 +70,7 @@
 
        @echo
        @echo "[+] Building the Windows version"
-       @env GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -o 
$(EXEC_DIR)tcping.exe $(SOURCE_FILES)
+       @env GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -o 
$(EXEC_DIR)tcping.exe
 
        @echo "[+] Packaging the Windows version"
        @zip -j $(EXEC_DIR)tcping_Windows.zip $(EXEC_DIR)tcping.exe > /dev/null
@@ -83,7 +81,7 @@
 
        @echo
        @echo "[+] Building the MacOS version"
-       @env GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w" -o 
$(EXEC_DIR)tcping $(SOURCE_FILES)
+       @env GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w" -o 
$(EXEC_DIR)tcping
 
        @echo "[+] Packaging the MacOS version"
        @tar -czvf $(EXEC_DIR)tcping_MacOS.tar.gz -C $(EXEC_DIR) tcping > 
/dev/null
@@ -94,7 +92,7 @@
 
        @echo
        @echo "[+] Building the MacOS ARM version"
-       @env GOOS=darwin GOARCH=arm64 go build -ldflags "-s -w" -o 
$(EXEC_DIR)tcping $(SOURCE_FILES)
+       @env GOOS=darwin GOARCH=arm64 go build -ldflags "-s -w" -o 
$(EXEC_DIR)tcping
        
        @echo "[+] Packaging the MacOS ARM version"
        @tar -czvf $(EXEC_DIR)tcping_MacOS_ARM.tar.gz -C $(EXEC_DIR) tcping > 
/dev/null
@@ -105,11 +103,11 @@
 
        @echo
        @echo "[+] Building the FreeBSD version"
-       @env GOOS=freebsd GOARCH=amd64 go build -ldflags "-s -w" -o 
$(EXEC_DIR)tcping $(SOURCE_FILES)
+       @env GOOS=freebsd GOARCH=amd64 go build -ldflags "-s -w" -o 
$(EXEC_DIR)tcping
 
        @echo "[+] Packaging the FreeBSD AMD64 version"
-       @tar -czvf $(EXEC_DIR)tcping_freebsd.tar.gz -C $(EXEC_DIR) tcping > 
/dev/null
-       @sha256sum $(EXEC_DIR)tcping_freebsd.tar.gz
+       @tar -czvf $(EXEC_DIR)tcping_FreeBSD.tar.gz -C $(EXEC_DIR) tcping > 
/dev/null
+       @sha256sum $(EXEC_DIR)tcping_FreeBSD.tar.gz
 
        @echo "[+] Removing the FreeBSD binary"
        @rm $(EXEC_DIR)tcping
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tcping-2.0.0/README.md new/tcping-2.4.0/README.md
--- old/tcping-2.0.0/README.md  2023-08-05 15:50:50.000000000 +0200
+++ new/tcping-2.4.0/README.md  2023-09-10 16:07:16.000000000 +0200
@@ -41,11 +41,12 @@
     - [Basic usage](#basic-usage)
     - [Retry hostname lookup (`-r`) flag](#retry-hostname-lookup--r-flag)
     - [JSON output (`-j --pretty`) flag](#json-output--j---pretty-flag)
-  - [Usage](#usage)
-    - [On `Linux` and `macOS`](#on-linux-and-macos)
-    - [On `Windows`](#on-windows)
-    - [Using Docker](#using-docker)
   - [Download](#download)
+  - [Usage](#usage)
+    - [Linux - Debian and Ubuntu](#linux---debian-and-ubuntu)
+    - [Linux, BSD and mac OS](#linux-bsd-and-mac-os)
+    - [Windows](#windows)
+    - [Docker](#docker)
   - [Flags](#flags)
   - [Tips](#tips)
   - [Notes](#notes)
@@ -77,17 +78,84 @@
 
 ---
 
+## Download
+
+- ### 
[Windows](https://github.com/pouriyajamshidi/tcping/releases/latest/download/tcping_Windows.zip)
+
+- ### 
[Linux](https://github.com/pouriyajamshidi/tcping/releases/latest/download/tcping_Linux.tar.gz)
 - Also available through `brew` and [.deb package](#linux---debian-and-ubuntu)
+
+- ### 
[macOS](https://github.com/pouriyajamshidi/tcping/releases/latest/download/tcping_MacOS.tar.gz)
 - Also available through `brew`
+
+- ### [macOS M1 - 
ARM](https://github.com/pouriyajamshidi/tcping/releases/latest/download/tcping_MacOS_ARM.tar.gz)
 - Also available through `brew`
+
+- ### 
[FreeBSD](https://github.com/pouriyajamshidi/tcping/releases/latest/download/tcping_FreeBSD.tar.gz)
+
+When the download is complete, head to the [usage](#usage) section.
+
+**Alternatively**, you can:
+
+- Use the `Docker` images:
+
+  ```bash
+  docker pull pouriyajamshidi/tcping:latest
+  ```
+
+  > Image is also available on GitHub container registry:
+
+  ```bash
+  docker pull ghcr.io/pouriyajamshidi/tcping:latest
+  ```
+
+- Install using `go install`:
+
+  ```bash
+  go install github.com/pouriyajamshidi/tcping@latest
+  ```
+
+- Install using `brew`:
+
+  ```bash
+  brew install pouriyajamshidi/tap/tcping
+  ```
+
+- Or compile the code yourself by running the `make` command in the `tcping` 
directory:
+
+  ```bash
+  make build
+  ```
+
+  This will give you a compressed file with executables for all the supported 
operating systems inside the `executables` folder.
+
+---
+
 ## Usage
 
-Download TCPING for your operating system [here](#download), extract it. Then, 
follow the instructions below:
+Follow the instructions below for your operating system:
 
-- [Linux and macOS](#on-linux-and-macos)
-- [Windows](#on-windows)
-- [Docker images](#using-docker)
+- [Linux - Debian and Ubuntu](#linux---debian-and-ubuntu)
+- [Linux, BSD and macOS](#linux-bsd-and-mac-os)
+- [Windows](#windows)
+- [Docker images](#docker)
 
 Also check the [available flags here](#flags).
 
-### On `Linux` and `macOS`
+### Linux - Debian and Ubuntu
+
+On **Debian** and its flavors such as **Ubuntu**, download the `.deb` package:
+
+```bash
+wget 
https://github.com/pouriyajamshidi/tcping/releases/latest/download/tcping_amd64.deb
 -O /tmp/tcping.deb
+```
+
+And install it:
+
+```bash
+sudo apt install -y /tmp/tcping.deb
+```
+
+If you are using different Linux distros, proceed to [this 
section](#linux-bsd-and-mac-os).
+
+### Linux, BSD and mac OS
 
 Extract the file:
 
@@ -101,6 +169,10 @@
 # on Mac OS ARM
 #
 tar -xvf tcping_MacOS_ARM.tar.gz
+#
+# on BSD
+#
+tar -xvf tcping_FreeBSD.tar.gz
 ```
 
 Make the file executable:
@@ -123,7 +195,7 @@
 tcping 10.10.10.1 22
 ```
 
-### On `Windows`
+### Windows
 
 We recommend [Windows 
Terminal](https://apps.microsoft.com/store/detail/windows-terminal/9N0DX20HK701)
 for the best experience and proper colorization.
 
@@ -136,7 +208,7 @@
 tcping www.example.com 443 -r 10
 ```
 
-### Using Docker
+### Docker
 
 The Docker image can be used like:
 
@@ -150,70 +222,26 @@
 
 ---
 
-## Download
-
-- ### 
[Windows](https://github.com/pouriyajamshidi/tcping/releases/latest/download/tcping_Windows.zip)
-
-- ### 
[Linux](https://github.com/pouriyajamshidi/tcping/releases/latest/download/tcping_Linux.tar.gz)
 - Also available through `brew`
-
-- ### 
[macOS](https://github.com/pouriyajamshidi/tcping/releases/latest/download/tcping_MacOS.tar.gz)
 - Also available through `brew`
-
-- ### [macOS M1 - 
ARM](https://github.com/pouriyajamshidi/tcping/releases/latest/download/tcping_MacOS_ARM.tar.gz)
 - Also available through `brew`
-
-When the download is complete, head to the [usage](#usage) section.
-
-**Alternatively**, you can:
-
-- Use the `Docker` images:
-
-  ```bash
-  docker pull pouriyajamshidi/tcping:latest
-  ```
-
-  > Image is also available on GitHub container registry:
-
-  ```bash
-  docker pull ghcr.io/pouriyajamshidi/tcping:latest
-  ```
-
-- Install using `go install`:
-
-  ```bash
-  go install github.com/pouriyajamshidi/tcping@latest
-  ```
-
-- Install using `brew`:
-
-  ```bash
-  brew install pouriyajamshidi/tap/tcping
-  ```
-
-- Or compile the code yourself by running the `make` command in the `tcping` 
directory:
-
-  ```bash
-  make build
-  ```
-
-  This will give you a compressed file with executables for all the supported 
operating systems inside the `executables` folder.
-
----
-
 ## Flags
 
 The following flags are available to control the behavior of application:
 
-| Flag       | Description                                                     
                                                          |
-| ---------- | 
-------------------------------------------------------------------------------------------------------------------------
 |
-| `-4`       | Only use IPv4 addresses                                         
                                                          |
-| `-6`       | Only use IPv6 addresses                                         
                                                          |
-| `-r`       | Retry resolving target's hostname after `<n>` number of failed 
probes. e.g. -r 10 to retry after 10 failed probes         |
-| `-c`       | Stop after `<n>` probes, regardless of the result. By default, 
no limit will be applied  (available from version `v1.23`) |
-| `-j`       | Output in `JSON` format                                         
                                                          |
-| `--pretty` | Prettify the `JSON` output                                      
                                                          |
-| `-v`       | Print version                                                   
                                                          |
-| `-u`       | Check for updates                                               
                                                          |
+| Flag       | Description                                                     
                                                  |
+| ---------- | 
-----------------------------------------------------------------------------------------------------------------
 |
+| `-4`       | Only use IPv4 addresses                                         
                                                  |
+| `-6`       | Only use IPv6 addresses                                         
                                                  |
+| `-r`       | Retry resolving target's hostname after `<n>` number of failed 
probes. e.g. -r 10 to retry after 10 failed probes |
+| `-c`       | Stop after `<n>` probes, regardless of the result. By default, 
no limit will be applied                           |
+| `--db`     | Path and file name to store tcping output to sqlite database. 
e.g. `--db /tmp/tcping.db`                          |
+| `-t`       | Time to wait for a response, in seconds. Real number allowed. 0 
means infinite timeout                            |
+| `-i`       | Interval between sending probes                                 
                                                  |
+| `-I`       | Interface name to use for sending probes                        
                                                  |
+| `-j`       | Output in `JSON` format                                         
                                                  |
+| `--pretty` | Prettify the `JSON` output                                      
                                                  |
+| `-v`       | Print version                                                   
                                                  |
+| `-u`       | Check for updates                                               
                                                  |
 
-> Without specifying the `-4` and `-6` flags, tcping will use one randomly 
based on DNS lookups.
+> Without specifying the `-4` and `-6` flags, tcping will randomly select an 
IP address based on DNS lookups.
 
 ---
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tcping-2.0.0/db.go new/tcping-2.4.0/db.go
--- old/tcping-2.0.0/db.go      1970-01-01 01:00:00.000000000 +0100
+++ new/tcping-2.4.0/db.go      2023-09-10 16:07:16.000000000 +0200
@@ -0,0 +1,257 @@
+package main
+
+import (
+       "database/sql"
+       "fmt"
+       "math"
+       "os"
+       "strings"
+       "time"
+       "unicode"
+
+       _ "github.com/mattn/go-sqlite3"
+)
+
+type database struct {
+       db        *sql.DB
+       dbPath    string
+       tableName string
+}
+
+const (
+       eventTypeStatistics     = "statistics"
+       eventTypeHostnameChange = "hostname change"
+       tableSchema             = `
+-- Organized row names together for better readability
+CREATE TABLE %s (
+    id INTEGER PRIMARY KEY,
+    event_type TEXT NOT NULL, -- for the data type eg. statistics, hostname 
change
+    timestamp DATETIME,
+    addr TEXT,
+    hostname TEXT,
+    port INTEGER,
+    hostname_resolve_retries INTEGER,
+
+    hostname_changed_to TEXT,
+    hostname_change_time DATETIME,
+
+    latency_min REAL,
+    latency_avg REAL,
+    latency_max REAL,
+
+       total_duration TEXT,
+    start_time DATETIME,
+    end_time DATETIME,
+
+       never_succeed_probe INTEGER, -- value will be 1 if a probe never 
succeeded
+       never_failed_probe INTEGER, -- value will be 1 if a probe never failed
+    last_successful_probe DATETIME,
+    last_unsuccessful_probe DATETIME,
+
+    longest_uptime TEXT,
+    longest_uptime_start DATETIME,
+    longest_uptime_end DATETIME,
+
+    longest_downtime TEXT,
+    longest_downtime_start DATETIME,
+    longest_downtime_end DATETIME,
+
+    total_packets INTEGER,
+    total_packet_loss REAL,
+    total_successful_probes INTEGER,
+    total_unsuccessful_probes INTEGER,
+
+    total_uptime TEXT,
+    total_downtime TEXT
+);`
+)
+
+// newDb creates a newDb with the given path and returns `database` struct
+func newDb(args []string, dbPath string) database {
+       tableName := newTableName(args)
+       tableSchema := fmt.Sprintf(tableSchema, tableName)
+
+       db, err := sql.Open("sqlite3", dbPath)
+       if err != nil {
+               colorRed("\nError while creating the database %q: %s\n", 
dbPath, err)
+               os.Exit(1)
+       }
+
+       _, err = db.Exec(tableSchema)
+       if err != nil {
+               colorRed("\nError while writing to the database %q \nerr: 
%s\n", dbPath, err)
+               os.Exit(1)
+       }
+
+       return database{db, dbPath, tableName}
+}
+
+// newTableName will return correctly formatted table name
+// formatting the table name as 
"example_com_port_hour_minute_sec_day_month_year"
+// table name can't have '.' and can't start with numbers
+func newTableName(args []string) string {
+       tableName := fmt.Sprintf("%s_%s_%s", strings.ReplaceAll(args[0], ".", 
"_"), args[1], time.Now().Format("15_04_05_01_02_2006"))
+
+       if unicode.IsNumber(rune(tableName[0])) {
+               tableName = "_" + tableName
+       }
+
+       return tableName
+}
+
+// save will insert the table name and
+// saves the args to the database
+func (s database) save(query string, args ...any) error {
+       // inserting the table name
+       statement := fmt.Sprintf(query, s.tableName)
+
+       // saving to the db
+       _, err := s.db.Exec(statement, args...)
+
+       return err
+}
+
+// saveStats saves stats to the dedbase with proper fomatting
+func (s database) saveStats(stat stats) error {
+       // %s will be replaced by the table name
+       schema := `INSERT INTO %s (event_type, timestamp,
+               addr, hostname, port, hostname_resolve_retries,
+               total_successful_probes, total_unsuccessful_probes,
+               never_succeed_probe, never_failed_probe,
+               last_successful_probe, last_unsuccessful_probe,
+               total_packets, total_packet_loss,
+               total_uptime, total_downtime,
+               longest_uptime, longest_uptime_end, longest_uptime_start,
+               longest_downtime, longest_downtime_start, longest_downtime_end,
+               latency_min, latency_avg, latency_max,
+               start_time, end_time, total_duration)
+               VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 
?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
+
+       totalPackets := stat.totalSuccessfulProbes + 
stat.totalUnsuccessfulProbes
+       packetLoss := (float32(stat.totalUnsuccessfulProbes) / 
float32(totalPackets)) * 100
+       if math.IsNaN(float64(packetLoss)) {
+               packetLoss = 0
+       }
+
+       // If the time is zero, that means it never failed.
+       // In this case, the time should be empty instead of "0001-01-01 
00:00:00".
+       // Rather, it should be left empty.
+       lastSuccessfulProbe := stat.lastSuccessfulProbe.Format(timeFormat)
+       var neverSucceedProbe, neverFailedProbe bool
+       if stat.lastSuccessfulProbe.IsZero() {
+               lastSuccessfulProbe = ""
+               neverSucceedProbe = true
+       }
+       lastUnsuccessfulProbe := stat.lastUnsuccessfulProbe.Format(timeFormat)
+       if stat.lastUnsuccessfulProbe.IsZero() {
+               lastUnsuccessfulProbe = ""
+               neverFailedProbe = true
+       }
+
+       // if the longest uptime is emtpy, then the column should also be empty
+       var longestUptimeDuration, longestUptimeStart, longestUptimeEnd string
+       var longestDowntimeDuration, longestDowntimeStart, longestDowntimeEnd 
string
+       if stat.longestUptime.start.IsZero() {
+               longestUptimeDuration = "0s"
+               longestUptimeStart = ""
+               longestUptimeEnd = ""
+       } else {
+               longestUptimeDuration = stat.longestUptime.duration.String()
+               longestUptimeStart = stat.longestUptime.start.Format(timeFormat)
+               longestUptimeEnd = stat.longestUptime.end.Format(timeFormat)
+       }
+       if stat.longestDowntime.start.IsZero() {
+               longestDowntimeDuration = "0s"
+               longestDowntimeStart = ""
+               longestDowntimeEnd = ""
+       } else {
+               longestDowntimeDuration = stat.longestDowntime.duration.String()
+               longestDowntimeStart = 
stat.longestDowntime.start.Format(timeFormat)
+               longestDowntimeEnd = stat.longestDowntime.end.Format(timeFormat)
+       }
+
+       var totalDuration string
+       if stat.endTime.IsZero() {
+               totalDuration = time.Since(stat.startTime).String()
+       } else {
+               totalDuration = stat.endTime.Sub(stat.startTime).String()
+       }
+
+       err := s.save(schema,
+               eventTypeStatistics, time.Now().Format(timeFormat),
+               stat.userInput.ip.String(), stat.userInput.hostname, 
stat.userInput.port, stat.retriedHostnameLookups,
+               stat.totalSuccessfulProbes, stat.totalUnsuccessfulProbes,
+               neverSucceedProbe, neverFailedProbe,
+               lastSuccessfulProbe, lastUnsuccessfulProbe,
+               totalPackets, packetLoss,
+               stat.totalUptime.String(), stat.totalDowntime.String(),
+               longestUptimeDuration, longestUptimeStart, longestUptimeEnd,
+               longestDowntimeDuration, longestDowntimeStart, 
longestDowntimeEnd,
+               stat.rttResults.min, stat.rttResults.average, 
stat.rttResults.max,
+               stat.startTime.Format(timeFormat), 
stat.endTime.Format(timeFormat), totalDuration,
+       )
+
+       return err
+}
+
+// saveHostNameChang saves the hostname changes
+// in multiple rows with event_type = eventTypeHostnameChange
+func (s database) saveHostNameChange(h []hostnameChange) error {
+       // %s will be replaced by the table name
+       schema := `INSERT INTO %s
+       (event_type, hostname_changed_to, hostname_change_time)
+       VALUES (?, ?, ?)`
+
+       for _, host := range h {
+               if host.Addr.String() == "" {
+                       continue
+               }
+               err := s.save(schema, eventTypeHostnameChange, 
host.Addr.String(), host.When.Format(timeFormat))
+               if err != nil {
+                       return err
+               }
+       }
+
+       return nil
+}
+
+// printStart will let the user know the program is running by
+// printing a msg with the hostname, and port number to stdout
+func (s database) printStart(hostname string, port uint16) {
+       fmt.Printf("TCPinging %s on port %d\n", hostname, port)
+}
+
+// printStatistics saves the statistics to the given database
+// calls stat.printer.printError() on err
+func (s database) printStatistics(stat stats) {
+       err := s.saveStats(stat)
+       if err != nil {
+               s.printError("\nError while writing stats to the database 
%q\nerr: %s", s.dbPath, err)
+       }
+
+       // Hostname changes should be written during the final call.
+       // If the endtime is 0, it indicates that this is not the last call.
+       if !stat.endTime.IsZero() {
+               err = s.saveHostNameChange(stat.hostnameChanges)
+               if err != nil {
+                       s.printError("\nError while writing hostname changes to 
the database %q\nerr: %s", s.dbPath, err)
+               }
+
+       }
+
+       colorYellow("\nStatistics for %q have been saved to %q in the table 
%q\n", stat.userInput.hostname, s.dbPath, s.tableName)
+}
+
+// printError prints the err to the stderr and exits with status code 1
+func (s database) printError(format string, args ...any) {
+       fmt.Fprintf(os.Stderr, format, args...)
+       os.Exit(1)
+}
+
+// Satisfying the "printer" interface.
+func (s database) printProbeSuccess(hostname, ip string, port uint16, streak 
uint, rtt float32) {}
+func (s database) printProbeFail(hostname, ip string, port uint16, streak 
uint)                 {}
+func (s database) printRetryingToResolve(hostname string)                      
                 {}
+func (s database) printTotalDownTime(downtime time.Duration)                   
                 {}
+func (s database) printVersion()                                               
                 {}
+func (s database) printInfo(format string, args ...any)                        
                 {}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tcping-2.0.0/db_test.go new/tcping-2.4.0/db_test.go
--- old/tcping-2.0.0/db_test.go 1970-01-01 01:00:00.000000000 +0100
+++ new/tcping-2.4.0/db_test.go 2023-09-10 16:07:16.000000000 +0200
@@ -0,0 +1,233 @@
+package main
+
+import (
+       "fmt"
+       "net/netip"
+       "strconv"
+       "testing"
+       "time"
+)
+
+func TestNewDB(t *testing.T) {
+       arg := []string{"localhost", "8001"}
+       s := newDb(arg, ":memory:")
+       rows, err := s.db.Query("SELECT name FROM sqlite_master WHERE 
type='table';")
+       if err != nil {
+               t.Error(err)
+               return
+       }
+       defer rows.Close()
+       defer s.db.Close()
+
+       rows.Next()
+       var tblName string
+       err = rows.Scan(&tblName)
+       isNil(t, err)
+       Equals(t, tblName, s.tableName)
+}
+
+func TestDbSaveStats(t *testing.T) {
+       // There are many fields, so many things could go wrong; that's why 
this elaborate test.
+       arg := []string{"localhost", "8001"}
+       s := newDb(arg, ":memory:")
+       defer s.db.Close()
+
+       stat := mockStats()
+       err := s.saveStats(stat)
+       isNil(t, err)
+
+       query := `SELECT
+               addr, hostname, port, hostname_resolve_retries,
+               total_successful_probes, total_unsuccessful_probes,
+               never_succeed_probe, never_failed_probe,
+               last_successful_probe, last_unsuccessful_probe,
+               total_packets, total_packet_loss,
+               total_uptime, total_downtime,
+               longest_uptime, longest_uptime_end, longest_uptime_start,
+               longest_downtime, longest_downtime_start, longest_downtime_end,
+               latency_min, latency_avg, latency_max,
+               start_time, end_time, total_duration
+       FROM ` + fmt.Sprintf("%s WHERE event_type = '%s'", s.tableName, 
eventTypeStatistics)
+
+       rows, err := s.db.Query(query)
+       isNil(t, err)
+
+       if !rows.Next() {
+               t.Error("rows are empty; expted 1 row")
+               return
+       }
+
+       var (
+               addr, hostname, port                           string
+               hostNameResolveTries                           uint
+               totalSuccessfulProbes, totalUnsuccessfulProbes uint
+               neverSucceedProbe, neverFailedProbe            bool
+               lastSuccessfulProbe, lastUnsuccessfulProbe     time.Time
+               totalPackets                                   uint
+               totalPacketsLoss                               float32
+               totalUptime, totalDowntime                     string
+               longestUptime                                  string
+               longestUptimeStart, longestUptimeEnd           time.Time
+               longestDowntime                                string
+               longestDowntimeStart, longestDowntimeEnd       time.Time
+               lMin, lAvg, lMax                               float32
+               startTimestamp, endTimestamp                   time.Time
+               totalDuration                                  string
+       )
+
+       err = rows.Scan(
+               &addr, &hostname, &port, &hostNameResolveTries,
+               &totalSuccessfulProbes, &totalUnsuccessfulProbes,
+               &neverSucceedProbe, &neverFailedProbe,
+               &lastSuccessfulProbe, &lastUnsuccessfulProbe,
+               &totalPackets, &totalPacketsLoss,
+               &totalUptime, &totalDowntime,
+               &longestUptime, &longestUptimeStart, &longestUptimeEnd,
+               &longestDowntime, &longestDowntimeStart, &longestDowntimeEnd,
+               &lMin, &lAvg, &lMax,
+               &startTimestamp, &endTimestamp, &totalDuration,
+       )
+
+       isNil(t, err)
+       rows.Close()
+
+       t.Log("the line number will tell you where the error happend")
+       Equals(t, addr, stat.userInput.ip.String())
+       Equals(t, hostname, stat.userInput.hostname)
+       Equals(t, totalSuccessfulProbes, stat.totalSuccessfulProbes)
+       Equals(t, totalUnsuccessfulProbes, stat.totalUnsuccessfulProbes)
+       Equals(t, port, strconv.Itoa(int(stat.userInput.port)))
+
+       Equals(t, neverSucceedProbe, stat.lastSuccessfulProbe.IsZero())
+       Equals(t, neverFailedProbe, stat.lastUnsuccessfulProbe.IsZero())
+
+       Equals(t, lMin, stat.rttResults.min)
+       Equals(t, lAvg, stat.rttResults.average)
+       Equals(t, lMax, stat.rttResults.max)
+       Equals(t, startTimestamp.Format(timeFormat), 
stat.startTime.Format(timeFormat))
+       Equals(t, endTimestamp.Format(timeFormat), 
stat.endTime.Format(timeFormat))
+
+       actualDuration := stat.endTime.Sub(stat.startTime).String()
+       Equals(t, totalDuration, actualDuration)
+       Equals(t, totalUptime, stat.totalUptime.String())
+       Equals(t, totalDowntime, stat.totalDowntime.String())
+       Equals(t, totalPackets, 
stat.totalSuccessfulProbes+stat.totalUnsuccessfulProbes)
+
+       Equals(t, longestUptime, stat.longestUptime.duration.String())
+       Equals(t, longestUptimeStart.Format(timeFormat), 
stat.longestUptime.start.Format(timeFormat))
+       Equals(t, longestUptimeEnd.Format(timeFormat), 
stat.longestUptime.end.Format(timeFormat))
+
+       Equals(t, longestDowntime, stat.longestDowntime.duration.String())
+       Equals(t, longestDowntimeStart.Format(timeFormat), 
stat.longestDowntime.start.Format(timeFormat))
+       Equals(t, longestDowntimeEnd.Format(timeFormat), 
stat.longestDowntime.end.Format(timeFormat))
+
+}
+
+func TestSaveHostname(t *testing.T) {
+       // There are many fields, so many things could go wrong; that's why 
this elaborate test.
+       arg := []string{"localhost", "8001"}
+       s := newDb(arg, ":memory:")
+       defer s.db.Close()
+       stat := mockStats()
+
+       err := s.saveHostNameChange(stat.hostnameChanges)
+       isNil(t, err)
+       // testing the host names if they are properly written
+       query := `SELECT
+       hostname_changed_to, hostname_change_time
+       FROM ` + fmt.Sprintf("%s WHERE event_type IS '%s';", s.tableName, 
eventTypeHostnameChange)
+
+       rows, err := s.db.Query(query)
+       isNil(t, err)
+
+       idx := 0
+       for rows.Next() {
+               var hostName string
+               var cTime time.Time
+               err = rows.Scan(&hostName, &cTime)
+               isNil(t, err)
+
+               actualHost := stat.hostnameChanges[idx]
+               idx++
+               Equals(t, hostName, actualHost.Addr.String())
+               Equals(t, cTime.Format(timeFormat), 
actualHost.When.Format(timeFormat))
+       }
+       Equals(t, idx, len(stat.hostnameChanges))
+       rows.Close()
+}
+
+func hostNameChange() []hostnameChange {
+       ipAddresses := []string{
+               "192.168.1.1",
+               "10.0.0.1",
+               "172.16.0.1",
+               "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
+       }
+       var hostNames []hostnameChange
+       for i, ip := range ipAddresses {
+               host := hostnameChange{
+                       Addr: netip.MustParseAddr(ip),
+                       When: time.Now().Add(time.Duration(i) * time.Minute),
+               }
+               hostNames = append(hostNames, host)
+       }
+       return hostNames
+}
+
+func mockStats() stats {
+       stat := stats{
+               startTime:           time.Now(),
+               endTime:             time.Now().Add(10 * time.Minute),
+               lastSuccessfulProbe: time.Now().Add(1 * time.Minute),
+               // lastUnsuccessfulProbe is left with the default value "0" to 
simulate no probe failed
+               retriedHostnameLookups: 10,
+               longestUptime: longestTime{
+                       start:    time.Now().Add(20 * time.Second),
+                       end:      time.Now().Add(80 * time.Second),
+                       duration: time.Minute,
+               },
+               longestDowntime: longestTime{
+                       start:    time.Now().Add(20 * time.Second),
+                       end:      time.Now().Add(140 * time.Second),
+                       duration: time.Minute * 2,
+               },
+               userInput: userInput{
+                       ip:       netip.MustParseAddr("192.168.1.1"),
+                       hostname: "example.com",
+                       port:     1234,
+               },
+               totalUptime:             time.Second * 32,
+               totalDowntime:           time.Second * 60,
+               totalSuccessfulProbes:   201,
+               totalUnsuccessfulProbes: 123,
+               rttResults: rttResult{
+                       min:     2.832,
+                       average: 3.8123,
+                       max:     4.0932,
+               },
+
+               hostnameChanges: hostNameChange(),
+       }
+
+       return stat
+}
+
+// Equals compares two values.
+// This is for avoiding code duplications.
+func Equals[T comparable](t *testing.T, value, want T) {
+       t.Helper()
+       if want != value {
+               t.Errorf("wanted %v; got %v", want, value)
+               t.FailNow()
+       }
+}
+
+// isNil compares a value to nil, in some cases you may need to do `Equals(t, 
value, nil)`
+func isNil(t *testing.T, value any) {
+       t.Helper()
+
+       if value != nil {
+               t.Logf(`expected "%v" to be nil`, value)
+               t.FailNow()
+       }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tcping-2.0.0/go.mod new/tcping-2.4.0/go.mod
--- old/tcping-2.0.0/go.mod     2023-08-05 15:50:50.000000000 +0200
+++ new/tcping-2.4.0/go.mod     2023-09-10 16:07:16.000000000 +0200
@@ -1,10 +1,11 @@
-module github.com/pouriyajamshidi/tcping
+module github.com/pouriyajamshidi/tcping/v2
 
-go 1.20
+go 1.21
 
 require (
        github.com/google/go-github/v45 v45.2.0
        github.com/gookit/color v1.5.4
+       github.com/mattn/go-sqlite3 v1.14.17
        github.com/stretchr/testify v1.8.4
 )
 
@@ -13,7 +14,7 @@
        github.com/google/go-querystring v1.1.0 // indirect
        github.com/pmezard/go-difflib v1.0.0 // indirect
        github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
-       golang.org/x/crypto v0.9.0 // indirect
-       golang.org/x/sys v0.10.0 // indirect
+       golang.org/x/crypto v0.13.0 // indirect
+       golang.org/x/sys v0.12.0 // indirect
        gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tcping-2.0.0/go.sum new/tcping-2.4.0/go.sum
--- old/tcping-2.0.0/go.sum     2023-08-05 15:50:50.000000000 +0200
+++ new/tcping-2.4.0/go.sum     2023-09-10 16:07:16.000000000 +0200
@@ -8,17 +8,19 @@
 github.com/google/go-querystring v1.1.0/go.mod 
h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
 github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
 github.com/gookit/color v1.5.4/go.mod 
h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
+github.com/mattn/go-sqlite3 v1.14.17 
h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
+github.com/mattn/go-sqlite3 v1.14.17/go.mod 
h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
 github.com/pmezard/go-difflib v1.0.0 
h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod 
h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/stretchr/testify v1.8.4 
h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 github.com/stretchr/testify v1.8.4/go.mod 
h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e 
h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod 
h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
-golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
-golang.org/x/crypto v0.9.0/go.mod 
h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
+golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
+golang.org/x/crypto v0.13.0/go.mod 
h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
 golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 
h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
-golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
-golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod 
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 
h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod 
h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tcping-2.0.0/statsprinter.go 
new/tcping-2.4.0/statsprinter.go
--- old/tcping-2.0.0/statsprinter.go    2023-08-05 15:50:50.000000000 +0200
+++ new/tcping-2.4.0/statsprinter.go    2023-09-10 16:07:16.000000000 +0200
@@ -203,7 +203,7 @@
        e *json.Encoder
 }
 
-func newJsonPrinter(withIndent bool) *jsonPrinter {
+func newJSONPrinter(withIndent bool) *jsonPrinter {
        encoder := json.NewEncoder(os.Stdout)
        if withIndent {
                encoder.SetIndent("", "\t")
@@ -262,6 +262,7 @@
        HostnameChanges      []hostnameChange 
`json:"hostname_changes,omitempty"`
        IsIP                 *bool            `json:"is_ip,omitempty"`
        Port                 uint16           `json:"port,omitempty"`
+       Rtt                  float32          `json:"time,omitempty"`
 
        // Success is a special field from probe messages, containing 
information
        // whether request was successful or not.
@@ -357,6 +358,7 @@
                        Hostname:              hostname,
                        Addr:                  ip,
                        Port:                  port,
+                       Rtt:                   rtt,
                        IsIP:                  &t,
                        Success:               &t,
                        TotalSuccessfulProbes: streak,
@@ -365,11 +367,11 @@
 
        if hostname != "" {
                data.IsIP = &f
-               data.Message = fmt.Sprintf("Reply from %s (%s) on port %d",
-                       hostname, ip, port)
+               data.Message = fmt.Sprintf("Reply from %s (%s) on port %d 
time=%.3f",
+                       hostname, ip, port, rtt)
        } else {
-               data.Message = fmt.Sprintf("Reply from %s on port %d",
-                       ip, port)
+               data.Message = fmt.Sprintf("Reply from %s on port %d time=%.3f",
+                       ip, port, rtt)
        }
 
        p.print(data)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tcping-2.0.0/tcping.go new/tcping-2.4.0/tcping.go
--- old/tcping-2.0.0/tcping.go  2023-08-05 15:50:50.000000000 +0200
+++ new/tcping-2.4.0/tcping.go  2023-09-10 16:07:16.000000000 +0200
@@ -17,6 +17,13 @@
        "github.com/google/go-github/v45/github"
 )
 
+const (
+       version    = "2.4.0"
+       owner      = "pouriyajamshidi"
+       repo       = "tcping"
+       dnsTimeout = 2 * time.Second
+)
+
 // printer is a set of methods for printers to implement.
 //
 // Printers should NOT modify any existing data nor do any calculations.
@@ -95,16 +102,25 @@
 }
 
 type userInput struct {
-       ip                       ipAddress
+       ip                       netip.Addr
        hostname                 string
+       networkInterface         networkInterface
        retryHostnameLookupAfter uint // Retry resolving target's hostname 
after a certain number of failed requests
        probesBeforeQuit         uint
+       timeout                  time.Duration
+       intervalBetweenProbes    time.Duration
        port                     uint16
        useIPv4                  bool
        useIPv6                  bool
        shouldRetryResolve       bool
 }
 
+type networkInterface struct {
+       raddr  *net.TCPAddr
+       dialer net.Dialer
+       use    bool
+}
+
 type longestTime struct {
        start    time.Time
        end      time.Time
@@ -118,28 +134,11 @@
        hasResults bool
 }
 
-type replyMsg struct {
-       msg string
-       rtt float32
-}
-
 type hostnameChange struct {
        Addr netip.Addr `json:"addr,omitempty"`
        When time.Time  `json:"when,omitempty"`
 }
 
-type (
-       ipAddress = netip.Addr
-       cliArgs   = []string
-)
-
-const (
-       version    = "2.0.0"
-       owner      = "pouriyajamshidi"
-       repo       = "tcping"
-       dnsTimeout = 2 * time.Second
-)
-
 // signalHandler catches SIGINT and SIGTERM then prints tcping stats
 func signalHandler(tcpStats *stats) {
        sigChan := make(chan os.Signal, 1)
@@ -169,11 +168,11 @@
 // This should be used instead, as it makes
 // all the necessary calculations beforehand.
 func (tcpStats *stats) printStats() {
-       calcLongestUptime(tcpStats,
-               time.Duration(tcpStats.ongoingSuccessfulProbes)*time.Second)
-       calcLongestDowntime(tcpStats,
-               time.Duration(tcpStats.ongoingUnsuccessfulProbes)*time.Second)
-
+       if tcpStats.wasDown {
+               calcLongestDowntime(tcpStats, 
time.Since(tcpStats.startOfDowntime))
+       } else {
+               calcLongestUptime(tcpStats, time.Since(tcpStats.startOfUptime))
+       }
        tcpStats.rttResults = calcMinAvgMaxRttTime(tcpStats.rtt)
 
        tcpStats.printer.printStatistics(*tcpStats)
@@ -182,9 +181,15 @@
 // shutdown calculates endTime, prints statistics and calls os.Exit(0).
 // This should be used as a main exit-point.
 func shutdown(tcpStats *stats) {
-       totalRuntime := tcpStats.totalUnsuccessfulProbes + 
tcpStats.totalSuccessfulProbes
-       tcpStats.endTime = tcpStats.startTime.Add(time.Duration(totalRuntime) * 
time.Second)
+       tcpStats.endTime = time.Now()
        tcpStats.printStats()
+
+       // if the printer type is `database`, then close the db before
+       // exiting to prevent any memory leaks
+       if db, ok := tcpStats.printer.(database); ok {
+               db.db.Close()
+       }
+
        os.Exit(0)
 }
 
@@ -216,10 +221,14 @@
        useIPv6 := flag.Bool("6", false, "only use IPv6.")
        retryHostnameResolveAfter := flag.Uint("r", 0, "retry resolving 
target's hostname after <n> number of failed probes. e.g. -r 10 to retry after 
10 failed probes.")
        probesBeforeQuit := flag.Uint("c", 0, "stop after <n> probes, 
regardless of the result. By default, no limit will be applied.")
-       outputJson := flag.Bool("j", false, "output in JSON format.")
-       prettyJson := flag.Bool("pretty", false, "use indentation when using 
json output format. No effect without the '-j' flag.")
+       outputJSON := flag.Bool("j", false, "output in JSON format.")
+       prettyJSON := flag.Bool("pretty", false, "use indentation when using 
json output format. No effect without the '-j' flag.")
        showVersion := flag.Bool("v", false, "show version.")
        shouldCheckUpdates := flag.Bool("u", false, "check for updates.")
+       secondsBetweenProbes := flag.Float64("i", 1, "interval between sending 
probes. Real number allowed with dot as a decimal separator. The default is one 
second")
+       timeout := flag.Float64("t", 1, "time to wait for a response, in 
seconds. Real number allowed. 0 means infinite timeout.")
+       outputDb := flag.String("db", "", "path and file name to store tcping 
output to sqlite database.")
+       interfaceName := flag.String("I", "", "interface name or address")
 
        flag.CommandLine.Usage = usage
 
@@ -232,8 +241,10 @@
 
        // we need to set printers first, because they're used for
        // errors reporting and other output.
-       if *outputJson {
-               tcpStats.printer = newJsonPrinter(*prettyJson)
+       if *outputJSON {
+               tcpStats.printer = newJSONPrinter(*prettyJSON)
+       } else if *outputDb != "" {
+               tcpStats.printer = newDb(args, *outputDb)
        } else {
                tcpStats.printer = &planePrinter{}
        }
@@ -257,7 +268,7 @@
                usage()
        }
 
-       if *prettyJson && !*outputJson {
+       if *prettyJSON && !*outputJSON {
                tcpStats.printer.printError("--pretty has no effect without the 
-j flag.")
                usage()
        }
@@ -296,6 +307,13 @@
        tcpStats.userInput.ip = resolveHostname(tcpStats)
        tcpStats.startTime = time.Now()
        tcpStats.userInput.probesBeforeQuit = *probesBeforeQuit
+       tcpStats.userInput.timeout = secondsToDuration(*timeout)
+
+       tcpStats.userInput.intervalBetweenProbes = 
secondsToDuration(*secondsBetweenProbes)
+       if tcpStats.userInput.intervalBetweenProbes < 2*time.Millisecond {
+               tcpStats.printer.printError("Wait interval should be more than 
2 ms")
+               os.Exit(1)
+       }
 
        // this serves as a default starting value for tracking changes.
        tcpStats.hostnameChanges = []hostnameChange{
@@ -309,6 +327,10 @@
        if tcpStats.userInput.retryHostnameLookupAfter > 0 && !tcpStats.isIP {
                tcpStats.userInput.shouldRetryResolve = true
        }
+
+       if *interfaceName != "" {
+               tcpStats.userInput.networkInterface = 
newNetworkInterface(tcpStats, *interfaceName)
+       }
 }
 
 /*
@@ -316,17 +338,30 @@
 
 see: https://pkg.go.dev/flag
 */
-func permuteArgs(args cliArgs) {
+func permuteArgs(args []string) {
        var flagArgs []string
        var nonFlagArgs []string
 
        for i := 0; i < len(args); i++ {
                v := args[i]
                if v[0] == '-' {
-                       optionName := v[1:]
+                       var optionName string
+                       if v[1] == '-' {
+                               optionName = v[2:]
+                       } else {
+                               optionName = v[1:]
+                       }
                        switch optionName {
                        case "c":
                                fallthrough
+                       case "t":
+                               fallthrough
+                       case "db":
+                               fallthrough
+                       case "I":
+                               fallthrough
+                       case "i":
+                               fallthrough
                        case "r":
                                /* out of index */
                                if len(args) <= i+1 {
@@ -354,6 +389,80 @@
        }
 }
 
+// newNetworkInterface uses the 1st ip address of the interface
+// if any err occurs it calls `tcpStats.printer.printError` and exits with 
statuscode 1.
+// or return `networkInterface`
+func newNetworkInterface(tcpStats *stats, netInterface string) 
networkInterface {
+       var interfaceAddress net.IP
+
+       // if netinterface is the addres `interfaceAddress` var will not be 
`nil`
+       interfaceAddress = net.ParseIP(netInterface)
+
+       if interfaceAddress == nil {
+               ief, err := net.InterfaceByName(netInterface)
+               if err != nil {
+                       tcpStats.printer.printError("Interface %s not found", 
netInterface)
+                       os.Exit(1)
+               }
+
+               addrs, err := ief.Addrs()
+               if err != nil {
+                       tcpStats.printer.printError("Unable to get Interface 
addresses")
+                       os.Exit(1)
+               }
+
+               // Iterating through the available addresses to identify valid 
IP configurations
+               for _, addr := range addrs {
+                       if ip := addr.(*net.IPNet).IP; ip != nil {
+                               // netip.Addr
+                               nipAddr, err := netip.ParseAddr(ip.String())
+                               if err != nil {
+                                       continue
+                               }
+
+                               if nipAddr.Is4() && !tcpStats.userInput.useIPv6 
{
+                                       interfaceAddress = ip
+                                       break
+                               } else if nipAddr.Is6() && 
!tcpStats.userInput.useIPv4 {
+                                       if nipAddr.IsLinkLocalUnicast() {
+                                               continue
+                                       }
+                                       interfaceAddress = ip
+                                       break
+                               }
+                       }
+               }
+
+               if interfaceAddress == nil {
+                       tcpStats.printer.printError("Unable to get Interface's 
IP Address")
+                       os.Exit(1)
+               }
+       }
+
+       // Initializing a networkInterface struct and setting the 'use' field 
to true
+       ni := networkInterface{
+               use: true,
+       }
+
+       // remote address
+       ni.raddr = &net.TCPAddr{
+               IP:   net.ParseIP(tcpStats.userInput.ip.String()),
+               Port: int(tcpStats.userInput.port),
+       }
+
+       // local address
+       laddr := &net.TCPAddr{
+               IP: interfaceAddress,
+       }
+
+       ni.dialer = net.Dialer{
+               LocalAddr: laddr,
+               Timeout:   tcpStats.userInput.timeout, // Set the timeout 
duration
+       }
+
+       return ni
+}
+
 // checkLatestVersion checks for updates and print a message
 func checkLatestVersion(p printer) {
        c := github.NewClient(nil)
@@ -387,10 +496,10 @@
 }
 
 // selectResolvedIP returns a single IPv4 or IPv6 address from the net.IP 
slice of resolved addresses
-func selectResolvedIP(tcpStats *stats, ipAddrs []netip.Addr) ipAddress {
+func selectResolvedIP(tcpStats *stats, ipAddrs []netip.Addr) netip.Addr {
        var index int
        var ipList []netip.Addr
-       var ip ipAddress
+       var ip netip.Addr
 
        switch {
        case tcpStats.userInput.useIPv4:
@@ -406,12 +515,12 @@
                }
 
                if len(ipList) > 1 {
-                       index = rand.Intn(len(ipAddrs))
+                       index = rand.Intn(len(ipList))
                } else {
                        index = 0
                }
 
-               ip, _ = netip.ParseAddr(ipList[index].String())
+               ip, _ = netip.ParseAddr(ipList[index].Unmap().String())
 
        case tcpStats.userInput.useIPv6:
                for _, ip := range ipAddrs {
@@ -426,12 +535,12 @@
                }
 
                if len(ipList) > 1 {
-                       index = rand.Intn(len(ipAddrs))
+                       index = rand.Intn(len(ipList))
                } else {
                        index = 0
                }
 
-               ip, _ = netip.ParseAddr(ipList[index].String())
+               ip, _ = netip.ParseAddr(ipList[index].Unmap().String())
 
        default:
                if len(ipAddrs) > 1 {
@@ -440,14 +549,14 @@
                        index = 0
                }
 
-               ip, _ = netip.ParseAddr(ipAddrs[index].String())
+               ip, _ = netip.ParseAddr(ipAddrs[index].Unmap().String())
        }
 
        return ip
 }
 
 // resolveHostname handles hostname resolution with a timeout value of a second
-func resolveHostname(tcpStats *stats) ipAddress {
+func resolveHostname(tcpStats *stats) netip.Addr {
        ip, err := netip.ParseAddr(tcpStats.userInput.hostname)
        if err == nil {
                return ip
@@ -577,17 +686,31 @@
        return float32(nano) / float32(time.Millisecond)
 }
 
+// secondsToDuration returns the corresonding duration from seconds expressed 
with a float.
+func secondsToDuration(seconds float64) time.Duration {
+       return time.Duration(1000*seconds) * time.Millisecond
+}
+
+// maxDuration is the implementation of the math.Max function for 
time.Duration types.
+// returns the longest duration of x or y.
+func maxDuration(x, y time.Duration) time.Duration {
+       if x > y {
+               return x
+       }
+       return y
+}
+
 // handleConnError processes failed probes
-func (tcpStats *stats) handleConnError(connTime time.Time) {
+func (tcpStats *stats) handleConnError(connTime time.Time, elapsed 
time.Duration) {
        if !tcpStats.wasDown {
                tcpStats.startOfDowntime = connTime
-               calcLongestUptime(tcpStats,
-                       
time.Duration(tcpStats.ongoingSuccessfulProbes)*time.Second)
+               uptime := tcpStats.startOfDowntime.Sub(tcpStats.startOfUptime)
+               calcLongestUptime(tcpStats, uptime)
                tcpStats.startOfUptime = time.Time{}
                tcpStats.wasDown = true
        }
 
-       tcpStats.totalDowntime += time.Second
+       tcpStats.totalDowntime += elapsed
        tcpStats.lastUnsuccessfulProbe = connTime
        tcpStats.totalUnsuccessfulProbes += 1
        tcpStats.ongoingUnsuccessfulProbes += 1
@@ -601,10 +724,10 @@
 }
 
 // handleConnSuccess processes successful probes
-func (tcpStats *stats) handleConnSuccess(rtt float32, connTime time.Time) {
+func (tcpStats *stats) handleConnSuccess(rtt float32, connTime time.Time, 
elapsed time.Duration) {
        if tcpStats.wasDown {
                tcpStats.startOfUptime = connTime
-               downtime := 
time.Since(tcpStats.startOfDowntime).Truncate(time.Second)
+               downtime := tcpStats.startOfUptime.Sub(tcpStats.startOfDowntime)
                calcLongestDowntime(tcpStats, downtime)
                tcpStats.printer.printTotalDownTime(downtime)
                tcpStats.startOfDowntime = time.Time{}
@@ -617,7 +740,7 @@
                tcpStats.startOfUptime = connTime
        }
 
-       tcpStats.totalUptime += time.Second
+       tcpStats.totalUptime += elapsed
        tcpStats.lastSuccessfulProbe = connTime
        tcpStats.totalSuccessfulProbes += 1
        tcpStats.ongoingSuccessfulProbes += 1
@@ -634,30 +757,39 @@
 
 // tcping pings a host, TCP style
 func tcping(tcpStats *stats) {
-       IPAndPort := netip.AddrPortFrom(tcpStats.userInput.ip, 
tcpStats.userInput.port)
-
+       var err error
+       var conn net.Conn
        connStart := time.Now()
-       conn, err := net.DialTimeout("tcp", IPAndPort.String(), time.Second)
-       connEnd := time.Since(connStart)
-       rtt := nanoToMillisecond(connEnd.Nanoseconds())
+
+       if tcpStats.userInput.networkInterface.use {
+               // dialer already contains the timeout value
+               conn, err = 
tcpStats.userInput.networkInterface.dialer.Dial("tcp", 
tcpStats.userInput.networkInterface.raddr.String())
+       } else {
+               IPAndPort := netip.AddrPortFrom(tcpStats.userInput.ip, 
tcpStats.userInput.port)
+               conn, err = net.DialTimeout("tcp", IPAndPort.String(), 
tcpStats.userInput.timeout)
+       }
+
+       connDuration := time.Since(connStart)
+       rtt := nanoToMillisecond(connDuration.Nanoseconds())
+
+       elapsed := maxDuration(connDuration, 
tcpStats.userInput.intervalBetweenProbes)
 
        if err != nil {
-               tcpStats.handleConnError(connStart)
+               tcpStats.handleConnError(connStart, elapsed)
        } else {
-               tcpStats.handleConnSuccess(rtt, connStart)
+               tcpStats.handleConnSuccess(rtt, connStart, elapsed)
                conn.Close()
        }
-
        <-tcpStats.ticker.C
+
 }
 
 func main() {
-       tcpStats := &stats{
-               ticker: time.NewTicker(time.Second),
-       }
+       tcpStats := &stats{}
+       processUserInput(tcpStats)
+       tcpStats.ticker = 
time.NewTicker(tcpStats.userInput.intervalBetweenProbes)
        defer tcpStats.ticker.Stop()
 
-       processUserInput(tcpStats)
        signalHandler(tcpStats)
 
        tcpStats.printer.printStart(tcpStats.userInput.hostname, 
tcpStats.userInput.port)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tcping-2.0.0/tcping_test.go 
new/tcping-2.4.0/tcping_test.go
--- old/tcping-2.0.0/tcping_test.go     2023-08-05 15:50:50.000000000 +0200
+++ new/tcping-2.4.0/tcping_test.go     2023-09-10 16:07:16.000000000 +0200
@@ -18,8 +18,10 @@
        s := stats{
                printer: &dummyPrinter{},
                userInput: userInput{
-                       ip:   addr,
-                       port: 12345,
+                       ip:                    addr,
+                       port:                  12345,
+                       intervalBetweenProbes: time.Second,
+                       timeout:               time.Second,
                },
                ticker: time.NewTicker(time.Second),
        }
@@ -203,3 +205,37 @@
                }
        })
 }
+
+func TestSecondsToDuration(t *testing.T) {
+       tests := []struct {
+               name     string
+               seconds  float64
+               duration time.Duration
+       }{
+               {
+                       name:     "positive integer",
+                       seconds:  2,
+                       duration: 2 * time.Second,
+               },
+               {
+                       name:     "positive float",
+                       seconds:  1.5, // 1.5 = 3 / 2
+                       duration: time.Second * 3 / 2,
+               },
+               {
+                       name:     "negative integer",
+                       seconds:  -3,
+                       duration: -3 * time.Second,
+               },
+               {
+                       name:     "negative float",
+                       seconds:  -2.5, // -2.5 = -5 / 2
+                       duration: time.Second * -5 / 2,
+               },
+       }
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       assert.Equal(t, tt.duration, 
secondsToDuration(tt.seconds))
+               })
+       }
+}

++++++ vendor.tar.gz ++++++
++++ 268941 lines of diff (skipped)

Reply via email to