Connect establishes a connection to the NTRIP caster. If a proxy is configured, it first establishes an HTTP CONNECT tunnel through the proxy with TLS client certificate authentication. Then it performs the NTRIP handshake (v1 SOURCE or v2 HTTP POST) and waits for the caster's acceptance response be
(ctx context.Context)
| 121 | // handshake (v1 SOURCE or v2 HTTP POST) and waits for the caster's |
| 122 | // acceptance response before allowing data writes. |
| 123 | func (c *Client) Connect(ctx context.Context) error { |
| 124 | conn, err := c.dial(ctx) |
| 125 | if err != nil { |
| 126 | return fmt.Errorf("dialing caster: %w", err) |
| 127 | } |
| 128 | |
| 129 | if c.config.Version == 2 { |
| 130 | err = c.postHandshake(conn) |
| 131 | } else { |
| 132 | err = c.sourceHandshake(conn) |
| 133 | } |
| 134 | if err != nil { |
| 135 | conn.Close() |
| 136 | return fmt.Errorf("NTRIP handshake: %w", err) |
| 137 | } |
| 138 | |
| 139 | // Wait for caster response before allowing data writes. |
| 140 | // v1 casters reply "ICY 200 OK", v2 casters reply "HTTP/1.1 200 OK". |
| 141 | if err := conn.SetReadDeadline(time.Now().Add(5 * time.Second)); err != nil { |
| 142 | conn.Close() |
| 143 | return fmt.Errorf("setting read deadline: %w", err) |
| 144 | } |
| 145 | // TCP may split the response across segments, so read the full status line |
| 146 | // rather than relying on whatever a single Read happens to return. |
| 147 | reader := bufio.NewReader(conn) |
| 148 | statusLine, err := reader.ReadString('\n') |
| 149 | if err != nil { |
| 150 | conn.Close() |
| 151 | return fmt.Errorf("reading caster response: %w", err) |
| 152 | } |
| 153 | if err := conn.SetReadDeadline(time.Time{}); err != nil { |
| 154 | conn.Close() |
| 155 | return fmt.Errorf("clearing read deadline: %w", err) |
| 156 | } |
| 157 | resp := strings.TrimSpace(statusLine) |
| 158 | c.logger.Info("received from caster", "data", resp) |
| 159 | // The status line is "ICY 200 OK" (v1) or "HTTP/1.1 200 OK" (v2); the code |
| 160 | // is the second field. Check it exactly rather than substring-matching |
| 161 | // "200", which would also match "1200", "200ms", etc. |
| 162 | fields := strings.Fields(resp) |
| 163 | if len(fields) < 2 || fields[1] != "200" { |
| 164 | conn.Close() |
| 165 | return fmt.Errorf("caster rejected: %s", resp) |
| 166 | } |
| 167 | |
| 168 | c.conn = conn |
| 169 | |
| 170 | // Drain any further responses from the caster in the background, reusing the |
| 171 | // reader so bytes buffered past the status line are not lost. |
| 172 | go func() { |
| 173 | buf := make([]byte, 1024) |
| 174 | for { |
| 175 | n, err := reader.Read(buf) |
| 176 | if n > 0 { |
| 177 | c.logger.Info("received from caster", "data", string(buf[:n])) |
| 178 | } |
| 179 | if err != nil { |
| 180 | c.logger.Debug("caster read closed", "error", err) |