Compare commits

...

109 commits

Author SHA1 Message Date
Jonathan Barrow
519e786544
Merge pull request #83 from DaniElectra/stationurl-custom-params
fix(types/station_url): Fix invalid checks for custom parameters
2025-03-15 17:10:12 -04:00
Daniel López Guimaraes
4a55120377
fix(types/station_url): Fix invalid checks for custom parameters
Check for empty string to prevent setting an empty value on the custom
parameters map. Also fix a typo on formatting which was using the
standard parameters as custom parameters.
2025-03-15 18:20:26 +00:00
Daniel López Guimaraes
4cb8b49c51
Merge pull request #81 from DaniElectra/size-checks
fix(prudp): Add missing size checks on PRUDP packets
2025-03-15 16:57:14 +00:00
Daniel López Guimaraes
397ebe4144
fix(prudp): Add missing size checks on PRUDP packets
PRUDPLite and PRUDPv1 were missing some size checks while parsing their
data. We also need one bounds check at the beginning to choose the
proper packet type by the magic.
2025-02-26 23:51:21 +00:00
Daniel López Guimaraes
f34f86f7b7
fix(prudp): Check for matching user PID and ticket source PID 2025-02-13 21:50:11 +00:00
Ash Logan
6ec50052cd fix(datetime): Have FromTimestamp modify the underlying DateTime
This got lost in the types refactor, and was a rare case of the pointer receiver actually being needed - various apps depended on this function modifying the underlying value.

One could also argue for this being factored out into a NewDateTimeFromTimestamp function or something, but this way is source-compatible with older code.
2025-01-27 19:52:18 +11:00
Daniel López Guimaraes
a7fb52ae81
chore: Update go modules 2025-01-12 20:15:30 +00:00
Jonathan Barrow
7159d9120d
Merge pull request #56 from PretendoNetwork/types-updates 2025-01-12 14:44:28 -05:00
Daniel López Guimaraes
be12609cc1
chore: Move DataHolder into its own file 2025-01-11 22:37:31 +00:00
Daniel López Guimaraes
9c8f7bd62e
chore(AnyObjectHolder): Inline cast check 2025-01-11 22:37:00 +00:00
Daniel López Guimaraes
340b851e5f
refactor: Replace AnyDataHolder with AnyObjectHolder 2025-01-09 23:21:11 +00:00
Daniel López Guimaraes
10faf0a38a
Merge branch 'master' into types-updates 2025-01-09 21:13:19 +00:00
Jonathan Barrow
9a3d0bcbb1
Merge pull request #76 from PretendoNetwork/patch/cleanup-connections 2025-01-06 18:30:37 -05:00
Ash Logan
9e5e0332b5 fix(endpoint): Check PID invariant in FindConnectionByPID
An expected invariant of this system is that StateConnected connections also have a PID, and other states do *not* have a PID.

While any code that breaks this invariant is buggy, we can still make FindConnectionByPID resilient to this to prevent that type of bug from taking the whole server down.

That type of bug can still be detected through explicit testing of the Connections map.
2025-01-04 17:33:33 +11:00
Ash Logan
fbe2b91e75 feat(endpoint): Route connection closures via new cleanupConnection method
Centralising connection closures helps ensure that everything gets done (.cleanup()) and there's One True Way to find them in the Connections map

It's worth noting that every callsite has a PRUDPConnection*, so maybe a different datastructure would serve us better here?
2025-01-04 17:33:33 +11:00
Ash
461d2f2846
Merge pull request #75 from PretendoNetwork/patch/simplify-socket-connection 2025-01-04 10:56:37 +11:00
Ash Logan
5dda129a6f chore: Remove Connections tracking from SocketConnection
Every SocketConnection had a map of *PRUDPConnection, which was never actually inserted into anywhere, so the intended function of tracking what OS sockets have which PRUDP streams was not being done.

Remove it as dead code. If the rest of the connection tracking is going to be implemented, this commit can be reverted.

Since the SocketConnection no longer has long-lived state in it, also remove the map on PRUDPServer and just create it as a simple data structure when needed.

The connection close handling of the websocket server had to be rewritten - the old code would never do anything due to the empty maps, so this can't be *worse* than nothing :)
2025-01-03 17:58:46 +11:00
Ash Logan
6f3f3d1b4a .gitignore: add IDEA 2025-01-03 17:54:55 +11:00
Daniel López Guimaraes
eee4fc9e88
fix(AnyDataHolder): Use CopyRef to get RVTypePtr of object data
The previous method didn't work because it was making a pointer of the
RVType interface instead of the underlying data.
2024-12-24 13:08:01 +00:00
Jonathan Barrow
2797330f2f
chore: fix kerberos.go indentation 2024-11-20 15:04:05 -05:00
Jonathan Barrow
359eb435c2
refactor(types): simplify CopyRef() to use Copy() internally 2024-11-17 14:36:06 -05:00
Jonathan Barrow
337fdcdbc4
chore: add method comments to RVType and RVTypePtr interfaces 2024-11-16 23:52:42 -05:00
Jonathan Barrow
d3b7a0175f
chore: make CopyRef do safer, full, copies 2024-11-16 23:48:42 -05:00
Jonathan Barrow
fb784e4e4f
Merge branch 'types-updates' of https://github.com/PretendoNetwork/nex-go into types-updates 2024-11-16 10:20:20 -05:00
Jonathan Barrow
b36470e680
feat: add CopyRef and Deref methods to RVType(Ptr) 2024-11-16 10:16:37 -05:00
Jonathan Barrow
37d01ad3d7
Merge branch 'master' into types-updates 2024-07-07 17:28:22 -04:00
Jonathan Barrow
a035545093
Merge pull request #70 from wolfendale/performance-tweaks 2024-07-07 13:55:06 -04:00
Michael Wolfendale
feee2dd599
fix: allocate buffers for incoming packets 2024-07-07 16:36:34 +01:00
Michael Wolfendale
4fab7a2c86
fix: fix kerberos key derivation 2024-07-07 16:36:34 +01:00
Jonathan Barrow
4fbbe2da25
chore: fix indentation in DeriveKerberosKey from merge 2024-07-05 13:01:58 -04:00
Jonathan Barrow
27cdd4c7e3
Merge branch 'master' into types-updates 2024-07-05 13:00:33 -04:00
Jonathan Barrow
d633dca018
Merge pull request #69 from wolfendale/performance-tweaks 2024-07-05 12:55:28 -04:00
Michael Wolfendale
22d2698cef
chore: add comment to TimeoutManager
Co-authored-by: Daniel López Guimaraes <112760654+DaniElectra@users.noreply.github.com>
2024-07-05 17:48:47 +01:00
Michael Wolfendale
ad488be838
chore: update kerberos key derivation to reduce allocations 2024-07-05 17:39:29 +01:00
Michael Wolfendale
abfca9a10d
chore: remove unnecessary goroutines 2024-07-05 17:15:17 +01:00
Michael Wolfendale
c214a2a7a5
chore: reduce buffer allocations when receiving packets 2024-07-05 16:37:52 +01:00
Michael Wolfendale
4fefb05f86
chore: limit the scope of connection locking in TimeoutManager 2024-07-05 16:37:52 +01:00
Michael Wolfendale
348c37df5d
fix: removed double lock from reliable ping packet handling 2024-07-05 16:37:52 +01:00
Jonathan Barrow
a0107a982a
fix: AccountDetailsByPID signature to not take in a pointer 2024-07-04 17:33:14 -04:00
Jonathan Barrow
182418f999
feat: add struct tags to struct types 2024-07-03 23:39:23 -04:00
Jonathan Barrow
2309a6d409
Merge branch 'master' into types-updates 2024-07-03 23:26:52 -04:00
Jonathan Barrow
293c20d6be
Merge pull request #68 from wolfendale/timeout-rework 2024-07-02 12:47:24 -04:00
Michael Wolfendale
0502cc64b1
chore: renamed GetRTO to RTO to match style rules 2024-07-02 17:31:42 +01:00
Michael Wolfendale
9997fb672a
chore: rename ResendScheduler TimeoutManager 2024-07-02 17:29:28 +01:00
Michael Wolfendale
0d8506dd52
chore: updated documentation 2024-07-02 17:28:31 +01:00
Michael Wolfendale
680abd3cb0
chore: update max silence time 2024-07-02 16:54:47 +01:00
Michael Wolfendale
18380590ab
feat: rework resending packets to be more accurate 2024-07-02 16:54:47 +01:00
Daniel López Guimaraes
2b6ea02749
Merge pull request #67 from wolfendale/master 2024-06-25 23:00:43 +02:00
Michael Wolfendale
83e5157350
feat: add delay to outgoing fragmented data packets 2024-06-25 21:58:40 +01:00
Jonathan Barrow
b57adeb748
refactor: remove references to "Primitive" naming conventions 2024-06-16 11:52:39 -04:00
Daniel López Guimaraes
090cfb5568
Merge pull request #65 from wolfendale/connection-state-checks
Connection State Changes
2024-06-14 22:29:19 +02:00
Jonathan Barrow
a4b341dab5
chore(types): update Godoc comment on StationURL.URL 2024-06-13 10:40:31 -04:00
Jonathan Barrow
1bee30dc05
chore: update test secure server StationURL 2024-06-13 10:38:22 -04:00
Jonathan Barrow
a1ca41e880
fix(types): add address and port validation in StationURL 2024-06-13 10:37:45 -04:00
Jonathan Barrow
88c51d6f3a
fix(types): make StationURL.Format a pointer receiver 2024-06-13 09:37:23 -04:00
Jonathan Barrow
2c2dd7a3bd
fix(types): add url length checks for StationURL 2024-06-13 09:36:25 -04:00
Jonathan Barrow
9373fd8723
refactor(types): add StationURL.SetURL and URL, and more accurate implementation 2024-06-13 09:05:44 -04:00
Jonathan Barrow
7f5a097a92
refactor(types): only call ensureFields in StationURL.ExtractFrom 2024-06-13 08:49:39 -04:00
Michael Wolfendale
de3610deaf
fix: added Purge method to PacketDispatchQueue 2024-06-12 23:03:41 +01:00
Jonathan Barrow
d19d660033
fix(types): make StationURL check if custom map is nil before accessing 2024-06-11 18:01:16 -04:00
Jonathan Barrow
2c48000d5f
feat(types): add support for custom parameters in StationURLs 2024-06-11 17:58:57 -04:00
Jonathan Barrow
d668caac02
fix(types): rename FromString to Parse on StationURL and reject unknown schemes 2024-06-11 17:42:06 -04:00
Jonathan Barrow
8271f9acfb
chore(types): rename EncodeToString to Format on StationURL 2024-06-11 17:31:48 -04:00
Michael Wolfendale
1258086480
fix: modify connection state checks 2024-06-11 12:49:10 +01:00
Jonathan Barrow
7dafc92ffc
fix(types): make StationURL check if map is nil before accessing 2024-06-10 18:03:41 -04:00
Jonathan Barrow
0e3840107f
fix(test): make secure endpoint secure again 2024-06-10 18:03:09 -04:00
Jonathan Barrow
ab4dbfcd52
chore(types): remove pointer from StationURL.SetPrincipalID 2024-06-07 17:02:49 -04:00
Jonathan Barrow
bfe9c9c702
chore(types): remove old Value and LegacyValue methods on PID 2024-06-07 16:59:18 -04:00
Daniel López Guimaraes
f7b8a549ba
Merge pull request #64 from wolfendale/revert-ack-changes 2024-06-06 22:51:36 +02:00
Michael Wolfendale
a638798255
fix: remove reliable check from ack packets 2024-06-06 21:24:17 +01:00
Jonathan Barrow
832bd19751
refactor(types): remove reliance on pointers 2024-06-05 17:24:00 -04:00
Jonathan Barrow
99acae026c
refactor(types): make Map into a type alias 2024-06-04 14:48:57 -04:00
Jonathan Barrow
3a90361903
fix: update StationURLs handling in NewPRUDPConnection 2024-06-04 14:47:58 -04:00
Daniel López Guimaraes
b0c937dbe4
Merge pull request #62 from wolfendale/fix-ping-ack 2024-06-03 23:25:55 +02:00
Jonathan Barrow
1f8c5ffeba
fix(types): remove recursive String call in List type 2024-06-02 15:48:48 -04:00
Jonathan Barrow
943f141096
fix(types): remove recursive String call in String type 2024-06-02 15:48:24 -04:00
Jonathan Barrow
8d306f43f4
refactor(types): make List into a type alias 2024-06-02 15:42:03 -04:00
Jonathan Barrow
daceca2dfb
Merge branch 'types-updates' of https://github.com/PretendoNetwork/nex-go into types-updates 2024-06-02 15:30:46 -04:00
Jonathan Barrow
446a730e4c
fix(types): fix bad Copy methods 2024-06-02 15:20:29 -04:00
Jonathan Barrow
885121a678
refactor(types): make QUUID into a type alias 2024-06-02 15:20:28 -04:00
Jonathan Barrow
c1aa55c3ba
fix(types): QBuffer.Equals should not be a pointer 2024-06-02 15:20:28 -04:00
Jonathan Barrow
3c28fb75de
refactor(types): make String into a type alias 2024-06-02 15:20:27 -04:00
Jonathan Barrow
feca72a672
refactor(types): make QResult into a type alias 2024-06-02 15:20:27 -04:00
Jonathan Barrow
502b6011c6
refactor(types): make PID into a type alias 2024-06-02 15:20:27 -04:00
Jonathan Barrow
d5feb922f4
refactor(types): make DateTime into a type alias 2024-06-02 15:20:26 -04:00
Jonathan Barrow
7998d1469d
refactor(types): make QBuffer into a type alias 2024-06-02 15:20:26 -04:00
Jonathan Barrow
2a775ebf35
refactor(types): make Buffer into a type alias 2024-06-02 15:20:25 -04:00
Jonathan Barrow
7bc3579294
refactor(types): make primitive types into type aliases 2024-06-02 15:20:20 -04:00
Michael Wolfendale
ae41e77566
fix: prevented ping packets from acknowledging reliable packets 2024-06-02 12:07:52 +01:00
Daniel López Guimaraes
4f59501d31
Merge pull request #59 from wolfendale/packet-dispatch-queue 2024-05-27 23:05:08 +02:00
Michael Wolfendale
a597a37d41
chore: update godoc comments 2024-05-27 21:59:51 +01:00
Michael Wolfendale
f66fae8827
chore: remove Purge from PacketDispatchQueue as it is unneeded 2024-05-27 21:59:51 +01:00
Michael Wolfendale
07dda5708e
chore: remove logic in SlidingWindow for incoming packets 2024-05-27 21:59:51 +01:00
Michael Wolfendale
75c402b7ee
feat: use PacketDispatchQueue in PRUDPEndpoint 2024-05-27 21:59:51 +01:00
Michael Wolfendale
24d542506c
feat: add PacketDispatchQueue 2024-05-27 20:39:28 +01:00
Jonathan Barrow
26c9159397
Merge pull request #57 from wolfendale/connectivity-issues 2024-05-25 17:47:56 -04:00
Michael Wolfendale
363cc7b5bf
fix: prevent multi-fragment messages being lost 2024-05-25 20:42:54 +01:00
Jonathan Barrow
8e0b1a2be4
Merge pull request #51 from wolfendale/sliding-window-lock 2024-05-22 18:24:56 -04:00
Jonathan Barrow
fabfca0c4b
fix(types): fix bad Copy methods 2024-05-22 17:09:17 -04:00
Jonathan Barrow
cc2b19e552
refactor(types): make QUUID into a type alias 2024-05-22 16:58:39 -04:00
Jonathan Barrow
44581f1302
fix(types): QBuffer.Equals should not be a pointer 2024-05-22 16:54:22 -04:00
Jonathan Barrow
2514f63383
refactor(types): make String into a type alias 2024-05-22 16:48:35 -04:00
Jonathan Barrow
8a848c57ba
refactor(types): make QResult into a type alias 2024-05-22 16:38:42 -04:00
Jonathan Barrow
f8b1187ad7
refactor(types): make PID into a type alias 2024-05-22 16:31:14 -04:00
Jonathan Barrow
bd05699837
refactor(types): make DateTime into a type alias 2024-05-22 16:26:05 -04:00
Jonathan Barrow
2b0f14b8f1
refactor(types): make QBuffer into a type alias 2024-05-22 16:20:22 -04:00
Jonathan Barrow
a76ede10d3
refactor(types): make Buffer into a type alias 2024-05-22 16:16:56 -04:00
Jonathan Barrow
1dc17555f0
refactor(types): make primitive types into type aliases 2024-05-22 15:27:58 -04:00
Michael Wolfendale
4315467d6a
fix: fixed race condition in SlidingWindow 2024-05-18 09:08:07 +01:00
80 changed files with 2715 additions and 2501 deletions

1
.gitignore vendored
View file

@ -23,6 +23,7 @@ go.work.sum
# custom
.vscode
.idea
.env
build
log

View file

@ -10,15 +10,15 @@ import "github.com/PretendoNetwork/nex-go/v2/types"
// guest account, an account which represents the authentication server, and one which represents
// the secure server. See https://nintendo-wiki.pretendo.network/docs/nex/kerberos for more information.
type Account struct {
PID *types.PID // * The PID of the account. PIDs are unique IDs per account. NEX PIDs start at 1800000000 and decrement with each new account.
Username string // * The username for the account. For NEX user accounts this is the same as the accounts PID.
Password string // * The password for the account. For NEX accounts this is always 16 characters long using seemingly any ASCII character
PID types.PID // * The PID of the account. PIDs are unique IDs per account. NEX PIDs start at 1800000000 and decrement with each new account.
Username string // * The username for the account. For NEX user accounts this is the same as the accounts PID.
Password string // * The password for the account. For NEX accounts this is always 16 characters long using seemingly any ASCII character
}
// NewAccount returns a new instance of Account.
// This does not register an account, only creates a new
// struct instance.
func NewAccount(pid *types.PID, username, password string) *Account {
func NewAccount(pid types.PID, username, password string) *Account {
return &Account{
PID: pid,
Username: username,

View file

@ -68,8 +68,8 @@ func (bsi *ByteStreamIn) Read(length uint64) ([]byte, error) {
return bsi.ReadBytesNext(int64(length)), nil
}
// ReadPrimitiveUInt8 reads a uint8
func (bsi *ByteStreamIn) ReadPrimitiveUInt8() (uint8, error) {
// ReadUInt8 reads a uint8
func (bsi *ByteStreamIn) ReadUInt8() (uint8, error) {
if bsi.Remaining() < 1 {
return 0, errors.New("Not enough data to read uint8")
}
@ -77,8 +77,8 @@ func (bsi *ByteStreamIn) ReadPrimitiveUInt8() (uint8, error) {
return uint8(bsi.ReadByteNext()), nil
}
// ReadPrimitiveUInt16LE reads a Little-Endian encoded uint16
func (bsi *ByteStreamIn) ReadPrimitiveUInt16LE() (uint16, error) {
// ReadUInt16LE reads a Little-Endian encoded uint16
func (bsi *ByteStreamIn) ReadUInt16LE() (uint16, error) {
if bsi.Remaining() < 2 {
return 0, errors.New("Not enough data to read uint16")
}
@ -86,8 +86,8 @@ func (bsi *ByteStreamIn) ReadPrimitiveUInt16LE() (uint16, error) {
return bsi.ReadU16LENext(1)[0], nil
}
// ReadPrimitiveUInt32LE reads a Little-Endian encoded uint32
func (bsi *ByteStreamIn) ReadPrimitiveUInt32LE() (uint32, error) {
// ReadUInt32LE reads a Little-Endian encoded uint32
func (bsi *ByteStreamIn) ReadUInt32LE() (uint32, error) {
if bsi.Remaining() < 4 {
return 0, errors.New("Not enough data to read uint32")
}
@ -95,8 +95,8 @@ func (bsi *ByteStreamIn) ReadPrimitiveUInt32LE() (uint32, error) {
return bsi.ReadU32LENext(1)[0], nil
}
// ReadPrimitiveUInt64LE reads a Little-Endian encoded uint64
func (bsi *ByteStreamIn) ReadPrimitiveUInt64LE() (uint64, error) {
// ReadUInt64LE reads a Little-Endian encoded uint64
func (bsi *ByteStreamIn) ReadUInt64LE() (uint64, error) {
if bsi.Remaining() < 8 {
return 0, errors.New("Not enough data to read uint64")
}
@ -104,8 +104,8 @@ func (bsi *ByteStreamIn) ReadPrimitiveUInt64LE() (uint64, error) {
return bsi.ReadU64LENext(1)[0], nil
}
// ReadPrimitiveInt8 reads a uint8
func (bsi *ByteStreamIn) ReadPrimitiveInt8() (int8, error) {
// ReadInt8 reads a uint8
func (bsi *ByteStreamIn) ReadInt8() (int8, error) {
if bsi.Remaining() < 1 {
return 0, errors.New("Not enough data to read int8")
}
@ -113,8 +113,8 @@ func (bsi *ByteStreamIn) ReadPrimitiveInt8() (int8, error) {
return int8(bsi.ReadByteNext()), nil
}
// ReadPrimitiveInt16LE reads a Little-Endian encoded int16
func (bsi *ByteStreamIn) ReadPrimitiveInt16LE() (int16, error) {
// ReadInt16LE reads a Little-Endian encoded int16
func (bsi *ByteStreamIn) ReadInt16LE() (int16, error) {
if bsi.Remaining() < 2 {
return 0, errors.New("Not enough data to read int16")
}
@ -122,8 +122,8 @@ func (bsi *ByteStreamIn) ReadPrimitiveInt16LE() (int16, error) {
return int16(bsi.ReadU16LENext(1)[0]), nil
}
// ReadPrimitiveInt32LE reads a Little-Endian encoded int32
func (bsi *ByteStreamIn) ReadPrimitiveInt32LE() (int32, error) {
// ReadInt32LE reads a Little-Endian encoded int32
func (bsi *ByteStreamIn) ReadInt32LE() (int32, error) {
if bsi.Remaining() < 4 {
return 0, errors.New("Not enough data to read int32")
}
@ -131,8 +131,8 @@ func (bsi *ByteStreamIn) ReadPrimitiveInt32LE() (int32, error) {
return int32(bsi.ReadU32LENext(1)[0]), nil
}
// ReadPrimitiveInt64LE reads a Little-Endian encoded int64
func (bsi *ByteStreamIn) ReadPrimitiveInt64LE() (int64, error) {
// ReadInt64LE reads a Little-Endian encoded int64
func (bsi *ByteStreamIn) ReadInt64LE() (int64, error) {
if bsi.Remaining() < 8 {
return 0, errors.New("Not enough data to read int64")
}
@ -140,8 +140,8 @@ func (bsi *ByteStreamIn) ReadPrimitiveInt64LE() (int64, error) {
return int64(bsi.ReadU64LENext(1)[0]), nil
}
// ReadPrimitiveFloat32LE reads a Little-Endian encoded float32
func (bsi *ByteStreamIn) ReadPrimitiveFloat32LE() (float32, error) {
// ReadFloat32LE reads a Little-Endian encoded float32
func (bsi *ByteStreamIn) ReadFloat32LE() (float32, error) {
if bsi.Remaining() < 4 {
return 0, errors.New("Not enough data to read float32")
}
@ -149,8 +149,8 @@ func (bsi *ByteStreamIn) ReadPrimitiveFloat32LE() (float32, error) {
return bsi.ReadF32LENext(1)[0], nil
}
// ReadPrimitiveFloat64LE reads a Little-Endian encoded float64
func (bsi *ByteStreamIn) ReadPrimitiveFloat64LE() (float64, error) {
// ReadFloat64LE reads a Little-Endian encoded float64
func (bsi *ByteStreamIn) ReadFloat64LE() (float64, error) {
if bsi.Remaining() < 8 {
return 0, errors.New("Not enough data to read float64")
}
@ -158,8 +158,8 @@ func (bsi *ByteStreamIn) ReadPrimitiveFloat64LE() (float64, error) {
return bsi.ReadF64LENext(1)[0], nil
}
// ReadPrimitiveBool reads a bool
func (bsi *ByteStreamIn) ReadPrimitiveBool() (bool, error) {
// ReadBool reads a bool
func (bsi *ByteStreamIn) ReadBool() (bool, error) {
if bsi.Remaining() < 1 {
return false, errors.New("Not enough data to read bool")
}

View file

@ -56,68 +56,68 @@ func (bso *ByteStreamOut) Write(data []byte) {
bso.WriteBytesNext(data)
}
// WritePrimitiveUInt8 writes a uint8
func (bso *ByteStreamOut) WritePrimitiveUInt8(u8 uint8) {
// WriteUInt8 writes a uint8
func (bso *ByteStreamOut) WriteUInt8(u8 uint8) {
bso.Grow(1)
bso.WriteByteNext(byte(u8))
}
// WritePrimitiveUInt16LE writes a uint16 as LE
func (bso *ByteStreamOut) WritePrimitiveUInt16LE(u16 uint16) {
// WriteUInt16LE writes a uint16 as LE
func (bso *ByteStreamOut) WriteUInt16LE(u16 uint16) {
bso.Grow(2)
bso.WriteU16LENext([]uint16{u16})
}
// WritePrimitiveUInt32LE writes a uint32 as LE
func (bso *ByteStreamOut) WritePrimitiveUInt32LE(u32 uint32) {
// WriteUInt32LE writes a uint32 as LE
func (bso *ByteStreamOut) WriteUInt32LE(u32 uint32) {
bso.Grow(4)
bso.WriteU32LENext([]uint32{u32})
}
// WritePrimitiveUInt64LE writes a uint64 as LE
func (bso *ByteStreamOut) WritePrimitiveUInt64LE(u64 uint64) {
// WriteUInt64LE writes a uint64 as LE
func (bso *ByteStreamOut) WriteUInt64LE(u64 uint64) {
bso.Grow(8)
bso.WriteU64LENext([]uint64{u64})
}
// WritePrimitiveInt8 writes a int8
func (bso *ByteStreamOut) WritePrimitiveInt8(s8 int8) {
// WriteInt8 writes a int8
func (bso *ByteStreamOut) WriteInt8(s8 int8) {
bso.Grow(1)
bso.WriteByteNext(byte(s8))
}
// WritePrimitiveInt16LE writes a uint16 as LE
func (bso *ByteStreamOut) WritePrimitiveInt16LE(s16 int16) {
// WriteInt16LE writes a uint16 as LE
func (bso *ByteStreamOut) WriteInt16LE(s16 int16) {
bso.Grow(2)
bso.WriteU16LENext([]uint16{uint16(s16)})
}
// WritePrimitiveInt32LE writes a int32 as LE
func (bso *ByteStreamOut) WritePrimitiveInt32LE(s32 int32) {
// WriteInt32LE writes a int32 as LE
func (bso *ByteStreamOut) WriteInt32LE(s32 int32) {
bso.Grow(4)
bso.WriteU32LENext([]uint32{uint32(s32)})
}
// WritePrimitiveInt64LE writes a int64 as LE
func (bso *ByteStreamOut) WritePrimitiveInt64LE(s64 int64) {
// WriteInt64LE writes a int64 as LE
func (bso *ByteStreamOut) WriteInt64LE(s64 int64) {
bso.Grow(8)
bso.WriteU64LENext([]uint64{uint64(s64)})
}
// WritePrimitiveFloat32LE writes a float32 as LE
func (bso *ByteStreamOut) WritePrimitiveFloat32LE(f32 float32) {
// WriteFloat32LE writes a float32 as LE
func (bso *ByteStreamOut) WriteFloat32LE(f32 float32) {
bso.Grow(4)
bso.WriteF32LENext([]float32{f32})
}
// WritePrimitiveFloat64LE writes a float64 as LE
func (bso *ByteStreamOut) WritePrimitiveFloat64LE(f64 float64) {
// WriteFloat64LE writes a float64 as LE
func (bso *ByteStreamOut) WriteFloat64LE(f64 float64) {
bso.Grow(8)
bso.WriteF64LENext([]float64{f64})
}
// WritePrimitiveBool writes a bool
func (bso *ByteStreamOut) WritePrimitiveBool(b bool) {
// WriteBool writes a bool
func (bso *ByteStreamOut) WriteBool(b bool) {
var bVar uint8
if b {
bVar = 1

View file

@ -11,6 +11,6 @@ import (
type ConnectionInterface interface {
Endpoint() EndpointInterface
Address() net.Addr
PID() *types.PID
SetPID(pid *types.PID)
PID() types.PID
SetPID(pid types.PID)
}

24
go.mod
View file

@ -1,23 +1,29 @@
module github.com/PretendoNetwork/nex-go/v2
go 1.21
go 1.22.0
toolchain go1.23.4
require (
github.com/PretendoNetwork/plogger-go v1.0.4
github.com/lxzan/gws v1.8.3
github.com/lxzan/gws v1.8.8
github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e
github.com/stretchr/testify v1.8.4
github.com/superwhiskers/crunch/v3 v3.5.7
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
golang.org/x/mod v0.17.0
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8
golang.org/x/mod v0.22.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dolthub/maphash v0.1.0 // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/jwalton/go-supportscolor v1.2.0 // indirect
github.com/klauspost/compress v1.17.8 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/term v0.20.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/term v0.28.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

36
go.sum
View file

@ -4,19 +4,18 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/jwalton/go-supportscolor v1.2.0 h1:g6Ha4u7Vm3LIsQ5wmeBpS4gazu0UP1DRDE8y6bre4H8=
github.com/jwalton/go-supportscolor v1.2.0/go.mod h1:hFVUAZV2cWg+WFFC4v8pT2X/S2qUUBYMioBD9AINXGs=
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/lxzan/gws v1.8.3 h1:umX2VLhXj7oVV4Sd2gCuqzrzpVWtqkKMy0tjHBBxXg0=
github.com/lxzan/gws v1.8.3/go.mod h1:FcGeRMB7HwGuTvMLR24ku0Zx0p6RXqeKASeMc4VYgi4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/lxzan/gws v1.8.8 h1:st193ZG8qN8sSw8/g/UituFhs7etmKzS7jUqhijg5wM=
github.com/lxzan/gws v1.8.8/go.mod h1:FcGeRMB7HwGuTvMLR24ku0Zx0p6RXqeKASeMc4VYgi4=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@ -27,18 +26,19 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/superwhiskers/crunch/v3 v3.5.7 h1:N9RLxaR65C36i26BUIpzPXGy2f6pQ7wisu2bawbKNqg=
github.com/superwhiskers/crunch/v3 v3.5.7/go.mod h1:4ub2EKgF1MAhTjoOCTU4b9uLMsAweHEa89aRrfAypXA=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -10,7 +10,7 @@ import (
type HPPClient struct {
address *net.TCPAddr
endpoint *HPPServer
pid *types.PID
pid types.PID
}
// Endpoint returns the server the client is connecting to
@ -24,12 +24,12 @@ func (c *HPPClient) Address() net.Addr {
}
// PID returns the clients NEX PID
func (c *HPPClient) PID() *types.PID {
func (c *HPPClient) PID() types.PID {
return c.pid
}
// SetPID sets the clients NEX PID
func (c *HPPClient) SetPID(pid *types.PID) {
func (c *HPPClient) SetPID(pid types.PID) {
c.pid = pid
}

View file

@ -18,7 +18,7 @@ type HPPServer struct {
dataHandlers []func(packet PacketInterface)
errorEventHandlers []func(err *Error)
byteStreamSettings *ByteStreamSettings
AccountDetailsByPID func(pid *types.PID) (*Account, *Error)
AccountDetailsByPID func(pid types.PID) (*Account, *Error)
AccountDetailsByUsername func(username string) (*Account, *Error)
useVerboseRMC bool
}

View file

@ -10,10 +10,10 @@ var logger = plogger.NewLogger()
func init() {
initResultCodes()
types.RegisterVariantType(1, types.NewPrimitiveS64(0))
types.RegisterVariantType(2, types.NewPrimitiveF64(0))
types.RegisterVariantType(3, types.NewPrimitiveBool(false))
types.RegisterVariantType(1, types.NewInt64(0))
types.RegisterVariantType(2, types.NewDouble(0))
types.RegisterVariantType(3, types.NewBool(false))
types.RegisterVariantType(4, types.NewString(""))
types.RegisterVariantType(5, types.NewDateTime(0))
types.RegisterVariantType(6, types.NewPrimitiveU64(0))
types.RegisterVariantType(6, types.NewUInt64(0))
}

View file

@ -69,8 +69,8 @@ func NewKerberosEncryption(key []byte) *KerberosEncryption {
// KerberosTicket represents a ticket granting a user access to a secure server
type KerberosTicket struct {
SessionKey []byte
TargetPID *types.PID
InternalData *types.Buffer
TargetPID types.PID
InternalData types.Buffer
}
// Encrypt writes the ticket data to the provided stream and returns the encrypted byte slice
@ -94,8 +94,8 @@ func NewKerberosTicket() *KerberosTicket {
// KerberosTicketInternalData holds the internal data for a kerberos ticket to be processed by the server
type KerberosTicketInternalData struct {
Server *PRUDPServer // TODO - Remove this dependency and make a settings struct
Issued *types.DateTime
SourcePID *types.PID
Issued types.DateTime
SourcePID types.PID
SessionKey []byte
}
@ -152,10 +152,10 @@ func (ti *KerberosTicketInternalData) Decrypt(stream *ByteStreamIn, key []byte)
return fmt.Errorf("Failed to read Kerberos ticket internal data. %s", err.Error())
}
hash := md5.Sum(append(key, ticketKey.Value...))
hash := md5.Sum(append(key, ticketKey...))
key = hash[:]
stream = NewByteStreamIn(data.Value, stream.LibraryVersions, stream.Settings)
stream = NewByteStreamIn(data, stream.LibraryVersions, stream.Settings)
}
encryption := NewKerberosEncryption(key)
@ -190,12 +190,15 @@ func NewKerberosTicketInternalData(server *PRUDPServer) *KerberosTicketInternalD
}
// DeriveKerberosKey derives a users kerberos encryption key based on their PID and password
func DeriveKerberosKey(pid *types.PID, password []byte) []byte {
func DeriveKerberosKey(pid types.PID, password []byte) []byte {
iterationCount := int(65000 + pid%1024)
key := password
hash := make([]byte, md5.Size)
for i := 0; i < 65000+int(pid.Value())%1024; i++ {
hash := md5.Sum(key)
key = hash[:]
for i := 0; i < iterationCount; i++ {
sum := md5.Sum(key)
copy(hash, sum[:])
key = hash
}
return key

16
kerberos_test.go Normal file
View file

@ -0,0 +1,16 @@
package nex
import (
"encoding/hex"
"testing"
"github.com/PretendoNetwork/nex-go/v2/types"
"github.com/stretchr/testify/assert"
)
func TestDeriveGuestKey(t *testing.T) {
pid := types.NewPID(100)
password := []byte("MMQea3n!fsik")
result := DeriveKerberosKey(pid, password)
assert.Equal(t, "9ef318f0a170fb46aab595bf9644f9e1", hex.EncodeToString(result))
}

View file

@ -26,6 +26,22 @@ func (m *MutexMap[K, V]) Get(key K) (V, bool) {
return value, ok
}
// GetOrSetDefault returns the value for the given key if it exists. If it does not exist, it creates a default
// with the provided function and sets it for that key
func (m *MutexMap[K, V]) GetOrSetDefault(key K, createDefault func() V) V {
m.Lock()
defer m.Unlock()
value, ok := m.real[key]
if !ok {
value = createDefault()
m.real[key] = value
}
return value
}
// Has checks if a key exists in the map
func (m *MutexMap[K, V]) Has(key K) bool {
m.RLock()

44
packet_dispatch_queue.go Normal file
View file

@ -0,0 +1,44 @@
package nex
// PacketDispatchQueue is an implementation of rdv::PacketDispatchQueue.
// PacketDispatchQueue is used to sequence incoming packets.
// In the original library each virtual connection stream only uses a single PacketDispatchQueue, but starting
// in PRUDPv1 NEX virtual connections may have multiple reliable substreams and thus multiple PacketDispatchQueues.
type PacketDispatchQueue struct {
queue map[uint16]PRUDPPacketInterface
nextExpectedSequenceId *Counter[uint16]
}
// Queue adds a packet to the queue to be dispatched
func (pdq *PacketDispatchQueue) Queue(packet PRUDPPacketInterface) {
pdq.queue[packet.SequenceID()] = packet
}
// GetNextToDispatch returns the next packet to be dispatched, nil if there are no packets
// and a boolean indicating whether anything was returned.
func (pdq *PacketDispatchQueue) GetNextToDispatch() (PRUDPPacketInterface, bool) {
if packet, ok := pdq.queue[pdq.nextExpectedSequenceId.Value]; ok {
return packet, true
}
return nil, false
}
// Dispatched removes a packet from the queue to be dispatched.
func (pdq *PacketDispatchQueue) Dispatched(packet PRUDPPacketInterface) {
pdq.nextExpectedSequenceId.Next()
delete(pdq.queue, packet.SequenceID())
}
// Purge clears the queue of all pending packets.
func (pdq *PacketDispatchQueue) Purge() {
clear(pdq.queue)
}
// NewPacketDispatchQueue initializes a new PacketDispatchQueue with a starting counter value.
func NewPacketDispatchQueue() *PacketDispatchQueue {
return &PacketDispatchQueue{
queue: make(map[uint16]PRUDPPacketInterface),
nextExpectedSequenceId: NewCounter[uint16](2), // * First DATA packet from a client will always be 2 as the CONNECT packet is assigned 1
}
}

View file

@ -0,0 +1,63 @@
package nex
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestReorderPackets(t *testing.T) {
pdq := NewPacketDispatchQueue()
packet1 := makePacket(2)
packet2 := makePacket(3)
packet3 := makePacket(4)
pdq.Queue(packet2)
pdq.Queue(packet3)
pdq.Queue(packet1)
result, ok := pdq.GetNextToDispatch()
assert.True(t, ok)
assert.Equal(t, packet1, result)
pdq.Dispatched(packet1)
result, ok = pdq.GetNextToDispatch()
assert.True(t, ok)
assert.Equal(t, packet2, result)
pdq.Dispatched(packet2)
result, ok = pdq.GetNextToDispatch()
assert.True(t, ok)
assert.Equal(t, packet3, result)
pdq.Dispatched(packet3)
result, ok = pdq.GetNextToDispatch()
assert.False(t, ok)
assert.Nil(t, result)
}
func TestCallingInLoop(t *testing.T) {
pdq := NewPacketDispatchQueue()
packet1 := makePacket(2)
packet2 := makePacket(3)
packet3 := makePacket(4)
pdq.Queue(packet2)
pdq.Queue(packet3)
pdq.Queue(packet1)
for nextPacket, ok := pdq.GetNextToDispatch(); ok; nextPacket, ok = pdq.GetNextToDispatch() {
pdq.Dispatched(nextPacket)
}
assert.Equal(t, uint16(5), pdq.nextExpectedSequenceId.Value)
}
func makePacket(sequenceID uint16) PRUDPPacketInterface {
packet, _ := NewPRUDPPacketV0(nil, nil, nil)
packet.SetSequenceID(sequenceID)
return packet
}

View file

@ -3,6 +3,7 @@ package nex
import (
"crypto/md5"
"net"
"sync"
"time"
"github.com/PretendoNetwork/nex-go/v2/constants"
@ -16,24 +17,29 @@ type PRUDPConnection struct {
Socket *SocketConnection // * The connections parent socket
endpoint *PRUDPEndPoint // * The PRUDP endpoint the connection is connected to
ConnectionState ConnectionState
ID uint32 // * Connection ID
SessionID uint8 // * Random value generated at the start of the session. Client and server IDs do not need to match
ServerSessionID uint8 // * Random value generated at the start of the session. Client and server IDs do not need to match
SessionKey []byte // * Secret key generated at the start of the session. Used for encrypting packets to the secure server
pid *types.PID // * PID of the user
DefaultPRUDPVersion int // * The PRUDP version the connection was established with. Used for sending PING packets
StreamType constants.StreamType // * rdv::Stream::Type used in this connection
StreamID uint8 // * rdv::Stream ID, also called the "port number", used in this connection. 0-15 on PRUDPv0/v1, and 0-31 on PRUDPLite
StreamSettings *StreamSettings // * Settings for this virtual connection
Signature []byte // * Connection signature for packets coming from the client, as seen by the server
ServerConnectionSignature []byte // * Connection signature for packets coming from the server, as seen by the client
UnreliablePacketBaseKey []byte // * The base key used for encrypting unreliable DATA packets
slidingWindows *MutexMap[uint8, *SlidingWindow] // * Reliable packet substreams
ID uint32 // * Connection ID
SessionID uint8 // * Random value generated at the start of the session. Client and server IDs do not need to match
ServerSessionID uint8 // * Random value generated at the start of the session. Client and server IDs do not need to match
SessionKey []byte // * Secret key generated at the start of the session. Used for encrypting packets to the secure server
pid types.PID // * PID of the user
DefaultPRUDPVersion int // * The PRUDP version the connection was established with. Used for sending PING packets
StreamType constants.StreamType // * rdv::Stream::Type used in this connection
StreamID uint8 // * rdv::Stream ID, also called the "port number", used in this connection. 0-15 on PRUDPv0/v1, and 0-31 on PRUDPLite
StreamSettings *StreamSettings // * Settings for this virtual connection
Signature []byte // * Connection signature for packets coming from the client, as seen by the server
ServerConnectionSignature []byte // * Connection signature for packets coming from the server, as seen by the client
UnreliablePacketBaseKey []byte // * The base key used for encrypting unreliable DATA packets
rtt *RTT // * The round-trip transmission time of this connection
slidingWindows *MutexMap[uint8, *SlidingWindow] // * Outbound reliable packet substreams
packetDispatchQueues *MutexMap[uint8, *PacketDispatchQueue] // * Inbound reliable packet substreams
incomingFragmentBuffers *MutexMap[uint8, []byte] // * Buffers which store the incoming payloads from fragmented DATA packets
outgoingUnreliableSequenceIDCounter *Counter[uint16]
outgoingPingSequenceIDCounter *Counter[uint16]
lastSentPingTime time.Time
heartbeatTimer *time.Timer
pingKickTimer *time.Timer
StationURLs *types.List[*types.StationURL]
StationURLs types.List[types.StationURL]
mutex *sync.Mutex
}
// Endpoint returns the PRUDP endpoint the connections socket is connected to
@ -47,19 +53,24 @@ func (pc *PRUDPConnection) Address() net.Addr {
}
// PID returns the clients unique PID
func (pc *PRUDPConnection) PID() *types.PID {
func (pc *PRUDPConnection) PID() types.PID {
return pc.pid
}
// SetPID sets the clients unique PID
func (pc *PRUDPConnection) SetPID(pid *types.PID) {
func (pc *PRUDPConnection) SetPID(pid types.PID) {
pc.pid = pid
}
// reset resets the connection state to all zero values
func (pc *PRUDPConnection) reset() {
pc.ConnectionState = StateNotConnected
pc.packetDispatchQueues.Clear(func(_ uint8, packetDispatchQueue *PacketDispatchQueue) {
packetDispatchQueue.Purge()
})
pc.slidingWindows.Clear(func(_ uint8, slidingWindow *SlidingWindow) {
slidingWindow.ResendScheduler.Stop()
slidingWindow.TimeoutManager.Stop()
})
pc.Signature = make([]byte, 0)
@ -75,19 +86,10 @@ func (pc *PRUDPConnection) cleanup() {
pc.stopHeartbeatTimers()
pc.Socket.Connections.Delete(pc.SessionID)
pc.endpoint.emitConnectionEnded(pc)
if pc.Socket.Connections.Size() == 0 {
// * No more PRUDP connections, assume the socket connection is also closed
pc.endpoint.Server.Connections.Delete(pc.Socket.Address.String())
// TODO - Is there any other cleanup that needs to happen here?
// TODO - Should we add an event for when a socket closes too?
}
}
// InitializeSlidingWindows returns the InitializeSlidingWindows for the given substream
// InitializeSlidingWindows initializes the SlidingWindows for all substreams
func (pc *PRUDPConnection) InitializeSlidingWindows(maxSubstreamID uint8) {
// * Nuke any existing SlidingWindows
pc.slidingWindows = NewMutexMap[uint8, *SlidingWindow]()
@ -97,11 +99,21 @@ func (pc *PRUDPConnection) InitializeSlidingWindows(maxSubstreamID uint8) {
}
}
// CreateSlidingWindow returns the CreateSlidingWindow for the given substream
// InitializePacketDispatchQueues initializes the PacketDispatchQueues for all substreams
func (pc *PRUDPConnection) InitializePacketDispatchQueues(maxSubstreamID uint8) {
// * Nuke any existing PacketDispatchQueues
pc.packetDispatchQueues = NewMutexMap[uint8, *PacketDispatchQueue]()
for i := 0; i < int(maxSubstreamID+1); i++ {
pc.CreatePacketDispatchQueue(uint8(i))
}
}
// CreateSlidingWindow creates a new SlidingWindow for the given substream and returns it
// if there is not a SlidingWindow for the given substream id it creates a new one
func (pc *PRUDPConnection) CreateSlidingWindow(substreamID uint8) *SlidingWindow {
slidingWindow := NewSlidingWindow()
slidingWindow.incomingSequenceIDCounter = NewCounter[uint16](2) // * First DATA packet from the client has sequence ID 2
slidingWindow.outgoingSequenceIDCounter = NewCounter[uint16](0) // * First DATA packet from the server has sequence ID 1 (start counter at 0 and is incremeneted)
slidingWindow.sequenceIDCounter = NewCounter[uint16](0) // * First DATA packet from the server has sequence ID 1 (start counter at 0 and is incremeneted)
slidingWindow.streamSettings = pc.StreamSettings.Copy()
pc.slidingWindows.Set(substreamID, slidingWindow)
@ -123,6 +135,28 @@ func (pc *PRUDPConnection) SlidingWindow(substreamID uint8) *SlidingWindow {
return slidingWindow
}
// CreatePacketDispatchQueue creates a new PacketDispatchQueue for the given substream and returns it
func (pc *PRUDPConnection) CreatePacketDispatchQueue(substreamID uint8) *PacketDispatchQueue {
pdq := NewPacketDispatchQueue()
pc.packetDispatchQueues.Set(substreamID, pdq)
return pdq
}
// PacketDispatchQueue returns the PacketDispatchQueue for the given substream
// if there is not a PacketDispatchQueue for the given substream it creates a new one
func (pc *PRUDPConnection) PacketDispatchQueue(substreamID uint8) *PacketDispatchQueue {
packetDispatchQueue, ok := pc.packetDispatchQueues.Get(substreamID)
if !ok {
// * Fail-safe. The connection may not always have
// * the correct number of substreams. See the
// * comment in handleSocketMessage of PRUDPEndPoint
// * for more details
packetDispatchQueue = pc.CreatePacketDispatchQueue(substreamID)
}
return packetDispatchQueue
}
// setSessionKey sets the connection's session key and updates the SlidingWindows
func (pc *PRUDPConnection) setSessionKey(sessionKey []byte) {
pc.SessionKey = sessionKey
@ -174,6 +208,41 @@ func (pc *PRUDPConnection) resetHeartbeat() {
}
}
// Lock locks the inner mutex for the Connection
// This is used internally when reordering incoming fragmented packets to prevent
// race conditions when multiple packets for the same fragmented message are processed at once
func (pc *PRUDPConnection) Lock() {
pc.mutex.Lock()
}
// Unlock unlocks the inner mutex for the Connection
// This is used internally when reordering incoming fragmented packets to prevent
// race conditions when multiple packets for the same fragmented message are processed at once
func (pc *PRUDPConnection) Unlock() {
pc.mutex.Unlock()
}
// Gets the incoming fragment buffer for the given substream
func (pc *PRUDPConnection) GetIncomingFragmentBuffer(substreamID uint8) []byte {
buffer, ok := pc.incomingFragmentBuffers.Get(substreamID)
if !ok {
buffer = make([]byte, 0)
pc.incomingFragmentBuffers.Set(substreamID, buffer)
}
return buffer
}
// Sets the incoming fragment buffer for a given substream
func (pc *PRUDPConnection) SetIncomingFragmentBuffer(substreamID uint8, buffer []byte) {
pc.incomingFragmentBuffers.Set(substreamID, buffer)
}
// Clears the outgoing buffer for a given substream
func (pc *PRUDPConnection) ClearOutgoingBuffer(substreamID uint8) {
pc.incomingFragmentBuffers.Set(substreamID, make([]byte, 0))
}
func (pc *PRUDPConnection) startHeartbeat() {
endpoint := pc.endpoint
@ -192,9 +261,7 @@ func (pc *PRUDPConnection) startHeartbeat() {
// * If the heartbeat still did not restart, assume the
// * connection is dead and clean up
pc.pingKickTimer = time.AfterFunc(maxSilenceTime, func() {
pc.cleanup() // * "removed" event is dispatched here
endpoint.deleteConnectionByID(pc.ID)
endpoint.cleanupConnection(pc)
})
})
}
@ -214,14 +281,16 @@ func NewPRUDPConnection(socket *SocketConnection) *PRUDPConnection {
pc := &PRUDPConnection{
Socket: socket,
ConnectionState: StateNotConnected,
rtt: NewRTT(),
pid: types.NewPID(0),
slidingWindows: NewMutexMap[uint8, *SlidingWindow](),
packetDispatchQueues: NewMutexMap[uint8, *PacketDispatchQueue](),
outgoingUnreliableSequenceIDCounter: NewCounter[uint16](1),
outgoingPingSequenceIDCounter: NewCounter[uint16](0),
StationURLs: types.NewList[*types.StationURL](),
incomingFragmentBuffers: NewMutexMap[uint8, []byte](),
StationURLs: types.NewList[types.StationURL](),
mutex: &sync.Mutex{},
}
pc.StationURLs.Type = types.NewStationURL("")
return pc
}

View file

@ -19,21 +19,26 @@ import (
// and secure servers. However the functionality of rdv::PRUDPEndPoint and nn::nex::SecureEndPoint is seemingly
// identical. Rather than duplicate the logic from PRUDPEndpoint, a IsSecureEndpoint flag has been added instead.
type PRUDPEndPoint struct {
Server *PRUDPServer
StreamID uint8
DefaultStreamSettings *StreamSettings
Connections *MutexMap[string, *PRUDPConnection]
packetHandlers map[uint16]func(packet PRUDPPacketInterface)
packetEventHandlers map[string][]func(packet PacketInterface)
connectionEndedEventHandlers []func(connection *PRUDPConnection)
errorEventHandlers []func(err *Error)
ConnectionIDCounter *Counter[uint32]
ServerAccount *Account
AccountDetailsByPID func(pid *types.PID) (*Account, *Error)
AccountDetailsByUsername func(username string) (*Account, *Error)
IsSecureEndPoint bool
Server *PRUDPServer
StreamID uint8
DefaultStreamSettings *StreamSettings
Connections *MutexMap[string, *PRUDPConnection]
packetHandlers map[uint16]func(packet PRUDPPacketInterface)
packetEventHandlers map[string][]func(packet PacketInterface)
connectionEndedEventHandlers []func(connection *PRUDPConnection)
errorEventHandlers []func(err *Error)
ConnectionIDCounter *Counter[uint32]
ServerAccount *Account
AccountDetailsByPID func(pid types.PID) (*Account, *Error)
AccountDetailsByUsername func(username string) (*Account, *Error)
IsSecureEndPoint bool
CalcRetransmissionTimeoutCallback CalcRetransmissionTimeoutCallback
}
// CalcRetransmissionTimeoutCallback is an optional callback which can be used to override the RTO calculation
// for packets sent by this `PRUDPEndpoint`
type CalcRetransmissionTimeoutCallback func(rtt float64, sendCount uint32) time.Duration
// RegisterServiceProtocol registers a NEX service with the endpoint
func (pep *PRUDPEndPoint) RegisterServiceProtocol(protocol ServiceProtocol) {
protocol.SetEndpoint(pep)
@ -82,51 +87,63 @@ func (pep *PRUDPEndPoint) on(name string, handler func(packet PacketInterface))
func (pep *PRUDPEndPoint) emit(name string, packet PRUDPPacketInterface) {
if handlers, ok := pep.packetEventHandlers[name]; ok {
for _, handler := range handlers {
go handler(packet)
handler(packet)
}
}
}
func (pep *PRUDPEndPoint) emitConnectionEnded(connection *PRUDPConnection) {
for _, handler := range pep.connectionEndedEventHandlers {
go handler(connection)
handler(connection)
}
}
// EmitError calls all the endpoints error event handlers with the provided error
func (pep *PRUDPEndPoint) EmitError(err *Error) {
for _, handler := range pep.errorEventHandlers {
go handler(err)
handler(err)
}
}
// deleteConnectionByID deletes the connection with the specified ID
func (pep *PRUDPEndPoint) deleteConnectionByID(cid uint32) {
pep.Connections.DeleteIf(func(key string, value *PRUDPConnection) bool {
return value.ID == cid
// cleanupConnection cleans up and deletes a connection from this endpoint. Will lock the Connections mutex - make sure
// you don't hold it during a call, or this will deadlock
func (pep *PRUDPEndPoint) cleanupConnection(connection *PRUDPConnection) {
discriminator := fmt.Sprintf("%s-%d-%d", connection.Socket.Address.String(), connection.StreamType, connection.StreamID)
found := false
pep.Connections.RunAndDelete(discriminator, func(key string, conn *PRUDPConnection) {
found = true
})
// * Probably this connection is on a different PRUDPEndPoint
if !found {
logger.Warningf("Tried to delete connection %v (ID %v) but it doesn't exist!", discriminator, connection.ID)
}
// * We can't do this during RunAndDelete, since we hold the Connections mutex then
// * This way we avoid any recursive locking
connection.cleanup()
}
func (pep *PRUDPEndPoint) processPacket(packet PRUDPPacketInterface, socket *SocketConnection) {
streamType := packet.SourceVirtualPortStreamType()
streamID := packet.SourceVirtualPortStreamID()
discriminator := fmt.Sprintf("%s-%d-%d", socket.Address.String(), streamType, streamID)
connection, ok := pep.Connections.Get(discriminator)
if !ok {
connection = NewPRUDPConnection(socket)
connection := pep.Connections.GetOrSetDefault(discriminator, func() *PRUDPConnection {
connection := NewPRUDPConnection(socket)
connection.endpoint = pep
connection.ID = pep.ConnectionIDCounter.Next()
connection.DefaultPRUDPVersion = packet.Version()
connection.StreamType = streamType
connection.StreamID = streamID
connection.StreamSettings = pep.DefaultStreamSettings.Copy()
return connection
})
pep.Connections.Set(discriminator, connection)
}
connection.Lock()
defer connection.Unlock()
packet.SetSender(connection)
connection.resetHeartbeat()
if packet.HasFlag(constants.PacketFlagAck) || packet.HasFlag(constants.PacketFlagMultiAck) {
pep.handleAcknowledgment(packet)
@ -142,19 +159,26 @@ func (pep *PRUDPEndPoint) processPacket(packet PRUDPPacketInterface, socket *Soc
func (pep *PRUDPEndPoint) handleAcknowledgment(packet PRUDPPacketInterface) {
connection := packet.Sender().(*PRUDPConnection)
if connection.ConnectionState != StateConnected {
// TODO - Log this?
// * Connection is in a bad state, drop the packet and let it die
if connection.ConnectionState < StateConnected {
return
}
connection.resetHeartbeat()
if packet.HasFlag(constants.PacketFlagMultiAck) {
pep.handleMultiAcknowledgment(packet)
return
}
slidingWindow := connection.SlidingWindow(packet.SubstreamID())
slidingWindow.ResendScheduler.AcknowledgePacket(packet.SequenceID())
if packet.Type() == constants.PingPacket {
if packet.SequenceID() == connection.outgoingPingSequenceIDCounter.Value {
connection.rtt.Adjust(time.Since(connection.lastSentPingTime))
}
} else {
slidingWindow := connection.SlidingWindow(packet.SubstreamID())
slidingWindow.TimeoutManager.AcknowledgePacket(packet.SequenceID())
}
}
func (pep *PRUDPEndPoint) handleMultiAcknowledgment(packet PRUDPPacketInterface) {
@ -167,13 +191,13 @@ func (pep *PRUDPEndPoint) handleMultiAcknowledgment(packet PRUDPPacketInterface)
if packet.SubstreamID() == 1 {
// * New aggregate acknowledgment packets set this to 1
// * and encode the real substream ID in in the payload
substreamID, _ := stream.ReadPrimitiveUInt8()
additionalIDsCount, _ := stream.ReadPrimitiveUInt8()
baseSequenceID, _ = stream.ReadPrimitiveUInt16LE()
substreamID, _ := stream.ReadUInt8()
additionalIDsCount, _ := stream.ReadUInt8()
baseSequenceID, _ = stream.ReadUInt16LE()
slidingWindow = connection.SlidingWindow(substreamID)
for i := 0; i < int(additionalIDsCount); i++ {
additionalID, _ := stream.ReadPrimitiveUInt16LE()
additionalID, _ := stream.ReadUInt16LE()
sequenceIDs = append(sequenceIDs, additionalID)
}
} else {
@ -184,14 +208,14 @@ func (pep *PRUDPEndPoint) handleMultiAcknowledgment(packet PRUDPPacketInterface)
baseSequenceID = packet.SequenceID()
for stream.Remaining() > 0 {
additionalID, _ := stream.ReadPrimitiveUInt16LE()
additionalID, _ := stream.ReadUInt16LE()
sequenceIDs = append(sequenceIDs, additionalID)
}
}
// * MutexMap.Each locks the mutex, can't remove while reading.
// * Have to just loop again
slidingWindow.ResendScheduler.packets.Each(func(sequenceID uint16, pending *PendingPacket) bool {
slidingWindow.TimeoutManager.packets.Each(func(sequenceID uint16, pending PRUDPPacketInterface) bool {
if sequenceID <= baseSequenceID && !slices.Contains(sequenceIDs, sequenceID) {
sequenceIDs = append(sequenceIDs, sequenceID)
}
@ -201,18 +225,13 @@ func (pep *PRUDPEndPoint) handleMultiAcknowledgment(packet PRUDPPacketInterface)
// * Actually remove the packets from the pool
for _, sequenceID := range sequenceIDs {
slidingWindow.ResendScheduler.AcknowledgePacket(sequenceID)
slidingWindow.TimeoutManager.AcknowledgePacket(sequenceID)
}
}
func (pep *PRUDPEndPoint) handleSyn(packet PRUDPPacketInterface) {
connection := packet.Sender().(*PRUDPConnection)
if connection.ConnectionState != StateNotConnected {
// TODO - Log this?
// * Connection is in a bad state, drop the packet and let it die
return
}
connection.resetHeartbeat()
var ack PRUDPPacketInterface
@ -259,12 +278,12 @@ func (pep *PRUDPEndPoint) handleSyn(packet PRUDPPacketInterface) {
func (pep *PRUDPEndPoint) handleConnect(packet PRUDPPacketInterface) {
connection := packet.Sender().(*PRUDPConnection)
if connection.ConnectionState != StateConnecting {
// TODO - Log this?
// * Connection is in a bad state, drop the packet and let it die
if connection.ConnectionState < StateConnecting {
return
}
connection.resetHeartbeat()
var ack PRUDPPacketInterface
if packet.Version() == 2 {
@ -306,9 +325,11 @@ func (pep *PRUDPEndPoint) handleConnect(packet PRUDPPacketInterface) {
ack.supportedFunctions = packet.(*PRUDPPacketV1).supportedFunctions
connection.InitializeSlidingWindows(ack.maximumSubstreamID)
connection.InitializePacketDispatchQueues(ack.maximumSubstreamID)
connection.outgoingUnreliableSequenceIDCounter = NewCounter[uint16](packet.(*PRUDPPacketV1).initialUnreliableSequenceID)
} else {
connection.InitializeSlidingWindows(0)
connection.InitializePacketDispatchQueues(0)
}
payload := make([]byte, 0)
@ -385,12 +406,12 @@ func (pep *PRUDPEndPoint) handleConnect(packet PRUDPPacketInterface) {
func (pep *PRUDPEndPoint) handleData(packet PRUDPPacketInterface) {
connection := packet.Sender().(*PRUDPConnection)
if connection.ConnectionState != StateConnected {
// TODO - Log this?
// * Connection is in a bad state, drop the packet and let it die
if connection.ConnectionState < StateConnected {
return
}
connection.resetHeartbeat()
if packet.HasFlag(constants.PacketFlagReliable) {
pep.handleReliable(packet)
} else {
@ -400,7 +421,6 @@ func (pep *PRUDPEndPoint) handleData(packet PRUDPPacketInterface) {
func (pep *PRUDPEndPoint) handleDisconnect(packet PRUDPPacketInterface) {
// TODO - Should we check the state here, or just let the connection disconnect at any time?
// TODO - Should we bother to set the connections state here? It's being destroyed anyway
if packet.HasFlag(constants.PacketFlagNeedsAck) {
pep.acknowledgePacket(packet)
@ -410,47 +430,60 @@ func (pep *PRUDPEndPoint) handleDisconnect(packet PRUDPPacketInterface) {
streamID := packet.SourceVirtualPortStreamID()
discriminator := fmt.Sprintf("%s-%d-%d", packet.Sender().Address().String(), streamType, streamID)
if connection, ok := pep.Connections.Get(discriminator); ok {
connection.cleanup()
pep.Connections.Delete(discriminator)
pep.cleanupConnection(connection)
}
pep.emit("disconnect", packet)
}
func (pep *PRUDPEndPoint) handlePing(packet PRUDPPacketInterface) {
connection := packet.Sender().(*PRUDPConnection)
if connection.ConnectionState < StateConnected {
return
}
connection.resetHeartbeat()
if packet.HasFlag(constants.PacketFlagNeedsAck) {
pep.acknowledgePacket(packet)
}
if packet.HasFlag(constants.PacketFlagReliable) {
substreamID := packet.SubstreamID()
packetDispatchQueue := connection.PacketDispatchQueue(substreamID)
packetDispatchQueue.Queue(packet)
}
}
func (pep *PRUDPEndPoint) readKerberosTicket(payload []byte) ([]byte, *types.PID, uint32, error) {
func (pep *PRUDPEndPoint) readKerberosTicket(payload []byte) ([]byte, types.PID, uint32, error) {
stream := NewByteStreamIn(payload, pep.Server.LibraryVersions, pep.ByteStreamSettings())
ticketData := types.NewBuffer(nil)
if err := ticketData.ExtractFrom(stream); err != nil {
return nil, nil, 0, err
return nil, 0, 0, err
}
requestData := types.NewBuffer(nil)
if err := requestData.ExtractFrom(stream); err != nil {
return nil, nil, 0, err
return nil, 0, 0, err
}
// * Sanity checks
serverAccount, _ := pep.AccountDetailsByUsername(pep.ServerAccount.Username)
if serverAccount == nil {
return nil, nil, 0, errors.New("Failed to find endpoint server account")
return nil, 0, 0, errors.New("Failed to find endpoint server account")
}
if serverAccount.Password != pep.ServerAccount.Password {
return nil, nil, 0, errors.New("Password for endpoint server account does not match the records from AccountDetailsByUsername")
return nil, 0, 0, errors.New("Password for endpoint server account does not match the records from AccountDetailsByUsername")
}
serverKey := DeriveKerberosKey(serverAccount.PID, []byte(serverAccount.Password))
ticket := NewKerberosTicketInternalData(pep.Server)
if err := ticket.Decrypt(NewByteStreamIn(ticketData.Value, pep.Server.LibraryVersions, pep.ByteStreamSettings()), serverKey); err != nil {
return nil, nil, 0, err
if err := ticket.Decrypt(NewByteStreamIn(ticketData, pep.Server.LibraryVersions, pep.ByteStreamSettings()), serverKey); err != nil {
return nil, 0, 0, err
}
ticketTime := ticket.Issued.Standard()
@ -458,32 +491,36 @@ func (pep *PRUDPEndPoint) readKerberosTicket(payload []byte) ([]byte, *types.PID
timeLimit := ticketTime.Add(time.Minute * 2)
if serverTime.After(timeLimit) {
return nil, nil, 0, errors.New("Kerberos ticket expired")
return nil, 0, 0, errors.New("Kerberos ticket expired")
}
sessionKey := ticket.SessionKey
kerberos := NewKerberosEncryption(sessionKey)
decryptedRequestData, err := kerberos.Decrypt(requestData.Value)
decryptedRequestData, err := kerberos.Decrypt(requestData)
if err != nil {
return nil, nil, 0, err
return nil, 0, 0, err
}
checkDataStream := NewByteStreamIn(decryptedRequestData, pep.Server.LibraryVersions, pep.ByteStreamSettings())
userPID := types.NewPID(0)
if err := userPID.ExtractFrom(checkDataStream); err != nil {
return nil, nil, 0, err
return nil, 0, 0, err
}
_, err = checkDataStream.ReadPrimitiveUInt32LE() // * CID of secure server station url
if err != nil {
return nil, nil, 0, err
if userPID != ticket.SourcePID {
return nil, 0, 0, errors.New("User PID and ticket source PID mismatch")
}
responseCheck, err := checkDataStream.ReadPrimitiveUInt32LE()
_, err = checkDataStream.ReadUInt32LE() // * CID of secure server station url
if err != nil {
return nil, nil, 0, err
return nil, 0, 0, err
}
responseCheck, err := checkDataStream.ReadUInt32LE()
if err != nil {
return nil, 0, 0, err
}
return sessionKey, userPID, responseCheck, nil
@ -526,17 +563,20 @@ func (pep *PRUDPEndPoint) handleReliable(packet PRUDPPacketInterface) {
connection := packet.Sender().(*PRUDPConnection)
slidingWindow := packet.Sender().(*PRUDPConnection).SlidingWindow(packet.SubstreamID())
substreamID := packet.SubstreamID()
for _, pendingPacket := range slidingWindow.Update(packet) {
if packet.Type() == constants.DataPacket {
packetDispatchQueue := connection.PacketDispatchQueue(substreamID)
packetDispatchQueue.Queue(packet)
for nextPacket, ok := packetDispatchQueue.GetNextToDispatch(); ok; nextPacket, ok = packetDispatchQueue.GetNextToDispatch() {
if nextPacket.Type() == constants.DataPacket {
var decryptedPayload []byte
if packet.Version() != 2 {
decryptedPayload = pendingPacket.decryptPayload()
if nextPacket.Version() != 2 {
decryptedPayload = nextPacket.decryptPayload()
} else {
// * PRUDPLite does not encrypt payloads
decryptedPayload = pendingPacket.Payload()
decryptedPayload = nextPacket.Payload()
}
decompressedPayload, err := connection.StreamSettings.CompressionAlgorithm.Decompress(decryptedPayload)
@ -544,23 +584,26 @@ func (pep *PRUDPEndPoint) handleReliable(packet PRUDPPacketInterface) {
logger.Error(err.Error())
}
payload := slidingWindow.AddFragment(decompressedPayload)
incomingFragmentBuffer := connection.GetIncomingFragmentBuffer(substreamID)
incomingFragmentBuffer = append(incomingFragmentBuffer, decompressedPayload...)
connection.SetIncomingFragmentBuffer(substreamID, incomingFragmentBuffer)
if packet.getFragmentID() == 0 {
if nextPacket.getFragmentID() == 0 {
message := NewRMCMessage(pep)
err := message.FromBytes(payload)
err := message.FromBytes(incomingFragmentBuffer)
if err != nil {
// TODO - Should this return the error too?
logger.Error(err.Error())
}
slidingWindow.ResetFragmentedPayload()
nextPacket.SetRMCMessage(message)
connection.ClearOutgoingBuffer(substreamID)
packet.SetRMCMessage(message)
pep.emit("data", packet)
pep.emit("data", nextPacket)
}
}
packetDispatchQueue.Dispatched(nextPacket)
}
}
@ -669,7 +712,7 @@ func (pep *PRUDPEndPoint) FindConnectionByPID(pid uint64) *PRUDPConnection {
var connection *PRUDPConnection
pep.Connections.Each(func(discriminator string, pc *PRUDPConnection) bool {
if pc.pid.Value() == pid {
if uint64(pc.pid) == pid && pc.ConnectionState == StateConnected {
connection = pc
return true
}
@ -680,6 +723,39 @@ func (pep *PRUDPEndPoint) FindConnectionByPID(pid uint64) *PRUDPConnection {
return connection
}
// ComputeRetransmitTimeout computes the RTO (Retransmit timeout) for a given packet
func (pep *PRUDPEndPoint) ComputeRetransmitTimeout(packet PRUDPPacketInterface) time.Duration {
connection := packet.Sender().(*PRUDPConnection)
rtt := connection.rtt
if callback := pep.CalcRetransmissionTimeoutCallback; callback != nil {
rttAverage := rtt.GetRTTSmoothedAvg()
rttDeviation := rtt.GetRTTSmoothedDev()
return callback(rttAverage+rttDeviation*4.0, packet.SendCount())
}
var retransmitTimeBase int64
if packet.Type() == constants.SynPacket {
retransmitTimeBase = int64(pep.DefaultStreamSettings.SynInitialRTT)
} else {
retransmitTimeBase = int64(pep.DefaultStreamSettings.InitialRTT)
if rtt.Initialized() {
retransmitTimeBase = int64(rtt.Average()/time.Millisecond) / 8
}
}
retransmitTimeBaseMultiplier := packet.SendCount()
var retransmitMultiplier float64
if packet.SendCount() < pep.DefaultStreamSettings.ExtraRetransmitTimeoutTrigger {
retransmitMultiplier = float64(pep.DefaultStreamSettings.RetransmitTimeoutMultiplier)
} else {
retransmitMultiplier = float64(pep.DefaultStreamSettings.ExtraRetransmitTimeoutMultiplier)
}
return time.Duration(float64(retransmitTimeBase*int64(retransmitTimeBaseMultiplier))*retransmitMultiplier) * time.Millisecond
}
// AccessKey returns the servers sandbox access key
func (pep *PRUDPEndPoint) AccessKey() string {
return pep.Server.AccessKey

View file

@ -2,6 +2,7 @@ package nex
import (
"crypto/rc4"
"time"
"github.com/PretendoNetwork/nex-go/v2/constants"
)
@ -24,6 +25,9 @@ type PRUDPPacket struct {
fragmentID uint8
payload []byte
message *RMCMessage
sendCount uint32
sentAt time.Time
timeout *Timeout
}
// SetSender sets the Client who sent the packet
@ -184,6 +188,32 @@ func (p *PRUDPPacket) SetRMCMessage(message *RMCMessage) {
p.message = message
}
// SendCount returns the number of times this packet has been sent
func (p *PRUDPPacket) SendCount() uint32 {
return p.sendCount
}
func (p *PRUDPPacket) incrementSendCount() {
p.sendCount++
}
// SentAt returns the latest time that this packet has been sent
func (p *PRUDPPacket) SentAt() time.Time {
return p.sentAt
}
func (p *PRUDPPacket) setSentAt(time time.Time) {
p.sentAt = time
}
func (p *PRUDPPacket) getTimeout() *Timeout {
return p.timeout
}
func (p *PRUDPPacket) setTimeout(timeout *Timeout) {
p.timeout = timeout
}
func (p *PRUDPPacket) processUnreliableCrypto() []byte {
// * Since unreliable DATA packets can come in out of
// * order, each packet uses a dedicated RC4 stream

View file

@ -2,6 +2,7 @@ package nex
import (
"net"
"time"
"github.com/PretendoNetwork/nex-go/v2/constants"
)
@ -36,6 +37,12 @@ type PRUDPPacketInterface interface {
SetPayload(payload []byte)
RMCMessage() *RMCMessage
SetRMCMessage(message *RMCMessage)
SendCount() uint32
incrementSendCount()
SentAt() time.Time
setSentAt(time time.Time)
getTimeout() *Timeout
setTimeout(timeout *Timeout)
decode() error
setSignature(signature []byte)
calculateConnectionSignature(addr net.Addr) ([]byte, error)

View file

@ -4,6 +4,7 @@ import (
"crypto/hmac"
"crypto/md5"
"encoding/binary"
"errors"
"fmt"
"net"
@ -117,7 +118,7 @@ func (p *PRUDPPacketLite) Version() int {
// decode parses the packets data
func (p *PRUDPPacketLite) decode() error {
magic, err := p.readStream.ReadPrimitiveUInt8()
magic, err := p.readStream.ReadUInt8()
if err != nil {
return fmt.Errorf("Failed to read PRUDPLite magic. %s", err.Error())
}
@ -126,17 +127,17 @@ func (p *PRUDPPacketLite) decode() error {
return fmt.Errorf("Invalid PRUDPLite magic. Expected 0x80, got 0x%x", magic)
}
p.optionsLength, err = p.readStream.ReadPrimitiveUInt8()
p.optionsLength, err = p.readStream.ReadUInt8()
if err != nil {
return fmt.Errorf("Failed to decode PRUDPLite options length. %s", err.Error())
}
payloadLength, err := p.readStream.ReadPrimitiveUInt16LE()
payloadLength, err := p.readStream.ReadUInt16LE()
if err != nil {
return fmt.Errorf("Failed to decode PRUDPLite payload length. %s", err.Error())
}
streamTypes, err := p.readStream.ReadPrimitiveUInt8()
streamTypes, err := p.readStream.ReadUInt8()
if err != nil {
return fmt.Errorf("Failed to decode PRUDPLite virtual ports stream types. %s", err.Error())
}
@ -144,22 +145,22 @@ func (p *PRUDPPacketLite) decode() error {
p.sourceVirtualPortStreamType = constants.StreamType(streamTypes >> 4)
p.destinationVirtualPortStreamType = constants.StreamType(streamTypes & 0xF)
p.sourceVirtualPortStreamID, err = p.readStream.ReadPrimitiveUInt8()
p.sourceVirtualPortStreamID, err = p.readStream.ReadUInt8()
if err != nil {
return fmt.Errorf("Failed to decode PRUDPLite virtual source port. %s", err.Error())
}
p.destinationVirtualPortStreamID, err = p.readStream.ReadPrimitiveUInt8()
p.destinationVirtualPortStreamID, err = p.readStream.ReadUInt8()
if err != nil {
return fmt.Errorf("Failed to decode PRUDPLite virtual destination port. %s", err.Error())
}
p.fragmentID, err = p.readStream.ReadPrimitiveUInt8()
p.fragmentID, err = p.readStream.ReadUInt8()
if err != nil {
return fmt.Errorf("Failed to decode PRUDPLite fragment ID. %s", err.Error())
}
typeAndFlags, err := p.readStream.ReadPrimitiveUInt16LE()
typeAndFlags, err := p.readStream.ReadUInt16LE()
if err != nil {
return fmt.Errorf("Failed to read PRUDPLite type and flags. %s", err.Error())
}
@ -167,7 +168,7 @@ func (p *PRUDPPacketLite) decode() error {
p.flags = typeAndFlags >> 4
p.packetType = typeAndFlags & 0xF
p.sequenceID, err = p.readStream.ReadPrimitiveUInt16LE()
p.sequenceID, err = p.readStream.ReadUInt16LE()
if err != nil {
return fmt.Errorf("Failed to decode PRUDPLite sequence ID. %s", err.Error())
}
@ -177,6 +178,10 @@ func (p *PRUDPPacketLite) decode() error {
return fmt.Errorf("Failed to decode PRUDPLite options. %s", err.Error())
}
if p.readStream.Remaining() < uint64(payloadLength) {
return errors.New("Failed to read PRUDPLite payload. Not have enough data")
}
p.payload = p.readStream.ReadBytesNext(int64(payloadLength))
return nil
@ -188,15 +193,15 @@ func (p *PRUDPPacketLite) Bytes() []byte {
stream := NewByteStreamOut(p.server.LibraryVersions, p.server.ByteStreamSettings)
stream.WritePrimitiveUInt8(0x80)
stream.WritePrimitiveUInt8(uint8(len(options)))
stream.WritePrimitiveUInt16LE(uint16(len(p.payload)))
stream.WritePrimitiveUInt8(uint8((p.sourceVirtualPortStreamType << 4) | p.destinationVirtualPortStreamType))
stream.WritePrimitiveUInt8(p.sourceVirtualPortStreamID)
stream.WritePrimitiveUInt8(p.destinationVirtualPortStreamID)
stream.WritePrimitiveUInt8(p.fragmentID)
stream.WritePrimitiveUInt16LE(p.packetType | (p.flags << 4))
stream.WritePrimitiveUInt16LE(p.sequenceID)
stream.WriteUInt8(0x80)
stream.WriteUInt8(uint8(len(options)))
stream.WriteUInt16LE(uint16(len(p.payload)))
stream.WriteUInt8(uint8((p.sourceVirtualPortStreamType << 4) | p.destinationVirtualPortStreamType))
stream.WriteUInt8(p.sourceVirtualPortStreamID)
stream.WriteUInt8(p.destinationVirtualPortStreamID)
stream.WriteUInt8(p.fragmentID)
stream.WriteUInt16LE(p.packetType | (p.flags << 4))
stream.WriteUInt16LE(p.sequenceID)
stream.Grow(int64(len(options)))
stream.WriteBytesNext(options)
@ -208,52 +213,64 @@ func (p *PRUDPPacketLite) Bytes() []byte {
}
func (p *PRUDPPacketLite) decodeOptions() error {
if p.readStream.Remaining() < uint64(p.optionsLength) {
return errors.New("Not have enough data")
}
data := p.readStream.ReadBytesNext(int64(p.optionsLength))
optionsStream := NewByteStreamIn(data, p.server.LibraryVersions, p.server.ByteStreamSettings)
for optionsStream.Remaining() > 0 {
optionID, err := optionsStream.ReadPrimitiveUInt8()
optionID, err := optionsStream.ReadUInt8()
if err != nil {
return err
}
optionSize, err := optionsStream.ReadPrimitiveUInt8() // * Options size. We already know the size based on the ID, though
optionSize, err := optionsStream.ReadUInt8() // * Options size. We already know the size based on the ID, though
if err != nil {
return err
}
if p.packetType == constants.SynPacket || p.packetType == constants.ConnectPacket {
if optionID == 0 {
p.supportedFunctions, err = optionsStream.ReadPrimitiveUInt32LE()
p.supportedFunctions, err = optionsStream.ReadUInt32LE()
p.minorVersion = p.supportedFunctions & 0xFF
p.supportedFunctions = p.supportedFunctions >> 8
}
if optionID == 1 {
p.connectionSignature = optionsStream.ReadBytesNext(int64(optionSize))
if optionsStream.Remaining() < uint64(optionSize) {
err = errors.New("Failed to read connection signature. Not have enough data")
} else {
p.connectionSignature = optionsStream.ReadBytesNext(int64(optionSize))
}
}
if optionID == 4 {
p.maximumSubstreamID, err = optionsStream.ReadPrimitiveUInt8()
p.maximumSubstreamID, err = optionsStream.ReadUInt8()
}
}
if p.packetType == constants.ConnectPacket {
if optionID == 3 {
p.initialUnreliableSequenceID, err = optionsStream.ReadPrimitiveUInt16LE()
p.initialUnreliableSequenceID, err = optionsStream.ReadUInt16LE()
}
}
if p.packetType == constants.DataPacket {
if optionID == 2 {
p.fragmentID, err = optionsStream.ReadPrimitiveUInt8()
p.fragmentID, err = optionsStream.ReadUInt8()
}
}
if p.packetType == constants.ConnectPacket && !p.HasFlag(constants.PacketFlagAck) {
if optionID == 0x80 {
p.liteSignature = optionsStream.ReadBytesNext(int64(optionSize))
if optionsStream.Remaining() < uint64(optionSize) {
err = errors.New("Failed to read lite signature. Not have enough data")
} else {
p.liteSignature = optionsStream.ReadBytesNext(int64(optionSize))
}
}
}
@ -272,20 +289,20 @@ func (p *PRUDPPacketLite) encodeOptions() []byte {
optionsStream := NewByteStreamOut(p.server.LibraryVersions, p.server.ByteStreamSettings)
if p.packetType == constants.SynPacket || p.packetType == constants.ConnectPacket {
optionsStream.WritePrimitiveUInt8(0)
optionsStream.WritePrimitiveUInt8(4)
optionsStream.WritePrimitiveUInt32LE(p.minorVersion | (p.supportedFunctions << 8))
optionsStream.WriteUInt8(0)
optionsStream.WriteUInt8(4)
optionsStream.WriteUInt32LE(p.minorVersion | (p.supportedFunctions << 8))
if p.packetType == constants.SynPacket && p.HasFlag(constants.PacketFlagAck) {
optionsStream.WritePrimitiveUInt8(1)
optionsStream.WritePrimitiveUInt8(16)
optionsStream.WriteUInt8(1)
optionsStream.WriteUInt8(16)
optionsStream.Grow(16)
optionsStream.WriteBytesNext(p.connectionSignature)
}
if p.packetType == constants.ConnectPacket && !p.HasFlag(constants.PacketFlagAck) {
optionsStream.WritePrimitiveUInt8(1)
optionsStream.WritePrimitiveUInt8(16)
optionsStream.WriteUInt8(1)
optionsStream.WriteUInt8(16)
optionsStream.Grow(16)
optionsStream.WriteBytesNext(p.liteSignature)
}

View file

@ -68,14 +68,14 @@ func (p *PRUDPPacketV0) decode() error {
server := p.server
start := p.readStream.ByteOffset()
source, err := p.readStream.ReadPrimitiveUInt8()
source, err := p.readStream.ReadUInt8()
if err != nil {
return fmt.Errorf("Failed to read PRUDPv0 source. %s", err.Error())
}
p.sourceVirtualPort = VirtualPort(source)
destination, err := p.readStream.ReadPrimitiveUInt8()
destination, err := p.readStream.ReadUInt8()
if err != nil {
return fmt.Errorf("Failed to read PRUDPv0 destination. %s", err.Error())
}
@ -83,7 +83,7 @@ func (p *PRUDPPacketV0) decode() error {
p.destinationVirtualPort = VirtualPort(destination)
if server.PRUDPV0Settings.IsQuazalMode {
typeAndFlags, err := p.readStream.ReadPrimitiveUInt8()
typeAndFlags, err := p.readStream.ReadUInt8()
if err != nil {
return fmt.Errorf("Failed to read PRUDPv0 type and flags. %s", err.Error())
}
@ -91,7 +91,7 @@ func (p *PRUDPPacketV0) decode() error {
p.flags = uint16(typeAndFlags >> 3)
p.packetType = uint16(typeAndFlags & 7)
} else {
typeAndFlags, err := p.readStream.ReadPrimitiveUInt16LE()
typeAndFlags, err := p.readStream.ReadUInt16LE()
if err != nil {
return fmt.Errorf("Failed to read PRUDPv0 type and flags. %s", err.Error())
}
@ -100,14 +100,14 @@ func (p *PRUDPPacketV0) decode() error {
p.packetType = typeAndFlags & 0xF
}
p.sessionID, err = p.readStream.ReadPrimitiveUInt8()
p.sessionID, err = p.readStream.ReadUInt8()
if err != nil {
return fmt.Errorf("Failed to read PRUDPv0 session ID. %s", err.Error())
}
p.signature = p.readStream.ReadBytesNext(4)
p.sequenceID, err = p.readStream.ReadPrimitiveUInt16LE()
p.sequenceID, err = p.readStream.ReadUInt16LE()
if err != nil {
return fmt.Errorf("Failed to read PRUDPv0 sequence ID. %s", err.Error())
}
@ -125,7 +125,7 @@ func (p *PRUDPPacketV0) decode() error {
return errors.New("Failed to read PRUDPv0 fragment ID. Not have enough data")
}
p.fragmentID, err = p.readStream.ReadPrimitiveUInt8()
p.fragmentID, err = p.readStream.ReadUInt8()
if err != nil {
return fmt.Errorf("Failed to read PRUDPv0 fragment ID. %s", err.Error())
}
@ -138,7 +138,7 @@ func (p *PRUDPPacketV0) decode() error {
return errors.New("Failed to read PRUDPv0 payload size. Not have enough data")
}
payloadSize, err = p.readStream.ReadPrimitiveUInt16LE()
payloadSize, err = p.readStream.ReadUInt16LE()
if err != nil {
return fmt.Errorf("Failed to read PRUDPv0 payload size. %s", err.Error())
}
@ -169,9 +169,9 @@ func (p *PRUDPPacketV0) decode() error {
var checksumU8 uint8
if server.PRUDPV0Settings.UseEnhancedChecksum {
checksum, err = p.readStream.ReadPrimitiveUInt32LE()
checksum, err = p.readStream.ReadUInt32LE()
} else {
checksumU8, err = p.readStream.ReadPrimitiveUInt8()
checksumU8, err = p.readStream.ReadUInt8()
checksum = uint32(checksumU8)
}
@ -193,19 +193,19 @@ func (p *PRUDPPacketV0) Bytes() []byte {
server := p.server
stream := NewByteStreamOut(server.LibraryVersions, server.ByteStreamSettings)
stream.WritePrimitiveUInt8(uint8(p.sourceVirtualPort))
stream.WritePrimitiveUInt8(uint8(p.destinationVirtualPort))
stream.WriteUInt8(uint8(p.sourceVirtualPort))
stream.WriteUInt8(uint8(p.destinationVirtualPort))
if server.PRUDPV0Settings.IsQuazalMode {
stream.WritePrimitiveUInt8(uint8(p.packetType | (p.flags << 3)))
stream.WriteUInt8(uint8(p.packetType | (p.flags << 3)))
} else {
stream.WritePrimitiveUInt16LE(p.packetType | (p.flags << 4))
stream.WriteUInt16LE(p.packetType | (p.flags << 4))
}
stream.WritePrimitiveUInt8(p.sessionID)
stream.WriteUInt8(p.sessionID)
stream.Grow(int64(len(p.signature)))
stream.WriteBytesNext(p.signature)
stream.WritePrimitiveUInt16LE(p.sequenceID)
stream.WriteUInt16LE(p.sequenceID)
if p.packetType == constants.SynPacket || p.packetType == constants.ConnectPacket {
stream.Grow(int64(len(p.connectionSignature)))
@ -213,11 +213,11 @@ func (p *PRUDPPacketV0) Bytes() []byte {
}
if p.packetType == constants.DataPacket {
stream.WritePrimitiveUInt8(p.fragmentID)
stream.WriteUInt8(p.fragmentID)
}
if p.HasFlag(constants.PacketFlagHasSize) {
stream.WritePrimitiveUInt16LE(uint16(len(p.payload)))
stream.WriteUInt16LE(uint16(len(p.payload)))
}
if len(p.payload) > 0 {
@ -228,9 +228,9 @@ func (p *PRUDPPacketV0) Bytes() []byte {
checksum := p.server.PRUDPV0Settings.ChecksumCalculator(p, stream.Bytes())
if server.PRUDPV0Settings.UseEnhancedChecksum {
stream.WritePrimitiveUInt32LE(checksum)
stream.WriteUInt32LE(checksum)
} else {
stream.WritePrimitiveUInt8(uint8(checksum))
stream.WriteUInt8(uint8(checksum))
}
return stream.Bytes()

View file

@ -89,6 +89,10 @@ func (p *PRUDPPacketV1) decode() error {
return fmt.Errorf("Failed to decode PRUDPv1 header. %s", err.Error())
}
if p.readStream.Remaining() < 16 {
return errors.New("Failed to read PRUDPv1 signature. Not have enough data")
}
p.signature = p.readStream.ReadBytesNext(16)
err = p.decodeOptions()
@ -96,6 +100,10 @@ func (p *PRUDPPacketV1) decode() error {
return fmt.Errorf("Failed to decode PRUDPv1 options. %s", err.Error())
}
if p.readStream.Remaining() < uint64(p.payloadLength) {
return errors.New("Failed to read PRUDPv1 payload. Not have enough data")
}
p.payload = p.readStream.ReadBytesNext(int64(p.payloadLength))
return nil
@ -134,7 +142,7 @@ func (p *PRUDPPacketV1) decodeHeader() error {
return errors.New("Failed to read PRUDPv1 magic. Not have enough data")
}
version, err := p.readStream.ReadPrimitiveUInt8()
version, err := p.readStream.ReadUInt8()
if err != nil {
return fmt.Errorf("Failed to decode PRUDPv1 version. %s", err.Error())
}
@ -143,24 +151,24 @@ func (p *PRUDPPacketV1) decodeHeader() error {
return fmt.Errorf("Invalid PRUDPv1 version. Expected 1, got %d", version)
}
p.optionsLength, err = p.readStream.ReadPrimitiveUInt8()
p.optionsLength, err = p.readStream.ReadUInt8()
if err != nil {
return fmt.Errorf("Failed to decode PRUDPv1 options length. %s", err.Error())
}
p.payloadLength, err = p.readStream.ReadPrimitiveUInt16LE()
p.payloadLength, err = p.readStream.ReadUInt16LE()
if err != nil {
return fmt.Errorf("Failed to decode PRUDPv1 payload length. %s", err.Error())
}
source, err := p.readStream.ReadPrimitiveUInt8()
source, err := p.readStream.ReadUInt8()
if err != nil {
return fmt.Errorf("Failed to read PRUDPv1 source. %s", err.Error())
}
p.sourceVirtualPort = VirtualPort(source)
destination, err := p.readStream.ReadPrimitiveUInt8()
destination, err := p.readStream.ReadUInt8()
if err != nil {
return fmt.Errorf("Failed to read PRUDPv1 destination. %s", err.Error())
}
@ -168,7 +176,7 @@ func (p *PRUDPPacketV1) decodeHeader() error {
p.destinationVirtualPort = VirtualPort(destination)
// TODO - Does QRV also encode it this way in PRUDPv1?
typeAndFlags, err := p.readStream.ReadPrimitiveUInt16LE()
typeAndFlags, err := p.readStream.ReadUInt16LE()
if err != nil {
return fmt.Errorf("Failed to read PRUDPv1 type and flags. %s", err.Error())
}
@ -176,17 +184,17 @@ func (p *PRUDPPacketV1) decodeHeader() error {
p.flags = typeAndFlags >> 4
p.packetType = typeAndFlags & 0xF
p.sessionID, err = p.readStream.ReadPrimitiveUInt8()
p.sessionID, err = p.readStream.ReadUInt8()
if err != nil {
return fmt.Errorf("Failed to read PRUDPv1 session ID. %s", err.Error())
}
p.substreamID, err = p.readStream.ReadPrimitiveUInt8()
p.substreamID, err = p.readStream.ReadUInt8()
if err != nil {
return fmt.Errorf("Failed to read PRUDPv1 substream ID. %s", err.Error())
}
p.sequenceID, err = p.readStream.ReadPrimitiveUInt16LE()
p.sequenceID, err = p.readStream.ReadUInt16LE()
if err != nil {
return fmt.Errorf("Failed to read PRUDPv1 sequence ID. %s", err.Error())
}
@ -197,60 +205,68 @@ func (p *PRUDPPacketV1) decodeHeader() error {
func (p *PRUDPPacketV1) encodeHeader() []byte {
stream := NewByteStreamOut(p.server.LibraryVersions, p.server.ByteStreamSettings)
stream.WritePrimitiveUInt8(1) // * Version
stream.WritePrimitiveUInt8(p.optionsLength)
stream.WritePrimitiveUInt16LE(uint16(len(p.payload)))
stream.WritePrimitiveUInt8(uint8(p.sourceVirtualPort))
stream.WritePrimitiveUInt8(uint8(p.destinationVirtualPort))
stream.WritePrimitiveUInt16LE(p.packetType | (p.flags << 4)) // TODO - Does QRV also encode it this way in PRUDPv1?
stream.WritePrimitiveUInt8(p.sessionID)
stream.WritePrimitiveUInt8(p.substreamID)
stream.WritePrimitiveUInt16LE(p.sequenceID)
stream.WriteUInt8(1) // * Version
stream.WriteUInt8(p.optionsLength)
stream.WriteUInt16LE(uint16(len(p.payload)))
stream.WriteUInt8(uint8(p.sourceVirtualPort))
stream.WriteUInt8(uint8(p.destinationVirtualPort))
stream.WriteUInt16LE(p.packetType | (p.flags << 4)) // TODO - Does QRV also encode it this way in PRUDPv1?
stream.WriteUInt8(p.sessionID)
stream.WriteUInt8(p.substreamID)
stream.WriteUInt16LE(p.sequenceID)
return stream.Bytes()
}
func (p *PRUDPPacketV1) decodeOptions() error {
if p.readStream.Remaining() < uint64(p.optionsLength) {
return errors.New("Not have enough data")
}
data := p.readStream.ReadBytesNext(int64(p.optionsLength))
optionsStream := NewByteStreamIn(data, p.server.LibraryVersions, p.server.ByteStreamSettings)
for optionsStream.Remaining() > 0 {
optionID, err := optionsStream.ReadPrimitiveUInt8()
optionID, err := optionsStream.ReadUInt8()
if err != nil {
return err
}
_, err = optionsStream.ReadPrimitiveUInt8() // * Options size. We already know the size based on the ID, though
_, err = optionsStream.ReadUInt8() // * Options size. We already know the size based on the ID, though
if err != nil {
return err
}
if p.packetType == constants.SynPacket || p.packetType == constants.ConnectPacket {
if optionID == 0 {
p.supportedFunctions, err = optionsStream.ReadPrimitiveUInt32LE()
p.supportedFunctions, err = optionsStream.ReadUInt32LE()
p.minorVersion = p.supportedFunctions & 0xFF
p.supportedFunctions = p.supportedFunctions >> 8
}
if optionID == 1 {
p.connectionSignature = optionsStream.ReadBytesNext(16)
if optionsStream.Remaining() < 16 {
err = errors.New("Not have enough data")
} else {
p.connectionSignature = optionsStream.ReadBytesNext(16)
}
}
if optionID == 4 {
p.maximumSubstreamID, err = optionsStream.ReadPrimitiveUInt8()
p.maximumSubstreamID, err = optionsStream.ReadUInt8()
}
}
if p.packetType == constants.ConnectPacket {
if optionID == 3 {
p.initialUnreliableSequenceID, err = optionsStream.ReadPrimitiveUInt16LE()
p.initialUnreliableSequenceID, err = optionsStream.ReadUInt16LE()
}
}
if p.packetType == constants.DataPacket {
if optionID == 2 {
p.fragmentID, err = optionsStream.ReadPrimitiveUInt8()
p.fragmentID, err = optionsStream.ReadUInt8()
}
}
@ -269,12 +285,12 @@ func (p *PRUDPPacketV1) encodeOptions() []byte {
optionsStream := NewByteStreamOut(p.server.LibraryVersions, p.server.ByteStreamSettings)
if p.packetType == constants.SynPacket || p.packetType == constants.ConnectPacket {
optionsStream.WritePrimitiveUInt8(0)
optionsStream.WritePrimitiveUInt8(4)
optionsStream.WritePrimitiveUInt32LE(p.minorVersion | (p.supportedFunctions << 8))
optionsStream.WriteUInt8(0)
optionsStream.WriteUInt8(4)
optionsStream.WriteUInt32LE(p.minorVersion | (p.supportedFunctions << 8))
optionsStream.WritePrimitiveUInt8(1)
optionsStream.WritePrimitiveUInt8(16)
optionsStream.WriteUInt8(1)
optionsStream.WriteUInt8(16)
optionsStream.Grow(16)
optionsStream.WriteBytesNext(p.connectionSignature)
@ -287,20 +303,20 @@ func (p *PRUDPPacketV1) encodeOptions() []byte {
// * parsed, though, order REALLY doesn't matter.
// * NintendoClients expects option 3 before 4, though
if p.packetType == constants.ConnectPacket {
optionsStream.WritePrimitiveUInt8(3)
optionsStream.WritePrimitiveUInt8(2)
optionsStream.WritePrimitiveUInt16LE(p.initialUnreliableSequenceID)
optionsStream.WriteUInt8(3)
optionsStream.WriteUInt8(2)
optionsStream.WriteUInt16LE(p.initialUnreliableSequenceID)
}
optionsStream.WritePrimitiveUInt8(4)
optionsStream.WritePrimitiveUInt8(1)
optionsStream.WritePrimitiveUInt8(p.maximumSubstreamID)
optionsStream.WriteUInt8(4)
optionsStream.WriteUInt8(1)
optionsStream.WriteUInt8(p.maximumSubstreamID)
}
if p.packetType == constants.DataPacket {
optionsStream.WritePrimitiveUInt8(2)
optionsStream.WritePrimitiveUInt8(1)
optionsStream.WritePrimitiveUInt8(p.fragmentID)
optionsStream.WriteUInt8(2)
optionsStream.WriteUInt8(1)
optionsStream.WriteUInt8(p.fragmentID)
}
return optionsStream.Bytes()

View file

@ -6,6 +6,7 @@ import (
"fmt"
"net"
"runtime"
"time"
"github.com/PretendoNetwork/nex-go/v2/constants"
"github.com/lxzan/gws"
@ -16,7 +17,6 @@ type PRUDPServer struct {
udpSocket *net.UDPConn
websocketServer *WebSocketServer
Endpoints *MutexMap[uint8, *PRUDPEndPoint]
Connections *MutexMap[string, *SocketConnection]
SupportedFunctions uint32
AccessKey string
KerberosTicketVersion int
@ -73,15 +73,16 @@ func (ps *PRUDPServer) ListenUDP(port int) {
func (ps *PRUDPServer) listenDatagram(quit chan struct{}) {
var err error
buffer := make([]byte, 64000)
for err == nil {
buffer := make([]byte, 64000)
var read int
var addr *net.UDPAddr
read, addr, err = ps.udpSocket.ReadFromUDP(buffer)
if err == nil {
packetData := buffer[:read]
packetData := make([]byte, read)
copy(packetData, buffer[:read])
err = ps.handleSocketMessage(packetData, addr, nil)
}
@ -126,6 +127,11 @@ func (ps *PRUDPServer) initPRUDPv1ConnectionSignatureKey() {
}
func (ps *PRUDPServer) handleSocketMessage(packetData []byte, address net.Addr, webSocketConnection *gws.Conn) error {
// * Check that the message is long enough for initial parsing
if len(packetData) < 2 {
return nil
}
readStream := NewByteStreamIn(packetData, ps.LibraryVersions, ps.ByteStreamSettings)
var packets []PRUDPPacketInterface
@ -192,13 +198,7 @@ func (ps *PRUDPServer) processPacket(packet PRUDPPacketInterface, address net.Ad
return
}
discriminator := address.String()
socket, ok := ps.Connections.Get(discriminator)
if !ok {
socket = NewSocketConnection(ps, address, webSocketConnection)
ps.Connections.Set(discriminator, socket)
}
socket := NewSocketConnection(ps, address, webSocketConnection)
endpoint.processPacket(packet, socket)
}
@ -222,6 +222,14 @@ func (ps *PRUDPServer) Send(packet PacketInterface) {
}
ps.sendPacket(packet)
// * This delay is here to prevent the server from overloading the client with too many packets.
// * The 16ms (1/60th of a second) value is chosen based on testing with the friends server and is a good balance between
// * Not being too slow and also not dropping any packets because we've overloaded the client. This may be because it
// * roughly matches the framerate that most games target (60fps)
if i < fragments {
time.Sleep(16 * time.Millisecond)
}
}
}
}
@ -242,6 +250,7 @@ func (ps *PRUDPServer) sendPacket(packet PRUDPPacketInterface) {
packetCopy.SetSequenceID(connection.outgoingUnreliableSequenceIDCounter.Next())
} else if packetCopy.Type() == constants.PingPacket {
packetCopy.SetSequenceID(connection.outgoingPingSequenceIDCounter.Next())
connection.lastSentPingTime = time.Now()
} else {
packetCopy.SetSequenceID(0)
}
@ -279,9 +288,12 @@ func (ps *PRUDPServer) sendPacket(packet PRUDPPacketInterface) {
packetCopy.setSignature(packetCopy.calculateSignature(connection.SessionKey, connection.ServerConnectionSignature))
}
packetCopy.incrementSendCount()
packetCopy.setSentAt(time.Now())
if packetCopy.HasFlag(constants.PacketFlagReliable) && packetCopy.HasFlag(constants.PacketFlagNeedsAck) {
slidingWindow := connection.SlidingWindow(packetCopy.SubstreamID())
slidingWindow.ResendScheduler.AddPacket(packetCopy)
slidingWindow.TimeoutManager.SchedulePacketTimeout(packetCopy)
}
ps.sendRaw(packetCopy.Sender().(*PRUDPConnection).Socket, packetCopy.Bytes())
@ -324,7 +336,6 @@ func (ps *PRUDPServer) SetFragmentSize(fragmentSize int) {
func NewPRUDPServer() *PRUDPServer {
return &PRUDPServer{
Endpoints: NewMutexMap[uint8, *PRUDPEndPoint](),
Connections: NewMutexMap[string, *SocketConnection](),
SessionKeyLength: 32,
FragmentSize: 1300,
LibraryVersions: NewLibraryVersions(),

View file

@ -1,148 +0,0 @@
package nex
import (
"time"
)
// TODO - REMOVE THIS ENTIRELY AND REPLACE IT WITH AN IMPLEMENTATION OF rdv::Timeout AND rdv::TimeoutManager AND USE MORE STREAM SETTINGS!
// PendingPacket represends a packet scheduled to be resent
type PendingPacket struct {
packet PRUDPPacketInterface
lastSendTime time.Time
resendCount uint32
isAcknowledged bool
interval time.Duration
ticker *time.Ticker
rs *ResendScheduler
}
func (pi *PendingPacket) startResendTimer() {
pi.lastSendTime = time.Now()
pi.ticker = time.NewTicker(pi.interval)
for range pi.ticker.C {
finished := false
if pi.isAcknowledged {
pi.ticker.Stop()
pi.rs.packets.Delete(pi.packet.SequenceID())
finished = true
} else {
finished = pi.rs.resendPacket(pi)
}
if finished {
return
}
}
}
// ResendScheduler manages the resending of reliable PRUDP packets
type ResendScheduler struct {
packets *MutexMap[uint16, *PendingPacket]
}
// Stop kills the resend scheduler and stops all pending packets
func (rs *ResendScheduler) Stop() {
stillPending := make([]uint16, rs.packets.Size())
rs.packets.Each(func(sequenceID uint16, packet *PendingPacket) bool {
if !packet.isAcknowledged {
stillPending = append(stillPending, sequenceID)
}
return false
})
for _, sequenceID := range stillPending {
if pendingPacket, ok := rs.packets.Get(sequenceID); ok {
pendingPacket.isAcknowledged = true // * Prevent an edge case where the ticker is already being processed
if pendingPacket.ticker != nil {
// * This should never happen, but popped up in CTGP-7 testing?
// * Did the GC clear this before we called it?
pendingPacket.ticker.Stop()
}
rs.packets.Delete(sequenceID)
}
}
}
// AddPacket adds a packet to the scheduler and begins it's timer
func (rs *ResendScheduler) AddPacket(packet PRUDPPacketInterface) {
connection := packet.Sender().(*PRUDPConnection)
slidingWindow := connection.SlidingWindow(packet.SubstreamID())
pendingPacket := &PendingPacket{
packet: packet,
rs: rs,
// TODO: This may not be accurate, needs more research
interval: time.Duration(slidingWindow.streamSettings.KeepAliveTimeout) * time.Millisecond,
}
rs.packets.Set(packet.SequenceID(), pendingPacket)
go pendingPacket.startResendTimer()
}
// AcknowledgePacket marks a pending packet as acknowledged. It will be ignored at the next resend attempt
func (rs *ResendScheduler) AcknowledgePacket(sequenceID uint16) {
if pendingPacket, ok := rs.packets.Get(sequenceID); ok {
pendingPacket.isAcknowledged = true
}
}
func (rs *ResendScheduler) resendPacket(pendingPacket *PendingPacket) bool {
if pendingPacket.isAcknowledged {
// * Prevent a race condition where resendPacket may be called
// * at the same time a packet is acknowledged
return false
}
packet := pendingPacket.packet
connection := packet.Sender().(*PRUDPConnection)
slidingWindow := connection.SlidingWindow(packet.SubstreamID())
if pendingPacket.resendCount >= slidingWindow.streamSettings.MaxPacketRetransmissions {
// * The maximum resend count has been reached, consider the connection dead.
pendingPacket.ticker.Stop()
rs.packets.Delete(packet.SequenceID())
connection.cleanup() // * "removed" event is dispatched here
connection.endpoint.deleteConnectionByID(connection.ID)
return true
}
// TODO: This may not be accurate, needs more research
if time.Since(pendingPacket.lastSendTime) >= time.Duration(slidingWindow.streamSettings.KeepAliveTimeout)*time.Millisecond {
// * Resend the packet to the connection
server := connection.endpoint.Server
data := packet.Bytes()
server.sendRaw(connection.Socket, data)
pendingPacket.resendCount++
var retransmitTimeoutMultiplier float32
if pendingPacket.resendCount < slidingWindow.streamSettings.ExtraRestransmitTimeoutTrigger {
retransmitTimeoutMultiplier = slidingWindow.streamSettings.RetransmitTimeoutMultiplier
} else {
retransmitTimeoutMultiplier = slidingWindow.streamSettings.ExtraRetransmitTimeoutMultiplier
}
pendingPacket.interval += time.Duration(uint32(float32(slidingWindow.streamSettings.KeepAliveTimeout)*retransmitTimeoutMultiplier)) * time.Millisecond
pendingPacket.ticker.Reset(pendingPacket.interval)
pendingPacket.lastSendTime = time.Now()
}
return false
}
// NewResendScheduler creates a new ResendScheduler
func NewResendScheduler() *ResendScheduler {
return &ResendScheduler{
packets: NewMutexMap[uint16, *PendingPacket](),
}
}

View file

@ -9,18 +9,18 @@ import (
// RMCMessage represents a message in the RMC (Remote Method Call) protocol
type RMCMessage struct {
Endpoint EndpointInterface
IsRequest bool // * Indicates if the message is a request message (true) or response message (false)
IsSuccess bool // * Indicates if the message is a success message (true) for a response message
IsHPP bool // * Indicates if the message is an HPP message
ProtocolID uint16 // * Protocol ID of the message. Only present in "packed" variations
ProtocolName *types.String // * Protocol name of the message. Only present in "verbose" variations
CallID uint32 // * Call ID associated with the message
MethodID uint32 // * Method ID in the requested protocol. Only present in "packed" variations
MethodName *types.String // * Method name in the requested protocol. Only present in "verbose" variations
ErrorCode uint32 // * Error code for a response message
ClassVersionContainer *types.ClassVersionContainer // * Contains version info for Structures in the request. Only present in "verbose" variations
Parameters []byte // * Input for the method
Endpoint EndpointInterface
IsRequest bool // * Indicates if the message is a request message (true) or response message (false)
IsSuccess bool // * Indicates if the message is a success message (true) for a response message
IsHPP bool // * Indicates if the message is an HPP message
ProtocolID uint16 // * Protocol ID of the message. Only present in "packed" variations
ProtocolName types.String // * Protocol name of the message. Only present in "verbose" variations
CallID uint32 // * Call ID associated with the message
MethodID uint32 // * Method ID in the requested protocol. Only present in "packed" variations
MethodName types.String // * Method name in the requested protocol. Only present in "verbose" variations
ErrorCode uint32 // * Error code for a response message
VersionContainer *types.ClassVersionContainer // * Contains version info for Structures in the request. Only present in "verbose" variations. Pointer to allow for nil checks
Parameters []byte // * Input for the method
// TODO - Verbose messages suffix response method names with "*". Should we have a "HasResponsePointer" sort of field?
}
@ -57,7 +57,7 @@ func (rmc *RMCMessage) FromBytes(data []byte) error {
func (rmc *RMCMessage) decodePacked(data []byte) error {
stream := NewByteStreamIn(data, rmc.Endpoint.LibraryVersions(), rmc.Endpoint.ByteStreamSettings())
length, err := stream.ReadPrimitiveUInt32LE()
length, err := stream.ReadUInt32LE()
if err != nil {
return fmt.Errorf("Failed to read RMC Message size. %s", err.Error())
}
@ -66,7 +66,7 @@ func (rmc *RMCMessage) decodePacked(data []byte) error {
return errors.New("RMC Message has unexpected size")
}
protocolID, err := stream.ReadPrimitiveUInt8()
protocolID, err := stream.ReadUInt8()
if err != nil {
return fmt.Errorf("Failed to read RMC Message protocol ID. %s", err.Error())
}
@ -74,7 +74,7 @@ func (rmc *RMCMessage) decodePacked(data []byte) error {
rmc.ProtocolID = uint16(protocolID & ^byte(0x80))
if rmc.ProtocolID == 0x7F {
rmc.ProtocolID, err = stream.ReadPrimitiveUInt16LE()
rmc.ProtocolID, err = stream.ReadUInt16LE()
if err != nil {
return fmt.Errorf("Failed to read RMC Message extended protocol ID. %s", err.Error())
}
@ -82,12 +82,12 @@ func (rmc *RMCMessage) decodePacked(data []byte) error {
if protocolID&0x80 != 0 {
rmc.IsRequest = true
rmc.CallID, err = stream.ReadPrimitiveUInt32LE()
rmc.CallID, err = stream.ReadUInt32LE()
if err != nil {
return fmt.Errorf("Failed to read RMC Message (request) call ID. %s", err.Error())
}
rmc.MethodID, err = stream.ReadPrimitiveUInt32LE()
rmc.MethodID, err = stream.ReadUInt32LE()
if err != nil {
return fmt.Errorf("Failed to read RMC Message (request) method ID. %s", err.Error())
}
@ -95,18 +95,18 @@ func (rmc *RMCMessage) decodePacked(data []byte) error {
rmc.Parameters = stream.ReadRemaining()
} else {
rmc.IsRequest = false
rmc.IsSuccess, err = stream.ReadPrimitiveBool()
rmc.IsSuccess, err = stream.ReadBool()
if err != nil {
return fmt.Errorf("Failed to read RMC Message (response) error check. %s", err.Error())
}
if rmc.IsSuccess {
rmc.CallID, err = stream.ReadPrimitiveUInt32LE()
rmc.CallID, err = stream.ReadUInt32LE()
if err != nil {
return fmt.Errorf("Failed to read RMC Message (response) call ID. %s", err.Error())
}
rmc.MethodID, err = stream.ReadPrimitiveUInt32LE()
rmc.MethodID, err = stream.ReadUInt32LE()
if err != nil {
return fmt.Errorf("Failed to read RMC Message (response) method ID. %s", err.Error())
}
@ -115,12 +115,12 @@ func (rmc *RMCMessage) decodePacked(data []byte) error {
rmc.Parameters = stream.ReadRemaining()
} else {
rmc.ErrorCode, err = stream.ReadPrimitiveUInt32LE()
rmc.ErrorCode, err = stream.ReadUInt32LE()
if err != nil {
return fmt.Errorf("Failed to read RMC Message (response) error code. %s", err.Error())
}
rmc.CallID, err = stream.ReadPrimitiveUInt32LE()
rmc.CallID, err = stream.ReadUInt32LE()
if err != nil {
return fmt.Errorf("Failed to read RMC Message (response) call ID. %s", err.Error())
}
@ -134,7 +134,7 @@ func (rmc *RMCMessage) decodePacked(data []byte) error {
func (rmc *RMCMessage) decodeVerbose(data []byte) error {
stream := NewByteStreamIn(data, rmc.Endpoint.LibraryVersions(), rmc.Endpoint.ByteStreamSettings())
length, err := stream.ReadPrimitiveUInt32LE()
length, err := stream.ReadUInt32LE()
if err != nil {
return fmt.Errorf("Failed to read RMC Message size. %s", err.Error())
}
@ -148,13 +148,13 @@ func (rmc *RMCMessage) decodeVerbose(data []byte) error {
return fmt.Errorf("Failed to read RMC Message protocol name. %s", err.Error())
}
rmc.IsRequest, err = stream.ReadPrimitiveBool()
rmc.IsRequest, err = stream.ReadBool()
if err != nil {
return fmt.Errorf("Failed to read RMC Message \"is request\" bool. %s", err.Error())
}
if rmc.IsRequest {
rmc.CallID, err = stream.ReadPrimitiveUInt32LE()
rmc.CallID, err = stream.ReadUInt32LE()
if err != nil {
return fmt.Errorf("Failed to read RMC Message (request) call ID. %s", err.Error())
}
@ -164,20 +164,21 @@ func (rmc *RMCMessage) decodeVerbose(data []byte) error {
return fmt.Errorf("Failed to read RMC Message (request) method name. %s", err.Error())
}
rmc.ClassVersionContainer = types.NewClassVersionContainer()
if err := rmc.ClassVersionContainer.ExtractFrom(stream); err != nil {
versionContainer := types.NewClassVersionContainer()
if err := versionContainer.ExtractFrom(stream); err != nil {
return fmt.Errorf("Failed to read RMC Message ClassVersionContainer. %s", err.Error())
}
rmc.VersionContainer = &versionContainer
rmc.Parameters = stream.ReadRemaining()
} else {
rmc.IsSuccess, err = stream.ReadPrimitiveBool()
rmc.IsSuccess, err = stream.ReadBool()
if err != nil {
return fmt.Errorf("Failed to read RMC Message (response) error check. %s", err.Error())
}
if rmc.IsSuccess {
rmc.CallID, err = stream.ReadPrimitiveUInt32LE()
rmc.CallID, err = stream.ReadUInt32LE()
if err != nil {
return fmt.Errorf("Failed to read RMC Message (response) call ID. %s", err.Error())
}
@ -190,12 +191,12 @@ func (rmc *RMCMessage) decodeVerbose(data []byte) error {
rmc.Parameters = stream.ReadRemaining()
} else {
rmc.ErrorCode, err = stream.ReadPrimitiveUInt32LE()
rmc.ErrorCode, err = stream.ReadUInt32LE()
if err != nil {
return fmt.Errorf("Failed to read RMC Message (response) error code. %s", err.Error())
}
rmc.CallID, err = stream.ReadPrimitiveUInt32LE()
rmc.CallID, err = stream.ReadUInt32LE()
if err != nil {
return fmt.Errorf("Failed to read RMC Message (response) call ID. %s", err.Error())
}
@ -228,35 +229,35 @@ func (rmc *RMCMessage) encodePacked() []byte {
// * do it for accuracy.
if !rmc.IsHPP || (rmc.IsHPP && rmc.IsRequest) {
if rmc.ProtocolID < 0x80 {
stream.WritePrimitiveUInt8(uint8(rmc.ProtocolID | protocolIDFlag))
stream.WriteUInt8(uint8(rmc.ProtocolID | protocolIDFlag))
} else {
stream.WritePrimitiveUInt8(uint8(0x7F | protocolIDFlag))
stream.WritePrimitiveUInt16LE(rmc.ProtocolID)
stream.WriteUInt8(uint8(0x7F | protocolIDFlag))
stream.WriteUInt16LE(rmc.ProtocolID)
}
}
if rmc.IsRequest {
stream.WritePrimitiveUInt32LE(rmc.CallID)
stream.WritePrimitiveUInt32LE(rmc.MethodID)
stream.WriteUInt32LE(rmc.CallID)
stream.WriteUInt32LE(rmc.MethodID)
if rmc.Parameters != nil && len(rmc.Parameters) > 0 {
stream.Grow(int64(len(rmc.Parameters)))
stream.WriteBytesNext(rmc.Parameters)
}
} else {
stream.WritePrimitiveBool(rmc.IsSuccess)
stream.WriteBool(rmc.IsSuccess)
if rmc.IsSuccess {
stream.WritePrimitiveUInt32LE(rmc.CallID)
stream.WritePrimitiveUInt32LE(rmc.MethodID | 0x8000)
stream.WriteUInt32LE(rmc.CallID)
stream.WriteUInt32LE(rmc.MethodID | 0x8000)
if rmc.Parameters != nil && len(rmc.Parameters) > 0 {
stream.Grow(int64(len(rmc.Parameters)))
stream.WriteBytesNext(rmc.Parameters)
}
} else {
stream.WritePrimitiveUInt32LE(uint32(rmc.ErrorCode))
stream.WritePrimitiveUInt32LE(rmc.CallID)
stream.WriteUInt32LE(uint32(rmc.ErrorCode))
stream.WriteUInt32LE(rmc.CallID)
}
}
@ -264,7 +265,7 @@ func (rmc *RMCMessage) encodePacked() []byte {
message := NewByteStreamOut(rmc.Endpoint.LibraryVersions(), rmc.Endpoint.ByteStreamSettings())
message.WritePrimitiveUInt32LE(uint32(len(serialized)))
message.WriteUInt32LE(uint32(len(serialized)))
message.Grow(int64(len(serialized)))
message.WriteBytesNext(serialized)
@ -275,17 +276,17 @@ func (rmc *RMCMessage) encodeVerbose() []byte {
stream := NewByteStreamOut(rmc.Endpoint.LibraryVersions(), rmc.Endpoint.ByteStreamSettings())
rmc.ProtocolName.WriteTo(stream)
stream.WritePrimitiveBool(rmc.IsRequest)
stream.WriteBool(rmc.IsRequest)
if rmc.IsRequest {
stream.WritePrimitiveUInt32LE(rmc.CallID)
stream.WriteUInt32LE(rmc.CallID)
rmc.MethodName.WriteTo(stream)
if rmc.ClassVersionContainer != nil {
rmc.ClassVersionContainer.WriteTo(stream)
if rmc.VersionContainer != nil {
rmc.VersionContainer.WriteTo(stream)
} else {
// * Fail safe. This is always present even if no structures are used
stream.WritePrimitiveUInt32LE(0)
stream.WriteUInt32LE(0)
}
if rmc.Parameters != nil && len(rmc.Parameters) > 0 {
@ -293,10 +294,10 @@ func (rmc *RMCMessage) encodeVerbose() []byte {
stream.WriteBytesNext(rmc.Parameters)
}
} else {
stream.WritePrimitiveBool(rmc.IsSuccess)
stream.WriteBool(rmc.IsSuccess)
if rmc.IsSuccess {
stream.WritePrimitiveUInt32LE(rmc.CallID)
stream.WriteUInt32LE(rmc.CallID)
rmc.MethodName.WriteTo(stream)
if rmc.Parameters != nil && len(rmc.Parameters) > 0 {
@ -304,8 +305,8 @@ func (rmc *RMCMessage) encodeVerbose() []byte {
stream.WriteBytesNext(rmc.Parameters)
}
} else {
stream.WritePrimitiveUInt32LE(uint32(rmc.ErrorCode))
stream.WritePrimitiveUInt32LE(rmc.CallID)
stream.WriteUInt32LE(uint32(rmc.ErrorCode))
stream.WriteUInt32LE(rmc.CallID)
}
}
@ -313,7 +314,7 @@ func (rmc *RMCMessage) encodeVerbose() []byte {
message := NewByteStreamOut(rmc.Endpoint.LibraryVersions(), rmc.Endpoint.ByteStreamSettings())
message.WritePrimitiveUInt32LE(uint32(len(serialized)))
message.WriteUInt32LE(uint32(len(serialized)))
message.Grow(int64(len(serialized)))
message.WriteBytesNext(serialized)

66
rtt.go Normal file
View file

@ -0,0 +1,66 @@
package nex
import (
"math"
"sync"
"time"
)
const (
alpha float64 = 1.0 / 8.0
beta float64 = 1.0 / 4.0
k float64 = 4.0
)
// RTT is an implementation of rdv::RTT.
// Used to calculate the average round trip time of reliable packets
type RTT struct {
sync.Mutex
lastRTT float64
average float64
variance float64
initialized bool
}
// Adjust updates the average RTT with the new value
func (rtt *RTT) Adjust(next time.Duration) {
// * This calculation comes from the RFC6298 which defines RTT calculation for TCP packets
rtt.Lock()
if rtt.initialized {
rtt.variance = (1.0-beta)*rtt.variance + beta*math.Abs(rtt.variance-float64(next))
rtt.average = (1.0-alpha)*rtt.average + alpha*float64(next)
} else {
rtt.lastRTT = float64(next)
rtt.variance = float64(next) / 2
rtt.average = float64(next) + k*rtt.variance
rtt.initialized = true
}
rtt.Unlock()
}
// GetRTTSmoothedAvg returns the smoothed average of this RTT, it is used in calls to the custom
// RTO calculation function set on `PRUDPEndpoint::SetCalcRetransmissionTimeoutCallback`
func (rtt *RTT) GetRTTSmoothedAvg() float64 {
return rtt.average / 16
}
// GetRTTSmoothedDev returns the smoothed standard deviation of this RTT, it is used in calls to the custom
// RTO calculation function set on `PRUDPEndpoint::SetCalcRetransmissionTimeoutCallback`
func (rtt *RTT) GetRTTSmoothedDev() float64 {
return rtt.variance / 8
}
// Initialized returns a bool indicating whether this RTT has been initialized
func (rtt *RTT) Initialized() bool {
return rtt.initialized
}
// GetRTO returns the current average
func (rtt *RTT) Average() time.Duration {
return time.Duration(rtt.average)
}
// NewRTT returns a new RTT based on the first value
func NewRTT() *RTT {
return &RTT{}
}

View file

@ -1,34 +1,14 @@
package nex
// SlidingWindow is an implementation of rdv::SlidingWindow.
// SlidingWindow reorders pending reliable packets to ensure they are handled in the expected order.
// In the original library each virtual connection stream only uses a single SlidingWindow, but starting
// Currently this is a stub and does not reflect the interface and usage of rdv:SlidingWindow.
// In the original library this is used to manage sequencing of outgoing packets.
// each virtual connection stream only uses a single SlidingWindow, but starting
// in PRUDPv1 with NEX virtual connections may have multiple reliable substreams and thus multiple SlidingWindows.
type SlidingWindow struct {
pendingPackets *MutexMap[uint16, PRUDPPacketInterface]
incomingSequenceIDCounter *Counter[uint16]
outgoingSequenceIDCounter *Counter[uint16]
streamSettings *StreamSettings
fragmentedPayload []byte
ResendScheduler *ResendScheduler
}
// Update adds an incoming packet to the list of known packets and returns a list of packets to be processed in order
func (sw *SlidingWindow) Update(packet PRUDPPacketInterface) []PRUDPPacketInterface {
packets := make([]PRUDPPacketInterface, 0)
if packet.SequenceID() >= sw.incomingSequenceIDCounter.Value && !sw.pendingPackets.Has(packet.SequenceID()) {
sw.pendingPackets.Set(packet.SequenceID(), packet)
for sw.pendingPackets.Has(sw.incomingSequenceIDCounter.Value) {
storedPacket, _ := sw.pendingPackets.Get(sw.incomingSequenceIDCounter.Value)
packets = append(packets, storedPacket)
sw.pendingPackets.Delete(sw.incomingSequenceIDCounter.Value)
sw.incomingSequenceIDCounter.Next()
}
}
return packets
sequenceIDCounter *Counter[uint16]
streamSettings *StreamSettings
TimeoutManager *TimeoutManager
}
// SetCipherKey sets the reliable substreams RC4 cipher keys
@ -38,7 +18,7 @@ func (sw *SlidingWindow) SetCipherKey(key []byte) {
// NextOutgoingSequenceID sets the reliable substreams RC4 cipher keys
func (sw *SlidingWindow) NextOutgoingSequenceID() uint16 {
return sw.outgoingSequenceIDCounter.Next()
return sw.sequenceIDCounter.Next()
}
// Decrypt decrypts the provided data with the substreams decipher
@ -51,26 +31,11 @@ func (sw *SlidingWindow) Encrypt(data []byte) ([]byte, error) {
return sw.streamSettings.EncryptionAlgorithm.Encrypt(data)
}
// AddFragment adds the given fragment to the substreams fragmented payload
// Returns the current fragmented payload
func (sw *SlidingWindow) AddFragment(fragment []byte) []byte {
sw.fragmentedPayload = append(sw.fragmentedPayload, fragment...)
return sw.fragmentedPayload
}
// ResetFragmentedPayload resets the substreams fragmented payload
func (sw *SlidingWindow) ResetFragmentedPayload() {
sw.fragmentedPayload = make([]byte, 0)
}
// NewSlidingWindow initializes a new SlidingWindow with a starting counter value.
func NewSlidingWindow() *SlidingWindow {
sw := &SlidingWindow{
pendingPackets: NewMutexMap[uint16, PRUDPPacketInterface](),
incomingSequenceIDCounter: NewCounter[uint16](0),
outgoingSequenceIDCounter: NewCounter[uint16](0),
ResendScheduler: NewResendScheduler(),
sequenceIDCounter: NewCounter[uint16](0),
TimeoutManager: NewTimeoutManager(),
}
return sw

View file

@ -9,10 +9,9 @@ import (
// SocketConnection represents a single open socket.
// A single socket may have many PRUDP connections open on it.
type SocketConnection struct {
Server *PRUDPServer // * PRUDP server the socket is connected to
Address net.Addr // * Sockets address
WebSocketConnection *gws.Conn // * Only used in PRUDPLite
Connections *MutexMap[uint8, *PRUDPConnection] // * Open PRUDP connections separated by rdv::Stream ID, also called "port number"
Server *PRUDPServer // * PRUDP server the socket is connected to
Address net.Addr // * Sockets address
WebSocketConnection *gws.Conn // * Only used in PRUDPLite
}
// NewSocketConnection creates a new SocketConnection
@ -21,6 +20,5 @@ func NewSocketConnection(server *PRUDPServer, address net.Addr, webSocketConnect
Server: server,
Address: address,
WebSocketConnection: webSocketConnection,
Connections: NewMutexMap[uint8, *PRUDPConnection](),
}
}

View file

@ -12,17 +12,18 @@ import (
// The original library has more settings which are not present here as their use is unknown.
// Not all values are used at this time, and only exist to future-proof for a later time.
type StreamSettings struct {
ExtraRestransmitTimeoutTrigger uint32 // * The number of times a packet can be retransmitted before ExtraRetransmitTimeoutMultiplier is used
ExtraRetransmitTimeoutTrigger uint32 // * The number of times a packet can be retransmitted before ExtraRetransmitTimeoutMultiplier is used
MaxPacketRetransmissions uint32 // * The number of times a packet can be retransmitted before the timeout time is checked
KeepAliveTimeout uint32 // * Presumably the time a packet can be alive for without acknowledgement? Milliseconds?
ChecksumBase uint32 // * Unused. The base value for PRUDPv0 checksum calculations
FaultDetectionEnabled bool // * Unused. Presumably used to detect PIA faults?
InitialRTT uint32 // * Unused. The connections initial RTT
InitialRTT uint32 // * The initial connection RTT used for all non-SYN packets
SynInitialRTT uint32 // * The initial connection RTT used for all SYN packets
EncryptionAlgorithm encryption.Algorithm // * The encryption algorithm used for packet payloads
ExtraRetransmitTimeoutMultiplier float32 // * Used as part of the RTO calculations when retransmitting a packet. Only used if ExtraRestransmitTimeoutTrigger has been reached
WindowSize uint32 // * Unused. The max number of (reliable?) packets allowed in a SlidingWindow
CompressionAlgorithm compression.Algorithm // * The compression algorithm used for packet payloads
RTTRetransmit uint32 // * Unused. Unknown use
RTTRetransmit uint32 // * This is the number of times that a retried packet will be included in RTT calculations if we receive an ACK packet for it
RetransmitTimeoutMultiplier float32 // * Used as part of the RTO calculations when retransmitting a packet. Only used if ExtraRestransmitTimeoutTrigger has not been reached
MaxSilenceTime uint32 // * Presumably the time a connection can go without any packets from the other side? Milliseconds?
}
@ -31,7 +32,7 @@ type StreamSettings struct {
func (ss *StreamSettings) Copy() *StreamSettings {
copied := NewStreamSettings()
copied.ExtraRestransmitTimeoutTrigger = ss.ExtraRestransmitTimeoutTrigger
copied.ExtraRetransmitTimeoutTrigger = ss.ExtraRetransmitTimeoutTrigger
copied.MaxPacketRetransmissions = ss.MaxPacketRetransmissions
copied.KeepAliveTimeout = ss.KeepAliveTimeout
copied.ChecksumBase = ss.ChecksumBase
@ -50,21 +51,22 @@ func (ss *StreamSettings) Copy() *StreamSettings {
// NewStreamSettings returns a new instance of StreamSettings with default params
func NewStreamSettings() *StreamSettings {
// * Default values based on WATCH_DOGS. Not all values are used currently, and only
// * Default values based on WATCH_DOGS other than where stated. Not all values are used currently, and only
// * exist to mimic what is seen in that game. Many are planned for future use.
return &StreamSettings{
ExtraRestransmitTimeoutTrigger: 0x32,
ExtraRetransmitTimeoutTrigger: 0x32,
MaxPacketRetransmissions: 0x14,
KeepAliveTimeout: 1000,
ChecksumBase: 0,
FaultDetectionEnabled: true,
InitialRTT: 0xFA,
InitialRTT: 0x2EE,
SynInitialRTT: 0xFA,
EncryptionAlgorithm: encryption.NewRC4Encryption(),
ExtraRetransmitTimeoutMultiplier: 1.0,
WindowSize: 8,
CompressionAlgorithm: compression.NewDummyCompression(),
RTTRetransmit: 0x32,
RTTRetransmit: 2, // * This value is taken from Xenoblade Chronicles, WATCH_DOGS sets this to 0x32 but it is then ignored. Setting this to 2 matches the TCP spec by not using resent packets in RTT calculations.
RetransmitTimeoutMultiplier: 1.25,
MaxSilenceTime: 5000,
MaxSilenceTime: 10000, // * This value is taken from Xenoblade Chronicles, WATCH_DOGS sets this to 5000.
}
}

View file

@ -2,6 +2,7 @@
package main
import (
"encoding/hex"
"fmt"
"github.com/PretendoNetwork/nex-go/v2"
@ -62,7 +63,7 @@ func login(packet nex.PRUDPPacketInterface) {
panic(err)
}
sourceAccount, _ := accountDetailsByUsername(strUserName.Value)
sourceAccount, _ := accountDetailsByUsername(string(strUserName))
targetAccount, _ := accountDetailsByUsername(secureServerAccount.Username)
retval := types.NewQResultSuccess(0x00010001)
@ -72,8 +73,7 @@ func login(packet nex.PRUDPPacketInterface) {
strReturnMsg := types.NewString("Test Build")
pConnectionData.StationURL = types.NewStationURL("prudps:/address=192.168.1.98;port=60001;CID=1;PID=2;sid=1;stream=10;type=2")
pConnectionData.SpecialProtocols = types.NewList[*types.PrimitiveU8]()
pConnectionData.SpecialProtocols.Type = types.NewPrimitiveU8(0)
pConnectionData.SpecialProtocols = types.NewList[types.UInt8]()
pConnectionData.StationURLSpecialProtocols = types.NewStationURL("")
pConnectionData.Time = types.NewDateTime(0).Now()
@ -106,6 +106,8 @@ func login(packet nex.PRUDPPacketInterface) {
responsePacket.SetSubstreamID(packet.SubstreamID())
responsePacket.SetPayload(response.Bytes())
fmt.Println(hex.EncodeToString(responsePacket.Payload()))
authServer.Send(responsePacket)
}
@ -159,5 +161,7 @@ func requestTicket(packet nex.PRUDPPacketInterface) {
responsePacket.SetSubstreamID(packet.SubstreamID())
responsePacket.SetPayload(response.Bytes())
fmt.Println(hex.EncodeToString(responsePacket.Payload()))
authServer.Send(responsePacket)
}

View file

@ -13,7 +13,7 @@ var hppServer *nex.HPPServer
type dataStoreGetNotificationURLParam struct {
types.Structure
PreviousURL *types.String
PreviousURL types.String
}
func (d *dataStoreGetNotificationURLParam) ExtractFrom(readable types.Readable) error {
@ -33,10 +33,10 @@ func (d *dataStoreGetNotificationURLParam) ExtractFrom(readable types.Readable)
type dataStoreReqGetNotificationURLInfo struct {
types.Structure
URL *types.String
Key *types.String
Query *types.String
RootCACert *types.Buffer
URL types.String
Key types.String
Query types.String
RootCACert types.Buffer
}
func (d *dataStoreReqGetNotificationURLInfo) WriteTo(writable types.Writable) {

View file

@ -13,7 +13,7 @@ var authenticationServerAccount *nex.Account
var secureServerAccount *nex.Account
var testUserAccount *nex.Account
func accountDetailsByPID(pid *types.PID) (*nex.Account, *nex.Error) {
func accountDetailsByPID(pid types.PID) (*nex.Account, *nex.Error) {
if pid.Equals(authenticationServerAccount.PID) {
return authenticationServerAccount, nil
}

View file

@ -16,13 +16,13 @@ var secureEndpoint *nex.PRUDPEndPoint
type principalPreference struct {
types.Structure
*types.Data
ShowOnlinePresence *types.PrimitiveBool
ShowCurrentTitle *types.PrimitiveBool
BlockFriendRequests *types.PrimitiveBool
types.Data
ShowOnlinePresence types.Bool
ShowCurrentTitle types.Bool
BlockFriendRequests types.Bool
}
func (pp *principalPreference) WriteTo(writable types.Writable) {
func (pp principalPreference) WriteTo(writable types.Writable) {
pp.ShowOnlinePresence.WriteTo(writable)
pp.ShowCurrentTitle.WriteTo(writable)
pp.BlockFriendRequests.WriteTo(writable)
@ -30,13 +30,13 @@ func (pp *principalPreference) WriteTo(writable types.Writable) {
type comment struct {
types.Structure
*types.Data
Unknown *types.PrimitiveU8
Contents *types.String
LastChanged *types.DateTime
types.Data
Unknown types.UInt8
Contents types.String
LastChanged types.DateTime
}
func (c *comment) WriteTo(writable types.Writable) {
func (c comment) WriteTo(writable types.Writable) {
c.Unknown.WriteTo(writable)
c.Contents.WriteTo(writable)
c.LastChanged.WriteTo(writable)
@ -52,6 +52,7 @@ func startSecureServer() {
secureEndpoint.AccountDetailsByPID = accountDetailsByPID
secureEndpoint.AccountDetailsByUsername = accountDetailsByUsername
secureEndpoint.ServerAccount = secureServerAccount
secureEndpoint.IsSecureEndPoint = true
secureEndpoint.OnData(func(packet nex.PacketInterface) {
if packet, ok := packet.(nex.PRUDPPacketInterface); ok {
@ -96,18 +97,17 @@ func registerEx(packet nex.PRUDPPacketInterface) {
parametersStream := nex.NewByteStreamIn(parameters, secureEndpoint.LibraryVersions(), secureEndpoint.ByteStreamSettings())
vecMyURLs := types.NewList[*types.StationURL]()
vecMyURLs.Type = types.NewStationURL("")
vecMyURLs := types.NewList[types.StationURL]()
if err := vecMyURLs.ExtractFrom(parametersStream); err != nil {
panic(err)
}
hCustomData := types.NewAnyDataHolder()
hCustomData := types.NewDataHolder()
if err := hCustomData.ExtractFrom(parametersStream); err != nil {
fmt.Println(err)
}
localStation, _ := vecMyURLs.Get(0)
localStation := vecMyURLs[0]
address := packet.Sender().Address().(*net.UDPAddr).IP.String()
@ -115,12 +115,12 @@ func registerEx(packet nex.PRUDPPacketInterface) {
localStation.SetPortNumber(uint16(packet.Sender().Address().(*net.UDPAddr).Port))
retval := types.NewQResultSuccess(0x00010001)
localStationURL := types.NewString(localStation.EncodeToString())
localStationURL := types.NewString(localStation.URL())
responseStream := nex.NewByteStreamOut(secureEndpoint.LibraryVersions(), secureEndpoint.ByteStreamSettings())
retval.WriteTo(responseStream)
responseStream.WritePrimitiveUInt32LE(connection.ID)
responseStream.WriteUInt32LE(connection.ID)
localStationURL.WriteTo(responseStream)
response.IsSuccess = true
@ -153,23 +153,23 @@ func updateAndGetAllInformation(packet nex.PRUDPPacketInterface) {
responseStream := nex.NewByteStreamOut(secureEndpoint.LibraryVersions(), secureEndpoint.ByteStreamSettings())
(&principalPreference{
ShowOnlinePresence: types.NewPrimitiveBool(true),
ShowCurrentTitle: types.NewPrimitiveBool(true),
BlockFriendRequests: types.NewPrimitiveBool(false),
(principalPreference{
ShowOnlinePresence: types.NewBool(true),
ShowCurrentTitle: types.NewBool(true),
BlockFriendRequests: types.NewBool(false),
}).WriteTo(responseStream)
(&comment{
Unknown: types.NewPrimitiveU8(0),
(comment{
Unknown: types.NewUInt8(0),
Contents: types.NewString("Rewrite Test"),
LastChanged: types.NewDateTime(0),
}).WriteTo(responseStream)
responseStream.WritePrimitiveUInt32LE(0) // * Stubbed empty list. responseStream.WriteListStructure(friendList)
responseStream.WritePrimitiveUInt32LE(0) // * Stubbed empty list. responseStream.WriteListStructure(friendRequestsOut)
responseStream.WritePrimitiveUInt32LE(0) // * Stubbed empty list. responseStream.WriteListStructure(friendRequestsIn)
responseStream.WritePrimitiveUInt32LE(0) // * Stubbed empty list. responseStream.WriteListStructure(blockList)
responseStream.WritePrimitiveBool(false) // * Unknown
responseStream.WritePrimitiveUInt32LE(0) // * Stubbed empty list. responseStream.WriteListStructure(notifications)
responseStream.WritePrimitiveBool(false) // * Unknown
responseStream.WriteUInt32LE(0) // * Stubbed empty list. responseStream.WriteListStructure(friendList)
responseStream.WriteUInt32LE(0) // * Stubbed empty list. responseStream.WriteListStructure(friendRequestsOut)
responseStream.WriteUInt32LE(0) // * Stubbed empty list. responseStream.WriteListStructure(friendRequestsIn)
responseStream.WriteUInt32LE(0) // * Stubbed empty list. responseStream.WriteListStructure(blockList)
responseStream.WriteBool(false) // * Unknown
responseStream.WriteUInt32LE(0) // * Stubbed empty list. responseStream.WriteListStructure(notifications)
responseStream.WriteBool(false) // * Unknown
response.IsSuccess = true
response.IsRequest = false
@ -201,7 +201,7 @@ func checkSettingStatus(packet nex.PRUDPPacketInterface) {
responseStream := nex.NewByteStreamOut(secureEndpoint.LibraryVersions(), secureEndpoint.ByteStreamSettings())
responseStream.WritePrimitiveUInt8(0) // * Unknown
responseStream.WriteUInt8(0) // * Unknown
response.IsSuccess = true
response.IsRequest = false

29
timeout.go Normal file
View file

@ -0,0 +1,29 @@
package nex
import (
"context"
"time"
)
// Timeout is an implementation of rdv::Timeout.
// Used to hold state related to resend timeouts on a packet
type Timeout struct {
timeout time.Duration
ctx context.Context
cancel context.CancelFunc
}
// SetRTO sets the timeout field on this instance
func (t *Timeout) SetRTO(timeout time.Duration) {
t.timeout = timeout
}
// GetRTO gets the timeout field of this instance
func (t *Timeout) RTO() time.Duration {
return t.timeout
}
// NewTimeout creates a new Timeout
func NewTimeout() *Timeout {
return &Timeout{}
}

99
timeout_manager.go Normal file
View file

@ -0,0 +1,99 @@
package nex
import (
"context"
"time"
)
// TimeoutManager is an implementation of rdv::TimeoutManager and manages the resending of reliable PRUDP packets
type TimeoutManager struct {
ctx context.Context
cancel context.CancelFunc
packets *MutexMap[uint16, PRUDPPacketInterface]
streamSettings *StreamSettings
}
// SchedulePacketTimeout adds a packet to the scheduler and begins it's timer
func (tm *TimeoutManager) SchedulePacketTimeout(packet PRUDPPacketInterface) {
endpoint := packet.Sender().Endpoint().(*PRUDPEndPoint)
rto := endpoint.ComputeRetransmitTimeout(packet)
ctx, cancel := context.WithTimeout(tm.ctx, rto)
timeout := NewTimeout()
timeout.SetRTO(rto)
timeout.ctx = ctx
timeout.cancel = cancel
packet.setTimeout(timeout)
tm.packets.Set(packet.SequenceID(), packet)
go tm.start(packet)
}
// AcknowledgePacket marks a pending packet as acknowledged. It will be ignored at the next resend attempt
func (tm *TimeoutManager) AcknowledgePacket(sequenceID uint16) {
// * Acknowledge the packet
tm.packets.RunAndDelete(sequenceID, func(_ uint16, packet PRUDPPacketInterface) {
// * Update the RTT on the connection if the packet hasn't been resent
if packet.SendCount() >= tm.streamSettings.RTTRetransmit {
rttm := time.Since(packet.SentAt())
packet.Sender().(*PRUDPConnection).rtt.Adjust(rttm)
}
})
}
func (tm *TimeoutManager) start(packet PRUDPPacketInterface) {
<-packet.getTimeout().ctx.Done()
connection := packet.Sender().(*PRUDPConnection)
// * If the connection is closed stop trying to resend
if connection.ConnectionState != StateConnected {
return
}
if tm.packets.Has(packet.SequenceID()) {
endpoint := packet.Sender().Endpoint().(*PRUDPEndPoint)
// * This is `<` instead of `<=` for accuracy with observed behavior, even though we're comparing send count vs _resend_ max
if packet.SendCount() < tm.streamSettings.MaxPacketRetransmissions {
packet.incrementSendCount()
packet.setSentAt(time.Now())
rto := endpoint.ComputeRetransmitTimeout(packet)
ctx, cancel := context.WithTimeout(tm.ctx, rto)
timeout := packet.getTimeout()
timeout.timeout = rto
timeout.ctx = ctx
timeout.cancel = cancel
// * Schedule the packet to be resent
go tm.start(packet)
// * Resend the packet to the connection
server := connection.endpoint.Server
data := packet.Bytes()
server.sendRaw(connection.Socket, data)
} else {
// * Packet has been retried too many times, consider the connection dead
endpoint.cleanupConnection(connection)
}
}
}
// Stop kills the resend scheduler and stops all pending packets
func (tm *TimeoutManager) Stop() {
tm.cancel()
tm.packets.Clear(func(key uint16, value PRUDPPacketInterface) {})
}
// NewTimeoutManager creates a new TimeoutManager
func NewTimeoutManager() *TimeoutManager {
ctx, cancel := context.WithCancel(context.Background())
return &TimeoutManager{
ctx: ctx,
cancel: cancel,
packets: NewMutexMap[uint16, PRUDPPacketInterface](),
streamSettings: NewStreamSettings(),
}
}

View file

@ -1,140 +0,0 @@
package types
import (
"fmt"
"strings"
)
// AnyDataHolderObjects holds a mapping of RVTypes that are accessible in a AnyDataHolder
var AnyDataHolderObjects = make(map[string]RVType)
// RegisterDataHolderType registers a RVType to be accessible in a AnyDataHolder
func RegisterDataHolderType(name string, rvType RVType) {
AnyDataHolderObjects[name] = rvType
}
// AnyDataHolder is a class which can contain any Structure. The official type name and namespace is unknown.
// These Structures usually inherit from at least one other Structure. Typically this base class is the empty
// `Data` Structure, but this is not always the case. The contained Structures name & length are sent with the
// Structure body, so the receiver can properly decode it.
type AnyDataHolder struct {
TypeName *String
Length1 *PrimitiveU32 // Length of ObjectData + Length2
Length2 *PrimitiveU32 // Length of ObjectData
ObjectData RVType
}
// WriteTo writes the AnyDataHolder to the given writable
func (adh *AnyDataHolder) WriteTo(writable Writable) {
contentWritable := writable.CopyNew()
adh.ObjectData.WriteTo(contentWritable)
objectData := contentWritable.Bytes()
length1 := uint32(len(objectData) + 4)
length2 := uint32(len(objectData))
adh.TypeName.WriteTo(writable)
writable.WritePrimitiveUInt32LE(length1)
writable.WritePrimitiveUInt32LE(length2)
writable.Write(objectData)
}
// ExtractFrom extracts the AnyDataHolder from the given readable
func (adh *AnyDataHolder) ExtractFrom(readable Readable) error {
var err error
err = adh.TypeName.ExtractFrom(readable)
if err != nil {
return fmt.Errorf("Failed to read AnyDataHolder type name. %s", err.Error())
}
err = adh.Length1.ExtractFrom(readable)
if err != nil {
return fmt.Errorf("Failed to read AnyDataHolder length 1. %s", err.Error())
}
err = adh.Length2.ExtractFrom(readable)
if err != nil {
return fmt.Errorf("Failed to read AnyDataHolder length 2. %s", err.Error())
}
if _, ok := AnyDataHolderObjects[adh.TypeName.Value]; !ok {
return fmt.Errorf("Unknown AnyDataHolder type: %s", adh.TypeName.Value)
}
adh.ObjectData = AnyDataHolderObjects[adh.TypeName.Value].Copy()
if err := adh.ObjectData.ExtractFrom(readable); err != nil {
return fmt.Errorf("Failed to read AnyDataHolder object data. %s", err.Error())
}
return nil
}
// Copy returns a new copied instance of AnyDataHolder
func (adh *AnyDataHolder) Copy() RVType {
copied := NewAnyDataHolder()
copied.TypeName = adh.TypeName.Copy().(*String)
copied.Length1 = adh.Length1.Copy().(*PrimitiveU32)
copied.Length2 = adh.Length2.Copy().(*PrimitiveU32)
copied.ObjectData = adh.ObjectData.Copy()
return copied
}
// Equals checks if the passed Structure contains the same data as the current instance
func (adh *AnyDataHolder) Equals(o RVType) bool {
if _, ok := o.(*AnyDataHolder); !ok {
return false
}
other := o.(*AnyDataHolder)
if !adh.TypeName.Equals(other.TypeName) {
return false
}
if !adh.Length1.Equals(other.Length1) {
return false
}
if !adh.Length2.Equals(other.Length2) {
return false
}
return adh.ObjectData.Equals(other.ObjectData)
}
// String returns a string representation of the struct
func (adh *AnyDataHolder) String() string {
return adh.FormatToString(0)
}
// FormatToString pretty-prints the struct data using the provided indentation level
func (adh *AnyDataHolder) FormatToString(indentationLevel int) string {
indentationValues := strings.Repeat("\t", indentationLevel+1)
indentationEnd := strings.Repeat("\t", indentationLevel)
var b strings.Builder
b.WriteString("AnyDataHolder{\n")
b.WriteString(fmt.Sprintf("%sTypeName: %s,\n", indentationValues, adh.TypeName))
b.WriteString(fmt.Sprintf("%sLength1: %s,\n", indentationValues, adh.Length1))
b.WriteString(fmt.Sprintf("%sLength2: %s,\n", indentationValues, adh.Length2))
b.WriteString(fmt.Sprintf("%sObjectData: %s\n", indentationValues, adh.ObjectData))
b.WriteString(fmt.Sprintf("%s}", indentationEnd))
return b.String()
}
// NewAnyDataHolder returns a new AnyDataHolder
func NewAnyDataHolder() *AnyDataHolder {
return &AnyDataHolder{
TypeName: NewString(""),
Length1: NewPrimitiveU32(0),
Length2: NewPrimitiveU32(0),
}
}

142
types/any_object_holder.go Normal file
View file

@ -0,0 +1,142 @@
package types
import (
"fmt"
"strings"
)
// HoldableObject defines a common interface for types which can be placed in AnyObjectHolder
type HoldableObject interface {
RVType
ObjectID() RVType // Returns the object identifier of the type
}
// AnyObjectHolderObjects holds a mapping of RVTypes that are accessible in a AnyDataHolder
var AnyObjectHolderObjects = make(map[RVType]HoldableObject)
// RegisterObjectHolderType registers a RVType to be accessible in a AnyDataHolder
func RegisterObjectHolderType(rvType HoldableObject) {
AnyObjectHolderObjects[rvType.ObjectID()] = rvType
}
// AnyObjectHolder can hold a reference to any RVType which can be held
type AnyObjectHolder[T HoldableObject] struct {
Object T
}
// WriteTo writes the AnyObjectHolder to the given writable
func (aoh AnyObjectHolder[T]) WriteTo(writable Writable) {
contentWritable := writable.CopyNew()
aoh.Object.WriteTo(contentWritable)
objectBuffer := NewBuffer(contentWritable.Bytes())
objectBufferLength := uint32(len(objectBuffer) + 4) // * Length of the Buffer
aoh.Object.ObjectID().WriteTo(writable)
writable.WriteUInt32LE(objectBufferLength)
objectBuffer.WriteTo(writable)
}
// ExtractFrom extracts the AnyObjectHolder from the given readable
func (aoh *AnyObjectHolder[T]) ExtractFrom(readable Readable) error {
var err error
// TODO - This assumes the identifier is a String
identifier := NewString("")
err = identifier.ExtractFrom(readable)
if err != nil {
return fmt.Errorf("Failed to read AnyObjectHolder identifier. %s", err.Error())
}
length := NewUInt32(0)
err = length.ExtractFrom(readable)
if err != nil {
return fmt.Errorf("Failed to read AnyObjectHolder length. %s", err.Error())
}
// * This is technically a Buffer, but we can't instantiate a new Readable from here so interpret it as a UInt32 and the object data
bufferLength := NewUInt32(0)
err = bufferLength.ExtractFrom(readable)
if err != nil {
return fmt.Errorf("Failed to read AnyObjectHolder buffer length. %s", err.Error())
}
if _, ok := AnyObjectHolderObjects[identifier]; !ok {
return fmt.Errorf("Unknown AnyObjectHolder identifier: %s", identifier)
}
ptr := AnyObjectHolderObjects[identifier].CopyRef()
if err := ptr.ExtractFrom(readable); err != nil {
return fmt.Errorf("Failed to read AnyObjectHolder object. %s", err.Error())
}
var ok bool
if aoh.Object, ok = ptr.Deref().(T); !ok {
return fmt.Errorf("Input AnyObjectHolder object %s is invalid", identifier)
}
return nil
}
// Copy returns a new copied instance of AnyObjectHolder
func (aoh AnyObjectHolder[T]) Copy() RVType {
copied := NewAnyObjectHolder[T]()
copied.Object = aoh.Object.Copy().(T)
return copied
}
// Equals checks if the passed Structure contains the same data as the current instance
func (aoh AnyObjectHolder[T]) Equals(o RVType) bool {
if _, ok := o.(AnyObjectHolder[T]); !ok {
return false
}
other := o.(AnyObjectHolder[T])
return aoh.Object.Equals(other.Object)
}
// CopyRef copies the current value of the AnyObjectHolder
// and returns a pointer to the new copy
func (aoh AnyObjectHolder[T]) CopyRef() RVTypePtr {
copied := aoh.Copy().(AnyObjectHolder[T])
return &copied
}
// Deref takes a pointer to the AnyObjectHolder
// and dereferences it to the raw value.
// Only useful when working with an instance of RVTypePtr
func (aoh *AnyObjectHolder[T]) Deref() RVType {
return *aoh
}
// String returns a string representation of the struct
func (aoh AnyObjectHolder[T]) String() string {
return aoh.FormatToString(0)
}
// FormatToString pretty-prints the struct data using the provided indentation level
func (aoh AnyObjectHolder[T]) FormatToString(indentationLevel int) string {
indentationValues := strings.Repeat("\t", indentationLevel+1)
indentationEnd := strings.Repeat("\t", indentationLevel)
var b strings.Builder
b.WriteString("AnyDataHolder{\n")
b.WriteString(fmt.Sprintf("%sIdentifier: %s,\n", indentationValues, aoh.Object.ObjectID()))
b.WriteString(fmt.Sprintf("%sObject: %s\n", indentationValues, aoh.Object))
b.WriteString(fmt.Sprintf("%s}", indentationEnd))
return b.String()
}
// NewAnyObjectHolder returns a new AnyObjectHolder
func NewAnyObjectHolder[T HoldableObject]() AnyObjectHolder[T] {
return AnyObjectHolder[T]{}
}

62
types/bool.go Normal file
View file

@ -0,0 +1,62 @@
package types
import "fmt"
// Bool is a type alias for the Go basic type bool for use as an RVType
type Bool bool
// WriteTo writes the Bool to the given writable
func (b Bool) WriteTo(writable Writable) {
writable.WriteBool(bool(b))
}
// ExtractFrom extracts the Bool value from the given readable
func (b *Bool) ExtractFrom(readable Readable) error {
value, err := readable.ReadBool()
if err != nil {
return err
}
*b = Bool(value)
return nil
}
// Copy returns a pointer to a copy of the Bool. Requires type assertion when used
func (b Bool) Copy() RVType {
return NewBool(bool(b))
}
// Equals checks if the input is equal in value to the current instance
func (b Bool) Equals(o RVType) bool {
other, ok := o.(Bool)
if !ok {
return false
}
return b == other
}
// CopyRef copies the current value of the Bool
// and returns a pointer to the new copy
func (b Bool) CopyRef() RVTypePtr {
copied := b.Copy().(Bool)
return &copied
}
// Deref takes a pointer to the Bool
// and dereferences it to the raw value.
// Only useful when working with an instance of RVTypePtr
func (b *Bool) Deref() RVType {
return *b
}
// String returns a string representation of the Bool
func (b Bool) String() string {
return fmt.Sprintf("%t", b)
}
// NewBool returns a new Bool
func NewBool(input bool) Bool {
b := Bool(input)
return b
}

View file

@ -2,30 +2,29 @@ package types
import (
"bytes"
"encoding/hex"
"fmt"
)
// Buffer is an implementation of rdv::Buffer.
// Wraps a primitive Go byte slice.
// Type alias of []byte.
// Same as QBuffer but with a uint32 length field.
type Buffer struct {
Value []byte
}
type Buffer []byte
// WriteTo writes the Buffer to the given writable
func (b *Buffer) WriteTo(writable Writable) {
length := len(b.Value)
func (b Buffer) WriteTo(writable Writable) {
length := len(b)
writable.WritePrimitiveUInt32LE(uint32(length))
writable.WriteUInt32LE(uint32(length))
if length > 0 {
writable.Write(b.Value)
writable.Write(b)
}
}
// ExtractFrom extracts the Buffer from the given readable
func (b *Buffer) ExtractFrom(readable Readable) error {
length, err := readable.ReadPrimitiveUInt32LE()
length, err := readable.ReadUInt32LE()
if err != nil {
return fmt.Errorf("Failed to read NEX Buffer length. %s", err.Error())
}
@ -35,31 +34,47 @@ func (b *Buffer) ExtractFrom(readable Readable) error {
return fmt.Errorf("Failed to read NEX Buffer data. %s", err.Error())
}
b.Value = value
*b = Buffer(value)
return nil
}
// Copy returns a pointer to a copy of the Buffer. Requires type assertion when used
func (b *Buffer) Copy() RVType {
return NewBuffer(b.Value)
func (b Buffer) Copy() RVType {
return NewBuffer(b)
}
// Equals checks if the input is equal in value to the current instance
func (b *Buffer) Equals(o RVType) bool {
if _, ok := o.(*Buffer); !ok {
func (b Buffer) Equals(o RVType) bool {
if _, ok := o.(Buffer); !ok {
return false
}
return bytes.Equal(b.Value, o.(*Buffer).Value)
return bytes.Equal(b, o.(Buffer))
}
// CopyRef copies the current value of the Buffer
// and returns a pointer to the new copy
func (b Buffer) CopyRef() RVTypePtr {
copied := b.Copy().(Buffer)
return &copied
}
// Deref takes a pointer to the Buffer
// and dereferences it to the raw value.
// Only useful when working with an instance of RVTypePtr
func (b *Buffer) Deref() RVType {
return *b
}
// String returns a string representation of the struct
func (b *Buffer) String() string {
return fmt.Sprintf("%x", b.Value)
func (b Buffer) String() string {
return hex.EncodeToString(b)
}
// NewBuffer returns a new Buffer
func NewBuffer(data []byte) *Buffer {
return &Buffer{Value: data}
func NewBuffer(input []byte) Buffer {
b := make(Buffer, len(input))
copy(b, input)
return b
}

View file

@ -9,11 +9,11 @@ import (
// Contains version info for Structures used in verbose RMC messages.
type ClassVersionContainer struct {
Structure
ClassVersions *Map[*String, *PrimitiveU16]
ClassVersions Map[String, UInt16] `json:"class_versions" db:"class_versions" bson:"class_versions" xml:"ClassVersions"`
}
// WriteTo writes the ClassVersionContainer to the given writable
func (cvc *ClassVersionContainer) WriteTo(writable Writable) {
func (cvc ClassVersionContainer) WriteTo(writable Writable) {
cvc.ClassVersions.WriteTo(writable)
}
@ -23,29 +23,43 @@ func (cvc *ClassVersionContainer) ExtractFrom(readable Readable) error {
}
// Copy returns a pointer to a copy of the ClassVersionContainer. Requires type assertion when used
func (cvc *ClassVersionContainer) Copy() RVType {
func (cvc ClassVersionContainer) Copy() RVType {
copied := NewClassVersionContainer()
copied.ClassVersions = cvc.ClassVersions.Copy().(*Map[*String, *PrimitiveU16])
copied.ClassVersions = cvc.ClassVersions.Copy().(Map[String, UInt16])
return copied
}
// Equals checks if the input is equal in value to the current instance
func (cvc *ClassVersionContainer) Equals(o RVType) bool {
if _, ok := o.(*ClassVersionContainer); !ok {
func (cvc ClassVersionContainer) Equals(o RVType) bool {
if _, ok := o.(ClassVersionContainer); !ok {
return false
}
return cvc.ClassVersions.Equals(o)
}
// CopyRef copies the current value of the ClassVersionContainer
// and returns a pointer to the new copy
func (cvc ClassVersionContainer) CopyRef() RVTypePtr {
copied := cvc.Copy().(ClassVersionContainer)
return &copied
}
// Deref takes a pointer to the ClassVersionContainer
// and dereferences it to the raw value.
// Only useful when working with an instance of RVTypePtr
func (cvc *ClassVersionContainer) Deref() RVType {
return *cvc
}
// String returns a string representation of the struct
func (cvc *ClassVersionContainer) String() string {
func (cvc ClassVersionContainer) String() string {
return cvc.FormatToString(0)
}
// FormatToString pretty-prints the struct data using the provided indentation level
func (cvc *ClassVersionContainer) FormatToString(indentationLevel int) string {
func (cvc ClassVersionContainer) FormatToString(indentationLevel int) string {
indentationValues := strings.Repeat("\t", indentationLevel+1)
indentationEnd := strings.Repeat("\t", indentationLevel)
@ -60,13 +74,10 @@ func (cvc *ClassVersionContainer) FormatToString(indentationLevel int) string {
}
// NewClassVersionContainer returns a new ClassVersionContainer
func NewClassVersionContainer() *ClassVersionContainer {
cvc := &ClassVersionContainer{
ClassVersions: NewMap[*String, *PrimitiveU16](),
func NewClassVersionContainer() ClassVersionContainer {
cvc := ClassVersionContainer{
ClassVersions: NewMap[String, UInt16](),
}
cvc.ClassVersions.KeyType = NewString("")
cvc.ClassVersions.ValueType = NewPrimitiveU16(0)
return cvc
}

View file

@ -11,8 +11,18 @@ type Data struct {
Structure
}
// ObjectID returns the object identifier of the type
func (d Data) ObjectID() RVType {
return d.DataObjectID()
}
// DataObjectID returns the object identifier of the type embedding Data
func (d Data) DataObjectID() RVType {
return NewString("Data")
}
// WriteTo writes the Data to the given writable
func (d *Data) WriteTo(writable Writable) {
func (d Data) WriteTo(writable Writable) {
d.WriteHeaderTo(writable, 0)
}
@ -26,7 +36,7 @@ func (d *Data) ExtractFrom(readable Readable) error {
}
// Copy returns a pointer to a copy of the Data. Requires type assertion when used
func (d *Data) Copy() RVType {
func (d Data) Copy() RVType {
copied := NewData()
copied.StructureVersion = d.StructureVersion
@ -34,23 +44,37 @@ func (d *Data) Copy() RVType {
}
// Equals checks if the input is equal in value to the current instance
func (d *Data) Equals(o RVType) bool {
if _, ok := o.(*Data); !ok {
func (d Data) Equals(o RVType) bool {
if _, ok := o.(Data); !ok {
return false
}
other := o.(*Data)
other := o.(Data)
return d.StructureVersion == other.StructureVersion
}
// CopyRef copies the current value of the Data
// and returns a pointer to the new copy
func (d Data) CopyRef() RVTypePtr {
copied := d.Copy().(Data)
return &copied
}
// Deref takes a pointer to the Data
// and dereferences it to the raw value.
// Only useful when working with an instance of RVTypePtr
func (d *Data) Deref() RVType {
return *d
}
// String returns a string representation of the struct
func (d *Data) String() string {
func (d Data) String() string {
return d.FormatToString(0)
}
// FormatToString pretty-prints the struct data using the provided indentation level
func (d *Data) FormatToString(indentationLevel int) string {
func (d Data) FormatToString(indentationLevel int) string {
indentationValues := strings.Repeat("\t", indentationLevel+1)
indentationEnd := strings.Repeat("\t", indentationLevel)
@ -64,6 +88,6 @@ func (d *Data) FormatToString(indentationLevel int) string {
}
// NewData returns a new Data Structure
func NewData() *Data {
return &Data{}
func NewData() Data {
return Data{}
}

16
types/data_holder.go Normal file
View file

@ -0,0 +1,16 @@
package types
// DataInterface defines an interface to track types which have Data anywhere
// in their parent tree.
type DataInterface interface {
HoldableObject
DataObjectID() RVType // Returns the object identifier of the type embedding Data
}
// DataHolder is an AnyObjectHolder for types which embed Data
type DataHolder = AnyObjectHolder[DataInterface]
// NewDataHolder returns a new DataHolder
func NewDataHolder() DataHolder {
return DataHolder{}
}

View file

@ -7,51 +7,63 @@ import (
)
// DateTime is an implementation of rdv::DateTime.
// Type alias of uint64.
// The underlying value is a uint64 bit field containing date and time information.
type DateTime struct {
value uint64
}
type DateTime uint64
// WriteTo writes the DateTime to the given writable
func (dt *DateTime) WriteTo(writable Writable) {
writable.WritePrimitiveUInt64LE(dt.value)
func (dt DateTime) WriteTo(writable Writable) {
writable.WriteUInt64LE(uint64(dt))
}
// ExtractFrom extracts the DateTime from the given readable
func (dt *DateTime) ExtractFrom(readable Readable) error {
value, err := readable.ReadPrimitiveUInt64LE()
value, err := readable.ReadUInt64LE()
if err != nil {
return fmt.Errorf("Failed to read DateTime value. %s", err.Error())
}
dt.value = value
*dt = DateTime(value)
return nil
}
// Copy returns a new copied instance of DateTime
func (dt *DateTime) Copy() RVType {
return NewDateTime(dt.value)
func (dt DateTime) Copy() RVType {
return NewDateTime(uint64(dt))
}
// Equals checks if the input is equal in value to the current instance
func (dt *DateTime) Equals(o RVType) bool {
if _, ok := o.(*DateTime); !ok {
func (dt DateTime) Equals(o RVType) bool {
if _, ok := o.(DateTime); !ok {
return false
}
return dt.value == o.(*DateTime).value
return dt == o.(DateTime)
}
// CopyRef copies the current value of the DateTime
// and returns a pointer to the new copy
func (dt DateTime) CopyRef() RVTypePtr {
copied := dt.Copy().(DateTime)
return &copied
}
// Deref takes a pointer to the DateTime
// and dereferences it to the raw value.
// Only useful when working with an instance of RVTypePtr
func (dt *DateTime) Deref() RVType {
return *dt
}
// Make initilizes a DateTime with the input data
func (dt *DateTime) Make(year, month, day, hour, minute, second int) *DateTime {
dt.value = uint64(second | (minute << 6) | (hour << 12) | (day << 17) | (month << 22) | (year << 26))
func (dt *DateTime) Make(year, month, day, hour, minute, second int) DateTime {
*dt = DateTime(second | (minute << 6) | (hour << 12) | (day << 17) | (month << 22) | (year << 26))
return dt
return *dt
}
// FromTimestamp converts a Time timestamp into a NEX DateTime
func (dt *DateTime) FromTimestamp(timestamp time.Time) *DateTime {
func (dt *DateTime) FromTimestamp(timestamp time.Time) DateTime {
year := timestamp.Year()
month := int(timestamp.Month())
day := timestamp.Day()
@ -63,47 +75,42 @@ func (dt *DateTime) FromTimestamp(timestamp time.Time) *DateTime {
}
// Now returns a NEX DateTime value of the current UTC time
func (dt *DateTime) Now() *DateTime {
func (dt DateTime) Now() DateTime {
return dt.FromTimestamp(time.Now().UTC())
}
// Value returns the stored DateTime time
func (dt *DateTime) Value() uint64 {
return dt.value
}
// Second returns the seconds value stored in the DateTime
func (dt *DateTime) Second() int {
return int(dt.value & 63)
func (dt DateTime) Second() int {
return int(dt & 63)
}
// Minute returns the minutes value stored in the DateTime
func (dt *DateTime) Minute() int {
return int((dt.value >> 6) & 63)
func (dt DateTime) Minute() int {
return int((dt >> 6) & 63)
}
// Hour returns the hours value stored in the DateTime
func (dt *DateTime) Hour() int {
return int((dt.value >> 12) & 31)
func (dt DateTime) Hour() int {
return int((dt >> 12) & 31)
}
// Day returns the day value stored in the DateTime
func (dt *DateTime) Day() int {
return int((dt.value >> 17) & 31)
func (dt DateTime) Day() int {
return int((dt >> 17) & 31)
}
// Month returns the month value stored in the DateTime
func (dt *DateTime) Month() time.Month {
return time.Month((dt.value >> 22) & 15)
func (dt DateTime) Month() time.Month {
return time.Month((dt >> 22) & 15)
}
// Year returns the year value stored in the DateTime
func (dt *DateTime) Year() int {
return int(dt.value >> 26)
func (dt DateTime) Year() int {
return int(dt >> 26)
}
// Standard returns the DateTime as a standard time.Time
func (dt *DateTime) Standard() time.Time {
func (dt DateTime) Standard() time.Time {
return time.Date(
dt.Year(),
dt.Month(),
@ -117,25 +124,26 @@ func (dt *DateTime) Standard() time.Time {
}
// String returns a string representation of the struct
func (dt *DateTime) String() string {
func (dt DateTime) String() string {
return dt.FormatToString(0)
}
// FormatToString pretty-prints the struct data using the provided indentation level
func (dt *DateTime) FormatToString(indentationLevel int) string {
func (dt DateTime) FormatToString(indentationLevel int) string {
indentationValues := strings.Repeat("\t", indentationLevel+1)
indentationEnd := strings.Repeat("\t", indentationLevel)
var b strings.Builder
b.WriteString("DateTime{\n")
b.WriteString(fmt.Sprintf("%svalue: %d (%s)\n", indentationValues, dt.value, dt.Standard().Format("2006-01-02 15:04:05")))
b.WriteString(fmt.Sprintf("%svalue: %d (%s)\n", indentationValues, dt, dt.Standard().Format("2006-01-02 15:04:05")))
b.WriteString(fmt.Sprintf("%s}", indentationEnd))
return b.String()
}
// NewDateTime returns a new DateTime instance
func NewDateTime(value uint64) *DateTime {
return &DateTime{value: value}
func NewDateTime(input uint64) DateTime {
dt := DateTime(input)
return dt
}

62
types/double.go Normal file
View file

@ -0,0 +1,62 @@
package types
import "fmt"
// Double is a type alias for the Go basic type float64 for use as an RVType
type Double float64
// WriteTo writes the Double to the given writable
func (d Double) WriteTo(writable Writable) {
writable.WriteFloat64LE(float64(d))
}
// ExtractFrom extracts the Double value from the given readable
func (d *Double) ExtractFrom(readable Readable) error {
value, err := readable.ReadFloat64LE()
if err != nil {
return err
}
*d = Double(value)
return nil
}
// Copy returns a pointer to a copy of the Double. Requires type assertion when used
func (d Double) Copy() RVType {
return NewDouble(float64(d))
}
// Equals checks if the input is equal in value to the current instance
func (d Double) Equals(o RVType) bool {
other, ok := o.(Double)
if !ok {
return false
}
return d == other
}
// CopyRef copies the current value of the Double
// and returns a pointer to the new copy
func (d Double) CopyRef() RVTypePtr {
copied := d.Copy().(Double)
return &copied
}
// Deref takes a pointer to the Double
// and dereferences it to the raw value.
// Only useful when working with an instance of RVTypePtr
func (d *Double) Deref() RVType {
return *d
}
// String returns a string representation of the Double
func (d Double) String() string {
return fmt.Sprintf("%f", d)
}
// NewDouble returns a new Double
func NewDouble(input float64) Double {
d := Double(input)
return d
}

62
types/float.go Normal file
View file

@ -0,0 +1,62 @@
package types
import "fmt"
// Float is a type alias for the Go basic type float32 for use as an RVType
type Float float32
// WriteTo writes the Float to the given writable
func (f Float) WriteTo(writable Writable) {
writable.WriteFloat32LE(float32(f))
}
// ExtractFrom extracts the Float value from the given readable
func (f *Float) ExtractFrom(readable Readable) error {
value, err := readable.ReadFloat32LE()
if err != nil {
return err
}
*f = Float(value)
return nil
}
// Copy returns a pointer to a copy of the Float. Requires type assertion when used
func (f Float) Copy() RVType {
return NewFloat(float32(f))
}
// Equals checks if the input is equal in value to the current instance
func (f Float) Equals(o RVType) bool {
other, ok := o.(Float)
if !ok {
return false
}
return f == other
}
// CopyRef copies the current value of the Float
// and returns a pointer to the new copy
func (f Float) CopyRef() RVTypePtr {
copied := f.Copy().(Float)
return &copied
}
// Deref takes a pointer to the Float
// and dereferences it to the raw value.
// Only useful when working with an instance of RVTypePtr
func (f *Float) Deref() RVType {
return *f
}
// String returns a string representation of the Float
func (f Float) String() string {
return fmt.Sprintf("%f", f)
}
// NewFloat returns a new Float
func NewFloat(input float32) Float {
f := Float(input)
return f
}

62
types/int16.go Normal file
View file

@ -0,0 +1,62 @@
package types
import "fmt"
// Int16 is a type alias for the Go basic type int16 for use as an RVType
type Int16 int16
// WriteTo writes the Int16 to the given writable
func (i16 Int16) WriteTo(writable Writable) {
writable.WriteInt16LE(int16(i16))
}
// ExtractFrom extracts the Int16 value from the given readable
func (i16 *Int16) ExtractFrom(readable Readable) error {
value, err := readable.ReadInt16LE()
if err != nil {
return err
}
*i16 = Int16(value)
return nil
}
// Copy returns a pointer to a copy of the Int16. Requires type assertion when used
func (i16 Int16) Copy() RVType {
return NewInt16(int16(i16))
}
// Equals checks if the input is equal in value to the current instance
func (i16 Int16) Equals(o RVType) bool {
other, ok := o.(Int16)
if !ok {
return false
}
return i16 == other
}
// CopyRef copies the current value of the Int16
// and returns a pointer to the new copy
func (i16 Int16) CopyRef() RVTypePtr {
copied := i16.Copy().(Int16)
return &copied
}
// Deref takes a pointer to the Int16
// and dereferences it to the raw value.
// Only useful when working with an instance of RVTypePtr
func (i16 *Int16) Deref() RVType {
return *i16
}
// String returns a string representation of the Int16
func (i16 Int16) String() string {
return fmt.Sprintf("%d", i16)
}
// NewInt16 returns a new Int16
func NewInt16(input int16) Int16 {
i16 := Int16(input)
return i16
}

62
types/int32.go Normal file
View file

@ -0,0 +1,62 @@
package types
import "fmt"
// Int32 is a type alias for the Go basic type int32 for use as an RVType
type Int32 int32
// WriteTo writes the Int32 to the given writable
func (i32 Int32) WriteTo(writable Writable) {
writable.WriteInt32LE(int32(i32))
}
// ExtractFrom extracts the Int32 value from the given readable
func (i32 *Int32) ExtractFrom(readable Readable) error {
value, err := readable.ReadInt32LE()
if err != nil {
return err
}
*i32 = Int32(value)
return nil
}
// Copy returns a pointer to a copy of the Int32. Requires type assertion when used
func (i32 Int32) Copy() RVType {
return NewInt32(int32(i32))
}
// Equals checks if the input is equal in value to the current instance
func (i32 Int32) Equals(o RVType) bool {
other, ok := o.(Int32)
if !ok {
return false
}
return i32 == other
}
// CopyRef copies the current value of the Int32
// and returns a pointer to the new copy
func (i32 Int32) CopyRef() RVTypePtr {
copied := i32.Copy().(Int32)
return &copied
}
// Deref takes a pointer to the Int32
// and dereferences it to the raw value.
// Only useful when working with an instance of RVTypePtr
func (i32 *Int32) Deref() RVType {
return *i32
}
// String returns a string representation of the Int32
func (i32 Int32) String() string {
return fmt.Sprintf("%d", i32)
}
// NewInt32 returns a new Int32
func NewInt32(input int32) Int32 {
i32 := Int32(input)
return i32
}

62
types/int64.go Normal file
View file

@ -0,0 +1,62 @@
package types
import "fmt"
// Int64 is a type alias for the Go basic type int64 for use as an RVType
type Int64 int64
// WriteTo writes the Int64 to the given writable
func (i64 Int64) WriteTo(writable Writable) {
writable.WriteInt64LE(int64(i64))
}
// ExtractFrom extracts the Int64 value from the given readable
func (i64 *Int64) ExtractFrom(readable Readable) error {
value, err := readable.ReadInt64LE()
if err != nil {
return err
}
*i64 = Int64(value)
return nil
}
// Copy returns a pointer to a copy of the Int64. Requires type assertion when used
func (i64 Int64) Copy() RVType {
return NewInt64(int64(i64))
}
// Equals checks if the input is equal in value to the current instance
func (i64 Int64) Equals(o RVType) bool {
other, ok := o.(Int64)
if !ok {
return false
}
return i64 == other
}
// CopyRef copies the current value of the Int64
// and returns a pointer to the new copy
func (i64 Int64) CopyRef() RVTypePtr {
copied := i64.Copy().(Int64)
return &copied
}
// Deref takes a pointer to the Int64
// and dereferences it to the raw value.
// Only useful when working with an instance of RVTypePtr
func (i64 *Int64) Deref() RVType {
return *i64
}
// String returns a string representation of the Int64
func (i64 Int64) String() string {
return fmt.Sprintf("%d", i64)
}
// NewInt64 returns a new Int64
func NewInt64(input int64) Int64 {
i64 := Int64(input)
return i64
}

62
types/int8.go Normal file
View file

@ -0,0 +1,62 @@
package types
import "fmt"
// Int8 is a type alias for the Go basic type int8 for use as an RVType
type Int8 int8
// WriteTo writes the Int8 to the given writable
func (i8 Int8) WriteTo(writable Writable) {
writable.WriteInt8(int8(i8))
}
// ExtractFrom extracts the Int8 value from the given readable
func (i8 *Int8) ExtractFrom(readable Readable) error {
value, err := readable.ReadInt8()
if err != nil {
return err
}
*i8 = Int8(value)
return nil
}
// Copy returns a pointer to a copy of the Int8. Requires type assertion when used
func (i8 Int8) Copy() RVType {
return NewInt8(int8(i8))
}
// Equals checks if the input is equal in value to the current instance
func (i8 Int8) Equals(o RVType) bool {
other, ok := o.(Int8)
if !ok {
return false
}
return i8 == other
}
// CopyRef copies the current value of the Int8
// and returns a pointer to the new copy
func (i8 Int8) CopyRef() RVTypePtr {
copied := i8.Copy().(Int8)
return &copied
}
// Deref takes a pointer to the Int8
// and dereferences it to the raw value.
// Only useful when working with an instance of RVTypePtr
func (i8 *Int8) Deref() RVType {
return *i8
}
// String returns a string representation of the Int8
func (i8 Int8) String() string {
return fmt.Sprintf("%d", i8)
}
// NewInt8 returns a new Int8
func NewInt8(input int8) Int8 {
i8 := Int8(input)
return i8
}

View file

@ -1,7 +1,6 @@
package types
import (
"errors"
"fmt"
)
@ -10,23 +9,32 @@ import (
//
// Unlike Buffer and qBuffer, which use the same data type with differing size field lengths,
// there does not seem to be an official rdv::List type
type List[T RVType] struct {
real []T
Type T
}
type List[T RVType] []T
// WriteTo writes the List to the given writable
func (l *List[T]) WriteTo(writable Writable) {
writable.WritePrimitiveUInt32LE(uint32(len(l.real)))
func (l List[T]) WriteTo(writable Writable) {
writable.WriteUInt32LE(uint32(len(l)))
for _, v := range l.real {
for _, v := range l {
v.WriteTo(writable)
}
}
func (l List[T]) extractType(t any, readable Readable) error {
// * This just makes List.ExtractFrom() a bit cleaner
// * since it doesn't have to type check
if ptr, ok := t.(RVTypePtr); ok {
return ptr.ExtractFrom(readable)
}
// * Maybe support other types..?
return fmt.Errorf("Unsupported List type %T", t)
}
// ExtractFrom extracts the List from the given readable
func (l *List[T]) ExtractFrom(readable Readable) error {
length, err := readable.ReadPrimitiveUInt32LE()
length, err := readable.ReadUInt32LE()
if err != nil {
return err
}
@ -34,46 +42,44 @@ func (l *List[T]) ExtractFrom(readable Readable) error {
slice := make([]T, 0, length)
for i := 0; i < int(length); i++ {
value := l.Type.Copy()
if err := value.ExtractFrom(readable); err != nil {
var value T
if err := l.extractType(&value, readable); err != nil {
return err
}
slice = append(slice, value.(T))
slice = append(slice, value)
}
l.real = slice
*l = slice
return nil
}
// Copy returns a pointer to a copy of the List. Requires type assertion when used
func (l *List[T]) Copy() RVType {
copied := NewList[T]()
copied.real = make([]T, len(l.real))
copied.Type = l.Type.Copy().(T)
func (l List[T]) Copy() RVType {
copied := make(List[T], 0)
for i, v := range l.real {
copied.real[i] = v.Copy().(T)
for _, v := range l {
copied = append(copied, v.Copy().(T))
}
return copied
}
// Equals checks if the input is equal in value to the current instance
func (l *List[T]) Equals(o RVType) bool {
if _, ok := o.(*List[T]); !ok {
func (l List[T]) Equals(o RVType) bool {
if _, ok := o.(List[T]); !ok {
return false
}
other := o.(*List[T])
other := o.(List[T])
if len(l.real) != len(other.real) {
if len(l) != len(other) {
return false
}
for i := 0; i < len(l.real); i++ {
if !l.real[i].Equals(other.real[i]) {
for i := 0; i < len(l); i++ {
if !l[i].Equals(other[i]) {
return false
}
}
@ -81,73 +87,24 @@ func (l *List[T]) Equals(o RVType) bool {
return true
}
// Slice returns the real underlying slice for the List
func (l *List[T]) Slice() []T {
return l.real
// CopyRef copies the current value of the List
// and returns a pointer to the new copy
func (l List[T]) CopyRef() RVTypePtr {
copied := l.Copy().(List[T])
return &copied
}
// Append appends an element to the List internal slice
func (l *List[T]) Append(value T) {
l.real = append(l.real, value)
// Deref takes a pointer to the List
// and dereferences it to the raw value.
// Only useful when working with an instance of RVTypePtr
func (l *List[T]) Deref() RVType {
return *l
}
// Get returns an element at the given index. Returns an error if the index is OOB
func (l *List[T]) Get(index int) (T, error) {
if index < 0 || index >= len(l.real) {
return l.Type.Copy().(T), errors.New("Index out of bounds")
}
return l.real[index], nil
}
// SetIndex sets a value in the List at the given index
func (l *List[T]) SetIndex(index int, value T) error {
if index < 0 || index >= len(l.real) {
return errors.New("Index out of bounds")
}
l.real[index] = value
return nil
}
// DeleteIndex deletes an element at the given index. Returns an error if the index is OOB
func (l *List[T]) DeleteIndex(index int) error {
if index < 0 || index >= len(l.real) {
return errors.New("Index out of bounds")
}
l.real = append(l.real[:index], l.real[index+1:]...)
return nil
}
// Remove removes the first occurance of the input from the List. Returns an error if the index is OOB
func (l *List[T]) Remove(check T) {
for i, value := range l.real {
if value.Equals(check) {
l.DeleteIndex(i)
return
}
}
}
// SetFromData sets the List's internal slice to the input data
func (l *List[T]) SetFromData(data []T) {
l.real = data
}
// Length returns the number of elements in the List
func (l *List[T]) Length() int {
return len(l.real)
}
// Each runs a callback function for every element in the List
// The List should not be modified inside the callback function
// Returns true if the loop was terminated early
func (l *List[T]) Each(callback func(i int, value T) bool) bool {
for i, value := range l.real {
if callback(i, value) {
// Contains checks if the provided value exists in the List
func (l List[T]) Contains(checkValue T) bool {
for _, v := range l {
if v.Equals(checkValue) {
return true
}
}
@ -155,29 +112,12 @@ func (l *List[T]) Each(callback func(i int, value T) bool) bool {
return false
}
// Contains checks if the provided value exists in the List
func (l *List[T]) Contains(checkValue T) bool {
contains := false
l.Each(func(_ int, value T) bool {
if value.Equals(checkValue) {
contains = true
return true
}
return false
})
return contains
}
// String returns a string representation of the struct
func (l *List[T]) String() string {
return fmt.Sprintf("%v", l.real)
func (l List[T]) String() string {
return fmt.Sprintf("%v", ([]T)(l))
}
// NewList returns a new List of the provided type
func NewList[T RVType]() *List[T] {
return &List[T]{real: make([]T, 0)}
func NewList[T RVType]() List[T] {
return make(List[T], 0)
}

View file

@ -6,99 +6,120 @@ import (
)
// Map represents a Quazal Rendez-Vous/NEX Map type.
//
// Type alias of map[K]V.
// There is not an official type in either the rdv or nn::nex namespaces.
// The data is stored as an array of key-value pairs.
type Map[K RVType, V RVType] struct {
// * Rendez-Vous/NEX MapMap types can have ANY value for the key, but Go requires
// * map keys to implement the "comparable" constraint. This is not possible with
// * RVTypes. We have to either break spec and only allow primitives as Map keys,
// * or store the key/value types indirectly
keys []K
values []V
KeyType K
ValueType V
// May have any RVType as both a key and value. If either they key or
// value types are not an RVType, they are ignored.
//
// Incompatible with RVType pointers!
type Map[K comparable, V RVType] map[K]V
func (m Map[K, V]) writeType(t any, writable Writable) {
// * This just makes Map.WriteTo() a bit cleaner
// * since it doesn't have to type check
if rvt, ok := t.(interface{ WriteTo(writable Writable) }); ok {
rvt.WriteTo(writable)
}
}
// WriteTo writes the Map to the given writable
func (m *Map[K, V]) WriteTo(writable Writable) {
writable.WritePrimitiveUInt32LE(uint32(m.Size()))
func (m Map[K, V]) WriteTo(writable Writable) {
writable.WriteUInt32LE(uint32(len(m)))
for i := 0; i < len(m.keys); i++ {
m.keys[i].WriteTo(writable)
m.values[i].WriteTo(writable)
for key, value := range m {
m.writeType(key, writable)
m.writeType(value, writable)
}
}
func (m Map[K, V]) extractType(t any, readable Readable) error {
// * This just makes Map.ExtractFrom() a bit cleaner
// * since it doesn't have to type check
if ptr, ok := t.(RVTypePtr); ok {
return ptr.ExtractFrom(readable)
}
// * Maybe support other types..?
return fmt.Errorf("Unsupported Map type %T", t)
}
// ExtractFrom extracts the Map from the given readable
func (m *Map[K, V]) ExtractFrom(readable Readable) error {
length, err := readable.ReadPrimitiveUInt32LE()
length, err := readable.ReadUInt32LE()
if err != nil {
return err
}
keys := make([]K, 0, length)
values := make([]V, 0, length)
extracted := make(Map[K, V])
for i := 0; i < int(length); i++ {
key := m.KeyType.Copy()
if err := key.ExtractFrom(readable); err != nil {
var key K
if err := m.extractType(&key, readable); err != nil {
return err
}
value := m.ValueType.Copy()
if err := value.ExtractFrom(readable); err != nil {
var value V
if err := m.extractType(&value, readable); err != nil {
return err
}
keys = append(keys, key.(K))
values = append(values, value.(V))
extracted[key] = value
}
m.keys = keys
m.values = values
*m = extracted
return nil
}
// Copy returns a pointer to a copy of the Map. Requires type assertion when used
func (m *Map[K, V]) Copy() RVType {
copied := NewMap[K, V]()
copied.keys = make([]K, len(m.keys))
copied.values = make([]V, len(m.values))
copied.KeyType = m.KeyType.Copy().(K)
copied.ValueType = m.ValueType.Copy().(V)
func (m Map[K, V]) copyType(t any) RVType {
// * This just makes Map.Copy() a bit cleaner
// * since it doesn't have to type check
if rvt, ok := t.(RVType); ok {
return rvt.Copy()
}
for i := 0; i < len(m.keys); i++ {
copied.keys[i] = m.keys[i].Copy().(K)
copied.values[i] = m.values[i].Copy().(V)
// TODO - Improve this, this isn't safe
return nil
}
// Copy returns a pointer to a copy of the Map. Requires type assertion when used
func (m Map[K, V]) Copy() RVType {
copied := make(Map[K, V])
for key, value := range m {
copied[m.copyType(key).(K)] = value.Copy().(V)
}
return copied
}
// Equals checks if the input is equal in value to the current instance
func (m *Map[K, V]) Equals(o RVType) bool {
if _, ok := o.(*Map[K, V]); !ok {
return false
}
other := o.(*Map[K, V])
if len(m.keys) != len(other.keys) {
return false
}
if len(m.values) != len(other.values) {
return false
}
for i := 0; i < len(m.keys); i++ {
if !m.keys[i].Equals(other.keys[i]) {
return false
func (m Map[K, V]) typesEqual(t1, t2 any) bool {
// * This just makes Map.Equals() a bit cleaner
// * since it doesn't have to type check
if rvt1, ok := t1.(RVType); ok {
if rvt2, ok := t2.(RVType); ok {
return rvt1.Equals(rvt2)
}
}
if !m.values[i].Equals(other.values[i]) {
return false
}
// Equals checks if the input is equal in value to the current instance
func (m Map[K, V]) Equals(o RVType) bool {
if _, ok := o.(Map[K, V]); !ok {
return false
}
other := o.(Map[K, V])
if len(m) != len(other) {
return false
}
for key, value := range m {
if otherValue, ok := other[key]; !ok || m.typesEqual(&value, &otherValue) {
return false
}
}
@ -106,70 +127,40 @@ func (m *Map[K, V]) Equals(o RVType) bool {
return true
}
// Set sets an element to the Map internal slices
func (m *Map[K, V]) Set(key K, value V) {
var index int = -1
for i := 0; i < len(m.keys); i++ {
if m.keys[i].Equals(key) {
index = i
break
}
}
// * Replace the element if exists, otherwise push new
if index != -1 {
m.keys[index] = key
m.values[index] = value
} else {
m.keys = append(m.keys, key)
m.values = append(m.values, value)
}
// CopyRef copies the current value of the Map
// and returns a pointer to the new copy
func (m Map[K, V]) CopyRef() RVTypePtr {
copied := m.Copy().(Map[K, V])
return &copied
}
// Get returns an element from the Map. If not found, "ok" is false
func (m *Map[K, V]) Get(key K) (V, bool) {
var index int = -1
for i := 0; i < len(m.keys); i++ {
if m.keys[i].Equals(key) {
index = i
break
}
}
if index != -1 {
return m.values[index], true
}
return m.ValueType.Copy().(V), false
}
// Size returns the length of the Map
func (m *Map[K, V]) Size() int {
return len(m.keys)
// Deref takes a pointer to the Map
// and dereferences it to the raw value.
// Only useful when working with an instance of RVTypePtr
func (m *Map[K, V]) Deref() RVType {
return *m
}
// String returns a string representation of the struct
func (m *Map[K, V]) String() string {
func (m Map[K, V]) String() string {
return m.FormatToString(0)
}
// FormatToString pretty-prints the struct data using the provided indentation level
func (m *Map[K, V]) FormatToString(indentationLevel int) string {
func (m Map[K, V]) FormatToString(indentationLevel int) string {
indentationValues := strings.Repeat("\t", indentationLevel+1)
indentationEnd := strings.Repeat("\t", indentationLevel)
var b strings.Builder
if len(m.keys) == 0 {
if len(m) == 0 {
b.WriteString("{}\n")
} else {
b.WriteString("{\n")
for i := 0; i < len(m.keys); i++ {
for key, value := range m {
// TODO - Special handle the the last item to not add the comma on last item
b.WriteString(fmt.Sprintf("%s%v: %v,\n", indentationValues, m.keys[i], m.values[i]))
b.WriteString(fmt.Sprintf("%s%v: %v,\n", indentationValues, key, value))
}
b.WriteString(fmt.Sprintf("%s}\n", indentationEnd))
@ -179,9 +170,6 @@ func (m *Map[K, V]) FormatToString(indentationLevel int) string {
}
// NewMap returns a new Map of the provided type
func NewMap[K RVType, V RVType]() *Map[K, V] {
return &Map[K, V]{
keys: make([]K, 0),
values: make([]V, 0),
}
func NewMap[K comparable, V RVType]() Map[K, V] {
return make(Map[K, V])
}

View file

@ -6,21 +6,17 @@ import (
)
// PID represents a unique number to identify a user.
// The official library treats this as a primitive integer.
//
// Type alias of uint64.
// The true size of this value depends on the client version.
// Legacy clients (WiiU/3DS) use a uint32, whereas modern clients (Nintendo Switch) use a uint64.
// Value is always stored as the higher uint64, the consuming API should assert accordingly
type PID struct {
pid uint64
}
// Legacy clients (Wii U/3DS) use a uint32, whereas modern clients (Nintendo Switch) use a uint64.
type PID uint64
// WriteTo writes the PID to the given writable
func (p *PID) WriteTo(writable Writable) {
func (p PID) WriteTo(writable Writable) {
if writable.PIDSize() == 8 {
writable.WritePrimitiveUInt64LE(p.pid)
writable.WriteUInt64LE(uint64(p))
} else {
writable.WritePrimitiveUInt32LE(uint32(p.pid))
writable.WriteUInt32LE(uint32(p))
}
}
@ -30,9 +26,9 @@ func (p *PID) ExtractFrom(readable Readable) error {
var err error
if readable.PIDSize() == 8 {
pid, err = readable.ReadPrimitiveUInt64LE()
pid, err = readable.ReadUInt64LE()
} else {
p, e := readable.ReadPrimitiveUInt32LE()
p, e := readable.ReadUInt32LE()
pid = uint64(p)
err = e
@ -42,55 +38,59 @@ func (p *PID) ExtractFrom(readable Readable) error {
return err
}
p.pid = pid
*p = PID(pid)
return nil
}
// Copy returns a pointer to a copy of the PID. Requires type assertion when used
func (p *PID) Copy() RVType {
return NewPID(p.pid)
func (p PID) Copy() RVType {
return NewPID(uint64(p))
}
// Equals checks if the input is equal in value to the current instance
func (p *PID) Equals(o RVType) bool {
if _, ok := o.(*PID); !ok {
func (p PID) Equals(o RVType) bool {
if _, ok := o.(PID); !ok {
return false
}
return p.pid == o.(*PID).pid
return p == o.(PID)
}
// Value returns the numeric value of the PID as a uint64 regardless of client version
func (p *PID) Value() uint64 {
return p.pid
// CopyRef copies the current value of the PID
// and returns a pointer to the new copy
func (p PID) CopyRef() RVTypePtr {
copied := p.Copy().(PID)
return &copied
}
// LegacyValue returns the numeric value of the PID as a uint32, for legacy clients
func (p *PID) LegacyValue() uint32 {
return uint32(p.pid)
// Deref takes a pointer to the PID
// and dereferences it to the raw value.
// Only useful when working with an instance of RVTypePtr
func (p *PID) Deref() RVType {
return *p
}
// String returns a string representation of the struct
func (p *PID) String() string {
func (p PID) String() string {
return p.FormatToString(0)
}
// FormatToString pretty-prints the struct data using the provided indentation level
func (p *PID) FormatToString(indentationLevel int) string {
func (p PID) FormatToString(indentationLevel int) string {
indentationValues := strings.Repeat("\t", indentationLevel+1)
indentationEnd := strings.Repeat("\t", indentationLevel)
var b strings.Builder
b.WriteString("PID{\n")
b.WriteString(fmt.Sprintf("%spid: %d\n", indentationValues, p.pid))
b.WriteString(fmt.Sprintf("%spid: %d\n", indentationValues, p))
b.WriteString(fmt.Sprintf("%s}", indentationEnd))
return b.String()
}
// NewPID returns a PID instance. The real size of PID depends on the client version
func NewPID(pid uint64) *PID {
return &PID{pid: pid}
func NewPID(input uint64) PID {
p := PID(input)
return p
}

View file

@ -1,49 +0,0 @@
package types
import "fmt"
// PrimitiveBool is wrapper around a Go primitive bool with receiver methods to conform to RVType
type PrimitiveBool struct {
Value bool
}
// WriteTo writes the bool to the given writable
func (b *PrimitiveBool) WriteTo(writable Writable) {
writable.WritePrimitiveBool(b.Value)
}
// ExtractFrom extracts the bool from the given readable
func (b *PrimitiveBool) ExtractFrom(readable Readable) error {
value, err := readable.ReadPrimitiveBool()
if err != nil {
return err
}
b.Value = value
return nil
}
// Copy returns a pointer to a copy of the PrimitiveBool. Requires type assertion when used
func (b *PrimitiveBool) Copy() RVType {
return NewPrimitiveBool(b.Value)
}
// Equals checks if the input is equal in value to the current instance
func (b *PrimitiveBool) Equals(o RVType) bool {
if _, ok := o.(*PrimitiveBool); !ok {
return false
}
return b.Value == o.(*PrimitiveBool).Value
}
// String returns a string representation of the struct
func (b *PrimitiveBool) String() string {
return fmt.Sprintf("%t", b.Value)
}
// NewPrimitiveBool returns a new PrimitiveBool
func NewPrimitiveBool(boolean bool) *PrimitiveBool {
return &PrimitiveBool{Value: boolean}
}

View file

@ -1,49 +0,0 @@
package types
import "fmt"
// PrimitiveF32 is wrapper around a Go primitive float32 with receiver methods to conform to RVType
type PrimitiveF32 struct {
Value float32
}
// WriteTo writes the float32 to the given writable
func (f32 *PrimitiveF32) WriteTo(writable Writable) {
writable.WritePrimitiveFloat32LE(f32.Value)
}
// ExtractFrom extracts the float32 from the given readable
func (f32 *PrimitiveF32) ExtractFrom(readable Readable) error {
value, err := readable.ReadPrimitiveFloat32LE()
if err != nil {
return err
}
f32.Value = value
return nil
}
// Copy returns a pointer to a copy of the float32. Requires type assertion when used
func (f32 *PrimitiveF32) Copy() RVType {
return NewPrimitiveF32(f32.Value)
}
// Equals checks if the input is equal in value to the current instance
func (f32 *PrimitiveF32) Equals(o RVType) bool {
if _, ok := o.(*PrimitiveF32); !ok {
return false
}
return f32.Value == o.(*PrimitiveF32).Value
}
// String returns a string representation of the struct
func (f32 *PrimitiveF32) String() string {
return fmt.Sprintf("%f", f32.Value)
}
// NewPrimitiveF32 returns a new PrimitiveF32
func NewPrimitiveF32(float float32) *PrimitiveF32 {
return &PrimitiveF32{Value: float}
}

View file

@ -1,49 +0,0 @@
package types
import "fmt"
// PrimitiveF64 is wrapper around a Go primitive float64 with receiver methods to conform to RVType
type PrimitiveF64 struct {
Value float64
}
// WriteTo writes the float64 to the given writable
func (f64 *PrimitiveF64) WriteTo(writable Writable) {
writable.WritePrimitiveFloat64LE(f64.Value)
}
// ExtractFrom extracts the float64 from the given readable
func (f64 *PrimitiveF64) ExtractFrom(readable Readable) error {
value, err := readable.ReadPrimitiveFloat64LE()
if err != nil {
return err
}
f64.Value = value
return nil
}
// Copy returns a pointer to a copy of the float64. Requires type assertion when used
func (f64 *PrimitiveF64) Copy() RVType {
return NewPrimitiveF64(f64.Value)
}
// Equals checks if the input is equal in value to the current instance
func (f64 *PrimitiveF64) Equals(o RVType) bool {
if _, ok := o.(*PrimitiveF64); !ok {
return false
}
return f64.Value == o.(*PrimitiveF64).Value
}
// String returns a string representation of the struct
func (f64 *PrimitiveF64) String() string {
return fmt.Sprintf("%f", f64.Value)
}
// NewPrimitiveF64 returns a new PrimitiveF64
func NewPrimitiveF64(float float64) *PrimitiveF64 {
return &PrimitiveF64{Value: float}
}

View file

@ -1,119 +0,0 @@
package types
import "fmt"
// PrimitiveS16 is wrapper around a Go primitive int16 with receiver methods to conform to RVType
type PrimitiveS16 struct {
Value int16
}
// WriteTo writes the int16 to the given writable
func (s16 *PrimitiveS16) WriteTo(writable Writable) {
writable.WritePrimitiveInt16LE(s16.Value)
}
// ExtractFrom extracts the int16 from the given readable
func (s16 *PrimitiveS16) ExtractFrom(readable Readable) error {
value, err := readable.ReadPrimitiveInt16LE()
if err != nil {
return err
}
s16.Value = value
return nil
}
// Copy returns a pointer to a copy of the int16. Requires type assertion when used
func (s16 *PrimitiveS16) Copy() RVType {
return NewPrimitiveS16(s16.Value)
}
// Equals checks if the input is equal in value to the current instance
func (s16 *PrimitiveS16) Equals(o RVType) bool {
if _, ok := o.(*PrimitiveS16); !ok {
return false
}
return s16.Value == o.(*PrimitiveS16).Value
}
// String returns a string representation of the struct
func (s16 *PrimitiveS16) String() string {
return fmt.Sprintf("%d", s16.Value)
}
// AND runs a bitwise AND operation on the PrimitiveS16 value. Consumes and returns a NEX primitive
func (s16 *PrimitiveS16) AND(other *PrimitiveS16) *PrimitiveS16 {
return NewPrimitiveS16(s16.PAND(other.Value))
}
// PAND (Primitive AND) runs a bitwise AND operation on the PrimitiveS16 value. Consumes and returns a Go primitive
func (s16 *PrimitiveS16) PAND(value int16) int16 {
return s16.Value & value
}
// OR runs a bitwise OR operation on the PrimitiveS16 value. Consumes and returns a NEX primitive
func (s16 *PrimitiveS16) OR(other *PrimitiveS16) *PrimitiveS16 {
return NewPrimitiveS16(s16.POR(other.Value))
}
// POR (Primitive OR) runs a bitwise OR operation on the PrimitiveS16 value. Consumes and returns a Go primitive
func (s16 *PrimitiveS16) POR(value int16) int16 {
return s16.Value | value
}
// XOR runs a bitwise XOR operation on the PrimitiveS16 value. Consumes and returns a NEX primitive
func (s16 *PrimitiveS16) XOR(other *PrimitiveS16) *PrimitiveS16 {
return NewPrimitiveS16(s16.PXOR(other.Value))
}
// PXOR (Primitive XOR) runs a bitwise XOR operation on the PrimitiveS16 value. Consumes and returns a Go primitive
func (s16 *PrimitiveS16) PXOR(value int16) int16 {
return s16.Value ^ value
}
// NOT runs a bitwise NOT operation on the PrimitiveS16 value. Returns a NEX primitive
func (s16 *PrimitiveS16) NOT() *PrimitiveS16 {
return NewPrimitiveS16(s16.PNOT())
}
// PNOT (Primitive NOT) runs a bitwise NOT operation on the PrimitiveS16 value. Returns a Go primitive
func (s16 *PrimitiveS16) PNOT() int16 {
return ^s16.Value
}
// ANDNOT runs a bitwise ANDNOT operation on the PrimitiveS16 value. Consumes and returns a NEX primitive
func (s16 *PrimitiveS16) ANDNOT(other *PrimitiveS16) *PrimitiveS16 {
return NewPrimitiveS16(s16.PANDNOT(other.Value))
}
// PANDNOT (Primitive AND-NOT) runs a bitwise AND-NOT operation on the PrimitiveS16 value. Consumes and returns a Go primitive
func (s16 *PrimitiveS16) PANDNOT(value int16) int16 {
return s16.Value &^ value
}
// LShift runs a left shift operation on the PrimitiveS16 value. Consumes and returns a NEX primitive
func (s16 *PrimitiveS16) LShift(other *PrimitiveS16) *PrimitiveS16 {
return NewPrimitiveS16(s16.PLShift(other.Value))
}
// PLShift (Primitive Left Shift) runs a left shift operation on the PrimitiveS16 value. Consumes and returns a Go primitive
func (s16 *PrimitiveS16) PLShift(value int16) int16 {
return s16.Value << value
}
// RShift runs a right shift operation on the PrimitiveS16 value. Consumes and returns a NEX primitive
func (s16 *PrimitiveS16) RShift(other *PrimitiveS16) *PrimitiveS16 {
return NewPrimitiveS16(s16.PRShift(other.Value))
}
// PRShift (Primitive Right Shift) runs a right shift operation on the PrimitiveS16 value. Consumes and returns a Go primitive
func (s16 *PrimitiveS16) PRShift(value int16) int16 {
return s16.Value >> value
}
// NewPrimitiveS16 returns a new PrimitiveS16
func NewPrimitiveS16(i16 int16) *PrimitiveS16 {
return &PrimitiveS16{Value: i16}
}

View file

@ -1,119 +0,0 @@
package types
import "fmt"
// PrimitiveS32 is wrapper around a Go primitive int32 with receiver methods to conform to RVType
type PrimitiveS32 struct {
Value int32
}
// WriteTo writes the int32 to the given writable
func (s32 *PrimitiveS32) WriteTo(writable Writable) {
writable.WritePrimitiveInt32LE(s32.Value)
}
// ExtractFrom extracts the int32 from the given readable
func (s32 *PrimitiveS32) ExtractFrom(readable Readable) error {
value, err := readable.ReadPrimitiveInt32LE()
if err != nil {
return err
}
s32.Value = value
return nil
}
// Copy returns a pointer to a copy of the int32. Requires type assertion when used
func (s32 *PrimitiveS32) Copy() RVType {
return NewPrimitiveS32(s32.Value)
}
// Equals checks if the input is equal in value to the current instance
func (s32 *PrimitiveS32) Equals(o RVType) bool {
if _, ok := o.(*PrimitiveS32); !ok {
return false
}
return s32.Value == o.(*PrimitiveS32).Value
}
// String returns a string representation of the struct
func (s32 *PrimitiveS32) String() string {
return fmt.Sprintf("%d", s32.Value)
}
// AND runs a bitwise AND operation on the PrimitiveS32 value. Consumes and returns a NEX primitive
func (s32 *PrimitiveS32) AND(other *PrimitiveS32) *PrimitiveS32 {
return NewPrimitiveS32(s32.PAND(other.Value))
}
// PAND (Primitive AND) runs a bitwise AND operation on the PrimitiveS32 value. Consumes and returns a Go primitive
func (s32 *PrimitiveS32) PAND(value int32) int32 {
return s32.Value & value
}
// OR runs a bitwise OR operation on the PrimitiveS32 value. Consumes and returns a NEX primitive
func (s32 *PrimitiveS32) OR(other *PrimitiveS32) *PrimitiveS32 {
return NewPrimitiveS32(s32.POR(other.Value))
}
// POR (Primitive OR) runs a bitwise OR operation on the PrimitiveS32 value. Consumes and returns a Go primitive
func (s32 *PrimitiveS32) POR(value int32) int32 {
return s32.Value | value
}
// XOR runs a bitwise XOR operation on the PrimitiveS32 value. Consumes and returns a NEX primitive
func (s32 *PrimitiveS32) XOR(other *PrimitiveS32) *PrimitiveS32 {
return NewPrimitiveS32(s32.PXOR(other.Value))
}
// PXOR (Primitive XOR) runs a bitwise XOR operation on the PrimitiveS32 value. Consumes and returns a Go primitive
func (s32 *PrimitiveS32) PXOR(value int32) int32 {
return s32.Value ^ value
}
// NOT runs a bitwise NOT operation on the PrimitiveS32 value. Returns a NEX primitive
func (s32 *PrimitiveS32) NOT() *PrimitiveS32 {
return NewPrimitiveS32(s32.PNOT())
}
// PNOT (Primitive NOT) runs a bitwise NOT operation on the PrimitiveS32 value. Returns a Go primitive
func (s32 *PrimitiveS32) PNOT() int32 {
return ^s32.Value
}
// ANDNOT runs a bitwise ANDNOT operation on the PrimitiveS32 value. Consumes and returns a NEX primitive
func (s32 *PrimitiveS32) ANDNOT(other *PrimitiveS32) *PrimitiveS32 {
return NewPrimitiveS32(s32.PANDNOT(other.Value))
}
// PANDNOT (Primitive AND-NOT) runs a bitwise AND-NOT operation on the PrimitiveS32 value. Consumes and returns a Go primitive
func (s32 *PrimitiveS32) PANDNOT(value int32) int32 {
return s32.Value &^ value
}
// LShift runs a left shift operation on the PrimitiveS32 value. Consumes and returns a NEX primitive
func (s32 *PrimitiveS32) LShift(other *PrimitiveS32) *PrimitiveS32 {
return NewPrimitiveS32(s32.PLShift(other.Value))
}
// PLShift (Primitive Left Shift) runs a left shift operation on the PrimitiveS32 value. Consumes and returns a Go primitive
func (s32 *PrimitiveS32) PLShift(value int32) int32 {
return s32.Value << value
}
// RShift runs a right shift operation on the PrimitiveS32 value. Consumes and returns a NEX primitive
func (s32 *PrimitiveS32) RShift(other *PrimitiveS32) *PrimitiveS32 {
return NewPrimitiveS32(s32.PRShift(other.Value))
}
// PRShift (Primitive Right Shift) runs a right shift operation on the PrimitiveS32 value. Consumes and returns a Go primitive
func (s32 *PrimitiveS32) PRShift(value int32) int32 {
return s32.Value >> value
}
// NewPrimitiveS32 returns a new PrimitiveS32
func NewPrimitiveS32(i32 int32) *PrimitiveS32 {
return &PrimitiveS32{Value: i32}
}

View file

@ -1,119 +0,0 @@
package types
import "fmt"
// PrimitiveS64 is wrapper around a Go primitive int64 with receiver methods to conform to RVType
type PrimitiveS64 struct {
Value int64
}
// WriteTo writes the int64 to the given writable
func (s64 *PrimitiveS64) WriteTo(writable Writable) {
writable.WritePrimitiveInt64LE(s64.Value)
}
// ExtractFrom extracts the int64 from the given readable
func (s64 *PrimitiveS64) ExtractFrom(readable Readable) error {
value, err := readable.ReadPrimitiveInt64LE()
if err != nil {
return err
}
s64.Value = value
return nil
}
// Copy returns a pointer to a copy of the int64. Requires type assertion when used
func (s64 *PrimitiveS64) Copy() RVType {
return NewPrimitiveS64(s64.Value)
}
// Equals checks if the input is equal in value to the current instance
func (s64 *PrimitiveS64) Equals(o RVType) bool {
if _, ok := o.(*PrimitiveS64); !ok {
return false
}
return s64.Value == o.(*PrimitiveS64).Value
}
// String returns a string representation of the struct
func (s64 *PrimitiveS64) String() string {
return fmt.Sprintf("%d", s64.Value)
}
// AND runs a bitwise AND operation on the PrimitiveS64 value. Consumes and returns a NEX primitive
func (s64 *PrimitiveS64) AND(other *PrimitiveS64) *PrimitiveS64 {
return NewPrimitiveS64(s64.PAND(other.Value))
}
// PAND (Primitive AND) runs a bitwise AND operation on the PrimitiveS64 value. Consumes and returns a Go primitive
func (s64 *PrimitiveS64) PAND(value int64) int64 {
return s64.Value & value
}
// OR runs a bitwise OR operation on the PrimitiveS64 value. Consumes and returns a NEX primitive
func (s64 *PrimitiveS64) OR(other *PrimitiveS64) *PrimitiveS64 {
return NewPrimitiveS64(s64.POR(other.Value))
}
// POR (Primitive OR) runs a bitwise OR operation on the PrimitiveS64 value. Consumes and returns a Go primitive
func (s64 *PrimitiveS64) POR(value int64) int64 {
return s64.Value | value
}
// XOR runs a bitwise XOR operation on the PrimitiveS64 value. Consumes and returns a NEX primitive
func (s64 *PrimitiveS64) XOR(other *PrimitiveS64) *PrimitiveS64 {
return NewPrimitiveS64(s64.PXOR(other.Value))
}
// PXOR (Primitive XOR) runs a bitwise XOR operation on the PrimitiveS64 value. Consumes and returns a Go primitive
func (s64 *PrimitiveS64) PXOR(value int64) int64 {
return s64.Value ^ value
}
// NOT runs a bitwise NOT operation on the PrimitiveS64 value. Returns a NEX primitive
func (s64 *PrimitiveS64) NOT() *PrimitiveS64 {
return NewPrimitiveS64(s64.PNOT())
}
// PNOT (Primitive NOT) runs a bitwise NOT operation on the PrimitiveS64 value. Returns a Go primitive
func (s64 *PrimitiveS64) PNOT() int64 {
return ^s64.Value
}
// ANDNOT runs a bitwise ANDNOT operation on the PrimitiveS64 value. Consumes and returns a NEX primitive
func (s64 *PrimitiveS64) ANDNOT(other *PrimitiveS64) *PrimitiveS64 {
return NewPrimitiveS64(s64.PANDNOT(other.Value))
}
// PANDNOT (Primitive AND-NOT) runs a bitwise AND-NOT operation on the PrimitiveS64 value. Consumes and returns a Go primitive
func (s64 *PrimitiveS64) PANDNOT(value int64) int64 {
return s64.Value &^ value
}
// LShift runs a left shift operation on the PrimitiveS64 value. Consumes and returns a NEX primitive
func (s64 *PrimitiveS64) LShift(other *PrimitiveS64) *PrimitiveS64 {
return NewPrimitiveS64(s64.PLShift(other.Value))
}
// PLShift (Primitive Left Shift) runs a left shift operation on the PrimitiveS64 value. Consumes and returns a Go primitive
func (s64 *PrimitiveS64) PLShift(value int64) int64 {
return s64.Value << value
}
// RShift runs a right shift operation on the PrimitiveS64 value. Consumes and returns a NEX primitive
func (s64 *PrimitiveS64) RShift(other *PrimitiveS64) *PrimitiveS64 {
return NewPrimitiveS64(s64.PRShift(other.Value))
}
// PRShift (Primitive Right Shift) runs a right shift operation on the PrimitiveS64 value. Consumes and returns a Go primitive
func (s64 *PrimitiveS64) PRShift(value int64) int64 {
return s64.Value >> value
}
// NewPrimitiveS64 returns a new PrimitiveS64
func NewPrimitiveS64(i64 int64) *PrimitiveS64 {
return &PrimitiveS64{Value: i64}
}

View file

@ -1,119 +0,0 @@
package types
import "fmt"
// PrimitiveS8 is wrapper around a Go primitive int8 with receiver methods to conform to RVType
type PrimitiveS8 struct {
Value int8
}
// WriteTo writes the int8 to the given writable
func (s8 *PrimitiveS8) WriteTo(writable Writable) {
writable.WritePrimitiveInt8(s8.Value)
}
// ExtractFrom extracts the int8 from the given readable
func (s8 *PrimitiveS8) ExtractFrom(readable Readable) error {
value, err := readable.ReadPrimitiveInt8()
if err != nil {
return err
}
s8.Value = value
return nil
}
// Copy returns a pointer to a copy of the int8. Requires type assertion when used
func (s8 *PrimitiveS8) Copy() RVType {
return NewPrimitiveS8(s8.Value)
}
// Equals checks if the input is equal in value to the current instance
func (s8 *PrimitiveS8) Equals(o RVType) bool {
if _, ok := o.(*PrimitiveS8); !ok {
return false
}
return s8.Value == o.(*PrimitiveS8).Value
}
// String returns a string representation of the struct
func (s8 *PrimitiveS8) String() string {
return fmt.Sprintf("%d", s8.Value)
}
// AND runs a bitwise AND operation on the PrimitiveS8 value. Consumes and returns a NEX primitive
func (s8 *PrimitiveS8) AND(other *PrimitiveS8) *PrimitiveS8 {
return NewPrimitiveS8(s8.PAND(other.Value))
}
// PAND (Primitive AND) runs a bitwise AND operation on the PrimitiveS8 value. Consumes and returns a Go primitive
func (s8 *PrimitiveS8) PAND(value int8) int8 {
return s8.Value & value
}
// OR runs a bitwise OR operation on the PrimitiveS8 value. Consumes and returns a NEX primitive
func (s8 *PrimitiveS8) OR(other *PrimitiveS8) *PrimitiveS8 {
return NewPrimitiveS8(s8.POR(other.Value))
}
// POR (Primitive OR) runs a bitwise OR operation on the PrimitiveS8 value. Consumes and returns a Go primitive
func (s8 *PrimitiveS8) POR(value int8) int8 {
return s8.Value | value
}
// XOR runs a bitwise XOR operation on the PrimitiveS8 value. Consumes and returns a NEX primitive
func (s8 *PrimitiveS8) XOR(other *PrimitiveS8) *PrimitiveS8 {
return NewPrimitiveS8(s8.PXOR(other.Value))
}
// PXOR (Primitive XOR) runs a bitwise XOR operation on the PrimitiveS8 value. Consumes and returns a Go primitive
func (s8 *PrimitiveS8) PXOR(value int8) int8 {
return s8.Value ^ value
}
// NOT runs a bitwise NOT operation on the PrimitiveS8 value. Returns a NEX primitive
func (s8 *PrimitiveS8) NOT() *PrimitiveS8 {
return NewPrimitiveS8(s8.PNOT())
}
// PNOT (Primitive NOT) runs a bitwise NOT operation on the PrimitiveS8 value. Returns a Go primitive
func (s8 *PrimitiveS8) PNOT() int8 {
return ^s8.Value
}
// ANDNOT runs a bitwise ANDNOT operation on the PrimitiveS8 value. Consumes and returns a NEX primitive
func (s8 *PrimitiveS8) ANDNOT(other *PrimitiveS8) *PrimitiveS8 {
return NewPrimitiveS8(s8.PANDNOT(other.Value))
}
// PANDNOT (Primitive AND-NOT) runs a bitwise AND-NOT operation on the PrimitiveS8 value. Consumes and returns a Go primitive
func (s8 *PrimitiveS8) PANDNOT(value int8) int8 {
return s8.Value &^ value
}
// LShift runs a left shift operation on the PrimitiveS8 value. Consumes and returns a NEX primitive
func (s8 *PrimitiveS8) LShift(other *PrimitiveS8) *PrimitiveS8 {
return NewPrimitiveS8(s8.PLShift(other.Value))
}
// PLShift (Primitive Left Shift) runs a left shift operation on the PrimitiveS8 value. Consumes and returns a Go primitive
func (s8 *PrimitiveS8) PLShift(value int8) int8 {
return s8.Value << value
}
// RShift runs a right shift operation on the PrimitiveS8 value. Consumes and returns a NEX primitive
func (s8 *PrimitiveS8) RShift(other *PrimitiveS8) *PrimitiveS8 {
return NewPrimitiveS8(s8.PRShift(other.Value))
}
// PRShift (Primitive Right Shift) runs a right shift operation on the PrimitiveS8 value. Consumes and returns a Go primitive
func (s8 *PrimitiveS8) PRShift(value int8) int8 {
return s8.Value >> value
}
// NewPrimitiveS8 returns a new PrimitiveS8
func NewPrimitiveS8(i8 int8) *PrimitiveS8 {
return &PrimitiveS8{Value: i8}
}

View file

@ -1,119 +0,0 @@
package types
import "fmt"
// PrimitiveU16 is wrapper around a Go primitive uint16 with receiver methods to conform to RVType
type PrimitiveU16 struct {
Value uint16
}
// WriteTo writes the uint16 to the given writable
func (u16 *PrimitiveU16) WriteTo(writable Writable) {
writable.WritePrimitiveUInt16LE(u16.Value)
}
// ExtractFrom extracts the uint16 from the given readable
func (u16 *PrimitiveU16) ExtractFrom(readable Readable) error {
value, err := readable.ReadPrimitiveUInt16LE()
if err != nil {
return err
}
u16.Value = value
return nil
}
// Copy returns a pointer to a copy of the uint16. Requires type assertion when used
func (u16 *PrimitiveU16) Copy() RVType {
return NewPrimitiveU16(u16.Value)
}
// Equals checks if the input is equal in value to the current instance
func (u16 *PrimitiveU16) Equals(o RVType) bool {
if _, ok := o.(*PrimitiveU16); !ok {
return false
}
return u16.Value == o.(*PrimitiveU16).Value
}
// String returns a string representation of the struct
func (u16 *PrimitiveU16) String() string {
return fmt.Sprintf("%d", u16.Value)
}
// AND runs a bitwise AND operation on the PrimitiveU16 value. Consumes and returns a NEX primitive
func (u16 *PrimitiveU16) AND(other *PrimitiveU16) *PrimitiveU16 {
return NewPrimitiveU16(u16.PAND(other.Value))
}
// PAND (Primitive AND) runs a bitwise AND operation on the PrimitiveU16 value. Consumes and returns a Go primitive
func (u16 *PrimitiveU16) PAND(value uint16) uint16 {
return u16.Value & value
}
// OR runs a bitwise OR operation on the PrimitiveU16 value. Consumes and returns a NEX primitive
func (u16 *PrimitiveU16) OR(other *PrimitiveU16) *PrimitiveU16 {
return NewPrimitiveU16(u16.POR(other.Value))
}
// POR (Primitive OR) runs a bitwise OR operation on the PrimitiveU16 value. Consumes and returns a Go primitive
func (u16 *PrimitiveU16) POR(value uint16) uint16 {
return u16.Value | value
}
// XOR runs a bitwise XOR operation on the PrimitiveU16 value. Consumes and returns a NEX primitive
func (u16 *PrimitiveU16) XOR(other *PrimitiveU16) *PrimitiveU16 {
return NewPrimitiveU16(u16.PXOR(other.Value))
}
// PXOR (Primitive XOR) runs a bitwise XOR operation on the PrimitiveU16 value. Consumes and returns a Go primitive
func (u16 *PrimitiveU16) PXOR(value uint16) uint16 {
return u16.Value ^ value
}
// NOT runs a bitwise NOT operation on the PrimitiveU16 value. Returns a NEX primitive
func (u16 *PrimitiveU16) NOT() *PrimitiveU16 {
return NewPrimitiveU16(u16.PNOT())
}
// PNOT (Primitive NOT) runs a bitwise NOT operation on the PrimitiveU16 value. Returns a Go primitive
func (u16 *PrimitiveU16) PNOT() uint16 {
return ^u16.Value
}
// ANDNOT runs a bitwise ANDNOT operation on the PrimitiveU16 value. Consumes and returns a NEX primitive
func (u16 *PrimitiveU16) ANDNOT(other *PrimitiveU16) *PrimitiveU16 {
return NewPrimitiveU16(u16.PANDNOT(other.Value))
}
// PANDNOT (Primitive AND-NOT) runs a bitwise AND-NOT operation on the PrimitiveU16 value. Consumes and returns a Go primitive
func (u16 *PrimitiveU16) PANDNOT(value uint16) uint16 {
return u16.Value &^ value
}
// LShift runs a left shift operation on the PrimitiveU16 value. Consumes and returns a NEX primitive
func (u16 *PrimitiveU16) LShift(other *PrimitiveU16) *PrimitiveU16 {
return NewPrimitiveU16(u16.PLShift(other.Value))
}
// PLShift (Primitive Left Shift) runs a left shift operation on the PrimitiveU16 value. Consumes and returns a Go primitive
func (u16 *PrimitiveU16) PLShift(value uint16) uint16 {
return u16.Value << value
}
// RShift runs a right shift operation on the PrimitiveU16 value. Consumes and returns a NEX primitive
func (u16 *PrimitiveU16) RShift(other *PrimitiveU16) *PrimitiveU16 {
return NewPrimitiveU16(u16.PRShift(other.Value))
}
// PRShift (Primitive Right Shift) runs a right shift operation on the PrimitiveU16 value. Consumes and returns a Go primitive
func (u16 *PrimitiveU16) PRShift(value uint16) uint16 {
return u16.Value >> value
}
// NewPrimitiveU16 returns a new PrimitiveU16
func NewPrimitiveU16(ui16 uint16) *PrimitiveU16 {
return &PrimitiveU16{Value: ui16}
}

View file

@ -1,119 +0,0 @@
package types
import "fmt"
// PrimitiveU32 is wrapper around a Go primitive uint32 with receiver methods to conform to RVType
type PrimitiveU32 struct {
Value uint32
}
// WriteTo writes the uint32 to the given writable
func (u32 *PrimitiveU32) WriteTo(writable Writable) {
writable.WritePrimitiveUInt32LE(u32.Value)
}
// ExtractFrom extracts the uint32 from the given readable
func (u32 *PrimitiveU32) ExtractFrom(readable Readable) error {
value, err := readable.ReadPrimitiveUInt32LE()
if err != nil {
return err
}
u32.Value = value
return nil
}
// Copy returns a pointer to a copy of the uint32. Requires type assertion when used
func (u32 *PrimitiveU32) Copy() RVType {
return NewPrimitiveU32(u32.Value)
}
// Equals checks if the input is equal in value to the current instance
func (u32 *PrimitiveU32) Equals(o RVType) bool {
if _, ok := o.(*PrimitiveU32); !ok {
return false
}
return u32.Value == o.(*PrimitiveU32).Value
}
// String returns a string representation of the struct
func (u32 *PrimitiveU32) String() string {
return fmt.Sprintf("%d", u32.Value)
}
// AND runs a bitwise AND operation on the PrimitiveU32 value. Consumes and returns a NEX primitive
func (u32 *PrimitiveU32) AND(other *PrimitiveU32) *PrimitiveU32 {
return NewPrimitiveU32(u32.PAND(other.Value))
}
// PAND (Primitive AND) runs a bitwise AND operation on the PrimitiveU32 value. Consumes and returns a Go primitive
func (u32 *PrimitiveU32) PAND(value uint32) uint32 {
return u32.Value & value
}
// OR runs a bitwise OR operation on the PrimitiveU32 value. Consumes and returns a NEX primitive
func (u32 *PrimitiveU32) OR(other *PrimitiveU32) *PrimitiveU32 {
return NewPrimitiveU32(u32.POR(other.Value))
}
// POR (Primitive OR) runs a bitwise OR operation on the PrimitiveU32 value. Consumes and returns a Go primitive
func (u32 *PrimitiveU32) POR(value uint32) uint32 {
return u32.Value | value
}
// XOR runs a bitwise XOR operation on the PrimitiveU32 value. Consumes and returns a NEX primitive
func (u32 *PrimitiveU32) XOR(other *PrimitiveU32) *PrimitiveU32 {
return NewPrimitiveU32(u32.PXOR(other.Value))
}
// PXOR (Primitive XOR) runs a bitwise XOR operation on the PrimitiveU32 value. Consumes and returns a Go primitive
func (u32 *PrimitiveU32) PXOR(value uint32) uint32 {
return u32.Value ^ value
}
// NOT runs a bitwise NOT operation on the PrimitiveU32 value. Returns a NEX primitive
func (u32 *PrimitiveU32) NOT() *PrimitiveU32 {
return NewPrimitiveU32(u32.PNOT())
}
// PNOT (Primitive NOT) runs a bitwise NOT operation on the PrimitiveU32 value. Returns a Go primitive
func (u32 *PrimitiveU32) PNOT() uint32 {
return ^u32.Value
}
// ANDNOT runs a bitwise ANDNOT operation on the PrimitiveU32 value. Consumes and returns a NEX primitive
func (u32 *PrimitiveU32) ANDNOT(other *PrimitiveU32) *PrimitiveU32 {
return NewPrimitiveU32(u32.PANDNOT(other.Value))
}
// PANDNOT (Primitive AND-NOT) runs a bitwise AND-NOT operation on the PrimitiveU32 value. Consumes and returns a Go primitive
func (u32 *PrimitiveU32) PANDNOT(value uint32) uint32 {
return u32.Value &^ value
}
// LShift runs a left shift operation on the PrimitiveU32 value. Consumes and returns a NEX primitive
func (u32 *PrimitiveU32) LShift(other *PrimitiveU32) *PrimitiveU32 {
return NewPrimitiveU32(u32.PLShift(other.Value))
}
// PLShift (Primitive Left Shift) runs a left shift operation on the PrimitiveU32 value. Consumes and returns a Go primitive
func (u32 *PrimitiveU32) PLShift(value uint32) uint32 {
return u32.Value << value
}
// RShift runs a right shift operation on the PrimitiveU32 value. Consumes and returns a NEX primitive
func (u32 *PrimitiveU32) RShift(other *PrimitiveU32) *PrimitiveU32 {
return NewPrimitiveU32(u32.PRShift(other.Value))
}
// PRShift (Primitive Right Shift) runs a right shift operation on the PrimitiveU32 value. Consumes and returns a Go primitive
func (u32 *PrimitiveU32) PRShift(value uint32) uint32 {
return u32.Value >> value
}
// NewPrimitiveU32 returns a new PrimitiveU32
func NewPrimitiveU32(ui32 uint32) *PrimitiveU32 {
return &PrimitiveU32{Value: ui32}
}

View file

@ -1,119 +0,0 @@
package types
import "fmt"
// PrimitiveU64 is wrapper around a Go primitive uint64 with receiver methods to conform to RVType
type PrimitiveU64 struct {
Value uint64
}
// WriteTo writes the uint64 to the given writable
func (u64 *PrimitiveU64) WriteTo(writable Writable) {
writable.WritePrimitiveUInt64LE(u64.Value)
}
// ExtractFrom extracts the uint64 from the given readable
func (u64 *PrimitiveU64) ExtractFrom(readable Readable) error {
value, err := readable.ReadPrimitiveUInt64LE()
if err != nil {
return err
}
u64.Value = value
return nil
}
// Copy returns a pointer to a copy of the uint64. Requires type assertion when used
func (u64 *PrimitiveU64) Copy() RVType {
return NewPrimitiveU64(u64.Value)
}
// Equals checks if the input is equal in value to the current instance
func (u64 *PrimitiveU64) Equals(o RVType) bool {
if _, ok := o.(*PrimitiveU64); !ok {
return false
}
return u64.Value == o.(*PrimitiveU64).Value
}
// String returns a string representation of the struct
func (u64 *PrimitiveU64) String() string {
return fmt.Sprintf("%d", u64.Value)
}
// AND runs a bitwise AND operation on the PrimitiveU64 value. Consumes and returns a NEX primitive
func (u64 *PrimitiveU64) AND(other *PrimitiveU64) *PrimitiveU64 {
return NewPrimitiveU64(u64.PAND(other.Value))
}
// PAND (Primitive AND) runs a bitwise AND operation on the PrimitiveU64 value. Consumes and returns a Go primitive
func (u64 *PrimitiveU64) PAND(value uint64) uint64 {
return u64.Value & value
}
// OR runs a bitwise OR operation on the PrimitiveU64 value. Consumes and returns a NEX primitive
func (u64 *PrimitiveU64) OR(other *PrimitiveU64) *PrimitiveU64 {
return NewPrimitiveU64(u64.POR(other.Value))
}
// POR (Primitive OR) runs a bitwise OR operation on the PrimitiveU64 value. Consumes and returns a Go primitive
func (u64 *PrimitiveU64) POR(value uint64) uint64 {
return u64.Value | value
}
// XOR runs a bitwise XOR operation on the PrimitiveU64 value. Consumes and returns a NEX primitive
func (u64 *PrimitiveU64) XOR(other *PrimitiveU64) *PrimitiveU64 {
return NewPrimitiveU64(u64.PXOR(other.Value))
}
// PXOR (Primitive XOR) runs a bitwise XOR operation on the PrimitiveU64 value. Consumes and returns a Go primitive
func (u64 *PrimitiveU64) PXOR(value uint64) uint64 {
return u64.Value ^ value
}
// NOT runs a bitwise NOT operation on the PrimitiveU64 value. Returns a NEX primitive
func (u64 *PrimitiveU64) NOT() *PrimitiveU64 {
return NewPrimitiveU64(u64.PNOT())
}
// PNOT (Primitive NOT) runs a bitwise NOT operation on the PrimitiveU64 value. Returns a Go primitive
func (u64 *PrimitiveU64) PNOT() uint64 {
return ^u64.Value
}
// ANDNOT runs a bitwise ANDNOT operation on the PrimitiveU64 value. Consumes and returns a NEX primitive
func (u64 *PrimitiveU64) ANDNOT(other *PrimitiveU64) *PrimitiveU64 {
return NewPrimitiveU64(u64.PANDNOT(other.Value))
}
// PANDNOT (Primitive AND-NOT) runs a bitwise AND-NOT operation on the PrimitiveU64 value. Consumes and returns a Go primitive
func (u64 *PrimitiveU64) PANDNOT(value uint64) uint64 {
return u64.Value &^ value
}
// LShift runs a left shift operation on the PrimitiveU64 value. Consumes and returns a NEX primitive
func (u64 *PrimitiveU64) LShift(other *PrimitiveU64) *PrimitiveU64 {
return NewPrimitiveU64(u64.PLShift(other.Value))
}
// PLShift (Primitive Left Shift) runs a left shift operation on the PrimitiveU64 value. Consumes and returns a Go primitive
func (u64 *PrimitiveU64) PLShift(value uint64) uint64 {
return u64.Value << value
}
// RShift runs a right shift operation on the PrimitiveU64 value. Consumes and returns a NEX primitive
func (u64 *PrimitiveU64) RShift(other *PrimitiveU64) *PrimitiveU64 {
return NewPrimitiveU64(u64.PRShift(other.Value))
}
// PRShift (Primitive Right Shift) runs a right shift operation on the PrimitiveU64 value. Consumes and returns a Go primitive
func (u64 *PrimitiveU64) PRShift(value uint64) uint64 {
return u64.Value >> value
}
// NewPrimitiveU64 returns a new PrimitiveU64
func NewPrimitiveU64(ui64 uint64) *PrimitiveU64 {
return &PrimitiveU64{Value: ui64}
}

View file

@ -1,119 +0,0 @@
package types
import "fmt"
// PrimitiveU8 is wrapper around a Go primitive uint8 with receiver methods to conform to RVType
type PrimitiveU8 struct {
Value uint8
}
// WriteTo writes the uint8 to the given writable
func (u8 *PrimitiveU8) WriteTo(writable Writable) {
writable.WritePrimitiveUInt8(u8.Value)
}
// ExtractFrom extracts the uint8 from the given readable
func (u8 *PrimitiveU8) ExtractFrom(readable Readable) error {
value, err := readable.ReadPrimitiveUInt8()
if err != nil {
return err
}
u8.Value = value
return nil
}
// Copy returns a pointer to a copy of the uint8. Requires type assertion when used
func (u8 *PrimitiveU8) Copy() RVType {
return NewPrimitiveU8(u8.Value)
}
// Equals checks if the input is equal in value to the current instance
func (u8 *PrimitiveU8) Equals(o RVType) bool {
if _, ok := o.(*PrimitiveU8); !ok {
return false
}
return u8.Value == o.(*PrimitiveU8).Value
}
// String returns a string representation of the struct
func (u8 *PrimitiveU8) String() string {
return fmt.Sprintf("%d", u8.Value)
}
// AND runs a bitwise AND operation on the PrimitiveU8 value. Consumes and returns a NEX primitive
func (u8 *PrimitiveU8) AND(other *PrimitiveU8) *PrimitiveU8 {
return NewPrimitiveU8(u8.PAND(other.Value))
}
// PAND (Primitive AND) runs a bitwise AND operation on the PrimitiveU8 value. Consumes and returns a Go primitive
func (u8 *PrimitiveU8) PAND(value uint8) uint8 {
return u8.Value & value
}
// OR runs a bitwise OR operation on the PrimitiveU8 value. Consumes and returns a NEX primitive
func (u8 *PrimitiveU8) OR(other *PrimitiveU8) *PrimitiveU8 {
return NewPrimitiveU8(u8.POR(other.Value))
}
// POR (Primitive OR) runs a bitwise OR operation on the PrimitiveU8 value. Consumes and returns a Go primitive
func (u8 *PrimitiveU8) POR(value uint8) uint8 {
return u8.Value | value
}
// XOR runs a bitwise XOR operation on the PrimitiveU8 value. Consumes and returns a NEX primitive
func (u8 *PrimitiveU8) XOR(other *PrimitiveU8) *PrimitiveU8 {
return NewPrimitiveU8(u8.PXOR(other.Value))
}
// PXOR (Primitive XOR) runs a bitwise XOR operation on the PrimitiveU8 value. Consumes and returns a Go primitive
func (u8 *PrimitiveU8) PXOR(value uint8) uint8 {
return u8.Value ^ value
}
// NOT runs a bitwise NOT operation on the PrimitiveU8 value. Returns a NEX primitive
func (u8 *PrimitiveU8) NOT() *PrimitiveU8 {
return NewPrimitiveU8(u8.PNOT())
}
// PNOT (Primitive NOT) runs a bitwise NOT operation on the PrimitiveU8 value. Returns a Go primitive
func (u8 *PrimitiveU8) PNOT() uint8 {
return ^u8.Value
}
// ANDNOT runs a bitwise ANDNOT operation on the PrimitiveU8 value. Consumes and returns a NEX primitive
func (u8 *PrimitiveU8) ANDNOT(other *PrimitiveU8) *PrimitiveU8 {
return NewPrimitiveU8(u8.PANDNOT(other.Value))
}
// PANDNOT (Primitive AND-NOT) runs a bitwise AND-NOT operation on the PrimitiveU8 value. Consumes and returns a Go primitive
func (u8 *PrimitiveU8) PANDNOT(value uint8) uint8 {
return u8.Value &^ value
}
// LShift runs a left shift operation on the PrimitiveU8 value. Consumes and returns a NEX primitive
func (u8 *PrimitiveU8) LShift(other *PrimitiveU8) *PrimitiveU8 {
return NewPrimitiveU8(u8.PLShift(other.Value))
}
// PLShift (Primitive Left Shift) runs a left shift operation on the PrimitiveU8 value. Consumes and returns a Go primitive
func (u8 *PrimitiveU8) PLShift(value uint8) uint8 {
return u8.Value << value
}
// RShift runs a right shift operation on the PrimitiveU8 value. Consumes and returns a NEX primitive
func (u8 *PrimitiveU8) RShift(other *PrimitiveU8) *PrimitiveU8 {
return NewPrimitiveU8(u8.PRShift(other.Value))
}
// PRShift (Primitive Right Shift) runs a right shift operation on the PrimitiveU8 value. Consumes and returns a Go primitive
func (u8 *PrimitiveU8) PRShift(value uint8) uint8 {
return u8.Value >> value
}
// NewPrimitiveU8 returns a new PrimitiveU8
func NewPrimitiveU8(ui8 uint8) *PrimitiveU8 {
return &PrimitiveU8{Value: ui8}
}

View file

@ -2,30 +2,29 @@ package types
import (
"bytes"
"encoding/hex"
"fmt"
)
// QBuffer is an implementation of rdv::qBuffer.
// Wraps a primitive Go byte slice.
// Type alias of []byte.
// Same as Buffer but with a uint16 length field.
type QBuffer struct {
Value []byte
}
type QBuffer []byte
// WriteTo writes the []byte to the given writable
func (qb *QBuffer) WriteTo(writable Writable) {
length := len(qb.Value)
func (qb QBuffer) WriteTo(writable Writable) {
length := len(qb)
writable.WritePrimitiveUInt16LE(uint16(length))
writable.WriteUInt16LE(uint16(length))
if length > 0 {
writable.Write(qb.Value)
writable.Write(qb)
}
}
// ExtractFrom extracts the QBuffer from the given readable
func (qb *QBuffer) ExtractFrom(readable Readable) error {
length, err := readable.ReadPrimitiveUInt16LE()
length, err := readable.ReadUInt16LE()
if err != nil {
return fmt.Errorf("Failed to read NEX qBuffer length. %s", err.Error())
}
@ -35,31 +34,47 @@ func (qb *QBuffer) ExtractFrom(readable Readable) error {
return fmt.Errorf("Failed to read NEX qBuffer data. %s", err.Error())
}
qb.Value = data
*qb = data
return nil
}
// Copy returns a pointer to a copy of the qBuffer. Requires type assertion when used
func (qb *QBuffer) Copy() RVType {
return NewQBuffer(qb.Value)
func (qb QBuffer) Copy() RVType {
return NewQBuffer(qb)
}
// Equals checks if the input is equal in value to the current instance
func (qb *QBuffer) Equals(o RVType) bool {
if _, ok := o.(*QBuffer); !ok {
func (qb QBuffer) Equals(o RVType) bool {
if _, ok := o.(QBuffer); !ok {
return false
}
return bytes.Equal(qb.Value, o.(*QBuffer).Value)
return bytes.Equal(qb, o.(QBuffer))
}
// CopyRef copies the current value of the QBuffer
// and returns a pointer to the new copy
func (qb QBuffer) CopyRef() RVTypePtr {
copied := qb.Copy().(QBuffer)
return &copied
}
// Deref takes a pointer to the QBuffer
// and dereferences it to the raw value.
// Only useful when working with an instance of RVTypePtr
func (qb *QBuffer) Deref() RVType {
return *qb
}
// String returns a string representation of the struct
func (qb *QBuffer) String() string {
return fmt.Sprintf("%x", qb.Value)
func (qb QBuffer) String() string {
return hex.EncodeToString(qb)
}
// NewQBuffer returns a new QBuffer
func NewQBuffer(data []byte) *QBuffer {
return &QBuffer{Value: data}
func NewQBuffer(input []byte) QBuffer {
qb := make(QBuffer, len(input))
copy(qb, input)
return qb
}

View file

@ -8,60 +8,72 @@ import (
var errorMask = 1 << 31
// QResult is an implementation of rdv::qResult.
// Type alias of uint32.
// Determines the result of an operation.
// If the MSB is set the result is an error, otherwise success
type QResult struct {
Code uint32
}
type QResult uint32
// WriteTo writes the QResult to the given writable
func (r *QResult) WriteTo(writable Writable) {
writable.WritePrimitiveUInt32LE(r.Code)
func (r QResult) WriteTo(writable Writable) {
writable.WriteUInt32LE(uint32(r))
}
// ExtractFrom extracts the QResult from the given readable
func (r *QResult) ExtractFrom(readable Readable) error {
code, err := readable.ReadPrimitiveUInt32LE()
code, err := readable.ReadUInt32LE()
if err != nil {
return fmt.Errorf("Failed to read QResult code. %s", err.Error())
}
r.Code = code
*r = QResult(code)
return nil
}
// Copy returns a pointer to a copy of the QResult. Requires type assertion when used
func (r *QResult) Copy() RVType {
return NewQResult(r.Code)
func (r QResult) Copy() RVType {
return NewQResult(uint32(r))
}
// Equals checks if the input is equal in value to the current instance
func (r *QResult) Equals(o RVType) bool {
if _, ok := o.(*QResult); !ok {
func (r QResult) Equals(o RVType) bool {
if _, ok := o.(QResult); !ok {
return false
}
return r.Code == o.(*QResult).Code
return r == o.(QResult)
}
// CopyRef copies the current value of the QResult
// and returns a pointer to the new copy
func (r QResult) CopyRef() RVTypePtr {
copied := r.Copy().(QResult)
return &copied
}
// Deref takes a pointer to the QResult
// and dereferences it to the raw value.
// Only useful when working with an instance of RVTypePtr
func (r *QResult) Deref() RVType {
return *r
}
// IsSuccess returns true if the QResult is a success
func (r *QResult) IsSuccess() bool {
return int(r.Code)&errorMask == 0
func (r QResult) IsSuccess() bool {
return int(r)&errorMask == 0
}
// IsError returns true if the QResult is a error
func (r *QResult) IsError() bool {
return int(r.Code)&errorMask != 0
func (r QResult) IsError() bool {
return int(r)&errorMask != 0
}
// String returns a string representation of the struct
func (r *QResult) String() string {
func (r QResult) String() string {
return r.FormatToString(0)
}
// FormatToString pretty-prints the struct data using the provided indentation level
func (r *QResult) FormatToString(indentationLevel int) string {
func (r QResult) FormatToString(indentationLevel int) string {
indentationValues := strings.Repeat("\t", indentationLevel+1)
indentationEnd := strings.Repeat("\t", indentationLevel)
@ -70,9 +82,9 @@ func (r *QResult) FormatToString(indentationLevel int) string {
b.WriteString("QResult{\n")
if r.IsSuccess() {
b.WriteString(fmt.Sprintf("%scode: %d (success)\n", indentationValues, r.Code))
b.WriteString(fmt.Sprintf("%scode: %d (success)\n", indentationValues, r))
} else {
b.WriteString(fmt.Sprintf("%scode: %d (error)\n", indentationValues, r.Code))
b.WriteString(fmt.Sprintf("%scode: %d (error)\n", indentationValues, r))
}
b.WriteString(fmt.Sprintf("%s}", indentationEnd))
@ -81,16 +93,17 @@ func (r *QResult) FormatToString(indentationLevel int) string {
}
// NewQResult returns a new QResult
func NewQResult(code uint32) *QResult {
return &QResult{code}
func NewQResult(input uint32) QResult {
r := QResult(input)
return r
}
// NewQResultSuccess returns a new QResult set as a success
func NewQResultSuccess(code uint32) *QResult {
func NewQResultSuccess(code uint32) QResult {
return NewQResult(uint32(int(code) & ^errorMask))
}
// NewQResultError returns a new QResult set as an error
func NewQResultError(code uint32) *QResult {
func NewQResultError(code uint32) QResult {
return NewQResult(uint32(int(code) | errorMask))
}

View file

@ -1,6 +1,7 @@
package types
import (
"bytes"
"encoding/hex"
"errors"
"fmt"
@ -9,54 +10,60 @@ import (
)
// QUUID is an implementation of rdv::qUUID.
// Type alias of []byte.
// Encodes a UUID in little-endian byte order.
type QUUID struct {
Data []byte
}
type QUUID []byte
// WriteTo writes the QUUID to the given writable
func (qu *QUUID) WriteTo(writable Writable) {
writable.Write(qu.Data)
func (qu QUUID) WriteTo(writable Writable) {
writable.Write(qu)
}
// ExtractFrom extracts the QUUID from the given readable
func (qu *QUUID) ExtractFrom(readable Readable) error {
if readable.Remaining() < uint64(16) {
if readable.Remaining() < 16 {
return errors.New("Not enough data left to read qUUID")
}
qu.Data, _ = readable.Read(16)
*qu, _ = readable.Read(16)
return nil
}
// Copy returns a new copied instance of qUUID
func (qu *QUUID) Copy() RVType {
copied := NewQUUID()
copied.Data = make([]byte, len(qu.Data))
copy(copied.Data, qu.Data)
return copied
func (qu QUUID) Copy() RVType {
return NewQUUID(qu)
}
// Equals checks if the passed Structure contains the same data as the current instance
func (qu *QUUID) Equals(o RVType) bool {
if _, ok := o.(*QUUID); !ok {
func (qu QUUID) Equals(o RVType) bool {
if _, ok := o.(QUUID); !ok {
return false
}
return qu.GetStringValue() == (o.(*QUUID)).GetStringValue()
return bytes.Equal(qu, o.(QUUID))
}
// CopyRef copies the current value of the QUUID
// and returns a pointer to the new copy
func (qu QUUID) CopyRef() RVTypePtr {
copied := qu.Copy().(QUUID)
return &copied
}
// Deref takes a pointer to the QUUID
// and dereferences it to the raw value.
// Only useful when working with an instance of RVTypePtr
func (qu *QUUID) Deref() RVType {
return *qu
}
// String returns a string representation of the struct
func (qu *QUUID) String() string {
func (qu QUUID) String() string {
return qu.FormatToString(0)
}
// FormatToString pretty-prints the struct data using the provided indentation level
func (qu *QUUID) FormatToString(indentationLevel int) string {
func (qu QUUID) FormatToString(indentationLevel int) string {
indentationValues := strings.Repeat("\t", indentationLevel+1)
indentationEnd := strings.Repeat("\t", indentationLevel)
@ -70,10 +77,10 @@ func (qu *QUUID) FormatToString(indentationLevel int) string {
}
// GetStringValue returns the UUID encoded in the qUUID
func (qu *QUUID) GetStringValue() string {
func (qu QUUID) GetStringValue() string {
// * Create copy of the data since slices.Reverse modifies the slice in-line
data := make([]byte, len(qu.Data))
copy(data, qu.Data)
data := make([]byte, len(qu))
copy(data, qu)
if len(data) != 16 {
// * Default dummy UUID as found in WATCH_DOGS
@ -165,16 +172,15 @@ func (qu *QUUID) FromString(uuid string) error {
slices.Reverse(data[12:14])
slices.Reverse(data[14:16])
qu.Data = make([]byte, 0, 16)
copy(qu.Data, data)
*qu = data
return nil
}
// NewQUUID returns a new qUUID
func NewQUUID() *QUUID {
return &QUUID{
Data: make([]byte, 0, 16),
}
func NewQUUID(input []byte) QUUID {
qu := make(QUUID, len(input))
copy(qu, input)
return qu
}

View file

@ -2,21 +2,21 @@ package types
// Readable represents a struct that types can read from
type Readable interface {
StringLengthSize() int // Returns the size of the length field for rdv::String types. Only 2 and 4 are valid
PIDSize() int // Returns the size of the length fields for nn::nex::PID types. Only 4 and 8 are valid
UseStructureHeader() bool // Returns whether or not Structure types should use a header
Remaining() uint64 // Returns the number of bytes left unread in the buffer
ReadRemaining() []byte // Reads the remaining data from the buffer
Read(length uint64) ([]byte, error) // Reads up to length bytes of data from the buffer. Returns an error if the read failed, such as if there was not enough data to read
ReadPrimitiveUInt8() (uint8, error) // Reads a primitive Go uint8. Returns an error if the read failed, such as if there was not enough data to read
ReadPrimitiveUInt16LE() (uint16, error) // Reads a primitive Go uint16. Returns an error if the read failed, such as if there was not enough data to read
ReadPrimitiveUInt32LE() (uint32, error) // Reads a primitive Go uint32. Returns an error if the read failed, such as if there was not enough data to read
ReadPrimitiveUInt64LE() (uint64, error) // Reads a primitive Go uint64. Returns an error if the read failed, such as if there was not enough data to read
ReadPrimitiveInt8() (int8, error) // Reads a primitive Go int8. Returns an error if the read failed, such as if there was not enough data to read
ReadPrimitiveInt16LE() (int16, error) // Reads a primitive Go int16. Returns an error if the read failed, such as if there was not enough data to read
ReadPrimitiveInt32LE() (int32, error) // Reads a primitive Go int32. Returns an error if the read failed, such as if there was not enough data to read
ReadPrimitiveInt64LE() (int64, error) // Reads a primitive Go int64. Returns an error if the read failed, such as if there was not enough data to read
ReadPrimitiveFloat32LE() (float32, error) // Reads a primitive Go float32. Returns an error if the read failed, such as if there was not enough data to read
ReadPrimitiveFloat64LE() (float64, error) // Reads a primitive Go float64. Returns an error if the read failed, such as if there was not enough data to read
ReadPrimitiveBool() (bool, error) // Reads a primitive Go bool. Returns an error if the read failed, such as if there was not enough data to read
StringLengthSize() int // Returns the size of the length field for rdv::String types. Only 2 and 4 are valid
PIDSize() int // Returns the size of the length fields for nn::nex::PID types. Only 4 and 8 are valid
UseStructureHeader() bool // Returns whether or not Structure types should use a header
Remaining() uint64 // Returns the number of bytes left unread in the buffer
ReadRemaining() []byte // Reads the remaining data from the buffer
Read(length uint64) ([]byte, error) // Reads up to length bytes of data from the buffer. Returns an error if the read failed, such as if there was not enough data to read
ReadUInt8() (uint8, error) // Reads a primitive Go uint8. Returns an error if the read failed, such as if there was not enough data to read
ReadUInt16LE() (uint16, error) // Reads a primitive Go uint16. Returns an error if the read failed, such as if there was not enough data to read
ReadUInt32LE() (uint32, error) // Reads a primitive Go uint32. Returns an error if the read failed, such as if there was not enough data to read
ReadUInt64LE() (uint64, error) // Reads a primitive Go uint64. Returns an error if the read failed, such as if there was not enough data to read
ReadInt8() (int8, error) // Reads a primitive Go int8. Returns an error if the read failed, such as if there was not enough data to read
ReadInt16LE() (int16, error) // Reads a primitive Go int16. Returns an error if the read failed, such as if there was not enough data to read
ReadInt32LE() (int32, error) // Reads a primitive Go int32. Returns an error if the read failed, such as if there was not enough data to read
ReadInt64LE() (int64, error) // Reads a primitive Go int64. Returns an error if the read failed, such as if there was not enough data to read
ReadFloat32LE() (float32, error) // Reads a primitive Go float32. Returns an error if the read failed, such as if there was not enough data to read
ReadFloat64LE() (float64, error) // Reads a primitive Go float64. Returns an error if the read failed, such as if there was not enough data to read
ReadBool() (bool, error) // Reads a primitive Go bool. Returns an error if the read failed, such as if there was not enough data to read
}

View file

@ -9,12 +9,12 @@ import (
// Holds information about how to make queries which may return large data.
type ResultRange struct {
Structure
Offset *PrimitiveU32 // Offset into the dataset
Length *PrimitiveU32 // Number of items to return
Offset UInt32 `json:"offset" db:"offset" bson:"offset" xml:"Offset"` // * Offset into the dataset
Length UInt32 `json:"length" db:"length" bson:"length" xml:"Length"` // * Number of items to return
}
// WriteTo writes the ResultRange to the given writable
func (rr *ResultRange) WriteTo(writable Writable) {
func (rr ResultRange) WriteTo(writable Writable) {
contentWritable := writable.CopyNew()
rr.Offset.WriteTo(contentWritable)
@ -49,42 +49,56 @@ func (rr *ResultRange) ExtractFrom(readable Readable) error {
}
// Copy returns a new copied instance of ResultRange
func (rr *ResultRange) Copy() RVType {
func (rr ResultRange) Copy() RVType {
copied := NewResultRange()
copied.StructureVersion = rr.StructureVersion
copied.Offset = rr.Offset.Copy().(*PrimitiveU32)
copied.Length = rr.Length.Copy().(*PrimitiveU32)
copied.Offset = rr.Offset.Copy().(UInt32)
copied.Length = rr.Length.Copy().(UInt32)
return copied
}
// Equals checks if the input is equal in value to the current instance
func (rr *ResultRange) Equals(o RVType) bool {
if _, ok := o.(*ResultRange); !ok {
func (rr ResultRange) Equals(o RVType) bool {
if _, ok := o.(ResultRange); !ok {
return false
}
other := o.(*ResultRange)
other := o.(ResultRange)
if rr.StructureVersion != other.StructureVersion {
return false
}
if !rr.Offset.Equals(other.Offset) {
if !rr.Offset.Equals(&other.Offset) {
return false
}
return rr.Length.Equals(other.Length)
return rr.Length.Equals(&other.Length)
}
// CopyRef copies the current value of the ResultRange
// and returns a pointer to the new copy
func (rr ResultRange) CopyRef() RVTypePtr {
copied := rr.Copy().(ResultRange)
return &copied
}
// Deref takes a pointer to the ResultRange
// and dereferences it to the raw value.
// Only useful when working with an instance of RVTypePtr
func (rr *ResultRange) Deref() RVType {
return *rr
}
// String returns a string representation of the struct
func (rr *ResultRange) String() string {
func (rr ResultRange) String() string {
return rr.FormatToString(0)
}
// FormatToString pretty-prints the struct data using the provided indentation level
func (rr *ResultRange) FormatToString(indentationLevel int) string {
func (rr ResultRange) FormatToString(indentationLevel int) string {
indentationValues := strings.Repeat("\t", indentationLevel+1)
indentationEnd := strings.Repeat("\t", indentationLevel)
@ -100,9 +114,9 @@ func (rr *ResultRange) FormatToString(indentationLevel int) string {
}
// NewResultRange returns a new ResultRange
func NewResultRange() *ResultRange {
return &ResultRange{
Offset: NewPrimitiveU32(0),
Length: NewPrimitiveU32(0),
func NewResultRange() ResultRange {
return ResultRange{
Offset: NewUInt32(0),
Length: NewUInt32(0),
}
}

View file

@ -9,14 +9,14 @@ import (
// Contains the locations and data of Rendez-Vous connection.
type RVConnectionData struct {
Structure
StationURL *StationURL
SpecialProtocols *List[*PrimitiveU8]
StationURLSpecialProtocols *StationURL
Time *DateTime
StationURL StationURL `json:"station_url" db:"station_url" bson:"station_url" xml:"StationURL"`
SpecialProtocols List[UInt8] `json:"special_protocols" db:"special_protocols" bson:"special_protocols" xml:"SpecialProtocols"`
StationURLSpecialProtocols StationURL `json:"station_url_special_protocols" db:"station_url_special_protocols" bson:"station_url_special_protocols" xml:"StationURLSpecialProtocols"`
Time DateTime `json:"time" db:"time" bson:"time" xml:"Time"`
}
// WriteTo writes the RVConnectionData to the given writable
func (rvcd *RVConnectionData) WriteTo(writable Writable) {
func (rvcd RVConnectionData) WriteTo(writable Writable) {
contentWritable := writable.CopyNew()
rvcd.StationURL.WriteTo(contentWritable)
@ -67,28 +67,28 @@ func (rvcd *RVConnectionData) ExtractFrom(readable Readable) error {
}
// Copy returns a new copied instance of RVConnectionData
func (rvcd *RVConnectionData) Copy() RVType {
func (rvcd RVConnectionData) Copy() RVType {
copied := NewRVConnectionData()
copied.StructureVersion = rvcd.StructureVersion
copied.StationURL = rvcd.StationURL.Copy().(*StationURL)
copied.SpecialProtocols = rvcd.SpecialProtocols.Copy().(*List[*PrimitiveU8])
copied.StationURLSpecialProtocols = rvcd.StationURLSpecialProtocols.Copy().(*StationURL)
copied.StationURL = rvcd.StationURL.Copy().(StationURL)
copied.SpecialProtocols = rvcd.SpecialProtocols.Copy().(List[UInt8])
copied.StationURLSpecialProtocols = rvcd.StationURLSpecialProtocols.Copy().(StationURL)
if rvcd.StructureVersion >= 1 {
copied.Time = rvcd.Time.Copy().(*DateTime)
copied.Time = *rvcd.Time.Copy().(*DateTime)
}
return copied
}
// Equals checks if the input is equal in value to the current instance
func (rvcd *RVConnectionData) Equals(o RVType) bool {
if _, ok := o.(*RVConnectionData); !ok {
func (rvcd RVConnectionData) Equals(o RVType) bool {
if _, ok := o.(RVConnectionData); !ok {
return false
}
other := o.(*RVConnectionData)
other := o.(RVConnectionData)
if rvcd.StructureVersion != other.StructureVersion {
return false
@ -107,21 +107,33 @@ func (rvcd *RVConnectionData) Equals(o RVType) bool {
}
if rvcd.StructureVersion >= 1 {
if !rvcd.Time.Equals(other.Time) {
return false
}
return rvcd.Time.Equals(other.Time)
}
return true
}
// CopyRef copies the current value of the RVConnectionData
// and returns a pointer to the new copy
func (rvcd RVConnectionData) CopyRef() RVTypePtr {
copied := rvcd.Copy().(RVConnectionData)
return &copied
}
// Deref takes a pointer to the RVConnectionData
// and dereferences it to the raw value.
// Only useful when working with an instance of RVTypePtr
func (rvcd *RVConnectionData) Deref() RVType {
return *rvcd
}
// String returns a string representation of the struct
func (rvcd *RVConnectionData) String() string {
func (rvcd RVConnectionData) String() string {
return rvcd.FormatToString(0)
}
// FormatToString pretty-prints the struct data using the provided indentation level
func (rvcd *RVConnectionData) FormatToString(indentationLevel int) string {
func (rvcd RVConnectionData) FormatToString(indentationLevel int) string {
indentationValues := strings.Repeat("\t", indentationLevel+1)
indentationEnd := strings.Repeat("\t", indentationLevel)
@ -139,15 +151,13 @@ func (rvcd *RVConnectionData) FormatToString(indentationLevel int) string {
}
// NewRVConnectionData returns a new RVConnectionData
func NewRVConnectionData() *RVConnectionData {
rvcd := &RVConnectionData{
func NewRVConnectionData() RVConnectionData {
rvcd := RVConnectionData{
StationURL: NewStationURL(""),
SpecialProtocols: NewList[*PrimitiveU8](),
SpecialProtocols: NewList[UInt8](),
StationURLSpecialProtocols: NewStationURL(""),
Time: NewDateTime(0),
}
rvcd.SpecialProtocols.Type = NewPrimitiveU8(0)
return rvcd
}

View file

@ -4,8 +4,16 @@ package types
// RVType represents a Quazal Rendez-Vous/NEX type.
// This includes primitives and custom types.
type RVType interface {
WriteTo(writable Writable)
ExtractFrom(readable Readable) error
Copy() RVType
Equals(other RVType) bool
WriteTo(writable Writable) // Writes the type data to the given Writable stream
Copy() RVType // Returns a non-pointer copy of the type data. Complex types are deeply copied
CopyRef() RVTypePtr // Returns a pointer to a copy of the type data. Complex types are deeply copied. Useful for obtaining a pointer without reflection, though limited to copies
Equals(other RVType) bool // Checks if the input type is strictly equal to the current type
}
// RVTypePtr represents a pointer to an RVType.
// Used to separate pointer receivers for easier type checking.
type RVTypePtr interface {
RVType
ExtractFrom(readable Readable) error // Reads the type data to the given Readable stream
Deref() RVType // Returns the raw type data from a pointer. Useful for ensuring you have raw data without reflection
}

View file

@ -12,12 +12,24 @@ import (
//
// Contains location of a station to connect to, with data about how to connect.
type StationURL struct {
urlType constants.StationURLType
flags uint8
params map[string]string
urlType constants.StationURLType
url string
flags uint8
standardParams map[string]string
customParams map[string]string
}
func (s *StationURL) numberParamValue(name string, bits int) (uint64, bool) {
func (s *StationURL) ensureFields() {
if s.standardParams == nil {
s.standardParams = make(map[string]string)
}
if s.customParams == nil {
s.customParams = make(map[string]string)
}
}
func (s StationURL) numberParamValue(name string, bits int) (uint64, bool) {
valueString, ok := s.ParamValue(name)
if !ok {
return 0, false
@ -31,7 +43,7 @@ func (s *StationURL) numberParamValue(name string, bits int) (uint64, bool) {
return value, true
}
func (s *StationURL) uint8ParamValue(name string) (uint8, bool) {
func (s StationURL) uint8ParamValue(name string) (uint8, bool) {
value, ok := s.numberParamValue(name, 8)
if !ok {
return 0, false
@ -40,7 +52,7 @@ func (s *StationURL) uint8ParamValue(name string) (uint8, bool) {
return uint8(value), true
}
func (s *StationURL) uint16ParamValue(name string) (uint16, bool) {
func (s StationURL) uint16ParamValue(name string) (uint16, bool) {
value, ok := s.numberParamValue(name, 16)
if !ok {
return 0, false
@ -49,7 +61,7 @@ func (s *StationURL) uint16ParamValue(name string) (uint16, bool) {
return uint16(value), true
}
func (s *StationURL) uint32ParamValue(name string) (uint32, bool) {
func (s StationURL) uint32ParamValue(name string) (uint32, bool) {
value, ok := s.numberParamValue(name, 32)
if !ok {
return 0, false
@ -58,11 +70,11 @@ func (s *StationURL) uint32ParamValue(name string) (uint32, bool) {
return uint32(value), true
}
func (s *StationURL) uint64ParamValue(name string) (uint64, bool) {
func (s StationURL) uint64ParamValue(name string) (uint64, bool) {
return s.numberParamValue(name, 64)
}
func (s *StationURL) boolParamValue(name string) bool {
func (s StationURL) boolParamValue(name string) bool {
valueString, ok := s.ParamValue(name)
if !ok {
return false
@ -72,37 +84,40 @@ func (s *StationURL) boolParamValue(name string) bool {
}
// WriteTo writes the StationURL to the given writable
func (s *StationURL) WriteTo(writable Writable) {
str := NewString(s.EncodeToString())
func (s StationURL) WriteTo(writable Writable) {
url := NewString(s.URL())
str.WriteTo(writable)
url.WriteTo(writable)
}
// ExtractFrom extracts the StationURL from the given readable
func (s *StationURL) ExtractFrom(readable Readable) error {
str := NewString("")
s.ensureFields()
if err := str.ExtractFrom(readable); err != nil {
url := NewString("")
if err := url.ExtractFrom(readable); err != nil {
return fmt.Errorf("Failed to read StationURL. %s", err.Error())
}
s.FromString(str.Value)
s.SetURL(string(url))
s.Parse()
return nil
}
// Copy returns a new copied instance of StationURL
func (s *StationURL) Copy() RVType {
return NewStationURL(s.EncodeToString())
func (s StationURL) Copy() RVType {
return NewStationURL(String(s.URL()))
}
// Equals checks if the input is equal in value to the current instance
func (s *StationURL) Equals(o RVType) bool {
if _, ok := o.(*StationURL); !ok {
func (s StationURL) Equals(o RVType) bool {
if _, ok := o.(StationURL); !ok {
return false
}
other := o.(*StationURL)
other := o.(StationURL)
if s.urlType != other.urlType {
return false
@ -112,12 +127,12 @@ func (s *StationURL) Equals(o RVType) bool {
return false
}
if len(s.params) != len(other.params) {
if len(s.standardParams) != len(other.standardParams) {
return false
}
for key, value1 := range s.params {
value2, ok := other.params[key]
for key, value1 := range s.standardParams {
value2, ok := other.standardParams[key]
if !ok || value1 != value2 {
return false
}
@ -126,16 +141,64 @@ func (s *StationURL) Equals(o RVType) bool {
return true
}
// CopyRef copies the current value of the StationURL
// and returns a pointer to the new copy
func (s StationURL) CopyRef() RVTypePtr {
copied := s.Copy().(StationURL)
return &copied
}
// Deref takes a pointer to the StationURL
// and dereferences it to the raw value.
// Only useful when working with an instance of RVTypePtr
func (s *StationURL) Deref() RVType {
return *s
}
// Set sets a StationURL parameter.
//
// "custom" determines whether or not the parameter is a standard
// parameter or an application-specific parameter
func (s *StationURL) Set(name, value string, custom bool) {
if custom {
s.customParams[name] = value
} else {
s.standardParams[name] = value
}
}
// Get returns the value of the requested param.
//
// Returns the string value and a bool indicating if the value existed or not.
//
// "custom" determines whether or not the parameter is a standard
// parameter or an application-specific parameter
func (s *StationURL) Get(name string, custom bool) (string, bool) {
var m map[string]string
if custom {
m = s.customParams
} else {
m = s.standardParams
}
if value, ok := m[name]; ok {
return value, true
}
return "", false
}
// SetParamValue sets a StationURL parameter
func (s *StationURL) SetParamValue(name, value string) {
s.params[name] = value
s.standardParams[name] = value
}
// RemoveParam removes a StationURL parameter.
//
// Originally called nn::nex::StationURL::Remove
func (s *StationURL) RemoveParam(name string) {
delete(s.params, name)
delete(s.standardParams, name)
}
// ParamValue returns the value of the requested param.
@ -143,8 +206,8 @@ func (s *StationURL) RemoveParam(name string) {
// Returns the string value and a bool indicating if the value existed or not.
//
// Originally called nn::nex::StationURL::GetParamValue
func (s *StationURL) ParamValue(name string) (string, bool) {
if value, ok := s.params[name]; ok {
func (s StationURL) ParamValue(name string) (string, bool) {
if value, ok := s.standardParams[name]; ok {
return value, true
}
@ -159,7 +222,7 @@ func (s *StationURL) SetAddress(address string) {
// Address gets the stations IP address.
//
// Originally called nn::nex::StationURL::GetAddress
func (s *StationURL) Address() (string, bool) {
func (s StationURL) Address() (string, bool) {
return s.ParamValue("address")
}
@ -185,7 +248,7 @@ func (s *StationURL) SetURLType(urlType constants.StationURLType) {
// URLType returns the stations scheme type
//
// Originally called nn::nex::StationURL::GetURLType
func (s *StationURL) URLType() constants.StationURLType {
func (s StationURL) URLType() constants.StationURLType {
return s.urlType
}
@ -203,7 +266,7 @@ func (s *StationURL) SetStreamID(streamID uint8) {
// Returns a bool indicating if the parameter existed or not.
//
// Originally called nn::nex::StationURL::GetStreamID
func (s *StationURL) StreamID() (uint8, bool) {
func (s StationURL) StreamID() (uint8, bool) {
return s.uint8ParamValue("sid")
}
@ -221,7 +284,7 @@ func (s *StationURL) SetStreamType(streamType constants.StreamType) {
// Returns a bool indicating if the parameter existed or not.
//
// Originally called nn::nex::StationURL::GetStreamType
func (s *StationURL) StreamType() (constants.StreamType, bool) {
func (s StationURL) StreamType() (constants.StreamType, bool) {
streamType, ok := s.uint8ParamValue("stream")
// TODO - Range check on the enum?
@ -241,13 +304,13 @@ func (s *StationURL) SetNodeID(nodeID uint16) {
// Returns a bool indicating if the parameter existed or not.
//
// Originally called nn::nex::StationURL::GetNodeId
func (s *StationURL) NodeID() (uint16, bool) {
func (s StationURL) NodeID() (uint16, bool) {
return s.uint16ParamValue("NodeID")
}
// SetPrincipalID sets the stations target PID
func (s *StationURL) SetPrincipalID(pid *PID) {
s.SetParamValue("PID", strconv.FormatUint(pid.Value(), 10))
func (s *StationURL) SetPrincipalID(pid PID) {
s.SetParamValue("PID", strconv.FormatUint(uint64(pid), 10))
}
// PrincipalID gets the stations target PID.
@ -255,10 +318,10 @@ func (s *StationURL) SetPrincipalID(pid *PID) {
// Returns a bool indicating if the parameter existed or not.
//
// Originally called nn::nex::StationURL::GetPrincipalID
func (s *StationURL) PrincipalID() (*PID, bool) {
func (s StationURL) PrincipalID() (PID, bool) {
pid, ok := s.uint64ParamValue("PID")
if !ok {
return nil, false
return NewPID(0), false
}
return NewPID(pid), true
@ -276,7 +339,7 @@ func (s *StationURL) SetConnectionID(connectionID uint32) {
// Returns a bool indicating if the parameter existed or not.
//
// Originally called nn::nex::StationURL::GetConnectionID
func (s *StationURL) ConnectionID() (uint32, bool) {
func (s StationURL) ConnectionID() (uint32, bool) {
return s.uint32ParamValue("CID")
}
@ -292,7 +355,7 @@ func (s *StationURL) SetRVConnectionID(connectionID uint32) {
// Returns a bool indicating if the parameter existed or not.
//
// Originally called nn::nex::StationURL::GetRVConnectionID
func (s *StationURL) RVConnectionID() (uint32, bool) {
func (s StationURL) RVConnectionID() (uint32, bool) {
return s.uint32ParamValue("RVCID")
}
@ -306,7 +369,7 @@ func (s *StationURL) SetProbeRequestID(probeRequestID uint32) {
// Returns a bool indicating if the parameter existed or not.
//
// Originally called nn::nex::StationURL::GetProbeRequestID
func (s *StationURL) ProbeRequestID() (uint32, bool) {
func (s StationURL) ProbeRequestID() (uint32, bool) {
return s.uint32ParamValue("PRID")
}
@ -322,7 +385,7 @@ func (s *StationURL) SetFastProbeResponse(fast bool) {
// IsFastProbeResponseEnabled checks if fast probe response is enabled
//
// Originally called nn::nex::StationURL::GetFastProbeResponse
func (s *StationURL) IsFastProbeResponseEnabled() bool {
func (s StationURL) IsFastProbeResponseEnabled() bool {
return s.boolParamValue("fastproberesponse")
}
@ -336,7 +399,7 @@ func (s *StationURL) SetNATMapping(mapping constants.NATMappingProperties) {
// Returns a bool indicating if the parameter existed or not.
//
// Originally called nn::nex::StationURL::GetNATMapping
func (s *StationURL) NATMapping() (constants.NATMappingProperties, bool) {
func (s StationURL) NATMapping() (constants.NATMappingProperties, bool) {
natm, ok := s.uint8ParamValue("natm")
// TODO - Range check on the enum?
@ -354,7 +417,7 @@ func (s *StationURL) SetNATFiltering(filtering constants.NATFilteringProperties)
// Returns a bool indicating if the parameter existed or not.
//
// Originally called nn::nex::StationURL::GetNATFiltering
func (s *StationURL) NATFiltering() (constants.NATFilteringProperties, bool) {
func (s StationURL) NATFiltering() (constants.NATFilteringProperties, bool) {
natf, ok := s.uint8ParamValue("natf")
// TODO - Range check on the enum?
@ -374,7 +437,7 @@ func (s *StationURL) SetProbeRequestInitiation(probeinit bool) {
// IsProbeRequestInitiationEnabled checks wheteher probing should be initiated.
//
// Originally called nn::nex::StationURL::GetProbeRequestInitiation
func (s *StationURL) IsProbeRequestInitiationEnabled() bool {
func (s StationURL) IsProbeRequestInitiationEnabled() bool {
return s.boolParamValue("probeinit")
}
@ -390,7 +453,7 @@ func (s *StationURL) SetUPnPSupport(supported bool) {
// IsUPnPSupported checks whether UPnP is enabled on the station.
//
// Originally called nn::nex::StationURL::GetUPnPSupport
func (s *StationURL) IsUPnPSupported() bool {
func (s StationURL) IsUPnPSupported() bool {
return s.boolParamValue("upnp")
}
@ -408,10 +471,23 @@ func (s *StationURL) SetNATPMPSupport(supported bool) {
// IsNATPMPSupported checks whether PMP is enabled on the station.
//
// Originally called nn::nex::StationURL::GetNatPMPSupport
func (s *StationURL) IsNATPMPSupported() bool {
func (s StationURL) IsNATPMPSupported() bool {
return s.boolParamValue("pmp")
}
// SetURL sets the internal url string used for parsing
func (s *StationURL) SetURL(url string) {
s.url = url
}
// URL returns the string formatted URL.
//
// Originally called nn::nex::StationURL::GetURL
func (s StationURL) URL() string {
s.Format()
return s.url
}
// SetType sets the stations type flags
func (s *StationURL) SetType(flags uint8) {
s.flags = flags // * This normally isn't done, but makes IsPublic and IsBehindNAT simpler
@ -423,7 +499,7 @@ func (s *StationURL) SetType(flags uint8) {
// Returns a bool indicating if the parameter existed or not.
//
// Originally called nn::nex::StationURL::GetType
func (s *StationURL) Type() (uint8, bool) {
func (s StationURL) Type() (uint8, bool) {
return s.uint8ParamValue("type")
}
@ -435,7 +511,7 @@ func (s *StationURL) SetRelayServerAddress(address string) {
// RelayServerAddress gets the address for the relay server
//
// Originally called nn::nex::StationURL::GetRelayServerAddress
func (s *StationURL) RelayServerAddress() (string, bool) {
func (s StationURL) RelayServerAddress() (string, bool) {
return s.ParamValue("Rsa")
}
@ -449,7 +525,7 @@ func (s *StationURL) SetRelayServerPort(port uint16) {
// Returns a bool indicating if the parameter existed or not.
//
// Originally called nn::nex::StationURL::GetRelayServerPort
func (s *StationURL) RelayServerPort() (uint16, bool) {
func (s StationURL) RelayServerPort() (uint16, bool) {
return s.uint16ParamValue("Rsp")
}
@ -461,7 +537,7 @@ func (s *StationURL) SetRelayAddress(address string) {
// RelayAddress gets the address for the relay
//
// Originally called nn::nex::StationURL::GetRelayAddress
func (s *StationURL) RelayAddress() (string, bool) {
func (s StationURL) RelayAddress() (string, bool) {
return s.ParamValue("Ra")
}
@ -475,7 +551,7 @@ func (s *StationURL) SetRelayPort(port uint16) {
// Returns a bool indicating if the parameter existed or not.
//
// Originally called nn::nex::StationURL::GetRelayPort
func (s *StationURL) RelayPort() (uint16, bool) {
func (s StationURL) RelayPort() (uint16, bool) {
return s.uint16ParamValue("Rp")
}
@ -491,7 +567,7 @@ func (s *StationURL) SetUseRelayServer(useRelayServer bool) {
// IsRelayServerEnabled checks whether the connection should use a relay server.
//
// Originally called nn::nex::StationURL::GetUseRelayServer
func (s *StationURL) IsRelayServerEnabled() bool {
func (s StationURL) IsRelayServerEnabled() bool {
return s.boolParamValue("R")
}
@ -508,64 +584,92 @@ func (s *StationURL) SetPlatformType(platformType uint8) {
// Returns a bool indicating if the parameter existed or not.
//
// Originally called nn::nex::StationURL::GetPlatformType
func (s *StationURL) PlatformType() (uint8, bool) {
func (s StationURL) PlatformType() (uint8, bool) {
return s.uint8ParamValue("Pl")
}
// IsPublic checks if the station is a public address
func (s *StationURL) IsPublic() bool {
func (s StationURL) IsPublic() bool {
return s.flags&uint8(constants.StationURLFlagPublic) == uint8(constants.StationURLFlagPublic)
}
// IsBehindNAT checks if the user is behind NAT
func (s *StationURL) IsBehindNAT() bool {
func (s StationURL) IsBehindNAT() bool {
return s.flags&uint8(constants.StationURLFlagBehindNAT) == uint8(constants.StationURLFlagBehindNAT)
}
// FromString parses the StationURL data from a string
func (s *StationURL) FromString(str string) {
if str == "" {
// Parse parses the StationURL data from a string
func (s *StationURL) Parse() {
url := s.url
if url == "" || len(url) > 1024 {
// TODO - Should we return an error here?
return
}
parts := strings.Split(str, ":/")
parametersString := ""
parts := strings.SplitN(string(url), ":/", 2)
// * Unknown schemes seem to be supported based on
// * Format__Q3_2nn3nex10StationURLFv
if len(parts) == 1 {
parametersString = parts[0]
s.SetURLType(constants.UnknownStationURLType)
} else if len(parts) == 2 {
scheme := parts[0]
parametersString = parts[1]
if scheme == "prudp" {
s.SetURLType(constants.StationURLPRUDP)
} else if scheme == "prudps" {
s.SetURLType(constants.StationURLPRUDPS)
} else if scheme == "udp" {
s.SetURLType(constants.StationURLUDP)
} else {
s.SetURLType(constants.UnknownStationURLType)
}
} else {
// * Badly formatted station
// * Unknown schemes are disallowed to be parsed
// * according to Parse__Q3_2nn3nex10StationURLFv
if len(parts) != 2 {
return
}
scheme := parts[0]
parametersString := parts[1]
switch scheme {
case "prudp":
s.SetURLType(constants.StationURLPRUDP)
case "prudps":
s.SetURLType(constants.StationURLPRUDPS)
case "udp":
s.SetURLType(constants.StationURLUDP)
default:
return // * Unknown scheme
}
// * Return if there are no fields
if parametersString == "" {
return
}
parameters := strings.Split(parametersString, ";")
parts = strings.SplitN(parametersString, "#", 2)
standardSection := parts[0]
customSection := ""
for i := 0; i < len(parameters); i++ {
// TODO - StationURL parameters support extra data through the # delimiter. What is that? Need to support it somehow
name, value, _ := strings.Cut(parameters[i], "=")
if len(parts) == 2 {
customSection = parts[1]
}
s.SetParamValue(name, value)
standardParameters := strings.Split(standardSection, ";")
for i := range standardParameters {
key, value, _ := strings.Cut(standardParameters[i], "=")
if key == "address" && len(value) > 256 {
// * The client can only hold a host name of up to 256 characters
// TODO - Should we return an error here?
return
}
if key == "port" {
if port, err := strconv.Atoi(value); err != nil || (port < 0 || port > 65535) {
// TODO - Should we return an error here?
return
}
}
s.Set(key, value, false)
}
if len(customSection) != 0 {
customParameters := strings.Split(customSection, ";")
for i := range customParameters {
key, value, _ := strings.Cut(customParameters[i], "=")
s.Set(key, value, true)
}
}
if flags, ok := s.uint8ParamValue("type"); ok {
@ -573,8 +677,8 @@ func (s *StationURL) FromString(str string) {
}
}
// EncodeToString encodes the StationURL into a string
func (s *StationURL) EncodeToString() string {
// Format encodes the StationURL into a string
func (s *StationURL) Format() {
scheme := ""
// * Unknown schemes seem to be supported based on
@ -589,40 +693,71 @@ func (s *StationURL) EncodeToString() string {
fields := make([]string, 0)
for key, value := range s.params {
// TODO - StationURL parameters support extra data through the # delimiter. What is that? Need to support it somehow
for key, value := range s.standardParams {
if key == "address" && len(value) > 256 {
// * The client can only hold a host name of up to 256 characters
// TODO - Should we return an error here?
return
}
if key == "port" {
if port, err := strconv.Atoi(value); err != nil || (port < 0 || port > 65535) {
// TODO - Should we return an error here?
return
}
}
fields = append(fields, fmt.Sprintf("%s=%s", key, value))
}
return scheme + strings.Join(fields, ";")
url := scheme + strings.Join(fields, ";")
if len(s.customParams) != 0 {
customFields := make([]string, 0)
for key, value := range s.customParams {
customFields = append(customFields, fmt.Sprintf("%s=%s", key, value))
}
url = url + "#" + strings.Join(customFields, ";")
}
if len(url) > 1024 {
// TODO - Should we return an error here?
return
}
s.url = url
}
// String returns a string representation of the struct
func (s *StationURL) String() string {
func (s StationURL) String() string {
return s.FormatToString(0)
}
// FormatToString pretty-prints the struct data using the provided indentation level
func (s *StationURL) FormatToString(indentationLevel int) string {
func (s StationURL) FormatToString(indentationLevel int) string {
indentationValues := strings.Repeat("\t", indentationLevel+1)
indentationEnd := strings.Repeat("\t", indentationLevel)
var b strings.Builder
b.WriteString("StationURL{\n")
b.WriteString(fmt.Sprintf("%surl: %q\n", indentationValues, s.EncodeToString()))
b.WriteString(fmt.Sprintf("%surl: %q\n", indentationValues, s.URL()))
b.WriteString(fmt.Sprintf("%s}", indentationEnd))
return b.String()
}
// NewStationURL returns a new StationURL
func NewStationURL(str string) *StationURL {
stationURL := &StationURL{
params: make(map[string]string),
func NewStationURL(url String) StationURL {
stationURL := StationURL{
url: string(url),
standardParams: make(map[string]string),
customParams: make(map[string]string),
}
stationURL.FromString(str)
stationURL.Parse()
return stationURL
}

View file

@ -7,23 +7,21 @@ import (
)
// String is an implementation of rdv::String.
// Wraps a primitive Go string.
type String struct {
Value string
}
// Type alias of string
type String string
// WriteTo writes the String to the given writable
func (s *String) WriteTo(writable Writable) {
str := s.Value + "\x00"
strLength := len(str)
func (s String) WriteTo(writable Writable) {
s = s + "\x00"
strLength := len(s)
if writable.StringLengthSize() == 4 {
writable.WritePrimitiveUInt32LE(uint32(strLength))
writable.WriteUInt32LE(uint32(strLength))
} else {
writable.WritePrimitiveUInt16LE(uint16(strLength))
writable.WriteUInt16LE(uint16(strLength))
}
writable.Write([]byte(str))
writable.Write([]byte(s))
}
// ExtractFrom extracts the String from the given readable
@ -32,11 +30,11 @@ func (s *String) ExtractFrom(readable Readable) error {
var err error
if readable.StringLengthSize() == 4 {
l, e := readable.ReadPrimitiveUInt32LE()
l, e := readable.ReadUInt32LE()
length = uint64(l)
err = e
} else {
l, e := readable.ReadPrimitiveUInt16LE()
l, e := readable.ReadUInt16LE()
length = uint64(l)
err = e
}
@ -56,31 +54,45 @@ func (s *String) ExtractFrom(readable Readable) error {
str := strings.TrimRight(string(stringData), "\x00")
s.Value = str
*s = String(str)
return nil
}
// Copy returns a pointer to a copy of the String. Requires type assertion when used
func (s *String) Copy() RVType {
return NewString(s.Value)
func (s String) Copy() RVType {
return NewString(string(s))
}
// Equals checks if the input is equal in value to the current instance
func (s *String) Equals(o RVType) bool {
if _, ok := o.(*String); !ok {
func (s String) Equals(o RVType) bool {
if _, ok := o.(String); !ok {
return false
}
return s.Value == o.(*String).Value
return s == o.(String)
}
// CopyRef copies the current value of the String
// and returns a pointer to the new copy
func (s String) CopyRef() RVTypePtr {
copied := s.Copy().(String)
return &copied
}
// Deref takes a pointer to the String
// and dereferences it to the raw value.
// Only useful when working with an instance of RVTypePtr
func (s *String) Deref() RVType {
return *s
}
// String returns a string representation of the struct
func (s *String) String() string {
return fmt.Sprintf("%q", s.Value)
func (s String) String() string {
return fmt.Sprintf("%q", string(s))
}
// NewString returns a new String
func NewString(str string) *String {
return &String{Value: str}
func NewString(input string) String {
s := String(input)
return s
}

View file

@ -7,18 +7,18 @@ import (
// Structure represents a Quazal Rendez-Vous/NEX Structure (custom class) base struct.
type Structure struct {
StructureVersion uint8
StructureVersion uint8 `json:"structure_version" db:"structure_version" bson:"structure_version" xml:"StructureVersion"`
}
// ExtractHeaderFrom extracts the structure header from the given readable
func (s *Structure) ExtractHeaderFrom(readable Readable) error {
if readable.UseStructureHeader() {
version, err := readable.ReadPrimitiveUInt8()
version, err := readable.ReadUInt8()
if err != nil {
return fmt.Errorf("Failed to read Structure version. %s", err.Error())
}
contentLength, err := readable.ReadPrimitiveUInt32LE()
contentLength, err := readable.ReadUInt32LE()
if err != nil {
return fmt.Errorf("Failed to read Structure content length. %s", err.Error())
}
@ -34,9 +34,9 @@ func (s *Structure) ExtractHeaderFrom(readable Readable) error {
}
// WriteHeaderTo writes the structure header to the given writable
func (s *Structure) WriteHeaderTo(writable Writable, contentLength uint32) {
func (s Structure) WriteHeaderTo(writable Writable, contentLength uint32) {
if writable.UseStructureHeader() {
writable.WritePrimitiveUInt8(s.StructureVersion)
writable.WritePrimitiveUInt32LE(contentLength)
writable.WriteUInt8(s.StructureVersion)
writable.WriteUInt32LE(contentLength)
}
}

62
types/uint16.go Normal file
View file

@ -0,0 +1,62 @@
package types
import "fmt"
// UInt16 is a type alias for the Go basic type uint16 for use as an RVType
type UInt16 uint16
// WriteTo writes the UInt16 to the given writable
func (u16 UInt16) WriteTo(writable Writable) {
writable.WriteUInt16LE(uint16(u16))
}
// ExtractFrom extracts the UInt16 value from the given readable
func (u16 *UInt16) ExtractFrom(readable Readable) error {
value, err := readable.ReadUInt16LE()
if err != nil {
return err
}
*u16 = UInt16(value)
return nil
}
// Copy returns a pointer to a copy of the UInt16. Requires type assertion when used
func (u16 UInt16) Copy() RVType {
return NewUInt16(uint16(u16))
}
// Equals checks if the input is equal in value to the current instance
func (u16 UInt16) Equals(o RVType) bool {
other, ok := o.(UInt16)
if !ok {
return false
}
return u16 == other
}
// CopyRef copies the current value of the UInt16
// and returns a pointer to the new copy
func (u16 UInt16) CopyRef() RVTypePtr {
copied := u16.Copy().(UInt16)
return &copied
}
// Deref takes a pointer to the UInt16
// and dereferences it to the raw value.
// Only useful when working with an instance of RVTypePtr
func (u16 *UInt16) Deref() RVType {
return *u16
}
// String returns a string representation of the UInt16
func (u16 UInt16) String() string {
return fmt.Sprintf("%d", u16)
}
// NewUInt16 returns a new UInt16
func NewUInt16(input uint16) UInt16 {
u16 := UInt16(input)
return u16
}

62
types/uint32.go Normal file
View file

@ -0,0 +1,62 @@
package types
import "fmt"
// UInt32 is a type alias for the Go basic type uint32 for use as an RVType
type UInt32 uint32
// WriteTo writes the UInt32 to the given writable
func (u32 UInt32) WriteTo(writable Writable) {
writable.WriteUInt32LE(uint32(u32))
}
// ExtractFrom extracts the UInt32 value from the given readable
func (u32 *UInt32) ExtractFrom(readable Readable) error {
value, err := readable.ReadUInt32LE()
if err != nil {
return err
}
*u32 = UInt32(value)
return nil
}
// Copy returns a pointer to a copy of the UInt32. Requires type assertion when used
func (u32 UInt32) Copy() RVType {
return NewUInt32(uint32(u32))
}
// Equals checks if the input is equal in value to the current instance
func (u32 UInt32) Equals(o RVType) bool {
other, ok := o.(UInt32)
if !ok {
return false
}
return u32 == other
}
// CopyRef copies the current value of the UInt32
// and returns a pointer to the new copy
func (u32 UInt32) CopyRef() RVTypePtr {
copied := u32.Copy().(UInt32)
return &copied
}
// Deref takes a pointer to the UInt32
// and dereferences it to the raw value.
// Only useful when working with an instance of RVTypePtr
func (u32 *UInt32) Deref() RVType {
return *u32
}
// String returns a string representation of the UInt32
func (u32 UInt32) String() string {
return fmt.Sprintf("%d", u32)
}
// NewUInt32 returns a new UInt32
func NewUInt32(input uint32) UInt32 {
u32 := UInt32(input)
return u32
}

62
types/uint64.go Normal file
View file

@ -0,0 +1,62 @@
package types
import "fmt"
// UInt64 is a type alias for the Go basic type uint64 for use as an RVType
type UInt64 uint64
// WriteTo writes the UInt64 to the given writable
func (u64 UInt64) WriteTo(writable Writable) {
writable.WriteUInt64LE(uint64(u64))
}
// ExtractFrom extracts the UInt64 value from the given readable
func (u64 *UInt64) ExtractFrom(readable Readable) error {
value, err := readable.ReadUInt64LE()
if err != nil {
return err
}
*u64 = UInt64(value)
return nil
}
// Copy returns a pointer to a copy of the UInt64. Requires type assertion when used
func (u64 UInt64) Copy() RVType {
return NewUInt64(uint64(u64))
}
// Equals checks if the input is equal in value to the current instance
func (u64 UInt64) Equals(o RVType) bool {
other, ok := o.(UInt64)
if !ok {
return false
}
return u64 == other
}
// CopyRef copies the current value of the UInt64
// and returns a pointer to the new copy
func (u64 UInt64) CopyRef() RVTypePtr {
copied := u64.Copy().(UInt64)
return &copied
}
// Deref takes a pointer to the UInt64
// and dereferences it to the raw value.
// Only useful when working with an instance of RVTypePtr
func (u64 *UInt64) Deref() RVType {
return *u64
}
// String returns a string representation of the UInt64
func (u64 UInt64) String() string {
return fmt.Sprintf("%d", u64)
}
// NewUInt64 returns a new UInt64
func NewUInt64(input uint64) UInt64 {
u64 := UInt64(input)
return u64
}

62
types/uint8.go Normal file
View file

@ -0,0 +1,62 @@
package types
import "fmt"
// UInt8 is a type alias for the Go basic type uint8 for use as an RVType
type UInt8 uint8
// WriteTo writes the UInt8 to the given writable
func (u8 UInt8) WriteTo(writable Writable) {
writable.WriteUInt8(uint8(u8))
}
// ExtractFrom extracts the UInt8 value from the given readable
func (u8 *UInt8) ExtractFrom(readable Readable) error {
value, err := readable.ReadUInt8()
if err != nil {
return err
}
*u8 = UInt8(value)
return nil
}
// Copy returns a pointer to a copy of the UInt8. Requires type assertion when used
func (u8 UInt8) Copy() RVType {
return NewUInt8(uint8(u8))
}
// Equals checks if the input is equal in value to the current instance
func (u8 UInt8) Equals(o RVType) bool {
other, ok := o.(UInt8)
if !ok {
return false
}
return u8 == other
}
// CopyRef copies the current value of the UInt8
// and returns a pointer to the new copy
func (u8 UInt8) CopyRef() RVTypePtr {
copied := u8.Copy().(UInt8)
return &copied
}
// Deref takes a pointer to the UInt8
// and dereferences it to the raw value.
// Only useful when working with an instance of RVTypePtr
func (u8 *UInt8) Deref() RVType {
return *u8
}
// String returns a string representation of the UInt8
func (u8 UInt8) String() string {
return fmt.Sprintf("%d", u8)
}
// NewUInt8 returns a new UInt8
func NewUInt8(input uint8) UInt8 {
u8 := UInt8(input)
return u8
}

View file

@ -6,22 +6,22 @@ import (
)
// VariantTypes holds a mapping of RVTypes that are accessible in a Variant
var VariantTypes = make(map[uint8]RVType)
var VariantTypes = make(map[UInt8]RVType)
// RegisterVariantType registers a RVType to be accessible in a Variant
func RegisterVariantType(id uint8, rvType RVType) {
func RegisterVariantType(id UInt8, rvType RVType) {
VariantTypes[id] = rvType
}
// Variant is an implementation of rdv::Variant.
// This type can hold many other types, denoted by a type ID.
type Variant struct {
TypeID *PrimitiveU8
Type RVType
TypeID UInt8 `json:"type_id" db:"type_id" bson:"type_id" xml:"TypeID"`
Type RVType `json:"type" db:"type" bson:"type" xml:"Type"`
}
// WriteTo writes the Variant to the given writable
func (v *Variant) WriteTo(writable Writable) {
func (v Variant) WriteTo(writable Writable) {
v.TypeID.WriteTo(writable)
if v.Type != nil {
@ -36,25 +36,34 @@ func (v *Variant) ExtractFrom(readable Readable) error {
return fmt.Errorf("Failed to read Variant type ID. %s", err.Error())
}
typeID := v.TypeID
// * Type ID of 0 is a "None" type. There is no data
if v.TypeID.Value == 0 {
if typeID == 0 {
return nil
}
if _, ok := VariantTypes[v.TypeID.Value]; !ok {
return fmt.Errorf("Invalid Variant type ID %d", v.TypeID)
if _, ok := VariantTypes[typeID]; !ok {
return fmt.Errorf("Invalid Variant type ID %d", typeID)
}
v.Type = VariantTypes[v.TypeID.Value].Copy()
// * Create a new copy and get a pointer to it.
// * Required so that we have access to ExtractFrom
ptr := VariantTypes[typeID].CopyRef()
if err := ptr.ExtractFrom(readable); err != nil {
return fmt.Errorf("Failed to read Variant type data. %s", err.Error())
}
return v.Type.ExtractFrom(readable)
v.Type = ptr.Deref() // * Dereference the RVTypePtr pointer back into a non-pointer type
return nil
}
// Copy returns a pointer to a copy of the Variant. Requires type assertion when used
func (v *Variant) Copy() RVType {
func (v Variant) Copy() RVType {
copied := NewVariant()
copied.TypeID = v.TypeID.Copy().(*PrimitiveU8)
copied.TypeID = v.TypeID.Copy().(UInt8)
if v.Type != nil {
copied.Type = v.Type.Copy()
@ -64,12 +73,12 @@ func (v *Variant) Copy() RVType {
}
// Equals checks if the input is equal in value to the current instance
func (v *Variant) Equals(o RVType) bool {
if _, ok := o.(*Variant); !ok {
func (v Variant) Equals(o RVType) bool {
if _, ok := o.(Variant); !ok {
return false
}
other := o.(*Variant)
other := o.(Variant)
if !v.TypeID.Equals(other.TypeID) {
return false
@ -82,13 +91,27 @@ func (v *Variant) Equals(o RVType) bool {
return true
}
// CopyRef copies the current value of the Variant
// and returns a pointer to the new copy
func (v Variant) CopyRef() RVTypePtr {
copied := v.Copy().(Variant)
return &copied
}
// Deref takes a pointer to the Variant
// and dereferences it to the raw value.
// Only useful when working with an instance of RVTypePtr
func (v *Variant) Deref() RVType {
return *v
}
// String returns a string representation of the struct
func (v *Variant) String() string {
func (v Variant) String() string {
return v.FormatToString(0)
}
// FormatToString pretty-prints the struct data using the provided indentation level
func (v *Variant) FormatToString(indentationLevel int) string {
func (v Variant) FormatToString(indentationLevel int) string {
indentationValues := strings.Repeat("\t", indentationLevel+1)
indentationEnd := strings.Repeat("\t", indentationLevel)
@ -109,10 +132,10 @@ func (v *Variant) FormatToString(indentationLevel int) string {
}
// NewVariant returns a new Variant
func NewVariant() *Variant {
func NewVariant() Variant {
// * Type ID of 0 is a "None" type. There is no data
return &Variant{
TypeID: NewPrimitiveU8(0),
return Variant{
TypeID: NewUInt8(0),
Type: nil,
}
}

View file

@ -2,21 +2,21 @@ package types
// Writable represents a struct that types can write to
type Writable interface {
StringLengthSize() int // Returns the size of the length field for rdv::String types. Only 2 and 4 are valid
PIDSize() int // Returns the size of the length fields for nn::nex::PID types. Only 4 and 8 are valid
UseStructureHeader() bool // Returns whether or not Structure types should use a header
CopyNew() Writable // Returns a new Writable with the same settings, but an empty buffer
Write(data []byte) // Writes the provided data to the buffer
WritePrimitiveUInt8(value uint8) // Writes a primitive Go uint8
WritePrimitiveUInt16LE(value uint16) // Writes a primitive Go uint16
WritePrimitiveUInt32LE(value uint32) // Writes a primitive Go uint32
WritePrimitiveUInt64LE(value uint64) // Writes a primitive Go uint64
WritePrimitiveInt8(value int8) // Writes a primitive Go int8
WritePrimitiveInt16LE(value int16) // Writes a primitive Go int16
WritePrimitiveInt32LE(value int32) // Writes a primitive Go int32
WritePrimitiveInt64LE(value int64) // Writes a primitive Go int64
WritePrimitiveFloat32LE(value float32) // Writes a primitive Go float32
WritePrimitiveFloat64LE(value float64) // Writes a primitive Go float64
WritePrimitiveBool(value bool) // Writes a primitive Go bool
Bytes() []byte // Returns the data written t othe buffer
StringLengthSize() int // Returns the size of the length field for rdv::String types. Only 2 and 4 are valid
PIDSize() int // Returns the size of the length fields for nn::nex::PID types. Only 4 and 8 are valid
UseStructureHeader() bool // Returns whether or not Structure types should use a header
CopyNew() Writable // Returns a new Writable with the same settings, but an empty buffer
Write(data []byte) // Writes the provided data to the buffer
WriteUInt8(value uint8) // Writes a primitive Go uint8
WriteUInt16LE(value uint16) // Writes a primitive Go uint16
WriteUInt32LE(value uint32) // Writes a primitive Go uint32
WriteUInt64LE(value uint64) // Writes a primitive Go uint64
WriteInt8(value int8) // Writes a primitive Go int8
WriteInt16LE(value int16) // Writes a primitive Go int16
WriteInt32LE(value int32) // Writes a primitive Go int32
WriteInt64LE(value int64) // Writes a primitive Go int64
WriteFloat32LE(value float32) // Writes a primitive Go float32
WriteFloat64LE(value float64) // Writes a primitive Go float64
WriteBool(value bool) // Writes a primitive Go bool
Bytes() []byte // Returns the data written to the buffer
}

View file

@ -21,27 +21,27 @@ func (wseh *wsEventHandler) OnOpen(socket *gws.Conn) {
_ = socket.SetDeadline(time.Now().Add(pingInterval + pingWait))
}
func (wseh *wsEventHandler) OnClose(wsConn *gws.Conn, err error) {
connections := make([]*PRUDPConnection, 0)
func (wseh *wsEventHandler) OnClose(wsConn *gws.Conn, _ error) {
// * Loop over all connections on all endpoints
wseh.prudpServer.Endpoints.Each(func(streamid uint8, pep *PRUDPEndPoint) bool {
connections := make([]*PRUDPConnection, 0)
socket, ok := wseh.prudpServer.Connections.Get(wsConn.RemoteAddr().String())
if !ok {
// TODO - Error?
return
}
pep.Connections.Each(func(discriminator string, pc *PRUDPConnection) bool {
if pc.Socket.Address == wsConn.RemoteAddr() {
connections = append(connections, pc)
}
return false
})
socket.Connections.Each(func(_ uint8, connection *PRUDPConnection) bool {
connections = append(connections, connection)
// * We cannot modify a MutexMap while looping over it
// * since the mutex is locked. We first need to grab
// * the entries we want to delete, and then loop over
// * them here to actually clean them up
for _, connection := range connections {
pep.cleanupConnection(connection) // * "removed" event is dispatched here
}
return false
})
// * We cannot modify a MutexMap while looping over it
// * since the mutex is locked. We first need to grab
// * the entries we want to delete, and then loop over
// * them here to actually clean them up
for _, connection := range connections {
connection.cleanup() // * "removed" event is dispatched here
}
}
func (wseh *wsEventHandler) OnPing(socket *gws.Conn, payload []byte) {