vcs spec and setup

This commit is contained in:
Richard 2023-11-23 22:38:07 +01:00
parent 51817c9f39
commit d3ff302e7a
3 changed files with 222 additions and 1 deletions

View File

@ -12,7 +12,7 @@ type Server interface {
func main() {
var challenge int
flag.IntVar(&challenge, "challenge",9, "Challenge number")
flag.IntVar(&challenge, "challenge",10, "Challenge number")
flag.Parse()
var port uint16
@ -39,6 +39,8 @@ func main() {
server = NewSecureServer(port);
case 9:
server = NewJobServer(port);
case 10:
server = NewVcsServer(port);
default:
fmt.Printf("Unknown challenge\n")
os.Exit(1)

133
spec/vcs.md Normal file
View File

@ -0,0 +1,133 @@
# Discovery
```
$ nc vcs.protohackers.com 30307
READY
help
OK usage: HELP|GET|PUT|LIST
READY
list /
OK 0
READY
put /snack 4
bla
OK r1
READY
list /
OK 1
snack r1
READY
get /snack
OK 4
bla
READY
put /snack 3
al
OK r2
READY
READY
list /
OK 1
snack r2
READY
put /snik/snak
ERR usage: PUT file length newline data
READY
put /snik/snak 3
ji
OK r1
READY
list /
OK 2
snack r2
snik/ DIR
READY
get
ERR usage: GET file [revision]
READY
get /snack r1
OK 4
bla
READY
```
# overview
this system is a simple filesystem that maintains file versions. All paths are absolute, there is no current working directory.
This is a tcp protocol that strictly prescribes who talks when. it has two modes:
* in command mode, server prints READY, then reads until it finds a newline
* in data mode, server reads a prespecified amount of bytes
* on connection, the server goes into command mode
* after a newline, if the command is put and it is a valid command, the server goes into data mode. after the specified amount of bytes has been read, the server prints a response (see below) and returns to command mode
* for all other commands, the server prints a response and returns to command mode
* responses start with OK or ERR and can be multiple lines
The filesystem must be global, shared by all users
# commands
commands and arguments are separated by spaces.
## help
OK response describing all commands.
if arguments are given they are ignored
## LIST
argument: dir
if more or less arguments are provided, response is 'ERR usage: LIST dir'
dir must start with a / and can contain more than one / (subdirs). trailing / is optional. Two consecutive slashed are not allowed.
if dir is an illegal dir name (does not start with a /), response is 'ERR illegal dir name'
If dir does not exist or is empty, the response is 'OK 0'
the response followed by the number of lines in the list
each line starts with the name of the item
if the item is a directory, the name is postfixed with a / and followed by the word DIR
If the item is a file, the name is followed by the revision, prefixed by 'r'
## PUT
arguments: file length
if more or less arguments are provided, the response is 'ERR usage: PUT file length newline data'
filenames have the same rules as dirnames but can not end with a /
if an illegal filename is given, the response is 'ERR illegal file name'
if length is not a number or a negative number, it is interpreted as 0
if no errors are found, the server goes into data mode to read the specified amount of bytes (can be 0)
the bytes are then stored in the file. if the file is new, it is given revision 1, otherwise the revision is incremented.
the response is 'OK r1' with r1 being the latest revision of the file.
## GET
arguments: file [revision]
If the wrong number of args is given, the response is 'ERR usage: GET file [revision]'
the file argument follows the same rules as PUT
if the file is not found, the response is 'ERR no such file'
if the file is a directory, the response is the same as when the file is not found
the revision is a number, optionally prefixed by a "r"
if the revision is postfixed by non-numeric chars, these are ignored
if the revision is not a number, it is not found
if the revision is not found, the response is 'ERR no such revision'
if no revision is given, the latest one is used.
The response is 'OK 5' where 5 is the number of bytes, followed by that number of bytes.

86
vcs.go Normal file
View File

@ -0,0 +1,86 @@
package main
import (
"net"
"os"
"fmt"
"bufio"
)
type PutRequest struct {
file string
content string
callback chan bool
}
type VcsServer struct {
port uint16
q chan PutRequest
}
func NewVcsServer(port uint16) *VcsServer {
return &VcsServer{
port,
make(chan PutRequest),
}
}
type VcsSession struct{
server *VcsServer
con net.Conn
}
func NewVcsSession(server *VcsServer, con net.Conn) *VcsSession {
return &VcsSession{
server,
con,
}
}
func (s *VcsServer) central(){
for m := range s.q {
fmt.Printf("I %+v\n", m)
}
}
func (s *VcsServer) Run() {
go s.central()
addr := fmt.Sprintf("0.0.0.0:%d", s.port)
server, err := net.Listen("tcp", addr)
if err != nil {
fmt.Println("Error listening:", err.Error())
os.Exit(1)
}
defer server.Close()
fmt.Println("VcsServer waiting for client...")
for {
connection, err := server.Accept()
if err != nil {
fmt.Println("Error accepting: ", err.Error())
os.Exit(1)
}
fmt.Println("client connected")
s.processClient(connection)
}
close(s.q)
}
func (s *VcsServer) processClient(con net.Conn) {
session := NewVcsSession(s, con)
go session.vcsHandler()
}
func (s *VcsSession) vcsHandler() {
r := bufio.NewReaderSize(s.con, 102400)
msg, err := r.ReadString('\n')
for err == nil {
fmt.Print(msg)
msg, err = r.ReadString('\n')
}
}