From 008f7415a60e0a7f350cfe61bda1b331e16ec835 Mon Sep 17 00:00:00 2001 From: nutcas3 Date: Tue, 14 Apr 2026 15:50:28 +0300 Subject: [PATCH] feat: Add ES2+ client for eSIM profile management operations - Add ES2Client struct with HTTP client and configuration - Add NewES2Client with 30s timeout and TLS configuration - Add DownloadProfile for eSIM profile download requests - Add GetProfileStatus for profile state queries - Add DeleteProfile for profile removal operations - Add EnableProfile and DisableProfile for profile state management - Add request/response types for all ES2+ operations (EID, ICCID, profile type, confirmation code) - Add setHeaders helper --- apps/carrier-connector/internal/es2/client.go | 271 ++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 apps/carrier-connector/internal/es2/client.go diff --git a/apps/carrier-connector/internal/es2/client.go b/apps/carrier-connector/internal/es2/client.go new file mode 100644 index 0000000..5bbb20e --- /dev/null +++ b/apps/carrier-connector/internal/es2/client.go @@ -0,0 +1,271 @@ +package es2 + +import ( + "bytes" + "context" + "crypto/tls" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/config" +) + +type ES2Client struct { + httpClient *http.Client + config *config.ES2Config + baseURL string +} + +func NewES2Client(cfg *config.ES2Config) *ES2Client { + return &ES2Client{ + httpClient: &http.Client{ + Timeout: 30 * time.Second, + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: cfg.InsecureSkipVerify, + }, + }, + }, + config: cfg, + baseURL: cfg.BaseURL, + } +} + +func (c *ES2Client) DownloadProfile(ctx context.Context, req *DownloadProfileRequest) (*DownloadProfileResponse, error) { + url := fmt.Sprintf("%s/es2plus/downloadProfile", c.baseURL) + + payload, err := json.Marshal(req) + if err != nil { + return nil, fmt.Errorf("failed to marshal request: %w", err) + } + + httpReq, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(payload)) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + c.setHeaders(httpReq) + + resp, err := c.httpClient.Do(httpReq) + if err != nil { + return nil, fmt.Errorf("failed to send request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } + + var downloadResp DownloadProfileResponse + if err := json.NewDecoder(resp.Body).Decode(&downloadResp); err != nil { + return nil, fmt.Errorf("failed to decode response: %w", err) + } + + return &downloadResp, nil +} + +func (c *ES2Client) GetProfileStatus(ctx context.Context, req *GetProfileStatusRequest) (*GetProfileStatusResponse, error) { + url := fmt.Sprintf("%s/es2plus/getProfileStatus", c.baseURL) + + payload, err := json.Marshal(req) + if err != nil { + return nil, fmt.Errorf("failed to marshal request: %w", err) + } + + httpReq, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(payload)) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + c.setHeaders(httpReq) + + resp, err := c.httpClient.Do(httpReq) + if err != nil { + return nil, fmt.Errorf("failed to send request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } + + var statusResp GetProfileStatusResponse + if err := json.NewDecoder(resp.Body).Decode(&statusResp); err != nil { + return nil, fmt.Errorf("failed to decode response: %w", err) + } + + return &statusResp, nil +} + +func (c *ES2Client) DeleteProfile(ctx context.Context, req *DeleteProfileRequest) (*DeleteProfileResponse, error) { + url := fmt.Sprintf("%s/es2plus/deleteProfile", c.baseURL) + + payload, err := json.Marshal(req) + if err != nil { + return nil, fmt.Errorf("failed to marshal request: %w", err) + } + + httpReq, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(payload)) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + c.setHeaders(httpReq) + + resp, err := c.httpClient.Do(httpReq) + if err != nil { + return nil, fmt.Errorf("failed to send request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } + + var deleteResp DeleteProfileResponse + if err := json.NewDecoder(resp.Body).Decode(&deleteResp); err != nil { + return nil, fmt.Errorf("failed to decode response: %w", err) + } + + return &deleteResp, nil +} + +func (c *ES2Client) EnableProfile(ctx context.Context, req *EnableProfileRequest) (*EnableProfileResponse, error) { + url := fmt.Sprintf("%s/es2plus/enableProfile", c.baseURL) + + payload, err := json.Marshal(req) + if err != nil { + return nil, fmt.Errorf("failed to marshal request: %w", err) + } + + httpReq, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(payload)) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + c.setHeaders(httpReq) + + resp, err := c.httpClient.Do(httpReq) + if err != nil { + return nil, fmt.Errorf("failed to send request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } + + var enableResp EnableProfileResponse + if err := json.NewDecoder(resp.Body).Decode(&enableResp); err != nil { + return nil, fmt.Errorf("failed to decode response: %w", err) + } + + return &enableResp, nil +} + +func (c *ES2Client) DisableProfile(ctx context.Context, req *DisableProfileRequest) (*DisableProfileResponse, error) { + url := fmt.Sprintf("%s/es2plus/disableProfile", c.baseURL) + + payload, err := json.Marshal(req) + if err != nil { + return nil, fmt.Errorf("failed to marshal request: %w", err) + } + + httpReq, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(payload)) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + c.setHeaders(httpReq) + + resp, err := c.httpClient.Do(httpReq) + if err != nil { + return nil, fmt.Errorf("failed to send request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } + + var disableResp DisableProfileResponse + if err := json.NewDecoder(resp.Body).Decode(&disableResp); err != nil { + return nil, fmt.Errorf("failed to decode response: %w", err) + } + + return &disableResp, nil +} + +func (c *ES2Client) setHeaders(req *http.Request) { + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", "Telecom-Platform/1.0") + + if c.config.APIKey != "" { + req.Header.Set("Authorization", "Bearer "+c.config.APIKey) + } + + if c.config.FunctionalityRequesterID != "" { + req.Header.Set("X-Admin-Protocol", "gsma-rsp") + req.Header.Set("X-Request-ID", generateRequestID()) + } +} + +func generateRequestID() string { + return fmt.Sprintf("%d", time.Now().UnixNano()) +} + +type DownloadProfileRequest struct { + EID string `json:"eid"` + ICCID string `json:"iccid"` + ProfileType string `json:"profileType"` + ConfirmationCode string `json:"confirmationCode,omitempty"` +} + +type DownloadProfileResponse struct { + ExecutionStatus string `json:"executionStatus"` + StatusMessage string `json:"statusMessage"` +} + +type GetProfileStatusRequest struct { + EID string `json:"eid"` + ICCID string `json:"iccid"` +} + +type GetProfileStatusResponse struct { + ExecutionStatus string `json:"executionStatus"` + StatusMessage string `json:"statusMessage"` + ProfileState string `json:"profileState,omitempty"` +} + +type DeleteProfileRequest struct { + EID string `json:"eid"` + ICCID string `json:"iccid"` +} + +type DeleteProfileResponse struct { + ExecutionStatus string `json:"executionStatus"` + StatusMessage string `json:"statusMessage"` +} + +type EnableProfileRequest struct { + EID string `json:"eid"` + ICCID string `json:"iccid"` +} + +type EnableProfileResponse struct { + ExecutionStatus string `json:"executionStatus"` + StatusMessage string `json:"statusMessage"` +} + +type DisableProfileRequest struct { + EID string `json:"eid"` + ICCID string `json:"iccid"` +} + +type DisableProfileResponse struct { + ExecutionStatus string `json:"executionStatus"` + StatusMessage string `json:"statusMessage"` +}