diff --git a/circuits.json b/circuits.json index 1677d84..485460c 100644 --- a/circuits.json +++ b/circuits.json @@ -71,13 +71,15 @@ 10 ] }, - "http_get_request": { - "file": "http/http_request/parser", + "get_request": { + "file": "http/parser/parser", "template": "Parser", - "params": [ - 158 - ] + "params": [60] }, + "get_response": { + "file": "http/parser/parser", + "template": "Parser", + "params": [89] "json_extract_value_string": { "file": "main/value_string", "template": "ExtractStringValue", diff --git a/circuits/http/parser_request/machine.circom b/circuits/http/interpreter.circom similarity index 66% rename from circuits/http/parser_request/machine.circom rename to circuits/http/interpreter.circom index ee0cff1..68e4a1d 100644 --- a/circuits/http/parser_request/machine.circom +++ b/circuits/http/interpreter.circom @@ -1,10 +1,17 @@ pragma circom 2.1.9; -include "language.circom"; +include "parser/language.circom"; include "../utils/array.circom"; -template ParseMethod() { - signal input bytes[7]; +/* TODO: +Notes -- +- This is a pretty efficient way to simply check what the method used in a request is by checking + the first `DATA_LENGTH` number of bytes. +- Could probably change this to a template that checks if it is one of the given methods + so we don't check them all in one +*/ +template YieldMethod(DATA_LENGTH) { + signal input bytes[DATA_LENGTH]; signal output MethodTag; component RequestMethod = RequestMethod(); diff --git a/circuits/http/parser_request/language.circom b/circuits/http/parser/language.circom similarity index 84% rename from circuits/http/parser_request/language.circom rename to circuits/http/parser/language.circom index a001d86..faf958b 100644 --- a/circuits/http/parser_request/language.circom +++ b/circuits/http/parser/language.circom @@ -13,9 +13,12 @@ template Syntax() { // - ASCII char `"` signal output QUOTE <== 34; //-White_space--------------------------------------------------------------------------------// - // - ASCII pair: `\r\n` - signal output CLRF <== [13, 10]; // https://www.rfc-editor.org/rfc/rfc2616#section-2.2 - // https://www.rfc-editor.org/rfc/rfc7230#section-3.5 + // https://www.rfc-editor.org/rfc/rfc2616#section-2.2 + // https://www.rfc-editor.org/rfc/rfc7230#section-3.5 + // - ASCII char `\r` (carriage return) + signal output CR <== 13; + // - ASCII char `\n` (line feed) + signal output LF <== 10; // - ASCII char: ` ` signal output SPACE <== 32; //-Escape-------------------------------------------------------------------------------------// diff --git a/circuits/http/parser/machine.circom b/circuits/http/parser/machine.circom new file mode 100644 index 0000000..da51e4f --- /dev/null +++ b/circuits/http/parser/machine.circom @@ -0,0 +1,73 @@ +pragma circom 2.1.9; + +include "language.circom"; +include "../../utils/array.circom"; + +template StateUpdate() { + signal input parsing_start; // Bool flag for if we are in the start line + signal input parsing_header; // Flag + Counter for what header line we are in + signal input parsing_body; + signal input line_status; // Flag that counts up to 4 to read a double CLRF + signal input byte; + + signal output next_parsing_start; + signal output next_parsing_header; + signal output next_parsing_body; + signal output next_line_status; + + component Syntax = Syntax(); + + //---------------------------------------------------------------------------------// + // Check if what we just read is a CR / LF + component readCR = IsEqual(); + readCR.in <== [byte, Syntax.CR]; + component readLF = IsEqual(); + readLF.in <== [byte, Syntax.LF]; + + signal notCRAndLF <== (1 - readCR.out) * (1 - readLF.out); + //---------------------------------------------------------------------------------// + + //---------------------------------------------------------------------------------// + // Check if we had read previously CR / LF or multiple + component prevReadCR = IsEqual(); + prevReadCR.in <== [line_status, 1]; + component prevReadCRLF = IsEqual(); + prevReadCRLF.in <== [line_status, 2]; + component prevReadCRLFCR = IsEqual(); + prevReadCRLFCR.in <== [line_status, 3]; + + signal readCRLF <== prevReadCR.out * readLF.out; + signal readCRLFCRLF <== prevReadCRLFCR.out * readLF.out; + //---------------------------------------------------------------------------------// + + //---------------------------------------------------------------------------------// + // Take current state and CRLF info to update state + signal state[3] <== [parsing_start, parsing_header, parsing_body]; + component stateChange = StateChange(); + stateChange.readCRLF <== readCRLF; + stateChange.readCRLFCRLF <== readCRLFCRLF; + stateChange.state <== state; + + component nextState = ArrayAdd(3); + nextState.lhs <== state; + nextState.rhs <== stateChange.out; + //---------------------------------------------------------------------------------// + + next_parsing_start <== nextState.out[0]; + next_parsing_header <== nextState.out[1]; + next_parsing_body <== nextState.out[2]; + next_line_status <== line_status + readCR.out + readCRLF + readCRLFCRLF - line_status * notCRAndLF; + +} + +template StateChange() { + signal input readCRLF; + signal input readCRLFCRLF; + signal input state[3]; + signal output out[3]; + + signal disableParsingStart <== readCRLF * state[0]; + signal disableParsingHeader <== readCRLFCRLF * state[1]; + + out <== [-disableParsingStart, disableParsingStart - disableParsingHeader, disableParsingHeader]; +} \ No newline at end of file diff --git a/circuits/http/parser/parser.circom b/circuits/http/parser/parser.circom new file mode 100644 index 0000000..487fb63 --- /dev/null +++ b/circuits/http/parser/parser.circom @@ -0,0 +1,51 @@ +pragma circom 2.1.9; + +include "../../utils/bytes.circom"; +include "machine.circom"; + + +template Parser(DATA_BYTES) { + signal input data[DATA_BYTES]; + + signal output Method; + + //--------------------------------------------------------------------------------------------// + //-CONSTRAINTS--------------------------------------------------------------------------------// + //--------------------------------------------------------------------------------------------// + component dataASCII = ASCII(DATA_BYTES); + dataASCII.in <== data; + //--------------------------------------------------------------------------------------------// + + // Initialze the parser + component State[DATA_BYTES]; + State[0] = StateUpdate(); + State[0].byte <== data[0]; + State[0].parsing_start <== 1; + State[0].parsing_header <== 0; + State[0].parsing_body <== 0; + State[0].line_status <== 0; + + for(var data_idx = 1; data_idx < DATA_BYTES; data_idx++) { + State[data_idx] = StateUpdate(); + State[data_idx].byte <== data[data_idx]; + State[data_idx].parsing_start <== State[data_idx - 1].next_parsing_start; + State[data_idx].parsing_header <== State[data_idx - 1].next_parsing_header; + State[data_idx].parsing_body <== State[data_idx - 1].next_parsing_body; + State[data_idx].line_status <== State[data_idx - 1].next_line_status; + + // Debugging + log("State[", data_idx, "].parsing_start ", "= ", State[data_idx].parsing_start); + log("State[", data_idx, "].parsing_header", "= ", State[data_idx].parsing_header); + log("State[", data_idx, "].parsing_body ", "= ", State[data_idx].parsing_body); + log("State[", data_idx, "].line_status ", "= ", State[data_idx].line_status); + log("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); + } + + // Debugging + log("State[", DATA_BYTES, "].parsing_start ", "= ", State[DATA_BYTES-1].next_parsing_start); + log("State[", DATA_BYTES, "].parsing_header", "= ", State[DATA_BYTES-1].next_parsing_header); + log("State[", DATA_BYTES, "].parsing_body ", "= ", State[DATA_BYTES-1].next_parsing_body); + log("State[", DATA_BYTES, "].line_status ", "= ", State[DATA_BYTES-1].next_line_status); + log("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); + +} \ No newline at end of file diff --git a/circuits/http/parser_request/parser.circom b/circuits/http/parser_request/parser.circom deleted file mode 100644 index 9fd9524..0000000 --- a/circuits/http/parser_request/parser.circom +++ /dev/null @@ -1,24 +0,0 @@ -pragma circom 2.1.9; - -include "../utils/bytes.circom"; -include "machine.circom"; - - -template Parser(DATA_BYTES) { - signal input data[DATA_BYTES]; - - signal output Method; - - //--------------------------------------------------------------------------------------------// - //-CONSTRAINTS--------------------------------------------------------------------------------// - //--------------------------------------------------------------------------------------------// - component dataASCII = ASCII(DATA_BYTES); - dataASCII.in <== data; - //--------------------------------------------------------------------------------------------// - - component ParseMethod = ParseMethod(); - for(var byte_idx = 0; byte_idx < 7; byte_idx++) { - ParseMethod.bytes[byte_idx] <== data[byte_idx]; - } - log("MethodTag: ", ParseMethod.MethodTag); -} \ No newline at end of file diff --git a/circuits/test/http/interpreter.test.ts b/circuits/test/http/interpreter.test.ts new file mode 100644 index 0000000..10cc80c --- /dev/null +++ b/circuits/test/http/interpreter.test.ts @@ -0,0 +1,25 @@ +import { circomkit, WitnessTester, generateDescription } from "../common"; + +describe("HTTP :: Interpreter", async () => { + describe("YieldMethod", async () => { + let circuit: WitnessTester<["bytes"], ["MethodTag"]>; + + function generatePassCase(input: any, expected: any, depth: number, desc: string) { + const description = generateDescription(input); + + it(`(valid) witness: ${description} ${desc}`, async () => { + circuit = await circomkit.WitnessTester(`YieldMethod`, { + file: "circuits/http/interpreter", + template: "YieldMethod", + params: [4], + }); + console.log("#constraints:", await circuit.getConstraintCount()); + + await circuit.expectPass(input, expected); + }); + } + + // The string `"GET "` + generatePassCase({ bytes: [71, 69, 84, 32] }, { MethodTag: 1 }, 0, ""); + }); +}); \ No newline at end of file diff --git a/examples/http/get_request.http b/examples/http/get_request.http index 88eb48d..89257e9 100644 --- a/examples/http/get_request.http +++ b/examples/http/get_request.http @@ -1,5 +1,3 @@ -GET /objectserver/restapi/alerts/status HTTP/1.1 +GET /api HTTP/1.1 Accept: application/json -Authorization: Basic dGVzdHVzZXIwMTpuZXRjb29s -Host: localhost -Connection: keep-alive \ No newline at end of file +Host: localhost \ No newline at end of file diff --git a/src/bin/witness.rs b/src/bin/witness.rs index d3c324f..bacabe0 100644 --- a/src/bin/witness.rs +++ b/src/bin/witness.rs @@ -10,11 +10,11 @@ struct Args { command: Command, /// Output directory (will be created if it doesn't exist) - #[arg(global = true, short, long, default_value = ".")] + #[arg(global = true, long, default_value = ".")] output_dir: PathBuf, /// Output filename (will be created if it doesn't exist) - #[arg(global = true, short, long, default_value = "output.json")] + #[arg(global = true, long, default_value = "output.json")] output_filename: String, } @@ -64,7 +64,16 @@ pub fn main() -> Result<(), Box> { (data, keys_map) } Command::Http { input_file } => { - let data = std::fs::read(input_file)?; + let mut data = std::fs::read(input_file)?; + let mut i = 0; + while i < data.len() { + if data[i] == 10 && (i == 0 || data[i - 1] != 13) { + data.insert(i, 13); + i += 2; + } else { + i += 1; + } + } let keys_map = serde_json::Map::new(); (data, keys_map) }