diff --git a/assignments/assignment-6/questions.md b/assignments/assignment-6/questions.md
new file mode 100644
index 0000000000000000000000000000000000000000..50b24b8087808c278c24b1d30de44ef049f647b4
--- /dev/null
+++ b/assignments/assignment-6/questions.md
@@ -0,0 +1,19 @@
+1. How does the remote client determine when a command's output is fully received from the server, and what techniques can be used to handle partial reads or ensure complete message transmission?
+
+_The remote client can append some sort of special character to indicate the end of the command to know that it's been fully received, use fixed-length messages so the client always knows the exact number of bytes it should receieve, or the server can send the client how many bytes it should expect. To make the client make sure it has every part of the message, you can either read the message in loops until the special character is receieved if you implement it the first way, have the exact amount of bytes needed given if you implement it the second way, or loop until the amount of bytes needed are found if you implement it the third way. _
+
+2. This week's lecture on TCP explains that it is a reliable stream protocol rather than a message-oriented one. Since TCP does not preserve message boundaries, how should a networked shell protocol define and detect the beginning and end of a command sent over a TCP connection? What challenges arise if this is not handled correctly?
+
+_To define the beginning and end of a command, you can implement one of the three ways given in question 1, either use some sort of special character, have a specific length given already, or give a message that says how many bytes are about to be sent over so it knows how many it should expect before running any commands. The main challenge is that if the bounds are not read correctly, then the commands given may not run properly and/or produce unexpected results since part of the command may be cut off and the client is now working with incomplete data._
+
+3. Describe the general differences between stateful and stateless protocols.
+
+_Stateless protocols have no session context, so each request is independent and contains all the necessary information given, they have no memory of previous requests, they are easier to design and implement since they don't require storing session data, and they recover from failure faster since they have no session context. Stateful protocols have session context and memory of previous requests, and because of this, they are much harder to design and implement and are slower to recover from failure because they will need to remember everything from previous requests and connections._
+
+4. Our lecture this week stated that UDP is "unreliable". If that is the case, why would we ever use it?
+
+_We still use UDP when we need something super fast that doesn't mind losing data here and there. It has a much lower overhead compared to TCP, has a lower latency due to lack of error correction and retransmission mechanisms, and it is generally much more simpler to implement than TCP._
+
+5. What interface/abstraction is provided by the operating system to enable applications to use network communications?
+
+_The operating system provides a socket interface to enable applications to use network communications._
diff --git a/assignments/assignment-6/starter/.debug/launch.json b/assignments/assignment-6/starter/.debug/launch.json
new file mode 100644
index 0000000000000000000000000000000000000000..fdb640995ea79fea50156406273be1567160c68c
--- /dev/null
+++ b/assignments/assignment-6/starter/.debug/launch.json
@@ -0,0 +1,29 @@
+{
+    "configurations": [
+        {
+            "name": "(gdb) 6-RShell",
+            "type": "cppdbg",
+            "request": "launch",
+            "program": "${workspaceFolder}/6-RShell/starter/dsh",
+            "args": [""],
+            "stopAtEntry": false,
+            "cwd": "${workspaceFolder}/6-RShell/starter",
+            "environment": [],
+            "externalConsole": false,
+            "MIMode": "gdb",
+            "setupCommands": [
+                {
+                    "description": "Enable pretty-printing for gdb",
+                    "text": "-enable-pretty-printing",
+                    "ignoreFailures": true
+                },
+                {
+                    "description": "Set Disassembly Flavor to Intel",
+                    "text": "-gdb-set disassembly-flavor intel",
+                    "ignoreFailures": true
+                }
+            ],
+            "preLaunchTask": "Build 6-RShell"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/assignments/assignment-6/starter/.debug/tasks.json b/assignments/assignment-6/starter/.debug/tasks.json
new file mode 100644
index 0000000000000000000000000000000000000000..d9a375427d019df28e6e15c17299be0fe3d880d5
--- /dev/null
+++ b/assignments/assignment-6/starter/.debug/tasks.json
@@ -0,0 +1,20 @@
+{
+    "version": "2.0.0",
+    "tasks": [
+      {
+        "label": "Build 6-RShell",
+        "type": "shell",
+        "command": "make",
+        "group": {
+          "kind": "build",
+          "isDefault": true
+        },
+        "options": {
+          "cwd": "${workspaceFolder}/6-RShell/starter"
+      },
+        "problemMatcher": ["$gcc"],
+        "detail": "Runs the 'make' command to build the project."
+      }
+    ]
+  }
+  
\ No newline at end of file
diff --git a/assignments/assignment-6/starter/.gitignore b/assignments/assignment-6/starter/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..eb47a8e82ad127f89b6fdbbf0f531cb8865b7bf8
--- /dev/null
+++ b/assignments/assignment-6/starter/.gitignore
@@ -0,0 +1 @@
+dsh
\ No newline at end of file
diff --git a/assignments/assignment-6/starter/bats/assignment_tests.sh b/assignments/assignment-6/starter/bats/assignment_tests.sh
new file mode 100755
index 0000000000000000000000000000000000000000..5246ee441c5e049315e4d3191569dd6314c40bb9
--- /dev/null
+++ b/assignments/assignment-6/starter/bats/assignment_tests.sh
@@ -0,0 +1,36 @@
+#!/usr/bin/env bats
+
+############################ DO NOT EDIT THIS FILE #####################################
+# File: assignement_tests.sh
+# 
+# DO NOT EDIT THIS FILE
+#
+# Add/Edit Student tests in student_tests.sh
+# 
+# All tests in this file must pass - it is used as part of grading!
+########################################################################################
+
+@test "Pipes" {
+    run "./dsh" <<EOF                
+ls | grep dshlib.c
+EOF
+
+    # Strip all whitespace (spaces, tabs, newlines) from the output
+    stripped_output=$(echo "$output" | tr -d '[:space:]')
+
+    # Expected output with all whitespace removed for easier matching
+    expected_output="dshlib.clocalmodedsh4>dsh4>cmdloopreturned0"
+
+    # These echo commands will help with debugging and will only print
+    #if the test fails
+    echo "Captured stdout:" 
+    echo "Output: $output"
+    echo "Exit Status: $status"
+    echo "${stripped_output} -> ${expected_output}"
+
+    # Check exact match
+    [ "$stripped_output" = "$expected_output" ]
+
+    # Assertions
+    [ "$status" -eq 0 ]
+}
diff --git a/assignments/assignment-6/starter/bats/student_tests.sh b/assignments/assignment-6/starter/bats/student_tests.sh
new file mode 100755
index 0000000000000000000000000000000000000000..1edac92c7be35ede8f61c96df53d08777c3fc300
--- /dev/null
+++ b/assignments/assignment-6/starter/bats/student_tests.sh
@@ -0,0 +1,221 @@
+#!/usr/bin/env bats
+
+# File: student_tests.sh
+# 
+# Create your unit tests suit in this file
+
+@test "Example: check ls runs without errors" {
+    run ./dsh <<EOF                
+ls
+EOF
+
+    # Assertions
+    [ "$status" -eq 0 ]
+}
+
+@test "Exit Command works" {
+	run ./dsh <<EOF
+exit
+EOF
+    	# Assertions
+    	[ "$status" -eq 0 ]
+}
+
+@test "Multiple commands via pipes are run properly" {
+	run ./dsh <<EOF
+ls | ls
+exit
+EOF
+	# Assertions
+	[ "$status" -eq 0 ]
+}
+
+@test "No command is given" {
+	run ./dsh <<EOF
+
+exit
+EOF
+
+	# Strip all whitespace (spaces, tabs, newlines) from the output
+	stripped_output=$(echo "$output" | tr -d '[:space:]')
+
+	# Expected output with all whitespace removed for easier matching
+	expected_output="localmodedsh4>warning:nocommandsprovideddsh4>"
+
+	echo "Captured stdout:"
+	echo "Output: $stripped_output"
+	echo "Expected: $expected_output"
+	echo "Exit Status: $status"
+
+	# Check exact match
+	[ "$stripped_output" = "$expected_output" ]
+
+	# Assertions
+	[ "$status" -eq 0 ]
+}
+
+@test "Check if built in cd command works correctly" {
+	run ./dsh <<EOF
+cd ..
+pwd
+EOF
+	# Assertions
+	[ "$status" -eq 0 ]
+}
+
+@test "Check error handling for too many pipes" {
+	run ./dsh <<EOF
+ls | grep dshlib.c | grep armed | grep and | grep dangerous | grep again | grep again | grep again | grep again
+exit
+EOF
+
+        # Strip all whitespace (spaces, tabs, newlines) from the output
+        stripped_output=$(echo "$output" | tr -d '[:space:]')
+
+        # Expected output with all whitespace removed for easier matching
+        expected_output="localmodedsh4>error:pipinglimitedto8commandsdsh4>"
+
+        echo "Captured stdout:"
+        echo "Output: $stripped_output"
+        echo "Expected: $expected_output"
+        echo "Exit Status: $status"
+
+        # Check exact match
+        [ "$stripped_output" = "$expected_output" ]
+
+        # Assertions
+        [ "$status" -eq 0 ]
+}
+
+@test "Check if > works" {
+        run ./dsh <<EOF
+echo "hello, class" > out.txt
+cat out.txt
+rm out.txt
+exit
+EOF
+
+        # Strip all whitespace (spaces, tabs, newlines) from the output
+        stripped_output=$(echo "$output" | tr -d '[:space:]')
+
+        # Expected output with all whitespace removed for easier matching
+        expected_output="\"hello,class\"localmodedsh4>dsh4>dsh4>dsh4>"
+
+        echo "Captured stdout:"
+        echo "Output: $stripped_output"
+        echo "Expected: $expected_output"
+        echo "Exit Status: $status"
+
+        # Check exact match
+        [ "$stripped_output" = "$expected_output" ]
+
+        # Assertions
+        [ "$status" -eq 0 ]
+}
+
+@test "Check if >> works" {
+        run ./dsh <<EOF
+echo "hello, class" > out.txt
+cat out.txt
+echo "this is the second line" >> out.txt
+cat out.txt
+rm out.txt
+exit
+EOF
+
+        # Strip all whitespace (spaces, tabs, newlines) from the output
+        stripped_output=$(echo "$output" | tr -d '[:space:]')
+
+        # Expected output with all whitespace removed for easier matching
+        expected_output="\"hello,class\"\"hello,class\"\"thisisthesecondline\"localmodedsh4>dsh4>dsh4>dsh4>dsh4>dsh4>"
+
+        echo "Captured stdout:"
+        echo "Output: $stripped_output"
+        echo "Expected: $expected_output"
+        echo "Exit Status: $status"
+
+        # Check exact match
+        [ "$stripped_output" = "$expected_output" ]
+
+        # Assertions
+        [ "$status" -eq 0 ]
+}
+
+@test "Check if multiple echo's work" {
+        run ./dsh <<EOF
+echo "I play mill druid"
+echo "Atleast it's not shadow priest"
+exit
+EOF
+
+        # Strip all whitespace (spaces, tabs, newlines) from the output
+        stripped_output=$(echo "$output" | tr -d '[:space:]')
+
+        # Expected output with all whitespace removed for easier matching
+        expected_output="\"Iplaymilldruid\"\"Atleastit'snotshadowpriest\"localmodedsh4>dsh4>dsh4>"
+
+        echo "Captured stdout:"
+        echo "Output: $stripped_output"
+        echo "Expected: $expected_output"
+        echo "Exit Status: $status"
+
+        # Check exact match
+        [ "$stripped_output" = "$expected_output" ]
+
+        # Assertions
+        [ "$status" -eq 0 ]
+}
+
+@test "Check if multiple commands print properly that isn't just echo" {
+        run ./dsh <<EOF
+ls
+echo "Goddess by Laufey is really good"
+exit
+EOF
+
+        # Strip all whitespace (spaces, tabs, newlines) from the output
+        stripped_output=$(echo "$output" | tr -d '[:space:]')
+
+        # Expected output with all whitespace removed for easier matching
+        expected_output="batsdshdsh_cli.cdshlib.cdshlib.hmakefilersh_cli.crshlib.hrsh_server.c\"GoddessbyLaufeyisreallygood\"localmodedsh4>dsh4>dsh4>"
+
+        echo "Captured stdout:"
+        echo "Output: $stripped_output"
+        echo "Expected: $expected_output"
+        echo "Exit Status: $status"
+
+        # Check exact match
+        [ "$stripped_output" = "$expected_output" ]
+
+        # Assertions
+        [ "$status" -eq 0 ]
+}
+
+@test "Check if more than just two commands will print out properly" {
+        run ./dsh <<EOF
+echo "What if you wanted to plant on B"
+ls
+cd ..
+ls
+echo "but god said..."
+echo "LET'S TURN THE TIDE"
+exit
+EOF
+
+        # Strip all whitespace (spaces, tabs, newlines) from the output
+        stripped_output=$(echo "$output" | tr -d '[:space:]')
+
+        # Expected output with all whitespace removed for easier matching
+        expected_output="\"WhatifyouwantedtoplantonB\"batsdshdsh_cli.cdshlib.cdshlib.hmakefilersh_cli.crshlib.hrsh_server.cquestions.mdstarter\"butgodsaid...\"\"LET'STURNTHETIDE\"localmodedsh4>dsh4>dsh4>dsh4>dsh4>dsh4>dsh4>"
+
+        echo "Captured stdout:"
+        echo "Output: $stripped_output"
+        echo "Expected: $expected_output"
+        echo "Exit Status: $status"
+
+        # Check exact match
+        [ "$stripped_output" = "$expected_output" ]
+
+        # Assertions
+        [ "$status" -eq 0 ]
+}
diff --git a/assignments/assignment-6/starter/dsh_cli.c b/assignments/assignment-6/starter/dsh_cli.c
new file mode 100644
index 0000000000000000000000000000000000000000..68fab16a055b002e0a6084fdc4d0a26e13147d80
--- /dev/null
+++ b/assignments/assignment-6/starter/dsh_cli.c
@@ -0,0 +1,149 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <argp.h>
+#include <getopt.h>
+
+#include "dshlib.h"
+#include "rshlib.h"
+
+
+/*
+ * Used to pass startup parameters back to main
+ */
+#define MODE_LCLI   0       //Local client
+#define MODE_SCLI   1       //Socket client
+#define MODE_SSVR   2       //Socket server
+
+typedef struct cmd_args{
+  int   mode;
+  char  ip[16];   //e.g., 192.168.100.101\0
+  int   port;
+  int   threaded_server;
+}cmd_args_t;
+
+
+
+//You dont really need to understand this but the C runtime library provides
+//an getopt() service to simplify handling command line arguments.  This
+//code will help setup dsh to handle triggering client or server mode along
+//with passing optional connection parameters. 
+
+void print_usage(const char *progname) {
+  printf("Usage: %s [-c | -s] [-i IP] [-p PORT] [-x] [-h]\n", progname);
+  printf("  Default is to run %s in local mode\n", progname);
+  printf("  -c            Run as client\n");
+  printf("  -s            Run as server\n");
+  printf("  -i IP         Set IP/Interface address (only valid with -c or -s)\n");
+  printf("  -p PORT       Set port number (only valid with -c or -s)\n");
+  printf("  -x            Enable threaded mode (only valid with -s)\n");
+  printf("  -h            Show this help message\n");
+  exit(0);
+}
+
+void parse_args(int argc, char *argv[], cmd_args_t *cargs) {
+  int opt;
+  memset(cargs, 0, sizeof(cmd_args_t));
+
+  //defaults
+  cargs->mode = MODE_LCLI;
+  cargs->port = RDSH_DEF_PORT;
+
+  while ((opt = getopt(argc, argv, "csi:p:xh")) != -1) {
+      switch (opt) {
+          case 'c':
+              if (cargs->mode != MODE_LCLI) {
+                  fprintf(stderr, "Error: Cannot use both -c and -s\n");
+                  exit(EXIT_FAILURE);
+              }
+              cargs->mode = MODE_SCLI;
+              strncpy(cargs->ip, RDSH_DEF_CLI_CONNECT, sizeof(cargs->ip) - 1);
+              break;
+          case 's':
+              if (cargs->mode != MODE_LCLI) {
+                  fprintf(stderr, "Error: Cannot use both -c and -s\n");
+                  exit(EXIT_FAILURE);
+              }
+              cargs->mode = MODE_SSVR;
+              strncpy(cargs->ip, RDSH_DEF_SVR_INTFACE, sizeof(cargs->ip) - 1);
+              break;
+          case 'i':
+              if (cargs->mode == MODE_LCLI) {
+                  fprintf(stderr, "Error: -i can only be used with -c or -s\n");
+                  exit(EXIT_FAILURE);
+              }
+              strncpy(cargs->ip, optarg, sizeof(cargs->ip) - 1);
+              cargs->ip[sizeof(cargs->ip) - 1] = '\0';  // Ensure null termination
+              break;
+          case 'p':
+              if (cargs->mode == MODE_LCLI) {
+                  fprintf(stderr, "Error: -p can only be used with -c or -s\n");
+                  exit(EXIT_FAILURE);
+              }
+              cargs->port = atoi(optarg);
+              if (cargs->port <= 0) {
+                  fprintf(stderr, "Error: Invalid port number\n");
+                  exit(EXIT_FAILURE);
+              }
+              break;
+          case 'x':
+              if (cargs->mode != MODE_SSVR) {
+                  fprintf(stderr, "Error: -x can only be used with -s\n");
+                  exit(EXIT_FAILURE);
+              }
+              cargs->threaded_server = 1;
+              break;
+          case 'h':
+              print_usage(argv[0]);
+              break;
+          default:
+              print_usage(argv[0]);
+      }
+  }
+
+  if (cargs->threaded_server && cargs->mode != MODE_SSVR) {
+      fprintf(stderr, "Error: -x can only be used with -s\n");
+      exit(EXIT_FAILURE);
+  }
+}
+
+
+
+/* DO NOT EDIT
+ * main() logic fully implemented to:
+ *    1. run locally (no parameters)
+ *    2. start the server with the -s option
+ *    3. start the client with the -c option
+*/
+int main(int argc, char *argv[]){
+  cmd_args_t cargs;
+  int rc;
+
+  memset(&cargs, 0, sizeof(cmd_args_t));
+  parse_args(argc, argv, &cargs);
+
+  switch(cargs.mode){
+    case MODE_LCLI:
+      printf("local mode\n");
+      rc = exec_local_cmd_loop();
+      break;
+    case MODE_SCLI:
+      printf("socket client mode:  addr:%s:%d\n", cargs.ip, cargs.port);
+      rc = exec_remote_cmd_loop(cargs.ip, cargs.port);
+      break;
+    case MODE_SSVR:
+      printf("socket server mode:  addr:%s:%d\n", cargs.ip, cargs.port);
+      if (cargs.threaded_server){
+        printf("-> Multi-Threaded Mode\n");
+      } else {
+        printf("-> Single-Threaded Mode\n");
+      }
+      rc = start_server(cargs.ip, cargs.port, cargs.threaded_server);
+      break;
+    default:
+      printf("error unknown mode\n");
+      exit(EXIT_FAILURE);
+  }
+
+  printf("cmd loop returned %d\n", rc);
+}
\ No newline at end of file
diff --git a/assignments/assignment-6/starter/dshlib.c b/assignments/assignment-6/starter/dshlib.c
new file mode 100644
index 0000000000000000000000000000000000000000..d951be5e6088c78e357ac0b837fa4b7c725df1d5
--- /dev/null
+++ b/assignments/assignment-6/starter/dshlib.c
@@ -0,0 +1,369 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+
+#include "dshlib.h"
+
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+
+#include "dshlib.h"
+
+/**** 
+ **** FOR REMOTE SHELL USE YOUR SOLUTION FROM SHELL PART 3 HERE
+ **** THE MAIN FUNCTION CALLS THIS ONE AS ITS ENTRY POINT TO
+ **** EXECUTE THE SHELL LOCALLY
+ ****
+ */
+
+/*
+ * Implement your exec_local_cmd_loop function by building a loop that prompts the 
+ * user for input.  Use the SH_PROMPT constant from dshlib.h and then
+ * use fgets to accept user input.
+ * 
+ *      while(1){
+ *        printf("%s", SH_PROMPT);
+ *        if (fgets(cmd_buff, ARG_MAX, stdin) == NULL){
+ *           printf("\n");
+ *           break;
+ *        }
+ *        //remove the trailing \n from cmd_buff
+ *        cmd_buff[strcspn(cmd_buff,"\n")] = '\0';
+ * 
+ *        //IMPLEMENT THE REST OF THE REQUIREMENTS
+ *      }
+ * 
+ *   Also, use the constants in the dshlib.h in this code.  
+ *      SH_CMD_MAX              maximum buffer size for user input
+ *      EXIT_CMD                constant that terminates the dsh program
+ *      SH_PROMPT               the shell prompt
+ *      OK                      the command was parsed properly
+ *      WARN_NO_CMDS            the user command was empty
+ *      ERR_TOO_MANY_COMMANDS   too many pipes used
+ *      ERR_MEMORY              dynamic memory management failure
+ * 
+ *   errors returned
+ *      OK                     No error
+ *      ERR_MEMORY             Dynamic memory management failure
+ *      WARN_NO_CMDS           No commands parsed
+ *      ERR_TOO_MANY_COMMANDS  too many pipes used
+ *   
+ *   console messages
+ *      CMD_WARN_NO_CMD        print on WARN_NO_CMDS
+ *      CMD_ERR_PIPE_LIMIT     print on ERR_TOO_MANY_COMMANDS
+ *      CMD_ERR_EXECUTE        print on execution failure of external command
+ * 
+ *  Standard Library Functions You Might Want To Consider Using (assignment 1+)
+ *      malloc(), free(), strlen(), fgets(), strcspn(), printf()
+ * 
+ *  Standard Library Functions You Might Want To Consider Using (assignment 2+)
+ *      fork(), execvp(), exit(), chdir()
+ */
+
+// Copy pasted my code from assignment 5
+
+int free_cmd_list(command_list_t *cmd_list) {
+	if(cmd_list == NULL) {
+		return ERR_MEMORY;
+	}
+
+	for(int i = 0; i < cmd_list->num; i++) {
+		if(cmd_list->commands[i]._cmd_buffer != NULL) {
+			free(cmd_list->commands[i]._cmd_buffer);
+		}
+
+		if(cmd_list->commands[i].input_redirect != NULL) {
+			free(cmd_list->commands[i].input_redirect);
+		}
+
+		if(cmd_list->commands[i].output_redirect != NULL) {
+			free(cmd_list->commands[i].output_redirect);
+		}
+
+		cmd_list->commands[i]._cmd_buffer = NULL;
+		cmd_list->commands[i].input_redirect = NULL;
+		cmd_list->commands[i].output_redirect = NULL;
+	}
+
+	cmd_list->num = 0;
+
+	return OK;
+}
+void trim_spaces(char *str) {
+	while (*str && isspace((unsigned char)*str)) {
+		str++;
+	}
+
+	size_t len = strlen(str);
+	while (len > 0 && isspace((unsigned char)str[len - 1])) {
+		str[len - 1] = '\0';
+		len--;
+	}
+}
+
+Built_In_Cmds match_command(const char *input) {
+	if(strcmp(input, EXIT_CMD) == 0) {
+		return BI_CMD_EXIT;
+	}
+	else if(strcmp(input, "dragon") == 0) {
+		return BI_CMD_DRAGON;
+	}
+	else if(strcmp(input, "cd") == 0) {
+		return BI_CMD_CD;
+	}
+	else {
+		return BI_NOT_BI;
+	}
+}
+
+Built_In_Cmds exec_built_in_cmd(cmd_buff_t *cmd) {
+	if(cmd->argc == 0) {
+		return BI_NOT_BI;
+	}
+
+	if(strcmp(cmd->argv[0], "cd") == 0) {
+		if(cmd->argc == 2) {
+			if(chdir(cmd->argv[1]) == -1) {
+				perror("cd failed");
+			}
+		}
+
+		return BI_CMD_CD;
+	}
+
+	return BI_NOT_BI;
+}
+
+int build_cmd_list(char *cmd_line, command_list_t *clist) {
+	//printf("Before trimming the cmd_line in build_cmd_list: '%s'\n", cmd_line);
+	trim_spaces(cmd_line);
+	//printf("Trimmed cmd_line: '%s'\n", cmd_line);
+
+	char *cmd_tok_save = NULL;
+	char *cmd_tok = strtok_r(cmd_line, PIPE_STRING, &cmd_tok_save);
+
+	int index = 0;
+
+	while(cmd_tok != NULL) {
+		trim_spaces(cmd_tok);
+
+		//printf("Command %d: '%s'\n", index, cmd_tok);
+
+		if(index >= CMD_MAX) {
+			return ERR_TOO_MANY_COMMANDS;
+		}
+
+		cmd_buff_t *cmd = &clist->commands[index];
+		cmd->argc = 0;
+		cmd->_cmd_buffer = cmd_tok;
+		cmd->input_redirect = NULL;
+		cmd->output_redirect = NULL;
+
+		//printf("Command %d (before args parsing): '%s'\n", index, cmd_tok);
+
+		char *arg_tok_save = NULL;
+		char *arg_tok = strtok_r(cmd_tok, " ", &arg_tok_save);
+
+		while(arg_tok != NULL && cmd->argc < CMD_ARGV_MAX) {
+			trim_spaces(arg_tok);
+
+			//printf("Command %d: argv[%d]: '%s'\n", index, cmd->argc, arg_tok);
+
+			if(strcmp(arg_tok, "<") == 0) {
+				arg_tok = strtok_r(NULL, " ", &arg_tok_save);
+				if(arg_tok != NULL) {
+					cmd->input_redirect = arg_tok;
+				}
+			}
+			else if(strcmp(arg_tok, ">>") == 0) {
+				arg_tok = strtok_r(NULL, " ", &arg_tok_save);
+				if(arg_tok != NULL) {
+					cmd->output_redirect = arg_tok;
+					cmd->output_append = 1;
+				}
+			}
+			else if(strcmp(arg_tok, ">") == 0) {
+				arg_tok = strtok_r(NULL, " ", &arg_tok_save);
+				if(arg_tok != NULL) {
+					cmd->output_redirect = arg_tok;
+				}
+			}
+			else {
+				cmd->argv[cmd->argc] = arg_tok;
+				cmd->argc++;
+			}
+
+			arg_tok = strtok_r(NULL, " ", &arg_tok_save);
+		}
+
+		cmd->argv[cmd->argc] = NULL;
+
+		//printf("Command %d finished parsing. argc = %d\n", index, cmd->argc);
+
+		index++;
+		cmd_tok = strtok_r(NULL, PIPE_STRING, &cmd_tok_save);
+	}
+
+	clist->num = index;
+	//printf("Total number of commands: %d\n", clist->num);
+	return OK;
+}
+
+int exec_cmd(cmd_buff_t *cmd, int cmd_index, command_list_t *clist, int pipefd_in) {
+	int pipefd[2];
+	if(pipe(pipefd) == -1) {
+		perror("pipe");
+		exit(EXIT_FAILURE);
+	}
+
+	pid_t pid = fork();
+	if(pid == -1) {
+		perror("fork");
+		exit(EXIT_FAILURE);
+	}
+
+	if(pid == 0) {
+		if(cmd_index > 0) {
+			close(pipefd[0]);
+		}
+
+		if(cmd_index < clist->num - 1) {
+			close(pipefd[0]);
+		}
+
+		if(cmd->input_redirect != NULL) {
+			int input_fd = open(cmd->input_redirect, O_RDONLY);
+			if(input_fd == -1) {
+				perror("open");
+				exit(EXIT_FAILURE);
+			}
+			dup2(input_fd, STDIN_FILENO);
+			close(input_fd);
+		}
+		else if(cmd_index > 0) {
+			dup2(pipefd_in, STDIN_FILENO);
+			close(pipefd_in);
+		}
+
+		if(cmd->output_redirect != NULL) {
+			int flags = O_WRONLY | O_CREAT;
+			if(cmd->output_append) {
+				flags |= O_APPEND;
+				//printf("OPENING FILE IN APPEND MODE: output_redirect = %s\n", cmd->output_redirect);
+			}
+			else {
+				flags |= O_TRUNC;
+				//printf("OPENING FILE IN OVERWRITE MODE: output_redirect = %s\n", cmd->output_redirect);
+			}
+			int output_fd = open(cmd->output_redirect, flags, 0644);
+			if(output_fd == -1) {
+				perror("open");
+				exit(EXIT_FAILURE);
+			}
+			//printf("FILE OPENED: output_fd = %d\n", output_fd);
+			dup2(output_fd, STDOUT_FILENO);
+			close(output_fd);
+		}
+		else if(cmd_index < clist->num - 1) {
+			close(STDOUT_FILENO);
+			dup2(pipefd[1], STDOUT_FILENO);
+			close(pipefd[1]);
+		}
+
+		execvp(cmd->argv[0], cmd->argv);
+
+		perror("execvp");
+		exit(EXIT_FAILURE);
+	}
+	else {
+		close(pipefd[1]);
+
+		if(cmd_index < clist->num - 1) {
+			int next_pipefd = pipefd[0];
+			waitpid(pid, NULL, 0);
+			return next_pipefd;
+		}
+		else {
+			waitpid(pid, NULL, 0);
+			close(pipefd[0]);
+			return -1;
+		}
+	}
+}
+
+int exec_local_cmd_loop() {
+
+	char *cmd_buff = malloc(SH_CMD_MAX * sizeof(char));
+	if (cmd_buff == NULL) {
+		fprintf(stderr, "Memory allocation failed\n");
+		return ERR_MEMORY;
+	}
+
+	int rc = 0;
+
+	command_list_t clist;
+
+
+	while(1) {
+		printf("%s", SH_PROMPT);
+		if(fgets(cmd_buff, ARG_MAX, stdin) == NULL) {
+			printf("\n");
+			break;
+		}
+
+		//printf("Raw input: '%s'\n", cmd_buff);
+
+		// remove the trailing \n from cmd_buff
+		cmd_buff[strcspn(cmd_buff, "\n")] = '\0';
+
+		trim_spaces(cmd_buff);
+		//printf("Trimmed input in exec_local_cmd_loop: '%s'\n", cmd_buff);
+
+		if(strlen(cmd_buff) == 0) {
+			printf(CMD_WARN_NO_CMD);
+			continue;
+		}
+
+		if(strcmp(cmd_buff, EXIT_CMD) == 0) {
+			free(cmd_buff);
+			exit(OK);
+		}
+
+		rc = build_cmd_list(cmd_buff, &clist);
+		//printf("RC value: %d\n", rc);
+
+		switch(rc) {
+			case OK:
+				int pipefd_in = -1;
+				for(int i = 0; i < clist.num; i++) {
+					if(exec_built_in_cmd(&clist.commands[i]) == BI_NOT_BI) {
+						pipefd_in = exec_cmd(&clist.commands[i], i, &clist, pipefd_in);
+					}
+				}
+				break;
+			case WARN_NO_CMDS:
+				printf(CMD_WARN_NO_CMD);
+				break;
+			case ERR_TOO_MANY_COMMANDS:
+				printf(CMD_ERR_PIPE_LIMIT, CMD_MAX);
+				break;
+		}
+	}
+
+	free(cmd_buff);
+	return OK;
+}
+
diff --git a/assignments/assignment-6/starter/dshlib.h b/assignments/assignment-6/starter/dshlib.h
new file mode 100644
index 0000000000000000000000000000000000000000..7631eabf28956381f03c4b4afd701c7531cfb932
--- /dev/null
+++ b/assignments/assignment-6/starter/dshlib.h
@@ -0,0 +1,92 @@
+#ifndef __DSHLIB_H__
+    #define __DSHLIB_H__
+
+
+//Constants for command structure sizes
+#define EXE_MAX 64
+#define ARG_MAX 256
+#define CMD_MAX 8
+#define CMD_ARGV_MAX (CMD_MAX + 1)
+// Longest command that can be read from the shell
+#define SH_CMD_MAX EXE_MAX + ARG_MAX
+
+typedef struct command
+{
+    char exe[EXE_MAX];
+    char args[ARG_MAX];
+} command_t;
+
+#include <stdbool.h>
+
+typedef struct cmd_buff
+{
+    int  argc;
+    char *argv[CMD_ARGV_MAX];
+    char *_cmd_buffer;
+    char *input_redirect;  // extra credit, stores input redirection file (for `<`)
+    char *output_redirect; // extra credit, stores output redirection file (for `>`)
+    bool output_append; // extra credit, sets append mode fomr output_file
+} cmd_buff_t;
+
+typedef struct command_list{
+    int num;
+    cmd_buff_t commands[CMD_MAX];
+}command_list_t;
+
+//Special character #defines
+#define SPACE_CHAR  ' '
+#define PIPE_CHAR   '|'
+#define PIPE_STRING "|"
+
+#define SH_PROMPT       "dsh4> "
+#define EXIT_CMD        "exit"
+#define RC_SC           99
+#define EXIT_SC         100
+
+//Standard Return Codes
+#define OK                       0
+#define WARN_NO_CMDS            -1
+#define ERR_TOO_MANY_COMMANDS   -2
+#define ERR_CMD_OR_ARGS_TOO_BIG -3
+#define ERR_CMD_ARGS_BAD        -4      //for extra credit
+#define ERR_MEMORY              -5
+#define ERR_EXEC_CMD            -6
+#define OK_EXIT                 -7
+
+
+
+//prototypes
+int alloc_cmd_buff(cmd_buff_t *cmd_buff);
+int free_cmd_buff(cmd_buff_t *cmd_buff);
+int clear_cmd_buff(cmd_buff_t *cmd_buff);
+int build_cmd_buff(char *cmd_line, cmd_buff_t *cmd_buff);
+int close_cmd_buff(cmd_buff_t *cmd_buff);
+int build_cmd_list(char *cmd_line, command_list_t *clist);
+int free_cmd_list(command_list_t *cmd_lst);
+
+//built in command stuff
+typedef enum {
+    BI_CMD_EXIT,
+    BI_CMD_DRAGON,
+    BI_CMD_CD,
+    BI_CMD_RC,              //extra credit command
+    BI_CMD_STOP_SVR,        //new command "stop-server"
+    BI_NOT_BI,
+    BI_EXECUTED,
+} Built_In_Cmds;
+Built_In_Cmds match_command(const char *input); 
+Built_In_Cmds exec_built_in_cmd(cmd_buff_t *cmd);
+
+//main execution context
+int exec_local_cmd_loop();
+int exec_cmd(cmd_buff_t *cmd, int cmd_index, command_list_t *clist, int pipefd_in);
+int execute_pipeline(command_list_t *clist);
+
+
+//output constants
+#define CMD_OK_HEADER       "PARSED COMMAND LINE - TOTAL COMMANDS %d\n"
+#define CMD_WARN_NO_CMD     "warning: no commands provided\n"
+#define CMD_ERR_PIPE_LIMIT  "error: piping limited to %d commands\n"
+#define BI_NOT_IMPLEMENTED "not implemented"
+
+#endif
diff --git a/assignments/assignment-6/starter/makefile b/assignments/assignment-6/starter/makefile
new file mode 100644
index 0000000000000000000000000000000000000000..b14b0723e23ea2036f0220406b40a93e6a0cd283
--- /dev/null
+++ b/assignments/assignment-6/starter/makefile
@@ -0,0 +1,31 @@
+# Compiler settings
+CC = gcc
+CFLAGS = -Wall -Wextra -g
+
+# Target executable name
+TARGET = dsh
+
+# Find all source and header files
+SRCS = $(wildcard *.c)
+HDRS = $(wildcard *.h)
+
+# Default target
+all: $(TARGET)
+
+# Compile source to executable
+$(TARGET): $(SRCS) $(HDRS)
+	$(CC) $(CFLAGS) -o $(TARGET) $(SRCS)
+
+# Clean up build files
+clean:
+	rm -f $(TARGET)
+
+test:
+	bats $(wildcard ./bats/*.sh)
+
+valgrind:
+	echo "pwd\nexit" | valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1 ./$(TARGET) 
+	echo "pwd\nexit" | valgrind --tool=helgrind --error-exitcode=1 ./$(TARGET) 
+
+# Phony targets
+.PHONY: all clean test
\ No newline at end of file
diff --git a/assignments/assignment-6/starter/rsh_cli.c b/assignments/assignment-6/starter/rsh_cli.c
new file mode 100644
index 0000000000000000000000000000000000000000..34629293504dc2394df44363619dc36a11582137
--- /dev/null
+++ b/assignments/assignment-6/starter/rsh_cli.c
@@ -0,0 +1,300 @@
+
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/un.h>
+#include <fcntl.h>
+
+#include "dshlib.h"
+#include "rshlib.h"
+
+
+
+
+/*
+ * exec_remote_cmd_loop(server_ip, port)
+ *      server_ip:  a string in ip address format, indicating the servers IP
+ *                  address.  Note 127.0.0.1 is the default meaning the server
+ *                  is running on the same machine as the client
+ *              
+ *      port:   The port the server will use.  Note the constant 
+ *              RDSH_DEF_PORT which is 1234 in rshlib.h.  If you are using
+ *              tux you may need to change this to your own default, or even
+ *              better use the command line override -c implemented in dsh_cli.c
+ *              For example ./dsh -c 10.50.241.18:5678 where 5678 is the new port
+ *              number and the server address is 10.50.241.18    
+ * 
+ *      This function basically implements the network version of 
+ *      exec_local_cmd_loop() from the last assignemnt.  It will:
+ *  
+ *          1. Allocate buffers for sending and receiving data over the 
+ *             network
+ *          2. Create a network connection to the server, getting an active
+ *             socket by calling the start_client(server_ip, port) function.
+ *          2. Go into an infinite while(1) loop prompting the user for
+ *             input commands. 
+ * 
+ *             a. Accept a command from the user via fgets()
+ *             b. Send that command to the server using send() - it should
+ *                be a null terminated string
+ *             c. Go into a loop and receive client requests.  Note each
+ *                receive might not be a C string so you need to print it
+ *                out using:
+ *                     printf("%.*s", (int)bytes_received, rsp_buff);
+ *                this version of printf() uses the "%.*s" flag that indicates
+ *                that the rsp_buff might be a null terminated string, or
+ *                it might not be, if its not, print exactly bytes_received
+ *                bytes. 
+ *             d. In the recv() loop described above. Each time you receive
+ *                data from the server, see if the last byte received is the
+ *                EOF character. This indicates the server is done and you can
+ *                send another command by going to the top of the loop.  The
+ *                best way to do this is as follows assuming you are receiving
+ *                data into a buffer called recv_buff, and you received
+ *                recv_bytes in the call to recv:
+ * 
+ *                  recv_bytes = recv(sock, recv_buff, recv_buff_sz, 0)
+ *                  
+ *                if recv_bytes:
+ *                  <negative_number>: communication error
+ *                    0:    Didn't receive anything, likely server down
+ *                  > 0:    Got some data. Check if the last byte is EOF
+ *                          is_eof = (recv_buff[recv_bytes-1] == RDSH_EOF_CHAR) ? 1 : 0;
+ *                    if is_eof is true, this is the last part of the transmission
+ *                    from the server and you can break out of the recv() loop. 
+ * 
+ *   returns:
+ *          OK:      The client executed all of its commands and is exiting
+ *                   either by the `exit` command that terminates the client
+ *                   or the `stop-server` command that terminates both the
+ *                   client and the server. 
+ *          ERR_MEMORY:             If this function cannot allocate memory via
+ *                                  malloc for the send and receive buffers
+ *          ERR_RDSH_CLIENT:        If the client cannot connect to the server. 
+ *                                  AKA the call to start_client() fails.
+ *          ERR_RDSH_COMMUNICATION: If there is a communication error, AKA
+ *                                  any failures from send() or recv().
+ * 
+ *   NOTE:  Since there are several exit points and each exit point must
+ *          call free() on the buffers allocated, close the socket, and
+ *          return an appropriate error code.  Its suggested you use the
+ *          helper function client_cleanup() for these purposes.  For example:
+ * 
+ *   return client_cleanup(cli_socket, request_buff, resp_buff, ERR_RDSH_COMMUNICATION);
+ *   return client_cleanup(cli_socket, request_buff, resp_buff, OK);
+ *
+ *   The above will return ERR_RDSH_COMMUNICATION and OK respectively to the main()
+ *   function after cleaning things up.  See the documentation for client_cleanup()
+ *      
+ */
+int exec_remote_cmd_loop(char *address, int port)
+{
+    char *cmd_buff;
+    char *rsp_buff;
+    int cli_socket;
+    ssize_t io_size;
+    int is_eof;
+
+    // TODO set up cmd and response buffs
+
+    // Allocate memory for cmd_buff and rsp_buff and check to make sure they allocated properly
+    cmd_buff = (char *)malloc(RDSH_COMM_BUFF_SZ);
+    if(cmd_buff == NULL) {
+	    perror("malloc failed");
+	    return ERR_MEMORY;
+    }
+
+    rsp_buff = (char *)malloc(RDSH_COMM_BUFF_SZ);
+    if(rsp_buff == NULL) {
+            perror("malloc failed");
+            return ERR_MEMORY;
+    }
+
+    // Start the client by connecting to the server
+    cli_socket = start_client(address,port);
+
+    // Check to make sure the connection succeeded, give proper error and cleanup resources if the connection failed
+    if (cli_socket < 0){
+        perror("start client");
+        return client_cleanup(cli_socket, cmd_buff, rsp_buff, ERR_RDSH_CLIENT);
+    }
+
+    // Starts an infinite loop to keep reading and sending commands
+    while (1) 
+    {
+        // TODO print prompt
+	printf("%s", SH_PROMPT);
+
+        // TODO fgets input
+	// If fgets fails or you press CTRL+D, it will print a newline and exit the loop
+	if(fgets(cmd_buff, ARG_MAX, stdin) == NULL) {
+		printf("\n");
+		break;
+	}
+
+	// Remove newline character
+	cmd_buff[strcspn(cmd_buff, "\n")] = '\0';
+
+        // TODO send() over cli_socket
+	int send_length = strlen(cmd_buff) + 1;
+	int bytes_sent;
+
+	// Send the command to the server
+	bytes_sent = send(cli_socket, cmd_buff, send_length, 0);
+
+	// Check to make sure the sending succeeded, print the proper error message and cleanup resources if it failed
+	if(bytes_sent < 0) {
+		perror("send failed");
+		return client_cleanup(cli_socket, cmd_buff, rsp_buff, ERR_RDSH_COMMUNICATION);
+	}
+
+        // TODO recv all the results
+	while((io_size = recv(cli_socket, rsp_buff, RDSH_COMM_BUFF_SZ, 0)) > 0) {
+		// Check to make sure the receiving succeeded, print the proper error message and cleanup resources if it failed
+		if(io_size < 0) {
+			perror("recv failed");
+			return client_cleanup(cli_socket, cmd_buff, rsp_buff, ERR_RDSH_COMMUNICATION);
+		}
+
+		// If there was no data received, break the loop
+		if(io_size == 0) {
+			break;
+		}
+		
+		// Check if the received data ends with the EOF character
+		is_eof = (rsp_buff[io_size - 1] == RDSH_EOF_CHAR);
+
+		// If it's the EOF, replace it with the null terminator
+		if(is_eof) {
+			rsp_buff[io_size - 1] = '\0';
+		}
+
+		// Print the received data
+		printf("%.*s", (int)io_size, rsp_buff);
+
+		// If EOF was detected, stop receiving commands and break the loop
+		if(is_eof) {
+			break;
+		}
+
+        	// TODO break on exit command
+		// If the user either uses the exits command or stops the server, break the loop
+		if(strcmp(cmd_buff, EXIT_CMD) == 0) {
+			break;
+		}
+
+		if(strcmp(cmd_buff, "stop-server") == 0) {
+			break;
+		}
+    	}
+    }
+
+    // Cleanup any resources
+    return client_cleanup(cli_socket, cmd_buff, rsp_buff, OK);
+}
+
+/*
+ * start_client(server_ip, port)
+ *      server_ip:  a string in ip address format, indicating the servers IP
+ *                  address.  Note 127.0.0.1 is the default meaning the server
+ *                  is running on the same machine as the client
+ *              
+ *      port:   The port the server will use.  Note the constant 
+ *              RDSH_DEF_PORT which is 1234 in rshlib.h.  If you are using
+ *              tux you may need to change this to your own default, or even
+ *              better use the command line override -c implemented in dsh_cli.c
+ *              For example ./dsh -c 10.50.241.18:5678 where 5678 is the new port
+ *              number and the server address is 10.50.241.18    
+ * 
+ *      This function basically runs the client by: 
+ *          1. Creating the client socket via socket()
+ *          2. Calling connect()
+ *          3. Returning the client socket after connecting to the server
+ * 
+ *   returns:
+ *          client_socket:      The file descriptor fd of the client socket
+ *          ERR_RDSH_CLIENT:    If socket() or connect() fail
+ * 
+ */
+int start_client(char *server_ip, int port){
+    struct sockaddr_in addr;
+    int cli_socket;
+    int ret;
+
+    // TODO set up cli_socket
+    
+    // Create the socket
+    cli_socket = socket(AF_INET, SOCK_STREAM, 0);
+    
+    // Check if the socket was created properly, if it didn't, print the correct error message and return the error code
+    if(cli_socket == -1) {
+	    perror("socket");
+	    return ERR_RDSH_CLIENT;
+    }
+
+    // Clear out the memory for the addr structure
+    memset(&addr, 0, sizeof(addr));
+
+    // Setup the address family
+    addr.sin_family = AF_INET;
+
+    // Convert the server IP string to a network address
+    addr.sin_addr.s_addr = inet_addr(server_ip);
+
+    // Set the port for the server connection
+    addr.sin_port = htons(port);
+
+    // Try to establish a connection with the server using the client socket
+    ret = connect(cli_socket, (struct sockaddr *)&addr, sizeof(addr));
+
+    // Check to see if it worked, if it didn't, print the proper error message, close the socket, and return an error code
+    if(ret == -1) {
+	    perror("connect");
+	    close(cli_socket);
+	    return ERR_RDSH_CLIENT;
+    }
+
+    // If the connection was successful, return the valid client socket
+    return cli_socket;
+}
+
+/*
+ * client_cleanup(int cli_socket, char *cmd_buff, char *rsp_buff, int rc)
+ *      cli_socket:   The client socket
+ *      cmd_buff:     The buffer that will hold commands to send to server
+ *      rsp_buff:     The buffer that will hld server responses
+ * 
+ *   This function does the following: 
+ *      1. If cli_socket > 0 it calls close(cli_socket) to close the socket
+ *      2. It calls free() on cmd_buff and rsp_buff
+ *      3. It returns the value passed as rc
+ *  
+ *   Note this function is intended to be helper to manage exit conditions
+ *   from the exec_remote_cmd_loop() function given there are several
+ *   cleanup steps.  We provide it to you fully implemented as a helper.
+ *   You do not have to use it if you want to develop an alternative
+ *   strategy for cleaning things up in your exec_remote_cmd_loop()
+ *   implementation. 
+ * 
+ *   returns:
+ *          rc:   This function just returns the value passed as the 
+ *                rc parameter back to the caller.  This way the caller
+ *                can just write return client_cleanup(...)
+ *      
+ */
+int client_cleanup(int cli_socket, char *cmd_buff, char *rsp_buff, int rc){
+    //If a valid socket number close it.
+    if(cli_socket > 0){
+        close(cli_socket);
+    }
+
+    //Free up the buffers 
+    free(cmd_buff);
+    free(rsp_buff);
+
+    //Echo the return value that was passed as a parameter
+    return rc;
+}
diff --git a/assignments/assignment-6/starter/rsh_server.c b/assignments/assignment-6/starter/rsh_server.c
new file mode 100644
index 0000000000000000000000000000000000000000..e34c1ab28006ff5091290b24800204aa502a3801
--- /dev/null
+++ b/assignments/assignment-6/starter/rsh_server.c
@@ -0,0 +1,675 @@
+
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/un.h>
+#include <fcntl.h>
+
+//INCLUDES for extra credit
+//#include <signal.h>
+//#include <pthread.h>
+//-------------------------
+
+#include "dshlib.h"
+#include "rshlib.h"
+
+
+/*
+ * start_server(ifaces, port, is_threaded)
+ *      ifaces:  a string in ip address format, indicating the interface
+ *              where the server will bind.  In almost all cases it will
+ *              be the default "0.0.0.0" which binds to all interfaces.
+ *              note the constant RDSH_DEF_SVR_INTFACE in rshlib.h
+ * 
+ *      port:   The port the server will use.  Note the constant 
+ *              RDSH_DEF_PORT which is 1234 in rshlib.h.  If you are using
+ *              tux you may need to change this to your own default, or even
+ *              better use the command line override -s implemented in dsh_cli.c
+ *              For example ./dsh -s 0.0.0.0:5678 where 5678 is the new port  
+ * 
+ *      is_threded:  Used for extra credit to indicate the server should implement
+ *                   per thread connections for clients  
+ * 
+ *      This function basically runs the server by: 
+ *          1. Booting up the server
+ *          2. Processing client requests until the client requests the
+ *             server to stop by running the `stop-server` command
+ *          3. Stopping the server. 
+ * 
+ *      This function is fully implemented for you and should not require
+ *      any changes for basic functionality.  
+ * 
+ *      IF YOU IMPLEMENT THE MULTI-THREADED SERVER FOR EXTRA CREDIT YOU NEED
+ *      TO DO SOMETHING WITH THE is_threaded ARGUMENT HOWEVER.  
+ */
+int start_server(char *ifaces, int port, int is_threaded){
+    int svr_socket;
+    int rc;
+
+    //
+    //TODO:  If you are implementing the extra credit, please add logic
+    //       to keep track of is_threaded to handle this feature
+    //
+
+    svr_socket = boot_server(ifaces, port);
+    if (svr_socket < 0){
+        int err_code = svr_socket;  //server socket will carry error code
+        return err_code;
+    }
+
+    rc = process_cli_requests(svr_socket);
+
+    stop_server(svr_socket);
+
+
+    return rc;
+}
+
+/*
+ * stop_server(svr_socket)
+ *      svr_socket: The socket that was created in the boot_server()
+ *                  function. 
+ * 
+ *      This function simply returns the value of close() when closing
+ *      the socket.  
+ */
+int stop_server(int svr_socket){
+    return close(svr_socket);
+}
+
+/*
+ * boot_server(ifaces, port)
+ *      ifaces & port:  see start_server for description.  They are passed
+ *                      as is to this function.   
+ * 
+ *      This function "boots" the rsh server.  It is responsible for all
+ *      socket operations prior to accepting client connections.  Specifically: 
+ * 
+ *      1. Create the server socket using the socket() function. 
+ *      2. Calling bind to "bind" the server to the interface and port
+ *      3. Calling listen to get the server ready to listen for connections.
+ * 
+ *      after creating the socket and prior to calling bind you might want to 
+ *      include the following code:
+ * 
+ *      int enable=1;
+ *      setsockopt(svr_socket, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
+ * 
+ *      when doing development you often run into issues where you hold onto
+ *      the port and then need to wait for linux to detect this issue and free
+ *      the port up.  The code above tells linux to force allowing this process
+ *      to use the specified port making your life a lot easier.
+ * 
+ *  Returns:
+ * 
+ *      server_socket:  Sockets are just file descriptors, if this function is
+ *                      successful, it returns the server socket descriptor, 
+ *                      which is just an integer.
+ * 
+ *      ERR_RDSH_COMMUNICATION:  This error code is returned if the socket(),
+ *                               bind(), or listen() call fails. 
+ * 
+ */
+int boot_server(char *ifaces, int port){
+    int svr_socket;
+    int ret;
+    
+    struct sockaddr_in addr;
+
+    // TODO set up the socket - this is very similar to the demo code
+
+    // Create a socket for the server
+    svr_socket = socket(AF_INET, SOCK_STREAM, 0);
+
+    // Check if the socket was created succesfully, give the proper error and return the right error code if it wasn't
+    if(svr_socket == -1) {
+	    perror("socket");
+	    return ERR_RDSH_COMMUNICATION;
+    }
+
+    int enable = 1;
+
+    // Allow address reuse and check to see if it works, if it doesn't, give the proper error, close the socket, and return the proper error code
+    if(setsockopt(svr_socket, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) == -1) {
+	    perror("setsockopt");
+	    close(svr_socket);
+	    return ERR_RDSH_COMMUNICATION;
+    }
+
+    // Clear memory in the addr struct
+    memset(&addr, 0, sizeof(addr));
+
+    // Set address family
+    addr.sin_family = AF_INET;
+
+    // Convert the ip string to a network address
+    addr.sin_addr.s_addr = inet_addr(ifaces);
+
+    // Set the port number
+    addr.sin_port = htons(port);
+
+    // Bind the socket to the address in the addr struct
+    ret = bind(svr_socket, (struct sockaddr *)&addr, sizeof(addr));
+
+    // Check if the bind was successful, if it wasn't, give the proper error, close the socket, and return the proper erorr code
+    if(ret == -1) {
+	    perror("bind");
+	    close(svr_socket);
+	    return ERR_RDSH_COMMUNICATION;
+    }
+
+
+    /*
+     * Prepare for accepting connections. The backlog size is set
+     * to 20. So while one request is being processed other requests
+     * can be waiting.
+     */
+    ret = listen(svr_socket, 20);
+    if (ret == -1) {
+        perror("listen");
+        return ERR_RDSH_COMMUNICATION;
+    }
+
+    return svr_socket;
+}
+
+/*
+ * process_cli_requests(svr_socket)
+ *      svr_socket:  The server socket that was obtained from boot_server()
+ *   
+ *  This function handles managing client connections.  It does this using
+ *  the following logic
+ * 
+ *      1.  Starts a while(1) loop:
+ *  
+ *          a. Calls accept() to wait for a client connection. Recall that 
+ *             the accept() function returns another socket specifically
+ *             bound to a client connection. 
+ *          b. Calls exec_client_requests() to handle executing commands
+ *             sent by the client. It will use the socket returned from
+ *             accept().
+ *          c. Loops back to the top (step 2) to accept connecting another
+ *             client.  
+ * 
+ *          note that the exec_client_requests() return code should be
+ *          negative if the client requested the server to stop by sending
+ *          the `stop-server` command.  If this is the case step 2b breaks
+ *          out of the while(1) loop. 
+ * 
+ *      2.  After we exit the loop, we need to cleanup.  Dont forget to 
+ *          free the buffer you allocated in step #1.  Then call stop_server()
+ *          to close the server socket. 
+ * 
+ *  Returns:
+ * 
+ *      OK_EXIT:  When the client sends the `stop-server` command this function
+ *                should return OK_EXIT. 
+ * 
+ *      ERR_RDSH_COMMUNICATION:  This error code terminates the loop and is
+ *                returned from this function in the case of the accept() 
+ *                function failing. 
+ * 
+ *      OTHERS:   See exec_client_requests() for return codes.  Note that positive
+ *                values will keep the loop running to accept additional client
+ *                connections, and negative values terminate the server. 
+ * 
+ */
+int process_cli_requests(int svr_socket){
+    int cli_socket;
+    int rc = OK;
+
+    while(1) {
+        // TODO use the accept syscall to create cli_socket 
+        // and then exec_client_requests(cli_socket)
+	
+    	cli_socket = accept(svr_socket, NULL, NULL);
+
+	// Check if the accept was successful, if it wasn't, give the proper error and store the error code in RC, and break the loop
+	if(cli_socket == -1) {
+		perror("accept");
+		rc = ERR_RDSH_COMMUNICATION;
+		break;
+	}
+
+	// Call exec_client_requests to handle the requests
+	rc = exec_client_requests(cli_socket);
+
+	// If the exec_client_requests return an error code, break out of the loop
+	if(rc < 0) {
+		break;
+	}
+    }
+
+    // After breaking out of the llop, if the client socket was created and is currently open, close it
+    if(cli_socket != -1) {
+	    close(cli_socket);
+    }
+
+    // Stop the server
+    stop_server(cli_socket);
+
+    // Return the result code
+    return rc;
+}
+
+/*
+ * exec_client_requests(cli_socket)
+ *      cli_socket:  The server-side socket that is connected to the client
+ *   
+ *  This function handles accepting remote client commands. The function will
+ *  loop and continue to accept and execute client commands.  There are 2 ways
+ *  that this ongoing loop accepting client commands ends:
+ * 
+ *      1.  When the client executes the `exit` command, this function returns
+ *          to process_cli_requests() so that we can accept another client
+ *          connection. 
+ *      2.  When the client executes the `stop-server` command this function
+ *          returns to process_cli_requests() with a return code of OK_EXIT
+ *          indicating that the server should stop. 
+ * 
+ *  Note that this function largely follows the implementation of the
+ *  exec_local_cmd_loop() function that you implemented in the last 
+ *  shell program deliverable. The main difference is that the command will
+ *  arrive over the recv() socket call rather than reading a string from the
+ *  keyboard. 
+ * 
+ *  This function also must send the EOF character after a command is
+ *  successfully executed to let the client know that the output from the
+ *  command it sent is finished.  Use the send_message_eof() to accomplish 
+ *  this. 
+ * 
+ *  Of final note, this function must allocate a buffer for storage to 
+ *  store the data received by the client. For example:
+ *     io_buff = malloc(RDSH_COMM_BUFF_SZ);
+ *  And since it is allocating storage, it must also properly clean it up
+ *  prior to exiting.
+ * 
+ *  Returns:
+ * 
+ *      OK:       The client sent the `exit` command.  Get ready to connect
+ *                another client. 
+ *      OK_EXIT:  The client sent `stop-server` command to terminate the server
+ * 
+ *      ERR_RDSH_COMMUNICATION:  A catch all for any socket() related send
+ *                or receive errors. 
+ */
+int exec_client_requests(int cli_socket) {
+    int io_size;
+    command_list_t *cmd_list = malloc(sizeof(command_list_t));
+    int rc;
+    int cmd_rc;
+    int last_rc;
+    char *io_buff;
+
+    io_buff = malloc(RDSH_COMM_BUFF_SZ);
+    if (io_buff == NULL){
+        return ERR_RDSH_SERVER;
+    }
+
+    while(1) {
+        // TODO use recv() syscall to get input
+	io_size = recv(cli_socket, io_buff, RDSH_COMM_BUFF_SZ - 1, 0);
+	// Check if recv was successful, if it wasn't, free io_buff and return the proper error code
+	if(io_size == -1) {
+		free(io_buff);
+		return ERR_RDSH_COMMUNICATION;
+	}
+	
+        // TODO build up a cmd_list
+	
+	// Set the size of cmd_list to 0, build the list using the function from assignment 5, and store whatever return code to rc
+	cmd_list->num = 0;
+	rc = build_cmd_list(io_buff, cmd_list);
+
+	// TODO: rsh_execute_pipeline to run your cmd_list
+	
+	// If building the list returned OK, we will execute the pipeline with the built list
+	if(rc == OK) {
+		cmd_rc = rsh_execute_pipeline(cli_socket, cmd_list);
+	}
+        
+	// If the user either uses the EXIT command or stops the server, free io_buff, free the list, and return the proper error code
+	if(strcmp(io_buff, EXIT_CMD) == 0) {
+		free(io_buff);
+		
+		rc = free_cmd_list(cmd_list);	
+		
+		return OK;
+	}
+	else if(strcmp(io_buff, "stop-server") == 0) {
+		free(io_buff);
+
+		rc = free_cmd_list(cmd_list);
+		
+		return OK_EXIT;
+	}
+
+        // TODO send appropriate respones with send_message_string
+        // - error constants for failures
+        // - buffer contents from execute commands
+        //  - etc.
+	if(cmd_rc == 0) {
+		send_message_string(cli_socket, io_buff);
+	}
+	else {
+		send_message_string(cli_socket, CMD_ERR_RDSH_EXEC);
+	}
+
+        // TODO send_message_eof when done
+	send_message_eof(cli_socket);
+    }
+}
+
+/*
+ * send_message_eof(cli_socket)
+ *      cli_socket:  The server-side socket that is connected to the client
+
+ *  Sends the EOF character to the client to indicate that the server is
+ *  finished executing the command that it sent. 
+ * 
+ *  Returns:
+ * 
+ *      OK:  The EOF character was sent successfully. 
+ * 
+ *      ERR_RDSH_COMMUNICATION:  The send() socket call returned an error or if
+ *           we were unable to send the EOF character. 
+ */
+int send_message_eof(int cli_socket){
+    int send_len = (int)sizeof(RDSH_EOF_CHAR);
+    int sent_len;
+    sent_len = send(cli_socket, &RDSH_EOF_CHAR, send_len, 0);
+
+    if (sent_len != send_len){
+        return ERR_RDSH_COMMUNICATION;
+    }
+    return OK;
+}
+
+
+/*
+ * send_message_string(cli_socket, char *buff)
+ *      cli_socket:  The server-side socket that is connected to the client
+ *      buff:        A C string (aka null terminated) of a message we want
+ *                   to send to the client. 
+ *   
+ *  Sends a message to the client.  Note this command executes both a send()
+ *  to send the message and a send_message_eof() to send the EOF character to
+ *  the client to indicate command execution terminated. 
+ * 
+ *  Returns:
+ * 
+ *      OK:  The message in buff followed by the EOF character was 
+ *           sent successfully. 
+ * 
+ *      ERR_RDSH_COMMUNICATION:  The send() socket call returned an error or if
+ *           we were unable to send the message followed by the EOF character. 
+ */
+int send_message_string(int cli_socket, char *buff){
+    // TODO: implement writing to cli_socket with send()
+    int send_length = strlen(buff) + 1;
+    int bytes_sent;
+
+    // Use send to send the string over the socket
+    bytes_sent = send(cli_socket, buff, send_length, 0);
+
+    // Check if the sending was successful, if it was not, give the proper error and return the proper error code
+    if(bytes_sent == -1) {
+	    perror("send failed");
+	    return ERR_RDSH_COMMUNICATION;
+    }
+
+    // Send an EOF character to indicate the end of the message
+    int result = send_message_eof(cli_socket);
+
+    // If the EOF message was sent successfully, return OK
+    if(result == OK) {
+	    return OK;
+    }
+    // If the EOF message failed to send, give the proper error and return the proper error code
+    else {
+	    perror("eof send failed");
+	    return ERR_RDSH_COMMUNICATION;
+    }
+}
+
+
+/*
+ * rsh_execute_pipeline(int cli_sock, command_list_t *clist)
+ *      cli_sock:    The server-side socket that is connected to the client
+ *      clist:       The command_list_t structure that we implemented in
+ *                   the last shell. 
+ *   
+ *  This function executes the command pipeline.  It should basically be a
+ *  replica of the execute_pipeline() function from the last deliverable. 
+ *  The only thing different is that you will be using the cli_sock as the
+ *  main file descriptor on the first executable in the pipeline for STDIN,
+ *  and the cli_sock for the file descriptor for STDOUT, and STDERR for the
+ *  last executable in the pipeline.  See picture below:  
+ * 
+ *      
+ *┌───────────┐                                                    ┌───────────┐
+ *│ cli_sock  │                                                    │ cli_sock  │
+ *└─────┬─────┘                                                    └────▲──▲───┘
+ *      │   ┌──────────────┐     ┌──────────────┐     ┌──────────────┐  │  │    
+ *      │   │   Process 1  │     │   Process 2  │     │   Process N  │  │  │    
+ *      │   │              │     │              │     │              │  │  │    
+ *      └───▶stdin   stdout├─┬──▶│stdin   stdout├─┬──▶│stdin   stdout├──┘  │    
+ *          │              │ │   │              │ │   │              │     │    
+ *          │        stderr├─┘   │        stderr├─┘   │        stderr├─────┘    
+ *          └──────────────┘     └──────────────┘     └──────────────┘   
+ *                                                      WEXITSTATUS()
+ *                                                      of this last
+ *                                                      process to get
+ *                                                      the return code
+ *                                                      for this function       
+ * 
+ *  Returns:
+ * 
+ *      EXIT_CODE:  This function returns the exit code of the last command
+ *                  executed in the pipeline.  If only one command is executed
+ *                  that value is returned.  Remember, use the WEXITSTATUS()
+ *                  macro that we discussed during our fork/exec lecture to
+ *                  get this value. 
+ */
+int rsh_execute_pipeline(int cli_socket, command_list_t *clist) {
+    int pipes[clist->num - 1][2];  // Array of pipes
+    pid_t pids[clist->num];
+    int  pids_st[clist->num];         // Array to store process IDs
+    Built_In_Cmds bi_cmd;
+    int exit_code;
+
+    // Create all necessary pipes
+    for (int i = 0; i < clist->num - 1; i++) {
+        if (pipe(pipes[i]) == -1) {
+            perror("pipe");
+            exit(EXIT_FAILURE);
+        }
+    }
+
+    for (int i = 0; i < clist->num; i++) {
+        // TODO this is basically the same as the piped fork/exec assignment, except for where you connect the begin and end of the pipeline (hint: cli_sock)
+
+        // TODO HINT you can dup2(cli_sock with STDIN_FILENO, STDOUT_FILENO, etc.
+	
+	// Fork a new process for each command in the pipeline
+	pids[i] = fork();
+
+	// CHeck if there are any errors with the fork, if there are, return the proper error and exit
+	if(pids[i] == -1) {
+		perror("fork");
+		exit(EXIT_FAILURE);
+	}
+
+	// Child process
+	if(pids[i] == 0) {
+		// Grab the current command from the command list
+		cmd_buff_t *cmd = &clist->commands[i];
+
+		// If it's the first command in the pipeline, connect the input with the file socket
+		if(i == 0) {
+			dup2(cli_socket, STDIN_FILENO);
+		}
+
+		// If it's the last command in the pipeline, connect stdout and stderr to the client socket
+		if(i == clist->num - 1) {
+			dup2(cli_socket, STDOUT_FILENO);
+			dup2(cli_socket, STDERR_FILENO);
+		}
+
+		// If it's not the first command, connect stdin to the previous pipe's output
+		if(i > 0) {
+			dup2(pipes[i - 1][0], STDIN_FILENO);
+		}
+
+		// If it's not the last command, connect stdout to the current pipe's input
+		if(i < clist->num - 1) {
+			dup2(pipes[i][1], STDOUT_FILENO);
+		}
+
+		// Close all pipes in the child process since they're no longer needed
+		for(int j = 0; j < clist->num - 1; j++) {
+			close(pipes[j][0]);
+			close(pipes[j][1]);
+		}
+
+		// Execute the command using execvp
+		execvp(cmd->argv[0], cmd->argv);
+
+		// If execvp fails, give the proper error message and exit the child process
+		perror("execvp");
+		exit(EXIT_FAILURE);
+	}
+
+    }
+
+
+    // Parent process: close all pipe ends
+    for (int i = 0; i < clist->num - 1; i++) {
+        close(pipes[i][0]);
+        close(pipes[i][1]);
+    }
+
+    // Wait for all children
+    for (int i = 0; i < clist->num; i++) {
+        waitpid(pids[i], &pids_st[i], 0);
+    }
+
+    //by default get exit code of last process
+    //use this as the return value
+    exit_code = WEXITSTATUS(pids_st[clist->num - 1]);
+    for (int i = 0; i < clist->num; i++) {
+        //if any commands in the pipeline are EXIT_SC
+        //return that to enable the caller to react
+        if (WEXITSTATUS(pids_st[i]) == EXIT_SC)
+            exit_code = EXIT_SC;
+    }
+    return exit_code;
+}
+
+/**************   OPTIONAL STUFF  ***************/
+/****
+ **** NOTE THAT THE FUNCTIONS BELOW ALIGN TO HOW WE CRAFTED THE SOLUTION
+ **** TO SEE IF A COMMAND WAS BUILT IN OR NOT.  YOU CAN USE A DIFFERENT
+ **** STRATEGY IF YOU WANT.  IF YOU CHOOSE TO DO SO PLEASE REMOVE THESE
+ **** FUNCTIONS AND THE PROTOTYPES FROM rshlib.h
+ **** 
+ */
+
+/*
+ * rsh_match_command(const char *input)
+ *      cli_socket:  The string command for a built-in command, e.g., dragon,
+ *                   cd, exit-server
+ *   
+ *  This optional function accepts a command string as input and returns
+ *  one of the enumerated values from the BuiltInCmds enum as output. For
+ *  example:
+ * 
+ *      Input             Output
+ *      exit              BI_CMD_EXIT
+ *      dragon            BI_CMD_DRAGON
+ * 
+ *  This function is entirely optional to implement if you want to handle
+ *  processing built-in commands differently in your implementation. 
+ * 
+ *  Returns:
+ * 
+ *      BI_CMD_*:   If the command is built-in returns one of the enumeration
+ *                  options, for example "cd" returns BI_CMD_CD
+ * 
+ *      BI_NOT_BI:  If the command is not "built-in" the BI_NOT_BI value is
+ *                  returned. 
+ */
+Built_In_Cmds rsh_match_command(const char *input)
+{
+    if (strcmp(input, "exit") == 0)
+        return BI_CMD_EXIT;
+    if (strcmp(input, "dragon") == 0)
+        return BI_CMD_DRAGON;
+    if (strcmp(input, "cd") == 0)
+        return BI_CMD_CD;
+    if (strcmp(input, "stop-server") == 0)
+        return BI_CMD_STOP_SVR;
+    if (strcmp(input, "rc") == 0)
+        return BI_CMD_RC;
+    return BI_NOT_BI;
+}
+
+/*
+ * rsh_built_in_cmd(cmd_buff_t *cmd)
+ *      cmd:  The cmd_buff_t of the command, remember, this is the 
+ *            parsed version fo the command
+ *   
+ *  This optional function accepts a parsed cmd and then checks to see if
+ *  the cmd is built in or not.  It calls rsh_match_command to see if the 
+ *  cmd is built in or not.  Note that rsh_match_command returns BI_NOT_BI
+ *  if the command is not built in. If the command is built in this function
+ *  uses a switch statement to handle execution if appropriate.   
+ * 
+ *  Again, using this function is entirely optional if you are using a different
+ *  strategy to handle built-in commands.  
+ * 
+ *  Returns:
+ * 
+ *      BI_NOT_BI:   Indicates that the cmd provided as input is not built
+ *                   in so it should be sent to your fork/exec logic
+ *      BI_EXECUTED: Indicates that this function handled the direct execution
+ *                   of the command and there is nothing else to do, consider
+ *                   it executed.  For example the cmd of "cd" gets the value of
+ *                   BI_CMD_CD from rsh_match_command().  It then makes the libc
+ *                   call to chdir(cmd->argv[1]); and finally returns BI_EXECUTED
+ *      BI_CMD_*     Indicates that a built-in command was matched and the caller
+ *                   is responsible for executing it.  For example if this function
+ *                   returns BI_CMD_STOP_SVR the caller of this function is
+ *                   responsible for stopping the server.  If BI_CMD_EXIT is returned
+ *                   the caller is responsible for closing the client connection.
+ * 
+ *   AGAIN - THIS IS TOTALLY OPTIONAL IF YOU HAVE OR WANT TO HANDLE BUILT-IN
+ *   COMMANDS DIFFERENTLY. 
+ */
+Built_In_Cmds rsh_built_in_cmd(cmd_buff_t *cmd)
+{
+    Built_In_Cmds ctype = BI_NOT_BI;
+    ctype = rsh_match_command(cmd->argv[0]);
+
+    switch (ctype)
+    {
+    // case BI_CMD_DRAGON:
+    //     print_dragon();
+    //     return BI_EXECUTED;
+    case BI_CMD_EXIT:
+        return BI_CMD_EXIT;
+    case BI_CMD_STOP_SVR:
+        return BI_CMD_STOP_SVR;
+    case BI_CMD_RC:
+        return BI_CMD_RC;
+    case BI_CMD_CD:
+        chdir(cmd->argv[1]);
+        return BI_EXECUTED;
+    default:
+        return BI_NOT_BI;
+    }
+}
diff --git a/assignments/assignment-6/starter/rshlib.h b/assignments/assignment-6/starter/rshlib.h
new file mode 100644
index 0000000000000000000000000000000000000000..6ae2ca9d992041d6d71d8d29a20304ddc5c1f700
--- /dev/null
+++ b/assignments/assignment-6/starter/rshlib.h
@@ -0,0 +1,78 @@
+#ifndef __RSH_LIB_H__
+    #define __RSH_LIB_H__
+
+#include "dshlib.h"
+
+//common remote shell client and server constants and definitions
+
+
+//Constants for communication
+//Note that these should work fine in a local VM but you will likely have
+//to change the port number if you are working on tux.
+#define RDSH_DEF_PORT           1234        //Default port #
+#define RDSH_DEF_SVR_INTFACE    "0.0.0.0"   //Default start all interfaces
+#define RDSH_DEF_CLI_CONNECT    "127.0.0.1" //Default server is running on
+                                            //localhost 127.0.0.1
+
+//constants for buffer sizes
+#define RDSH_COMM_BUFF_SZ       (1024*64)   //64K
+#define STOP_SERVER_SC          200         //returned from pipeline excution
+                                            //if the command is to stop the
+                                            //server.  See documentation for 
+                                            //exec_client_requests() for more info
+
+//end of message delimiter.  This is super important.  TCP is a stream, therefore
+//the protocol designer is responsible for managing where messages begin and end
+//there are many common techniques for this, but one of the simplest ways is to
+//use an end of stream marker.  Since rsh is a "shell" program we will be using
+//ascii code 0x04, which is commonly used as the end-of-file (EOF) character in
+//linux based systems. 
+static const char RDSH_EOF_CHAR = 0x04;    
+
+//rdsh specific error codes for functions
+#define ERR_RDSH_COMMUNICATION  -50     //Used for communication errors
+#define ERR_RDSH_SERVER         -51     //General server errors
+#define ERR_RDSH_CLIENT         -52     //General client errors
+#define ERR_RDSH_CMD_EXEC       -53     //RSH command execution errors
+#define WARN_RDSH_NOT_IMPL      -99     //Not Implemented yet warning
+
+//Output message constants for server
+#define CMD_ERR_RDSH_COMM   "rdsh-error: communications error\n"
+#define CMD_ERR_RDSH_EXEC   "rdsh-error: command execution error\n"
+#define CMD_ERR_RDSH_ITRNL  "rdsh-error: internal server error - %d\n"
+#define CMD_ERR_RDSH_SEND   "rdsh-error: partial send.  Sent %d, expected to send %d\n"
+#define RCMD_SERVER_EXITED  "server appeared to terminate - exiting\n"
+
+//Output message constants for client
+#define RCMD_MSG_CLIENT_EXITED  "client exited: getting next connection...\n"
+#define RCMD_MSG_SVR_STOP_REQ   "client requested server to stop, stopping...\n"
+#define RCMD_MSG_SVR_EXEC_REQ   "rdsh-exec:  %s\n"
+#define RCMD_MSG_SVR_RC_CMD     "rdsh-exec:  rc = %d\n"
+
+//client prototypes for rsh_cli.c - - see documentation for each function to
+//see what they do
+int start_client(char *address, int port);
+int client_cleanup(int cli_socket, char *cmd_buff, char *rsp_buff, int rc);
+int exec_remote_cmd_loop(char *address, int port);
+    
+
+//server prototypes for rsh_server.c - see documentation for each function to
+//see what they do
+int start_server(char *ifaces, int port, int is_threaded);
+int boot_server(char *ifaces, int port);
+int stop_server(int svr_socket);
+int send_message_eof(int cli_socket);
+int send_message_string(int cli_socket, char *buff);
+int process_cli_requests(int svr_socket);
+int exec_client_requests(int cli_socket);
+int rsh_execute_pipeline(int socket_fd, command_list_t *clist);
+
+Built_In_Cmds rsh_match_command(const char *input);
+Built_In_Cmds rsh_built_in_cmd(cmd_buff_t *cmd);
+
+//eliminate from template, for extra credit
+void set_threaded_server(int val);
+int exec_client_thread(int main_socket, int cli_socket);
+void *handle_client(void *arg);
+
+#endif
\ No newline at end of file