From ecb6f6b1fa461d788be0352a4d9da76cb8a32d51 Mon Sep 17 00:00:00 2001 From: creamlike1024 Date: Fri, 22 Mar 2024 22:30:27 +0800 Subject: [PATCH] third commit --- .github/workflows/releaser.yaml | 43 +++++++ README.md | 78 ++++++++++-- config.go | 22 +--- go.mod | 2 - go.sum | 2 - http.go | 17 +-- main.go | 3 +- rlpa_client.go | 120 ++++++++----------- rlpa_packet.go | 26 +++- struct.go | 25 +++- utils.go | 46 ++++++++ workmode.go | 202 ++++++++++++++++++++++++++++++++ 12 files changed, 470 insertions(+), 116 deletions(-) create mode 100644 .github/workflows/releaser.yaml create mode 100644 utils.go create mode 100644 workmode.go diff --git a/.github/workflows/releaser.yaml b/.github/workflows/releaser.yaml new file mode 100644 index 0000000..a78511f --- /dev/null +++ b/.github/workflows/releaser.yaml @@ -0,0 +1,43 @@ +name: Release + +on: + release: + types: [created] + workflow_dispatch: + +permissions: + contents: write + packages: write + +jobs: + release: + name: Release + runs-on: ubuntu-latest + strategy: + matrix: + goos: [ linux, windows, darwin, freebsd ] + goarch: [ "386", arm, amd64, arm64, mipsle ] + exclude: + - goarch: "386" + goos: darwin + - goarch: mipsle + goos: windows + - goarch: mipsle + goos: darwin + - goarch: mipsle + goos: freebsd + - goarch: arm + goos: windows + - goarch: arm + goos: darwin + - goarch: arm + goos: freebsd + + steps: + - uses: actions/checkout@v4 + - uses: wangyoucao577/go-release-action@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + goos: ${{ matrix.goos }} + goarch: ${{ matrix.goarch }} + ldflags: "-s -w" \ No newline at end of file diff --git a/README.md b/README.md index 6ec07a7..cefe182 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,75 @@ # rlpa-server -🚧🚧🚧 Under construction 🚧🚧🚧 +Another go implement of estk.me [rlpa-server.php](https://github.com/estkme-group/lpac/blob/main/src/rlpa-server.php) -- [x] rlpa packet +- [x] Download Profile (not tested yet) +- [x] Process Notification (consistent with the behavior of `rlpa-server.php`, notifications for non "delete" operations will be removed after processing) -TODO -- [] json 化 http response -- [] api disconnect 逻辑 -- [] 日志输出 -- ... \ No newline at end of file +Under construction: + +- Remote Management(http api) + +This feature is currently under construction. However, it is not necessary to use this feature in normal usage scenarios, and I am not sure if I should complete it + +## Usage + +Compile latest [lpac](https://github.com/estkme-group/lpac), then place the `lpac` binary program in the same directory as the `rlpa-server` program + +use environment variables to set port + +- `SOCKET_PORT`: socket port for estk rlpa, default 1888 +- `API_PORT`: http management api port, default 8008 + +debug log output: start with `-debug` argument to enable debug log level + +### systemd service example + +Write the following content into `/etc/systemd/system/rlpa-server.service` +``` +[Unit] +Description=rlpa server +Wants=network-online.target +After=network-online.target + +[Service] +ExecStart=/path/to/rlpa-server +WorkingDirectory=/path/to/rlpa-server-directory +Restart=on-failure +User=[your-user] + +[Install] +WantedBy=multi-user.target +``` +- `ExecStart`: rlpa-server binary path +- `WorkingDirectory`: The directory where rlpa server is located +- `User`: Replace with actual user + +Then execute `sudo systemctl daemon-reload` to reload services + +- Start rlpa-server: `sudo systemctl start rlpa-server` +- Let rlpa-server start with system: `sudo systemctl enable rlpa-server` + +## Public Server +⚠️ No guarantee, use at your own risk + +| IP | Port | Location | +|:------------:|:----:|:-----------------:| +|205.185.117.85| 1888 | Las Vegas, NV, US | + + +## API Document + +- lpac shell command + +Post json to `/shell/{manageID}` with header `Password: {Password}` + +example + +```bash +curl -X POST -H "Content-Type: application/json" \ +-H "Password: 2660" \ +-d '{"type":0, "command":"chip info"}' \ +http://example.com:8008/shell/rAct +``` + +Will get lpac output \ No newline at end of file diff --git a/config.go b/config.go index 500ff03..060d864 100644 --- a/config.go +++ b/config.go @@ -11,38 +11,26 @@ import ( ) type Config struct { - SocketPort uint16 - APIPort uint16 - LpacExeName string - LpacPath string - APDUInterface string - HTTPInterface string + SocketPort uint16 + APIPort uint16 + LpacExeName string + LpacPath string } var CFG Config func InitConfig() error { - const apduInterface = "libapduinterface_stdio" - const httpInterface = "libhttpinterface_curl" switch runtime.GOOS { case "windows": CFG.LpacExeName = "lpac.exe" - CFG.APDUInterface = apduInterface + ".dll" - CFG.HTTPInterface = httpInterface + ".dll" - case "darwin": - CFG.LpacExeName = "lpac" - CFG.APDUInterface = apduInterface + ".dylib" - CFG.HTTPInterface = httpInterface + ".dylib" default: CFG.LpacExeName = "lpac" - CFG.APDUInterface = apduInterface + ".so" - CFG.HTTPInterface = httpInterface + ".so" } pwd, err := os.Getwd() if err != nil { return errors.New(fmt.Sprint("Failed to get pwd: ", err)) } - CFG.LpacPath = filepath.Join(pwd, "lpac", CFG.LpacExeName) + CFG.LpacPath = filepath.Join(pwd, CFG.LpacExeName) socketPort := strings.TrimSpace(os.Getenv("SOCKET_PORT")) if socketPort == "" { CFG.SocketPort = 1888 diff --git a/go.mod b/go.mod index bff94b9..22a2d21 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,3 @@ module rlpa-server go 1.22 - -require github.com/gorilla/mux v1.8.1 // indirect diff --git a/go.sum b/go.sum index 7128337..e69de29 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +0,0 @@ -github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= -github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= diff --git a/http.go b/http.go index 33e8579..2573ed9 100644 --- a/http.go +++ b/http.go @@ -25,14 +25,17 @@ func HttpServer() { } func homeHandler(w http.ResponseWriter, r *http.Request) { + // TODO fmt.Fprintf(w, "rlpa-server\n") } func manifestHandler(w http.ResponseWriter, r *http.Request) { + // TODO fmt.Fprintf(w, "manifest\n") } func infoHandler(w http.ResponseWriter, r *http.Request) { + // TODO id := r.PathValue("id") if verify(id, r.Header.Get("Password")) { w.WriteHeader(http.StatusOK) @@ -44,6 +47,7 @@ func infoHandler(w http.ResponseWriter, r *http.Request) { } func connectHandler(w http.ResponseWriter, r *http.Request) { + // TODO id := r.PathValue("id") passwd := r.Header.Get("Password") if verify(id, passwd) { @@ -53,11 +57,6 @@ func connectHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "rlpa client disconnected") return } - if c.WorkMode != ShellMode { - w.WriteHeader(http.StatusBadGateway) - fmt.Fprintf(w, "rlpa client not in shell mode") - return - } if c.APILocked { w.WriteHeader(http.StatusConflict) return @@ -77,6 +76,7 @@ func disconnectHandler(w http.ResponseWriter, r *http.Request) { } func keepaliveHandler(w http.ResponseWriter, r *http.Request) { + // TODO id := r.PathValue("id") passwd := r.Header.Get("Password") if verify(id, passwd) { @@ -109,11 +109,6 @@ func shellHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "rlpa client disconnected") return } - if c.WorkMode != ShellMode { - w.WriteHeader(http.StatusBadGateway) - fmt.Fprintf(w, "rlpa client not in shell mode") - return - } // decode json body var payload ShellRequest err = json.NewDecoder(r.Body).Decode(&payload) @@ -135,7 +130,7 @@ func shellHandler(w http.ResponseWriter, r *http.Request) { return } c.DebugLog("command " + payload.Command) - err = c.processOpenLpac(strings.Split(strings.TrimSpace(payload.Command), " ")) + err = c.processOpenLpac(strings.Split(strings.TrimSpace(payload.Command), " ")...) if err != nil { w.WriteHeader(http.StatusBadGateway) fmt.Fprintf(w, "failed to open lpac") diff --git a/main.go b/main.go index b9ec218..8924c0f 100644 --- a/main.go +++ b/main.go @@ -31,7 +31,7 @@ Environment Variables: print(help) return } - slog.SetLogLoggerLevel(slog.LevelDebug) + slog.SetLogLoggerLevel(slog.LevelInfo) if *debug { slog.SetLogLoggerLevel(slog.LevelDebug) } @@ -70,7 +70,6 @@ Environment Variables: func handleConnection(conn net.Conn) { client := NewRLPAClient(conn) - Clients = append(Clients, client) for { // 接受 Packet diff --git a/rlpa_client.go b/rlpa_client.go index fa1a4f2..2e2e06b 100644 --- a/rlpa_client.go +++ b/rlpa_client.go @@ -20,19 +20,12 @@ const keepaliveDuration time.Duration = 60 * time.Second const letters = "abcdefghijkmnpqrstuvwxyzACDEFGHJKLMNPQRSTUVWXY345679" const digits = "0123456789" -const ( - WaitingMode = 0 - DownloadMode = 1 - ProcessNotificationMode = 2 - ShellMode = 3 -) - var Credentials map[string]string type RLPAClient struct { IsClosing bool ID string - WorkMode int + WorkMode RLPAWorkMode Socket net.Conn Packet RLPAPacket CMD *exec.Cmd @@ -45,7 +38,7 @@ type RLPAClient struct { KeepAliveTimer *time.Timer } -var Clients []*RLPAClient +var APIClients []*RLPAClient func NewRLPAClient(conn net.Conn) *RLPAClient { return &RLPAClient{ @@ -57,9 +50,9 @@ func NewRLPAClient(conn net.Conn) *RLPAClient { func (c *RLPAClient) GenCredential() string { for { - id := generateID(2) + id := generateID(4) if _, exists := Credentials[id]; !exists { - passwd := generatePasswd(2) + passwd := generatePasswd(4) Credentials[id] = passwd c.ID = id return passwd @@ -86,7 +79,7 @@ func generatePasswd(n int) string { } func FindClient(id string) (*RLPAClient, error) { - for _, c := range Clients { + for _, c := range APIClients { if c.ID == id { return c, nil } @@ -158,43 +151,23 @@ func (c *RLPAClient) ProcessPacket() error { return nil } - if c.WorkMode != WaitingMode { + // 已经在工作模式中 + if c.WorkMode != nil { return nil } switch c.Packet.Tag { case TagManagement: - // TODO: shellMode - c.WorkMode = ShellMode - passwd := c.GenCredential() - err := c.MessageBox(fmt.Sprintf("ManageID: %s\nPassword: %s", c.ID, passwd)) - if err != nil { - c.ErrLog("Failed to send manageID and password") - return err - } + c.WorkMode = new(ShellWorkMode) c.InfoLog("Enter ShellMode") break case TagProcessNotification: - // TODO: processNotificationMode - c.WorkMode = ProcessNotificationMode + c.WorkMode = new(ProcessNotificationWorkMode) c.InfoLog("Enter Process Notification Mode") - err := c.MessageBox("Unimplemented command.") - if err != nil { - return err - } - c.ErrLog("process notification mode unimplemented") - return errors.New("unimplemented command") break case TagDownloadProfile: - // TODO: downloadProfileMode - c.WorkMode = TagDownloadProfile + c.WorkMode = new(DownloadWorkMode) c.InfoLog("Enter Download Profile Mode") - err := c.MessageBox("Unimplemented command.") - if err != nil { - return err - } - c.ErrLog("download profile mode unimplemented") - return errors.New("unimplemented command") break default: err := c.MessageBox("Unimplemented command.") @@ -203,8 +176,12 @@ func (c *RLPAClient) ProcessPacket() error { } c.ErrLog("unimplemented mode") return errors.New("unimplemented command") - break } + if c.WorkMode == nil { + return errors.New("no workmode selected") + } + + c.WorkMode.Start(c) return nil } @@ -213,6 +190,18 @@ func (c *RLPAClient) Close(result int) { return } c.IsClosing = true + _, errFound := FindClient(c.ID) + if errFound == nil { + // 如果连接了 API,移除凭据 + delete(Credentials, c.ID) + var newAPIClients []*RLPAClient + for _, client := range APIClients { + if c.ID != client.ID { + newAPIClients = append(newAPIClients, client) + } + } + APIClients = newAPIClients + } if c.CMD != nil { if c.CMD.ProcessState == nil { err := c.CMD.Process.Kill() @@ -221,21 +210,19 @@ func (c *RLPAClient) Close(result int) { } } } - - delete(Credentials, c.ID) - if c.ResponseWaiting { - switch result { - case ResultFinished: - c.ResponseChan <- []byte("OK") - break - case ResultClientDisconnect: - c.ResponseChan <- []byte("client disconnected") - break - case ResultError: - c.ResponseChan <- []byte("lpac error") - break - } - } + // if c.ResponseWaiting { + // switch result { + // case ResultFinished: + // c.ResponseChan <- []byte("OK") + // break + // case ResultClientDisconnect: + // c.ResponseChan <- []byte("client disconnected") + // break + // case ResultError: + // c.ResponseChan <- []byte("lpac error") + // break + // } + // } err := c.UnlockAPDU() if err != nil { @@ -256,7 +243,7 @@ func (c *RLPAClient) Close(result int) { c.Socket = nil } -func (c *RLPAClient) processOpenLpac(args []string) error { +func (c *RLPAClient) processOpenLpac(args ...string) error { // TODO err := c.LockAPDU() if err != nil { @@ -264,8 +251,7 @@ func (c *RLPAClient) processOpenLpac(args []string) error { } c.CMD = exec.Command(CFG.LpacPath, args...) c.CMD.Env = []string{ - "APDU_INTERFACE=" + CFG.APDUInterface, - "HTTP_INTERFACE=" + CFG.HTTPInterface, + "LPAC_APDU=stdio", } // 连接 stdio c.LpacStdin, err = c.CMD.StdinPipe() @@ -294,7 +280,6 @@ func (c *RLPAClient) processOpenLpac(args []string) error { } } }() - // stderr 和 stdout 处理函数如果遇到结果将发送给 responseChan go func() { errStdout := c.OnLpacStdout() if errStdout != nil { @@ -361,13 +346,11 @@ func (c *RLPAClient) OnLpacStdout() error { } case "lpa": c.DebugLog("run lpac finished") - // 完成,发送结果 json - if c.ResponseWaiting { - c.ResponseChan <- line - } + c.WorkMode.OnProcessFinished(c, &req.Payload) return nil default: - return errors.New("undefined output struct") + // 一般是 type: process + break } } return nil @@ -376,10 +359,11 @@ func (c *RLPAClient) OnLpacStdout() error { func (c *RLPAClient) OnLpacStderr() { scanner := bufio.NewScanner(c.LpacStderr) for scanner.Scan() { - line := scanner.Bytes() - if c.ResponseWaiting { - c.ResponseChan <- line - } + line := scanner.Text() + // if c.ResponseWaiting { + // c.ResponseChan <- line + // } + c.ErrLog(line) c.Close(ResultError) return } @@ -409,7 +393,7 @@ func (c *RLPAClient) StartOrResetTimer() { func (c *RLPAClient) DisconnectAPI() { // TODO if c.ResponseWaiting { - c.ResponseChan <- []byte("timeout") + // c.ResponseChan <- []byte("timeout") } c.APILocked = false } @@ -423,7 +407,7 @@ func (c *RLPAClient) DebugLogWriteLpacStdin(data []byte) { } func (c *RLPAClient) DebugLogReadLpacStdout(data []byte) { - slog.Debug("Read lpac stdin "+string(data), "client", c.RemoteAddr()) + slog.Debug("Read lpac stdout "+string(data), "client", c.RemoteAddr()) } func (c *RLPAClient) InfoLog(msg string) { diff --git a/rlpa_packet.go b/rlpa_packet.go index c17b2fb..d8b04ca 100644 --- a/rlpa_packet.go +++ b/rlpa_packet.go @@ -72,13 +72,31 @@ func (p *RLPAPacket) Recv(conn net.Conn) error { if p.IsFinished() { return nil } - // 设定 buf 长度即可读取指定长度 - buf := make([]byte, p.NextReadLen) - _, err := conn.Read(buf) + // 读取至 nextReadLen 长度 + readExactly := func(conn net.Conn, nextReadLen int) ([]byte, error) { + buffer := make([]byte, nextReadLen) + totalRead := 0 + + for totalRead < nextReadLen { + n, err := conn.Read(buffer[totalRead:]) + if err != nil { + if err == io.EOF { + // 对面关闭连接,读到 EOF + return buffer[:totalRead], io.ErrUnexpectedEOF + } + return buffer[:totalRead], err + } + totalRead += n + slog.Debug(fmt.Sprint("Socket Read buffer: ", buffer[:totalRead]), "client", conn.RemoteAddr().String()) + } + return buffer, nil + } + buf, err := readExactly(conn, int(p.NextReadLen)) + if err != nil { return err } - slog.Debug(fmt.Sprint("Socket Read buffer: ", buf), "client", conn.RemoteAddr().String()) + // 长度为 0 说明读取失败 if len(buf) == 0 { return errors.New("read buffer length 0") diff --git a/struct.go b/struct.go index 20eb8bb..8273e24 100644 --- a/struct.go +++ b/struct.go @@ -1,12 +1,31 @@ package main +import "encoding/json" + type Payload struct { - Func string `json:"func"` - Param string `json:"param"` - Ecode int `json:"ecode"` + Code int `json:"code"` + Func string `json:"func"` + Param string `json:"param"` + Ecode int `json:"ecode"` + Data json.RawMessage `json:"data"` } type Request struct { Type string `json:"type"` Payload Payload `json:"payload"` } + +type Notification struct { + SeqNumber int `json:"seqNumber"` + ProfileManagementOperation string `json:"profileManagementOperation"` + NotificationAddress string `json:"notificationAddress"` + Iccid string `json:"iccid"` +} + +type PullInfo struct { + SMDP string + MatchID string + ObjectID string + ConfirmCode string + IMEI string +} diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..bf2caf8 --- /dev/null +++ b/utils.go @@ -0,0 +1,46 @@ +package main + +import ( + "errors" + _ "image/jpeg" + "strings" +) + +func DecodeLpaActivationCode(code string) (info PullInfo, confirmCodeNeeded bool, err error) { + // ref: https://www.gsma.com/esim/wp-content/uploads/2020/06/SGP.22-v2.2.2.pdf#page=111 + err = errors.New("LPA Activation Code format error") + code = strings.TrimSpace(code) + var ok bool + if code, ok = strings.CutPrefix(code, "LPA:"); !ok { + return + } + switch parts := strings.Split(code, "$"); parts[0] { + case "1": // Activation Code Format + var codeNeeded string + bindings := []*string{&info.SMDP, &info.MatchID, &info.ObjectID, &codeNeeded} + for index, value := range parts[1:] { + *bindings[index] = strings.TrimSpace(value) + } + confirmCodeNeeded = codeNeeded == "1" + if info.SMDP != "" { + err = nil + } + } + return +} + +func CompleteActivationCode(input string) string { + // 如果输入已经以 LPA:1$ 开始,则认为它是完整的 + if strings.HasPrefix(input, "LPA:1$") { + return input + } + // 1$rspAddr$matchID + if strings.HasPrefix(input, "1$") { + return "LPA:" + input + } + // $rspAddr$matchID + if strings.HasPrefix(input, "$") { + return "LPA:1" + input + } + return input +} diff --git a/workmode.go b/workmode.go new file mode 100644 index 0000000..987f3b1 --- /dev/null +++ b/workmode.go @@ -0,0 +1,202 @@ +package main + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" +) + +type RLPAWorkMode interface { + Start(client *RLPAClient) + OnProcessFinished(client *RLPAClient, data *Payload) + Finished() bool +} + +type ShellWorkMode struct { +} + +func (m *ShellWorkMode) Start(c *RLPAClient) { + // 添加到 Client 列表并发送 ID 和密码 + APIClients = append(APIClients, c) + passwd := c.GenCredential() + err := c.MessageBox(fmt.Sprintf("ManageID: %s\nPassword: %s", c.ID, passwd)) + if err != nil { + c.ErrLog(err.Error()) + c.Close(ResultError) + return + } +} + +func (m *ShellWorkMode) OnProcessFinished(c *RLPAClient, data *Payload) { + // TODO 完成,发送结果 json + resp, err := json.Marshal(data) + if err != nil { + c.ResponseChan <- []byte(err.Error()) + return + } + if c.ResponseWaiting { + c.ResponseChan <- resp + return + } + +} + +func (m *ShellWorkMode) Finished() bool { + return false +} + +type ProcessNotificationWorkMode struct { + State int + FailedCount int + TotalCount int + Notifications []*Notification +} + +func (m *ProcessNotificationWorkMode) Start(c *RLPAClient) { + m.State = 0 + err := c.processOpenLpac("notification", "list") + if err != nil { + c.Close(ResultError) + return + } +} + +func (m *ProcessNotificationWorkMode) OnProcessFinished(c *RLPAClient, data *Payload) { + if data == nil { + c.Close(ResultError) + return + } + switch m.State { + case 0: + if data.Code != 0 { + c.Close(ResultError) + return + } + err := json.Unmarshal(data.Data, &m.Notifications) + if err != nil { + c.Close(ResultError) + return + } + m.State = 1 + m.TotalCount = len(m.Notifications) + m.processOneNotification(c) + break + case 1: + if data.Code == 0 { + c.InfoLog("Process success") + } else { + c.InfoLog("Process failed") + m.FailedCount++ + } + m.processOneNotification(c) + break + } +} + +func (m *ProcessNotificationWorkMode) Finished() bool { + return m.State == 2 +} + +func (m *ProcessNotificationWorkMode) processOneNotification(c *RLPAClient) { + // 如果没有通知,输出结果,并切换状态 + if len(m.Notifications) == 0 { + + if m.FailedCount != 0 { + c.InfoLog(fmt.Sprint(m.FailedCount, "notification failed to process")) + } else { + c.InfoLog("All notification process successfully") + } + err := c.MessageBox(fmt.Sprint("All notification processing finished\n", m.TotalCount-m.FailedCount, " succeed\n", m.FailedCount, " failed")) + if err != nil { + c.Close(ResultError) + } else { + c.Close(ResultFinished) + } + m.State = 2 + return + } + notification := m.Notifications[0] + m.Notifications = m.Notifications[1:] + c.InfoLog(fmt.Sprint("Processing ", notification.SeqNumber, " Operation: ", notification.ProfileManagementOperation)) + switch notification.ProfileManagementOperation { + case "install": + fallthrough + case "enable": + fallthrough + case "disable": + err := c.processOpenLpac("notification", "process", strconv.Itoa(notification.SeqNumber), "-r") + if err != nil { + c.Close(ResultError) + } + break + case "delete": + err := c.processOpenLpac("notification", "process", strconv.Itoa(notification.SeqNumber)) + if err != nil { + c.Close(ResultError) + } + break + default: + c.Close(ResultError) + } +} + +type DownloadWorkMode struct { + State int +} + +func (m *DownloadWorkMode) Start(c *RLPAClient) { + m.State = 0 + // 替换所有的 \x02 (STX) 字符为 $ + data := strings.Replace(string(c.Packet.Value), string([]byte{0x02}), "$", -1) + // 替换所有的 \x11 (DC1) 字符为 _ + data = strings.Replace(data, string([]byte{0x11}), "_", -1) + data = strings.TrimSpace(data) + pullInfo, confirmCodeNeeded, err := DecodeLpaActivationCode(CompleteActivationCode(data)) + if err != nil { + _ = c.MessageBox(err.Error()) + c.Close(ResultError) + return + } + if confirmCodeNeeded { + _ = c.MessageBox("Confirm Code is not supported yet") + c.Close(ResultFinished) + return + } + args := []string{"profile", "download"} + if pullInfo.SMDP != "" { + args = append(args, "-s", pullInfo.SMDP) + } + if pullInfo.MatchID != "" { + args = append(args, "-m", pullInfo.MatchID) + } + err = c.processOpenLpac(args...) + if err != nil { + c.ErrLog(err.Error()) + c.Close(ResultError) + } +} + +func (m *DownloadWorkMode) OnProcessFinished(c *RLPAClient, data *Payload) { + if data == nil { + c.Close(ResultError) + return + } + if data.Code == 0 { + c.InfoLog("Download success") + _ = c.MessageBox("Download success") + c.Close(ResultFinished) + } else { + c.ErrLog("download error: " + string(data.Data)) + // TODO 解析错误信息 + // 也许把 EasyLPAC 代码搬过来 + errorMsg := fmt.Sprint("Data: ", data.Data) + _ = c.MessageBox(errorMsg) + c.Close(ResultError) + } + m.State = 1 +} + +func (m *DownloadWorkMode) Finished() bool { + return m.State == 1 +}