1
0
mirror of https://github.com/v2fly/v2ray-core.git synced 2024-06-25 17:05:23 +00:00

rename VMessAccount to vmess.Account

This commit is contained in:
v2ray 2016-07-25 17:36:24 +02:00
parent 2049759640
commit 2034d54bab
No known key found for this signature in database
GPG Key ID: 7251FFA14BB18169
17 changed files with 288 additions and 274 deletions

View File

@ -1,30 +1,5 @@
package protocol
import (
"github.com/v2ray/v2ray-core/common/dice"
)
type Account interface {
Equals(Account) bool
}
type VMessAccount struct {
ID *ID
AlterIDs []*ID
}
func (this *VMessAccount) AnyValidID() *ID {
if len(this.AlterIDs) == 0 {
return this.ID
}
return this.AlterIDs[dice.Roll(len(this.AlterIDs))]
}
func (this *VMessAccount) Equals(account Account) bool {
vmessAccount, ok := account.(*VMessAccount)
if !ok {
return false
}
// TODO: handle AlterIds difference
return this.ID.Equals(vmessAccount.ID)
}

View File

@ -1,36 +0,0 @@
// +build json
package protocol
import (
"errors"
"github.com/v2ray/v2ray-core/common/uuid"
)
type AccountJson struct {
ID string `json:"id"`
AlterIds uint16 `json:"alterId"`
Username string `json:"user"`
Password string `json:"pass"`
}
func (this *AccountJson) GetAccount() (Account, error) {
if len(this.ID) > 0 {
id, err := uuid.ParseString(this.ID)
if err != nil {
return nil, err
}
primaryID := NewID(id)
alterIDs := NewAlterIDs(primaryID, this.AlterIds)
return &VMessAccount{
ID: primaryID,
AlterIDs: alterIDs,
}, nil
}
return nil, errors.New("Protocol: Malformed account.")
}

View File

@ -5,30 +5,33 @@ import (
v2net "github.com/v2ray/v2ray-core/common/net"
. "github.com/v2ray/v2ray-core/common/protocol"
"github.com/v2ray/v2ray-core/common/uuid"
"github.com/v2ray/v2ray-core/testing/assert"
)
type TestAccount struct {
id int
}
func (this *TestAccount) Equals(account Account) bool {
return account.(*TestAccount).id == this.id
}
func TestReceiverUser(t *testing.T) {
assert := assert.On(t)
id := NewID(uuid.New())
alters := NewAlterIDs(id, 100)
account := &VMessAccount{
ID: id,
AlterIDs: alters,
account := &TestAccount{
id: 0,
}
user := NewUser(account, UserLevel(0), "")
user := NewUser(UserLevel(0), "")
user.Account = account
rec := NewServerSpec(v2net.TCPDestination(v2net.DomainAddress("v2ray.com"), 80), AlwaysValid(), user)
assert.Bool(rec.HasUser(user)).IsTrue()
id2 := NewID(uuid.New())
alters2 := NewAlterIDs(id2, 100)
account2 := &VMessAccount{
ID: id2,
AlterIDs: alters2,
account2 := &TestAccount{
id: 1,
}
user2 := NewUser(account2, UserLevel(0), "")
user2 := NewUser(UserLevel(0), "")
user2.Account = account2
assert.Bool(rec.HasUser(user2)).IsFalse()
rec.AddUser(user2)

View File

@ -13,11 +13,10 @@ type User struct {
Email string
}
func NewUser(account Account, level UserLevel, email string) *User {
func NewUser(level UserLevel, email string) *User {
return &User{
Account: account,
Level: level,
Email: email,
Level: level,
Email: email,
}
}

View File

@ -14,16 +14,8 @@ func (u *User) UnmarshalJSON(data []byte) error {
return err
}
var rawAccount AccountJson
if err := json.Unmarshal(data, &rawAccount); err != nil {
return err
}
account, err := rawAccount.GetAccount()
if err != nil {
return err
}
*u = *NewUser(account, UserLevel(rawUserValue.LevelByte), rawUserValue.EmailString)
u.Email = rawUserValue.EmailString
u.Level = UserLevel(rawUserValue.LevelByte)
return nil
}

View File

@ -22,24 +22,13 @@ func TestUserParsing(t *testing.T) {
}`), user)
assert.Error(err).IsNil()
assert.Byte(byte(user.Level)).Equals(1)
account, ok := user.Account.(*VMessAccount)
assert.Bool(ok).IsTrue()
assert.String(account.ID.String()).Equals("96edb838-6d68-42ef-a933-25f7ac3a9d09")
assert.String(user.Email).Equals("love@v2ray.com")
}
func TestInvalidUserJson(t *testing.T) {
assert := assert.On(t)
user := new(User)
err := json.Unmarshal([]byte(`{"id": 1234}`), user)
assert.Error(err).IsNotNil()
}
func TestInvalidIdJson(t *testing.T) {
assert := assert.On(t)
user := new(User)
err := json.Unmarshal([]byte(`{"id": "1234"}`), user)
err := json.Unmarshal([]byte(`{"email": 1234}`), user)
assert.Error(err).IsNotNil()
}

View File

@ -1,163 +1,12 @@
package protocol
import (
"sync"
"time"
"github.com/v2ray/v2ray-core/common"
"github.com/v2ray/v2ray-core/common/signal"
)
const (
updateIntervalSec = 10
cacheDurationSec = 120
)
type idEntry struct {
id *ID
userIdx int
lastSec Timestamp
lastSecRemoval Timestamp
}
type UserValidator interface {
common.Releasable
Add(user *User) error
Get(timeHash []byte) (*User, Timestamp, bool)
}
type TimedUserValidator struct {
sync.RWMutex
running bool
validUsers []*User
userHash map[[16]byte]*indexTimePair
ids []*idEntry
hasher IDHash
cancel *signal.CancelSignal
}
type indexTimePair struct {
index int
timeSec Timestamp
}
func NewTimedUserValidator(hasher IDHash) UserValidator {
tus := &TimedUserValidator{
validUsers: make([]*User, 0, 16),
userHash: make(map[[16]byte]*indexTimePair, 512),
ids: make([]*idEntry, 0, 512),
hasher: hasher,
running: true,
cancel: signal.NewCloseSignal(),
}
go tus.updateUserHash(updateIntervalSec * time.Second)
return tus
}
func (this *TimedUserValidator) Release() {
if !this.running {
return
}
this.cancel.Cancel()
<-this.cancel.WaitForDone()
this.Lock()
defer this.Unlock()
if !this.running {
return
}
this.running = false
this.validUsers = nil
this.userHash = nil
this.ids = nil
this.hasher = nil
this.cancel = nil
}
func (this *TimedUserValidator) generateNewHashes(nowSec Timestamp, idx int, entry *idEntry) {
var hashValue [16]byte
var hashValueRemoval [16]byte
idHash := this.hasher(entry.id.Bytes())
for entry.lastSec <= nowSec {
idHash.Write(entry.lastSec.Bytes(nil))
idHash.Sum(hashValue[:0])
idHash.Reset()
idHash.Write(entry.lastSecRemoval.Bytes(nil))
idHash.Sum(hashValueRemoval[:0])
idHash.Reset()
this.Lock()
this.userHash[hashValue] = &indexTimePair{idx, entry.lastSec}
delete(this.userHash, hashValueRemoval)
this.Unlock()
entry.lastSec++
entry.lastSecRemoval++
}
}
func (this *TimedUserValidator) updateUserHash(interval time.Duration) {
L:
for {
select {
case now := <-time.After(interval):
nowSec := Timestamp(now.Unix() + cacheDurationSec)
for _, entry := range this.ids {
this.generateNewHashes(nowSec, entry.userIdx, entry)
}
case <-this.cancel.WaitForCancel():
break L
}
}
this.cancel.Done()
}
func (this *TimedUserValidator) Add(user *User) error {
idx := len(this.validUsers)
this.validUsers = append(this.validUsers, user)
account := user.Account.(*VMessAccount)
nowSec := time.Now().Unix()
entry := &idEntry{
id: account.ID,
userIdx: idx,
lastSec: Timestamp(nowSec - cacheDurationSec),
lastSecRemoval: Timestamp(nowSec - cacheDurationSec*3),
}
this.generateNewHashes(Timestamp(nowSec+cacheDurationSec), idx, entry)
this.ids = append(this.ids, entry)
for _, alterid := range account.AlterIDs {
entry := &idEntry{
id: alterid,
userIdx: idx,
lastSec: Timestamp(nowSec - cacheDurationSec),
lastSecRemoval: Timestamp(nowSec - cacheDurationSec*3),
}
this.generateNewHashes(Timestamp(nowSec+cacheDurationSec), idx, entry)
this.ids = append(this.ids, entry)
}
return nil
}
func (this *TimedUserValidator) Get(userHash []byte) (*User, Timestamp, bool) {
defer this.RUnlock()
this.RLock()
if !this.running {
return nil, 0, false
}
var fixedSizeHash [16]byte
copy(fixedSizeHash[:], userHash)
pair, found := this.userHash[fixedSizeHash]
if found {
return this.validUsers[pair.index], pair.timeSec, true
}
return nil, 0, false
}

View File

@ -0,0 +1,31 @@
// +build json
package vmess
import (
"encoding/json"
"github.com/v2ray/v2ray-core/common/log"
"github.com/v2ray/v2ray-core/common/protocol"
"github.com/v2ray/v2ray-core/common/uuid"
)
func (u *Account) UnmarshalJSON(data []byte) error {
type JsonConfig struct {
ID string `json:"id"`
AlterIds uint16 `json:"alterId"`
}
var rawConfig JsonConfig
if err := json.Unmarshal(data, &rawConfig); err != nil {
return err
}
id, err := uuid.ParseString(rawConfig.ID)
if err != nil {
log.Error("VMess: Failed to parse ID: ", err)
return err
}
u.ID = protocol.NewID(id)
u.AlterIDs = protocol.NewAlterIDs(u.ID, rawConfig.AlterIds)
return nil
}

View File

@ -9,6 +9,7 @@ import (
"github.com/v2ray/v2ray-core/common/crypto"
"github.com/v2ray/v2ray-core/common/log"
"github.com/v2ray/v2ray-core/common/protocol"
"github.com/v2ray/v2ray-core/proxy/vmess"
"github.com/v2ray/v2ray-core/transport"
)
@ -50,7 +51,7 @@ func NewClientSession(idHash protocol.IDHash) *ClientSession {
func (this *ClientSession) EncodeRequestHeader(header *protocol.RequestHeader, writer io.Writer) {
timestamp := protocol.NewTimestampGenerator(protocol.NowTime(), 30)()
idHash := this.idHash(header.User.Account.(*protocol.VMessAccount).AnyValidID().Bytes())
idHash := this.idHash(header.User.Account.(*vmess.Account).AnyValidID().Bytes())
idHash.Write(timestamp.Bytes(nil))
writer.Write(idHash.Sum(nil))
@ -81,7 +82,7 @@ func (this *ClientSession) EncodeRequestHeader(header *protocol.RequestHeader, w
timestampHash := md5.New()
timestampHash.Write(hashTimestamp(timestamp))
iv := timestampHash.Sum(nil)
account := header.User.Account.(*protocol.VMessAccount)
account := header.User.Account.(*vmess.Account)
aesStream := crypto.NewAesEncryptionStream(account.ID.CmdKey(), iv)
aesStream.XORKeyStream(buffer, buffer)
writer.Write(buffer)

View File

@ -7,6 +7,7 @@ import (
v2net "github.com/v2ray/v2ray-core/common/net"
"github.com/v2ray/v2ray-core/common/protocol"
"github.com/v2ray/v2ray-core/common/uuid"
"github.com/v2ray/v2ray-core/proxy/vmess"
. "github.com/v2ray/v2ray-core/proxy/vmess/encoding"
"github.com/v2ray/v2ray-core/testing/assert"
)
@ -15,12 +16,12 @@ func TestRequestSerialization(t *testing.T) {
assert := assert.On(t)
user := protocol.NewUser(
&protocol.VMessAccount{
ID: protocol.NewID(uuid.New()),
AlterIDs: nil,
},
protocol.UserLevelUntrusted,
"test@v2ray.com")
user.Account = &vmess.Account{
ID: protocol.NewID(uuid.New()),
AlterIDs: nil,
}
expectedRequest := &protocol.RequestHeader{
Version: 1,
@ -35,7 +36,7 @@ func TestRequestSerialization(t *testing.T) {
client := NewClientSession(protocol.DefaultIDHash)
client.EncodeRequestHeader(expectedRequest, buffer)
userValidator := protocol.NewTimedUserValidator(protocol.DefaultIDHash)
userValidator := vmess.NewTimedUserValidator(protocol.DefaultIDHash)
userValidator.Add(user)
server := NewServerSession(userValidator)

View File

@ -10,6 +10,7 @@ import (
v2net "github.com/v2ray/v2ray-core/common/net"
"github.com/v2ray/v2ray-core/common/protocol"
"github.com/v2ray/v2ray-core/common/serial"
"github.com/v2ray/v2ray-core/proxy/vmess"
"github.com/v2ray/v2ray-core/transport"
)
@ -58,7 +59,7 @@ func (this *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.Requ
timestampHash := md5.New()
timestampHash.Write(hashTimestamp(timestamp))
iv := timestampHash.Sum(nil)
account := user.Account.(*protocol.VMessAccount)
account := user.Account.(*vmess.Account)
aesStream := crypto.NewAesDecryptionStream(account.ID.CmdKey(), iv)
decryptor := crypto.NewCryptionReader(aesStream, reader)

View File

@ -3,6 +3,7 @@ package inbound
import (
"github.com/v2ray/v2ray-core/common/log"
"github.com/v2ray/v2ray-core/common/protocol"
"github.com/v2ray/v2ray-core/proxy/vmess"
)
func (this *VMessInboundHandler) generateCommand(request *protocol.RequestHeader) protocol.ResponseCommand {
@ -23,8 +24,8 @@ func (this *VMessInboundHandler) generateCommand(request *protocol.RequestHeader
}
return &protocol.CommandSwitchAccount{
Port: inboundHandler.Port(),
ID: user.Account.(*protocol.VMessAccount).ID.UUID(),
AlterIds: uint16(len(user.Account.(*protocol.VMessAccount).AlterIDs)),
ID: user.Account.(*vmess.Account).ID.UUID(),
AlterIds: uint16(len(user.Account.(*vmess.Account).AlterIDs)),
Level: user.Level,
ValidMin: byte(availableMin),
}

View File

@ -8,6 +8,7 @@ import (
"github.com/v2ray/v2ray-core/common/protocol"
"github.com/v2ray/v2ray-core/proxy/internal"
"github.com/v2ray/v2ray-core/proxy/vmess"
)
func (this *DetourConfig) UnmarshalJSON(data []byte) error {
@ -53,16 +54,15 @@ func (this *DefaultConfig) UnmarshalJSON(data []byte) error {
func (this *Config) UnmarshalJSON(data []byte) error {
type JsonConfig struct {
Users []*protocol.User `json:"clients"`
Features *FeaturesConfig `json:"features"`
Defaults *DefaultConfig `json:"default"`
DetourConfig *DetourConfig `json:"detour"`
Users []json.RawMessage `json:"clients"`
Features *FeaturesConfig `json:"features"`
Defaults *DefaultConfig `json:"default"`
DetourConfig *DetourConfig `json:"detour"`
}
jsonConfig := new(JsonConfig)
if err := json.Unmarshal(data, jsonConfig); err != nil {
return errors.New("VMessIn: Failed to parse config: " + err.Error())
}
this.AllowedUsers = jsonConfig.Users
this.Features = jsonConfig.Features // Backward compatibility
this.Defaults = jsonConfig.Defaults
if this.Defaults == nil {
@ -76,6 +76,20 @@ func (this *Config) UnmarshalJSON(data []byte) error {
if this.Features != nil && this.DetourConfig == nil {
this.DetourConfig = this.Features.Detour
}
this.AllowedUsers = make([]*protocol.User, len(jsonConfig.Users))
for idx, rawData := range jsonConfig.Users {
user := new(protocol.User)
if err := json.Unmarshal(rawData, user); err != nil {
return errors.New("VMess|Inbound: Invalid user: " + err.Error())
}
account := new(vmess.Account)
if err := json.Unmarshal(rawData, account); err != nil {
return errors.New("VMess|Inbound: Invalid user: " + err.Error())
}
user.Account = account
this.AllowedUsers[idx] = user
}
return nil
}

View File

@ -15,6 +15,7 @@ import (
"github.com/v2ray/v2ray-core/common/uuid"
"github.com/v2ray/v2ray-core/proxy"
"github.com/v2ray/v2ray-core/proxy/internal"
"github.com/v2ray/v2ray-core/proxy/vmess"
"github.com/v2ray/v2ray-core/proxy/vmess/encoding"
vmessio "github.com/v2ray/v2ray-core/proxy/vmess/io"
"github.com/v2ray/v2ray-core/transport/internet"
@ -51,11 +52,12 @@ func (this *userByEmail) Get(email string) (*protocol.User, bool) {
if !found {
id := protocol.NewID(uuid.New())
alterIDs := protocol.NewAlterIDs(id, this.defaultAlterIDs)
account := &protocol.VMessAccount{
account := &vmess.Account{
ID: id,
AlterIDs: alterIDs,
}
user = protocol.NewUser(account, this.defaultLevel, email)
user = protocol.NewUser(this.defaultLevel, email)
user.Account = account
this.cache[email] = user
}
this.Unlock()
@ -249,7 +251,7 @@ func (this *Factory) Create(space app.Space, rawConfig interface{}, meta *proxy.
}
config := rawConfig.(*Config)
allowedClients := protocol.NewTimedUserValidator(protocol.DefaultIDHash)
allowedClients := vmess.NewTimedUserValidator(protocol.DefaultIDHash)
for _, user := range config.AllowedUsers {
allowedClients.Add(user)
}

View File

@ -5,16 +5,18 @@ import (
v2net "github.com/v2ray/v2ray-core/common/net"
"github.com/v2ray/v2ray-core/common/protocol"
"github.com/v2ray/v2ray-core/proxy/vmess"
)
func (this *VMessOutboundHandler) handleSwitchAccount(cmd *protocol.CommandSwitchAccount) {
primary := protocol.NewID(cmd.ID)
alters := protocol.NewAlterIDs(primary, cmd.AlterIds)
account := &protocol.VMessAccount{
account := &vmess.Account{
ID: primary,
AlterIDs: alters,
}
user := protocol.NewUser(account, cmd.Level, "")
user := protocol.NewUser(cmd.Level, "")
user.Account = account
dest := v2net.TCPDestination(cmd.Host, cmd.Port)
until := time.Now().Add(time.Duration(cmd.ValidMin) * time.Minute)
this.serverList.AddServer(protocol.NewServerSpec(dest, protocol.BeforeTime(until), user))

View File

@ -11,13 +11,14 @@ import (
"github.com/v2ray/v2ray-core/common/protocol"
"github.com/v2ray/v2ray-core/common/serial"
"github.com/v2ray/v2ray-core/proxy/internal"
"github.com/v2ray/v2ray-core/proxy/vmess"
)
func (this *Config) UnmarshalJSON(data []byte) error {
type RawConfigTarget struct {
Address *v2net.AddressJson `json:"address"`
Port v2net.Port `json:"port"`
Users []*protocol.User `json:"users"`
Users []json.RawMessage `json:"users"`
}
type RawOutbound struct {
Receivers []*RawConfigTarget `json:"vnext"`
@ -45,7 +46,19 @@ func (this *Config) UnmarshalJSON(data []byte) error {
rec.Address.Address = v2net.IPAddress(serial.Uint32ToBytes(2891346854, nil))
}
spec := protocol.NewServerSpec(v2net.TCPDestination(rec.Address.Address, rec.Port), protocol.AlwaysValid())
for _, user := range rec.Users {
for _, rawUser := range rec.Users {
user := new(protocol.User)
if err := json.Unmarshal(rawUser, user); err != nil {
log.Error("VMess|Outbound: Invalid user: ", err)
return err
}
account := new(vmess.Account)
if err := json.Unmarshal(rawUser, account); err != nil {
log.Error("VMess|Outbound: Invalid user: ", err)
return err
}
user.Account = account
spec.AddUser(user)
}
serverSpecs[idx] = spec

View File

@ -4,3 +4,180 @@
// together with 'freedom' to talk to final destination, while VMess outbound is usually used on
// clients with 'socks' for proxying.
package vmess // import "github.com/v2ray/v2ray-core/proxy/vmess"
import (
"sync"
"time"
"github.com/v2ray/v2ray-core/common/dice"
"github.com/v2ray/v2ray-core/common/protocol"
"github.com/v2ray/v2ray-core/common/signal"
)
type Account struct {
ID *protocol.ID
AlterIDs []*protocol.ID
}
func (this *Account) AnyValidID() *protocol.ID {
if len(this.AlterIDs) == 0 {
return this.ID
}
return this.AlterIDs[dice.Roll(len(this.AlterIDs))]
}
func (this *Account) Equals(account protocol.Account) bool {
vmessAccount, ok := account.(*Account)
if !ok {
return false
}
// TODO: handle AlterIds difference
return this.ID.Equals(vmessAccount.ID)
}
const (
updateIntervalSec = 10
cacheDurationSec = 120
)
type idEntry struct {
id *protocol.ID
userIdx int
lastSec protocol.Timestamp
lastSecRemoval protocol.Timestamp
}
type TimedUserValidator struct {
sync.RWMutex
running bool
validUsers []*protocol.User
userHash map[[16]byte]*indexTimePair
ids []*idEntry
hasher protocol.IDHash
cancel *signal.CancelSignal
}
type indexTimePair struct {
index int
timeSec protocol.Timestamp
}
func NewTimedUserValidator(hasher protocol.IDHash) protocol.UserValidator {
tus := &TimedUserValidator{
validUsers: make([]*protocol.User, 0, 16),
userHash: make(map[[16]byte]*indexTimePair, 512),
ids: make([]*idEntry, 0, 512),
hasher: hasher,
running: true,
cancel: signal.NewCloseSignal(),
}
go tus.updateUserHash(updateIntervalSec * time.Second)
return tus
}
func (this *TimedUserValidator) Release() {
if !this.running {
return
}
this.cancel.Cancel()
<-this.cancel.WaitForDone()
this.Lock()
defer this.Unlock()
if !this.running {
return
}
this.running = false
this.validUsers = nil
this.userHash = nil
this.ids = nil
this.hasher = nil
this.cancel = nil
}
func (this *TimedUserValidator) generateNewHashes(nowSec protocol.Timestamp, idx int, entry *idEntry) {
var hashValue [16]byte
var hashValueRemoval [16]byte
idHash := this.hasher(entry.id.Bytes())
for entry.lastSec <= nowSec {
idHash.Write(entry.lastSec.Bytes(nil))
idHash.Sum(hashValue[:0])
idHash.Reset()
idHash.Write(entry.lastSecRemoval.Bytes(nil))
idHash.Sum(hashValueRemoval[:0])
idHash.Reset()
this.Lock()
this.userHash[hashValue] = &indexTimePair{idx, entry.lastSec}
delete(this.userHash, hashValueRemoval)
this.Unlock()
entry.lastSec++
entry.lastSecRemoval++
}
}
func (this *TimedUserValidator) updateUserHash(interval time.Duration) {
L:
for {
select {
case now := <-time.After(interval):
nowSec := protocol.Timestamp(now.Unix() + cacheDurationSec)
for _, entry := range this.ids {
this.generateNewHashes(nowSec, entry.userIdx, entry)
}
case <-this.cancel.WaitForCancel():
break L
}
}
this.cancel.Done()
}
func (this *TimedUserValidator) Add(user *protocol.User) error {
idx := len(this.validUsers)
this.validUsers = append(this.validUsers, user)
account := user.Account.(*Account)
nowSec := time.Now().Unix()
entry := &idEntry{
id: account.ID,
userIdx: idx,
lastSec: protocol.Timestamp(nowSec - cacheDurationSec),
lastSecRemoval: protocol.Timestamp(nowSec - cacheDurationSec*3),
}
this.generateNewHashes(protocol.Timestamp(nowSec+cacheDurationSec), idx, entry)
this.ids = append(this.ids, entry)
for _, alterid := range account.AlterIDs {
entry := &idEntry{
id: alterid,
userIdx: idx,
lastSec: protocol.Timestamp(nowSec - cacheDurationSec),
lastSecRemoval: protocol.Timestamp(nowSec - cacheDurationSec*3),
}
this.generateNewHashes(protocol.Timestamp(nowSec+cacheDurationSec), idx, entry)
this.ids = append(this.ids, entry)
}
return nil
}
func (this *TimedUserValidator) Get(userHash []byte) (*protocol.User, protocol.Timestamp, bool) {
defer this.RUnlock()
this.RLock()
if !this.running {
return nil, 0, false
}
var fixedSizeHash [16]byte
copy(fixedSizeHash[:], userHash)
pair, found := this.userHash[fixedSizeHash]
if found {
return this.validUsers[pair.index], pair.timeSec, true
}
return nil, 0, false
}