mirror of
https://github.com/blacktop/ipsw.git
synced 2026-06-07 12:27:36 +00:00
445 lines
10 KiB
Go
445 lines
10 KiB
Go
package tss
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestRandomECID(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "generate random ECID",
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, err := RandomECID()
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("RandomECID() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if got == 0 {
|
|
t.Errorf("RandomECID() should not return 0")
|
|
}
|
|
// Test that multiple calls return different values
|
|
got2, err := RandomECID()
|
|
if err != nil {
|
|
t.Errorf("RandomECID() second call error = %v", err)
|
|
}
|
|
if got == got2 {
|
|
t.Errorf("RandomECID() should return different values on multiple calls")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRandomHex(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
size int
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "generate 32 byte hex",
|
|
size: 32,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "generate 20 byte hex",
|
|
size: 20,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "generate 0 byte hex",
|
|
size: 0,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, err := randomHex(tt.size)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("randomHex() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if len(got) != tt.size {
|
|
t.Errorf("randomHex() got length %d, want %d", len(got), tt.size)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestApplyRestoreRequestRules(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
entry map[string]any
|
|
parameters map[string]any
|
|
rules any
|
|
wantEntry map[string]any
|
|
}{
|
|
{
|
|
name: "no rules",
|
|
entry: map[string]any{"Digest": "test"},
|
|
parameters: map[string]any{
|
|
"ApProductionMode": true,
|
|
"ApSecurityMode": true,
|
|
},
|
|
rules: nil,
|
|
wantEntry: map[string]any{"Digest": "test"},
|
|
},
|
|
{
|
|
name: "invalid rules format",
|
|
entry: map[string]any{"Digest": "test"},
|
|
parameters: map[string]any{
|
|
"ApProductionMode": true,
|
|
},
|
|
rules: "invalid",
|
|
wantEntry: map[string]any{"Digest": "test"},
|
|
},
|
|
{
|
|
name: "rule with fulfilled conditions",
|
|
entry: map[string]any{"Digest": "test"},
|
|
parameters: map[string]any{
|
|
"ApProductionMode": true,
|
|
"ApSecurityMode": true,
|
|
},
|
|
rules: []any{
|
|
map[string]any{
|
|
"Conditions": map[string]any{
|
|
"ApRawProductionMode": true,
|
|
"ApRequiresImage4": true,
|
|
},
|
|
"Actions": map[string]any{
|
|
"EPRO": true,
|
|
"ESEC": false,
|
|
},
|
|
},
|
|
},
|
|
wantEntry: map[string]any{
|
|
"Digest": "test",
|
|
"EPRO": true,
|
|
"ESEC": false,
|
|
},
|
|
},
|
|
{
|
|
name: "rule with unfulfilled conditions",
|
|
entry: map[string]any{"Digest": "test"},
|
|
parameters: map[string]any{
|
|
"ApProductionMode": false,
|
|
"ApSecurityMode": true,
|
|
},
|
|
rules: []any{
|
|
map[string]any{
|
|
"Conditions": map[string]any{
|
|
"ApRawProductionMode": true,
|
|
},
|
|
"Actions": map[string]any{
|
|
"EPRO": true,
|
|
},
|
|
},
|
|
},
|
|
wantEntry: map[string]any{"Digest": "test"},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
applyRestoreRequestRules(tt.entry, tt.parameters, tt.rules)
|
|
for k, v := range tt.wantEntry {
|
|
if got, ok := tt.entry[k]; !ok || got != v {
|
|
t.Errorf("applyRestoreRequestRules() entry[%s] = %v, want %v", k, got, v)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetApImg4Ticket(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
payload string
|
|
response string
|
|
statusCode int
|
|
wantErr bool
|
|
errContains string
|
|
}{
|
|
{
|
|
name: "successful response",
|
|
payload: "<plist></plist>",
|
|
response: "STATUS=0&MESSAGE=SUCCESS&REQUEST_STRING=<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\"><dict><key>ApImg4Ticket</key><data>dGVzdA==</data></dict></plist>",
|
|
statusCode: 200,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "not signed response",
|
|
payload: "<plist></plist>",
|
|
response: "STATUS=94&MESSAGE=This device isn't eligible for the requested build.",
|
|
statusCode: 200,
|
|
wantErr: true,
|
|
errContains: "not signed",
|
|
},
|
|
{
|
|
name: "server error",
|
|
payload: "<plist></plist>",
|
|
response: "",
|
|
statusCode: 500,
|
|
wantErr: true,
|
|
errContains: "failed to connect",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "POST" {
|
|
t.Errorf("Expected POST request, got %s", r.Method)
|
|
}
|
|
if r.Header.Get("Content-type") != "text/xml; charset=\"utf-8\"" {
|
|
t.Errorf("Expected content type 'text/xml; charset=\"utf-8\"', got %s", r.Header.Get("Content-type"))
|
|
}
|
|
w.WriteHeader(tt.statusCode)
|
|
w.Write([]byte(tt.response))
|
|
}))
|
|
defer server.Close()
|
|
|
|
// Temporarily replace the TSS URL for testing
|
|
originalURL := tssControllerActionURL
|
|
defer func() {
|
|
// This is a bit of a hack since tssControllerActionURL is a const
|
|
// In a real scenario, we'd make this configurable
|
|
}()
|
|
|
|
// We can't easily test getApImg4Ticket directly due to the const URL
|
|
// This test mainly validates the logic structure
|
|
_ = originalURL
|
|
_ = server.URL
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPersonalize_InvalidInput(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
conf *PersonalConfig
|
|
wantErr bool
|
|
errContains string
|
|
}{
|
|
{
|
|
name: "invalid hex nonce",
|
|
conf: &PersonalConfig{
|
|
PersonlID: map[string]any{
|
|
"ApNonce": "invalid-hex",
|
|
},
|
|
},
|
|
wantErr: true,
|
|
errContains: "failed to decode nonce",
|
|
},
|
|
{
|
|
name: "missing build manifest",
|
|
conf: &PersonalConfig{
|
|
PersonlID: map[string]any{
|
|
"ApNonce": "0000000000000000000000000000000000000000000000000000000000000000",
|
|
"BoardId": float64(8),
|
|
"ChipID": float64(33040),
|
|
"UniqueChipID": float64(6303405673529390),
|
|
},
|
|
BuildManifest: nil,
|
|
},
|
|
wantErr: true,
|
|
errContains: "nil pointer", // This will catch the panic scenario
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
if !tt.wantErr {
|
|
t.Errorf("Personalize() panic = %v, wantErr %v", r, tt.wantErr)
|
|
}
|
|
}
|
|
}()
|
|
|
|
_, err := Personalize(tt.conf)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("Personalize() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if err != nil && tt.errContains != "" && !strings.Contains(err.Error(), tt.errContains) {
|
|
t.Errorf("Personalize() error = %v, want error containing %s", err, tt.errContains)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRequest_Validation(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
request Request
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "valid request",
|
|
request: Request{
|
|
UUID: "test-uuid",
|
|
ApImg4Ticket: true,
|
|
HostPlatformInfo: "mac",
|
|
VersionInfo: tssClientVersion,
|
|
ApBoardID: 8,
|
|
ApChipID: 33040,
|
|
ApECID: 6303405673529390,
|
|
ApNonce: make([]byte, 32),
|
|
ApProductionMode: true,
|
|
ApSecurityDomain: 1,
|
|
ApSecurityMode: true,
|
|
UIDMode: false,
|
|
SepNonce: make([]byte, 20),
|
|
},
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Test that the request struct can be properly populated
|
|
if tt.request.UUID == "" {
|
|
t.Errorf("Request UUID should not be empty")
|
|
}
|
|
if tt.request.ApECID == 0 {
|
|
t.Errorf("Request ApECID should not be zero")
|
|
}
|
|
if len(tt.request.ApNonce) == 0 {
|
|
t.Errorf("Request ApNonce should not be empty")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetTSSResponse_EdgeCases(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
conf *Config
|
|
wantErr bool
|
|
errContains string
|
|
}{
|
|
{
|
|
name: "zero ECID",
|
|
conf: &Config{
|
|
Device: "iPhone10,1",
|
|
Version: "15.0",
|
|
Build: "19A346",
|
|
ECID: 0,
|
|
},
|
|
wantErr: true,
|
|
errContains: "ECID must be provided",
|
|
},
|
|
{
|
|
name: "missing info",
|
|
conf: &Config{
|
|
Device: "iPhone10,1",
|
|
Version: "15.0",
|
|
Build: "19A346",
|
|
ECID: 123456789,
|
|
Info: nil,
|
|
},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
if !tt.wantErr {
|
|
t.Errorf("GetTSSResponse() panic = %v, wantErr %v", r, tt.wantErr)
|
|
}
|
|
}
|
|
}()
|
|
|
|
_, err := GetTSSResponse(tt.conf)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("GetTSSResponse() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if err != nil && tt.errContains != "" && !strings.Contains(err.Error(), tt.errContains) {
|
|
t.Errorf("GetTSSResponse() error = %v, want error containing %s", err, tt.errContains)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBlob_Structure(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
blob Blob
|
|
}{
|
|
{
|
|
name: "valid blob structure",
|
|
blob: Blob{
|
|
ServerVersion: "1.0",
|
|
ApImg4Ticket: []byte("test-ticket"),
|
|
BBTicket: []byte("test-bb-ticket"),
|
|
EUICCTicket: []byte("test-euicc-ticket"),
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.blob.ServerVersion == "" {
|
|
t.Errorf("Blob ServerVersion should not be empty")
|
|
}
|
|
if len(tt.blob.ApImg4Ticket) == 0 {
|
|
t.Errorf("Blob ApImg4Ticket should not be empty")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHexDecoding(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
hexStr string
|
|
wantLen int
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "valid 32-byte hex",
|
|
hexStr: "0000000000000000000000000000000000000000000000000000000000000000",
|
|
wantLen: 32,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "valid 20-byte hex",
|
|
hexStr: "0000000000000000000000000000000000000000",
|
|
wantLen: 20,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "invalid hex",
|
|
hexStr: "invalid-hex-string",
|
|
wantLen: 0,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "empty hex",
|
|
hexStr: "",
|
|
wantLen: 0,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, err := hex.DecodeString(tt.hexStr)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("hex.DecodeString() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if len(got) != tt.wantLen {
|
|
t.Errorf("hex.DecodeString() length = %d, want %d", len(got), tt.wantLen)
|
|
}
|
|
})
|
|
}
|
|
}
|