|
|
@@ -1,92 +1,140 @@
|
|
|
+/*
|
|
|
+ * Copyright (c) 2018, Psiphon Inc.
|
|
|
+ * All rights reserved.
|
|
|
+ *
|
|
|
+ * This program is free software: you can redistribute it and/or modify
|
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
|
+ * the Free Software Foundation, either version 3 of the License, or
|
|
|
+ * (at your option) any later version.
|
|
|
+ *
|
|
|
+ * This program is distributed in the hope that it will be useful,
|
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
+ * GNU General Public License for more details.
|
|
|
+ *
|
|
|
+ * You should have received a copy of the GNU General Public License
|
|
|
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
+ *
|
|
|
+ */
|
|
|
+
|
|
|
package main
|
|
|
|
|
|
-// #include <stdlib.h>
|
|
|
+/*
|
|
|
+#include <stdlib.h>
|
|
|
+#include <stdint.h>
|
|
|
+
|
|
|
+// For descriptions of fields, see below.
|
|
|
+// Additional information can also be found in the Parameters structure in clientlib.go.
|
|
|
+struct Parameters {
|
|
|
+ size_t sizeofStruct; // Must be set to sizeof(Parameters); helps with ABI compatibiity
|
|
|
+ char *dataRootDirectory;
|
|
|
+ char *clientPlatform;
|
|
|
+ char *networkID;
|
|
|
+ int32_t *establishTunnelTimeoutSeconds;
|
|
|
+};
|
|
|
+*/
|
|
|
import "C"
|
|
|
|
|
|
import (
|
|
|
"context"
|
|
|
"encoding/json"
|
|
|
- "errors"
|
|
|
"fmt"
|
|
|
- "sync"
|
|
|
"time"
|
|
|
"unsafe"
|
|
|
|
|
|
- "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon"
|
|
|
+ "github.com/Psiphon-Labs/psiphon-tunnel-core/ClientLibrary/clientlib"
|
|
|
"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
|
|
|
- "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/protocol"
|
|
|
)
|
|
|
|
|
|
+/*
|
|
|
+If/when new fields are added to the C Parameters struct, we can use this code to ensure
|
|
|
+ABI compatibility. We'll take these steps:
|
|
|
+1. Copy the old struct into a new `ParametersV1`. The new struct will be `Parameters`.
|
|
|
+2. Uncomment the code below. It will not compile (link, specifically) if the size of
|
|
|
+ `Parameters` is the same as the size of `ParametersV1`.
|
|
|
+ - If the compile fails, padding may need to be added to `Parameters` to force it to be
|
|
|
+ a different size than `ParametersV1`.
|
|
|
+3. In `Start`, we'll check the value of `sizeofStruct` to determine which version of
|
|
|
+ `Parameters` the caller is using, and behave according.
|
|
|
+4. Do similar kinds of things for V2, V3, etc.
|
|
|
+*/
|
|
|
+/*
|
|
|
+func nonexistentFunction()
|
|
|
+func init() {
|
|
|
+ if C.sizeof_struct_Parameters == C.sizeof_struct_ParametersV1 {
|
|
|
+ // There is only an attempt to link this nonexistent function if the struct sizes
|
|
|
+ // are the same. So they must not be.
|
|
|
+ nonexistentFunction()
|
|
|
+ }
|
|
|
+}
|
|
|
+*/
|
|
|
+
|
|
|
type startResultCode int
|
|
|
|
|
|
const (
|
|
|
- startResultCodeSuccess startResultCode = iota
|
|
|
- startResultCodeTimeout
|
|
|
- startResultCodeOtherError
|
|
|
+ startResultCodeSuccess startResultCode = 0
|
|
|
+ startResultCodeTimeout = 1
|
|
|
+ startResultCodeOtherError = 2
|
|
|
)
|
|
|
|
|
|
type noticeEvent struct {
|
|
|
- Data map[string]interface{} `json:"data"`
|
|
|
- NoticeType string `json:"noticeType"`
|
|
|
+ Data map[string]interface{}
|
|
|
+ NoticeType string
|
|
|
}
|
|
|
|
|
|
type startResult struct {
|
|
|
- Code startResultCode `json:"result_code"`
|
|
|
- BootstrapTime float64 `json:"bootstrap_time,omitempty"`
|
|
|
- ErrorString string `json:"error,omitempty"`
|
|
|
- HttpProxyPort int `json:"http_proxy_port,omitempty"`
|
|
|
- SocksProxyPort int `json:"socks_proxy_port,omitempty"`
|
|
|
+ Code startResultCode
|
|
|
+ ConnectTimeMS int64 `json:",omitempty"`
|
|
|
+ Error string `json:",omitempty"`
|
|
|
+ HTTPProxyPort int `json:",omitempty"`
|
|
|
+ SOCKSProxyPort int `json:",omitempty"`
|
|
|
}
|
|
|
|
|
|
-type psiphonTunnel struct {
|
|
|
- controllerWaitGroup sync.WaitGroup
|
|
|
- controllerCtx context.Context
|
|
|
- stopController context.CancelFunc
|
|
|
- httpProxyPort int
|
|
|
- socksProxyPort int
|
|
|
-}
|
|
|
-
|
|
|
-var tunnel psiphonTunnel
|
|
|
+var tunnel *clientlib.PsiphonTunnel
|
|
|
|
|
|
// Memory managed by PsiphonTunnel which is allocated in Start and freed in Stop
|
|
|
var managedStartResult *C.char
|
|
|
|
|
|
-//export psiphon_tunnel_start
|
|
|
+//export PsiphonTunnelStart
|
|
|
//
|
|
|
// ******************************* WARNING ********************************
|
|
|
// The underlying memory referenced by the return value of Start is managed
|
|
|
// by PsiphonTunnel and attempting to free it explicitly will cause the
|
|
|
-// program to crash. This memory is freed once Stop is called.
|
|
|
+// program to crash. This memory is freed once Stop is called, or if Start
|
|
|
+// is called again.
|
|
|
// ************************************************************************
|
|
|
//
|
|
|
-// Start starts the controller and returns once either of the following has occured: an active tunnel has been
|
|
|
-// established, the timeout has elapsed before an active tunnel could be established or an error has occured.
|
|
|
+// Start starts the controller and returns once one of the following has occured:
|
|
|
+// an active tunnel has been established, the timeout has elapsed before an active tunnel
|
|
|
+// could be established, or an error has occured.
|
|
|
//
|
|
|
-// Start returns a startResult object serialized as a JSON string in the form of a null-terminated buffer of C chars.
|
|
|
+// Start returns a startResult object serialized as a JSON string in the form of a
|
|
|
+// null-terminated buffer of C chars.
|
|
|
// Start will return,
|
|
|
// On success:
|
|
|
// {
|
|
|
-// "result_code": 0,
|
|
|
-// "bootstrap_time": <time_to_establish_tunnel>,
|
|
|
-// "http_proxy_port": <http_proxy_port_num>,
|
|
|
-// "socks_proxy_port": <socks_proxy_port_num>
|
|
|
+// "Code": 0,
|
|
|
+// "ConnectTimeMS": <milliseconds to establish tunnel>,
|
|
|
+// "HTTPProxyPort": <http proxy port number>,
|
|
|
+// "SOCKSProxyPort": <socks proxy port number>
|
|
|
// }
|
|
|
//
|
|
|
// On timeout:
|
|
|
-// {
|
|
|
-// "result_code": 1,
|
|
|
-// "error": <error message>
|
|
|
-// }
|
|
|
+// {
|
|
|
+// "Code": 1,
|
|
|
+// "Error": <error message>
|
|
|
+// }
|
|
|
//
|
|
|
// On other error:
|
|
|
// {
|
|
|
-// "result_code": 2,
|
|
|
-// "error": <error message>
|
|
|
+// "Code": 2,
|
|
|
+// "Error": <error message>
|
|
|
// }
|
|
|
//
|
|
|
-// clientPlatform should be of the form OS_OSVersion_BundleIdentifier where both the OSVersion and BundleIdentifier
|
|
|
-// fields are optional. If clientPlatform is set to an empty string the "ClientPlatform" field in the provided json
|
|
|
-// config will be used instead.
|
|
|
+// Parameters.clientPlatform should be of the form OS_OSVersion_BundleIdentifier where
|
|
|
+// both the OSVersion and BundleIdentifier fields are optional. If clientPlatform is set
|
|
|
+// to an empty string the "ClientPlatform" field in the provided JSON config will be
|
|
|
+// used instead.
|
|
|
//
|
|
|
// Provided below are links to platform specific code which can be used to find some of the above fields:
|
|
|
// Android:
|
|
|
@@ -102,228 +150,110 @@ var managedStartResult *C.char
|
|
|
// "iOS_11.4_com.example.exampleApp"
|
|
|
// "Windows"
|
|
|
//
|
|
|
-// networkID must be a non-empty string and follow the format specified by
|
|
|
+// Parameters.networkID must be a non-empty string and follow the format specified by:
|
|
|
// https://godoc.org/github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon#NetworkIDGetter.
|
|
|
-//
|
|
|
-// Provided below are links to platform specific code which can be used to generate valid network identifier strings:
|
|
|
+// Provided below are links to platform specific code which can be used to generate
|
|
|
+// valid network identifier strings:
|
|
|
// Android:
|
|
|
// - https://github.com/Psiphon-Labs/psiphon-tunnel-core/blob/3d344194d21b250e0f18ededa4b4459a373b0690/MobileLibrary/Android/PsiphonTunnel/PsiphonTunnel.java#L371
|
|
|
// iOS:
|
|
|
// - https://github.com/Psiphon-Labs/psiphon-tunnel-core/blob/3d344194d21b250e0f18ededa4b4459a373b0690/MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/PsiphonTunnel.m#L1105
|
|
|
//
|
|
|
-// timeout specifies a time limit after which to stop attempting to connect and return an error if an active tunnel
|
|
|
-// has not been established. A timeout of 0 will result in no timeout condition and the controller will attempt to
|
|
|
-// establish an active tunnel indefinitely (or until psiphon_tunnel_stop is called). Timeout values >= 0 override
|
|
|
-// the optional `EstablishTunnelTimeoutSeconds` config field.
|
|
|
-func psiphon_tunnel_start(cConfigJSON, cEmbeddedServerEntryList, cClientPlatform, cNetworkID *C.char, timeout *int64) *C.char {
|
|
|
-
|
|
|
+// Parameters.establishTunnelTimeoutSeconds specifies a time limit after which to stop
|
|
|
+// attempting to connect and return an error if an active tunnel has not been established.
|
|
|
+// A timeout of 0 will result in no timeout condition and the controller will attempt to
|
|
|
+// establish an active tunnel indefinitely (or until PsiphonTunnelStop is called).
|
|
|
+// Timeout values >= 0 override the optional `EstablishTunnelTimeoutSeconds` config field;
|
|
|
+// null causes the config value to be used.
|
|
|
+func PsiphonTunnelStart(cConfigJSON, cEmbeddedServerEntryList *C.char, cParams *C.struct_Parameters) *C.char {
|
|
|
// Stop any active tunnels
|
|
|
+ PsiphonTunnelStop()
|
|
|
|
|
|
- psiphon_tunnel_stop()
|
|
|
-
|
|
|
- // Validate timeout value
|
|
|
-
|
|
|
- if timeout != nil && *timeout < 0 {
|
|
|
- managedStartResult = startErrorJson(errors.New("Timeout value must be non-negative"))
|
|
|
+ if cConfigJSON == nil {
|
|
|
+ err := common.ContextError(fmt.Errorf("configJSON is required"))
|
|
|
+ managedStartResult = startErrorJSON(err)
|
|
|
return managedStartResult
|
|
|
}
|
|
|
|
|
|
- // NOTE: all arguments which are still referenced once Start returns should be copied onto the Go heap
|
|
|
- // to ensure that they don't disappear later on and cause Go to crash.
|
|
|
-
|
|
|
- configJSON := C.GoString(cConfigJSON)
|
|
|
- embeddedServerEntryList := C.GoString(cEmbeddedServerEntryList)
|
|
|
- clientPlatform := C.GoString(cClientPlatform)
|
|
|
- networkID := C.GoString(cNetworkID)
|
|
|
-
|
|
|
- // Load provided config
|
|
|
-
|
|
|
- config, err := psiphon.LoadConfig([]byte(configJSON))
|
|
|
- if err != nil {
|
|
|
- managedStartResult = startErrorJson(err)
|
|
|
+ if cParams == nil {
|
|
|
+ err := common.ContextError(fmt.Errorf("params is required"))
|
|
|
+ managedStartResult = startErrorJSON(err)
|
|
|
return managedStartResult
|
|
|
}
|
|
|
|
|
|
- // Set network ID
|
|
|
-
|
|
|
- config.NetworkID = networkID
|
|
|
-
|
|
|
- // Set client platform
|
|
|
-
|
|
|
- config.ClientPlatform = clientPlatform
|
|
|
-
|
|
|
- // Set timeout
|
|
|
-
|
|
|
- if timeout != nil {
|
|
|
- // timeout overrides optional timeout field in config
|
|
|
- *config.EstablishTunnelTimeoutSeconds = 0
|
|
|
- }
|
|
|
-
|
|
|
- // All config fields should be set before calling commit
|
|
|
- err = config.Commit()
|
|
|
- if err != nil {
|
|
|
- managedStartResult = startErrorJson(err)
|
|
|
+ if cParams.sizeofStruct != C.sizeof_struct_Parameters {
|
|
|
+ err := common.ContextError(fmt.Errorf("sizeofStruct does not match sizeof(Parameters)"))
|
|
|
+ managedStartResult = startErrorJSON(err)
|
|
|
return managedStartResult
|
|
|
}
|
|
|
|
|
|
- // Setup signals
|
|
|
-
|
|
|
- connected := make(chan bool)
|
|
|
-
|
|
|
- testError := make(chan error)
|
|
|
-
|
|
|
- // Set up notice handling
|
|
|
-
|
|
|
- psiphon.SetNoticeWriter(psiphon.NewNoticeReceiver(
|
|
|
- func(notice []byte) {
|
|
|
-
|
|
|
- var event noticeEvent
|
|
|
-
|
|
|
- err := json.Unmarshal(notice, &event)
|
|
|
- if err != nil {
|
|
|
- err = errors.New(fmt.Sprintf("Failed to unmarshal json: %s", err.Error()))
|
|
|
- select {
|
|
|
- case testError <- err:
|
|
|
- default:
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if event.NoticeType == "ListeningHttpProxyPort" {
|
|
|
- port := event.Data["port"].(float64)
|
|
|
- tunnel.httpProxyPort = int(port)
|
|
|
- } else if event.NoticeType == "ListeningSocksProxyPort" {
|
|
|
- port := event.Data["port"].(float64)
|
|
|
- tunnel.socksProxyPort = int(port)
|
|
|
- } else if event.NoticeType == "Tunnels" {
|
|
|
- count := event.Data["count"].(float64)
|
|
|
- if count > 0 {
|
|
|
- select {
|
|
|
- case connected <- true:
|
|
|
- default:
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }))
|
|
|
-
|
|
|
- // Initialize data store
|
|
|
+ // NOTE: all arguments which may be referenced once Start returns must be copied onto
|
|
|
+ // the Go heap to ensure that they don't disappear later on and cause Go to crash.
|
|
|
+ configJSON := []byte(C.GoString(cConfigJSON))
|
|
|
+ embeddedServerEntryList := C.GoString(cEmbeddedServerEntryList)
|
|
|
|
|
|
- err = psiphon.OpenDataStore(config)
|
|
|
- if err != nil {
|
|
|
- managedStartResult = startErrorJson(err)
|
|
|
- return managedStartResult
|
|
|
+ params := clientlib.Parameters{}
|
|
|
+ if cParams.dataRootDirectory != nil {
|
|
|
+ v := C.GoString(cParams.dataRootDirectory)
|
|
|
+ params.DataRootDirectory = &v
|
|
|
}
|
|
|
-
|
|
|
- // Store embedded server entries
|
|
|
-
|
|
|
- serverEntries, err := protocol.DecodeServerEntryList(
|
|
|
- embeddedServerEntryList,
|
|
|
- common.GetCurrentTimestamp(),
|
|
|
- protocol.SERVER_ENTRY_SOURCE_EMBEDDED)
|
|
|
- if err != nil {
|
|
|
- managedStartResult = startErrorJson(err)
|
|
|
- return managedStartResult
|
|
|
+ if cParams.clientPlatform != nil {
|
|
|
+ v := C.GoString(cParams.clientPlatform)
|
|
|
+ params.ClientPlatform = &v
|
|
|
}
|
|
|
-
|
|
|
- err = psiphon.StoreServerEntries(config, serverEntries, false)
|
|
|
- if err != nil {
|
|
|
- managedStartResult = startErrorJson(err)
|
|
|
- return managedStartResult
|
|
|
+ if cParams.networkID != nil {
|
|
|
+ v := C.GoString(cParams.networkID)
|
|
|
+ params.NetworkID = &v
|
|
|
}
|
|
|
-
|
|
|
- // Run Psiphon
|
|
|
-
|
|
|
- controller, err := psiphon.NewController(config)
|
|
|
- if err != nil {
|
|
|
- managedStartResult = startErrorJson(err)
|
|
|
- return managedStartResult
|
|
|
+ if cParams.establishTunnelTimeoutSeconds != nil {
|
|
|
+ v := int(*cParams.establishTunnelTimeoutSeconds)
|
|
|
+ params.EstablishTunnelTimeoutSeconds = &v
|
|
|
}
|
|
|
|
|
|
- tunnel.controllerCtx, tunnel.stopController = context.WithCancel(context.Background())
|
|
|
-
|
|
|
- // Set start time
|
|
|
-
|
|
|
startTime := time.Now()
|
|
|
|
|
|
- optionalTimeout := make(chan error)
|
|
|
-
|
|
|
- if timeout != nil && *timeout != 0 {
|
|
|
- // Setup timeout signal
|
|
|
-
|
|
|
- runtimeTimeout := time.Duration(*timeout) * time.Second
|
|
|
-
|
|
|
- timeoutSignal, cancelTimeout := context.WithTimeout(context.Background(), runtimeTimeout)
|
|
|
-
|
|
|
- defer cancelTimeout()
|
|
|
+ // Start the tunnel connection
|
|
|
+ var err error
|
|
|
+ tunnel, err = clientlib.StartTunnel(
|
|
|
+ context.Background(), configJSON, embeddedServerEntryList, params, nil, nil)
|
|
|
|
|
|
- go func() {
|
|
|
- <-timeoutSignal.Done()
|
|
|
- optionalTimeout <- timeoutSignal.Err()
|
|
|
- }()
|
|
|
- }
|
|
|
-
|
|
|
- // Run test
|
|
|
-
|
|
|
- var result startResult
|
|
|
-
|
|
|
- tunnel.controllerWaitGroup.Add(1)
|
|
|
- go func() {
|
|
|
- defer tunnel.controllerWaitGroup.Done()
|
|
|
- controller.Run(tunnel.controllerCtx)
|
|
|
-
|
|
|
- select {
|
|
|
- case testError <- errors.New("controller.Run exited unexpectedly"):
|
|
|
- default:
|
|
|
- }
|
|
|
- }()
|
|
|
-
|
|
|
- // Wait for an active tunnel, timeout or error
|
|
|
-
|
|
|
- select {
|
|
|
- case <-connected:
|
|
|
- result.Code = startResultCodeSuccess
|
|
|
- result.BootstrapTime = secondsBeforeNow(startTime)
|
|
|
- result.HttpProxyPort = tunnel.httpProxyPort
|
|
|
- result.SocksProxyPort = tunnel.socksProxyPort
|
|
|
- case err := <-optionalTimeout:
|
|
|
- result.Code = startResultCodeTimeout
|
|
|
- if err != nil {
|
|
|
- result.ErrorString = fmt.Sprintf("Timeout occured before Psiphon connected: %s", err.Error())
|
|
|
+ if err != nil {
|
|
|
+ if err == clientlib.ErrTimeout {
|
|
|
+ managedStartResult = marshalStartResult(startResult{
|
|
|
+ Code: startResultCodeTimeout,
|
|
|
+ Error: fmt.Sprintf("Timeout occurred before Psiphon connected: %s", err.Error()),
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ managedStartResult = marshalStartResult(startResult{
|
|
|
+ Code: startResultCodeOtherError,
|
|
|
+ Error: err.Error(),
|
|
|
+ })
|
|
|
}
|
|
|
- tunnel.stopController()
|
|
|
- case err := <-testError:
|
|
|
- result.Code = startResultCodeOtherError
|
|
|
- result.ErrorString = err.Error()
|
|
|
- tunnel.stopController()
|
|
|
+ return managedStartResult
|
|
|
}
|
|
|
|
|
|
- // Return result
|
|
|
- managedStartResult = marshalStartResult(result)
|
|
|
-
|
|
|
+ // Success
|
|
|
+ managedStartResult = marshalStartResult(startResult{
|
|
|
+ Code: startResultCodeSuccess,
|
|
|
+ ConnectTimeMS: int64(time.Now().Sub(startTime) / time.Millisecond),
|
|
|
+ HTTPProxyPort: tunnel.HTTPProxyPort,
|
|
|
+ SOCKSProxyPort: tunnel.SOCKSProxyPort,
|
|
|
+ })
|
|
|
return managedStartResult
|
|
|
}
|
|
|
|
|
|
-//export psiphon_tunnel_stop
|
|
|
+//export PsiphonTunnelStop
|
|
|
//
|
|
|
// Stop stops the controller if it is running and waits for it to clean up and exit.
|
|
|
//
|
|
|
// Stop should always be called after a successful call to Start to ensure the
|
|
|
-// controller is not left running.
|
|
|
-func psiphon_tunnel_stop() {
|
|
|
+// controller is not left running and memory is released.
|
|
|
+// It is safe to call this function when the tunnel is not running.
|
|
|
+func PsiphonTunnelStop() {
|
|
|
freeManagedStartResult()
|
|
|
-
|
|
|
- if tunnel.stopController != nil {
|
|
|
- tunnel.stopController()
|
|
|
+ if tunnel != nil {
|
|
|
+ tunnel.Stop()
|
|
|
}
|
|
|
-
|
|
|
- tunnel.controllerWaitGroup.Wait()
|
|
|
-
|
|
|
- psiphon.CloseDataStore()
|
|
|
-}
|
|
|
-
|
|
|
-// secondsBeforeNow returns the delta seconds of the current time subtract startTime.
|
|
|
-func secondsBeforeNow(startTime time.Time) float64 {
|
|
|
- delta := time.Now().Sub(startTime)
|
|
|
- return delta.Seconds()
|
|
|
}
|
|
|
|
|
|
// marshalStartResult serializes a startResult object as a JSON string in the form
|
|
|
@@ -331,25 +261,29 @@ func secondsBeforeNow(startTime time.Time) float64 {
|
|
|
func marshalStartResult(result startResult) *C.char {
|
|
|
resultJSON, err := json.Marshal(result)
|
|
|
if err != nil {
|
|
|
- return C.CString(fmt.Sprintf("{\"result_code\":%d, \"error\": \"%s\"}", startResultCodeOtherError, err.Error()))
|
|
|
+ err = common.ContextErrorMsg(err, "json.Marshal failed")
|
|
|
+ // Fail back to manually constructing the JSON
|
|
|
+ return C.CString(fmt.Sprintf("{\"Code\":%d, \"Error\": \"%s\"}",
|
|
|
+ startResultCodeOtherError, err.Error()))
|
|
|
}
|
|
|
|
|
|
return C.CString(string(resultJSON))
|
|
|
}
|
|
|
|
|
|
-// startErrorJson returns a startResult object serialized as a JSON string in the form
|
|
|
-// of a null-terminated buffer of C chars. The object's return result code will be set to
|
|
|
-// startResultCodeOtherError (2) and its error string set to the error string of the provided error.
|
|
|
+// startErrorJSON returns a startResult object serialized as a JSON string in the form of
|
|
|
+// a null-terminated buffer of C chars. The object's return result code will be set to
|
|
|
+// startResultCodeOtherError (2) and its error string set to the error string of the
|
|
|
+// provided error.
|
|
|
//
|
|
|
// The JSON will be in the form of:
|
|
|
// {
|
|
|
-// "result_code": 2,
|
|
|
-// "error": <error message>
|
|
|
+// "Code": 2,
|
|
|
+// "Error": <error message>
|
|
|
// }
|
|
|
-func startErrorJson(err error) *C.char {
|
|
|
+func startErrorJSON(err error) *C.char {
|
|
|
var result startResult
|
|
|
result.Code = startResultCodeOtherError
|
|
|
- result.ErrorString = err.Error()
|
|
|
+ result.Error = err.Error()
|
|
|
|
|
|
return marshalStartResult(result)
|
|
|
}
|