1
0
mirror of https://github.com/fumiama/tienyik.git synced 2026-06-27 22:30:29 +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 # MacOS
.DS_Store .DS_Store
# WASM
*.wasm
# configs
*.yaml

View File

@@ -94,11 +94,11 @@ type RequestLogin struct {
ChallengeID string `form:"challengeId"` ChallengeID string `form:"challengeId"`
DeviceCode string `form:"deviceCode"` DeviceCode string `form:"deviceCode"`
DeviceName string `form:"deviceName"` DeviceName string `form:"deviceName"`
DeviceType string `form:"deviceType"` DeviceType uint64 `form:"deviceType"`
DeviceModel string `form:"deviceModel"` DeviceModel string `form:"deviceModel"`
AppVersion string `form:"appVersion"` AppVersion string `form:"appVersion"`
SysVersion string `form:"sysVersion"` SysVersion string `form:"sysVersion"`
ClientVersion string `form:"clientVersion"` ClientVersion uint64 `form:"clientVersion"`
} }
type ResponseLogin struct { type ResponseLogin struct {
@@ -135,6 +135,8 @@ type ResponseLogin struct {
func (r *ResponseLogin) SetClient(cli *hcli.Client) { func (r *ResponseLogin) SetClient(cli *hcli.Client) {
cli.Tenantid = strconv.FormatInt(r.TenantID, 10) cli.Tenantid = strconv.FormatInt(r.TenantID, 10)
cli.Usereid = r.UserEid cli.Usereid = r.UserEid
cli.SetSecretKey(r.SecretKey)
cli.SetTimestamp(r.Timestamp)
} }
func Login(tya *tienyik.AES, cli *hcli.Client, r *RequestLogin) (*ResponseLogin, error) { 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() defer resp.Body.Close()
return hson.Unmarshal[*ResponseLogin](tya, resp.Body) 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 ( import (
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"os"
"testing" "testing"
"github.com/fumiama/tienyik" "github.com/fumiama/tienyik"
"github.com/fumiama/tienyik/api/cdserv"
"github.com/fumiama/tienyik/hcli"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@@ -36,3 +39,39 @@ func TestNegotiationEncKey(t *testing.T) {
t.Fatal(err) 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" "strings"
) )
const Version = "103010001" const (
Version = 103010001
AppVersion = "3.1.0"
)
const ( const (
DeviceTypePC = "25" DeviceTypePC = 25
DeviceTypeMAC = "45" DeviceTypeMAC = 45
DeviceTypeWEB = "60" DeviceTypeWEB = 60
) )
const ( const (
@@ -21,6 +24,21 @@ const (
AppModelPHONE = "3" 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 { func NewDeviceCode() string {
sb := &strings.Builder{} sb := &strings.Builder{}
sb.WriteString("web_") 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 ( require (
github.com/fumiama/go-base16384 v1.7.1 github.com/fumiama/go-base16384 v1.7.1
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/tetratelabs/wazero v1.9.0
golang.org/x/net v0.28.0 golang.org/x/net v0.28.0
golang.org/x/term v0.23.0
gopkg.in/yaml.v3 v3.0.1
) )
require ( 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.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 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 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 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= 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.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 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.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 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 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= 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/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.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@@ -1,6 +1,7 @@
package hcli package hcli
import ( import (
"context"
"net/http" "net/http"
"reflect" "reflect"
"strconv" "strconv"
@@ -21,17 +22,22 @@ var DefaultClient = http.Client{
type Client struct { type Client struct {
rcnt uintptr rcnt uintptr
sg tienyik.Signer
secretKey string
offsetTime int64
Appmodel string Appmodel string
Devicecode string Devicecode string
Devicetype string Devicetype uint64
Servernode string Servernode string
Tenantid string Tenantid string
Usereid string Usereid string
Version string Version uint64
} }
func NewClient() *Client { func NewClient() *Client {
return &Client{ return &Client{
sg: tienyik.NewSigner(context.TODO()),
Appmodel: tienyik.AppModelTOB, Appmodel: tienyik.AppModelTOB,
Devicecode: tienyik.NewDeviceCode(), Devicecode: tienyik.NewDeviceCode(),
Devicetype: tienyik.DeviceTypeWEB, 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) { func (c *Client) setHeaders(req *http.Request) {
if c == nil { if c == nil {
return return
@@ -54,27 +69,39 @@ func (c *Client) setHeaders(req *http.Request) {
} }
fieldValue := v.Field(i) fieldValue := v.Field(i)
if fieldValue.IsZero() {
continue
}
k := base14.DecodeString("廝呲舀㴄") + field.Name
if fieldValue.Kind() == reflect.String { switch fieldValue.Kind() {
req.Header.Set( case reflect.String:
base14.DecodeString("廝呲舀㴄")+field.Name, req.Header.Set(k, fieldValue.String())
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 != "" { if c.Appmodel != "" {
ts := time.Now().UnixMilli() 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) 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("廝呲草獱歙攷徥爀㴆"), requestid)
req.Header.Set(base14.DecodeString("廝呲荑睭杜蕆厵縀㴆"), timestamp) req.Header.Set(base14.DecodeString("廝呲荑睭杜蕆厵縀㴆"), timestamp)
}
if c.Servernode != "" { if c.secretKey != "" {
//TODO: gensign 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 ( import (
"net/url" "net/url"
"reflect" "reflect"
"strconv"
"github.com/fumiama/tienyik" "github.com/fumiama/tienyik"
"github.com/fumiama/tienyik/internal/textio" "github.com/fumiama/tienyik/internal/textio"
@@ -22,8 +23,13 @@ func Marshal(tya *tienyik.AES, x any) []byte {
continue continue
} }
if field.Kind() == reflect.String { switch field.Kind() {
case reflect.String:
q.Set(formTag, field.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()
}