package client import ( "crypto/md5" "encoding/hex" "fmt" "io" "net" "strings" ) type ( Client struct { addr string } Payload interface { WriteTo(server io.Writer) (int64, error) Len() int64 } PayloadStrict struct { data string } PayloadCloser struct { data string closer byte } Command byte ) const ( Send Command = 1 ) func (p PayloadCloser) WriteTo(server io.Writer) (int64, error) { n1, err := server.Write([]byte(p.data)) if err != nil { return int64(n1), err } n2, err := server.Write([]byte{p.closer}) if err != nil { return int64(n1 + n2), err } return int64(n1 + n2), nil } func (p PayloadCloser) Len() int64 { return int64(len(p.data) + 1) } func (p PayloadStrict) WriteTo(server io.Writer) (int64, error) { n, err := server.Write([]byte(p.data)) return int64(n), err } func (p PayloadStrict) Len() int64 { return int64(len(p.data)) } func New(ip string, port int) *Client { return &Client{ addr: fmt.Sprintf("%s:%d", ip, port), } } func (c *Client) SendSMS(phoneNumber, msg string) error { if len(msg) > 140 { return fmt.Errorf("message too long") } conn, err := net.Dial("tcp", c.addr) if err != nil { return fmt.Errorf("failed to connect: %w", err) } defer conn.Close() m := md5.New() m.Write([]byte(msg)) hash := hex.EncodeToString(m.Sum(nil)) if err := c.Send(conn, Send, PayloadStrict{phoneNumber}, PayloadStrict{hash}, PayloadCloser{msg, 1}); err != nil { return fmt.Errorf("failed to send data: %w", err) } resp, err := c.ReadAll(conn) if err != nil { return err } if len(resp) == 0 { return fmt.Errorf("empty message: the server stop responding") } fmt.Println(resp) return nil } func (c *Client) Send(server net.Conn, cmd Command, payload ...Payload) error { if err := c.SendCommand(server, cmd); err != nil { return err } for _, data := range payload { n, err := data.WriteTo(server) if err != nil { return fmt.Errorf("failed to send phone number: %w", err) } if n != data.Len() { return fmt.Errorf("network error: broken pipe") } } return nil } func (c *Client) SendCommand(server net.Conn, cmd Command) error { n, err := server.Write([]byte{byte(cmd)}) if err != nil { return fmt.Errorf("failed to send command: %w", err) } if n != 1 { return fmt.Errorf("failed to send command: broken pipe") } return nil } func (c *Client) ReadAll(server net.Conn) (string, error) { var err error var data []byte var message string waiting := true for { data, err = io.ReadAll(server) if err != nil { return "", fmt.Errorf("failed to received response: %w", err) } if len(data) == 0 { if !waiting { break } } else { waiting = false message += string(data) } } if strings.HasPrefix(message, "error:") { return "", fmt.Errorf("%s", message) } return message, nil }