LMU ☀️ CMSI 662
SECURE SOFTWARE DEVELOPMENT
HOMEWORK #1 PARTIAL ANSWERS
  1. Answers vary. Here is my attempt at short definitions:
    1. Risk: A measure of possible loss
    2. Threat: An adversary or a possible negative event
    3. Defect: A problem with a system
    4. Flaw: An defect in the design
    5. Bug: An defect in the code
    6. Vulnerability: A defect that can be targeted by an attacker
    7. Weakness: An error that can become a vulnerability
    8. Failure: An occurrence of a system not meeting correctness criteria
    9. Exploit: Code or data that does the bad things
    10. Integrity: Immunity from malicious or accidental modification
    11. Authentication: Determining whether one is who they say they are
    12. Authorization: Determining whether one is allowed to do something
  2. Answers vary, as you can pick your own videos.
  3. Here are my summaries. Yours will probably be better.
    Heartbleed
    Heartbleed is a vulnerability introduced into OpenSSL in December 2011 and patched in April 2014. OpenSSL does transport layer security for a massive number of servers on the global Internet, and the memory contents of a running OpenSSL processes may contain some pretty sensitive stuff. One of the services in OpenSSL is the heartbeat, where a program periodically asks OpenSSL, “hey server, if you are still there, repeat [this message] back to me, and it is [this many] bytes long.” The server allocates a buffer of the requested number of bytes somewhere in memory (regardless of what is already there), copies the message into it, then responds with the whole buffer. If you ask OpenSSL to echo your 100 byte message but tell it your message is 65536 long, OpenSSL will give you back your 100 bytes plus 65436 bytes of stuff that may contain keys, passwords, logs, who knows. It’s called Heartbleed because it bleeds (leaks) memory to the attacker during the heartbeat.
    XSS
    Cross-site scripting (XSS) occurs when a webapp writes out HTML containing unsanitized user input to a browser. An attacker can submit executable JavaScript (between <script> and </script>tags) via a web form or API call, and if the victim does not validate or sanitize the input and drops it into a page, the JavaScript code will run. The malicious JavaScript can do things like read the victim's cookies or local storage and send things to the attacker’s server or another server that collects this info.
    Billion Laughs
    The famous billion laughs attack can be used against any server that accepts user input in XML format with a payload containing entity definitions which the server starts parsing without checking to see if those entities are nasty. It is possible with just a few hundred bytes to craft an entity that expands into several gigabytes. One particular payload expands, on processing, into one billion instances of the string lol, hence the name billion laughs. That’s at least 3GB, enough to impact the availability of a server as a good chunk of its memory gets filled up. This trick can be used to generate payloads of just about any size, so the generic attack is called Uncontrolled XML Entity Expansion.
    CSRF
    In a Cross-Site Request Forgery (CSRF) attack, the attacker tricks the victim into sending data to a server that the victim is authenticated with, but the payload is controlled by the attacker and causes unintended (and bad) things to happen, such as the user changing their password (to something the attacker might know) or to transfer money to the attacker’s account. The attack works if the user is authenticated only via a cookie or token. The attacker crafts a request with the malicious payload and gets the victim to invoke it, generally by visiting an evil site or clicking on a link or image. The malicious request has the same format as a legal form or API request that the user would normally make. There are many protections a server can, and should, employ to keep its users safe from these attacks. Even with such protections in place, users should be careful what they click on.
  4. NASA’s Power of Ten, from Wikipedia, since the video really only covered nine:
    1. Avoid complex control flow constructs, e.g., do not use goto, setjmp, longjump or recursion.
    2. Give all loops a fixed upper bound.
    3. Do not use dynamic memory allocation after initialization.
    4. No function should be longer than what can be printed on a single sheet of paper in a standard format with one line per statement and one line per declaration.
    5. Use a minimum of two assertions per function.
    6. Declare all data objects at the smallest possible level of scope.
    7. Each calling function must check the return value of nonvoid functions, and each called function must check the validity of all parameters provided by the caller.
    8. The use of the preprocessor must be limited to the inclusion of header files and simple macro definitions.
    9. Limit pointer use to a single dereference and do not use function pointers.
    10. Compile with all possible warnings active and address all warnings before release.
  5. Here’s a JavaScript solution to the Shopping Cart problem. My answer is overkill because I like to use homework answers to introduce new material. Don’t panic when reading these that you were expected to do all these additional security things! Remember, the homework answers are here as “additional course content”—for you!

    cart.js
    // Shopping Cart Module
    
    // A possible solution to the JavaScript shopping cart secure software
    // demonstration homework problem. Only certain requirements surrounding
    // validation, immutability, and defensive copying were given in the
    // assignment, with the suggestion that you look for more opportunities
    // for secure programming on your own. For this homework solution, I've
    // gone somewhat overboard on input validation since homework answers can
    // be an opportunity to introduce and learn new things.
    
    // I've also over-commented the code, because it is provided as a homework
    // solution, and you might find the comments useful.
    
    // -----------------------------------------------------------------------------
    // Context: This module is assumed to exist WITHIN a server application
    // and as such, everything in this module is called by other parts of our
    // server system and *nothing is called directly by an end user*. We may
    // therefore assume that authentication has been previously done so that
    // all fetches from the shopping cart, say, are preformed exclusively by the
    // shopping cart's owner.
    //
    // In practice, subsystems are tied together with services. Our shopping cart
    // makes use of a catalog service. For this simple exercise, we will NOT
    // worry about an inventory service. We will mock the shopping cart service;
    // in a real system, the shopping cart service would be a separate module
    // that can access a real database.
    //
    // We will be concerned with issues about larger system security later in the
    // course (including authentication, databases, and concurrency). For now, we
    // will keep things simple.
    // -----------------------------------------------------------------------------
    
    import { v4 as uuidv4 } from "uuid"
    
    // Many secure practices (especially validation) are aided by keeping checks
    // close to domain objects, so we need more than just a single Shopping Cart
    // class. Validation should happen in constructors and in any method that
    // mutates an object. That said, there are some nice validation utilities
    // that apply to basic objects like numbers and strings, so we'll start with
    // those. Utilities are much better than having to write a while bunch of
    // if statements everywhere.
    
    const validate = Object.freeze({
      that(condition, message) {
        if (!condition) throw new Error(message)
      },
      hasType(value, type) {
        // In JavaScript, determining the "type" of an object can be done in
        // several ways, but the best is to check the object’s constructor
        // property. This works great except for the values null and undefined,
        // but we can make this safe by using the optional chaining operator (?.)
        // which is amazing.
        validate.that(value?.constructor === type, "Type mismatch")
      },
      isString(value, { minLength, maxLength }) {
        // Bounds checking is good
        validate.hasType(value, String)
        validate.that(value.length >= minLength, `Length must be at least ${minLength}`)
        validate.that(value.length <= maxLength, `Length must be at most ${maxLength}`)
      },
      isNumber(value, { minimum, maximum }) {
        // Bounds checking is good
        validate.hasType(value, Number)
        validate.that(value >= minimum, `Must be at least ${minimum}`)
        validate.that(value <= maximum, `Must be at most ${maximum}`)
      },
      isInteger(value, { minimum, maximum }) {
        validate.isNumber(value, { minimum, maximum })
        validate.that(Number.isInteger(value), "Must be an integer")
      },
      matches(value, pattern) {
        validate.hasType(pattern, RegExp)
        validate.that(pattern.test(value), `Must match pattern ${pattern}`)
      },
    })
    
    // Now the first of our domain objects will be for the items that our
    // store offers. Items have a SKU, description, and price. As a best
    // practice, we will make items immutable, which we do by freezing the
    // object upon construction. Note that we do not have to make any of the
    // properties private, since we do want to read them! Note how each of the
    // properties are validated in the constructor before we accept them. We
    // are careful to put bounds on all strings and all numbers.
    
    export class Item {
      constructor(sku, description, price) {
        validate.hasType(sku, SKU)
        validate.isString(description, { minLength: 1, maxLength: 1000 })
        validate.isNumber(price, { minimum: 0.01, maximum: 999999.99 })
        this.sku = sku
        this.description = description
        this.price = price
        Object.freeze(this)
      }
    }
    
    // Now here is the SKU for each item. Note how SKUs have to have a certain
    // format so we explicitly made a SKU class. In other words: SKUs a not
    // strings! While SKUs are not strings, they do "wrap" strings, meaning their
    // internal representation is a string, which we expose through the property
    // called "code". In addition to requiring a certain format, we also want to
    // ensure that SKUs are immutable, so we freeze them in the constructor.
    
    export class SKU {
      static #PATTERN = /^[A-Z]{3}_[A-Z]{3}_\d{2}$/
      constructor(code) {
        validate.matches(code, SKU.#PATTERN)
        this.code = code
        Object.freeze(this)
      }
    }
    
    // The quantity of each item in the cart can not be zero or negative, and
    // it can not be more than some reasonable upper bound. We make a Quantity
    // class because classes are not "just numbers." Also, because immutability
    // is a best practice, we make quantities immutable.
    
    export class Quantity {
      constructor(value) {
        validate.isInteger(value, { minimum: 1, maximum: 500 })
        this.value = value
        Object.freeze(this)
      }
    }
    
    // Now in the specification for this assignment, we are required to
    // implement customer ids to have a certain format. Therefore, customer
    // ids are not strings! They are a special class of their own, with custom
    // validation inside the constructor. We also want customer ids to be
    // immutable, so we freeze them in the constructor.
    
    export class CustomerId {
      // Best to hold validation specifications within the class as private,
      // class-wide properties. This makes it easy to see them as part of the
      // class's specification.
      static #PATTERN = /^\p{L}{3}\d{5}\p{L}{2}-[AQ]$/u
    
      constructor(lexeme) {
        // Here the length validation, while not strictly necessary, is
        // interesting to include for the purposes of showing multi-
        // layered validations. The length check is quick, so do it first.
        validate.isString(lexeme, { minLength: 12, maxLength: 12 })
        validate.matches(lexeme, CustomerId.#PATTERN)
        this.lexeme = lexeme
        Object.freeze(this)
      }
    }
    
    // The catalog is a map of SKUs to items. We will use a map object to
    // store the items, and we will expose two methods: one to fetch an item
    // by its SKU and another to validate that an item exists in the catalog.
    // The catalog is populated with items when the module is loaded. In a
    // real system, the catalog would be in a database, and the catalogService
    // would be in a separate module. But for this exercise, we will just hold
    // the catalog in memory.
    
    const catalogService = (() => {
      const contents = new Map([
        ["ABC_DEF_21", new Item(new SKU("ABC_DEF_21"), "Widget", 10.0)],
        ["ZZZ_BOB_77", new Item(new SKU("ZZZ_BOB_77"), "Thing", 20.0)],
        ["XXX_ROB_77", new Item(new SKU("XXX_ROB_77"), "Cosa", 15.0)],
        ["YYY_JIL_77", new Item(new SKU("YYY_JIL_77"), "Stuff", 200.0)],
        ["WWW_BIL_77", new Item(new SKU("WWW_BIL_77"), "Cool stuff", 0.1)],
      ])
    
      return Object.freeze({
        fetchItem(sku) {
          validate.hasType(sku, SKU)
          return contents.get(sku.code)
        },
    
        validateHasItem(sku) {
          validate.hasType(sku, SKU)
          validate.that(contents.has(sku.code), "Invalid SKU")
        },
      })
    })()
    
    // The actual shopping cart belongs to a single user and contains several items
    // mapping each item to a quantity. Because the cart id and the customer id
    // are immutable, they do not have to be defensively copied and can be directly
    // accessed. The internal mapping (sku->quantity), however, can only be changed
    // carefully through methods, and information about the cart contents is only
    // obtained by making a (defensive) copy of the mapping.
    
    // The cart object is frozen on construction so the owner cannot change and the
    // identity of the map object cannot change. But since maps in JavaScript are
    // mutable, we can still add and remove items.
    
    export class Cart {
      // The card id and the owner's customer id do NOT have to be private
      // because we will freeze the object. However, simply freezing the object
      // does NOT make properties that are objects immutable. Therefore, it is
      // imperative that we make the map private so that we can control access
      // to it. We will make the map immutable by using a getter that returns
      // a defensive copy of the map.
      #items
    
      constructor(customerId) {
        this.id = uuidv4()
        validate.hasType(customerId, CustomerId)
        this.customerId = customerId
        this.#items = new Map()
        Object.freeze(this)
      }
    
      fetchItems() {
        // Defensive (deep!) copy of the map: gotta love structuredClone.
        return structuredClone(this.#items)
      }
    
      addItems(sku, quantity) {
        validate.hasType(sku, SKU)
        validate.hasType(quantity, Quantity)
        catalogService.validateHasItem(sku)
        // At this point we might consider doing an inventory check to make
        // sure there are at least the desired number of units in the inventory.
        // But this is beyond the scope of the current assignment, so we're not
        // going to bother with that here. There are a bunch of additional issues
        // around doing these kinds of checks in a distributed system, anyway,
        // so it is fine to leave it for later.
        this.#items.set(sku.code, (this.#items.get(sku.code) ?? 0) + quantity.value)
      }
    
      removeItem(sku) {
        validate.hasType(sku, SKU)
        this.#items.delete(sku.code)
      }
    
      updateItemQuantity(sku, quantity) {
        validate.hasType(sku, SKU)
        validate.hasType(quantity, Quantity)
        this.#items.set(sku.code, quantity.value)
      }
    
      totalCost() {
        let total = 0
        for (const [sku, quantity] of this.#items.entries()) {
          const item = catalogService.fetchItem(new SKU(sku))
          total += item.price * quantity
        }
        return total
      }
    }
    

    I have some unit tests using the built-in Node test runner (run with node --test test/cart.test.js):

    cart.test.js
    import { describe, it } from "node:test"
    import assert from "assert/strict"
    import { SKU, Item, CustomerId, Quantity, Cart } from "../cart.js"
    
    describe("The Shopping Cart Module", () => {
      it("builds and validates skus", () => {
        assert.throws(() => new SKU(null), "cannot be null")
        assert.throws(() => new SKU(5), "cannot be a number")
        assert.throws(() => new SKU("3333"), "too short")
        assert.throws(() => new SKU("hello".repeat(100)), "too long")
        assert.throws(() => new SKU("ABC-DEF-89"), "malformed")
        const sku = new SKU("ABC_DEF_89")
        assert.deepEqual(sku.code, "ABC_DEF_89", "can read code")
        assert.throws(() => (sku.code = "ABC_DEF_90"), "is immutable")
      })
    
      it("builds and validates quantities", () => {
        assert.throws(() => new Quantity(null), "cannot be null")
        assert.throws(() => new Quantity(true), "cannot be a boolean")
        assert.throws(() => new Quantity("7"), "cannot be a string")
        assert.throws(() => new Quantity(0), "cannot be less than 1")
        assert.throws(() => new Quantity(501), "cannot be greater than 500")
        assert.throws(() => new Quantity(10.0000001), "cannot be non-integer")
        assert.ok(() => new Quantity(1), "can be lowest value")
        assert.ok(() => new Quantity(500), "can be highest value")
        assert.ok(() => new Quantity(99), "can be any value in between")
        const five = new Quantity(5)
        assert.deepEqual(five.value, 5, "value is readable")
        assert.throws(() => (five.value = 3), "is immutable")
      })
    
      it("builds and validates items", () => {
        const sku = new SKU("ABC_DEF_21")
        assert.throws(() => new Item(1, 2, 3), "sku must be SKU")
        assert.throws(() => new Item(sku, "Oreos", 0), "quantity must be Quantity")
        assert.throws(() => new Item(sku, "", 3), "description cannot be empty")
        const item = new Item(new SKU("ABC_DEF_21"), "Turmeric", 3.25)
        assert.deepEqual(item.sku, sku, "can read sku")
        assert.deepEqual(item.description, "Turmeric", "can read description")
        assert.deepEqual(item.price, 3.25, "can read price")
        assert.throws(() => (item.sku = new SKU("ABC_DEF_22")), "is immutable")
      })
    
      it("builds and validates customer ids", () => {
        assert.throws(() => new CustomerId(5), "cannot be a number")
        assert.throws(() => new CustomerId("3333"), "too short")
        assert.throws(() => new CustomerId("hello".repeat(100)), "too long")
        assert.throws(() => new CustomerId("abc7309Zdo-Q"), "malformed")
        const u1 = new CustomerId("abc94788yq-A")
        const u2 = new CustomerId("abc94788yq-A")
        assert.deepEqual(u1, u2, "same id is equal")
        assert.deepEqual(u1.lexeme, "abc94788yq-A", "toString works")
        assert.throws(() => (u1.lexeme = "abc94788yq-B"), "is immutable")
      })
    
      it("validates cart constructor parameters", () => {
        const customerId = new CustomerId("XYZ12345AB-Q")
        assert.throws(() => new Cart(null), "throws on null customerId")
        assert.throws(() => new Cart({}), "throws on non-customerId")
        assert.ok(new Cart(customerId), "a good customerId")
      })
    
      it("can add and fetch items", () => {
        const shopper = new CustomerId("XOX01010XO-A")
        const c = new Cart(shopper)
        const widgetSku = new SKU("ABC_DEF_21")
        const bookSku = new SKU("XXX_ROB_77")
        c.addItems(widgetSku, new Quantity(3))
        c.addItems(bookSku, new Quantity(2))
        const fetchedItems = c.fetchItems()
        const expectedItems = new Map([
          [widgetSku.code, 3],
          [bookSku.code, 2],
        ])
        assert.deepEqual(fetchedItems, expectedItems)
      })
    
      it("properly defensively copies items on fetch", () => {
        const shopper = new CustomerId("XOX01010XO-A")
        const c = new Cart(shopper)
        const widgetSku = new SKU("ABC_DEF_21")
        c.addItems(widgetSku, new Quantity(3))
        const fetchedItems = c.fetchItems()
        const expectedItems = new Map([[widgetSku.code, 3]])
        assert.deepEqual(fetchedItems, expectedItems)
        // Make sure changing the result of fetchItems does not change
        // the cart itself through a back door
        fetchedItems.set(widgetSku.code, 100)
        assert.deepEqual(c.fetchItems(), expectedItems)
      })
    
      it("can remove, update, and total items", () => {
        const shopper = new CustomerId("XOX01010XO-A")
        const c = new Cart(shopper)
        const widgetSku = new SKU("ABC_DEF_21")
        const bookSku = new SKU("XXX_ROB_77")
        c.addItems(widgetSku, new Quantity(3))
        assert.deepEqual(c.totalCost(), 3 * 10.0)
        c.addItems(bookSku, new Quantity(2))
        assert.deepEqual(c.totalCost(), 3 * 10.0 + 2 * 15.0)
        c.removeItem(widgetSku)
        c.updateItemQuantity(bookSku, new Quantity(5))
        assert.deepEqual(c.totalCost(), 5 * 15.0)
      })
    })
    

    SUPER BONUS FUN TIME

    Here’s a Java solution to the Shopping Cart problem. Again, my answer is overkill because I like to use homework answers to introduce new material, and to over-comment to explain how the solutions tie in to the learning objectives.

    Cart.java
    // Shopping Cart Module
    
    // A possible solution to the JavaScript shopping cart secure software
    // demonstration homework problem. Only certain requirements surrounding
    // validation, immutability, and defensive copying were given in the
    // assignment, with the suggestion that you look for more opportunities
    // for secure programming on your own. For this homework solution, I've
    // gone somewhat overboard on input validation since homework answers can
    // be an opportunity to introduce and learn new things.
    
    // I've also over-commented the code, because it is provided as a homework
    // solution, and you might find the comments useful.
    
    // -----------------------------------------------------------------------------
    // Context: This module is assumed to exist WITHIN a server application
    // and as such, everything in this module is called by other parts of our
    // server system and *nothing is called directly by an end user*. We may
    // therefore assume that authentication has been previously done so that
    // all fetches from the shopping cart, say, are preformed exclusively by the
    // shopping cart's owner.
    //
    // In practice, subsystems are tied together with services. Our shopping cart
    // makes use of a catalog service. For this simple exercise, we will NOT
    // worry about an inventory service. We will mock the shopping cart service;
    // in a real system, the shopping cart service would be a separate module
    // that can access a real database.
    //
    // We will be concerned with issues about larger system security later in the
    // course (including authentication, databases, and concurrency). For now, we
    // will keep things simple.
    // -----------------------------------------------------------------------------
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Objects;
    import java.util.UUID;
    
    interface Validate {
        static void ok(boolean condition, String message) {
            if (!condition) {
                throw new IllegalArgumentException(message);
            }
        }
    
        static void notNull(Object value) {
            // This check is built into Java but we are supplying a pass-through
            // method for consistency with the other validation methods.
            Objects.requireNonNull(value, "Must not be null");
        }
    
        static void stringLength(String value, int minLength, int maxLength) {
            ok(value.length() >= minLength, "Length must be at least " + minLength);
            ok(value.length() <= maxLength, "Length must be at most " + maxLength);
        }
    
        static void doubleRange(double value, double minimum, double maximum) {
            ok(value >= minimum, "Must be at least " + minimum);
            ok(value <= maximum, "Must be at most " + maximum);
        }
    
        static void integerRange(int value, int minimum, int maximum) {
            ok(value >= minimum, "Must be at least " + minimum);
            ok(value <= maximum, "Must be at most " + maximum);
        }
    
        static void matches(String value, String pattern) {
            ok(value.matches(pattern), "Must match pattern " + pattern);
        }
    }
    
    // Now the first of our domain objects will be for the items that our
    // store offers. Items have a SKU, description, and price. As a best
    // practice, we will make items immutable, which we do by using a Java
    // record. Note how each of the properties are validated in the 
    // constructor before we accept them. We are careful to put bounds
    // on all strings and all numbers.
    
    record Item(Sku sku, String description, double price) {
        public Item {
            Validate.notNull(sku);
            Validate.notNull(description);
            Validate.stringLength(description, 1, 1000);
            Validate.doubleRange(price, 0.01, 999999.99);
        }
    }
    
    // Now here is the Sku for each item. Note how skus have to have a certain
    // format so we explicitly made a Sku class. In other words: skus a not
    // strings! While skus are not strings, they do "wrap" strings, meaning their
    // internal representation is a string, which we expose through the property
    // called "code". In addition to requiring a certain format, we also want to
    // ensure that skus are immutable, so again we use a record.
    
    record Sku(String code) {
        public Sku {
            Validate.notNull(code);
            Validate.matches(code, "^[A-Z]{3}_[A-Z]{3}_\\d{2}$");
        }
    }
    
    // The quantity of each item in the cart can not be zero or negative, and
    // it can not be more than some reasonable upper bound. We make a Quantity
    // class because classes are not "just numbers." Also, because immutability
    // is a best practice, we make quantities immutable.
    
    record Quantity(int value) {
        public Quantity {
            Validate.integerRange(value, 1, 500);
        }
    }
    
    // Now in the specification for this assignment, we are required to
    // implement customer ids to have a certain format. Therefore, customer
    // ids are not strings! They are a special class of their own, with custom
    // validation inside the constructor. We also want customer ids to be
    // immutable, so again we use a record.
    
    record CustomerId(String lexeme) {
        // Best to hold validation specifications within the class as private,
        // class-wide properties. This makes it easy to see them as part of th
        // class's specification. Here the length validation, while not strictly
        // necessary, is interesting to include for the purposes of showing multi-
        // layered validations.
        private static final int USER_ID_LENGTH = 12;
        private static final String USER_ID_PATTERN = "^\\p{L}{3}\\d{5}\\p{L}{2}-[AQ]$";
    
        public CustomerId {
            Validate.notNull(lexeme);
            // The length check is quick, so done before the regex check
            Validate.stringLength(lexeme, USER_ID_LENGTH, USER_ID_LENGTH);
            Validate.matches(lexeme, USER_ID_PATTERN);
        }
    }
    
    // The catalog is a map of SKUs to items. We will use a map object to
    // store the items, and we will expose two methods: one to fetch an item
    // by its SKU and another to validate that an item exists in the catalog.
    // The catalog is populated with items when the module is loaded. In a
    // real system, the catalog would be in a database, and the catalogService
    // would be in a separate module. But for this exercise, we will just hold
    // the catalog in memory.
    
    class CatalogService {
        private static Map<String, Item> contents = Map.of("ABC_DEF_21",
                new Item(new Sku("ABC_DEF_21"), "Widget", 10.0), "ZZZ_BOB_77",
                new Item(new Sku("ZZZ_BOB_77"), "Thing", 20.0), "XXX_ROB_77",
                new Item(new Sku("XXX_ROB_77"), "Cosa", 15.0), "YYY_JIL_77",
                new Item(new Sku("YYY_JIL_77"), "Stuff", 200.0), "WWW_BIL_77",
                new Item(new Sku("WWW_BIL_77"), "Cool stuff", 0.1));
    
        private CatalogService() {
            // This class is not meant to be instantiated
        }
    
        public static Item fetchItem(Sku sku) {
            return contents.get(sku.code());
        }
    
        public static void validateHasItem(Sku sku) {
            Validate.ok(contents.containsKey(sku.code()), "Invalid SKU");
        }
    }
    
    // The actual shopping cart belongs to a single user and contains several items
    // mapping each item to a quantity. Because the cart id and the customer id
    // are immutable, they do not have to be defensively copied and can be directly
    // accessed. The internal mapping (sku->quantity), however, can only be changed
    // carefully through methods, and information about the cart contents is only
    // obtained by making a (defensive) copy of the mapping.
    
    // The cart object is frozen on construction so the owner cannot change and the
    // identity of the map object cannot change. But since maps in Java are
    // mutable, we can still add and remove items.
    
    class Cart {
        private final UUID id;
        private final CustomerId customerId;
        private final Map<String, Integer> items;
    
        public Cart(CustomerId customerId) {
            this.id = UUID.randomUUID();
            Validate.notNull(customerId);
            this.customerId = customerId;
            this.items = new HashMap<>();
        }
    
        public UUID id() {
            return id;
        }
    
        public CustomerId customerId() {
            return customerId;
        }
    
        public Map<String, Integer> fetchItems() {
            // Java does not have a built-in way to make a deep copy of a map.
            // But in this case, the map only contains immutable values, so we
            // can get away with a shallow copy!
            return Map.copyOf(items);
        }
    
        public void addItems(Sku sku, Quantity quantity) {
            CatalogService.validateHasItem(sku);
            // At this point we might consider doing an inventory check to make
            // sure there are at least the desired number of units in the inventory.
            // But this is beyond the scope of the current assignment, so we're not
            // going to bother with that here. There are a bunch of additional issues
            // around doing these kinds of checks in a distributed system, anyway,
            // so it is fine to leave it for later.
            this.items.put(
                    sku.code(),
                    this.items.getOrDefault(sku.code(), 0) + quantity.value());
        }
    
        public void removeItem(Sku sku) {
            this.items.remove(sku.code());
        }
    
        public void updateItemQuantity(Sku sku, Quantity quantity) {
            this.items.put(sku.code(), quantity.value());
        }
    
        public double totalCost() {
            var total = 0.0;
            for (var e : this.items.entrySet()) {
                total += CatalogService.fetchItem(new Sku(e.getKey())).price()
                        * e.getValue();
            }
            return total;
        }
    }
    

    And a test:

    CartTest.java
    import java.util.Map;
    import org.junit.jupiter.api.Test;
    
    import static org.junit.Assert.assertNotNull;
    import static org.junit.jupiter.api.Assertions.assertEquals;
    import static org.junit.jupiter.api.Assertions.assertThrows;
    
    public class CartTest {
    
        @Test
        public void testBuildsAndValidatesSkus() {
            assertThrows(RuntimeException.class, () -> new Sku(null));
            assertThrows(RuntimeException.class, () -> new Sku("3333"));
            assertThrows(RuntimeException.class, () -> new Sku("hello".repeat(100)));
            assertThrows(RuntimeException.class, () -> new Sku("ABC-DEF-89"));
            var sku = new Sku("ABC_DEF_89");
            assertEquals(sku.code(), "ABC_DEF_89");
            assertEquals(sku.toString(), "Sku[code=ABC_DEF_89]");
        }
    
        @Test
        public void testBuildsAndValidatesQuantities() {
            assertThrows(RuntimeException.class, () -> new Quantity(0));
            assertThrows(RuntimeException.class, () -> new Quantity(501));
            assertThrows(RuntimeException.class, () -> new Quantity(-1));
            assertNotNull(new Quantity(1));
            assertNotNull(new Quantity(500));
            var q1 = new Quantity(5);
            var q2 = new Quantity(5);
            assertEquals(q1.value(), 5);
            assertEquals(q1, q2);
            assertEquals(q1.toString(), "Quantity[value=5]");
        }
    
        @Test
        public void textBuildsAndValidatesItems() {
            assertThrows(RuntimeException.class, () -> new Sku(null));
            var sku = new Sku("ABC_DEF_21");
            assertThrows(RuntimeException.class, () -> new Item(sku, "", 3));
            var item = new Item(new Sku("ABC_DEF_21"), "Turmeric", 3.25);
            assertEquals(item.sku(), sku);
            assertEquals(item.description(), "Turmeric");
            assertEquals(item.price(), 3.25);
        }
    
        @Test
        public void testBuildsAndValidatesCustomerIds() {
            assertThrows(RuntimeException.class, () -> new CustomerId(null));
            assertThrows(RuntimeException.class, () -> new CustomerId("3333"));
            assertThrows(RuntimeException.class, () -> new CustomerId("hello".repeat(100)));
            assertThrows(RuntimeException.class, () -> new CustomerId("abc7309Zdo-Q"));
            var u1 = new CustomerId("abc94788yq-A");
            var u2 = new CustomerId("abc94788yq-A");
            assertEquals(u1, u2);
            assertEquals(u1.lexeme(), "abc94788yq-A");
        }
    
        @Test
        public void testValidatesCartConstructorParameters() {
            assertThrows(RuntimeException.class, () -> new Cart(null));
            var customerId = new CustomerId("abc94788yq-A");
            assertNotNull(new Cart(customerId));
        }
    
        @Test
        public void testCanAddAndFetchItems() {
            var shopper = new CustomerId("XOX01010XO-A");
            var c = new Cart(shopper);
            var widgetSku = new Sku("ABC_DEF_21");
            var cosaSku = new Sku("XXX_ROB_77");
            c.addItems(widgetSku, new Quantity(3));
            c.addItems(cosaSku, new Quantity(2));
            var fetchedItems = c.fetchItems();
            var expectedItems = Map.of(widgetSku.code(), 3, cosaSku.code(), 2);
            assertEquals(fetchedItems, expectedItems);
        }
    
        @Test
        public void testDefensivelyCopiesItemsOnFetch() {
            var shopper = new CustomerId("XOX01010XO-A");
            var c = new Cart(shopper);
            var widgetSku = new Sku("ABC_DEF_21");
            c.addItems(widgetSku, new Quantity(3));
            var fetchedItems = c.fetchItems();
            var expectedItems = Map.of(widgetSku.code(), 3);
            assertEquals(fetchedItems, expectedItems);
            // Make sure changing the result of fetchItems does not change the
            // cart itself through a back door. Java actually throws an exception
            // (UnsupportedOperationException) here because we actually returned
            assertThrows(UnsupportedOperationException.class,
                    () -> fetchedItems.put(widgetSku.code(), 100));
        }
    
        @Test
        public void testCanAddRemoveUpdateAndTotal() {
            var shopper = new CustomerId("XOX01010XO-A");
            var c = new Cart(shopper);
            var widgetSku = new Sku("ABC_DEF_21");
            var cosaSku = new Sku("XXX_ROB_77");
            c.addItems(widgetSku, new Quantity(3));
            assertEquals(c.totalCost(), 3 * 10.00);
            c.addItems(cosaSku, new Quantity(2));
            assertEquals(c.totalCost(), 3 * 10.00 + 2 * 15.00);
            c.removeItem(widgetSku);
            c.updateItemQuantity(cosaSku, new Quantity(5));
            assertEquals(c.totalCost(), 5 * 15.00);
        }
    }