Skip to content

Commit

Permalink
feat, wip: start to work on message validation for server
Browse files Browse the repository at this point in the history
  • Loading branch information
EagleoutIce committed Aug 30, 2023
1 parent 0febf84 commit 33aee12
Show file tree
Hide file tree
Showing 13 changed files with 345 additions and 196 deletions.
44 changes: 44 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"command-line-args": "^5.2.1",
"command-line-usage": "^7.0.1",
"csv-parse": "^5.4.0",
"joi": "^17.10.0",
"n-readlines": "^1.0.1",
"n3": "^1.17.1",
"object-hash": "^3.0.0",
Expand Down
4 changes: 2 additions & 2 deletions src/cli/repl/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export * from './server/messages'
export * from './server/messages/messages'
export * from './core'
export * from './prompt'
export * from './server/messages'
export * from './server/messages/messages'
export * from './execute'
Empty file added src/cli/repl/server/cache.ts
Empty file.
130 changes: 130 additions & 0 deletions src/cli/repl/server/connection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { LAST_STEP, SteppingSlicer, STEPS_PER_SLICE } from '../../../core'
import net from 'node:net'
import { RShell, TokenMap } from '../../../r-bridge'
import {
answerForValidationError, FileAnalysisRequestMessage, FileAnalysisResponseMessage,
FlowrErrorMessage, FlowrHelloResponseMessage, requestAnalysisMessage,
sendMessage,
SliceRequestMessage, validateBaseMessageFormat, validateMessage
} from './messages'

export interface FlowRFileInformation {
filename: string,
slicer: SteppingSlicer
}

export class FlowRServerConnection {
private readonly socket: net.Socket
private readonly shell: RShell
private readonly tokenMap: TokenMap
private readonly name: string

// maps token to information
private readonly fileMap = new Map<string, FlowRFileInformation>()


// we do not have to ensure synchronized shell-access as we are always running synchronized
constructor(socket: net.Socket, name: string, shell: RShell, tokenMap: TokenMap) {
this.socket = socket
this.tokenMap = tokenMap
this.shell = shell
this.name = name
this.socket.on('data', data => this.handleData(String(data)))
}

// TODO: do we have to deal with partial messages?
private handleData(message: string) {
const request = validateBaseMessageFormat(message)
if(request.type === 'error') {
answerForValidationError(this.socket, request)
return
}
switch(request.message.type) {
case 'request-file-analysis':
this.handleFileAnalysisRequest(request.message as FileAnalysisRequestMessage)
break
case 'request-slice':
this.handleSliceRequest(request.message as SliceRequestMessage)
break
default:
sendMessage<FlowrErrorMessage>(this.socket, {
type: 'error',
fatal: true,
reason: `The message type ${JSON.stringify(request.type ?? 'undefined')} is not supported.`
})
this.socket.end()
}
}

// TODO: do not crash with errors!

// TODO: add name to clients?
// TODO: integrate this with lsp?
private handleFileAnalysisRequest(base: FileAnalysisRequestMessage) {
const requestResult = validateMessage(base, requestAnalysisMessage)
if(requestResult.type === 'error') {
answerForValidationError(this.socket, requestResult)
return
}
const message = requestResult.message
console.log(`[${this.name}] Received file analysis request for ${message.filename} (token: ${message.filetoken})`)

// TODO: guard with json schema so that all are correctly keys given
if(this.fileMap.has(message.filetoken)) {
console.log(`File token ${message.filetoken} already exists. Overwriting.`)
}
const slicer = new SteppingSlicer({
stepOfInterest: LAST_STEP,
shell: this.shell,
tokenMap: this.tokenMap,
request: {
request: 'text',
content: message.content,
attachSourceInformation: true,
ensurePackageInstalled: true
},
criterion: [] // currently unknown
// TODO: allow to configure the rest?
})
this.fileMap.set(message.filetoken, {
filename: message.filename,
slicer
})

void slicer.allRemainingSteps(false).then(results => {
sendMessage(this.socket, {
type: 'response-file-analysis',
success: true,
results
})
})
}

private handleSliceRequest(request: SliceRequestMessage) {
console.log(`[${request.filetoken}] Received slice request with criteria ${JSON.stringify(request.criterion)}`)

const fileInformation = this.fileMap.get(request.filetoken)
if(!fileInformation) {
sendMessage<FlowrErrorMessage>(this.socket, {
type: 'error',
fatal: false,
reason: `The file token ${request.filetoken} has never been analyzed.`
})
return
}
// TODO: remove failed messages as they are part of error?
// TODO: ensure correct criteria
// TODO: cache slices?
// TODO: unique message ids in requests and answsers to link them?
fileInformation.slicer.updateCriterion(request.criterion)
void fileInformation.slicer.allRemainingSteps(true).then(results => {
sendMessage(this.socket, {
type: 'response-slice',
success: true,
// TODO: is there a better way?
results: Object.fromEntries(Object.entries(results).filter(([k,]) => Object.hasOwn(STEPS_PER_SLICE, k)))
})
})
}

}
70 changes: 0 additions & 70 deletions src/cli/repl/server/messages.ts

This file was deleted.

33 changes: 33 additions & 0 deletions src/cli/repl/server/messages/analysis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { FlowrBaseMessage, RequestMessageDefinition } from './messages'
import { LAST_PER_FILE_STEP, StepResults } from '../../../../core'
import Joi from 'joi'
import { FlowRServerConnection } from '../connection'

export interface FileAnalysisRequestMessage extends FlowrBaseMessage {
type: 'request-file-analysis',
filetoken: string,
filename: string,
content: string
}


export const requestAnalysisMessage: RequestMessageDefinition<FileAnalysisRequestMessage, [FlowRServerConnection]> = {
type: 'request-file-analysis',
schema: Joi.object({
type: Joi.string().valid('request-file-analysis').required(),
filetoken: Joi.string().required(),
filename: Joi.string().required(),
content: Joi.string().required()
}),

handle(message: FileAnalysisRequestMessage, information: [FlowRServerConnection]) {
console.log('received file analysis request')
}
}


export interface FileAnalysisResponseMessage extends FlowrBaseMessage {
type: 'response-file-analysis',
results: StepResults<typeof LAST_PER_FILE_STEP>
}

8 changes: 8 additions & 0 deletions src/cli/repl/server/messages/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { FlowrBaseMessage } from './messages'

export interface FlowrErrorMessage extends FlowrBaseMessage {
type: 'error',
/** if fatal, the connection will be partially closed afterward */
fatal: boolean,
reason: string
}
10 changes: 10 additions & 0 deletions src/cli/repl/server/messages/hello.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { VersionInformation } from '../../commands/version'
import { FlowrBaseMessage } from './messages'

export interface FlowrHelloResponseMessage extends FlowrBaseMessage{
type: 'hello',
/** a unique name assigned to each client it has no semantic meaning and is only used for debugging */
clientName: string,
versions: VersionInformation
}

5 changes: 5 additions & 0 deletions src/cli/repl/server/messages/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './error'
export * from './hello'
export * from './slice'
export * from './analysis'
export * from './messages'
Loading

0 comments on commit 33aee12

Please sign in to comment.