1
0
mirror of https://github.com/fumiama/tienyik.git synced 2026-06-04 23:10:26 +08:00

feat: add more apis & cmd tyaliv

This commit is contained in:
源文雨
2025-11-24 17:35:20 +08:00
parent da1de770ad
commit eaf6d29fd8
14 changed files with 990 additions and 20 deletions

6
.gitignore vendored
View File

@@ -33,3 +33,9 @@ go.work.sum
# MacOS
.DS_Store
# WASM
*.wasm
# configs
*.yaml

View File

@@ -94,11 +94,11 @@ type RequestLogin struct {
ChallengeID string `form:"challengeId"`
DeviceCode string `form:"deviceCode"`
DeviceName string `form:"deviceName"`
DeviceType string `form:"deviceType"`
DeviceType uint64 `form:"deviceType"`
DeviceModel string `form:"deviceModel"`
AppVersion string `form:"appVersion"`
SysVersion string `form:"sysVersion"`
ClientVersion string `form:"clientVersion"`
ClientVersion uint64 `form:"clientVersion"`
}
type ResponseLogin struct {
@@ -135,6 +135,8 @@ type ResponseLogin struct {
func (r *ResponseLogin) SetClient(cli *hcli.Client) {
cli.Tenantid = strconv.FormatInt(r.TenantID, 10)
cli.Usereid = r.UserEid
cli.SetSecretKey(r.SecretKey)
cli.SetTimestamp(r.Timestamp)
}
func Login(tya *tienyik.AES, cli *hcli.Client, r *RequestLogin) (*ResponseLogin, error) {
@@ -148,3 +150,10 @@ func Login(tya *tienyik.AES, cli *hcli.Client, r *RequestLogin) (*ResponseLogin,
defer resp.Body.Close()
return hson.Unmarshal[*ResponseLogin](tya, resp.Body)
}
func Logout(tya *tienyik.AES, cli *hcli.Client) error {
_, err := cli.Post(
textio.API(), "", nil,
)
return err
}

View File

@@ -3,9 +3,12 @@ package auth
import (
"crypto/rand"
"crypto/rsa"
"os"
"testing"
"github.com/fumiama/tienyik"
"github.com/fumiama/tienyik/api/cdserv"
"github.com/fumiama/tienyik/hcli"
"github.com/sirupsen/logrus"
)
@@ -36,3 +39,39 @@ func TestNegotiationEncKey(t *testing.T) {
t.Fatal(err)
}
}
func TestLogin(t *testing.T) {
cli := hcli.NewClient()
sd, err := cdserv.GetServData()
if err != nil {
t.Fatal(err)
}
t.Log("get serv data:", sd)
x, err := GenChallengeData(nil, cli)
if err != nil {
t.Fatal(err)
}
sd.SetClient(cli)
rsp, err := Login(nil, cli, &RequestLogin{
UserAccount: os.Getenv("TYUSR"),
Password: tienyik.ChallengePassword(os.Getenv("TYPWD"), x.ChallengeCode),
SHA256Password: tienyik.ChallengeSHA256Password(os.Getenv("TYPWD"), x.ChallengeCode),
ChallengeID: x.ChallengeID,
DeviceCode: cli.Devicecode,
DeviceName: tienyik.DeviceNameEdge,
DeviceType: cli.Devicetype,
DeviceModel: tienyik.DeviceModelMacOS,
AppVersion: tienyik.AppVersion,
SysVersion: tienyik.DeviceModelMacOS,
ClientVersion: cli.Version,
})
if err != nil {
t.Fatal(err)
}
t.Log(rsp)
rsp.SetClient(cli)
err = Logout(nil, cli)
if err != nil {
t.Fatal(err)
}
}

323
api/desktop/client.go Normal file
View File

@@ -0,0 +1,323 @@
package desktop
import (
"bytes"
"net/url"
"strconv"
"github.com/fumiama/tienyik"
"github.com/fumiama/tienyik/hcli"
"github.com/fumiama/tienyik/internal/horm"
"github.com/fumiama/tienyik/internal/hson"
"github.com/fumiama/tienyik/internal/textio"
)
const (
DefaultRequestPageDesktopSortType = "createTimeV1"
)
type RequestPageDesktop struct {
GetCnt int `json:"getCnt"`
DesktopTypes []string `json:"desktopTypes"`
SortType string `json:"sortType"`
}
type ResponsePageDesktop struct {
Timestamp int64 `json:"timestamp"`
SortList []struct {
ObjID string `json:"objId"`
ObjType int `json:"objType"`
ObjValue string `json:"objValue"`
DesktopTypes []string `json:"desktopTypes"`
} `json:"sortList"`
DesktopPoolList []any `json:"desktopPoolList"`
DesktopList []struct {
ObjType int `json:"objType"`
TenantID int `json:"tenantId"`
ObjID string `json:"objId"`
ConnectURL []string `json:"connectUrl"`
ObjName string `json:"objName"`
Backupurl []string `json:"backupurl"`
OsType string `json:"osType"`
OsName string `json:"osName"`
ConnectMaster int `json:"connectMaster"`
NeedLineUp bool `json:"needLineUp"`
UserDesktopGroupID any `json:"userDesktopGroupId"`
Strategy struct {
ReconnectMsg any `json:"reconnectMsg"`
RebootMsg any `json:"rebootMsg"`
ShutoffMsg any `json:"shutoffMsg"`
ShutdownStrategy any `json:"shutdownStrategy"`
RebootStrategy any `json:"rebootStrategy"`
ModifyComputerAllas string `json:"modifyComputerAllas"`
CheckBeforeConnect any `json:"checkBeforeConnect"`
} `json:"strategy"`
CloudMobileType any `json:"cloudMobileType"`
DesktopID string `json:"desktopId"`
DesktopName string `json:"desktopName"`
FlavorName any `json:"flavorName"`
ImageName string `json:"imageName"`
OsBit string `json:"osBit"`
CPUCore any `json:"cpuCore"`
MemoryGB any `json:"memoryGB"`
RootDiskGB any `json:"rootDiskGB"`
DataDiskGB any `json:"dataDiskGB"`
Status string `json:"status"`
Summary any `json:"summary"`
TanentCode string `json:"tanentCode"`
TanentName string `json:"tanentName"`
UseStatus string `json:"useStatus"`
DesktopCode string `json:"desktopCode"`
ForeignDesktopID string `json:"foreignDesktopId"`
ForbiddenConnect bool `json:"forbiddenConnect"`
GpuType bool `json:"gpuType"`
GpuVirtualMethod any `json:"gpuVirtualMethod"`
UserMode int `json:"userMode"`
DefaultDesktop bool `json:"defaultDesktop"`
ExpireDate any `json:"expireDate"`
CreateDate int64 `json:"createDate"`
NowDate any `json:"nowDate"`
NoticeInterval int `json:"noticeInterval"`
BandExpireDate any `json:"bandExpireDate"`
BandNoticeInterval int `json:"bandNoticeInterval"`
UpperResolution any `json:"upperResolution"`
ProdType string `json:"prodType"`
ProdGroupType int `json:"prodGroupType"`
ProdInstID string `json:"prodInstId"`
ProdGroupName string `json:"prodGroupName"`
DesktopMirrorTagSet []any `json:"desktopMirrorTagSet"`
LicenseExpireDate int64 `json:"licenseExpireDate"`
LicenseNoticeInterval int `json:"licenseNoticeInterval"`
AllowConnStartTime any `json:"allowConnStartTime"`
AllowConnEndTime any `json:"allowConnEndTime"`
OperationAuditSupported bool `json:"operationAuditSupported"`
ProjectionScreenState any `json:"projectionScreenState"`
NickName string `json:"nickName"`
VMType int `json:"vmType"`
PayType string `json:"payType"`
ProdSubType any `json:"prodSubType"`
InstStatus int `json:"instStatus"`
UseTimeVO any `json:"useTimeVO"`
UseStatusShowActions any `json:"useStatusShowActions"`
UseStatusText string `json:"useStatusText"`
UseStatusColor string `json:"useStatusColor"`
ModifyComputerAllas any `json:"modifyComputerAllas"`
UsePrivateImageFile bool `json:"usePrivateImageFile"`
ImageID int `json:"imageId"`
ImageCategoryID int `json:"imageCategoryId"`
HaProdType int `json:"haProdType"`
CtrlTypes []any `json:"ctrlTypes"`
OrderProductData struct {
TimeLimitTotal any `json:"timeLimitTotal"`
TimeLimitUsed any `json:"timeLimitUsed"`
NextAcctTime any `json:"nextAcctTime"`
BusiChannelType any `json:"busiChannelType"`
ManageData any `json:"manageData"`
ActiveDate any `json:"activeDate"`
KeepTime any `json:"keepTime"`
} `json:"orderProductData"`
LicenseID int `json:"licenseId"`
RegionID int `json:"regionId"`
TenantCode string `json:"tenantCode"`
ConnectAPIURL struct {
ConnectPath string `json:"connectPath"`
StatusPath string `json:"statusPath"`
StatePath string `json:"statePath"`
AppendData any `json:"appendData"`
} `json:"connectApiUrl"`
} `json:"desktopList"`
PreemptionDesktopList []any `json:"preemptionDesktopList"`
}
func PageDesktop(tya *tienyik.AES, cli *hcli.Client, r *RequestPageDesktop) (*ResponsePageDesktop, error) {
resp, err := cli.Post(
textio.API(), textio.ContenTypeJSON,
bytes.NewReader(hson.Marshal(tya, r)),
)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return hson.Unmarshal[*ResponsePageDesktop](tya, resp.Body)
}
type ResponseFeature struct {
CPUCore int `json:"cpuCore"`
MemoryGB int `json:"memoryGB"`
SystemDiskGB int `json:"systemDiskGB"`
DataDiskGB any `json:"dataDiskGB"`
TotalDiskGB int `json:"totalDiskGB"`
SysDisk struct {
Size int `json:"size"`
Path string `json:"path"`
Code string `json:"code"`
} `json:"sysDisk"`
DataDiskList []any `json:"dataDiskList"`
MirrorVersion string `json:"mirrorVersion"`
MirrorCategoryName string `json:"mirrorCategoryName"`
DesktopName string `json:"desktopName"`
GpuSliceRAM any `json:"gpuSliceRam"`
GpuSliceRAMDesc any `json:"gpuSliceRamDesc"`
ExpireDate any `json:"expireDate"`
CreateDate int64 `json:"createDate"`
NowDate int64 `json:"nowDate"`
LinkInfo any `json:"linkInfo"`
OrderProduct struct {
TimeLimitTotal any `json:"timeLimitTotal"`
TimeLimitUsed any `json:"timeLimitUsed"`
NextAcctTime any `json:"nextAcctTime"`
BusiChannelType string `json:"busiChannelType"`
ManageData any `json:"manageData"`
ActiveDate any `json:"activeDate"`
KeepTime any `json:"keepTime"`
} `json:"orderProduct"`
MirrorID int `json:"mirrorId"`
MirrorCategoryID int `json:"mirrorCategoryId"`
ProductName string `json:"productName"`
PayType string `json:"payType"`
ProdType string `json:"prodType"`
ProdSubType any `json:"prodSubType"`
TimePkgVOS []any `json:"timePkgVOS"`
Os string `json:"os"`
MirrorType string `json:"mirrorType"`
}
func Feature(tya *tienyik.AES, cli *hcli.Client, desktopId string, objType int, objId string) (*ResponseFeature, error) {
u, err := url.Parse(textio.API())
if err != nil {
return nil, err
}
q := u.Query()
q.Set("desktopId", desktopId)
q.Set("objType", strconv.Itoa(objType))
q.Set("objId", objId)
u.RawQuery = tya.EUrlParams(q)
resp, err := cli.Get(u.String())
if err != nil {
return nil, err
}
defer resp.Body.Close()
return hson.Unmarshal[*ResponseFeature](tya, resp.Body)
}
type ResponseGetDesktopExtraInfo struct {
Strategy struct {
ReconnectMsg any `json:"reconnectMsg"`
RebootMsg any `json:"rebootMsg"`
ShutoffMsg any `json:"shutoffMsg"`
ShutdownStrategy string `json:"shutdownStrategy"`
RebootStrategy string `json:"rebootStrategy"`
ModifyComputerAllas any `json:"modifyComputerAllas"`
CheckBeforeConnect any `json:"checkBeforeConnect"`
} `json:"strategy"`
UpperResolution string `json:"upperResolution"`
TimeLimitProductData any `json:"timeLimitProductData"`
HaProdType int `json:"haProdType"`
}
func GetDesktopExtraInfo(tya *tienyik.AES, cli *hcli.Client, objId string, objType int) (*ResponseGetDesktopExtraInfo, error) {
u, err := url.Parse(textio.API())
if err != nil {
return nil, err
}
q := u.Query()
q.Set("objId", objId)
q.Set("objType", strconv.Itoa(objType))
u.RawQuery = tya.EUrlParams(q)
resp, err := cli.Get(u.String())
if err != nil {
return nil, err
}
defer resp.Body.Close()
return hson.Unmarshal[*ResponseGetDesktopExtraInfo](tya, resp.Body)
}
type RequestConnect struct {
ObjID string `form:"objId"`
ObjType int `form:"objType"`
OsType string `form:"osType"`
DeviceID int `form:"deviceId"`
DeviceCode string `form:"deviceCode"`
DeviceName string `form:"deviceName"`
SysVersion string `form:"sysVersion"`
AppVersion string `form:"appVersion"`
HostName string `form:"hostName"`
VdCommand string `form:"vdCommand"`
IPAddress string `form:"ipAddress"`
MacAddress string `form:"macAddress"`
HardwareFeatureCode string `form:"hardwareFeatureCode"`
SpecifiedCertCategory int `form:"specifiedCertCategory"`
}
type ResponseConnect struct {
GoingRetry bool `json:"goingRetry"`
DesktopInfo any `json:"desktopInfo"`
ShadowDesktopInfo struct {
InHaMode int `json:"inHaMode"`
HaDesktopID any `json:"haDesktopId"`
HaConnectingTips any `json:"haConnectingTips"`
HaConnectSucTips any `json:"haConnectSucTips"`
} `json:"shadowDesktopInfo"`
DesktopAnywhereInfo struct {
AnywhereStatus int `json:"anywhereStatus"`
MigrateStatus any `json:"migrateStatus"`
AnywhereDesktopID any `json:"anywhereDesktopId"`
SrcResPoolName any `json:"srcResPoolName"`
TargetResPoolName any `json:"targetResPoolName"`
EstimatedTime int `json:"estimatedTime"`
ReminderDays any `json:"reminderDays"`
RoamingDays any `json:"roamingDays"`
NeedReserveRemind int `json:"needReserveRemind"`
ShadowInfoDTO any `json:"shadowInfoDTO"`
AnywhereOpen bool `json:"anywhereOpen"`
ConnectTargetDesktop bool `json:"connectTargetDesktop"`
} `json:"desktopAnywhereInfo"`
DesktopID string `json:"desktopId"`
PollingKey string `json:"pollingKey"`
AuthInfo any `json:"authInfo"`
}
func Connect(tya *tienyik.AES, cli *hcli.Client, r *RequestConnect) (*ResponseConnect, error) {
resp, err := cli.Post(
textio.API(), textio.ContenTypeForm,
bytes.NewReader(horm.Marshal(tya, r)),
)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return hson.Unmarshal[*ResponseConnect](tya, resp.Body)
}
type RequestState struct {
ObjID string `json:"objId"`
ObjType int `json:"objType"`
}
type ResponseState struct {
ObjType int `json:"objType"`
ObjID string `json:"objId"`
DesktopID int `json:"desktopId"`
DesktopState string `json:"desktopState"`
RunningTask int `json:"runningTask"`
RunningTaskName string `json:"runningTaskName"`
TaskStartTime int64 `json:"taskStartTime"`
MirrorReady any `json:"mirrorReady"`
UseStatus string `json:"useStatus"`
UseStatusText string `json:"useStatusText"`
UseStatusColor string `json:"useStatusColor"`
}
func State(tya *tienyik.AES, cli *hcli.Client, r []RequestState) ([]ResponseState, error) {
resp, err := cli.Post(
textio.API(), textio.ContenTypeJSON,
bytes.NewReader(hson.Marshal(tya, &r)),
)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return hson.Unmarshal[[]ResponseState](tya, resp.Body)
}

101
api/desktop/client_test.go Normal file
View File

@@ -0,0 +1,101 @@
package desktop
import (
"os"
"testing"
"github.com/fumiama/tienyik"
"github.com/fumiama/tienyik/api/auth"
"github.com/fumiama/tienyik/api/cdserv"
"github.com/fumiama/tienyik/hcli"
)
func TestDesktop(t *testing.T) {
cli := hcli.NewClient()
sd, err := cdserv.GetServData()
if err != nil {
t.Fatal(err)
}
t.Log("get serv data:", sd)
x, err := auth.GenChallengeData(nil, cli)
if err != nil {
t.Fatal(err)
}
sd.SetClient(cli)
rsp, err := auth.Login(nil, cli, &auth.RequestLogin{
UserAccount: os.Getenv("TYUSR"),
Password: tienyik.ChallengePassword(os.Getenv("TYPWD"), x.ChallengeCode),
SHA256Password: tienyik.ChallengeSHA256Password(os.Getenv("TYPWD"), x.ChallengeCode),
ChallengeID: x.ChallengeID,
DeviceCode: cli.Devicecode,
DeviceName: tienyik.DeviceNameEdge,
DeviceType: cli.Devicetype,
DeviceModel: tienyik.DeviceModelMacOS,
AppVersion: tienyik.AppVersion,
SysVersion: tienyik.DeviceModelMacOS,
ClientVersion: cli.Version,
})
if err != nil {
t.Fatal(err)
}
t.Log(rsp)
rsp.SetClient(cli)
pd, err := PageDesktop(nil, cli, &RequestPageDesktop{
GetCnt: 1,
DesktopTypes: []string{"1", tienyik.ArchX86, tienyik.ArchARM, tienyik.ArchHW},
SortType: DefaultRequestPageDesktopSortType,
})
if err != nil {
t.Fatal(err)
}
t.Log(pd)
for _, x := range pd.DesktopList {
feat, err := Feature(nil, cli, x.DesktopID, x.ObjType, x.ObjID)
if err != nil {
t.Fatal(err)
}
t.Log("feat:", feat)
ext, err := GetDesktopExtraInfo(nil, cli, x.ObjID, x.ObjType)
if err != nil {
t.Fatal(err)
}
t.Log("ext:", ext)
s, err := State(nil, cli, []RequestState{{
ObjID: x.ObjID,
ObjType: x.ObjType,
}})
if err != nil {
t.Fatal(err)
}
t.Log("s1:", s)
con, err := Connect(nil, cli, &RequestConnect{
ObjID: x.ObjID,
ObjType: x.ObjType,
OsType: x.OsType,
DeviceID: int(cli.Devicetype),
DeviceCode: cli.Devicecode,
DeviceName: tienyik.DeviceNameEdge,
SysVersion: tienyik.DeviceModelMacOS,
AppVersion: tienyik.AppVersion,
HostName: tienyik.DeviceNameEdge,
HardwareFeatureCode: cli.Devicecode,
SpecifiedCertCategory: 1,
})
if err != nil {
t.Fatal(err)
}
t.Log("con:", con)
s, err = State(nil, cli, []RequestState{{
ObjID: x.ObjID,
ObjType: x.ObjType,
}})
if err != nil {
t.Fatal(err)
}
t.Log("s2:", s)
}
err = auth.Logout(nil, cli)
if err != nil {
t.Fatal(err)
}
}

26
cfg.go
View File

@@ -7,12 +7,15 @@ import (
"strings"
)
const Version = "103010001"
const (
Version = 103010001
AppVersion = "3.1.0"
)
const (
DeviceTypePC = "25"
DeviceTypeMAC = "45"
DeviceTypeWEB = "60"
DeviceTypePC = 25
DeviceTypeMAC = 45
DeviceTypeWEB = 60
)
const (
@@ -21,6 +24,21 @@ const (
AppModelPHONE = "3"
)
const (
DeviceNameEdge = "Edge浏览器"
)
// alos sysVersion
const (
DeviceModelMacOS = "Macintosh; Intel Mac OS X 10_15_7"
)
const (
ArchX86 = "2001"
ArchARM = "2002"
ArchHW = "2003"
)
func NewDeviceCode() string {
sb := &strings.Builder{}
sb.WriteString("web_")

198
cmd/tyaliv/main.go Normal file
View File

@@ -0,0 +1,198 @@
package main
import (
"flag"
"fmt"
"os"
"os/signal"
"strings"
"syscall"
"time"
"gopkg.in/yaml.v3"
base14 "github.com/fumiama/go-base16384"
"github.com/fumiama/tienyik"
"github.com/fumiama/tienyik/api/auth"
"github.com/fumiama/tienyik/api/cdserv"
"github.com/fumiama/tienyik/api/desktop"
"github.com/fumiama/tienyik/hcli"
"github.com/fumiama/tienyik/internal/log"
"github.com/fumiama/tienyik/internal/textio"
)
type config struct {
UserAccount string `yaml:"UserAccount"`
PasswordB14 string `yaml:"PasswordB14"` // PasswordB14 is a little bit more secure
CheckIntervalSec int `yaml:"CheckIntervalSec"` // CheckIntervalSec default 60
GetDeviceCount int `yaml:"GetDeviceCount"` // GetDeviceCount default 20
}
func main() {
c := flag.String("c", "config.yaml", "load config file")
s := flag.String("s", "", "save config file template")
flag.Parse()
cfg := config{}
if *s != "" {
fmt.Print("UserAccount: ")
fmt.Scanln(&cfg.UserAccount)
pwd := ""
fmt.Print("Password: ")
textio.NoEchoScanln(&pwd)
cfg.PasswordB14 = base14.EncodeString(pwd)
cfg.CheckIntervalSec = 60
cfg.GetDeviceCount = 20
data, err := yaml.Marshal(&cfg)
if err != nil {
log.Fatalln(err)
}
err = os.WriteFile(*s, data, 0644)
if err != nil {
log.Fatalln(err)
}
return
}
f, err := os.Open(*c)
if err != nil {
log.Fatalln(err)
}
err = yaml.NewDecoder(f).Decode(&cfg)
_ = f.Close()
if err != nil {
log.Fatalln(err)
}
if cfg.UserAccount == "" {
log.Fatalln("user account must be set")
}
if cfg.PasswordB14 == "" {
log.Fatalln("password must be set (in b14 format)")
}
if cfg.CheckIntervalSec <= 0 {
cfg.CheckIntervalSec = 60
}
if cfg.GetDeviceCount <= 0 {
cfg.GetDeviceCount = 20
}
RECONN:
cli := hcli.NewClient()
sd, err := cdserv.GetServData()
if err != nil {
log.Fatalln(err)
}
x, err := auth.GenChallengeData(nil, cli)
if err != nil {
log.Fatalln(err)
}
sd.SetClient(cli)
pwd := base14.DecodeString(cfg.PasswordB14)
rsp, err := auth.Login(nil, cli, &auth.RequestLogin{
UserAccount: cfg.UserAccount,
Password: tienyik.ChallengePassword(pwd, x.ChallengeCode),
SHA256Password: tienyik.ChallengeSHA256Password(pwd, x.ChallengeCode),
ChallengeID: x.ChallengeID,
DeviceCode: cli.Devicecode,
DeviceName: tienyik.DeviceNameEdge,
DeviceType: cli.Devicetype,
DeviceModel: tienyik.DeviceModelMacOS,
AppVersion: tienyik.AppVersion,
SysVersion: tienyik.DeviceModelMacOS,
ClientVersion: cli.Version,
})
if err != nil {
log.Fatalln(err)
}
rsp.SetClient(cli)
defer auth.Logout(nil, cli)
pd, err := desktop.PageDesktop(nil, cli, &desktop.RequestPageDesktop{
GetCnt: 20,
DesktopTypes: []string{"1", tienyik.ArchX86},
SortType: desktop.DefaultRequestPageDesktopSortType,
})
if err != nil {
log.Fatalln(err)
}
mp := make(map[string][2]string, len(pd.DesktopList)*4)
sb := strings.Builder{}
sb.WriteString("available desktops:")
for _, x := range pd.DesktopList {
if x.UseStatusText == "运行中" {
sb.WriteString(" |●")
} else {
sb.WriteString(" |○")
}
sb.WriteString("[")
sb.WriteString(x.ObjID)
sb.WriteString("]")
sb.WriteString(x.ObjName)
sb.WriteString("(")
sb.WriteString(x.OsName)
sb.WriteString(")|")
mp[x.ObjID] = [2]string{x.ObjName, x.OsType}
}
log.Infoln(&sb)
t := time.NewTicker(time.Second * time.Duration(cfg.CheckIntervalSec))
defer t.Stop()
mainStopCh := make(chan struct{})
mc := make(chan os.Signal, 4)
signal.Notify(mc, os.Interrupt, syscall.SIGQUIT, syscall.SIGTERM)
go func() {
for {
<-mc
close(mainStopCh)
}
}()
for {
select {
case <-t.C:
log.Infoln("start refreshing...")
reqs := make([]desktop.RequestState, len(pd.DesktopList))
for i, x := range pd.DesktopList {
reqs[i].ObjID = x.ObjID
reqs[i].ObjType = x.ObjType
}
s, err := desktop.State(nil, cli, reqs)
if err != nil {
log.Warnln("get state err:", err)
goto RECONN
}
for _, x := range s {
log.Infof("%s [%s]%s status is %s", x.ObjID, mp[x.ObjID][0], x.DesktopState)
if x.DesktopState == "ACTIVE" {
continue
}
log.Infof("%s [%s]%s do refresh", x.ObjID, mp[x.ObjID][0])
_, err = desktop.Connect(nil, cli, &desktop.RequestConnect{
ObjID: x.ObjID,
ObjType: x.ObjType,
OsType: mp[x.ObjID][1],
DeviceID: int(cli.Devicetype),
DeviceCode: cli.Devicecode,
DeviceName: tienyik.DeviceNameEdge,
SysVersion: tienyik.DeviceModelMacOS,
AppVersion: tienyik.AppVersion,
HostName: tienyik.DeviceNameEdge,
HardwareFeatureCode: cli.Devicecode,
SpecifiedCertCategory: 1,
})
if err != nil {
log.Warnln("connect err:", err)
goto RECONN
}
}
case <-mainStopCh:
log.Warnln("quit loop")
return
}
}
}

3
go.mod
View File

@@ -5,7 +5,10 @@ go 1.24.4
require (
github.com/fumiama/go-base16384 v1.7.1
github.com/sirupsen/logrus v1.9.3
github.com/tetratelabs/wazero v1.9.0
golang.org/x/net v0.28.0
golang.org/x/term v0.23.0
gopkg.in/yaml.v3 v3.0.1
)
require (

5
go.sum
View File

@@ -13,15 +13,20 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@@ -1,6 +1,7 @@
package hcli
import (
"context"
"net/http"
"reflect"
"strconv"
@@ -21,17 +22,22 @@ var DefaultClient = http.Client{
type Client struct {
rcnt uintptr
sg tienyik.Signer
secretKey string
offsetTime int64
Appmodel string
Devicecode string
Devicetype string
Devicetype uint64
Servernode string
Tenantid string
Usereid string
Version string
Version uint64
}
func NewClient() *Client {
return &Client{
sg: tienyik.NewSigner(context.TODO()),
Appmodel: tienyik.AppModelTOB,
Devicecode: tienyik.NewDeviceCode(),
Devicetype: tienyik.DeviceTypeWEB,
@@ -39,6 +45,15 @@ func NewClient() *Client {
}
}
func (c *Client) SetSecretKey(k string) {
c.secretKey = k
}
func (c *Client) SetTimestamp(ts int64) {
n := time.Now().UnixMilli()
c.offsetTime = n - ts
}
func (c *Client) setHeaders(req *http.Request) {
if c == nil {
return
@@ -54,27 +69,39 @@ func (c *Client) setHeaders(req *http.Request) {
}
fieldValue := v.Field(i)
if fieldValue.IsZero() {
continue
}
k := base14.DecodeString("廝呲舀㴄") + field.Name
if fieldValue.Kind() == reflect.String {
req.Header.Set(
base14.DecodeString("廝呲舀㴄")+field.Name,
fieldValue.String(),
)
switch fieldValue.Kind() {
case reflect.String:
req.Header.Set(k, fieldValue.String())
case reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uint, reflect.Uintptr:
req.Header.Set(k, strconv.FormatUint(fieldValue.Uint(), 10))
case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int:
req.Header.Set(k, strconv.FormatInt(fieldValue.Int(), 10))
default:
panic("unsupported field " + field.Name + " value type " + fieldValue.Type().String())
}
}
if c.Appmodel != "" {
ts := time.Now().UnixMilli()
rid := uint64(atomic.AddUintptr(&c.rcnt, 1)) + uint64(ts)
requestid := strconv.FormatUint(rid, 10)
ts -= c.offsetTime
timestamp := strconv.FormatInt(ts, 10)
requestid := strconv.FormatUint(
uint64(atomic.AddUintptr(&c.rcnt, 1))+uint64(ts), 10,
)
req.Header.Set(base14.DecodeString("廝呲草獱歙攷徥爀㴆"), requestid)
req.Header.Set(base14.DecodeString("廝呲荑睭杜蕆厵縀㴆"), timestamp)
}
if c.Servernode != "" {
//TODO: gensign
if c.secretKey != "" {
req.Header.Set(base14.DecodeString("廝呲荍睧榘敇揉獳欜渀㴂"), c.sg.GenKeyNew(
context.TODO(), c.Devicetype, uint64(ts), rid, c.secretKey,
c.Usereid, req.URL.EscapedPath(), c.Servernode, c.Version,
))
}
}
}

View File

@@ -3,6 +3,7 @@ package horm
import (
"net/url"
"reflect"
"strconv"
"github.com/fumiama/tienyik"
"github.com/fumiama/tienyik/internal/textio"
@@ -22,8 +23,13 @@ func Marshal(tya *tienyik.AES, x any) []byte {
continue
}
if field.Kind() == reflect.String {
switch field.Kind() {
case reflect.String:
q.Set(formTag, field.String())
case reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uint, reflect.Uintptr:
q.Set(formTag, strconv.FormatUint(field.Uint(), 10))
case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int:
q.Set(formTag, strconv.FormatInt(field.Int(), 10))
}
}

View File

@@ -0,0 +1,17 @@
package textio
import (
"fmt"
"os"
"golang.org/x/term"
)
func NoEchoScanln(a ...any) (n int, err error) {
fd := int(os.Stdin.Fd())
bytePassword, err := term.ReadPassword(fd)
if err != nil {
return 0, err
}
return fmt.Sscanln(string(bytePassword)+"\n", a...)
}

198
wasm.go Normal file
View File

@@ -0,0 +1,198 @@
package tienyik
import (
"context"
"fmt"
"strings"
_ "embed"
"github.com/fumiama/tienyik/internal/op"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
)
//go:embed main.1755740488270.wasm
var wasmdata []byte
type Signer struct {
rt wazero.Runtime
md api.Module
genKey api.Function
genKeyNew api.Function
genKeyWithoutURI api.Function
_malloc api.Function
_free api.Function
}
func NewSigner(ctx context.Context) (sg Signer) {
rt := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigInterpreter())
_, err := rt.NewHostModuleBuilder("a").
// ___cxa_throw
NewFunctionBuilder().WithFunc(func(ctx context.Context, e, n, t uint32) {
panic(fmt.Sprintf("___cxa_throw: ptr(e)=%08x, type(n)=%08x, destructor(t)=%08x", e, n, t))
}).Export("c").
// __abort_js
NewFunctionBuilder().WithFunc(func(ctx context.Context) {
panic("wasm aborted")
}).Export("a").
// _emscripten_resize_heap
NewFunctionBuilder().WithFunc(func(ctx context.Context, _ uint32) uint32 {
panic("wasm oom")
}).Export("b").
Instantiate(ctx)
if err != nil {
panic(err)
}
md, err := rt.InstantiateWithConfig(ctx, wasmdata,
wazero.NewModuleConfig())
if err != nil {
_ = rt.Close(ctx)
panic(err)
}
sg.rt = rt
sg.md = md
sg.genKey = md.ExportedFunction("f")
sg.genKeyNew = md.ExportedFunction("g")
sg.genKeyWithoutURI = md.ExportedFunction("h")
sg._malloc = md.ExportedFunction("i")
sg._free = md.ExportedFunction("j")
return
}
// GenKey is the go repr of js func
//
// generatorSign(e) {
// const t = Module.lengthBytesUTF8(e.secretKey) + 1
// , n = Module._malloc(t);
// Module.stringToUTF8(e.secretKey, n, t);
// const r = Module._gen_key(e.deviceType, BigInt(e.timestamp), BigInt(e.requestId), n, e.tenantId, e.userId, e.version)
// , o = Module.UTF8ToString(r);
// return Module._free(n),
// Module._free(r),
// o
// }
func (sg *Signer) GenKey(
ctx context.Context, deviceType, timestamp, requestID uint64,
secretKey string, tenantID, userID, version uint64,
) string {
t := len(secretKey) + 1
n := sg.malloc(ctx, uint64(t))
if !sg.md.Memory().WriteString(uint32(n), secretKey+"\x00") {
panic("write out-of-bound")
}
defer sg.free(ctx, n)
return sg.string(op.Must(sg.genKey.Call(
ctx, deviceType, timestamp, requestID,
n, tenantID, userID, version),
)[0])
}
// GenKeyNew is the go repr of js func
//
// generatorSignNew(e) {
// const t = Module.lengthBytesUTF8(e.secretKey) + 1
// , n = Module._malloc(t);
// Module.stringToUTF8(e.secretKey, n, t);
// const r = Module.lengthBytesUTF8(e.userEid) + 1
// , o = Module._malloc(r);
// Module.stringToUTF8(e.userEid, o, r);
// const i = Module.lengthBytesUTF8(e.requestUri) + 1
// , a = Module._malloc(i);
// Module.stringToUTF8(e.requestUri, a, i);
// const s = Module.lengthBytesUTF8(this.serverNode) + 1
// , l = Module._malloc(s);
// Module.stringToUTF8(this.serverNode, l, s);
// const c = Module._gen_key_new(e.deviceType, BigInt(e.timestamp), BigInt(e.requestId), n, o, a, l, e.version)
// , u = Module.UTF8ToString(c);
// return Module._free(n),
// Module._free(o),
// Module._free(a),
// Module._free(l),
// Module._free(c),
// u
// }
func (sg *Signer) GenKeyNew(
ctx context.Context, deviceType, timestamp, requestID uint64,
secretKey, userEID, requestURI, serverNode string, version uint64,
) string {
t := len(secretKey) + 1
n := sg.malloc(ctx, uint64(t))
if !sg.md.Memory().WriteString(uint32(n), secretKey+"\x00") {
panic("write out-of-bound")
}
defer sg.free(ctx, n)
r := len(userEID) + 1
o := sg.malloc(ctx, uint64(r))
if !sg.md.Memory().WriteString(uint32(o), userEID+"\x00") {
panic("write out-of-bound")
}
defer sg.free(ctx, o)
i := len(requestURI) + 1
a := sg.malloc(ctx, uint64(i))
if !sg.md.Memory().WriteString(uint32(a), requestURI+"\x00") {
panic("write out-of-bound")
}
defer sg.free(ctx, a)
s := len(serverNode) + 1
l := sg.malloc(ctx, uint64(s))
if !sg.md.Memory().WriteString(uint32(l), serverNode+"\x00") {
panic("write out-of-bound")
}
defer sg.free(ctx, l)
return sg.string(op.Must(sg.genKeyNew.Call(
ctx, deviceType, timestamp, requestID,
n, o, a, l, version),
)[0])
}
func (sg *Signer) malloc(ctx context.Context, n uint64) uint64 {
return op.Must(sg._malloc.Call(ctx, n))[0]
}
func (sg *Signer) free(ctx context.Context, n uint64) {
op.Must(sg._free.Call(ctx, n))
}
func (sg *Signer) string(ptr uint64) string {
buf := strings.Builder{}
x := uint32(ptr)
for {
b, ok := sg.md.Memory().ReadByte(x)
x++
if !ok {
panic("read out-of-bound")
}
if b == 0 {
break
}
buf.WriteByte(b)
}
return buf.String()
}
func (sg *Signer) IsClosed() bool {
return sg.md == nil || sg.rt == nil || sg.md.IsClosed() ||
sg.genKey == nil || sg.genKeyNew == nil || sg.genKeyWithoutURI == nil ||
sg._malloc == nil || sg._free == nil
}
func (sg *Signer) Close(ctx context.Context) {
if sg.md != nil && !sg.md.IsClosed() {
sg.md.Close(ctx)
sg.md = nil
}
if sg.rt != nil {
sg.rt.Close(ctx)
sg.rt = nil
}
}

20
wasm_test.go Normal file
View File

@@ -0,0 +1,20 @@
package tienyik
import (
"context"
"testing"
)
func TestSigner(t *testing.T) {
ctx := context.Background()
sg := NewSigner(ctx)
sigstr := sg.GenKeyNew(
ctx, 60, 1763891935806, 1763891937568, "9c047f01dfab388a3ef7c4ae34855e3a",
"6806caebc2cdaef5f10987a94d21cf1f", "/api/cdserv/client/msgcenter/page",
"8aff800b3c96e620043bb7deb4a0258d", 103010001,
)
if sigstr != "66443F2BF714E1D2B4AC8D3B3CBC3913" {
t.Fatal("got", sigstr)
}
t.Fail()
}