;; -*- Lisp -*- vim:filetype=lisp
;; some tests for RAWSOCK
;; clisp -E 1:1 -q -norc -i ../tests/tests -x '(run-test "../modules/rawsock/test" :logname "rawsock/test")'
;; relies on some functions in the syscalls module

(list (null (require "rawsock"))) (#-RAWSOCK NIL #+RAWSOCK T)
(listp (show (multiple-value-list (ext:module-info "rawsock" t)) :pretty t)) T

(progn
  (defun to-bytes (string)
    (ext:convert-string-to-bytes
     string #+UNICODE charset:ascii #-UNICODE :default))
  (defun from-bytes (vec &key (start 0) end)
    (ext:convert-string-from-bytes
     vec #+UNICODE charset:ascii #-UNICODE :default :start start :end end))
  (defun make-byte-vector (len)
    (make-array (etypecase len
                  (integer len)
                  (sequence (length len)))
                :element-type '(unsigned-byte 8) :initial-element 0))
  (defun show-he (he &aux (type (os:hostent-addrtype he)))
    (show (cons he
                (mapcar (lambda (ip)
                          (let* ((numeric (rawsock:convert-address type ip))
                                 (dotted (rawsock:convert-address
                                          type numeric)))
                            (assert (string= ip dotted))
                            (list :address ip numeric
                                  (handler-case
                                      (os:resolve-host-ipaddr numeric)
                                    (error (e) e)))))
                        (posix:hostent-addr-list he)))
          :pretty t))
  (defun host->ip (host)
    (let* ((he (os:resolve-host-ipaddr host)) all
           (host1 (os:hostent-name he)))
      (show-he he)
      (or (handler-case
              (loop :for ip :in (os:hostent-addr-list he)
                :for h1 = (os:resolve-host-ipaddr ip)
                :do (show-he h1) (push h1 all)
                :when (string-equal host1 (os:hostent-name h1))
                :return (show (cons (rawsock:convert-address
                                     (os:hostent-addrtype h1) ip)
                                    (os:hostent-addrtype h1))))
            (error (e) (princ-error e) nil))
          (if (eq host :default)
              (let* ((l (rawsock:ifaddrs :flags-and '(:up :running :broadcast)))
                     (i (find :INET (show l :pretty t)
                              :key (lambda (i)
                                     (rawsock:sockaddr-family
                                      (rawsock:ifaddrs-address i)))))
                     (sa (rawsock:ifaddrs-address
                          (or (show i :pretty t)
                              (error "~S: no running broadcast INET interface"
                                     'host->ip)))))
                (cons (subseq (rawsock:sockaddr-data (show sa :pretty t)) 2 6)
                      :INET))
              (error "~S(~S): no match in ~S and ~S" 'host->ip host he all)))))
  (defun host->sa (host &optional (port 0))
    (let ((ip+type (host->ip host)) sa
          (ve (make-byte-vector (nth-value 1 (rawsock::sockaddr-slot :data)))))
      (setf port (rawsock:htons port)
            (aref ve 0) (ldb #.(byte 8 0) port)
            (aref ve 1) (ldb #.(byte 8 8) port))
      (replace ve (car ip+type) :start1 2)
      (show ve)
      (setq sa (show (rawsock:make-sockaddr (cdr ip+type) ve)))
      (assert (equalp ve (rawsock:sockaddr-data sa)))
      (show (list 'rawsock:sockaddr-family
                  (multiple-value-list (rawsock:sockaddr-family sa))))
      sa))
  (defun local-sa-check (sock sa-local)
    (let* ((sa (rawsock:getsockname sock T))
           (data (rawsock:sockaddr-data sa)))
      (show sa)
      (show (list 'port (+ (aref data 1) (ash (aref data 0) 8))))
      (and (eq (rawsock:sockaddr-family sa) (rawsock:sockaddr-family sa-local))
           (equalp (subseq data 2)
                   (subseq (rawsock:sockaddr-data sa-local) 2)))))
  (defun maybe-bind (sock sa-local)
    ;; sometimes it is 127.0.0.1 and sometimes 127.0.1.1
    (show (subseq (rawsock:sockaddr-data sa-local) 2 6))
    (unless (equalp #(127 0) (subseq (rawsock:sockaddr-data sa-local) 2 4))
      (rawsock:bind sock sa-local)
      (not (local-sa-check sock sa-local))))
  (dolist (what '(nil :data :family))
    (show (cons (list 'rawsock::sockaddr-slot what)
                (multiple-value-list (rawsock::sockaddr-slot what)))))
  (defvar *sa-remote*) (defvar *sa-local*)
  (defvar *buffer* (make-byte-vector 1024))
  (defvar *sock*) (defvar *sock1*) (defvar *sock2*)
  (defvar *recv-ret*) (defvar *recvfrom-ret*) #-:win32 (defvar *read-ret*)
  (defun my-recvfrom (so ve sa &optional (status :output) &aux size)
    (when (socket:socket-status (cons so :input) 1)
      (multiple-value-bind (len sa-len sa1) (rawsock:recvfrom so ve sa)
        (assert (eq sa sa1))
        (setq size len)
        (show (list len sa-len sa (subseq ve 0 len)) :pretty t)))
    (assert (eq status (show (socket:socket-status so))))
    (rawsock:sock-close so)
    size)
  (defun my-bind (sock sa)
    (handler-case (rawsock:bind sock sa)
      (:no-error () (show (list 'my-bind sock sa) :pretty t) nil)
      (error (e) (show (list 'my-bind sock sa e (princ-to-string e)) :pretty t))))
  T) T

(progn (setq *sa-remote* (host->sa "ftp.gnu.org" 80)) T) T
(progn (setq *sa-local* (host->sa :default)) T) T

(catch 'type-error-handler
  (handler-bind ((type-error #'type-error-handler))
    (rawsock:socket :INET t nil)))
NIL
(catch 'type-error-handler
  (handler-bind ((type-error #'type-error-handler))
    (rawsock:socket t :STREAM nil)))
NIL
(catch 'type-error-handler
  (handler-bind ((type-error #'type-error-handler))
    (rawsock:socket :INET :STREAM t)))
NIL

(integerp (show (setq *sock* (rawsock:socket :INET :STREAM nil)))) T

(maybe-bind *sock* *sa-local*) NIL
(rawsock:connect *sock* *sa-remote*) NIL
(let ((p (rawsock:getpeername *sock* T)))
  (or (equalp p *sa-remote*)
      (list "fails when proxied" p *sa-remote*))) T

(listp (show (list (multiple-value-list (socket:socket-stream-local *sock*))
                   (multiple-value-list (socket:socket-stream-peer *sock*)))))
T

(rawsock:send *sock* (to-bytes (format nil "GET / HTTP/1.0~2%"))) 16

(ext:socket-status (list (cons *sock* :input)) 0 1) (:INPUT)

(let ((size (rawsock:recv *sock* *buffer*)))
  (show (setq *recv-ret* (list size (from-bytes *buffer* :end size))))
  (ext:socket-status *sock*))
:IO

#+unix (listp (show (rawsock:socket-option *sock* NIL) :pretty t)) T
#+unix (listp (show (rawsock:socket-option *sock* NIL :level :ALL) :pretty t))T

#+unix (setf (rawsock:socket-option *sock* NIL) '(:debug nil))
#+unix (:DEBUG NIL)

#+unix (setf (rawsock:socket-option *sock* NIL :level :all)
             '(:sol-socket (:debug nil)))
#+unix (:SOL-SOCKET (:DEBUG NIL))

#+unix (setf (rawsock:socket-option *sock* :debug) nil)
#+unix NIL

#+unix (setf (rawsock:socket-option *sock* :debug :level :all)
             '(:sol-socket nil))
#+unix (:SOL-SOCKET NIL)

(ext:socket-status *sock*) :OUTPUT
(ext:socket-stream-shutdown *sock* :io) NIL

(block foo
  (handler-bind ((rawsock:rawsock-error
                  (lambda (e)
                    (princ-error e)
                    (return-from foo (= (rawsock:rawsock-error-socket e)
                                        *sock*)))))
    (rawsock:sock-write *sock* #A((UNSIGNED-BYTE 8) (2) (1 2)))))
T

(rawsock:sock-close *sock*) 0

;; re-create the socket after it has been closed
(let ((so (rawsock:socket :INET :STREAM nil)))
  (show (list so *sock*))
  (= so *sock*))
T

(maybe-bind *sock* *sa-local*) NIL
(rawsock:connect *sock* *sa-remote*) NIL
(let ((p (rawsock:getpeername *sock* T)))
  (or (equalp p *sa-remote*)
      (list "fails when proxied" p *sa-remote*))) T

;; adding timeout to this socket-status merely shifts the wait
;; to the next form (sock-read) will wait instead,
(ext:socket-status (list (cons *sock* :input))) (:INPUT)

#-:win32 ;; on win32, read() cannot be called on a socket!
(let ((size (rawsock:sock-read *sock* *buffer*)))
  (show (setq *read-ret* (list size (from-bytes *buffer* :end size))))
  (ext:socket-status *sock*))
#-:win32 :OUTPUT

#-:win32 (equal *recv-ret* *read-ret*) #-:win32 T

(rawsock:sock-close *sock*) 0

;; re-create the socket after it has been closed
(let ((so (rawsock:socket :INET :STREAM nil)))
  (show (list so *sock*))
  (= so *sock*))
T

(maybe-bind *sock* *sa-local*) NIL
(rawsock:connect *sock* *sa-remote*) NIL

(let ((size (my-recvfrom *sock* *buffer* *sa-remote*)))
  (show (setq *recvfrom-ret* (list size (from-bytes *buffer* :end size))))
  (equal *recv-ret* *recvfrom-ret*))
T

(progn
  (setf (values *sock1* *sock2*)
        ;; :INET works on cygwin but not on Linux
        (rawsock:socketpair #+:win32 :INET #-:win32 :UNIX :STREAM nil))
  (show `((,(rawsock:getpeername *sock1* T) ,(rawsock:getsockname *sock1* T)
           ,*sock1*)
          (,(rawsock:getpeername *sock2* T) ,(rawsock:getsockname *sock2* T)
           ,*sock2*)
          ,(multiple-value-list (socket:socket-stream-local *sock1*))
          ,(multiple-value-list (socket:socket-stream-peer *sock1*))
          ,(multiple-value-list (socket:socket-stream-local *sock2*))
          ,(multiple-value-list (socket:socket-stream-peer *sock2*)))
        :pretty t)
  T) T

(let ((message "abazonk"))
  (rawsock:sock-write *sock1* (to-bytes message))
  (string= message (from-bytes *buffer*
                               :end (rawsock:sock-read *sock2* *buffer*))))
T

#-:win32 ;; on win32, read()/write() cannot be called on a socket!
(let* ((message '("I" "love" "you"))
       (char-num (reduce #'+ message :key #'length))
       (buf1 (map 'vector #'to-bytes message))
       (buf2 (map 'vector #'make-byte-vector message)))
  (show (list buf1 buf2))
  ;; assume ASCII-compatible encoding
  (assert (= char-num (rawsock:sock-write *sock1* buf1)))
  (assert (= char-num (rawsock:sock-read *sock2* buf2)))
  (list (equalp buf1 buf2)
        (equalp message (map 'list #'from-bytes buf2))))
#-:win32 (T T)

(list (ext:socket-status *sock1*) (ext:socket-status *sock2*))
(:OUTPUT :OUTPUT)

(list (rawsock:sock-close *sock1*) (rawsock:sock-close *sock2*)) (0 0)

;; lisp implementation of socketpair
(progn (setq *sock1* (rawsock:socket :INET :STREAM nil)
             *sock2* (rawsock:socket :INET :STREAM nil)
             *sa-local* (host->sa :default))
       (rawsock:bind *sock2* *sa-local*)
       (rawsock:sock-listen *sock2* 1)
       ;; figure out what port was assigned:
       (rawsock:getsockname *sock2* *sa-local*)
       NIL)
NIL

(socket:socket-status *sock2* 0) NIL

(rawsock:connect *sock1* *sa-local*) NIL

(socket:socket-status *sock2*) :INPUT

(progn (setq *sock* (rawsock:accept *sock2* *sa-local*))
       (socket:socket-status *sock1*))
:OUTPUT
(socket:socket-status *sock*) :OUTPUT

(rawsock:send *sock* (to-bytes "dog bites man")) 13
(rawsock:recv *sock1* *buffer* :start 17 :end 30) 13
(from-bytes *buffer* :start 17 :end 30) "dog bites man"

(rawsock:send *sock1* (to-bytes "man bites dog")) 13
(rawsock:recv *sock* *buffer* :start 1000 :end 1013) 13
(from-bytes *buffer* :start 1000 :end 1013) "man bites dog"

(rawsock:sock-close *sock*)  0
(rawsock:sock-close *sock1*) 0
(rawsock:sock-close *sock2*) 0

;; message
(when (and (fboundp 'rawsock:sendmsg) (fboundp 'rawsock:recvmsg))
  (let* ((message '("man" "bites" "dog"))
         (len (reduce #'+ message :key #'length))
         (message1
          (rawsock:make-message :addr *sa-local*
                                :iovec (map 'vector #'to-bytes message)))
         (message2
          (rawsock:make-message :addr (rawsock:make-sockaddr :inet)
                                :iovec (map 'vector #'make-byte-vector
                                            message))))
    (show (list :before message1 message2) :pretty t)
    ;; new connectionless-mode sockets
    (setq *sock1* (rawsock:socket :INET :DGRAM nil)
          *sock2* (rawsock:socket :INET :DGRAM nil))
    (rawsock:bind *sock2* *sa-local*)
    (assert (= len (rawsock:sendmsg *sock1* message1)))
    (assert (= len (rawsock:recvmsg *sock2* message2)))
    (show (list :after message1 message2) :pretty t)
    (assert (equalp (rawsock:message-iovec message1)
                    (rawsock:message-iovec message2)))
    (when (fboundp 'rawsock:getnameinfo)
      (show (list 'rawsock:getnameinfo
                  (multiple-value-list
                   (rawsock:getnameinfo (rawsock:message-addr message1)))
                  (multiple-value-list
                   (rawsock:getnameinfo (rawsock:message-addr message2))))))
    ;; I get "(EFAULT): Bad address" on Linux 2.6.14-1.1637_FC4
    ;; (when (fboundp 'rawsock:sockatmark)
    ;;   (show (list 'rawsock:sockatmark
    ;;               (rawsock:sockatmark *sock1*)
    ;;               (rawsock:sockatmark *sock2*))))
    (rawsock:sock-close *sock1*) (rawsock:sock-close *sock2*))
  nil)
NIL

#-win32 (rawsock:sock-write 1 (to-bytes "foo")) #-win32 3

;;;; root only??
(integerp (show (setq *sock* (rawsock:socket :INET :DGRAM 0)))) T

(progn
  (setq *sa-remote*
        (rawsock:make-sockaddr
         :inet '(101 116 104 48 0 0 0 0 0 0 0 0 0 0 0 0)))
  (show *sa-remote*)
  ;;(show (posix:resolve-host-ipaddr
  ;;       (make-array 4 :element-type '(unsigned-byte 8)
  ;;                   :displaced-to (rawsock:sockaddr-data *sa-remote*))))
  (fill *buffer* 0)
  (loop :for (x y) :in '((12 8)(14 #x45) (17 40)(22 #x40)(23 6)(26 10)(29 10))
    :do (setf (aref *buffer* x) y))
  (rawsock:ipcsum *buffer*))
51056

(rawsock:sendto *sock* *buffer* *sa-remote*)
1024

(rawsock:sock-close *sock*) 0

(or (not (fboundp 'rawsock:protocol))
    (rawsock:protocol-p (show (rawsock:protocol "IP") :pretty t))) T
(or (not (fboundp 'rawsock:protocol))
    (listp (show (rawsock:protocol) :pretty t))) T
(or (not (fboundp 'rawsock:network))
    (listp (show (rawsock:network) :pretty t))) T
(or (not (fboundp 'rawsock:if-name-index))
    (listp (show (rawsock:if-name-index) :pretty t))) T

(when (fboundp 'rawsock:if-name-index)
  (dolist (i-n (rawsock:if-name-index))
    (assert (= (rawsock:if-name-index (cdr i-n)) (car i-n)))
    (assert (string= (rawsock:if-name-index (car i-n)) (cdr i-n)))))
NIL

(or (not (fboundp 'rawsock:ifaddrs))
    (listp (show (rawsock:ifaddrs) :pretty t))) T
(or (not (fboundp 'rawsock:ifaddrs))
    (listp (show (rawsock:ifaddrs :flags-and '(:BROADCAST)) :pretty t))) T
(or (not (fboundp 'rawsock:ifaddrs))
    (flet ((drop-data (l)
             (dolist (i l l)
               (setf (rawsock:ifaddrs-data i) nil))))
      (equalp (drop-data (rawsock:ifaddrs :flags-and '(:BROADCAST)))
              (drop-data (rawsock:ifaddrs :flags-or '(:BROADCAST)))))) T
(or (not (fboundp 'rawsock:ifaddrs))
    (listp (show (rawsock:ifaddrs :flags-or '(:BROADCAST :MULTICAST)
                                  :flags-and '(:UP :RUNNING)) :pretty t))) T
(or (not (fboundp 'rawsock:ifaddrs))
    (null (rawsock:ifaddrs :flags-and '(:LOOPBACK :BROADCAST)))) T

(or (not (fboundp 'rawsock:getaddrinfo))
    (listp (show (rawsock:getaddrinfo :node "localhost") :pretty t))) T
(or (not (fboundp 'rawsock:getaddrinfo))
    (listp (show (rawsock:getaddrinfo :service "21") :pretty t))) T
(or (not (fboundp 'rawsock:getaddrinfo))
    (listp (show (rawsock:getaddrinfo :service "www") :pretty t))) T

(or (not (fboundp 'rawsock:getnameinfo))
    (listp (show (multiple-value-list (rawsock:getnameinfo *sa-remote*))))) T
(or (not (fboundp 'rawsock:getnameinfo))
    (listp (show (multiple-value-list (rawsock:getnameinfo *sa-local*))))) T

#+unix                          ; for Don Cohen
(when (and (string-equal (posix:uname-sysname (posix:uname)) "linux")
           (zerop (posix:uid))) ; root?
  (show (setq *sock* (rawsock:socket :INET :PACKET 3)))
  (show (setq *sa-local* (rawsock:make-sockaddr :PACKET)))
  (my-recvfrom *sock* *buffer* *sa-local*)
  nil)
#+unix NIL

;; https://sourceforge.net/p/clisp/mailman/message/14250678/
#+unix
(when (and (string-equal (posix:uname-sysname (posix:uname)) "linux")
           (zerop (posix:uid))) ; root?
  (show (setq *sock* (rawsock:socket :INET :RAW :IPPROTO-ICMP)))
  (shell "ping -c 1 localhost") ; generate one icmp packet
  (show (setq *sa-local* (rawsock:make-sockaddr :PACKET 20)))
  (my-recvfrom *sock* *buffer* *sa-local* :IO)
  nil)
#+unix NIL

;; https://sourceforge.net/p/clisp/mailman/message/14250705/
(progn
  (show (list '*sa-local* (setq *sa-local* (host->sa :default 7777))) :pretty t)
  (show (list '*sock* (setq *sock* (rawsock:socket :INET :DGRAM nil))))
  (rawsock:bind *sock* *sa-local*)
  (loop :for i :below 256 :do (setf (aref *buffer* i) i))
  (rawsock:sendto *sock* *buffer* *sa-local* :end 256))
256

(ext:socket-status (list (cons *sock* :input))) (:INPUT)

(let* ((buf (make-byte-vector 256))
       (sa1 (rawsock:make-sockaddr 0))
       (len (my-recvfrom *sock* buf sa1)))
  (assert (equalp sa1 *sa-local*))
  (loop :for i :below 256 :do (assert (= (aref buf i) i)))
  len)
256

;; os:hostid sometimes appears to be a mangled IP address
(and (fboundp 'os:hostid)
     (let ((id (os:hostid)))
       (listp (show (cons (if (< 32 (integer-length id))
                              (rawsock:convert-address :inet6 id)
                              (rawsock:convert-address :inet id))
                          (os:hostent-addr-list
                           (os:resolve-host-ipaddr :default)))))))
T

(symbols-cleanup
 '(to-bytes from-bytes make-byte-vector show-he host->ip host->sa
   local-sa-check maybe-bind my-recvfrom my-bind
   *sa-remote* *sa-local* *buffer* *sock* *sock1* *sock2*
   *recv-ret* *recvfrom-ret* *read-ret*))
()
