Initial commit

This commit is contained in:
NortPerm
2023-10-30 15:21:12 +03:00
committed by GitHub
commit 13e2575b4e
149 changed files with 3961 additions and 0 deletions

0
hw11_telnet_client/.sync Normal file
View File

View File

@@ -0,0 +1,109 @@
## Домашнее задание №11 «Клиент TELNET»
Необходимо реализовать крайне примитивный TELNET клиент
(без поддержки команд, опций и протокола в целом).
Примеры вызовов:
```bash
$ go-telnet --timeout=10s host port
$ go-telnet mysite.ru 8080
$ go-telnet --timeout=3s 1.1.1.1 123
```
* Программа должна подключаться к указанному хосту (IP или доменное имя) и порту по протоколу TCP.
* После подключения STDIN программы должен записываться в сокет,
а данные, полученные из сокета, должны выводиться в STDOUT - всё происходит конкурентно.
* Опционально в программу можно передать таймаут на подключение к серверу
(через аргумент `--timeout`) - по умолчанию `10s`.
* При нажатии `Ctrl+D` программа должна закрывать сокет и завершаться с сообщением.
* При получении `SIGINT` программа должна завершать свою работу.
* Если сокет закрылся со стороны сервера, то при следующей попытке отправить сообщение программа
должна завершаться (допускается завершать программу после "неудачной" отправки нескольких сообщений).
* При подключении к несуществующему серверу, программа должна завершаться с ошибкой соединения/таймаута.
При необходимости можно выделять дополнительные функции / ошибки.
Примеры работы:
1) Сервер обрывает соединение
```bash
$ nc -l localhost 4242
Hello from NC
I'm telnet client
Bye, client!
^C
```
```bash
$ go-telnet --timeout=5s localhost 4242
...Connected to localhost:4242
Hello from NC
I'm telnet client
Bye, client!
Bye-bye
...Connection was closed by peer
```
Здесь сообщения
```
Hello from NC
Bye, client!
```
и операция Ctrl+C (Unix) относятся к `nc`,
а сообщения
```
I'm telnet client
Bye-bye
```
относятся к `go-telnet`.
2) Клиент завершает ввод
```bash
$ go-telnet localhost 4242
...Connected to localhost:4242
I
will be
back!
^D
...EOF
```
```bash
$ nc -l localhost 4242
I
will be
back!
```
Здесь сообщения
```
I
will be
back!
```
и операция Ctrl+D (Unix) относятся к `go-telnet`,
Сообщения
```
...Connected to localhost:4242
...Connection was closed by peer
...EOF
```
являются служебными.
Они **выводятся в STDERR** и не тестируются `test.sh`, их формат на усмотрение автора.
### Критерии оценки
- Пайплайн зелёный - 4 балла
- Добавлены юнит-тесты - до 2 баллов
- Понятность и чистота кода - до 4 баллов
#### Зачёт от 7 баллов
### Подсказки
- Для ручного тестирования рекомендуется использовать nc (как в `test.sh`).
- `flag.DurationVar`
- `net.JoinHostPort`, `net.DialTimeout`
- `bufio` / `io`
- `signal.NotifyContext`
- https://stackoverflow.com/questions/51317968/write-on-a-closed-net-conn-but-returned-nil-error

11
hw11_telnet_client/go.mod Normal file
View File

@@ -0,0 +1,11 @@
module github.com/fixme_my_friend/hw11_telnet_client
go 1.19
require github.com/stretchr/testify v1.7.0
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)

13
hw11_telnet_client/go.sum Normal file
View File

@@ -0,0 +1,13 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -0,0 +1,6 @@
package main
func main() {
// Place your code here,
// P.S. Do not rush to throw context down, think think if it is useful with blocking operation?
}

View File

@@ -0,0 +1,21 @@
package main
import (
"io"
"time"
)
type TelnetClient interface {
Connect() error
io.Closer
Send() error
Receive() error
}
func NewTelnetClient(address string, timeout time.Duration, in io.ReadCloser, out io.Writer) TelnetClient {
// Place your code here.
return nil
}
// Place your code here.
// P.S. Author's solution takes no more than 50 lines.

View File

@@ -0,0 +1,65 @@
package main
import (
"bytes"
"io"
"net"
"sync"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestTelnetClient(t *testing.T) {
t.Run("basic", func(t *testing.T) {
l, err := net.Listen("tcp", "127.0.0.1:")
require.NoError(t, err)
defer func() { require.NoError(t, l.Close()) }()
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
in := &bytes.Buffer{}
out := &bytes.Buffer{}
timeout, err := time.ParseDuration("10s")
require.NoError(t, err)
client := NewTelnetClient(l.Addr().String(), timeout, io.NopCloser(in), out)
require.NoError(t, client.Connect())
defer func() { require.NoError(t, client.Close()) }()
in.WriteString("hello\n")
err = client.Send()
require.NoError(t, err)
err = client.Receive()
require.NoError(t, err)
require.Equal(t, "world\n", out.String())
}()
go func() {
defer wg.Done()
conn, err := l.Accept()
require.NoError(t, err)
require.NotNil(t, conn)
defer func() { require.NoError(t, conn.Close()) }()
request := make([]byte, 1024)
n, err := conn.Read(request)
require.NoError(t, err)
require.Equal(t, "hello\n", string(request)[:n])
n, err = conn.Write([]byte("world\n"))
require.NoError(t, err)
require.NotEqual(t, 0, n)
}()
wg.Wait()
})
}

34
hw11_telnet_client/test.sh Executable file
View File

@@ -0,0 +1,34 @@
#!/usr/bin/env bash
set -xeuo pipefail
go build -o go-telnet
(echo -e "Hello\nFrom\nNC\n" && cat 2>/dev/null) | nc -l localhost 4242 >/tmp/nc.out &
NC_PID=$!
sleep 1
(echo -e "I\nam\nTELNET client\n" && cat 2>/dev/null) | ./go-telnet --timeout=5s localhost 4242 >/tmp/telnet.out &
TL_PID=$!
sleep 5
kill ${TL_PID} 2>/dev/null || true
kill ${NC_PID} 2>/dev/null || true
function fileEquals() {
local fileData
fileData=$(cat "$1")
[ "${fileData}" = "${2}" ] || (echo -e "unexpected output, $1:\n${fileData}" && exit 1)
}
expected_nc_out='I
am
TELNET client'
fileEquals /tmp/nc.out "${expected_nc_out}"
expected_telnet_out='Hello
From
NC'
fileEquals /tmp/telnet.out "${expected_telnet_out}"
rm -f go-telnet
echo "PASS"