Added session open and connect code, and started session data
parent
a52250bcf8
commit
a438837c81
@ -0,0 +1,22 @@
|
||||
package pnyx
|
||||
|
||||
import (
|
||||
)
|
||||
|
||||
type ChannelID uint32
|
||||
|
||||
const RootChannelID = 0
|
||||
|
||||
type ModeID uint8
|
||||
type CommandID uint8
|
||||
|
||||
type PermissionMap map[ClientID]map[ModeID]map[CommandID]bool
|
||||
|
||||
type Channel struct {
|
||||
modes map[ModeID]Mode
|
||||
permissions PermissionMap
|
||||
parent ChannelID
|
||||
}
|
||||
|
||||
type Mode interface {
|
||||
}
|
@ -1,29 +1,22 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"git.metznet.ca/MetzNet/pnyx"
|
||||
)
|
||||
|
||||
func main() {
|
||||
address, err := net.ResolveUDPAddr("udp", os.Args[1])
|
||||
client, err := pnyx.NewClient(nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
connection, err := net.DialUDP("udp", nil, address)
|
||||
|
||||
server_public, secret, err := client.Connect(os.Args[1])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for true {
|
||||
written, err := connection.Write([]byte(os.Args[2]))
|
||||
if written != len(os.Args[2]) {
|
||||
panic(written)
|
||||
} else if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
fmt.Printf("Started session %s with %s", pnyx.ID[pnyx.SessionID](secret), pnyx.ID[pnyx.ClientID](server_public))
|
||||
}
|
||||
|
@ -0,0 +1,175 @@
|
||||
So far I've been thinking of this as similar to IRC, but without passing messages between servers(if a user wants messages from a server they need to connect directly).
|
||||
|
||||
This complicates DMs since there isn't a message routing path between servers, but a potential workaround is to have a "mailbox" instead of DMs(who stores the mailbox?)
|
||||
|
||||
So far this is the architecture I've though of:
|
||||
- At the core is a session manager on top of a UDP socket. This session manager allows clients to start and reconnect to sessions with the server.
|
||||
- Once in a session with the server, a user can send commands, and the server streams data to the user depending on the commands sent.
|
||||
- Similar to IRC a server is split into 'channels', but unlike IRC these channels are confined to a specific server(so can be referenced by the server to be unique)
|
||||
- Channels are multimodal and when choosing to join a channel clients select which modes they are joining.
|
||||
|
||||
With this two problems I can think of are:
|
||||
1. Text channels retaining data and sending that retained data
|
||||
2. Sending direct messages to users would be confined to servers, or complicated
|
||||
|
||||
To solve the text channel problem I propose that text channels in the discord sense are not the same as the text mode of a pnyx channel since pnyx channels are live-streaming data while discord text channels are more of a forum.
|
||||
Is there a way to split these in such a way that either can be operated independently, or the application can be expanded in similar ways easily(modular)?
|
||||
|
||||
Right now it sounds like the server would act as a multiplexer for data subscribed by channel, and the forum feature doesn't relate to that.
|
||||
Other than forum, another good analogy is a group chat or slack channel where the messages are asyncrhonous, but not quite as isolated as forum posts.
|
||||
|
||||
The problem with 'forum mode' being another mode of a channel is that it requires user commands specific to the mode(which others would to I guess like not seeing everyones video at once).
|
||||
|
||||
So if 'forum mode' was the default text chat mode, these would be the channel modes:
|
||||
- forum
|
||||
- audio
|
||||
- video
|
||||
|
||||
modes can be implemented modularly, which means that channels will have to store different state objects based on the supported modes
|
||||
|
||||
The mode module would have to be responsible for the multiplexing of the packets based off of it's state, so there should be some function that takes in a packet and the channel state, and returns packets to send with the updated channel state
|
||||
- It could also be responsible for setting up routing tables, so the function would be function(command, state, routing_table) -> (new_state, routing_table)
|
||||
- This way the state doesn't have to be updated every message, just on commands and the path for data is minimalized
|
||||
|
||||
Modes would have to be completely independent for simplicity
|
||||
|
||||
So basically:
|
||||
- A server is a collection of channels
|
||||
- A channel is a collection of modes
|
||||
- A mode is a routing table and state information, which is updated by commands
|
||||
|
||||
What about channel permissions and creating channels?
|
||||
|
||||
Can/Should it be implemented such that channels are to a server as modes are to a channel?
|
||||
I don't think there's a reason to nest past modes, so there's no reason to make modes and channels the same.
|
||||
|
||||
Wait but if channels are a mode on the server, then you can nest channels by making channels with the channel mode, so maybe it is useful?
|
||||
|
||||
OK so for permissions it's going to be completely server-based, and in the config you can specify a public key to be a server admin for easy configuration.
|
||||
|
||||
How would commands work? I'm thinking it would be a struct like this
|
||||
|
||||
struct {
|
||||
uint8 mode
|
||||
uint8 command
|
||||
[]byte data
|
||||
}
|
||||
|
||||
So for example, to join the raw mode of the servers root channel would be something like
|
||||
{
|
||||
mode: 0x00 (MODE_RAW)
|
||||
command: 0x00 (MODE_RAW_COMMAND_JOIN)
|
||||
data: 0 length byte array
|
||||
}
|
||||
|
||||
To send data to the raw mode of the servers root channel would be
|
||||
{
|
||||
mode: 0x00 (MODE_RAW)
|
||||
command: 0x01 (MODE_RAW_COMMAND_DATA)
|
||||
data: n length byte array
|
||||
}
|
||||
|
||||
|
||||
If channels are a tree(with the server being a root channel with only the 'channels' mode), then permissions would similarily be tree-based and defined on a per-mode basis.
|
||||
e.x. for someone to have all permissions on all modes on the server they would get the 'wildcard'(*) permission. If someone was granted all permissions within the 'test' channel on the server then it would be something like:
|
||||
`c/test/*`, broken down this is `c` for the 'channels' mode(the typical start for a command), `test` to specify the channel, and then wildcard
|
||||
|
||||
The downside of this is how commands would be targetted. For routing purpouses it could make sense to have it be layered. E.x. assuming 0x0C is the 'channel' mode and '0x00' is the 'raw' mode, a data packet could look something like this
|
||||
|
||||
{
|
||||
mode: 0x0C (MODE_CHANNELS)
|
||||
command: 0x01 (MODE_CHANNELS_DATA)
|
||||
data: {
|
||||
channel: 0xDE (sub-channel ID)
|
||||
command: {
|
||||
mode: 0x0C (MODE_CHANNELS)
|
||||
command: 0x01 (MODE_CHANNELS_DATA)
|
||||
data: {
|
||||
channel: 0xAD
|
||||
command: {
|
||||
mode: 0x00 (MODE_RAW)
|
||||
command: 0x01 (MODE_RAW_COMMAND_DATA)
|
||||
data: n length byte array
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
It would be better if channels were not nested and instead can be referenced by a global ID(instead of a hierarchical ID), but the tree can still be maintained in memory for orginazation/permissions.
|
||||
|
||||
So instead of nested 0x0C commands it would be like this:
|
||||
|
||||
{
|
||||
channel: 0xDEAD (server-unique channel identifier)
|
||||
mode: 0x00 (MODE_RAW)
|
||||
command: 0x01
|
||||
data: ...
|
||||
}
|
||||
|
||||
Would the commands to modify/create/delete these channels then be server-wide, or parsed by the channel?
|
||||
|
||||
If the commands are processed by the server state and update the server state then I'd store a map in memory of:
|
||||
|
||||
|
||||
Yea I like that, but how do permissions work?
|
||||
|
||||
If I'm client ID X and send a command for channel Y(which hierarchically is A/Y) and I have all permissions on channel A, how is this looked up?
|
||||
|
||||
The command would target channel Y, so first lookup would be server.Channels[Y]
|
||||
First check if the ClientID has the permission on the channel directly with that Permissions that have been looked up
|
||||
If that returns access denied, go to the channels parent(if it's not the zero ID to signify the root channel) and check if the user has wildcard permissions on the parent
|
||||
Continue that until the root node, returning access granted if the user has the direct(or wildcard) permissions on the channel itself, or wildcard permissions on any of it's parents
|
||||
|
||||
The downside of this is that the first check is likely expensive, while the other checks are cheap(checking if a user is
|
||||
|
||||
So right now the server state would be something like
|
||||
|
||||
|
||||
type ChannelID uuid.UUID
|
||||
|
||||
type Channel struct{
|
||||
Children []ChannelID,
|
||||
Parent ChannelID
|
||||
}
|
||||
|
||||
type Permission string
|
||||
type Permissions struct {
|
||||
This map[ClientID]map[ModeID][]Permission
|
||||
Children map[ClientID]map[ChannelID]
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
Channels map[ChannelID]Channel
|
||||
Permissions map[ClientID]map[ChannelID]map[ModeID][]ComandID
|
||||
}
|
||||
|
||||
Permissions with 1 admin user:
|
||||
{
|
||||
ADMIN_ID: nil
|
||||
}
|
||||
|
||||
What the difference between that and:
|
||||
{
|
||||
ADMIN_ID: {
|
||||
ZERO_ID: nil
|
||||
}
|
||||
}
|
||||
|
||||
How is "no commands on the root channel, but all commands on the root channels children" expressed?
|
||||
{
|
||||
}
|
||||
|
||||
Also this allows for the permission map to look like:
|
||||
{
|
||||
ADMIN_ID: {
|
||||
ZERO_ID: nil
|
||||
0x0F: {
|
||||
MODE_RAW: {
|
||||
JOIN,
|
||||
SEND,
|
||||
LEAVE,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue