cmd/hakurei: short identifier from lower half
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Test / Create distribution (push) Successful in 33s
				
			
		
			
				
	
				Test / Sandbox (push) Successful in 39s
				
			
		
			
				
	
				Test / Sandbox (race detector) (push) Successful in 40s
				
			
		
			
				
	
				Test / Hakurei (push) Successful in 2m14s
				
			
		
			
				
	
				Test / Hakurei (race detector) (push) Successful in 2m57s
				
			
		
			
				
	
				Test / Hpkg (push) Successful in 3m12s
				
			
		
			
				
	
				Test / Flake checks (push) Successful in 1m25s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Test / Create distribution (push) Successful in 33s
				
			Test / Sandbox (push) Successful in 39s
				
			Test / Sandbox (race detector) (push) Successful in 40s
				
			Test / Hakurei (push) Successful in 2m14s
				
			Test / Hakurei (race detector) (push) Successful in 2m57s
				
			Test / Hpkg (push) Successful in 3m12s
				
			Test / Flake checks (push) Successful in 1m25s
				
			The upper half is now a nanosecond timestamp. Lower half is still random bytes, so use lower half for short identifier. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
		
							parent
							
								
									2442eda8d9
								
							
						
					
					
						commit
						7de593e816
					
				| @ -301,7 +301,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr | |||||||
| 
 | 
 | ||||||
| 			case 1: // instance | 			case 1: // instance | ||||||
| 				name := args[0] | 				name := args[0] | ||||||
| 				config, entry := tryShort(msg, name) | 				config, entry := tryIdentifier(msg, name) | ||||||
| 				if config == nil { | 				if config == nil { | ||||||
| 					config = tryPath(msg, name) | 					config = tryPath(msg, name) | ||||||
| 				} | 				} | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| package main | package main | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"encoding/hex" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"io" | 	"io" | ||||||
| 	"log" | 	"log" | ||||||
| @ -15,6 +16,9 @@ import ( | |||||||
| 	"hakurei.app/message" | 	"hakurei.app/message" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | // tryPath attempts to read [hst.Config] from multiple sources. | ||||||
|  | // tryPath reads from [os.Stdin] if name has value "-". | ||||||
|  | // Otherwise, name is passed to tryFd, and if that returns nil, name is passed to [os.Open]. | ||||||
| func tryPath(msg message.Msg, name string) (config *hst.Config) { | func tryPath(msg message.Msg, name string) (config *hst.Config) { | ||||||
| 	var r io.ReadCloser | 	var r io.ReadCloser | ||||||
| 	config = new(hst.Config) | 	config = new(hst.Config) | ||||||
| @ -42,6 +46,7 @@ func tryPath(msg message.Msg, name string) (config *hst.Config) { | |||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // tryFd returns a [io.ReadCloser] if name represents an integer corresponding to a valid file descriptor. | ||||||
| func tryFd(msg message.Msg, name string) io.ReadCloser { | func tryFd(msg message.Msg, name string) io.ReadCloser { | ||||||
| 	if v, err := strconv.Atoi(name); err != nil { | 	if v, err := strconv.Atoi(name); err != nil { | ||||||
| 		if !errors.Is(err, strconv.ErrSyntax) { | 		if !errors.Is(err, strconv.ErrSyntax) { | ||||||
| @ -61,10 +66,48 @@ func tryFd(msg message.Msg, name string) io.ReadCloser { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func tryShort(msg message.Msg, name string) (config *hst.Config, entry *hst.State) { | // shortLengthMin is the minimum length a short form identifier can have and still be interpreted as an identifier. | ||||||
| 	likePrefix := false | const shortLengthMin = 1 << 3 | ||||||
| 	if len(name) <= 32 { | 
 | ||||||
| 		likePrefix = true | // shortIdentifier returns an eight character short representation of [hst.ID] from its random bytes. | ||||||
|  | func shortIdentifier(id *hst.ID) string { | ||||||
|  | 	return shortIdentifierString(id.String()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // shortIdentifierString implements shortIdentifier on an arbitrary string. | ||||||
|  | func shortIdentifierString(s string) string { | ||||||
|  | 	return s[len(hst.ID{}) : len(hst.ID{})+shortLengthMin] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // tryIdentifier attempts to match [hst.State] from a [hex] representation of [hst.ID] or a prefix of its lower half. | ||||||
|  | func tryIdentifier(msg message.Msg, name string) (config *hst.Config, entry *hst.State) { | ||||||
|  | 	return tryIdentifierEntries(msg, name, func() map[hst.ID]*hst.State { | ||||||
|  | 		var sc hst.Paths | ||||||
|  | 		app.CopyPaths().Copy(&sc, new(app.Hsu).MustID(nil)) | ||||||
|  | 		s := state.NewMulti(msg, sc.RunDirPath.String()) | ||||||
|  | 		if entries, err := state.Join(s); err != nil { | ||||||
|  | 			msg.GetLogger().Printf("cannot join store: %v", err) // not fatal | ||||||
|  | 			return nil | ||||||
|  | 		} else { | ||||||
|  | 			return entries | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // tryIdentifierEntries implements tryIdentifier with a custom entries pair getter. | ||||||
|  | func tryIdentifierEntries( | ||||||
|  | 	msg message.Msg, | ||||||
|  | 	name string, | ||||||
|  | 	getEntries func() map[hst.ID]*hst.State, | ||||||
|  | ) (config *hst.Config, entry *hst.State) { | ||||||
|  | 	const ( | ||||||
|  | 		likeShort = 1 << iota | ||||||
|  | 		likeFull | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	var likely uintptr | ||||||
|  | 	if len(name) >= shortLengthMin && len(name) <= len(hst.ID{}) { // half the hex representation | ||||||
|  | 		// cannot safely decode here due to unknown alignment | ||||||
| 		for _, c := range name { | 		for _, c := range name { | ||||||
| 			if c >= '0' && c <= '9' { | 			if c >= '0' && c <= '9' { | ||||||
| 				continue | 				continue | ||||||
| @ -72,35 +115,50 @@ func tryShort(msg message.Msg, name string) (config *hst.Config, entry *hst.Stat | |||||||
| 			if c >= 'a' && c <= 'f' { | 			if c >= 'a' && c <= 'f' { | ||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
| 			likePrefix = false | 			return | ||||||
| 			break |  | ||||||
| 		} | 		} | ||||||
|  | 		likely |= likeShort | ||||||
|  | 	} else if len(name) == hex.EncodedLen(len(hst.ID{})) { | ||||||
|  | 		likely |= likeFull | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// try to match from state store | 	if likely == 0 { | ||||||
| 	if likePrefix && len(name) >= 8 { | 		return | ||||||
| 		msg.Verbose("argument looks like prefix") | 	} | ||||||
|  | 	entries := getEntries() | ||||||
|  | 	if entries == nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 		var sc hst.Paths | 	switch { | ||||||
| 		app.CopyPaths().Copy(&sc, new(app.Hsu).MustID(nil)) | 	case likely&likeShort != 0: | ||||||
| 		s := state.NewMulti(msg, sc.RunDirPath.String()) | 		msg.Verbose("argument looks like short identifier") | ||||||
| 		if entries, err := state.Join(s); err != nil { | 		for id := range entries { | ||||||
| 			log.Printf("cannot join store: %v", err) | 			v := id.String() | ||||||
| 			// drop to fetch from file | 			if strings.HasPrefix(v[len(hst.ID{}):], name) { | ||||||
| 		} else { | 				// match, use config from this state entry | ||||||
| 			for id := range entries { | 				entry = entries[id] | ||||||
| 				v := id.String() | 				config = entry.Config | ||||||
| 				if strings.HasPrefix(v, name) { | 				break | ||||||
| 					// match, use config from this state entry |  | ||||||
| 					entry = entries[id] |  | ||||||
| 					config = entry.Config |  | ||||||
| 					break |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				msg.Verbosef("instance %s skipped", v) |  | ||||||
| 			} | 			} | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	return | 			msg.Verbosef("instance %s skipped", v) | ||||||
|  | 		} | ||||||
|  | 		return | ||||||
|  | 
 | ||||||
|  | 	case likely&likeFull != 0: | ||||||
|  | 		var likelyID hst.ID | ||||||
|  | 		if likelyID.UnmarshalText([]byte(name)) != nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		msg.Verbose("argument looks like identifier") | ||||||
|  | 		if ent, ok := entries[likelyID]; ok { | ||||||
|  | 			entry = ent | ||||||
|  | 			config = ent.Config | ||||||
|  | 		} | ||||||
|  | 		return | ||||||
|  | 
 | ||||||
|  | 	default: | ||||||
|  | 		panic("unreachable") | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										101
									
								
								cmd/hakurei/parse_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								cmd/hakurei/parse_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,101 @@ | |||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"reflect" | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"hakurei.app/hst" | ||||||
|  | 	"hakurei.app/message" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestShortIdentifier(t *testing.T) { | ||||||
|  | 	t.Parallel() | ||||||
|  | 	id := hst.ID{ | ||||||
|  | 		0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, | ||||||
|  | 		0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	const want = "fedcba98" | ||||||
|  | 	if got := shortIdentifier(&id); got != want { | ||||||
|  | 		t.Errorf("shortIdentifier: %q, want %q", got, want) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestTryIdentifier(t *testing.T) { | ||||||
|  | 	t.Parallel() | ||||||
|  | 	msg := message.NewMsg(nil) | ||||||
|  | 	id := hst.ID{ | ||||||
|  | 		0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, | ||||||
|  | 		0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	testCases := []struct { | ||||||
|  | 		name    string | ||||||
|  | 		s       string | ||||||
|  | 		entries map[hst.ID]*hst.State | ||||||
|  | 		want    *hst.State | ||||||
|  | 	}{ | ||||||
|  | 		{"likely entries fault", "ffffffff", nil, nil}, | ||||||
|  | 
 | ||||||
|  | 		{"likely short too short", "ff", nil, nil}, | ||||||
|  | 		{"likely short too long", "fffffffffffffffff", nil, nil}, | ||||||
|  | 		{"likely short invalid lower", "fffffff\x00", nil, nil}, | ||||||
|  | 		{"likely short invalid higher", "0000000\xff", nil, nil}, | ||||||
|  | 		{"short no match", "fedcba98", map[hst.ID]*hst.State{hst.ID{}: nil}, nil}, | ||||||
|  | 		{"short match", "fedcba98", map[hst.ID]*hst.State{ | ||||||
|  | 			hst.ID{}: nil, | ||||||
|  | 			id: { | ||||||
|  | 				ID:      id, | ||||||
|  | 				PID:     0xcafebabe, | ||||||
|  | 				ShimPID: 0xdeadbeef, | ||||||
|  | 				Config:  hst.Template(), | ||||||
|  | 			}, | ||||||
|  | 		}, &hst.State{ | ||||||
|  | 			ID:      id, | ||||||
|  | 			PID:     0xcafebabe, | ||||||
|  | 			ShimPID: 0xdeadbeef, | ||||||
|  | 			Config:  hst.Template(), | ||||||
|  | 		}}, | ||||||
|  | 		{"short match longer", "fedcba98765", map[hst.ID]*hst.State{ | ||||||
|  | 			hst.ID{}: nil, | ||||||
|  | 			id: { | ||||||
|  | 				ID:      id, | ||||||
|  | 				PID:     0xcafebabe, | ||||||
|  | 				ShimPID: 0xdeadbeef, | ||||||
|  | 				Config:  hst.Template(), | ||||||
|  | 			}, | ||||||
|  | 		}, &hst.State{ | ||||||
|  | 			ID:      id, | ||||||
|  | 			PID:     0xcafebabe, | ||||||
|  | 			ShimPID: 0xdeadbeef, | ||||||
|  | 			Config:  hst.Template(), | ||||||
|  | 		}}, | ||||||
|  | 
 | ||||||
|  | 		{"likely long invalid", "0123456789abcdeffedcba987654321\x00", map[hst.ID]*hst.State{}, nil}, | ||||||
|  | 		{"long no match", "0123456789abcdeffedcba9876543210", map[hst.ID]*hst.State{hst.ID{}: nil}, nil}, | ||||||
|  | 		{"long match", "0123456789abcdeffedcba9876543210", map[hst.ID]*hst.State{ | ||||||
|  | 			hst.ID{}: nil, | ||||||
|  | 			id: { | ||||||
|  | 				ID:      id, | ||||||
|  | 				PID:     0xcafebabe, | ||||||
|  | 				ShimPID: 0xdeadbeef, | ||||||
|  | 				Config:  hst.Template(), | ||||||
|  | 			}, | ||||||
|  | 		}, &hst.State{ | ||||||
|  | 			ID:      id, | ||||||
|  | 			PID:     0xcafebabe, | ||||||
|  | 			ShimPID: 0xdeadbeef, | ||||||
|  | 			Config:  hst.Template(), | ||||||
|  | 		}}, | ||||||
|  | 	} | ||||||
|  | 	for _, tc := range testCases { | ||||||
|  | 		t.Run(tc.name, func(t *testing.T) { | ||||||
|  | 			t.Parallel() | ||||||
|  | 
 | ||||||
|  | 			_, got := tryIdentifierEntries(msg, tc.s, func() map[hst.ID]*hst.State { return tc.entries }) | ||||||
|  | 			if !reflect.DeepEqual(got, tc.want) { | ||||||
|  | 				t.Errorf("tryIdentifier: %#v, want %#v", got, tc.want) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @ -215,7 +215,7 @@ func printPs(output io.Writer, now time.Time, s state.Store, short, flagJSON boo | |||||||
| 			encodeJSON(log.Fatal, output, short, v) | 			encodeJSON(log.Fatal, output, short, v) | ||||||
| 		} else { | 		} else { | ||||||
| 			for _, e := range exp { | 			for _, e := range exp { | ||||||
| 				mustPrintln(output, e.s[:8]) | 				mustPrintln(output, shortIdentifierString(e.s)) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		return | 		return | ||||||
| @ -237,12 +237,12 @@ func printPs(output io.Writer, now time.Time, s state.Store, short, flagJSON boo | |||||||
| 			as = strconv.Itoa(e.Config.Identity) | 			as = strconv.Itoa(e.Config.Identity) | ||||||
| 			id := e.Config.ID | 			id := e.Config.ID | ||||||
| 			if id == "" { | 			if id == "" { | ||||||
| 				id = "app.hakurei." + e.s[:8] | 				id = "app.hakurei." + shortIdentifierString(e.s) | ||||||
| 			} | 			} | ||||||
| 			as += " (" + id + ")" | 			as += " (" + id + ")" | ||||||
| 		} | 		} | ||||||
| 		t.Printf("\t%s\t%d\t%s\t%s\n", | 		t.Printf("\t%s\t%d\t%s\t%s\n", | ||||||
| 			e.s[:8], e.PID, as, now.Sub(e.Time).Round(time.Second).String()) | 			shortIdentifierString(e.s), e.PID, as, now.Sub(e.Time).Round(time.Second).String()) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -523,13 +523,13 @@ func TestPrintPs(t *testing.T) { | |||||||
| 		{"state corruption", map[hst.ID]*hst.State{hst.ID{}: testState}, false, false, "    Instance    PID    Application    Uptime\n"}, | 		{"state corruption", map[hst.ID]*hst.State{hst.ID{}: testState}, false, false, "    Instance    PID    Application    Uptime\n"}, | ||||||
| 
 | 
 | ||||||
| 		{"valid pd", map[hst.ID]*hst.State{testID: {ID: testID, PID: 1 << 8, Config: new(hst.Config), Time: testAppTime}}, false, false, `    Instance    PID    Application                 Uptime | 		{"valid pd", map[hst.ID]*hst.State{testID: {ID: testID, PID: 1 << 8, Config: new(hst.Config), Time: testAppTime}}, false, false, `    Instance    PID    Application                 Uptime | ||||||
|     8e2c76b0    256    0 (app.hakurei.8e2c76b0)    1h2m32s |     4cf073bd    256    0 (app.hakurei.4cf073bd)    1h2m32s | ||||||
| `}, | `}, | ||||||
| 
 | 
 | ||||||
| 		{"valid", map[hst.ID]*hst.State{testID: testState}, false, false, `    Instance    PID           Application                  Uptime | 		{"valid", map[hst.ID]*hst.State{testID: testState}, false, false, `    Instance    PID           Application                  Uptime | ||||||
|     8e2c76b0    3405691582    9 (org.chromium.Chromium)    1h2m32s |     4cf073bd    3405691582    9 (org.chromium.Chromium)    1h2m32s | ||||||
| `}, | `}, | ||||||
| 		{"valid short", map[hst.ID]*hst.State{testID: testState}, true, false, "8e2c76b0\n"}, | 		{"valid short", map[hst.ID]*hst.State{testID: testState}, true, false, "4cf073bd\n"}, | ||||||
| 		{"valid json", map[hst.ID]*hst.State{testID: testState}, false, true, `{ | 		{"valid json", map[hst.ID]*hst.State{testID: testState}, false, true, `{ | ||||||
|   "8e2c76b066dabe574cf073bdb46eb5c1": { |   "8e2c76b066dabe574cf073bdb46eb5c1": { | ||||||
|     "instance": "8e2c76b066dabe574cf073bdb46eb5c1", |     "instance": "8e2c76b066dabe574cf073bdb46eb5c1", | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user