diff --git a/.gitignore b/.gitignore index a60d266..8ad5d3d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,6 @@ /skald /skaldctl/skaldctl /data -/groups/**/*.json +/halls/**/*.json /static/**/*.d.ts /static/third-party/tasks-vision diff --git a/README.md b/README.md index 5e3737f..5b0b85f 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,8 @@ hall conferencing server. The current development repository is git clone https://git.stormux.org/storm/skald cd skald CGO_ENABLED=0 go build -ldflags='-s -w' -mkdir groups -echo '{"users": {"vimes": {"password":"sybil", "permissions":"op"}}}' > groups/night-watch.json +mkdir halls +echo '{"users": {"vimes": {"password":"sybil", "permissions":"op"}}}' > halls/night-watch.json ./skald & ``` diff --git a/diskwriter/diskwriter.go b/diskwriter/diskwriter.go index dbe3395..1ecbc5b 100644 --- a/diskwriter/diskwriter.go +++ b/diskwriter/diskwriter.go @@ -24,7 +24,7 @@ import ( gcodecs "git.stormux.org/storm/skald/codecs" "git.stormux.org/storm/skald/conn" - "git.stormux.org/storm/skald/group" + "git.stormux.org/storm/skald/hall" "git.stormux.org/storm/skald/rtptime" ) @@ -36,8 +36,8 @@ const ( var Directory string type Client struct { - group *group.Group - id string + hall *hall.Hall + id string mu sync.Mutex down map[string]*diskConn @@ -50,12 +50,12 @@ func newId() string { return hex.EncodeToString(b) } -func New(g *group.Group) *Client { - return &Client{group: g, id: newId()} +func New(g *hall.Hall) *Client { + return &Client{hall: g, id: newId()} } -func (client *Client) Group() *group.Group { - return client.group +func (client *Client) Hall() *hall.Hall { + return client.hall } func (client *Client) Id() string { @@ -86,7 +86,7 @@ func (client *Client) PushClient(group, kind, id, username string, perms []strin return nil } -func (client *Client) RequestConns(target group.Client, g *group.Group, id string) error { +func (client *Client) RequestConns(target hall.Client, g *hall.Hall, id string) error { return nil } @@ -104,7 +104,7 @@ func (client *Client) Close() error { func (client *Client) Kick(id string, user *string, message string) error { err := client.Close() - group.DelClient(client) + hall.DelClient(client) return err } @@ -116,8 +116,8 @@ func (client *Client) Joined(group, kind string) error { return nil } -func (client *Client) PushConn(g *group.Group, id string, up conn.Up, tracks []conn.UpTrack, replace string) error { - if client.group != g { +func (client *Client) PushConn(g *hall.Hall, id string, up conn.Up, tracks []conn.UpTrack, replace string) error { + if client.hall != g { return nil } @@ -148,7 +148,7 @@ func (client *Client) PushConn(g *group.Group, id string, up conn.Up, tracks []c return nil } - directory := filepath.Join(Directory, client.group.Name()) + directory := filepath.Join(Directory, client.hall.Name()) err := os.MkdirAll(directory, 0700) if err != nil { g.WallOps("Write to disk: " + err.Error()) @@ -192,7 +192,7 @@ func (conn *diskConn) warn(message string) { return } log.Println(message) - conn.client.group.WallOps(message) + conn.client.hall.WallOps(message) conn.lastWarning = now } @@ -330,7 +330,7 @@ func newDiskConn(client *Client, directory string, up conn.Up, remoteTracks []co if audio == nil { audio = remote } else { - client.group.WallOps("Multiple audio tracks, recording just one") + client.hall.WallOps("Multiple audio tracks, recording just one") } } else if strings.EqualFold(codec, "video/vp8") || strings.EqualFold(codec, "video/vp9") || @@ -338,10 +338,10 @@ func newDiskConn(client *Client, directory string, up conn.Up, remoteTracks []co if video == nil || video.Label() == "l" { video = remote } else if remote.Label() != "l" { - client.group.WallOps("Multiple video tracks, recording just one") + client.hall.WallOps("Multiple video tracks, recording just one") } } else { - client.group.WallOps("Unknown codec, " + codec + ", not recording") + client.hall.WallOps("Unknown codec, " + codec + ", not recording") } } diff --git a/group/client.go b/hall/client.go similarity index 91% rename from group/client.go rename to hall/client.go index 71a983b..ca2cbf4 100644 --- a/group/client.go +++ b/hall/client.go @@ -1,4 +1,4 @@ -package group +package hall import ( "bytes" @@ -134,7 +134,7 @@ type ClientCredentials struct { } type Client interface { - Group() *Group + Hall() *Hall Addr() net.Addr Id() string Username() string @@ -142,9 +142,9 @@ type Client interface { Permissions() []string SetPermissions([]string) Data() map[string]interface{} - PushConn(g *Group, id string, conn conn.Up, tracks []conn.UpTrack, replace string) error - RequestConns(target Client, g *Group, id string) error - Joined(group, kind string) error - PushClient(group, kind, id, username string, perms []string, data map[string]interface{}) error + PushConn(g *Hall, id string, conn conn.Up, tracks []conn.UpTrack, replace string) error + RequestConns(target Client, g *Hall, id string) error + Joined(hall, kind string) error + PushClient(hall, kind, id, username string, perms []string, data map[string]interface{}) error Kick(id string, user *string, message string) error } diff --git a/group/client_test.go b/hall/client_test.go similarity index 99% rename from group/client_test.go rename to hall/client_test.go index b0f0581..e903d3b 100644 --- a/group/client_test.go +++ b/hall/client_test.go @@ -1,4 +1,4 @@ -package group +package hall import ( "bytes" diff --git a/group/description.go b/hall/description.go similarity index 89% rename from group/description.go rename to hall/description.go index 47754e1..d87238a 100644 --- a/group/description.go +++ b/hall/description.go @@ -1,4 +1,4 @@ -package group +package hall import ( "encoding/json" @@ -147,11 +147,11 @@ func (u UserDescription) MarshalJSON() ([]byte, error) { return json.Marshal(uu) } -// Description represents a group description together with some metadata +// Description represents a hall description together with some metadata // about the JSON file it was deserialised from. type Description struct { // The file this was deserialised from. This is not necessarily - // the name of the group, for example in case of a subgroup. + // the name of the hall, for example in case of a subgroup. FileName string `json:"-"` // The modtime and size of the file. These are used to detect @@ -162,10 +162,10 @@ type Description struct { // Whether this is an automatically generated subgroup isSubgroup bool `json:"-"` - // The user-friendly group name + // The user-friendly hall name DisplayName string `json:"displayName,omitempty"` - // A user-readable description of the group. + // A user-readable description of the hall. Description string `json:"description,omitempty"` // A user-readable contact, typically an e-mail address. @@ -174,10 +174,10 @@ type Description struct { // A user-readable comment. Ignored by the server. Comment string `json:"comment,omitempty"` - // Whether to display the group on the landing page. + // Whether to display the hall on the landing page. Public bool `json:"public,omitempty"` - // A URL to redirect the group to. If this is not empty, most + // A URL to redirect the hall to. If this is not empty, most // other fields are ignored. Redirect string `json:"redirect,omitempty"` @@ -202,7 +202,7 @@ type Description struct { // Whether subgroups are created on the fly. AutoSubgroups bool `json:"auto-subgroups,omitempty"` - // Whether to lock the group when the last op logs out. + // Whether to lock the hall when the last op logs out. Autolock bool `json:"autolock,omitempty"` // Whether to kick all users when the last op logs out. @@ -278,7 +278,7 @@ func descriptionMatch(d1, d2 *Description) bool { return true } -// descriptionUnchanged returns true if a group's description hasn't +// descriptionUnchanged returns true if a hall's description hasn't // changed since it was last read. func descriptionUnchanged(name string, desc *Description) bool { fi, fileName, _, err := getDescriptionFile(name, true, os.Stat) @@ -292,7 +292,7 @@ func descriptionUnchanged(name string, desc *Description) bool { return true } -// GetDescription gets a group description, either from cache or from disk +// GetDescription gets a hall description, either from cache or from disk func GetDescription(name string) (*Description, error) { g := Get(name) if g != nil { @@ -336,10 +336,10 @@ func makeETag(fileSize int64, modTime time.Time) string { } // DeleteDescription deletes a description (and therefore persistently -// deletes a group) but only if it matches a given ETag. +// deletes a hall) but only if it matches a given ETag. func DeleteDescription(name, etag string) error { - groups.mu.Lock() - defer groups.mu.Unlock() + halls.mu.Lock() + defer halls.mu.Unlock() fi, fileName, _, err := getDescriptionFile(name, false, os.Stat) if err != nil { @@ -352,14 +352,14 @@ func DeleteDescription(name, etag string) error { } // UpdateDescription overwrites a description if it matches a given ETag. -// In order to create a new group, pass an empty ETag. +// In order to create a new hall, pass an empty ETag. func UpdateDescription(name, etag string, desc *Description) error { if desc.Users != nil || desc.WildcardUser != nil || desc.AuthKeys != nil { return errors.New("description is not sanitised") } - groups.mu.Lock() - defer groups.mu.Unlock() + halls.mu.Lock() + defer halls.mu.Unlock() oldetag := "" var filename string @@ -438,7 +438,7 @@ func rewriteDescriptionFile(filename string, desc *Description) error { } -// readDescription reads a group's description from disk +// readDescription reads a hall's description from disk func readDescription(name string, allowSubgroups bool) (*Description, error) { r, fileName, isSubgroup, err := getDescriptionFile(name, allowSubgroups, os.Open) @@ -585,7 +585,7 @@ func GetDescriptionNames() ([]string, error) { return names, err } -func SetKeys(group string, keys []map[string]any) error { +func SetKeys(hall string, keys []map[string]any) error { if keys != nil { _, err := token.ParseKeys(keys, "", "") if err != nil { @@ -593,10 +593,10 @@ func SetKeys(group string, keys []map[string]any) error { } } - groups.mu.Lock() - defer groups.mu.Unlock() + halls.mu.Lock() + defer halls.mu.Unlock() - desc, err := readDescription(group, false) + desc, err := readDescription(hall, false) if err != nil { return err } @@ -604,8 +604,8 @@ func SetKeys(group string, keys []map[string]any) error { return rewriteDescriptionFile(desc.FileName, desc) } -func GetUsers(group string) ([]string, string, error) { - desc, err := GetDescription(group) +func GetUsers(hall string) ([]string, string, error) { + desc, err := GetDescription(hall) if err != nil { return nil, "", err } @@ -618,13 +618,13 @@ func GetUsers(group string) ([]string, string, error) { return users, makeETag(desc.fileSize, desc.modTime), nil } -func GetSanitisedUser(group, username string, wildcard bool) (UserDescription, string, error) { +func GetSanitisedUser(hall, username string, wildcard bool) (UserDescription, string, error) { if wildcard && username != "" { return UserDescription{}, "", errors.New("wildcard with username") } - desc, err := GetDescription(group) + desc, err := GetDescription(hall) if err != nil { return UserDescription{}, "", err } @@ -651,20 +651,20 @@ func GetSanitisedUser(group, username string, wildcard bool) (UserDescription, s return u, makeETag(desc.fileSize, desc.modTime), nil } -func GetUserTag(group, username string, wildcard bool) (string, error) { - _, etag, err := GetSanitisedUser(group, username, wildcard) +func GetUserTag(hall, username string, wildcard bool) (string, error) { + _, etag, err := GetSanitisedUser(hall, username, wildcard) return etag, err } -func DeleteUser(group, username string, wildcard bool, etag string) error { +func DeleteUser(hall, username string, wildcard bool, etag string) error { if wildcard && username != "" { return errors.New("wildcard with username") } - groups.mu.Lock() - defer groups.mu.Unlock() + halls.mu.Lock() + defer halls.mu.Unlock() - desc, err := readDescription(group, false) + desc, err := readDescription(hall, false) if err != nil { return err } @@ -697,7 +697,7 @@ func DeleteUser(group, username string, wildcard bool, etag string) error { return rewriteDescriptionFile(desc.FileName, desc) } -func UpdateUser(group, username string, wildcard bool, etag string, user *UserDescription) error { +func UpdateUser(hall, username string, wildcard bool, etag string, user *UserDescription) error { if wildcard && username != "" { return errors.New("wildcard with username") } @@ -705,10 +705,10 @@ func UpdateUser(group, username string, wildcard bool, etag string, user *UserDe return errors.New("user description is not sanitised") } - groups.mu.Lock() - defer groups.mu.Unlock() + halls.mu.Lock() + defer halls.mu.Unlock() - desc, err := readDescription(group, false) + desc, err := readDescription(hall, false) if err != nil { return err } @@ -749,15 +749,15 @@ func UpdateUser(group, username string, wildcard bool, etag string, user *UserDe return rewriteDescriptionFile(desc.FileName, desc) } -func SetUserPassword(group, username string, wildcard bool, pw Password) error { +func SetUserPassword(hall, username string, wildcard bool, pw Password) error { if wildcard && username != "" { return errors.New("wildcard with username") } - groups.mu.Lock() - defer groups.mu.Unlock() + halls.mu.Lock() + defer halls.mu.Unlock() - desc, err := readDescription(group, false) + desc, err := readDescription(hall, false) if err != nil { return err } diff --git a/group/description_test.go b/hall/description_test.go similarity index 99% rename from group/description_test.go rename to hall/description_test.go index 00bd09c..24ec663 100644 --- a/group/description_test.go +++ b/hall/description_test.go @@ -1,4 +1,4 @@ -package group +package hall import ( "encoding/json" diff --git a/group/group.go b/hall/hall.go similarity index 85% rename from group/group.go rename to hall/hall.go index eb5fafc..9c0f8d0 100644 --- a/group/group.go +++ b/hall/hall.go @@ -1,4 +1,4 @@ -package group +package hall import ( "encoding/json" @@ -98,7 +98,7 @@ const ( MaxBitrate = 1024 * 1024 * 1024 ) -type Group struct { +type Hall struct { name string mu sync.Mutex @@ -110,11 +110,11 @@ type Group struct { data map[string]interface{} } -func (g *Group) Name() string { +func (g *Hall) Name() string { return g.name } -func (g *Group) Locked() (bool, string) { +func (g *Hall) Locked() (bool, string) { g.mu.Lock() defer g.mu.Unlock() if g.locked != nil { @@ -124,7 +124,7 @@ func (g *Group) Locked() (bool, string) { } } -func (g *Group) SetLocked(locked bool, message string) { +func (g *Hall) SetLocked(locked bool, message string) { g.mu.Lock() if locked { g.locked = &message @@ -139,13 +139,13 @@ func (g *Group) SetLocked(locked bool, message string) { } } -func (g *Group) Data() map[string]interface{} { +func (g *Hall) Data() map[string]interface{} { g.mu.Lock() defer g.mu.Unlock() return maps.Clone(g.data) } -func (g *Group) UpdateData(d map[string]interface{}) { +func (g *Hall) UpdateData(d map[string]interface{}) { g.mu.Lock() if g.data == nil { g.data = make(map[string]interface{}) @@ -165,19 +165,19 @@ func (g *Group) UpdateData(d map[string]interface{}) { } } -func (g *Group) Description() *Description { +func (g *Hall) Description() *Description { g.mu.Lock() defer g.mu.Unlock() return g.description } -func (g *Group) ClientCount() int { +func (g *Hall) ClientCount() int { g.mu.Lock() defer g.mu.Unlock() return len(g.clients) } -func (g *Group) mayExpire() bool { +func (g *Hall) mayExpire() bool { g.mu.Lock() defer g.mu.Unlock() @@ -190,12 +190,12 @@ func (g *Group) mayExpire() bool { return time.Since(g.timestamp) > maxHistoryAge(g.description) } -var groups struct { - mu sync.Mutex - groups map[string]*Group +var halls struct { + mu sync.Mutex + halls map[string]*Hall } -func (g *Group) API() (*webrtc.API, error) { +func (g *Hall) API() (*webrtc.API, error) { g.mu.Lock() codecs := g.description.Codecs g.mu.Unlock() @@ -432,7 +432,7 @@ func APIFromNames(names []string) (*webrtc.API, error) { return APIFromCodecs(codecs) } -func Add(name string, desc *Description) (*Group, error) { +func Add(name string, desc *Description) (*Hall, error) { g, notify, err := add(name, desc) for _, c := range notify { c.Joined(g.Name(), "change") @@ -440,7 +440,7 @@ func Add(name string, desc *Description) (*Group, error) { return g, err } -func validGroupName(name string) bool { +func validHallName(name string) bool { if strings.ContainsRune(name, '\\') { return false } @@ -458,21 +458,21 @@ func validGroupName(name string) bool { return s == "/"+name } -func add(name string, desc *Description) (*Group, []Client, error) { - if !validGroupName(name) { - return nil, nil, UserError("illegal group name") +func add(name string, desc *Description) (*Hall, []Client, error) { + if !validHallName(name) { + return nil, nil, UserError("illegal hall name") } - groups.mu.Lock() - defer groups.mu.Unlock() + halls.mu.Lock() + defer halls.mu.Unlock() - if groups.groups == nil { - groups.groups = make(map[string]*Group) + if halls.halls == nil { + halls.halls = make(map[string]*Hall) } var err error - g := groups.groups[name] + g := halls.halls[name] if g == nil { if desc == nil { desc, err = readDescription(name, true) @@ -481,13 +481,13 @@ func add(name string, desc *Description) (*Group, []Client, error) { } } - g = &Group{ + g = &Hall{ name: name, description: desc, clients: make(map[string]Client), timestamp: time.Now(), } - groups.groups[name] = g + halls.halls[name] = g } g.mu.Lock() @@ -503,7 +503,7 @@ func add(name string, desc *Description) (*Group, []Client, error) { desc, err = readDescription(name, true) if err != nil { if !errors.Is(err, os.ErrNotExist) { - log.Printf("Reading group %v: %v", name, err) + log.Printf("Reading hall %v: %v", name, err) } deleteUnlocked(g) return nil, nil, err @@ -521,11 +521,11 @@ func add(name string, desc *Description) (*Group, []Client, error) { return g, clients, nil } -func Range(f func(g *Group) bool) { - groups.mu.Lock() - defer groups.mu.Unlock() +func Range(f func(g *Hall) bool) { + halls.mu.Lock() + defer halls.mu.Unlock() - for _, g := range groups.groups { + for _, g := range halls.halls { ok := f(g) if !ok { break @@ -536,7 +536,7 @@ func Range(f func(g *Group) bool) { func GetNames() []string { names := make([]string, 0) - Range(func(g *Group) bool { + Range(func(g *Hall) bool { names = append(names, g.name) return true }) @@ -552,7 +552,7 @@ func GetSubGroups(parent string) []SubGroup { prefix := parent + "/" subgroups := make([]SubGroup, 0) - Range(func(g *Group) bool { + Range(func(g *Hall) bool { if strings.HasPrefix(g.name, prefix) { g.mu.Lock() count := len(g.clients) @@ -567,19 +567,19 @@ func GetSubGroups(parent string) []SubGroup { return subgroups } -func Get(name string) *Group { - groups.mu.Lock() - defer groups.mu.Unlock() - if groups.groups == nil { +func Get(name string) *Hall { + halls.mu.Lock() + defer halls.mu.Unlock() + if halls.halls == nil { return nil } - return groups.groups[name] + return halls.halls[name] } func Delete(name string) bool { - groups.mu.Lock() - defer groups.mu.Unlock() - g := groups.groups[name] + halls.mu.Lock() + defer halls.mu.Unlock() + g := halls.halls[name] if g == nil { return false } @@ -589,13 +589,13 @@ func Delete(name string) bool { return deleteUnlocked(g) } -// Called with both groups.mu and g.mu taken. -func deleteUnlocked(g *Group) bool { +// Called with both halls.mu and g.mu taken. +func deleteUnlocked(g *Hall) bool { if len(g.clients) != 0 { return false } - delete(groups.groups, g.name) + delete(halls.halls, g.name) return true } @@ -608,8 +608,8 @@ func member(v string, l []string) bool { return false } -func AddClient(group string, c Client, creds ClientCredentials) (*Group, error) { - g, err := Add(group, nil) +func AddClient(hall string, c Client, creds ClientCredentials) (*Hall, error) { + g, err := Add(hall, nil) if err != nil { return nil, err } @@ -632,7 +632,7 @@ func AddClient(group string, c Client, creds ClientCredentials) (*Group, error) if g.locked != nil { m := *g.locked if m == "" { - m = "this group is locked" + m = "this hall is locked" } return nil, UserError(m) } @@ -642,13 +642,13 @@ func AddClient(group string, c Client, creds ClientCredentials) (*Group, error) if g.description.NotBefore != nil && g.description.NotBefore.After(now) { return nil, UserError( - "this group is not open yet", + "this hall is not open yet", ) } if g.description.Expires != nil && g.description.Expires.Before(now) { return nil, UserError( - "this group is closed", + "this hall is closed", ) } } @@ -663,7 +663,7 @@ func AddClient(group string, c Client, creds ClientCredentials) (*Group, error) if !ops { return nil, UserError( "there are no operators " + - "in this group", + "in this hall", ) } } @@ -702,7 +702,7 @@ func AddClient(group string, c Client, creds ClientCredentials) (*Group, error) } // called locked -func autoLockKick(g *Group) { +func autoLockKick(g *Hall) { if !(g.description.Autolock && g.locked == nil) && !g.description.Autokick { return @@ -715,7 +715,7 @@ func autoLockKick(g *Group) { } } if g.description.Autolock && g.locked == nil { - m := "this group is locked" + m := "this hall is locked" g.locked = &m for _, c := range clients { c.Joined(g.Name(), "change") @@ -723,14 +723,14 @@ func autoLockKick(g *Group) { } if g.description.Autokick { - // we cannot call kickall, since it requires the group to + // we cannot call kickall, since it requires the hall to // be unlocked. And calling it asynchronously might // spuriously kick out an operator. go func(clients []Client) { for _, c := range clients { c.Kick( "", nil, - "there are no operators in this group", + "there are no operators in this hall", ) } }(g.getClientsUnlocked(nil)) @@ -738,7 +738,7 @@ func autoLockKick(g *Group) { } func DelClient(c Client) { - g := c.Group() + g := c.Hall() if g == nil { return } @@ -762,13 +762,13 @@ func DelClient(c Client) { autoLockKick(g) } -func (g *Group) GetClients(except Client) []Client { +func (g *Hall) GetClients(except Client) []Client { g.mu.Lock() defer g.mu.Unlock() return g.getClientsUnlocked(except) } -func (g *Group) getClientsUnlocked(except Client) []Client { +func (g *Hall) getClientsUnlocked(except Client) []Client { clients := make([]Client, 0, len(g.clients)) for _, c := range g.clients { if c != except { @@ -778,13 +778,13 @@ func (g *Group) getClientsUnlocked(except Client) []Client { return clients } -func (g *Group) GetClient(id string) Client { +func (g *Hall) GetClient(id string) Client { g.mu.Lock() defer g.mu.Unlock() return g.getClientUnlocked(id) } -func (g *Group) getClientUnlocked(id string) Client { +func (g *Hall) getClientUnlocked(id string) Client { for idd, c := range g.clients { if idd == id { return c @@ -793,7 +793,7 @@ func (g *Group) getClientUnlocked(id string) Client { return nil } -func (g *Group) Range(f func(c Client) bool) { +func (g *Hall) Range(f func(c Client) bool) { g.mu.Lock() defer g.mu.Unlock() for _, c := range g.clients { @@ -804,7 +804,7 @@ func (g *Group) Range(f func(c Client) bool) { } } -func kickall(g *Group, message string) { +func kickall(g *Hall, message string) { g.Range(func(c Client) bool { c.Kick("", nil, message) return true @@ -812,7 +812,7 @@ func kickall(g *Group, message string) { } func Shutdown(message string) { - Range(func(g *Group) bool { + Range(func(g *Hall) bool { g.SetLocked(true, message) kickall(g, message) return true @@ -823,7 +823,7 @@ type warner interface { Warn(oponly bool, message string) error } -func (g *Group) WallOps(message string) { +func (g *Hall) WallOps(message string) { clients := g.GetClients(nil) for _, c := range clients { w, ok := c.(warner) @@ -865,7 +865,7 @@ func deleteFunc[S ~[]E, E any](s S, f func(E) bool) S { return s[:i] } -func (g *Group) ClearChatHistory(id string, userId string) { +func (g *Hall) ClearChatHistory(id string, userId string) { g.mu.Lock() defer g.mu.Unlock() if id == "" && userId == "" { @@ -877,7 +877,7 @@ func (g *Group) ClearChatHistory(id string, userId string) { }) } -func (g *Group) AddToChatHistory(id, source string, user *string, time time.Time, kind string, value interface{}) { +func (g *Hall) AddToChatHistory(id, source string, user *string, time time.Time, kind string, value interface{}) { g.mu.Lock() defer g.mu.Unlock() @@ -905,7 +905,7 @@ func discardObsoleteHistory(h []ChatHistoryEntry, duration time.Duration) []Chat return h } -func (g *Group) GetChatHistory() []ChatHistoryEntry { +func (g *Hall) GetChatHistory() []ChatHistoryEntry { g.mu.Lock() defer g.mu.Unlock() @@ -993,7 +993,7 @@ func GetConfiguration() (*Configuration, error) { } // called locked -func (g *Group) getPasswordPermission(creds ClientCredentials) (Permissions, error) { +func (g *Hall) getPasswordPermission(creds ClientCredentials) (Permissions, error) { desc := g.description if creds.Username == nil { @@ -1024,14 +1024,14 @@ func (g *Group) getPasswordPermission(creds ClientCredentials) (Permissions, err // Return true if there is a user entry with the given username. // Always return false for an empty username. -func (g *Group) UserExists(username string) bool { +func (g *Hall) UserExists(username string) bool { g.mu.Lock() defer g.mu.Unlock() return g.userExists(username) } // called locked -func (g *Group) userExists(username string) bool { +func (g *Hall) userExists(username string) bool { desc := g.description if desc.Users == nil { return false @@ -1046,11 +1046,11 @@ func (g *Group) userExists(username string) bool { // usernames might lead to security vulnaribilities. // For now, we just do the minimal validation that avoids path traversal. func validUsername(username string) bool { - return username == "" || validGroupName(username) + return username == "" || validHallName(username) } // called locked -func (g *Group) getPermission(creds ClientCredentials) (string, []string, error) { +func (g *Hall) getPermission(creds ClientCredentials) (string, []string, error) { desc := g.description var username string var perms []string @@ -1096,7 +1096,7 @@ func (g *Group) getPermission(creds ClientCredentials) (string, []string, error) return username, perms, nil } -func (g *Group) GetPermission(creds ClientCredentials) (string, []string, error) { +func (g *Hall) GetPermission(creds ClientCredentials) (string, []string, error) { g.mu.Lock() defer g.mu.Unlock() return g.getPermission(creds) @@ -1116,10 +1116,10 @@ type Status struct { CanChangePassword bool `json:"canChangePassword,omitempty"` } -// Status returns a group's status. -// Base is the base URL for groups; if omitted, then both the Location and +// Status returns a hall's status. +// Base is the base URL for halls; if omitted, then both the Location and // Endpoint members are omitted from the result. -func (g *Group) Status(authentified bool, base *url.URL) Status { +func (g *Hall) Status(authentified bool, base *url.URL) Status { desc := g.Description() if desc.Redirect != "" { @@ -1141,7 +1141,7 @@ func (g *Group) Status(authentified bool, base *url.URL) Status { Scheme: base.Scheme, Host: base.Host, Path: path.Join( - path.Join(base.Path, "/group/"), + path.Join(base.Path, "/hall/"), g.Name()) + "/", } location = l.String() @@ -1181,7 +1181,7 @@ func (g *Group) Status(authentified bool, base *url.URL) Status { func GetPublic(base *url.URL) []Status { gs := make([]Status, 0) - Range(func(g *Group) bool { + Range(func(g *Hall) bool { if g.Description().Public { gs = append(gs, g.Status(false, base)) } @@ -1193,9 +1193,9 @@ func GetPublic(base *url.URL) []Status { return gs } -// Update checks that all in-memory groups are up-to-date and updates the -// list of public groups. It also removes from memory any non-public -// groups that haven't been accessed in maxHistoryAge. +// Update checks that all in-memory halls are up-to-date and updates the +// list of public halls. It also removes from memory any non-public +// halls that haven't been accessed in maxHistoryAge. func Update() { _, err := GetConfiguration() if err != nil { @@ -1214,11 +1214,11 @@ func Update() { deleted := false if g.mayExpire() { - // Delete checks if the group is still empty + // Delete checks if the hall is still empty deleted = Delete(name) } - // update group description + // update hall description if !deleted { Add(name, nil) } @@ -1228,14 +1228,14 @@ func Update() { Directory, func(path string, d fs.DirEntry, err error) error { if err != nil { - log.Printf("Group file %v: %v", path, err) + log.Printf("Hall file %v: %v", path, err) return nil } if d.IsDir() { base := filepath.Base(path) if base[0] == '.' { log.Printf( - "Ignoring group directory %v", + "Ignoring hall directory %v", path, ) return fs.SkipDir @@ -1244,25 +1244,25 @@ func Update() { } filename, err := filepath.Rel(Directory, path) if err != nil { - log.Printf("Group file %v: %v", path, err) + log.Printf("Hall file %v: %v", path, err) return nil } if !strings.HasSuffix(filename, ".json") { log.Printf( - "Unexpected extension for group file %v", + "Unexpected extension for hall file %v", path, ) return nil } base := filepath.Base(filename) if base[0] == '.' { - log.Printf("Ignoring group file %v", filename) + log.Printf("Ignoring hall file %v", filename) return nil } name := strings.TrimSuffix(filename, ".json") desc, err := GetDescription(name) if err != nil { - log.Printf("Group file %v: %v", path, err) + log.Printf("Hall file %v: %v", path, err) return nil } if desc.Public { @@ -1273,6 +1273,6 @@ func Update() { ) if err != nil { - log.Printf("Couldn't read groups: %v", err) + log.Printf("Couldn't read halls: %v", err) } } diff --git a/group/group_test.go b/hall/hall_test.go similarity index 93% rename from group/group_test.go rename to hall/hall_test.go index 018592c..59ffd45 100644 --- a/group/group_test.go +++ b/hall/hall_test.go @@ -1,4 +1,4 @@ -package group +package hall import ( "encoding/json" @@ -35,21 +35,21 @@ func TestConstantTimeCompare(t *testing.T) { } func TestGroup(t *testing.T) { - groups.groups = nil - Add("group", &Description{}) - Add("group/subgroup", &Description{Public: true}) - if len(groups.groups) != 2 { - t.Errorf("Expected 2, got %v", len(groups.groups)) + halls.halls = nil + Add("hall", &Description{}) + Add("hall/subgroup", &Description{Public: true}) + if len(halls.halls) != 2 { + t.Errorf("Expected 2, got %v", len(halls.halls)) } - g := Get("group") - g2 := Get("group/subgroup") + g := Get("hall") + g2 := Get("hall/subgroup") if g == nil { - t.Fatalf("Couldn't get group") + t.Fatalf("Couldn't get hall") } if g2 == nil { - t.Fatalf("Couldn't get group/subgroup") + t.Fatalf("Couldn't get hall/subgroup") } - if name := g.Name(); name != "group" { + if name := g.Name(); name != "hall" { t.Errorf("Name: expected group1, got %v", name) } if locked, _ := g.Locked(); locked { @@ -64,17 +64,17 @@ func TestGroup(t *testing.T) { t.Errorf("Expected 2, got %v", names) } - if subs := GetSubGroups("group"); len(subs) != 0 { + if subs := GetSubGroups("hall"); len(subs) != 0 { t.Errorf("Expected [], got %v", subs) } - if public := GetPublic(nil); len(public) != 1 || public[0].Name != "group/subgroup" { - t.Errorf("Expected group/subgroup, got %v", public) + if public := GetPublic(nil); len(public) != 1 || public[0].Name != "hall/subgroup" { + t.Errorf("Expected hall/subgroup, got %v", public) } } func TestChatHistory(t *testing.T) { - g := Group{ + g := Hall{ description: &Description{}, } user := "user" @@ -213,7 +213,7 @@ var goodClients = []credPerm{ } func TestPermissions(t *testing.T) { - var g Group + var g Hall err := json.Unmarshal([]byte(desc2JSON), &g.description) if err != nil { t.Fatalf("unmarshal: %v", err) @@ -299,7 +299,7 @@ func TestExtraPermissions(t *testing.T) { } func TestUsernameTaken(t *testing.T) { - var g Group + var g Hall err := json.Unmarshal([]byte(desc2JSON), &g.description) if err != nil { t.Fatalf("unmarshal: %v", err) @@ -345,7 +345,7 @@ func TestFmtpValue(t *testing.T) { } } -func TestValidGroupName(t *testing.T) { +func TestValidHallName(t *testing.T) { type nameTest struct { name string result bool @@ -367,7 +367,7 @@ func TestValidGroupName(t *testing.T) { } for _, test := range tests { - r := validGroupName(test.name) + r := validHallName(test.name) if r != test.result { t.Errorf("Valid %v: got %v, expected %v", test.name, r, test.result) diff --git a/rtpconn/rtpconn.go b/rtpconn/rtpconn.go index 2c9224d..54db4ee 100644 --- a/rtpconn/rtpconn.go +++ b/rtpconn/rtpconn.go @@ -17,7 +17,7 @@ import ( "git.stormux.org/storm/skald/codecs" "git.stormux.org/storm/skald/conn" "git.stormux.org/storm/skald/estimator" - "git.stormux.org/storm/skald/group" + "git.stormux.org/storm/skald/hall" "git.stormux.org/storm/skald/ice" "git.stormux.org/storm/skald/jitter" "git.stormux.org/storm/skald/packetcache" @@ -193,8 +193,8 @@ func (down *rtpDownConnection) getTracks() []*rtpDownTrack { return tracks } -func newDownConn(c group.Client, id string, remote conn.Up) (*rtpDownConnection, error) { - api, err := c.Group().API() +func newDownConn(c hall.Client, id string, remote conn.Up) (*rtpDownConnection, error) { + api, err := c.Hall().API() if err != nil { return nil, err } @@ -504,7 +504,7 @@ func (up *rtpUpTrack) hasRtcpFb(tpe, parameter string) bool { type rtpUpConnection struct { id string - client group.Client + client hall.Client label string pc *webrtc.PeerConnection iceCandidates []*webrtc.ICECandidateInit @@ -599,7 +599,7 @@ func (up *rtpUpConnection) flushICECandidates() error { } // pushConnNow pushes a connection to all of the clients in a group -func pushConnNow(up *rtpUpConnection, g *group.Group, cs []group.Client) { +func pushConnNow(up *rtpUpConnection, g *hall.Hall, cs []hall.Client) { up.mu.Lock() up.pushed = true replace := up.replace @@ -616,12 +616,12 @@ func pushConnNow(up *rtpUpConnection, g *group.Group, cs []group.Client) { } // pushConn schedules a call to pushConnNow -func pushConn(up *rtpUpConnection, g *group.Group, cs []group.Client) { +func pushConn(up *rtpUpConnection, g *hall.Hall, cs []hall.Client) { up.mu.Lock() up.pushed = false up.mu.Unlock() - go func(g *group.Group, cs []group.Client) { + go func(g *hall.Hall, cs []hall.Client) { time.Sleep(200 * time.Millisecond) up.mu.Lock() pushed := up.pushed @@ -633,14 +633,14 @@ func pushConn(up *rtpUpConnection, g *group.Group, cs []group.Client) { }(g, cs) } -func newUpConn(c group.Client, id string, label string, offer string) (*rtpUpConnection, error) { +func newUpConn(c hall.Client, id string, label string, offer string) (*rtpUpConnection, error) { var o sdp.SessionDescription err := o.Unmarshal([]byte(offer)) if err != nil { return nil, err } - api, err := c.Group().API() + api, err := c.Hall().API() if err != nil { return nil, err } @@ -686,10 +686,10 @@ func newUpConn(c group.Client, id string, label string, offer string) (*rtpUpCon up.mu.Unlock() - pushConn(up, c.Group(), c.Group().GetClients(c)) + pushConn(up, c.Hall(), c.Hall().GetClients(c)) }) - pushConn(up, c.Group(), c.Group().GetClients(c)) + pushConn(up, c.Hall(), c.Hall().GetClients(c)) go rtcpUpSender(up) return up, nil @@ -890,7 +890,7 @@ func sadd(x, y uint64) uint64 { func maxUpBitrate(t *rtpUpTrack) uint64 { minrate := ^uint64(0) - maxrate := uint64(group.MinBitrate) + maxrate := uint64(hall.MinBitrate) maxsid := 0 maxtid := 0 local := t.getLocal() @@ -902,8 +902,8 @@ func maxUpBitrate(t *rtpUpTrack) uint64 { if maxtid < tid { maxtid = tid } - if r < group.MinBitrate { - r = group.MinBitrate + if r < hall.MinBitrate { + r = hall.MinBitrate } if minrate > r { minrate = r @@ -997,14 +997,14 @@ func sendUpRTCP(up *rtpUpConnection) error { if t.Kind() == webrtc.RTPCodecTypeAudio { rate = sadd(rate, 100*1024) } else if t.Label() == "l" { - rate = sadd(rate, group.LowBitrate) + rate = sadd(rate, hall.LowBitrate) } else { rate = sadd(rate, maxUpBitrate(t)) } } - if rate > group.MaxBitrate { - rate = group.MaxBitrate + if rate > hall.MaxBitrate { + rate = hall.MaxBitrate } if len(ssrcs) > 0 { packets = append(packets, diff --git a/rtpconn/webclient.go b/rtpconn/webclient.go index 80889f4..377164d 100644 --- a/rtpconn/webclient.go +++ b/rtpconn/webclient.go @@ -20,7 +20,7 @@ import ( "git.stormux.org/storm/skald/conn" "git.stormux.org/storm/skald/diskwriter" "git.stormux.org/storm/skald/estimator" - "git.stormux.org/storm/skald/group" + "git.stormux.org/storm/skald/hall" "git.stormux.org/storm/skald/ice" "git.stormux.org/storm/skald/token" "git.stormux.org/storm/skald/unbounded" @@ -33,7 +33,7 @@ func errorToWSCloseMessage(id string, err error) (*clientMessage, []byte) { switch e := err.(type) { case *websocket.CloseError: code = websocket.CloseNormalClosure - case group.ProtocolError: + case hall.ProtocolError: code = websocket.CloseProtocolError m = &clientMessage{ Type: "usermessage", @@ -43,7 +43,7 @@ func errorToWSCloseMessage(id string, err error) (*clientMessage, []byte) { Value: e.Error(), } text = e.Error() - case group.UserError, group.KickError: + case hall.UserError, hall.KickError: code = websocket.CloseNormalClosure m = errorMessage(id, err) text = e.Error() @@ -60,7 +60,7 @@ func isWSNormalError(err error) bool { } type webClient struct { - group *group.Group + hall *hall.Hall addr net.Addr id string username string @@ -77,8 +77,8 @@ type webClient struct { up map[string]*rtpUpConnection } -func (c *webClient) Group() *group.Group { - return c.group +func (c *webClient) Hall() *hall.Hall { + return c.hall } func (c *webClient) Addr() net.Addr { @@ -130,7 +130,7 @@ type clientMessage struct { Token string `json:"token,omitempty"` Privileged bool `json:"privileged,omitempty"` Permissions []string `json:"permissions,omitempty"` - Status *group.Status `json:"status,omitempty"` + Status *hall.Status `json:"status,omitempty"` Data map[string]interface{} `json:"data,omitempty"` Group string `json:"group,omitempty"` Value interface{} `json:"value,omitempty"` @@ -229,7 +229,7 @@ func delUpConn(c *webClient, id string, userId string, push bool) error { replace := conn.getReplace(false) delete(c.up, id) - g := c.group + g := c.hall c.mu.Unlock() conn.mu.Lock() @@ -386,9 +386,9 @@ func addDownTrackUnlocked(conn *rtpDownConnection, remoteTrack *rtpUpTrack) erro // replace the RTCP feedback types with the ones we understand remoteCodec := remoteTrack.Codec() if strings.HasPrefix(strings.ToLower(remoteCodec.MimeType), "video/") { - remoteCodec.RTCPFeedback = group.VideoRTCPFeedback + remoteCodec.RTCPFeedback = hall.VideoRTCPFeedback } else { - remoteCodec.RTCPFeedback = group.AudioRTCPFeedback + remoteCodec.RTCPFeedback = hall.AudioRTCPFeedback } local, err := webrtc.NewTrackLocalStaticRTP( @@ -408,7 +408,7 @@ func addDownTrackUnlocked(conn *rtpDownConnection, remoteTrack *rtpUpTrack) erro } codec := local.Codec() - ptype, err := group.CodecPayloadType(local.Codec()) + ptype, err := hall.CodecPayloadType(local.Codec()) if err != nil { log.Printf("Couldn't determine ptype for codec %v: %v", codec.MimeType, err) @@ -726,31 +726,31 @@ func parseRequested(r interface{}) (map[string][]string, error) { } func (c *webClient) setRequested(requested map[string][]string) error { - if c.group == nil { + if c.hall == nil { return errors.New("attempted to request with no group joined") } c.requested = requested - requestConns(c, c.group, "") + requestConns(c, c.hall, "") return nil } func (c *webClient) setRequestedStream(down *rtpDownConnection, requested []string) error { - var remoteClient group.Client + var remoteClient hall.Client remote, ok := down.remote.(*rtpUpConnection) if ok { remoteClient = remote.client } down.requested = requested - return remoteClient.RequestConns(c, c.group, remote.id) + return remoteClient.RequestConns(c, c.hall, remote.id) } -func (c *webClient) RequestConns(target group.Client, g *group.Group, id string) error { +func (c *webClient) RequestConns(target hall.Client, g *hall.Hall, id string) error { c.action(requestConnsAction{g, target, id}) return nil } -func requestConns(target group.Client, g *group.Group, id string) { +func requestConns(target hall.Client, g *hall.Hall, id string) { clients := g.GetClients(target) for _, c := range clients { c.RequestConns(target, g, id) @@ -817,7 +817,7 @@ func requestedTracks(c *webClient, requested []string, tracks []conn.UpTrack) ([ return ts, limitSid } -func (c *webClient) PushConn(g *group.Group, id string, up conn.Up, tracks []conn.UpTrack, replace string) error { +func (c *webClient) PushConn(g *hall.Hall, id string, up conn.Up, tracks []conn.UpTrack, replace string) error { c.action(pushConnAction{g, id, up, tracks, replace}) return nil } @@ -854,7 +854,7 @@ func StartClient(conn *websocket.Conn, addr net.Addr) (err error) { ), ) conn.Close() - err = group.ProtocolError("client didn't handshake") + err = hall.ProtocolError("client didn't handshake") return } @@ -883,7 +883,7 @@ func StartClient(conn *websocket.Conn, addr net.Addr) (err error) { m, e := errorToWSCloseMessage(c.id, err) if isWSNormalError(err) { err = nil - } else if _, ok := err.(group.KickError); ok { + } else if _, ok := err.(hall.KickError); ok { err = nil } if m != nil { @@ -896,7 +896,7 @@ func StartClient(conn *websocket.Conn, addr net.Addr) (err error) { } type pushConnAction struct { - group *group.Group + hall *hall.Hall id string conn conn.Up tracks []conn.UpTrack @@ -904,8 +904,8 @@ type pushConnAction struct { } type requestConnsAction struct { - group *group.Group - target group.Client + hall *hall.Hall + target hall.Client id string } @@ -939,7 +939,7 @@ type kickAction struct { message string } -var errEmptyId = group.ProtocolError("empty id") +var errEmptyId = hall.ProtocolError("empty id") func member(v string, l []string) bool { for _, w := range l { @@ -1108,14 +1108,14 @@ func pushDownConn(c *webClient, id string, up conn.Up, tracks []conn.UpTrack, re func handleAction(c *webClient, a any) error { switch a := a.(type) { case pushConnAction: - if c.group == nil || c.group != a.group { + if c.hall == nil || c.hall != a.hall { log.Printf("Got connectsions for wrong group") return nil } return pushDownConn(c, a.id, a.conn, a.tracks, a.replace) case requestConnsAction: - g := c.group - if g == nil || a.group != g { + g := c.hall + if g == nil || a.hall != g { log.Printf("Misdirected pushConns") return nil } @@ -1148,7 +1148,7 @@ func handleAction(c *webClient, a any) error { tracks[i] = t.remote } c.PushConn( - c.group, + c.hall, down.remote.Id(), down.remote, tracks, "", ) @@ -1163,8 +1163,8 @@ func handleAction(c *webClient, a any) error { } case pushClientAction: - if a.group != c.group.Name() { - log.Printf("got client for wrong group") + if a.group != c.hall.Name() { + log.Printf("got client for wrong hall") return nil } perms := append([]string(nil), a.permissions...) @@ -1178,11 +1178,11 @@ func handleAction(c *webClient, a any) error { Data: a.data, }) case joinedAction: - var status *group.Status + var status *hall.Status var data map[string]interface{} - var g *group.Group + var g *hall.Hall if a.group != "" { - g = group.Get(a.group) + g = hall.Get(a.group) if g != nil { s := g.Status(true, nil) status = &s @@ -1230,7 +1230,7 @@ func handleAction(c *webClient, a any) error { switch a.kind { case "op": c.permissions = addnew("op", c.permissions) - g := c.Group() + g := c.Hall() if g != nil && g.Description().AllowRecording { c.permissions = addnew("record", c.permissions) } @@ -1246,11 +1246,11 @@ func handleAction(c *webClient, a any) error { case "unshutup": c.permissions = addnew("message", c.permissions) default: - return group.UserError("unknown permission") + return hall.UserError("unknown permission") } c.action(permissionsChangedAction{}) case permissionsChangedAction: - g := c.Group() + g := c.Hall() if g == nil { return errors.New("Permissions changed in no group") } @@ -1284,7 +1284,7 @@ func handleAction(c *webClient, a any) error { user := c.Username() d := c.Data() clients := g.GetClients(nil) - go func(clients []group.Client) { + go func(clients []hall.Client) { for _, cc := range clients { cc.PushClient( g.Name(), "change", id, user, perms, d, @@ -1292,7 +1292,7 @@ func handleAction(c *webClient, a any) error { } }(clients) case kickAction: - return group.KickError{ + return hall.KickError{ a.id, a.username, a.message, } default: @@ -1313,7 +1313,7 @@ func failUpConnection(c *webClient, id string, message string) error { } } if message != "" { - err := c.error(group.UserError(message)) + err := c.error(hall.UserError(message)) if err != nil { return err } @@ -1322,7 +1322,7 @@ func failUpConnection(c *webClient, id string, message string) error { } func leaveGroup(c *webClient) { - if c.group == nil { + if c.hall == nil { return } @@ -1337,11 +1337,11 @@ func leaveGroup(c *webClient) { } } - group.DelClient(c) + hall.DelClient(c) c.permissions = nil c.data = nil c.requested = make(map[string][]string) - c.group = nil + c.hall = nil } func closeDownConn(c *webClient, id string, message string) error { @@ -1357,7 +1357,7 @@ func closeDownConn(c *webClient, id string, message string) error { return err } if message != "" { - err := c.error(group.UserError(message)) + err := c.error(hall.UserError(message)) if err != nil { return err } @@ -1375,10 +1375,10 @@ func (c *webClient) Joined(group, kind string) error { return nil } -func kickClient(g *group.Group, id string, user *string, dest string, message string) error { +func kickClient(g *hall.Hall, id string, user *string, dest string, message string) error { client := g.GetClient(dest) if client == nil { - return group.UserError("no such user") + return hall.UserError("no such user") } return client.Kick(id, user, message) @@ -1387,14 +1387,14 @@ func kickClient(g *group.Group, id string, user *string, dest string, message st func handleClientMessage(c *webClient, m clientMessage) error { if m.Source != "" { if m.Source != c.Id() { - return group.ProtocolError("spoofed client id") + return hall.ProtocolError("spoofed client id") } } if m.Type != "join" { if m.Username != nil { if *m.Username != c.Username() { - return group.ProtocolError("spoofed username") + return hall.ProtocolError("spoofed username") } } } @@ -1402,25 +1402,25 @@ func handleClientMessage(c *webClient, m clientMessage) error { switch m.Type { case "join": if m.Kind == "leave" { - if c.group == nil || c.group.Name() != m.Group { - return group.UserError("you are not joined") + if c.hall == nil || c.hall.Name() != m.Group { + return hall.UserError("you are not joined") } leaveGroup(c) return nil } if m.Kind != "join" { - return group.ProtocolError("unknown kind") + return hall.ProtocolError("unknown kind") } - if c.group != nil { - return group.ProtocolError( + if c.hall != nil { + return hall.ProtocolError( "cannot join multiple groups", ) } c.data = m.Data - g, err := group.AddClient(m.Group, c, - group.ClientCredentials{ + g, err := hall.AddClient(m.Group, c, + hall.ClientCredentials{ Username: m.Username, Password: m.Password, Token: m.Token, @@ -1428,11 +1428,11 @@ func handleClientMessage(c *webClient, m clientMessage) error { ) if err != nil { var e, s string - var autherr *group.NotAuthorisedError + var autherr *hall.NotAuthorisedError if errors.Is(err, token.ErrUsernameRequired) { s = err.Error() e = "need-username" - } else if errors.Is(err, group.ErrDuplicateUsername) { + } else if errors.Is(err, hall.ErrDuplicateUsername) { s = err.Error() e = "duplicate-username" } else if errors.As(err, &autherr) { @@ -1441,7 +1441,7 @@ func handleClientMessage(c *webClient, m clientMessage) error { log.Printf("Join group: %v", err) } else if errors.Is(err, os.ErrNotExist) { s = "group does not exist" - } else if _, ok := err.(group.UserError); ok { + } else if _, ok := err.(hall.UserError); ok { s = err.Error() } else { s = "internal server error" @@ -1469,7 +1469,7 @@ func handleClientMessage(c *webClient, m clientMessage) error { Value: redirect, }) } - c.group = g + c.hall = g case "request": requested, err := parseRequested(m.Request) if err != nil { @@ -1498,7 +1498,7 @@ func handleClientMessage(c *webClient, m clientMessage) error { Type: "abort", Id: m.Id, }) - return c.error(group.UserError("not authorised")) + return c.error(hall.UserError("not authorised")) } err := gotOffer(c, m.Id, m.Label, m.SDP, m.Replace) if err != nil { @@ -1565,16 +1565,16 @@ func handleClientMessage(c *webClient, m clientMessage) error { return errEmptyId } if m.Candidate == nil { - return group.ProtocolError("null candidate") + return hall.ProtocolError("null candidate") } err := gotICE(c, m.Candidate, m.Id) if err != nil { log.Printf("ICE: %v", err) } case "chat", "usermessage": - g := c.group + g := c.hall if g == nil { - return c.error(group.UserError("join a group first")) + return c.error(hall.UserError("join a group first")) } required := "message" @@ -1582,7 +1582,7 @@ func handleClientMessage(c *webClient, m clientMessage) error { required = "caption" } if !member(required, c.permissions) { - return c.error(group.UserError("not authorised")) + return c.error(hall.UserError("not authorised")) } id := m.Id @@ -1614,7 +1614,7 @@ func handleClientMessage(c *webClient, m clientMessage) error { Value: m.Value, } if m.Dest == "" { - var except group.Client + var except hall.Client if m.NoEcho { except = c } @@ -1625,38 +1625,38 @@ func handleClientMessage(c *webClient, m clientMessage) error { } else { cc := g.GetClient(m.Dest) if cc == nil { - return c.error(group.UserError("user unknown")) + return c.error(hall.UserError("user unknown")) } ccc, ok := cc.(*webClient) if !ok { - return c.error(group.UserError( + return c.error(hall.UserError( "this user doesn't chat", )) } ccc.write(mm) } case "groupaction": - g := c.group + g := c.hall if g == nil { - return c.error(group.UserError("join a group first")) + return c.error(hall.UserError("join a group first")) } switch m.Kind { case "clearchat": if !member("op", c.permissions) { - return c.error(group.UserError("not authorised")) + return c.error(hall.UserError("not authorised")) } var id, userId string if m.Value != nil { value, ok := m.Value.(map[string]any) if !ok { - return c.error(group.UserError( + return c.error(hall.UserError( "bad value in clearchat", )) } id, _ = value["id"].(string) userId, _ = value["userId"].(string) if userId == "" && id != "" { - return c.error(group.UserError( + return c.error(hall.UserError( "bad value in clearchat", )) } @@ -1674,7 +1674,7 @@ func handleClientMessage(c *webClient, m clientMessage) error { } case "lock", "unlock": if !member("op", c.permissions) { - return c.error(group.UserError("not authorised")) + return c.error(hall.UserError("not authorised")) } message := "" v, ok := m.Value.(string) @@ -1684,17 +1684,17 @@ func handleClientMessage(c *webClient, m clientMessage) error { g.SetLocked(m.Kind == "lock", message) case "record": if !member("record", c.permissions) { - return c.error(group.UserError("not authorised")) + return c.error(hall.UserError("not authorised")) } for _, cc := range g.GetClients(c) { _, ok := cc.(*diskwriter.Client) if ok { - return c.error(group.UserError("already recording")) + return c.error(hall.UserError("already recording")) } } disk := diskwriter.New(g) - _, err := group.AddClient(g.Name(), disk, - group.ClientCredentials{ + _, err := hall.AddClient(g.Name(), disk, + hall.ClientCredentials{ System: true, }, ) @@ -1702,24 +1702,24 @@ func handleClientMessage(c *webClient, m clientMessage) error { disk.Close() return c.error(err) } - requestConns(disk, c.group, "") + requestConns(disk, c.hall, "") case "unrecord": if !member("record", c.permissions) { - return c.error(group.UserError("not authorised")) + return c.error(hall.UserError("not authorised")) } for _, cc := range g.GetClients(c) { disk, ok := cc.(*diskwriter.Client) if ok { disk.Close() - group.DelClient(disk) + hall.DelClient(disk) } } case "subgroups": if !member("op", c.permissions) { - return c.error(group.UserError("not authorised")) + return c.error(hall.UserError("not authorised")) } s := "" - for _, sg := range group.GetSubGroups(g.Name()) { + for _, sg := range hall.GetSubGroups(g.Name()) { plural := "" if sg.Clients > 1 { plural = "s" @@ -1737,11 +1737,11 @@ func handleClientMessage(c *webClient, m clientMessage) error { }) case "setdata": if !member("op", c.permissions) { - return c.error(group.UserError("not authorised")) + return c.error(hall.UserError("not authorised")) } data, ok := m.Value.(map[string]interface{}) if !ok { - return c.error(group.UserError( + return c.error(hall.UserError( "Bad value in setdata", )) } @@ -1773,7 +1773,7 @@ func handleClientMessage(c *webClient, m clientMessage) error { return terror("error", "client specified token") } - if tok.Group != c.group.Name() { + if tok.Group != c.hall.Name() { return terror("error", "wrong group in token") } if tok.IncludeSubgroups { @@ -1786,7 +1786,7 @@ func handleClientMessage(c *webClient, m clientMessage) error { } if tok.Username != nil && - c.group.UserExists(*tok.Username) { + c.hall.UserExists(*tok.Username) { return terror("error", "that username is taken") } @@ -1881,7 +1881,7 @@ func handleClientMessage(c *webClient, m clientMessage) error { !member("token", c.permissions) { return terror("not-authorised", "not authorised") } - tokens, _, err := token.List(c.group.Name()) + tokens, _, err := token.List(c.hall.Name()) if err != nil { return terror("error", err.Error()) } @@ -1892,37 +1892,37 @@ func handleClientMessage(c *webClient, m clientMessage) error { Value: tokens, }) default: - return group.UserError("unknown group action") + return hall.UserError("unknown group action") } case "useraction": - g := c.group + g := c.hall if g == nil { - return c.error(group.UserError("join a group first")) + return c.error(hall.UserError("join a group first")) } switch m.Kind { case "op", "unop", "present", "unpresent", "shutup", "unshutup": if !member("op", c.permissions) { - return c.error(group.UserError("not authorised")) + return c.error(hall.UserError("not authorised")) } t := g.GetClient(m.Dest) if t == nil { - return c.error(group.UserError("no such user")) + return c.error(hall.UserError("no such user")) } target, ok := t.(*webClient) if !ok { - return c.error(group.UserError( + return c.error(hall.UserError( "this is not a real user", )) } target.action(changePermissionsAction{m.Kind}) case "identify": if !member("op", c.permissions) { - return c.error(group.UserError("not authorised")) + return c.error(hall.UserError("not authorised")) } d := g.GetClient(m.Dest) if d == nil { return c.error( - group.UserError("client not found"), + hall.UserError("client not found"), ) } value := make(map[string]any) @@ -1950,7 +1950,7 @@ func handleClientMessage(c *webClient, m clientMessage) error { }) case "kick": if !member("op", c.permissions) { - return c.error(group.UserError("not authorised")) + return c.error(hall.UserError("not authorised")) } message := "" v, ok := m.Value.(string) @@ -1963,11 +1963,11 @@ func handleClientMessage(c *webClient, m clientMessage) error { } case "setdata": if m.Dest != c.Id() { - return c.error(group.UserError("not authorised")) + return c.error(hall.UserError("not authorised")) } data, ok := m.Value.(map[string]interface{}) if !ok { - return c.error(group.UserError( + return c.error(hall.UserError( "Bad value in setdata", )) } @@ -1985,7 +1985,7 @@ func handleClientMessage(c *webClient, m clientMessage) error { user := c.Username() perms := c.Permissions() data = c.Data() - go func(clients []group.Client) { + go func(clients []hall.Client) { for _, cc := range clients { cc.PushClient( g.Name(), "change", @@ -1994,7 +1994,7 @@ func handleClientMessage(c *webClient, m clientMessage) error { } }(g.GetClients(nil)) default: - return group.UserError("unknown user action") + return hall.UserError("unknown user action") } case "pong": // nothing @@ -2004,7 +2004,7 @@ func handleClientMessage(c *webClient, m clientMessage) error { }) default: log.Printf("unexpected message: %v", m.Type) - return group.ProtocolError("unexpected message") + return hall.ProtocolError("unexpected message") } return nil } @@ -2199,7 +2199,7 @@ func (c *webClient) write(m clientMessage) error { } } -func broadcast(cs []group.Client, m clientMessage) error { +func broadcast(cs []hall.Client, m clientMessage) error { b, err := json.Marshal(m) if err != nil { return err @@ -2228,7 +2228,7 @@ func (c *webClient) close(data []byte) error { func errorMessage(id string, err error) *clientMessage { switch e := err.(type) { - case group.UserError: + case hall.UserError: return &clientMessage{ Type: "usermessage", Kind: "error", @@ -2236,7 +2236,7 @@ func errorMessage(id string, err error) *clientMessage { Privileged: true, Value: e.Error(), } - case group.KickError: + case hall.KickError: message := e.Message if message == "" { message = "you have been kicked out" diff --git a/rtpconn/whipclient.go b/rtpconn/whipclient.go index 6ff2ce3..9c3d84a 100644 --- a/rtpconn/whipclient.go +++ b/rtpconn/whipclient.go @@ -7,7 +7,7 @@ import ( "sync" "git.stormux.org/storm/skald/conn" - "git.stormux.org/storm/skald/group" + "git.stormux.org/storm/skald/hall" "git.stormux.org/storm/skald/sdpfrag" "github.com/pion/sdp/v3" @@ -15,7 +15,7 @@ import ( ) type WhipClient struct { - group *group.Group + hall *hall.Hall addr net.Addr id string token string @@ -27,12 +27,12 @@ type WhipClient struct { etag string } -func NewWhipClient(g *group.Group, id string, token string, addr net.Addr) *WhipClient { - return &WhipClient{group: g, id: id, token: token, addr: addr} +func NewWhipClient(g *hall.Hall, id string, token string, addr net.Addr) *WhipClient { + return &WhipClient{hall: g, id: id, token: token, addr: addr} } -func (c *WhipClient) Group() *group.Group { - return c.group +func (c *WhipClient) Hall() *hall.Hall { + return c.hall } func (c *WhipClient) Addr() net.Addr { @@ -83,12 +83,12 @@ func (c *WhipClient) SetETag(etag string) { c.etag = etag } -func (c *WhipClient) PushConn(g *group.Group, id string, conn conn.Up, tracks []conn.UpTrack, replace string) error { +func (c *WhipClient) PushConn(g *hall.Hall, id string, conn conn.Up, tracks []conn.UpTrack, replace string) error { return nil } -func (c *WhipClient) RequestConns(target group.Client, g *group.Group, id string) error { - if g != c.group { +func (c *WhipClient) RequestConns(target hall.Client, g *hall.Hall, id string) error { + if g != c.hall { return nil } @@ -122,7 +122,7 @@ func (c *WhipClient) Kick(id string, user *string, message string) error { func (c *WhipClient) Close() error { c.mu.Lock() defer c.mu.Unlock() - g := c.group + g := c.hall if g == nil { return nil } @@ -136,8 +136,8 @@ func (c *WhipClient) Close() error { } c.connection = nil } - group.DelClient(c) - c.group = nil + hall.DelClient(c) + c.hall = nil return nil } diff --git a/skald-api.md b/skald-api.md index d5e2189..41d4825 100644 --- a/skald-api.md +++ b/skald-api.md @@ -1,6 +1,6 @@ # Skald's administrative API -Skald provides an HTTP-based API that can be used to create groups and +Skald provides an HTTP-based API that can be used to create halls and users. For example, in order to create a hall, a client may do PUT /skald-api/v0/.halls/hallname/ @@ -39,11 +39,11 @@ Provides a number of statistics about the running server, in JSON. The exact format is undocumented, and may change between versions. The only allowed methods are HEAD and GET. -### List of groups +### List of halls /skald-api/v0/.halls/ -Returns a list of groups, as a JSON array. The only allowed methods are +Returns a list of halls, as a JSON array. The only allowed methods are HEAD and GET. ### Group definition diff --git a/skald-install.md b/skald-install.md index bec06cb..b47a625 100644 --- a/skald-install.md +++ b/skald-install.md @@ -56,17 +56,17 @@ The following instructions assume that your server is called `skald.example.org` and that you have already created a dedicated user called `skald`. -First, make sure that the `groups` and `data` directories exist: +First, make sure that the `halls` and `data` directories exist: ```sh -mkdir groups data +mkdir halls data ``` Now copy the `skald` binary, and the directories `static`, `data` and -`groups` to the server: +`halls` to the server: ```sh -rsync -a skald static data groups skald@skald.example.org: +rsync -a skald static data halls skald@skald.example.org: ``` If you don't have a TLS certificate, Skald will generate a self-signed diff --git a/skald.go b/skald.go index 45f094f..1c42de6 100644 --- a/skald.go +++ b/skald.go @@ -15,7 +15,7 @@ import ( "time" "git.stormux.org/storm/skald/diskwriter" - "git.stormux.org/storm/skald/group" + "git.stormux.org/storm/skald/hall" "git.stormux.org/storm/skald/ice" "git.stormux.org/storm/skald/limit" "git.stormux.org/storm/skald/token" @@ -32,10 +32,10 @@ func main() { "web server root `directory`") flag.BoolVar(&webserver.Insecure, "insecure", false, "act as an HTTP server rather than HTTPS") - flag.StringVar(&group.DataDirectory, "data", "./data/", + flag.StringVar(&hall.DataDirectory, "data", "./data/", "data `directory`") - flag.StringVar(&group.Directory, "groups", "./groups/", - "group description `directory`") + flag.StringVar(&hall.Directory, "halls", "./halls/", + "hall description `directory`") flag.StringVar(&diskwriter.Directory, "recordings", "./recordings/", "recordings `directory`") flag.StringVar(&cpuprofile, "cpuprofile", "", @@ -46,7 +46,7 @@ func main() { "store mutex profile in `file`") flag.StringVar(&udpRange, "udp-range", "", "UDP `port` (multiplexing) or port1-port2 (range)") - flag.BoolVar(&group.UseMDNS, "mdns", false, "gather mDNS addresses") + flag.BoolVar(&hall.UseMDNS, "mdns", false, "gather mDNS addresses") flag.BoolVar(&ice.ICERelayOnly, "relay-only", false, "require use of TURN relays for all media traffic") flag.StringVar(&turnserver.Address, "turn", "auto", @@ -63,14 +63,14 @@ func main() { if n != 2 || min <= 0 || max <= 0 || min > max { log.Fatalf("UDP range: bad range") } - group.UDPMin = min - group.UDPMax = max + hall.UDPMin = min + hall.UDPMax = max } else { port, err := strconv.Atoi(udpRange) if err != nil { log.Fatalf("UDP: %v", err) } - err = group.SetUDPMux(port) + err = hall.SetUDPMux(port) if err != nil { log.Fatalf("UDP: %v", err) } @@ -122,22 +122,22 @@ func main() { log.Printf("File descriptor limit is %v, please increase it!", n) } - ice.ICEFilename = filepath.Join(group.DataDirectory, "ice-servers.json") + ice.ICEFilename = filepath.Join(hall.DataDirectory, "ice-servers.json") token.SetStatefulFilename( filepath.Join( - filepath.Join(group.DataDirectory, "var"), + filepath.Join(hall.DataDirectory, "var"), "tokens.jsonl", ), ) - // make sure the list of public groups is updated early - go group.Update() + // make sure the list of public halls is updated early + go hall.Update() // causes the built-in server to start if required ice.Update() defer turnserver.Stop() - err = webserver.Serve(httpAddr, group.DataDirectory) + err = webserver.Serve(httpAddr, hall.DataDirectory) if err != nil { log.Fatalf("Server: %v", err) } @@ -157,7 +157,7 @@ func main() { select { case <-ticker.C: go func() { - group.Update() + hall.Update() token.Expire() }() case <-slowTicker.C: diff --git a/skald.md b/skald.md index 2747ca7..bc180fc 100644 --- a/skald.md +++ b/skald.md @@ -177,23 +177,23 @@ fields are as follows: will be redirected to the canonical one. -## Group definitions +## Hall definitions -Groups are described by JSON files in the `groups/` directory. These +Halls are described by JSON files in the `halls/` directory. These files are normally administered using the `skaldctl` utility, but may also be edited manually (there is no need to restart the server). -### Managing groups using `skaldctl` +### Managing halls using `skaldctl` -#### Creating, modifying, and deleting groups +#### Creating, modifying, and deleting halls -A group is created using `skaldctl create-hall`: +A hall is created using `skaldctl create-hall`: ```sh skaldctl create-hall -hall city-watch ``` -There are a number of options to customise the behaviour of the group, see +There are a number of options to customise the behaviour of the hall, see `skaldctl create-hall -help` for a full list. For example, in order to create a hall that allows unrestricted creation of tokens, say: @@ -203,30 +203,30 @@ skaldctl create-hall -hall city-watch -unrestricted-tokens For more advanced configuration, `skaldctl create-hall` can be invoked with the `-json` flag, in which case it takes a JSON template from standard -input. The syntax of a JSON template is just like that of a group + input. The syntax of a JSON template is just like that of a hall definition file (see below), except that it must not contain the fields `users` and `wildcard-user`. For example, in order to create a redirect -(see the section *Group description reference* below): +(see the section *Hall description reference* below): ```sh echo '{"redirect": "https://skald.example.org:8443/hall/city-watch/"}' | skaldctl create-hall -hall amcw -json ``` -Groups are modified using `skaldctl update-hall`: +Halls are modified using `skaldctl update-hall`: ```sh skaldctl update-hall -hall city-watch -unrestricted-tokens=false ``` If a JSON template is provided to `skaldctl update-hall`, then it is -merged with the existing group configuration. Entries may be deleted +merged with the existing hall configuration. Entries may be deleted by setting them to `null` in the template: ```sh echo '{"redirect": null}' | skaldctl update-hall -hall amcw ``` -A group is deleted using `skaldctl delete-hall`: +A hall is deleted using `skaldctl delete-hall`: ```sh skaldctl delete-hall -hall amcw @@ -266,7 +266,7 @@ skaldctl create-user -hall city-watch -wildcard skaldctl set-password -hall city-watch -wildcard ``` -For open groups, where any user can login with any password, the wildcard +For open halls, where any user can login with any password, the wildcard user's password is set to the password of type `wildcard`: ```sh @@ -278,17 +278,17 @@ password types. #### Automatic subgroups -It is sometimes necessary to create a large number of identical groups. +It is sometimes necessary to create a large number of identical halls. For example, the author has been using Skald to supervise computer -science practicals, where up to 40 students are working in groups of two. +science practicals, where up to 40 students are working in pairs. -While it is possible to automate the creation of groups by accessing +While it is possible to automate the creation of halls by accessing Skald's API, by scripting calls to skaldctl, or by generating files -directly under `groups/`, Skald provides a facility called *automatic -subgroups* that can be used to generate groups on demand. +directly under `halls/`, Skald provides a facility called *automatic +subgroups* that can be used to generate halls on demand. Automatic subgroups are enabled by setting the `"auto-subgroups"` -field in the group description: +field in the hall description: ```sh skaldctl create-hall unseen-university -auto-subgroups @@ -297,7 +297,7 @@ skaldctl create-hall unseen-university -auto-subgroups Whenever a user attempts to access a subgroup of `unseen-university`, for example `unseen-university/hex`, the group is created in memory and persists until it is empty and its chat history has expired. The main -group's operator can view the list of populated subgroups with the command +hall's operator can view the list of populated subgroups with the command `/subgroups`. #### Managing tokens @@ -313,26 +313,26 @@ skaldctl list-tokens -l -hall city-watch ``` A token that is generated with the `-include-subgroups` flag applies to -the whole hierarchy rooted at the given group, including both ordinary -groups and automatically generated subgroups. +the whole hierarchy rooted at the given hall, including both ordinary +halls and automatically generated subgroups. ```sh skaldctl create-token -hall city-watch -include-subgroups ``` -Such a token can be attached to the root of the group hierarchy, and -therefore be valid for any group on the server: +Such a token can be attached to the root of the hall hierarchy, and +therefore be valid for any hall on the server: ```sh skaldctl create-token -hall '' -include-subgroups ``` -### Group description reference +### Hall description reference -The definition for the group called *hallname* is in the file -`groups/hallname.json`; it does not contain the group name, which makes +The definition for the hall called *hallname* is in the file +`halls/hallname.json`; it does not contain the hall name, which makes it easy to copy or link hall definitions. You may use subdirectories: -a file `groups/teaching/networking.json` defines a group called +a file `halls/teaching/networking.json` defines a hall called *teaching/networking*. Every hall definition file contains a single JSON dictionary. All fields @@ -479,7 +479,7 @@ allows any username with any password. ### Hashed passwords For security reasons, passwords are usually hashed before being stored in -group descriptions (in fact, the `skaldctl` utility does not even support +hall descriptions (in fact, the `skaldctl` utility does not even support storing plaintext passwords). A hashed password is represented as a JSON dictionary with a field `type` and a number of type-specific fields. @@ -501,7 +501,7 @@ A user entry with a hashed password looks like this: ``` Hashed passwords are normally generated transparently to the user by the -`skaldctl set-password` command. When editing group description files +`skaldctl set-password` command. When editing hall description files manually, hashed passwords can be generated with the `skaldctl hash-password` utility. diff --git a/skaldctl/skaldctl.go b/skaldctl/skaldctl.go index 4a1e1dc..4f32a30 100644 --- a/skaldctl/skaldctl.go +++ b/skaldctl/skaldctl.go @@ -26,7 +26,7 @@ import ( "golang.org/x/crypto/pbkdf2" "golang.org/x/term" - "git.stormux.org/storm/skald/group" + "git.stormux.org/storm/skald/hall" "git.stormux.org/storm/skald/token" ) @@ -229,11 +229,11 @@ func readConfig(filename string) (configuration, error) { return config, nil } -func makePassword(pw string, algorithm string, iterations, length, saltlen, cost int) (group.Password, error) { +func makePassword(pw string, algorithm string, iterations, length, saltlen, cost int) (hall.Password, error) { salt := make([]byte, saltlen) _, err := rand.Read(salt) if err != nil { - return group.Password{}, err + return hall.Password{}, err } switch algorithm { @@ -242,7 +242,7 @@ func makePassword(pw string, algorithm string, iterations, length, saltlen, cost []byte(pw), salt, iterations, length, sha256.New, ) encoded := hex.EncodeToString(key) - return group.Password{ + return hall.Password{ Type: "pbkdf2", Hash: "sha-256", Key: &encoded, @@ -254,11 +254,11 @@ func makePassword(pw string, algorithm string, iterations, length, saltlen, cost []byte(pw), cost, ) if err != nil { - return group.Password{}, err + return hall.Password{}, err } k := string(key) - return group.Password{ + return hall.Password{ Type: "bcrypt", Key: &k, }, nil @@ -269,11 +269,11 @@ func makePassword(pw string, algorithm string, iterations, length, saltlen, cost "must be the empty string", ) } - return group.Password{ + return hall.Password{ Type: "wildcard", }, nil default: - return group.Password{}, errors.New("unknown password type") + return hall.Password{}, errors.New("unknown password type") } } @@ -392,18 +392,18 @@ func initialSetupCmd(cmdname string, args []string) { defer skaldConfig.Close() defer skaldctlConfig.Close() - var users map[string]group.UserDescription + var users map[string]hall.UserDescription if adminPassword != "" { pw, err := makePassword(adminPassword, "bcrypt", 0, 0, 0, 12) if err != nil { log.Fatalf("makePassword: %v", err) } - perms, err := group.NewPermissions("admin") + perms, err := hall.NewPermissions("admin") if err != nil { log.Fatalf("NewPermissions: %v", err) } - users = map[string]group.UserDescription{ + users = map[string]hall.UserDescription{ adminUsername: { Password: pw, Permissions: perms, @@ -411,7 +411,7 @@ func initialSetupCmd(cmdname string, args []string) { } } - config := group.Configuration{ + config := hall.Configuration{ WritableGroups: true, Users: users, } @@ -963,7 +963,7 @@ func parsePermissions(p string, expand bool) (any, error) { if !expand { return p, nil } - pp, err := group.NewPermissions(p) + pp, err := hall.NewPermissions(p) if err != nil { return nil, err } @@ -985,7 +985,7 @@ func formatRawPermissions(permissions []string) string { return fmt.Sprintf("[%s]", perms) } -func formatPermissions(permissions group.Permissions) string { +func formatPermissions(permissions hall.Permissions) string { s := permissions.String() if len(s) > 0 && s[0] != '[' { return s @@ -1056,7 +1056,7 @@ func listUsersCmd(cmdname string, args []string) { fmt.Printf("%-12s (ERROR=%v)\n", user, err) continue } - var d group.UserDescription + var d hall.UserDescription _, err = getJSON(uu, &d) if err != nil { fmt.Printf("%-12s (ERROR=%v)\n", user, err) diff --git a/skaldctl/skaldctl_test.go b/skaldctl/skaldctl_test.go index 5fe8356..eaa30ae 100644 --- a/skaldctl/skaldctl_test.go +++ b/skaldctl/skaldctl_test.go @@ -4,11 +4,11 @@ import ( "encoding/json" "testing" - "git.stormux.org/storm/skald/group" + "git.stormux.org/storm/skald/hall" ) func TestMakePassword(t *testing.T) { - doit := func(pw group.Password) { + doit := func(pw hall.Password) { ok, _ := pw.Match("secret") if !ok { t.Errorf("%v didn't match", pw) @@ -50,7 +50,7 @@ func TestFormatPermissions(t *testing.T) { {`[]`, "[]", "[]"}, } for _, test := range tests { - var p group.Permissions + var p hall.Permissions err := json.Unmarshal([]byte(test.j), &p) if err != nil { t.Errorf("Unmarshal %#v: %v", test.j, err) diff --git a/static/skald.js b/static/skald.js index a975c49..b2fa1e9 100644 --- a/static/skald.js +++ b/static/skald.js @@ -4712,7 +4712,7 @@ async function start() { if(groupStatus.name) { group = groupStatus.name; } else { - console.warn("no group name in status"); + console.warn("no hall name in status"); group = decodeURIComponent( location.pathname.replace(/^\/[a-z]*\//, '').replace(/\/$/, ''), ); diff --git a/stats/stats.go b/stats/stats.go index 7eeb535..e1685b7 100644 --- a/stats/stats.go +++ b/stats/stats.go @@ -5,7 +5,7 @@ import ( "sort" "time" - "git.stormux.org/storm/skald/group" + "git.stormux.org/storm/skald/hall" ) type GroupStats struct { @@ -59,11 +59,11 @@ type Track struct { } func GetGroups() []GroupStats { - names := group.GetNames() + names := hall.GetNames() gs := make([]GroupStats, 0, len(names)) for _, name := range names { - g := group.Get(name) + g := hall.Get(name) if g == nil { continue } diff --git a/todo.txt b/todo.txt index ecbbd84..d3cc437 100644 --- a/todo.txt +++ b/todo.txt @@ -62,13 +62,13 @@ X Search for remaining Galene names and classify each survivor as attribution/hi # PHASE 2: HALL TERMINOLOGY AND DATA MODEL # ============================================================================ -- Rename Go package group/ to hall/ if the package-wide rename stays tractable -- Rename package declarations, types, variables, funcs, tests, and comments from group to hall -- Rename Group type to Hall and update all compile errors intentionally -- Rename group description files/concepts to hall description files/concepts -- Rename default directory flag: -groups -> -halls -- Rename default data directory: groups/ -> halls/ -- Rename public listing concept from public groups to public halls +X Rename Go package group/ to hall/ if the package-wide rename stays tractable +X Rename package declarations, types, variables, funcs, tests, and comments from group to hall +X Rename Group type to Hall and update all compile errors intentionally +X Rename group description files/concepts to hall description files/concepts +X Rename default directory flag: -groups -> -halls +X Rename default data directory: groups/ -> halls/ +X Rename public listing concept from public groups to public halls - Rename subgroups to subhalls everywhere, including JSON fields and commands - Rename auto-subgroups -> auto-subhalls - Rename allow-subgroups -> allow-subhalls diff --git a/webserver/api.go b/webserver/api.go index ab6da32..2715a0a 100644 --- a/webserver/api.go +++ b/webserver/api.go @@ -13,7 +13,7 @@ import ( "golang.org/x/crypto/bcrypt" - "git.stormux.org/storm/skald/group" + "git.stormux.org/storm/skald/hall" "git.stormux.org/storm/skald/stats" "git.stormux.org/storm/skald/token" ) @@ -47,7 +47,7 @@ func checkPasswordAdmin(w http.ResponseWriter, r *http.Request, hallname, user s } } if ok && !wildcard && username == user { - desc, err := group.GetDescription(hallname) + desc, err := hall.GetDescription(hallname) if err != nil { internalError(w, "Get description for group %v: %v", @@ -193,7 +193,7 @@ func apiGroupHandler(w http.ResponseWriter, r *http.Request, pth string) { methodNotAllowed(w, "HEAD, GET") return } - groups, err := group.GetDescriptionNames() + groups, err := hall.GetDescriptionNames() if err != nil { httpError(w, err) return @@ -233,7 +233,7 @@ func apiGroupHandler(w http.ResponseWriter, r *http.Request, pth string) { } if r.Method == "HEAD" || r.Method == "GET" { - desc, etag, err := group.GetSanitisedDescription(g) + desc, etag, err := hall.GetSanitisedDescription(g) if err != nil { httpError(w, err) return @@ -249,7 +249,7 @@ func apiGroupHandler(w http.ResponseWriter, r *http.Request, pth string) { sendJSON(w, r, desc) return } else if r.Method == "PUT" { - etag, err := group.GetDescriptionTag(g) + etag, err := hall.GetDescriptionTag(g) if errors.Is(err, os.ErrNotExist) { err = nil etag = "" @@ -263,12 +263,12 @@ func apiGroupHandler(w http.ResponseWriter, r *http.Request, pth string) { return } - var newdesc group.Description + var newdesc hall.Description done = getJSON(w, r, &newdesc) if done { return } - err = group.UpdateDescription(g, etag, &newdesc) + err = hall.UpdateDescription(g, etag, &newdesc) if err != nil { httpError(w, err) return @@ -280,7 +280,7 @@ func apiGroupHandler(w http.ResponseWriter, r *http.Request, pth string) { } return } else if r.Method == "DELETE" { - etag, err := group.GetDescriptionTag(g) + etag, err := hall.GetDescriptionTag(g) if err != nil { httpError(w, err) return @@ -290,7 +290,7 @@ func apiGroupHandler(w http.ResponseWriter, r *http.Request, pth string) { if done { return } - err = group.DeleteDescription(g, etag) + err = hall.DeleteDescription(g, etag) if err != nil { httpError(w, err) return @@ -318,7 +318,7 @@ func usersHandler(w http.ResponseWriter, r *http.Request, g, pth string) { methodNotAllowed(w, "HEAD, GET") return } - users, etag, err := group.GetUsers(g) + users, etag, err := hall.GetUsers(g) if err != nil { httpError(w, err) return @@ -371,7 +371,7 @@ func userHandler(w http.ResponseWriter, r *http.Request, g, user string, wildcar } if r.Method == "HEAD" || r.Method == "GET" { - user, etag, err := group.GetSanitisedUser(g, user, wildcard) + user, etag, err := hall.GetSanitisedUser(g, user, wildcard) if err != nil { httpError(w, err) return @@ -384,7 +384,7 @@ func userHandler(w http.ResponseWriter, r *http.Request, g, user string, wildcar sendJSON(w, r, user) return } else if r.Method == "PUT" { - etag, err := group.GetUserTag(g, user, wildcard) + etag, err := hall.GetUserTag(g, user, wildcard) if errors.Is(err, os.ErrNotExist) { etag = "" err = nil @@ -398,12 +398,12 @@ func userHandler(w http.ResponseWriter, r *http.Request, g, user string, wildcar return } - var newdesc group.UserDescription + var newdesc hall.UserDescription done = getJSON(w, r, &newdesc) if done { return } - err = group.UpdateUser(g, user, wildcard, etag, &newdesc) + err = hall.UpdateUser(g, user, wildcard, etag, &newdesc) if err != nil { httpError(w, err) return @@ -415,7 +415,7 @@ func userHandler(w http.ResponseWriter, r *http.Request, g, user string, wildcar } return } else if r.Method == "DELETE" { - etag, err := group.GetUserTag(g, user, wildcard) + etag, err := hall.GetUserTag(g, user, wildcard) if err != nil { httpError(w, err) return @@ -426,7 +426,7 @@ func userHandler(w http.ResponseWriter, r *http.Request, g, user string, wildcar return } - err = group.DeleteUser(g, user, wildcard, etag) + err = hall.DeleteUser(g, user, wildcard, etag) if err != nil { httpError(w, err) return @@ -447,12 +447,12 @@ func passwordHandler(w http.ResponseWriter, r *http.Request, g, user string, wil } if r.Method == "PUT" { - var pw group.Password + var pw hall.Password done := getJSON(w, r, &pw) if done { return } - err := group.SetUserPassword(g, user, wildcard, pw) + err := hall.SetUserPassword(g, user, wildcard, pw) if err != nil { httpError(w, err) return @@ -471,11 +471,11 @@ func passwordHandler(w http.ResponseWriter, r *http.Request, g, user string, wil } k := string(key) - pw := group.Password{ + pw := hall.Password{ Type: "bcrypt", Key: &k, } - err = group.SetUserPassword(g, user, wildcard, pw) + err = hall.SetUserPassword(g, user, wildcard, pw) if err != nil { httpError(w, err) return @@ -483,7 +483,7 @@ func passwordHandler(w http.ResponseWriter, r *http.Request, g, user string, wil w.WriteHeader(http.StatusNoContent) return } else if r.Method == "DELETE" { - err := group.SetUserPassword(g, user, wildcard, group.Password{}) + err := hall.SetUserPassword(g, user, wildcard, hall.Password{}) if err != nil { httpError(w, err) return @@ -528,7 +528,7 @@ func keysHandler(w http.ResponseWriter, r *http.Request, g string) { httpError(w, err) return } - err = group.SetKeys(g, keys.Keys) + err = hall.SetKeys(g, keys.Keys) if err != nil { httpError(w, err) return @@ -536,7 +536,7 @@ func keysHandler(w http.ResponseWriter, r *http.Request, g string) { w.WriteHeader(http.StatusNoContent) return } else if r.Method == "DELETE" { - err := group.SetKeys(g, nil) + err := hall.SetKeys(g, nil) if err != nil { httpError(w, err) return @@ -563,7 +563,7 @@ func tokensHandler(w http.ResponseWriter, r *http.Request, g, pth string) { if g != "" { // check that the group exists - _, err := group.GetDescription(g) + _, err := hall.GetDescription(g) if err != nil { httpError(w, err) return diff --git a/webserver/api_test.go b/webserver/api_test.go index d635529..4889029 100644 --- a/webserver/api_test.go +++ b/webserver/api_test.go @@ -15,7 +15,7 @@ import ( "path/filepath" "testing" - "git.stormux.org/storm/skald/group" + "git.stormux.org/storm/skald/hall" "git.stormux.org/storm/skald/token" ) @@ -34,8 +34,8 @@ func setup() { func setupTest(dir, datadir string) error { setup() - group.Directory = dir - group.DataDirectory = datadir + hall.Directory = dir + hall.DataDirectory = datadir config := `{ "writableGroups": true, "users": { @@ -45,7 +45,7 @@ func setupTest(dir, datadir string) error { } } }` - f, err := os.Create(filepath.Join(group.DataDirectory, "config.json")) + f, err := os.Create(filepath.Join(hall.DataDirectory, "config.json")) if err != nil { return err } @@ -142,7 +142,7 @@ func TestApi(t *testing.T) { t.Errorf("Create hall: %v %v", err, resp.StatusCode) } - var desc *group.Description + var desc *hall.Description err = getJSON("/skald-api/v0/.halls/test/", &desc) if err != nil || len(desc.Users) != 0 { t.Errorf("Get hall: %v", err) @@ -223,7 +223,7 @@ func TestApi(t *testing.T) { t.Errorf("Set password (POST): %v %v", err, resp.StatusCode) } - var user group.UserDescription + var user hall.UserDescription err = getJSON("/skald-api/v0/.halls/test/.users/jch", &user) if err != nil { t.Errorf("Get user: %v", err) @@ -232,7 +232,7 @@ func TestApi(t *testing.T) { t.Errorf("User not sanitised properly") } - desc, err = group.GetDescription("test") + desc, err = hall.GetDescription("test") if err != nil { t.Errorf("GetDescription: %v", err) } @@ -251,7 +251,7 @@ func TestApi(t *testing.T) { t.Errorf("Delete hall: %v %v", err, resp.StatusCode) } - desc, err = group.GetDescription("test") + desc, err = hall.GetDescription("test") if err != nil { t.Errorf("GetDescription: %v", err) } @@ -272,7 +272,7 @@ func TestApi(t *testing.T) { t.Errorf("Get wildcard user: %v", err) } - desc, err = group.GetDescription("test") + desc, err = hall.GetDescription("test") if err != nil { t.Errorf("GetDescription: %v", err) } @@ -287,7 +287,7 @@ func TestApi(t *testing.T) { t.Errorf("Delete wildcard user: %v %v", err, resp.StatusCode) } - desc, err = group.GetDescription("test") + desc, err = hall.GetDescription("test") if err != nil { t.Errorf("GetDescription: %v", err) } @@ -414,7 +414,7 @@ func TestApi(t *testing.T) { t.Errorf("Delete hall: %v %v", err, resp.StatusCode) } - _, err = group.GetDescription("test") + _, err = hall.GetDescription("test") if !errors.Is(err, os.ErrNotExist) { t.Errorf("Group exists after delete") } @@ -451,7 +451,7 @@ func TestApiBadAuth(t *testing.T) { do("GET", "/skald-api/v0/.halls/") do("PUT", "/skald-api/v0/.halls/test/") - f, err := os.Create(filepath.Join(group.Directory, "test.json")) + f, err := os.Create(filepath.Join(hall.Directory, "test.json")) if err != nil { t.Fatalf("Create(test.json): %v", err) } diff --git a/webserver/webserver.go b/webserver/webserver.go index 7db26ed..3a20bc1 100644 --- a/webserver/webserver.go +++ b/webserver/webserver.go @@ -22,7 +22,7 @@ import ( "github.com/gorilla/websocket" "git.stormux.org/storm/skald/diskwriter" - "git.stormux.org/storm/skald/group" + "git.stormux.org/storm/skald/hall" "git.stormux.org/storm/skald/rtpconn" "github.com/jech/cert" ) @@ -64,7 +64,7 @@ func Serve(address string, dataDir string) error { } } s.RegisterOnShutdown(func() { - group.Shutdown("server is shutting down") + hall.Shutdown("server is shutting down") }) server = s @@ -130,11 +130,11 @@ func httpError(w http.ResponseWriter, err error) { notFound(w) return } - if errors.Is(err, group.ErrUnknownPermission) { + if errors.Is(err, hall.ErrUnknownPermission) { http.Error(w, "unknown permission", http.StatusBadRequest) return } - var autherr *group.NotAuthorisedError + var autherr *hall.NotAuthorisedError if errors.As(err, &autherr) { log.Printf("HTTP server error: %v", err) http.Error(w, "not authorised", http.StatusUnauthorized) @@ -160,7 +160,7 @@ const ( ) func redirect(w http.ResponseWriter, r *http.Request) bool { - conf, err := group.GetConfiguration() + conf, err := hall.GetConfiguration() if err != nil || conf.CanonicalHost == "" { return false } @@ -285,7 +285,7 @@ func serveFile(w http.ResponseWriter, r *http.Request, p string) { http.ServeContent(w, r, fi.Name(), fi.ModTime(), f) } -func parseGroupName(prefix string, p string) string { +func parseHallName(prefix string, p string) string { if !strings.HasPrefix(p, prefix) { return "" } @@ -346,13 +346,13 @@ func groupHandler(w http.ResponseWriter, r *http.Request) { return } - name := parseGroupName("/hall/", r.URL.Path) + name := parseHallName("/hall/", r.URL.Path) if name == "" { notFound(w) return } - g, err := group.Add(name, nil) + g, err := hall.Add(name, nil) if err != nil { httpError(w, err) return @@ -375,7 +375,7 @@ func groupHandler(w http.ResponseWriter, r *http.Request) { } func baseURL(r *http.Request) (*url.URL, error) { - conf, err := group.GetConfiguration() + conf, err := hall.GetConfiguration() if err != nil { return nil, err } @@ -415,13 +415,13 @@ func groupStatusHandler(w http.ResponseWriter, r *http.Request) { internalError(w, "groupStatusHandler: this shouldn't happen") return } - name := parseGroupName("/hall/", pth) + name := parseHallName("/hall/", pth) if name == "" { notFound(w) return } - g, err := group.Add(name, nil) + g, err := hall.Add(name, nil) if err != nil { httpError(w, err) return @@ -458,13 +458,13 @@ func publicHandler(w http.ResponseWriter, r *http.Request) { return } - g := group.GetPublic(base) + g := hall.GetPublic(base) e := json.NewEncoder(w) e.Encode(g) } func adminMatch(username, password string) (bool, error) { - conf, err := group.GetConfiguration() + conf, err := hall.GetConfiguration() if err != nil { return false, err } @@ -512,7 +512,7 @@ func CheckOrigin(w http.ResponseWriter, r *http.Request, admin bool) bool { if err == nil && strings.EqualFold(o.Host, r.Host) { ok = true } else { - conf, err := group.GetConfiguration() + conf, err := hall.GetConfiguration() if err != nil { return false } @@ -613,20 +613,20 @@ func recordingsHandler(w http.ResponseWriter, r *http.Request) { for len(p) > 0 && p[len(p)-1] == '/' { p = p[:len(p)-1] } - group = parseGroupName("/", p) + group = parseHallName("/", p) if group == "" { - http.Error(w, "Bad group name", http.StatusBadRequest) + http.Error(w, "Bad hall name", http.StatusBadRequest) return } } else { if p[len(p)-1] == '/' { - http.Error(w, "Bad group name", http.StatusBadRequest) + http.Error(w, "Bad hall name", http.StatusBadRequest) return } group, filename = path.Split(p) - group = parseGroupName("/", group) + group = parseHallName("/", group) if group == "" { - http.Error(w, "Bad group name", http.StatusBadRequest) + http.Error(w, "Bad hall name", http.StatusBadRequest) return } } @@ -711,13 +711,13 @@ func checkGroupPermissions(w http.ResponseWriter, r *http.Request, hallname stri return false } - g := group.Get(hallname) + g := hall.Get(hallname) if g == nil { return false } _, p, err := g.GetPermission( - group.ClientCredentials{ + hall.ClientCredentials{ Username: &user, Password: pass, }, @@ -732,7 +732,7 @@ func checkGroupPermissions(w http.ResponseWriter, r *http.Request, hallname stri } } if err != nil || !record { - var autherr *group.NotAuthorisedError + var autherr *hall.NotAuthorisedError if errors.As(err, &autherr) { time.Sleep(200 * time.Millisecond) } diff --git a/webserver/webserver_test.go b/webserver/webserver_test.go index b400135..bac810e 100644 --- a/webserver/webserver_test.go +++ b/webserver/webserver_test.go @@ -10,10 +10,10 @@ import ( "github.com/pion/webrtc/v4" - "git.stormux.org/storm/skald/group" + "git.stormux.org/storm/skald/hall" ) -func TestParseGroupName(t *testing.T) { +func TestParseHallName(t *testing.T) { a := []struct{ p, g string }{ {"", ""}, {"/foo", ""}, @@ -29,7 +29,7 @@ func TestParseGroupName(t *testing.T) { } for _, pg := range a { - g := parseGroupName("/hall/", pg.p) + g := parseHallName("/hall/", pg.p) if g != pg.g { t.Errorf("Path %v, got %v, expected %v", pg.p, g, pg.g) @@ -58,10 +58,10 @@ func TestBase(t *testing.T) { } dir := t.TempDir() - group.DataDirectory = dir + hall.DataDirectory = dir for _, v := range a { - conf := group.Configuration{ + conf := hall.Configuration{ ProxyURL: v.p, } c, err := json.Marshal(conf) @@ -200,7 +200,7 @@ func TestFormatICEServer(t *testing.T) { func TestMatchAdmin(t *testing.T) { d := t.TempDir() - group.DataDirectory = d + hall.DataDirectory = d filename := filepath.Join(d, "config.json") f, err := os.Create(filename) diff --git a/webserver/whip.go b/webserver/whip.go index 6a6e204..e5467f8 100644 --- a/webserver/whip.go +++ b/webserver/whip.go @@ -17,7 +17,7 @@ import ( "github.com/pion/webrtc/v4" - "git.stormux.org/storm/skald/group" + "git.stormux.org/storm/skald/hall" "git.stormux.org/storm/skald/ice" "git.stormux.org/storm/skald/rtpconn" "git.stormux.org/storm/skald/sdpfrag" @@ -153,13 +153,13 @@ func whipEndpointHandler(w http.ResponseWriter, r *http.Request) { return } - name := parseGroupName("/hall/", pth) + name := parseHallName("/hall/", pth) if name == "" { notFound(w) return } - g, err := group.Add(name, nil) + g, err := hall.Add(name, nil) if err != nil { httpError(w, err) return @@ -199,7 +199,7 @@ func whipEndpointHandler(w http.ResponseWriter, r *http.Request) { token := parseBearerToken(r.Header.Get("Authorization")) whip := "whip" - creds := group.ClientCredentials{ + creds := hall.ClientCredentials{ Username: &whip, Token: token, } @@ -221,7 +221,7 @@ func whipEndpointHandler(w http.ResponseWriter, r *http.Request) { c := rtpconn.NewWhipClient(g, id, token, addr) - _, err = group.AddClient(g.Name(), c, creds) + _, err = hall.AddClient(g.Name(), c, creds) if err != nil { log.Printf("WHIP: %v", err) httpError(w, err) @@ -229,7 +229,7 @@ func whipEndpointHandler(w http.ResponseWriter, r *http.Request) { } if !canPresent(c.Permissions()) { - group.DelClient(c) + hall.DelClient(c) http.Error(w, "Forbidden", http.StatusForbidden) return } @@ -238,7 +238,7 @@ func whipEndpointHandler(w http.ResponseWriter, r *http.Request) { answer, err := c.NewConnection(r.Context(), body) if err != nil { - group.DelClient(c) + hall.DelClient(c) log.Printf("WHIP offer: %v", err) httpError(w, err) return @@ -269,13 +269,13 @@ func whipResourceHandler(w http.ResponseWriter, r *http.Request) { return } - name := parseGroupName("/hall/", pth) + name := parseHallName("/hall/", pth) if name == "" { notFound(w) return } - g := group.Get(name) + g := hall.Get(name) if g == nil { notFound(w) return @@ -295,7 +295,7 @@ func whipResourceHandler(w http.ResponseWriter, r *http.Request) { if t := c.Token(); t != "" { token := parseBearerToken(r.Header.Get("Authorization")) - if !group.ConstantTimeCompare(t, token) { + if !hall.ConstantTimeCompare(t, token) { http.Error(w, "Forbidden", http.StatusForbidden) return }