tag:blogger.com,1999:blog-74680084182708452252024-03-05T02:46:46.117-08:00Clinically AwesomeJason Mansfield is a software engineer, security enthusiast, and crazy thinker living in San Diego.Jason Mansfieldhttp://www.blogger.com/profile/01931242304514657402noreply@blogger.comBlogger215125tag:blogger.com,1999:blog-7468008418270845225.post-34141047042909672432021-10-02T17:09:00.000-07:002021-10-02T17:09:16.755-07:00Go: Reference Mutation Testing<p>In the Go community we do a lot of table-driving testing, where we write tests that iterate over a slice or map of test cases and run them through a single test where the inputs vary. These are great because it reduces code (and usually bugs!) and makes it easier to focus on the data.</p><p>I've recently come up with specialization of this that I think helps focus on the exact differences between "good" and "bad" data in test cases. I'm probably not the first person to come up with this and if someone provides me with a link to prior art I'll update the post with the correct terminology and links to sources.</p><p>A common idiom for table-driving testing is that each test case provides a unique input and an expected outcome. For each case the full input needs to be provided. For test inputs with many fields this creates a lot of repetition which makes it more difficult to see what the novel changes are from one test case to the next.</p><p>In this technique rather than providing the full input for each test case, a single "reference" input is created and used repeatedly. The test cases then include a function that takes a pointer of the reference value type and mutates that value in some way. For each test case the the reference value is copied , the operation under test is applied to the copied value, and the outcome is verified. Then, the test case's mutator is called on the copied value. The operation is then repeated and the outcome is verified.</p><p>I call this technique "reference mutation testing". Here's some example code:</p><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><span style="font-family: courier;">package main</span><span style="font-family: courier;"><br /></span><span style="font-family: courier;">import (<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>"errors"<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>"testing"<br /></span><span style="font-family: courier;">)</span><span style="font-family: courier;"><br /></span><span style="font-family: courier;">type Record struct {<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>Label string<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>Value int<br /></span><span style="font-family: courier;">}</span><span style="font-family: courier;"><br /></span><span style="font-family: courier;">func (r Record) Validate() error {<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>if len(r.Label) == 0 {<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>return errors.New("label cannot be empty")<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>}<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>if len(r.Label) > 64 {<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>return errors.New("label must be less than 64 characters")<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>}<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>if r.Value < 0 {<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>return errors.New("value must be positive")<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>}<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>if r.Value > 1023 {<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>return errors.New("value cannot exceed 1023")<br /></span></blockquote><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><div><span style="font-family: courier;"><span style="white-space: pre;"> </span>}<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>return nil</span><br /><span style="font-family: courier;">}<br /></span> <span style="font-family: courier;"><br /></span><span style="font-family: courier;">func TestValidateErrors(t *testing.T) {<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>t.Parallel()<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>goodRecord := Record{<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>Label: "test",<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>Value: 16,</span></div></blockquote><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><div><span style="font-family: courier;"><span style="white-space: pre;"> </span>}<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>for label, tCase := range map[string]struct {<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>mutate func(*Record)<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>expectErr bool<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>}{<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>"empty label": {<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>mutate: func(r *Record) {<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>r.Label = ""<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>},<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>expectErr: true,<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>},<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>"short label": {<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>mutate: func(r *Record) {<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>r.Label = "t"<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>},<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>expectErr: false,<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>},<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>"overlong label": {<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>mutate: func(r *Record) {<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>r.Label = "0123456789abcdef" +<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>"0123456789abcdef" +<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>"0123456789abcdef" +<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>"0123456789abcdef" +<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>"0"<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>},<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>expectErr: true,<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>},<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>"long label": {<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>mutate: func(r *Record) {<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>r.Label = "0123456789abcdef" +<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>"0123456789abcdef" +<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>"0123456789abcdef" +<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>"0123456789abcdef"<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>},<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>expectErr: false,<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>},<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>"overhigh value": {<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>mutate: func(r *Record) {<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>r.Value = 1024<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>},<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>expectErr: true,<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>},<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>"high value": {<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>mutate: func(r *Record) {<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>r.Value = 1023<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>},<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>expectErr: false,<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>},<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>"negative value": {<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>mutate: func(r *Record) {<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>r.Value = -1<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>},<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>expectErr: true,<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>},<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>"0 value": {<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>mutate: func(r *Record) {<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>r.Value = 0<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>},<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>expectErr: false,<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>},<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>} {<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>label, tCase := label, tCase<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>goodRecord := goodRecord<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>t.Run(label, func(t *testing.T) {<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>t.Parallel()</span><span style="font-family: courier;"><br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>if err := goodRecord.Validate(); err != nil {<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>t.Fatalf("unexpected error: %s", err.Error())<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>}</span><span style="font-family: courier;"><br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>tCase.mutate(&goodRecord)<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>err := goodRecord.Validate()<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>if tCase.expectErr && err == nil {<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>t.Fatal("expected error, got none")<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>} else if !tCase.expectErr && err != nil {<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>t.Fatalf("got unexpected error: %s", err)<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>}<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>})<br /></span><span style="font-family: courier;"><span style="white-space: pre;"> </span>}<br /></span><span style="font-family: courier;">}</span></div></blockquote><p style="text-align: left;">Using the mutator function makes it clear what the <i>exact</i> differences are between each test case. Since you're mutating the reference value it's important that you work on a copy. It's tempting to factor testing the reference value out of the test case, but you shouldn't. Even working on a copy of reference value changes can persist; changing the contents of a member slice or map can persist between copies. By testing the reference value each time you're reducing the chances that mutations persisting will go undetected.</p><p style="text-align: left;">This technique isn't ideal for every situation or even every table-driven test but it can be handy where you want to test how behavior changes in response to very specific changes to input.</p>Jason Mansfieldhttp://www.blogger.com/profile/01931242304514657402noreply@blogger.com0tag:blogger.com,1999:blog-7468008418270845225.post-9835994428579019322021-09-21T09:09:00.000-07:002021-09-21T09:09:43.869-07:00Not Quite Zero<p><span style="color: #666666; font-family: Arial; font-size: 15pt; white-space: pre-wrap;">Fact and Fiction In Zero Trust Architecture</span></p><span id="docs-internal-guid-6c846548-7fff-e3f6-50ba-421ed25d745f"><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Zero Trust Architecture (ZTA) is a panacea for organizational security challenges... at least that's what vendors would have you believe. There is so much confusion around what ZTA is and isn't that it's easy to believe that it's all smoke and mirrors; a grift to siphon millions from corporations desperate to shift their liability to someone else.</span></p></span><span><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">ZTA is a real thing but it's not a panacea or product. It's a philosophy for designing interactions between clients and services. Implementing ZTA involves tackling a mountain of individualized complexity. In this post I'll discuss some of this complexity and in doing so I hope to separate the sham from the substance and help you decide whether or not ZTA is something worth pursuing within your organization. Along the way I hope to show that implementing a Zero Trust Architecture is not an advanced technology problem, but one of integrating technologies we're already familiar with.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Much like Kubernetes if you don't know exactly why you need Zero Trust Architecture, you probably don't and the process of trying to implement it will be expensive and painful. You should know what you're getting into.</span></p><h1 dir="ltr" style="line-height: 1.38; margin-bottom: 6pt; margin-top: 20pt;"><span style="font-family: Arial; font-size: 20pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 400; vertical-align: baseline; white-space: pre-wrap;">Why Me?</span></h1><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">I was on the Google security team during the conception and implementation of BeyondCorp: Google's ZTA solution. I developed components related to machine inventory and device access. I know this flavor of Kool-Aid very well and I've seen things that worked very well and things that went poorly.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Everything I'm discussing here is relative to Google's BeyondCorp project. Other ZTA strategies may exist, I'm not an authority on them. Additionally, I left Google in 2016 and I'm confident that their solution has seen significant change since that time. </span><span style="font-family: Arial; font-size: 11pt; white-space: pre-wrap;">That said, I'm sure the concepts are still valuable.</span></p><h1 dir="ltr" style="line-height: 1.38; margin-bottom: 6pt; margin-top: 20pt;"><span style="font-family: Arial; font-size: 20pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 400; vertical-align: baseline; white-space: pre-wrap;">Problem Statement</span></h1><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">It's a common aspect of network architecture that you divide your network into different segments for different purposes. Perhaps you have a common network for clients, a restricted network for internal services, a guest network for untrusted devices, and one or more lab networks for R&D or testing.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">The common client network is intended for corporate devices. Those devices need access to services that shouldn't be exposed to the Internet at large and those services are either on the same network as the clients, directly exposed to those clients, or with minimal segregation and filtering. These are company client devices and are assumed trustworthy.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span style="font-size: 14.6667px;">The internal network may have poorly-protected ingress points such as shared WiFi passwords or network jacks with no access control. Arbitrary </span>devices can access these internal networks, regardless of whether or not they are corporate devices.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Even devices on networks with strong mechanisms to keep out non-corporate devices (e.g. 802.1x) aren't strictly trustworthy. Compromised clients can relay traffic from external attackers onto the internal network.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">In both cases the network has a hard outer shell and a soft interior. That hard shell is easily bypassed by determined attackers, bringing the bad guys onto the good network. Security practitioners have long understood that trusting the internal network is tenuous at best.</span></p><h1 dir="ltr" style="line-height: 1.38; margin-bottom: 6pt; margin-top: 20pt;"><span style="font-family: Arial; font-size: 20pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 400; vertical-align: baseline; white-space: pre-wrap;">What Is Zero Trust Architecture?</span></h1><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Zero Trust Architecture primarily attempts to address the trust misplaced in corporate networks. The crux of ZTA is that client devices are distinctly identified and that access to the service is granted not just based on the access granted to the user but also based on the security posture of the device. </span><span style="font-family: Arial; font-size: 11pt; white-space: pre-wrap;"> In the following sections we discuss different aspects of device authentication and authorization, each progressing from the status quo to the ZTA ideal.</span></p><h2 dir="ltr" style="line-height: 1.38; margin-bottom: 6pt; margin-top: 18pt;"><span style="font-family: Arial; font-size: 16pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 400; vertical-align: baseline; white-space: pre-wrap;">Client Authentication</span></h2><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Distinctly identifying the client device requires each device to have unique credentials that can be used to distinguish between it and others. A simple device secret could suffice, as is the case with classic Kerberos. When these secrets are held in the filesystem, an attacker compromising the device can copy that secret and impersonate the device.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Client x509 certificates are a commonly used mechanism for device authentication with mutual TLS (mTLS). Usually a client certificate's private key is held in the filesystem and can be exported by an attacker like a simple secret. Managing an internal corporate x509 public key infrastructure (PKI) is tricky and requires internal development to manage if it is to operate successfully at a non-trivial scale. There are few standards for device enrollment and little in the way of off-the-shelf solutions.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Ideally, a certificate's private key is held in hardware in a way that it can't be exported, such as in a Trusted Platform Module (TPM). Clients with certificates based on non-exportable keys have the strongest device authentication. Issuing certificates for these devices can be tricky and require custom automation beyond conventional PKI certificate enrollment processes.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Many devices will support mTLS but can't provide certificates based on non-exportable keys. Most commonly this includes user devices such as laptops and desktops with software configurations that support mTLS but lack the crypto hardware for non-exportable keys. These devices can uniquely identify themselves but not in such a way that they can't be impersonated if compromised.</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><br /></span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Finally, there are devices that can't authenticate themselves strongly, like simple IoT devices, or at all, like classic network printers.</span></p><h2 dir="ltr" style="line-height: 1.38; margin-bottom: 6pt; margin-top: 18pt;"><span style="font-family: Arial; font-size: 16pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 400; vertical-align: baseline; white-space: pre-wrap;">Device State Based Access</span></h2><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Services normally don't consider the client device at all when making access decisions. In some rare cases services will attempt to identify the client device as "one of ours" with mTLS. It is almost never the case that access is granted or denied based on how secure the device is.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Corporate IT departments maintain a device inventory. If it's up to date devices can be recognized as corporate assets, assuming they can be identified.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">IT departments often deploy centralized device management solutions. An agent runs on each device and reports the current state of the device to a central system and potentially corrects deviations from the desired configuration. The central management system knows whether or not a device has the properties of a secure configuration such as full disk encryption, up to date patches, client firewall, etc.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Similarly there are often endpoint protection solutions deployed to each device. These attempt to prevent intrusions and will report attempted intrusions and indications of compromise to a central monitoring system.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Ideally, services that make access decisions would identify the client device and consider its current state in granting or denying access to resources. Basic device inventory can distinguish between "friendly" and "unfriendly" devices. The inventory indicates who the device was issued to and access might only be granted to friendly devices where the authenticated user matches the user the device was issued to.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Device management knows if the device has a suitable security configuration and that the configuration has been checked recently. Similarly endpoint security knows with some certainty if the device has been compromised.</span></p><h2 dir="ltr" style="line-height: 1.38; margin-bottom: 6pt; margin-top: 18pt;"><span style="font-family: Arial; font-size: 16pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 400; vertical-align: baseline; white-space: pre-wrap;">Device State Policy</span></h2><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">When we grant users access to protected resources we do so while ensuring that those users are capable of and prepared to protect those resources. We do this through user training and hopefully not putting a user in a role where they can't fulfill their security obligations.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">We don't usually take into account similar considerations for devices. A lot of valuable information about device posture is available but not taken into consideration when making an access decision. Should a device that is known to be compromised be allowed to access any resources that aren't part of the remediation process? Should a device be permitted to download intellectual property if it's not known to be able to protect that data when that device is lost or stolen?</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Devices accessing the most sensitive resources should provide the best available protections. They should have full disk encryption with the company's configuration to reduce the chances that data can be recovered if the device is stolen. This configuration may be distinct to each organization. There may be multiple acceptable configurations in the device fleet simultaneously, especially when users have varied platforms. When a decision is made to grant or deny access to a resource, the policy system should understand what can constitutes appropriate full disk encryption and know how to verify it by checking with configuration management.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">This extends to other security and IT controls like endpoint security, device management, automated backups, etc. These each play a role in protecting data and resources and it makes sense to deny access to devices that can't protect those resources.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">It's not feasible, however, to require the strictest device configuration to access all resources. A service providing software and updates may need to provide packages and configuration required for the device to be in the desired configuration. The device can't attest to its endpoint security configuration if it doesn't have the endpoint security software installed.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">In a Zero Trust Architecture the current state of the accessing device is taken into consideration as much as possible when making access decisions. Which security properties are required and which are nice to have varies from organization to organization and even from resource to resource. Access policies cannot be delivered "canned" or prescribed by an external group and can't be part of a generic solution.</span></p><h2 dir="ltr" style="line-height: 1.38; margin-bottom: 6pt; margin-top: 18pt;"><span style="font-family: Arial; font-size: 16pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 400; vertical-align: baseline; white-space: pre-wrap;">Enforcing Policy</span></h2><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Knowing and codifying device security requirements for resource access is useless if those policies cannot be enforced. While many services can authenticate a client through mTLS, authorization following from that is rare. Services will sometimes have rich languages for doing access control for users but rarely for devices. Having state data to make access decisions on is a complex problem.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Supposing the service has the device policy to enforce, it needs to know the device's state. Aspects of the device's state could be encoded into the certificate it presents. That state is then fixed for the lifetime of the certificate. If the device's state degrades the service won't know either until the certificate expires or it appears in revocation. Short lived certificates add operational overhead and if certificate enrollment requires user interaction it becomes a burden to the user. If the incident response team wants to keep a device from accessing sensitive resources, should they be content waiting for daily cert expiration? Revocation is an alternative but revocation is rarely used and the state of the device likely changes frequently.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Few services have the capability to enforce these policies. The simplest solution is to put services behind a reverse proxy that can enforce policy. This proxy then needs access to device state information. If it's not delivered as part of mTLS it needs to be available in some other way. The state information either needs to be synchronized into the proxy itself or the proxy needs a way to retrieve state information. If the proxy is retrieving the information it either needs to make queries to the sources of truth at request time which has performance cost and reliability risk or it needs to synchronize the data from those sources locally. This has better performance and reliability but will naturally provide stale results some of the time.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Given a standard way to retrieve state data, does each service have its own integration? This can represent large development or operational overhead. Device state can be centrally collected to a service of its own. Policy decisions can then be made using state data managed by a shared resource. This simplifies the individual services but adds a potential point of failure to the serving infrastructure.</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><br /></span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Getting the state information to consult is a challenge on its own. Device management, endpoint security, and related systems often have APIs that automation can interact with but these APIs are usually unique. There are few common APIs or data schemas to allow for interoperability. For each API you wish to retrieve data from you likely need to create a custom integration.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">The proxy solution is viable for services you control. Cloud SaaS services are another matter entirely. An organization can try to put a cloud service behind a reverse proxy but this is frequently brittle and users will often find a way around the proxy which will usually be faster.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">All of this is for HTTP services. It's possible to create a TCP proxy that can enforce device policy but how does it deny a connection? The general solution is to send a RST. For the user, this is indistinguishable from a non-security-related service failure; they can't tell if they've been denied or if the service is broken, leading to support headaches. This TCP proxy could be aware of the protocol and attempt to send a protocol specific message, such as IMAP </span><span style="font-family: courier; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">AUTHORIZATIONFAILED</span><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">. Now the proxy needs to be aware of the protocols it's passing.</span></p><h2 dir="ltr" style="line-height: 1.38; margin-bottom: 6pt; margin-top: 18pt;"><span style="font-family: Arial; font-size: 16pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 400; vertical-align: baseline; white-space: pre-wrap;">Solutions</span></h2><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Implementing a real Zero Trust Architecture presents a lot of problems. The solution for Google is straightforward: leverage an army of world-class software engineers. Google created most of the technology it uses and has the resources to adapt them to new standards. As a web technology company it's not crazy for them to make every service a web service and put it behind a web proxy. They use very little vendor software and have little trouble building integrations between those systems that generate state data, those that house the state data, and those that need the state data.</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><br /></span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Chances are, you're not Google or a company with similar resources. </span><span style="font-family: Arial; font-size: 11pt; white-space: pre-wrap;">Every organization has their own security infrastructure and device configurations. Taking all of those unique properties into account requires custom integrations. The whole point of Zero Trust Architecture is to make security decisions based on how the organization's devices should be configured and how they should behave. That information is available in the disparate device management solutions but the systems that should be making use of it don't have access to it.</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; white-space: pre-wrap;"><br /></span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; white-space: pre-wrap;">There aren't off the shelf solutions to pull this together so if you want it you have to build it. This was a significant undertaking even for the might of Google, but Google has orders of magnitude more resources, infrastructure, and sources of data than most companies. While you may not have the resources to throw at the problem, you also don't have the same scale of problems.</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; white-space: pre-wrap;"><br /></span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; white-space: pre-wrap;">The goal of Zero Trust Architecture is to use all available data when making an access decision. The challenge is that this data isn't very available. For Zero Trust Architecture to be realistic for all but the most well-heeled organizations there needs to be new standards, particularly around interoperability. Systems collecting device state data must make that data available to automation in a common way to centralized collection systems such that integration is a matter of configuration rather than bespoke software development. Similarly, we need standards for retrieving that data by systems that have to make access decisions.</span></p><h1 dir="ltr" style="line-height: 1.38; margin-bottom: 6pt; margin-top: 20pt;"><span style="font-family: Arial; font-size: 20pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 400; vertical-align: baseline; white-space: pre-wrap;">Misconceptions</span></h1><h2 dir="ltr" style="line-height: 1.38; margin-bottom: 6pt; margin-top: 18pt;"><span style="font-family: Arial; font-size: 16pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 400; vertical-align: baseline; white-space: pre-wrap;">It Means Don't Trust Anything</span></h2><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Zero Trust is a terrible name; Zero Faith would be more accurate. It's really about being very explicit about what does and doesn't make a device trustworthy for a certain action.</span></p><h2 dir="ltr" style="line-height: 1.38; margin-bottom: 6pt; margin-top: 18pt;"><span style="font-family: Arial; font-size: 16pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 400; vertical-align: baseline; white-space: pre-wrap;">It Means All Devices Use mTLS</span></h2><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Yes and no. Client certificates are much stronger device authentication than IP addresses, for sure. In the simple case a client certificate and private key are simply files that can be exported from the device. An attacker with these files can then impersonate a compromised device from other devices they control. Ideally, mTLS is rooted in TPM credentials which cannot be exported from the device.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">A compromised device with non-exportable credentials can perform proper authentication under an attacker's control. Strong client authentication only establishes the device's identity, not that the device is trustworthy.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Some devices simply can't do mTLS on their own. A multifunction copier is never going to be subject to strong authentication or state attestation. For those devices, the services they need to access either need to have more lenient security requirements or something else must act on the device's behalf.</span></p><h2 dir="ltr" style="line-height: 1.38; margin-bottom: 6pt; margin-top: 18pt;"><span style="font-family: Arial; font-size: 16pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 400; vertical-align: baseline; white-space: pre-wrap;">It Means Get Rid of Your Firewalls</span></h2><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">We use firewalls to segregate "our network" from "not our network"; while not strictly safe the former is safer than the latter. If we don't trust the internal network there's no point in segregating it.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Having devices on an internal network doesn't make them "good", but it can make them "not obviously bad". Your firewall can keep out obviously bad stuff. If you don't run your own mail services, is there any reason to permit mail traffic onto your network? If you never expect to receive traffic from some part of the world, maybe you can afford to preemptively filter it out. Your coarse filtering devices help ensure that your network resources are devoted to your needs.</span></p><h2 dir="ltr" style="line-height: 1.38; margin-bottom: 6pt; margin-top: 18pt;"><span style="font-family: Arial; font-size: 16pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 400; vertical-align: baseline; white-space: pre-wrap;">It Means Get Rid of Your VPN</span></h2><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">VPNs are a useful tool. They also allow your users to extend the corporate network into arbitrary other networks. When services can make use of strong device authentication it becomes more reasonable to make them Internet-facing. This provides convenience for the users because it can reduce the time users need to be on the VPN. For some services it will be infeasible for the organization to restrict access with strong device authentication. For these services a VPN might be the right solution.</span></p><h2 dir="ltr" style="line-height: 1.38; margin-bottom: 6pt; margin-top: 18pt;"><span style="font-family: Arial; font-size: 16pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 400; vertical-align: baseline; white-space: pre-wrap;">It's a Product</span></h2><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">There's no shortage of vendors who will sell you a Zero Trust Architecture solution. At best they are taking creative liberties with the term. At worst they know they're selling snake oil. In between are a lot of products and services that represent a partial solution.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">No vendor is offering a solution that works with all of your services and understands your all your client devices. Perhaps it's a viable offering if they provide 100% outsourced IT. Then the vendor controls all the clients and all the services and can ensure that all aspects of IT infrastructure have Zero Trust solutions.</span></p><h1 style="line-height: 1.38; margin-bottom: 6pt; margin-top: 18pt; text-align: left;"><span style="font-family: Arial; font-size: 16pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 400; vertical-align: baseline; white-space: pre-wrap;">Summary</span></h1><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"></span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial;"><span style="font-size: 14.6667px; white-space: pre-wrap;">Zero Trust Architecture is the future of network security. As is often the case the future has not been evenly distributed. If we want this capability to be commonly available we must put pressure on vendors to ensure simple, robust, efficient APIs are available for retrieving data about our devices from their products. For services, we need them to have mechanisms to make use of the data being collected. When vendors are competing for our contracts we can choose interoperability as a key deciding factor.</span></span></p></span>Jason Mansfieldhttp://www.blogger.com/profile/01931242304514657402noreply@blogger.com0tag:blogger.com,1999:blog-7468008418270845225.post-2716074112809513362020-08-11T15:56:00.003-07:002020-08-11T16:01:19.150-07:00Go: Convert errors.Wrap calls to fmt.Errorf<p> I was a longtime fan of <a href="https://github.com/pkg/errors" target="_blank">https://github.com/pkg/errors</a>. It was a great way to add context to why an error was being returned which made tracing them easier. The need for <span style="font-family: courier;">pkg/errors</span> has gone away with the new <span style="font-family: courier;">fmt.Errorf %w</span> directive, <span style="font-family: courier;">errors.Is()</span>, and <span style="font-family: courier;">errors.As()</span>.</p><p>I used <span style="font-family: courier;">errors.Wrap()</span> <i>a lot</i> so naturally my code has lots of function calls I need to migrate. One repo had close to 300 calls to <span style="font-family: courier;">errors.Wrap()</span> which is more than I'm willing to do by hand. I wrote a simple tool to take care of the most common case I have: <span style="font-family: courier;">errors.Wrap(err, "<message>")</span>.</p><p></p><ul style="text-align: left;"><li>Any line it doesn't know how to handle it leaves unchanged.</li><li>It looks specifically for <span style="font-family: courier;">errors.Wrap(err, "</span></li><li><span style="font-family: inherit;">On that same line it expects to find a double quote followed by a closing paren</span></li><li><span style="font-family: inherit;">The existing context string has </span><span style="font-family: courier;">: %w</span><span style="font-family: inherit;"> appended to it</span></li><li><span style="font-family: inherit;">It does <i>not</i> edit your imports; you should run </span><span style="font-family: courier;">goimports</span><span style="font-family: inherit;"> or a similar tool</span></li><li><span style="font-family: inherit;">By default the tool just outputs to </span><span style="font-family: courier;">stdout</span><span style="font-family: inherit;">; use </span><span style="font-family: courier;">-o</span><span style="font-family: inherit;"> to overwrite the file in-place</span></li><li>Fix everything by doing <span style="font-family: courier;">for i in $(grep -R errors.Wrap `ls`); do errors_wrap_convert -in $i -o; end</span></li><li><span style="font-family: inherit;">Definitely make sure you have a snapshot of your code to revert back to in case this tool does bad things</span></li><li><span style="font-family: inherit;">This could have been done better using </span><span style="font-family: courier;">gofix</span><span style="font-family: inherit;"> but I was in too much of a hurry to learn how to extend </span><span style="font-family: courier;">gofix</span><span style="font-family: inherit;">.</span></li></ul>
<code style="font-family: courier;">
<div># errors_wrap_convert.go</div><div><div>package main</div><div><br /></div><div>import (</div><div> "bufio"</div><div> "bytes"</div><div> "flag"</div><div> "fmt"</div><div> "io"</div><div> "io/ioutil"</div><div> "log"</div><div> "os"</div><div> "strings"</div><div>)</div><div><br /></div><div>var (</div><div> fIn = flag.String("in", "", "input file")</div><div> fOverwrite = flag.Bool("o", false, "overwrite the existing file")</div><div>)</div><div><br /></div><div>func fatalIfError(err error, msg string) {</div><div> if err != nil {</div><div> log.Fatal("error ", msg, ": ", err)</div><div> }</div><div>}</div><div><br /></div><div>func main() {</div><div> flag.Parse()</div><div> b, err := ioutil.ReadFile(*fIn)</div><div> fatalIfError(err, "reading input file")</div><div><br /></div><div> var out io.WriteCloser = os.Stdout</div><div> if *fOverwrite {</div><div> out, err = os.Create(*fIn)</div><div> fatalIfError(err, "opening output file")</div><div> }</div><div> defer out.Close()</div><div><br /></div><div> scanner := bufio.NewScanner(bytes.NewBuffer(b))</div><div> for scanner.Scan() {</div><div> fmt.Fprintln(out, Rewrite(scanner.Text()))</div><div> }</div><div> fatalIfError(scanner.Err(), "scanner error")</div><div>}</div><div><br /></div><div><br /></div><div>func Rewrite(in string) string {</div><div> idx := strings.Index(in, `errors.Wrap(err, "`)</div><div> if idx == -1 {</div><div> return in</div><div> }</div><div><br /></div><div> eIdx := strings.Index(in[idx:], ")")</div><div> if eIdx == -1 {</div><div> return in</div><div> }</div><div> eIdx += idx</div><div><br /></div><div> q1Idx := strings.Index(in[idx:], `"`)</div><div> if q1Idx == -1 {</div><div> return in</div><div> }</div><div> q1Idx += idx</div><div><br /></div><div> q2Idx := eIdx - 1</div><div> if in[q2Idx] != '"' {</div><div> return in</div><div> }</div><div><br /></div><div> out := in[:idx] +</div><div> `fmt.Errorf(` +</div><div> in[q1Idx:q2Idx] +</div><div> `: %w", err)` +</div><div> in[eIdx+1:]</div><div> return out</div><div>}</div></div><div><br /></div></code>
<div>And a couple of basic tests:</div><div><br /></div>
<code style="font-family: courier;">
<div><div># errors_convert_test.go</div><div>package main</div><div><br /></div><div>import "testing"</div><div><br /></div><div>func TestRewrite(t *testing.T) {</div><div> t.Parallel()</div><div> for in, want := range map[string]string{</div><div> "": "",</div><div> ` return nil, errors.Wrap(err, "bad thing") // foo bar`: ` return nil, fmt.Errorf("bad thing: %w", err) // foo bar`,</div><div> `return nil, errors.Wrap(err, "foo " + blarg + " bar")`: `return nil, fmt.Errorf("foo " + blarg + " bar: %w", err)`,</div><div> } {</div><div> got := Rewrite(in)</div><div> if got != want {</div><div> t.Fatalf("got %q, want %q, for %q", got, want, in)</div><div> }</div><div> }</div><div>}</div></div><div><br /></div><div>I had searched for a tool to do this but it either doesn't exist or my searching ability failed me. If you would like to pick this up and generalize I'd happily refer to your version as canonical.</div></code>Jason Mansfieldhttp://www.blogger.com/profile/01931242304514657402noreply@blogger.com0tag:blogger.com,1999:blog-7468008418270845225.post-57916069329284461302020-08-02T17:29:00.003-07:002020-08-06T21:59:54.064-07:00The Autobucket Saga<h1 style="text-align: left;">The Leaking A/C and Early Failure</h1><div style="text-align: justify;">A few years ago our air conditioning started leaking. We discovered this when a stream of water started running from the corner of our kitchen's ballast lighting. Naturally we were alarmed. We found the location of the leak and got a plastic tub underneath it to catch the water. Until we could get a technician to the house we got to choose why we weren't sleeping well; either because it was too hot with the A/C off or every couple of hours one of us had to empty the tub with a wet vac.</div><div style="text-align: justify;"><br /></div><div style="text-align: justify;">This problem annoyed me. I'm a smart, technical guy, I should be able to solve this. I had taught myself some electronics and should be able to programmatically control a pump. I bought a little 5v USB pump and some float switches. I hooked it all up to a raspberry pi with the pump's power being controlled by the pi via an NPN transistor. Pump turns on when the high-water mark switch closes. Pump turns off when the low-water mark switch opens. Super simple, and for the life of me I couldn't get it to actually work.</div><h1 style="text-align: left;">The Student Elevates Himself</h1><div style="text-align: justify;">Since then I've learned a lot more about electronics, though I'm still a newbie. This year at <a href="https://www.bsidessd.org/" target="_blank">BSides San Diego</a> I bought an arduino-compatible microcontroller board and some other components as a way to help fund the event. Since I bought them I had to experiment with them!</div><div style="text-align: justify;"><br /></div><div style="text-align: justify;">The basic stuff is pretty easy! In the course of fiddling and experimenting I realized the problem with my original setup; with neither the pump control transistor nor the float switches was I tying them to a ground reference (via resistor, of course).</div><div style="text-align: justify;"><br /></div><div style="text-align: justify;">I spoke about my new understanding and new confidence to my <a href="https://www.sassierglasses.com/" target="_blank">loving partner</a>. She noted how the condensation from the A/C just gets pumped out down the side of the house. We catch it with a bucket but rarely think to dump that water on our orange trees. It should would be nice to have the water moved over there automatically! I was inspired.</div><h1 style="text-align: left;">The Autobucket Is Born</h1><div>This came together on a breadboard pretty quickly.</div><div><br /></div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsPWp_bWWwH_lIXsHCa2udYGavPF_d1pGkmFAS6Ulv1McldC1dXL_jTlMpRwGNzluDb-K1fq_iRWxczQ2pw5WBdCpohIswMUcRECZU1ScGIFy5SbBOh1qUYtta-_M4cNuZ4qg-lSyVCG8/s4032/DSC_0052.JPG" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="3024" data-original-width="4032" height="384" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsPWp_bWWwH_lIXsHCa2udYGavPF_d1pGkmFAS6Ulv1McldC1dXL_jTlMpRwGNzluDb-K1fq_iRWxczQ2pw5WBdCpohIswMUcRECZU1ScGIFy5SbBOh1qUYtta-_M4cNuZ4qg-lSyVCG8/w512-h384/DSC_0052.JPG" width="512" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Random USB wires to your gaming laptop is fine, right?<br /></td></tr></tbody></table><div><br /></div><div style="text-align: justify;">Each float switch goes to a GPIO pin on a raspi zero w with the other side having a 10k resistor to ground and a 1k resistor to the pi's 3v3. I elected to have discrete pulldown resistors rather than integrated pulldown/pullup purely because I understand it better. The pump was directly connected to USB 5v with ground going to the NPN's collector. The base has 10k to ground and 1k to a GPIO pin. I wrote all the software in <a href="https://golang.org/" target="_blank">go</a> with <a href="https://gobot.io/documentation/platforms/raspi/" target="_blank">gobot</a> including a feature to notify me via Telegram as the pump cycles on and off.</div><div style="text-align: justify;"><br /></div><div style="text-align: justify;">Amazingly, it just worked! On my bench I could move the float switches and see the program change state. Rather than powering on a dry pump I plugged in a device that charges via USB with a charging indicator LED. In the appropriate states it would turn on and off. Awesome!</div><div style="text-align: justify;"><br /></div><div style="text-align: justify;">Next up, the bucket. We have a bunch of orange buckets so hacking one up wasn't an issue:</div><div><br /></div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlrzDsbuIaxLC1gpvnEH1gGWswA6tBc9A1jQPA7wKDkcWtBp2g-nHpV42Tf5i54msKRTdzKKzm_HFk4MlqWigm-PwMCliGlgOqsLCjWLpzH1HYO1f0lLrPfwCMc18obpdCA7IAxyVW-ok/s4032/DSC_0053.JPG" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="3024" data-original-width="4032" height="384" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlrzDsbuIaxLC1gpvnEH1gGWswA6tBc9A1jQPA7wKDkcWtBp2g-nHpV42Tf5i54msKRTdzKKzm_HFk4MlqWigm-PwMCliGlgOqsLCjWLpzH1HYO1f0lLrPfwCMc18obpdCA7IAxyVW-ok/w512-h384/DSC_0053.JPG" width="512" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Water, wires: besties!<br /></td></tr></tbody></table><div><br /></div><div style="text-align: justify;">I installed a couple of holes at different heights and installed the float switches. With their rubber gaskets it didn't even leak! I dropped the pump in there and for the time being just accepted that it didn't sit fully at the bottom but sufficiently below the low-water mark.</div><div style="text-align: justify;"><br /></div><div style="text-align: justify;">I didn't want the control system exposed to rain and sunlight and I needed it to be near power. I have a covered patio nearby with AC outlets, I just needed to establish connectivity between the two points. I needed 5 wires: USB+, USB ground, 3v3+, float switch 1, float switch 2. I have a supply of CAT-5 which is great since it even provides easy to differentiate individual wires. I wanted to be able to disconnect it so two wires went to a female USB connector and three into a molex hard drive power connector from my parts box. With this in place I could connect and disconnect as needed. Once things were settled I could shrink-wrap the connections for some weather protection.</div><div><br /></div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIOhKAMddvcoGOVKBMY7PNoM3dp-tPsb8ANIPnWYRw0NE5ouOzGH3XZOg7gSL3UQ6BoGEzWCj-wEOm8CJhHeq-CKnbncNKKuxsQP6e5XjugmjL9DAnvPHb0D_Z7xK7q6rSgMRjSnw906c/s4032/DSC_0054.JPG" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="4032" data-original-width="3024" height="512" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIOhKAMddvcoGOVKBMY7PNoM3dp-tPsb8ANIPnWYRw0NE5ouOzGH3XZOg7gSL3UQ6BoGEzWCj-wEOm8CJhHeq-CKnbncNKKuxsQP6e5XjugmjL9DAnvPHb0D_Z7xK7q6rSgMRjSnw906c/w384-h512/DSC_0054.JPG" width="384" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">I'm playing outside!<br /></td></tr></tbody></table><div><br /></div><div style="text-align: justify;">Testing again with the circuit on the board and again, success! You may note in this photo that power is supplied by exposed USB wires and gator clips. This is fine for a day of testing but not a workable long-term solution.</div><div style="text-align: justify;"><br /></div><div style="text-align: justify;">I'd like a neater board but don't keep perfboard handy. I do, however, have a 3D printer, a decent ability with CAD, and general lack of good judgement.</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEheyi2fNxl-JB9LKeYbd5aJ7G2WT39Hm2YedX4SryaT2UIVyLPpCV_4466Xic4l3b4sIkLYOBfgc3E4r_SpCAs0kGv4KpFLEtCWBWkxEmMxQqyiS0-mgDX-VO5Ss-1Zitk0UQW2FTeQPUY/s4032/DSC_0067.JPG" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="3024" data-original-width="4032" height="384" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEheyi2fNxl-JB9LKeYbd5aJ7G2WT39Hm2YedX4SryaT2UIVyLPpCV_4466Xic4l3b4sIkLYOBfgc3E4r_SpCAs0kGv4KpFLEtCWBWkxEmMxQqyiS0-mgDX-VO5Ss-1Zitk0UQW2FTeQPUY/w512-h384/DSC_0067.JPG" width="512" /></a></div><br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEib27PsLh-lCkv10OTGr2dJ-CaNaxIiCsyBfDR49uZt44crNrdK1mZ1Ke3ZKkD28OMvd1EnTeokcnyfKWhU7fLai2lRQSahShrO-u2d7zCjm1fdpvsjhZDCOTP5WeoHMq0AKMxM9dHD8HQ/s4032/DSC_0066.JPG" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="3024" data-original-width="4032" height="384" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEib27PsLh-lCkv10OTGr2dJ-CaNaxIiCsyBfDR49uZt44crNrdK1mZ1Ke3ZKkD28OMvd1EnTeokcnyfKWhU7fLai2lRQSahShrO-u2d7zCjm1fdpvsjhZDCOTP5WeoHMq0AKMxM9dHD8HQ/w512-h384/DSC_0066.JPG" width="512" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Sorry about your gag reflex.<br /></td></tr></tbody></table><div><br /></div><div style="text-align: justify;">Since I'm already using CAT-5 to connect to the bucket, why not RJ45? I had some RJ45 keystone jacks so I super glued a couple to my board. One was intended to connect to the bucket, the other to go to the raspi. Instead I ended up connecting to the raspi via a female header connector snipped in half then glued together so that I could easily connect and disconnect to GPIO. While I was at it, I hooked a BME280 sensor via I2C so I'd have an outdoor temperature/pressure/humidity sensor that I can expose as a webserver.</div><div style="text-align: justify;"><br /></div><div style="text-align: justify;">For power I grabbed a phone cable I had with only two wires. Part of the way through the conversion I had something that at least made me chuckle:</div><div><br /></div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPMOWURpq7rj_eJIxrUasfXtlE75XPVCKABkrMN9M00piyyZ5gVIwgVM6WhfUfMZVZe73YOcDISw8ihQwlOu7vAu08eolIum9FeSYuhPTB_2d7mW949kcrSDoHfLVG4ySAu3SauuNf6nc/s4032/DSC_0065.JPG" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="4032" data-original-width="3024" height="512" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPMOWURpq7rj_eJIxrUasfXtlE75XPVCKABkrMN9M00piyyZ5gVIwgVM6WhfUfMZVZe73YOcDISw8ihQwlOu7vAu08eolIum9FeSYuhPTB_2d7mW949kcrSDoHfLVG4ySAu3SauuNf6nc/w384-h512/DSC_0065.JPG" width="384" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Windows is configuring your new magic smoke</td></tr></tbody></table><div><br /><div style="text-align: justify;">This will need an enclosure but for the next step I started with a semi-disposable plastic storage container.</div></div><div><br /></div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpfUFZMKCx6rORTzWXnMq317bON7GoB8Yb6Zy8yD_U5rzBusjkid4knv3e3SmxIvQqvRHfdQvpzjWnMEaxxiYSZOW8JAgI0n-7hQWH40ZlP45SsTVkV_ja_F0QFl1BXiHl-orNwPiOix0/s4032/DSC_0068.JPG" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="3024" data-original-width="4032" height="384" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpfUFZMKCx6rORTzWXnMq317bON7GoB8Yb6Zy8yD_U5rzBusjkid4knv3e3SmxIvQqvRHfdQvpzjWnMEaxxiYSZOW8JAgI0n-7hQWH40ZlP45SsTVkV_ja_F0QFl1BXiHl-orNwPiOix0/w512-h384/DSC_0068.JPG" width="512" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Pioneering Avant Garde project enclosures<br /></td></tr></tbody></table><div><br /></div><div style="text-align: justify;">And much to my surprise, it's <i>still working</i> at this stage. Before I go for a more permanent enclosure I want to let it run for a few days and make sure it doesn't need any changes.</div><h1 style="text-align: left;">Trouble In the Garden of E-Dumb</h1><div style="text-align: justify;">It does work, mostly. The pump is tiny and weak which is to be expected. I don't really care how <i>fast</i> the water makes its way to the orange trees, just that it gets there. Eventually though the pump is being started but not shut off. The pump is on, no water is flowing. It has to push the water through 1/4" inner diameter vinyl tubing up the height of the bucket, then a few yards over to the tree. I fluff the tubing and the water starts flowing. My hope at this point is that I'd only have to prime it after it's been idle for a while. The next step up in pump power is 12v and I'm reluctant to go there.</div><div style="text-align: justify;"><br /></div><div style="text-align: justify;">This problem persists. If I prime it, it gets going otherwise not much is happening. At first I thought maybe the primary purpose the pump is serving here is to get the initial water over the bucket height and then siphoning is taking care of the rest. It's not reliably doing even that though.</div><div style="text-align: justify;"><br /></div><div style="text-align: justify;">It eventually occurs to me that I can help the siphoning action by elevating the bucket. The path along the ground from the bucket to the tree is all flat. If the water source is elevated higher than the destination the siphoning should be more effective. I'm about to arrive...</div><h1 style="text-align: left;">The Autobucket: Passive Edition</h1><div style="text-align: justify;">I eventually realize I'm being pretty stupid. I've realized that gravity is doing most of the work here. I can ensure that the water reservoir is higher than the outlet.</div><div style="text-align: justify;"><br /></div><div style="text-align: justify;">I don't need the pump, the sensors, or the control at all. I need a hole near the bottom of the bucket and to seal the tube in that hole. Gravity will cause the water to drain through the tube. I had fixated on a solution to the complete neglect of the objective.</div><div style="text-align: justify;"><br /></div><div style="text-align: justify;">In summary:</div><div style="text-align: justify;"><ul><li><i>What I built</i>: A network-connected gray water reclamation and irrigation system</li><li><i>What I needed</i>: A bucket with a hose glued into it</li></ul></div><div><br /></div><div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjS2CDHLo5z3atEZ8MGFjY7LyJD6GsQy54K_GnK6997AYKaOYFW42MwipIuiovCc6fxEMiZqf8GnRLIwoHWv6oSqhmg6URsLxaeEho0m_VXJ-JsqY84wi954c335SPwwhajyqUCFomc6b0/s4032/DSC_0074.JPG" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="3024" data-original-width="4032" height="384" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjS2CDHLo5z3atEZ8MGFjY7LyJD6GsQy54K_GnK6997AYKaOYFW42MwipIuiovCc6fxEMiZqf8GnRLIwoHWv6oSqhmg6URsLxaeEho0m_VXJ-JsqY84wi954c335SPwwhajyqUCFomc6b0/w512-h384/DSC_0074.JPG" width="512" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Sometimes the dumb solution is the right solution</td></tr></tbody></table></div><h1 style="text-align: left;">Reflections</h1><div style="text-align: justify;">I basically took three lefts instead of a right but the journey wasn't all for naught. I got validation that I had overcome my gaps in electronics knowledge since the leaky air conditioner.</div><div style="text-align: justify;"><br /></div><div style="text-align: justify;">One thing that went very well was my process. In past projects I've had frustrating failures by pushing through to a complete solution. When something didn't work I had gone so far that troubleshooting meant tearing down and starting over. This time I worked much more incrementally, validating my progress at each stage and having the chance to make corrections. While I rarely needed corrections along the way, the anxiety that I might have screwed something up and wasted all my time was minimal.</div><div style="text-align: justify;"><br /></div><div style="text-align: justify;">I diagrammed lots of things. I put things together on the breadboard to make it work then translated that to a circuit diagram that I could follow more easily. When wiring up the CAT-5 I wrote down which colors would do what before I made any connections. From then on I could easily know which was the correct wire. I could then maintain that color scheme for portions downstream from the cable to keep it consistent and easier to wrap my head around. I haven't built up my electronics chops yet to keep what each wire does in my head.</div><div style="text-align: justify;"><br /></div><div style="text-align: justify;">Wire type really matters. I've often used internal CAT-5 strands as plentiful solid-core wire with color-coding. When I put that on the female pin header it didn't flex for anything and was very hard to work with. I had some stranded but only three colors, which complicated things. I ordered an assorted color stranded wire kit and for subsequent projects and it's been great.</div><div style="text-align: justify;"><br /></div><div style="text-align: justify;">The weather sensor was a great addition. I loved being able to check it from my phone. I ended up scrapping the raspi setup and setting up just weather sensors with the BME280, an Adafruit ESP8266 board, and some software that made the data available in Prometheus format.</div><div><br /></div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjl_tWxnoFMJiYrinwiAORHti05UwOyYP211T8Pi0UejyLO1MUyzW_M510nqkrS1H906sgGFFGx2lZ4hOlYq2gzMAjk0jIdWKxLKqAnC4U0tmC1Kv6_gEQOOLl48XKZ7xIFoLU3b2poH20/s4032/DSC_0073.JPG" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="3024" data-original-width="4032" height="384" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjl_tWxnoFMJiYrinwiAORHti05UwOyYP211T8Pi0UejyLO1MUyzW_M510nqkrS1H906sgGFFGx2lZ4hOlYq2gzMAjk0jIdWKxLKqAnC4U0tmC1Kv6_gEQOOLl48XKZ7xIFoLU3b2poH20/w512-h384/DSC_0073.JPG" width="512" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Chibi Weather Station<br /></td></tr></tbody></table><div><br /></div><div style="text-align: justify;">Ultimately we don't learn a whole lot from our successes; we learn much more reflecting on failure. Maybe my missteps can be useful to you.</div>Jason Mansfieldhttp://www.blogger.com/profile/01931242304514657402noreply@blogger.com0tag:blogger.com,1999:blog-7468008418270845225.post-2721583101859529982020-02-27T13:08:00.000-08:002020-02-27T13:08:26.352-08:00Priority Channel in GoI'm kind of impressed with this ugly monster:<br />
<br />
<pre>package main
import (
"context"
"fmt"
"sync"
"time"
)
func main() {
const levels = 3
const sourceDepth = 5
sources := make([]chan int, levels)
for i := 0; i < levels; i++ {
sources[i] = make(chan int, sourceDepth)
}
out := make(chan int)
ctx, cancel := context.WithCancel(context.Background())
wg := &sync.WaitGroup{}
pc := New(ctx, sources, 10, out)
wg.Add(1)
go func() {
defer wg.Done()
defer close(out)
pc.Prioritize()
}()
wg.Add(1)
go func() {
defer wg.Done()
for i := range out {
fmt.Println("i: ", i)
time.Sleep(time.Second / 4)
}
}()
for _, i := range []int{0, 2, 1, 0, 2, 1, 0, 2, 1} {
fmt.Println("submitting ", i)
pc.Submit(i, i)
}
time.Sleep(time.Second * 3)
cancel()
wg.Wait()
}
type PriorityChannel struct {
notify chan struct{}
sources []chan int
out chan int
ctx context.Context
}
func New(ctx context.Context, sources []chan int, cap int, out chan int) PriorityChannel {
pc := PriorityChannel{
notify: make(chan struct{}, cap),
sources: sources,
out: out,
ctx: ctx,
}
for i := 0; i < cap; i++ {
pc.notify <- struct{}{}
}
return pc
}
func (pc PriorityChannel) Prioritize() {
for {
// block until there's a value
select {
case pc.notify <- struct{}{}:
// proceed
case <-pc.ctx.Done():
return
}
SOURCES:
for _, rcv := range pc.sources {
select {
case i := <-rcv:
pc.out <- i
break SOURCES
default:
// keep looping
}
}
}
}
func (pc PriorityChannel) Submit(i, priority int) {
if priority < 0 || priority >= len(pc.sources) {
panic("invalid priority")
}
pc.sources[priority] <- i
<-pc.notify
}
</pre>Jason Mansfieldhttp://www.blogger.com/profile/01931242304514657402noreply@blogger.com0tag:blogger.com,1999:blog-7468008418270845225.post-84232267540381127632020-01-13T00:50:00.001-08:002020-01-13T00:50:09.950-08:00The Tool Concert: A SynopsisDrummer: <Bonk uh dunk<div>Bonk uh dunk</div><div>Bonk uh dunk tsh</div><div>Bonk uh dunk</div><div>Bonk uh dunk</div><div>Bonk uh dunk tsh></div><div><br></div><div>Lead Guitar: <Grong gugga gug</div><div>Gug grong gugga gug</div><div>Gug grong gugga gug></div><div><br></div><div>Bass Guitar: <Do doon doon do></div><div>(no one knows what a bassist is doing)</div><div><br></div><div>Singer: I can't express the pain of being intellectually superior to everyone</div><div><br></div><div>Background Visuals: <Terence McKenna and David Cronenberg are fighting for control of the Winamp visualization plugins></div><div><br></div><div>My conclusion: Tool is an alternate reality version of Phish where they dedicated themselves to rebelling against yuppies and neocons.</div><div><br></div><div><br></div><div>In all seriousness though, it was a really great show, and I'm not really a Tool fan. For the songs I was familiar with they were exactly as you hear on the radio; Rush level precision.</div><div><br></div><div>The opening act was awful and I won't dignify the name. Tool played a two and a half hour set which included a fifteen minute intermission. Also, I've never seen a crowd so engaged.</div><div><br></div><div>I was surprised by how much I liked the show. Not my favorite style of music but they really did earn my respect. </div>Jason Mansfieldhttp://www.blogger.com/profile/01931242304514657402noreply@blogger.com0tag:blogger.com,1999:blog-7468008418270845225.post-26047097746283965872019-11-29T21:30:00.001-08:002019-11-29T21:30:15.537-08:00It's Possible To Not Feel Like GarbageI commonly see a type of post on social media. In this post, the person says something to the effect of "You matter" or "You are loved". The intent is to bolster the spirits of people who feel hopeless. It's well-meaning but in my opinion is useless at best and counterproductive at worst.<div><br></div><div>When you have depression part of your brain is dedicated to crushing your spirit. It knows all about you; all your doubts, fears, and regrets which it will use to bring down your sense of self. It is always with you and always working against you.</div><div><br></div><div><i>Your own self</i> tells you that you are worthless and unlovable. So when a stranger says you have value and that you are loved it doesn't come across as a message of hope. At best it's a message of ignorance and at worst it's patronizing. "You don't know the first thing about me" is obvious and true. A perfect stranger telling you a fact about yourself is pretty hard to swallow.</div><div><br></div><div>A better message is "You don't have to feel like garbage". Depression makes you believe that feeling worthless is simply natural to you. It sounds silly but the idea that you can feel simply <i>okay</i> is a genuine message of hope. True happiness might be unrealistic but "not garbage" is something people with depression do experience on occasion. It's plausible that this is a normal state and might be achievable.</div><div><br></div><div>When trying to reach out, keep in mind that your positive messages may be hard to believe. People that may need to hear you won't always listen or be ready to understand. Be patient, be open-minded, and accept that their problems are unique to them and will require solutions unique to them that you may not have access to.<br><div><br></div></div>Jason Mansfieldhttp://www.blogger.com/profile/01931242304514657402noreply@blogger.com0tag:blogger.com,1999:blog-7468008418270845225.post-16728357383888112542019-06-14T16:04:00.000-07:002019-06-14T16:46:01.224-07:00The Scenic Route To Go Interfaces<a href="https://golang.org/" target="_blank">Go</a> is an awesome language and <i>interfaces</i> are one of its most powerful features. They allow for decoupling pieces of code cleanly to help make components like database implementations interchangeable. They're the primary mechanism for dependency injection without requiring a DI framework.<br />
<br />
Newcomers are often mystified by them but I think they're less confusing if you get to them via the scenic route. Let's look at creating our own types in Go. Along the way we'll find parallels that help make interfaces more clear.<br />
<br />
<div style="font-size: smaller; border-style: solid; border-width: 1px; border-color: gray; padding: 3px"><h4>Sidebar: Java Interfaces</h4><div>If you're not experienced with Java, move on to the next section. <i>Nothing to see here</i>.</div><div><br />
</div><div>If you're experienced with Java, Go interfaces will be pretty familiar and comfortable. The key difference is that a class in Java must <i>explicitly</i> implement a predefined interface. In Go, any type that has the proper method signatures implements an interface, even interfaces created after the type. In Go, implementing an interface is <i>implicit</i>.</div></div><br />
<h2>Custom Primitive Types</h2><div>Go emphasizes simple, clear types. You can define your own to help model your problem space. Here I might want to capture a set of boolean flags in one variable:</div><div><br />
</div><div><span style="font-family: "courier new" , "courier" , monospace;"><span style="background-color: lime;">type</span> <span style="background-color: cyan;">BitFlags</span> <span style="background-color: orange;">int32</span></span></div><div><ul><li><span style="background-color: lime;">I'm defining my own type</span></li>
<li><span style="background-color: cyan;">I'm giving it the name BitFlags</span></li>
<li><span style="background-color: orange;">It represents an int32</span></li>
</ul><div>Why not just use <i>int32</i> if that's what I want?</div></div><div><br />
</div><div>One reason is <i>methods</i>. I can attach methods to a type I've defined to give my type additional behavior. Perhaps I define a bunch of constants to represent individual flags and I provide methods like <i>IsSet(BitFlag) bool</i> and <i>Set(BitFlag)</i>.</div><div><br />
</div><div>Another reason is <i>explicit type conversion</i>. In other languages it's valid to assign a 64 bit integer variable to a 32 bit integer variable. They're both integers so it's logical to do so. However, you're possibly losing the high 32 bits of the source value. There's an <i>implicit</i> type conversion happening that is often silent and often surprising.</div><div><br />
</div><div>Go doesn't allow implicit type conversions:</div><div><br />
</div><div><span style="font-family: "courier new" , "courier" , monospace;">i32 := int32(17)</span></div><div><span style="font-family: "courier new" , "courier" , monospace;">var bf BitFlags</span></div><div><span style="font-family: "courier new" , "courier" , monospace;">bf = i32 // not allowed</span></div><div><span style="font-family: "courier new" , "courier" , monospace;">bf = BitFlags(i32) // just fine</span></div><div><span style="font-family: "courier new" , "courier" , monospace;"><br />
</span></div><div>This is done to eliminate surprises. The compiler isn't silently setting a type conversion that can change your data without your knowledge. It requires that <i>you state that you want the conversion</i>. This makes it harder for users of the BitFlags type to accidentally provide a numeric value that shouldn't be interpreted as flags.</div><h2>Custom Struct Types</h2><div><span style="font-family: "courier new" , "courier" , monospace;"><span style="background-color: lime;">type</span> <span style="background-color: cyan;">Foo</span> <span style="background-color: orange;">struct {</span></span></div><div><span style="background-color: orange;"><span style="white-space: pre;"> </span><span style="font-family: "courier new" , "courier" , monospace;">A string</span></span></div><div><span style="background-color: orange;"><span style="white-space: pre;"> </span><span style="font-family: "courier new" , "courier" , monospace;">B int</span></span></div><div><span style="background-color: orange; font-family: "courier new" , "courier" , monospace;">}</span></div><div><ul><li><span style="background-color: lime;">I'm defining my own type</span></li>
<li><span style="background-color: cyan;">I'm giving it the name Foo</span></li>
<li><span style="background-color: orange;">It contains the following data</span></li>
</ul><div>Structs allow you to bundle pieces of data together into a single item. That item can be passed around as a unit. Like custom primitive types, custom struct types can have methods attached.</div></div><div><br />
</div><div>Also like custom primitive types you can assign one to the other if they are equivalent using an explicit type cast:</div><div><br />
</div><div><div><span style="font-family: "courier new" , "courier" , monospace;">type Foo struct {</span></div><div><span style="font-family: "courier new" , "courier" , monospace;"><span style="white-space: pre;"> </span>A string</span></div><div><span style="font-family: "courier new" , "courier" , monospace;"><span style="white-space: pre;"> </span>B int</span></div><div><span style="font-family: "courier new" , "courier" , monospace;">}</span></div><div><span style="font-family: "courier new" , "courier" , monospace;"><br />
</span></div><div><span style="font-family: "courier new" , "courier" , monospace;">type Bar struct {</span></div><div><span style="font-family: "courier new" , "courier" , monospace;"><span style="white-space: pre;"> </span>A string</span></div><div><span style="font-family: "courier new" , "courier" , monospace;"><span style="white-space: pre;"> </span>B int</span></div><div><span style="font-family: "courier new" , "courier" , monospace;">}</span></div></div><div><span style="font-family: "courier new" , "courier" , monospace;"><br />
</span></div><div><span style="font-family: "courier new" , "courier" , monospace;">func main() {</span></div><div><span style="font-family: "courier new" , "courier" , monospace;"><span style="white-space: pre;"> </span>f := Foo{A: "foo", B: 3}</span></div><div><span style="font-family: "courier new" , "courier" , monospace;"><span style="white-space: pre;"> </span>var b Bar</span></div><div><span style="font-family: "courier new" , "courier" , monospace;"><span style="white-space: pre;"> </span>b = f<span style="white-space: pre;"> </span><span style="white-space: pre;"> </span>// invalid</span></div><div><span style="font-family: "courier new" , "courier" , monospace;"><span style="white-space: pre;"> </span>b = Bar(f)<span style="white-space: pre;"> </span>// just fine</span></div><div><span style="font-family: "courier new" , "courier" , monospace;">}</span></div><h2>Custom Interface Types</h2><div>An interface type specifies <i>requirements for behavior</i>. Methods are behavior which is why we tend to name them with verbs or action phrases. In go, any type that has those exact method signatures satisfies the interface's requirements.</div><div><br />
</div><div><span style="background-color: lime;">t<span style="font-family: "courier new" , "courier" , monospace;">ype</span></span><span style="font-family: "courier new" , "courier" , monospace;"> <span style="background-color: cyan;">Storage</span> <span style="background-color: orange;">interface {</span></span></div><div><span style="background-color: orange; font-family: "courier new" , "courier" , monospace;"><span style="white-space: pre;"> </span>Create(key string, o Object) error</span></div><div><span style="background-color: orange; font-family: "courier new" , "courier" , monospace;"><span style="white-space: pre;"> </span>Read(key string) (Object, error)</span></div><div><span style="background-color: orange; font-family: "courier new" , "courier" , monospace;"><span style="white-space: pre;"> </span>Update(key string, o Object) error</span></div><div><span style="background-color: orange; font-family: "courier new" , "courier" , monospace;"><span style="white-space: pre;"> </span>Delete(key string) error</span></div><div><span style="background-color: orange; font-family: "courier new" , "courier" , monospace;">}</span></div><div><br />
</div><div><ul><li><span style="background-color: lime;">I'm defining my own type</span></li>
<li><span style="background-color: cyan;">I'm giving it the name Storage</span></li>
<li><span style="background-color: orange;">Anything with these methods qualifies as this type</span></li>
</ul><div><br />
</div></div><div>Using interfaces I can define requirements for a storage system for my application to use. My application needs something through which I can create, read, update, and delete objects associated with a given key.</div><div><br />
</div><div><span style="font-family: "courier new" , "courier" , monospace;">func GeneratePDFReport(output io.Writer, storage Storage) error {</span></div><div><span style="font-family: "courier new" , "courier" , monospace;"><span style="white-space: pre;"> </span>// ...</span></div><div><span style="font-family: "courier new" , "courier" , monospace;">}</span></div><div><br />
</div><div>My application isn't concerned with how those operations are actually performed. The underlying storage could be an SQL database, S3 bucket, local files, Mongo, Redis, or anything that can be adapted to do those four things. Perhaps the report generator supports many storage mechanisms and when the application starts it decides which storage to use based on a config file or flags. It also means that when I need to write tests for my report generator I don't need to have an actual SQL database or write files to disk; I can create an implementation of <i>Storage</i> that only works with test data and behaves in an entirely predictable way.</div><div><br />
</div><h2>Interface <i>nil</i> and Type Assertions</h2><div>For all variables of interface types the <i>runtime</i> keeps track of two things: the underlying value and that value's type. This leads to two different ways an interface variable can be <i>nil</i>. First, the interface value itself can be <i>nil</i>. In this case there's no type information, no underlying value; nothing to talk about. This is very common with the <i>error</i> interface. In the case of no error the interface variable itself is <i>nil</i> because there's no error to be communicated.</div><div><br />
</div><div>In the second case there's type information but the underlying value is <i>nil</i>. A comparison like <i>myInt == nil</i> returns <i>false</i> because the interface value exists and points to type information. Ideally in this case <i>nil</i> is useful for that type as in the final example in <a href="https://dave.cheney.net/2013/01/19/what-is-the-zero-value-and-why-is-it-useful" target="_blank">Dave Cheney's zero post</a>.</div><div><br />
</div><div>If needed you can get at the underlying value inside an interface variable.</div><div><br />
</div><div><i>io.Writer</i> is a commonly-used interface. It has only one method: <i>Write([]byte) (int, error)</i>. If I have a variable <i>out</i> of type <i>io.Writer</i> the only operation I can perform on it is <i>Write</i>. What if I want to <i>Close</i> it? Ideally if you have <i>Close</i> as an requirement you should make <i>Close</i> part of the interface type of your variable (or use <i>io.WriteCloser</i> instead of <i>io.Writer)</i>.</div><div><br />
</div><div>For the purposes of illustration you can do a <i>type assertion</i>. This asks that the <i>runtime</i> verify that the underlying thing in your variable is of a certain type:</div><div><br />
</div><div><span style="font-family: "courier new" , "courier" , monospace;">if c, ok := out.(io.WriteCloser); ok {</span></div><div><span style="font-family: "courier new" , "courier" , monospace;"><span style="white-space: pre;"> </span>err := c.Close()</span></div><div><span style="font-family: "courier new" , "courier" , monospace;"><span style="white-space: pre;"> </span>// handle error</span></div><div><span style="font-family: "courier new" , "courier" , monospace;">} else {</span></div><div><span style="font-family: "courier new" , "courier" , monospace;"><span style="white-space: pre;"> </span>// not an <i>io.WriteCloser!</i></span></div><div><span style="font-family: "courier new" , "courier" , monospace;">}</span></div><div><br />
</div><div>In the above example, if <i>out</i> happens to be an <i>io.WriteCloser</i> then <i>ok</i> will be <i>true</i> and <i>c</i> will be <i>out</i> as type <i>io.WriteCloser</i>. If <i>out</i> doesn't happen to be an <i>io.WriteCloser</i>, <i>ok</i> is <i>false</i> and <i>c</i> is <a href="https://dave.cheney.net/2013/01/19/what-is-the-zero-value-and-why-is-it-useful" target="_blank">zero for <i>io.WriteCloser</i></a> which is <i>nil</i>.</div><h2>Anonymous Struct Types</h2><div>Given a preexisting struct type I can create a value of that with data in one statement:</div><div><br />
</div><div><span style="background-color: lime;">f <span style="font-family: "courier new" , "courier" , monospace;">:=</span></span><span style="font-family: "courier new" , "courier" , monospace;"> <span style="background-color: cyan;">Foo</span><span style="background-color: orange;">{</span></span></div><div><span style="background-color: orange; font-family: "courier new" , "courier" , monospace;"><span style="white-space: pre;"> </span>A: "foo",</span></div><div><span style="background-color: orange; font-family: "courier new" , "courier" , monospace;"><span style="white-space: pre;"> </span>B: 17,</span></div><div><span style="background-color: orange; font-family: "courier new" , "courier" , monospace;">}</span></div><div><ul><li><span style="background-color: lime;">I'm creating a variable f</span></li>
<li><span style="background-color: cyan;">It is of type Foo</span></li>
<li><span style="background-color: orange;">It contains these values</span></li>
</ul></div><div><br />
</div><div>In the above struct examples each of the types I defined have a name; this isn't always necessary providing I'm creating the struct on the spot and assigning it somewhere.</div><div><br />
</div><div><span style="font-family: "courier new" , "courier" , monospace;"><span style="background-color: lime;">ff :=</span> <span style="background-color: cyan;">struct {</span></span></div><div><span style="background-color: cyan; font-family: "courier new" , "courier" , monospace;"><span style="white-space: pre;"> </span>A string</span></div><div><span style="background-color: cyan; font-family: "courier new" , "courier" , monospace;"><span style="white-space: pre;"> </span>B int</span></div><div><span style="font-family: "courier new" , "courier" , monospace;"><span style="background-color: cyan;">}</span><span style="background-color: orange;">{</span></span></div><div><span style="background-color: orange; font-family: "courier new" , "courier" , monospace;"><span style="white-space: pre;"> </span>A: "foo",</span></div><div><span style="background-color: orange; font-family: "courier new" , "courier" , monospace;"><span style="white-space: pre;"> </span>B: 17,</span></div><div><span style="background-color: orange; font-family: "courier new" , "courier" , monospace;">}</span></div><div><br />
</div><div><br />
<br />
<li><span style="background-color: lime;">I'm creating a variable ff</span></li><br />
<li><span style="background-color: cyan;">It is of <i>this type</i></span></li><br />
<li><span style="background-color: orange;">It contains these values</span></li><br />
</div><div>Like the named struct types above I can do an assignment with an explicit type conversion:</div><div><br />
</div><div><span style="font-family: "courier new" , "courier" , monospace;">f = ff</span><span style="font-family: "courier new" , "courier" , monospace; white-space: pre;"> </span><span style="font-family: "courier new" , "courier" , monospace; white-space: pre;"> </span><span style="font-family: "courier new" , "courier" , monospace;">// invalid</span></div><div><span style="font-family: "courier new" , "courier" , monospace;">f = Foo(ff)<span style="white-space: pre;"> </span>// totally fine</span></div><div><br />
</div><div>This sort of anonymous struct type is common in <a href="https://dave.cheney.net/2013/06/09/writing-table-driven-tests-in-go" target="_blank">table driven tests</a>. It's also not uncommon in defining nested structs as <a href="https://mholt.github.io/json-to-go/" target="_blank">mholt's JSON to Go converter</a> does.</div><div><br />
</div><div>You'll also sometimes see this:</div><div><br />
</div><div><span style="font-family: "courier new" , "courier" , monospace;"><span style="background-color: lime;">stringSet :=</span> <span style="background-color: cyan;">map[string]struct{}</span><span style="background-color: orange;">{}</span></span></div><div><br />
</div><div><br />
<br />
<li><span style="background-color: lime;">I'm creating a variable stringSet</span></li><br />
<li><span style="background-color: cyan;">It is of <i>this type</i></span></li><br />
<li><span style="background-color: orange;">It contains these values</span></li><br />
</div><div>The last part looks a little strange. It's a map with strings for keys but what are the values? The values are <i>empty structs</i>: they contain nothing and therefore take up no memory. What good is that? It's a map that only tracks the presence of keys which functions as a logical set. The final magenta curly braces define the initial contents of the map; it's empty.</div><h2>Anonymous Interfaces</h2><div>Just like you can have anonymous struct types you can have anonymous interface types. The following are equivalent:</div><div><br />
</div><div><span style="font-family: "courier new" , "courier" , monospace;">var foo io.Reader</span></div><div><span style="font-family: "courier new" , "courier" , monospace;"><br />
</span></div><div><span style="font-family: "courier new" , "courier" , monospace;">var foo interface{</span></div><div><span style="font-family: "courier new" , "courier" , monospace;"><span style="white-space: pre;"> </span>Read([]byte) (int, error)</span></div><div><span style="font-family: "courier new" , "courier" , monospace;">}</span></div><div><br />
</div><div>In either case I can assign anything with a <i>Read([]byte) (int, error)</i> method to <i>foo</i>.</div><div><br />
</div><div>We're near the end of our journey which brings us to the enigmatic <i>interface{}</i>:</div><div><br />
</div><div><span style="font-family: "courier new" , "courier" , monospace;"><span style="background-color: cyan;">foo</span> <span style="background-color: lime;">:=</span> <span style="background-color: orange;">interface{}</span><span style="background-color: yellow;">{}</span></span></div><div><span style="font-family: "courier new" , "courier" , monospace;"><span style="background-color: lime;">var</span> <span style="background-color: cyan;">foo</span> <span style="background-color: orange;">interface{}</span> <span style="background-color: yellow;">= nil</span></span></div><div><ul><li><span style="background-color: lime;">I'm defining a variable</span></li>
<li><span style="background-color: cyan;">I'm giving it the name foo</span></li>
<li><span style="background-color: orange;">Anything with these methods qualifies as this type</span></li>
<li><span style="background-color: yellow;">The contents are explicitly zero</span></li>
</ul><div><i>interface{}</i> is an anonymous interface type. It has <i>no requirements</i> so <i>any value</i> is suitable. I can pass around a value of type <i>interface{}</i> but I can't do anything with it without using a type assertion or the <i>reflect</i> package.</div><div><br />
</div><div>In this way the empty interface is different than other interface types in that it <i>doesn't</i> specify required behavior. It sidesteps the type system and turns what could be compile-time errors into run-time errors. When writing code that uses the empty interface use great care.</div></div>Jason Mansfieldhttp://www.blogger.com/profile/01931242304514657402noreply@blogger.com1tag:blogger.com,1999:blog-7468008418270845225.post-20750680133041686392019-06-08T20:01:00.000-07:002019-06-08T20:01:18.417-07:00Embedding Version In Go Binaries From Git TagsIn my go binaries I commonly embed version information that is read automatically from git tags. I wrote up my setup:<br />
<br />
<a href="https://github.com/AgentZombie/go-embed-version">https://github.com/AgentZombie/go-embed-version</a>Jason Mansfieldhttp://www.blogger.com/profile/01931242304514657402noreply@blogger.com0tag:blogger.com,1999:blog-7468008418270845225.post-55663663717997793832019-03-06T21:28:00.002-08:002019-03-06T21:28:49.639-08:003D Printer Filament Cheap Vacuum DesiccationLots of 3D printer filaments are <a href="https://en.wikipedia.org/wiki/Hygroscopy" target="_blank">hygroscopic</a>; they absorb water from the atmosphere. This is problematic for filaments because during printing they are subject to a sudden, dramatic jump in temperature. This causes the absorbed water to explode potentially making detrimental changes to the material properties of the filament. For filaments like PETG and Nylon this makes for a rougher surface and anomalies in the finished part.<br />
<br />
The standard mechanism for drying filament is to put it in the oven for a while. I started getting strange results with my PETG because I got lazy and left it out between prints. I wanted to fix the issue but wasn't keen about putting plastics in my oven. I wondered about other methods and the silica gel packets came to mind. Those are the little packets that come with shoes and other things and say DO NOT EAT. I was skeptical about the power of these to reverse the absorption that had already taken place, at least not in a timely fashion. Also, I like to tinker and wanted to experiment with weird ideas.<br />
<br />
I like to sous vide and had recently gotten a food vacuum sealer with an attachment for sucking the air out of wine bottles. I could seal the filament spools in a vacuum bag with some desiccant but I felt like it would work better if I could get closer to a proper vacuum. I didn't want to invest in a high-grade vacuum chamber and pump for an experiment. I started looking around and found the <a href="https://www.foodsaver.com/food-storage-bags-and-containers/vacuum-food-containers/vacuum-marinator/foodsaver-quick-marinator/T02-0050-015.html" target="_blank">FoodSaver Quick Marinator</a>; a rigid container with a valve for the hose that connected the vacuum sealer to attachments.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiD82Te7ndzZDsPPtxgCI0SGAfnO0gulfcskkHBxTNZ56NOCk1cY_P1FsNMZIY4Q61UJVrIDznxfvgtA_xIj9I1QIF1vewtpSxEkMikLvOstVOsGkFZVfw5NvdgAUyalPXDz8l_8BGVpBQ/s1600/IMG_20190306_204834.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="1200" data-original-width="1600" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiD82Te7ndzZDsPPtxgCI0SGAfnO0gulfcskkHBxTNZ56NOCk1cY_P1FsNMZIY4Q61UJVrIDznxfvgtA_xIj9I1QIF1vewtpSxEkMikLvOstVOsGkFZVfw5NvdgAUyalPXDz8l_8BGVpBQ/s320/IMG_20190306_204834.jpg" width="320" /></a></div>
Here's my setup:<br />
<br />
<ul>
<li>Off-brand food vacuum sealer</li>
<li>The FoodSaver Quick Marinator</li>
<li>The vacuum sealer attachment hose</li>
<li>Coffee Filters</li>
<li><a href="https://www.amazon.com/Panacea-60102-Silica-Crystals-1-5-Pound/dp/B001E5U3QQ" target="_blank">Flower desiccant powder</a></li>
</ul>
<div>
The flower desiccant powder is the same material as the silica gel packets you can get but it's very fine, providing a higher surface area, and they're trivial to renew.</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1HkUfDAdtKf31qI2MIsi1FOu8PRuWZfKG95OfNxX5pjRVWihVB08s709IzrgAY6v5jq0uSLj3_ovaUVsWqbPD8MqlYzzgO20SiTSmxbuVZkqHJ8TsfkbEz6PI-SBSXJwCBmyKnp3Gyr4/s1600/IMG_20190306_204849.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="1200" data-original-width="1600" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1HkUfDAdtKf31qI2MIsi1FOu8PRuWZfKG95OfNxX5pjRVWihVB08s709IzrgAY6v5jq0uSLj3_ovaUVsWqbPD8MqlYzzgO20SiTSmxbuVZkqHJ8TsfkbEz6PI-SBSXJwCBmyKnp3Gyr4/s320/IMG_20190306_204849.jpg" width="320" /></a></div>
<div>
I put 5 (arbitrary) tablespoons of desiccant powder into a coffee filter, fold it up, and tape it closed so the powder can't easily escape.</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMu9YfprJ0nwBBQ5-bC0FTCrg341LdMAona3uM1iZvb4HyDV4oadQ2LiOi-03IiGrIhY6t32aogm7UixFL3W4KpZut3D4RcFj2GUgi5jKFKLIW2u7RKT7bQGsn2niMXbt8EzzDvGqUg90/s1600/IMG_20190306_204934.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="1200" data-original-width="1600" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMu9YfprJ0nwBBQ5-bC0FTCrg341LdMAona3uM1iZvb4HyDV4oadQ2LiOi-03IiGrIhY6t32aogm7UixFL3W4KpZut3D4RcFj2GUgi5jKFKLIW2u7RKT7bQGsn2niMXbt8EzzDvGqUg90/s320/IMG_20190306_204934.jpg" width="320" /></a></div>
<div>
I put my filament spool into the marinator, drop my desiccant pack on top and put the lid on the with tabs on the same side.</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjeD4eaATIKuSVfum_l1blamYhXJdpnIYNbMQSzRvE3R5UJxG7bIDWzAwGNL5bn8CVt-aCHFy22xge4z6JNHhG0sj3lr9wL_hPOqX-XdaeakbV0KxowBt9p_ouXQfuLQj8QImWAYCDGXq0/s1600/IMG_20190306_204958.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="1200" data-original-width="1600" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjeD4eaATIKuSVfum_l1blamYhXJdpnIYNbMQSzRvE3R5UJxG7bIDWzAwGNL5bn8CVt-aCHFy22xge4z6JNHhG0sj3lr9wL_hPOqX-XdaeakbV0KxowBt9p_ouXQfuLQj8QImWAYCDGXq0/s320/IMG_20190306_204958.jpg" width="320" /></a></div>
<div>
Then, suck the air out. I run the pump until it shuts off. Wait a few seconds, and repeat. I go through about three cycles of this. After the pump has shut off, ensure the pump isn't warm; you don't want to ruin your pump by overheating it.</div>
<div>
<br /></div>
<div>
Finally I ensure the valve is closed before removing the hose.</div>
<div>
<br /></div>
<div>
Surprisingly, this worked. My next round of PETG printing produced a nice, smooth finish. I've taken to storing the spools with one of my homemade desiccant packets in a small <a href="http://spacesaver.us/" target="_blank">Space Saver bag and vacuum the air out.</a> It doesn't look like they sell just the small bags and the medium and larger are grossly too large for a filament spool.</div>
<div>
<br /></div>
<div>
<b>It's important to note</b> that so far I've only done this desiccating process on Dremel filament spools which are 0.5kg. As you can see in the picture above these spools <i>just fit</i> in the quick marinator. Larger spools probably won't fit, loose filament can depending on how much you want to stuff in there. When I get larger spools I'll probably have to come up with another mechanism.</div>
Jason Mansfieldhttp://www.blogger.com/profile/01931242304514657402noreply@blogger.com0tag:blogger.com,1999:blog-7468008418270845225.post-61191224842650987892019-01-10T19:43:00.001-08:002019-01-10T19:43:43.811-08:00New 3D Printer: Dremel 3D45<h2>
Background</h2>
<div>
For a while I had a 3D printer. Specifically a Printrbot Simple Metal. When it worked, it was a lot of fun. I could start with an idea and turn it into a real physical object without the need for a big workshop, huge mess, or personal injury. When it worked, it was great! But the process of getting it to work and keeping it working took all the joy out of the prospect of desktop fabrication.</div>
<div>
<br /></div>
<div>
Three years later I found myself thinking about getting another 3D printer, hoping that the reliability had improved. I started looking at what was on the market with an emphasis on reliability: I didn't want to tinker with the printer, I just wanted to turn designs into objects. Dremel seemed to have some reliable offerings though a little pricey. I pulled the trigger on the 3D45 and so far I'm very pleased.</div>
<h3>
Disclaimer</h3>
<div>
I should be getting compensated for this review. My printer came with a small flyer that said if I post a review of the printer on my blog I would receive two spools of PLA for free. The flyer didn't provide direction on what the review <i>should say</i>.</div>
<h2>
Initial Impressions</h2>
<div>
I pulled the printer out of the box and had it set up in about 20 minutes. I was able to start a job for a frog figure from the internal storage. It came out <i>almost</i> perfect with a couple of misaligned layers and some poor adhesion ("springing") at the top.</div>
<div>
<br /></div>
<div>
Overall though I had good luck with the printer initially. I would sometimes have trouble with first layer adhesion which is the most common problem printing. Trying different things to correct it I found that washing the build plate, perform bed leveling, and reapplying glue would almost always correct the issue.</div>
<h2>
The Good, the Bad, and the Ugly</h2>
<h3>
Pros</h3>
<div>
It <i>just works</i>. I've used an entire spool of ECO-ABS with an overwhelmingly positive success to failure ratio. I've printed in PETG and Nylon as well with no failures there. Nylon is notoriously annoying to print with and it worked on my first try. Note; The ECO-ABS is a proprietary formulation of PLA.</div>
<div>
<br /></div>
<div>
It looks nice. While I don't actually care how it looks, the fact that it's fully self-contained means that the cat, the dog, dust, Dorito crumbs, ejected shell casings, etc won't put my prints a risk.</div>
<div>
<br /></div>
<div>
It has almost all the bells and whistles. As stated, it's fully-enclosed to help keep it work inside and reduce warping. It has a heated bed to help with the same. The build plate is tempered glass in an easy to remove and handle frame. I'm really pleased with how easy it is to work with the build plate. It's got a touch screen that provides helpful information and allows you to perform basic operations and start/pause/cancel prints from onboard storage or a USB drive. It prints "ECO-ABS", PLA, PETG, and Nylon. It has an integrated web cam, runout sensor, yadda-yadda-yadda.</div>
<div>
<br /></div>
<div>
The filament seems really good. The color and performance are consistent throughout the spool. I don't really have problems with first-layer or intra-layer adhesion. The Dremel filament spools have an embedded RFID tag that the printer reads and use to <i>automatically</i> set the temperature for the extruder and bed.</div>
<h3>
Cons</h3>
<div>
It's <i>expensive</i>. The printer retails for $1800. I got mine for $1400 on Amazon black Friday. That's fine as a reliable device that lasts years is worth investing in. The filament, however, is also expensive. Common, non-funky filaments run between $15-30 per kg. Dremel filament runs $30-40 per .5kg. This is tempered somewhat by the fact that between the quality of the printer and their filament my prints succeed more so I waste less (save my own design mistakes).</div>
<div>
<br /></div>
<div>
It's something of a <i>walled garden</i>. There's not a lot of room to tinker with the printer. I don't <i>want</i> to tinker with the printer... I don't want to have to. However, should it be necessary it would be nice. I can't complain too much as I made a conscious decision to sacrifice hackability for reliability.</div>
<div>
<br /></div>
<div>
The network features are <i>weak</i>. You can manage and monitor print jobs through their cloud service but you can't do so over the LAN. The frame rate for the video in their cloud service is like <i>0.1fps</i>. It's difficult to actually understand what's happening.</div>
<h3>
WTF?</h3>
<div>
The cloud slicer is <i>garbage</i>. You can upload an STL to the cloud service and have it do the slicing for you. It was easy! I never got a successful print from the cloud slider. Usually there would be a loss of inter-layer adhesion leaving me with a slinky vaguely in the shape of my print. Using the Dremel Cura desktop slicer works extremely well and when I upload the gcode it produces to the cloud service the results are great. Unfortunately they kind of tout the cloud service and using only the cloud service brought me nothing but failure. Luckily I have some printing experience and knew how to troubleshoot this.</div>
<div>
<br /></div>
<div>
"Filament <i>DRM</i>". They strongly encourage you to print using only Dremel filament. It seems to be high quality and the RFID identification feature is definitely neat. However, the state that using non-Dremel filament will void your warranty. This seems <i>insane</i> to me. I get that they wouldn't want to support issues people have using janky filament. I would totally accept that if I use 3rd party filament and my house burns down they shouldn't be held liable. But to say that they are unwilling to support me for using less expensive filament leaves a bad taste.</div>
<div>
<br /></div>
<div>
And the filament <i>cost</i>. I would feel a lot less annoyed about the quasi-restriction of using only Dremel filament if it weren't 2-4 times the cost of other filaments. It <i>is</i> good but the difference in cost is kind of crazy. To some extent they market this product line for education; <i>schools can't afford this</i>. Also, they should encourage other filament vendors to use the same RFID spec. It's a cool feature. Overall, they should make the filament significantly cheaper or penalize their customers less for using third-party filaments.</div>
<div>
<br /></div>
<div>
I'm really disappointed that I can't really use the built-in camera decently. I get that they wouldn't want to stream 720p over the Internet to everyone; they aren't trying to be a video streaming service. There should be a way for me to watch it in full quality over my home network.</div>
<h2>
Trouble Strikes!</h2>
<div>
Eventually I had a serious issue that wash/level/glue wouldn't resolve. I wasn't <i>getting</i> a first layer. The extruder would travel around the surface of the bed and yell <i>CLICK CLICK CLICK CLICK</i> as though some gears were slipping, like something was stuck. Thinking I had a clog I looked in the manual for their clearing process. The troubleshooting matrix says that if have a clog to contact support. Simultaneously the manual has instructions for clearing a clog. I went through the clog clearing process and found that the extruder could push filament out fine. Attempting another print I got the same symptom. It seemed that the tip of the nozzle was flush with the bed and it was clogged because there was no place to extrude filament <i>to</i>.</div>
<div>
<br /></div>
<div>
I call customer support but it was a Friday afternoon. Wonderfully, they have US-based support. Unfortunately for me they're in Central Time, I'm in Pacific Time and they were already closed. I used their email contact form and received an automated response saying I'd hear back from them in two <i>business days</i>. This being Friday I shouldn't hear back from them until Tuesday or Wednesday. On Monday I got a response with corrective instructions that fixed the issue in a few minutes. It sucks that I was "out of commission" for several days. It's awesome that I had access to people whose job it was to help me. Free input on 3D printing issues will vary far and wide on the nature of your problem and remediation.</div>
<h2>
Conclusions</h2>
<div>
I think my money on this printer was well-spent. I've been really enjoying it. I feel like I got the reliability I was looking for. The filament is really expensive which makes me hesitant to experiment.</div>
Jason Mansfieldhttp://www.blogger.com/profile/01931242304514657402noreply@blogger.com0tag:blogger.com,1999:blog-7468008418270845225.post-53335017148760400342018-03-03T16:38:00.001-08:002018-03-03T16:38:02.316-08:00Impossible Burger<p dir="ltr">I just ate an Impossible Burger at Fatburger. If you hadn't told me what I was eating I would have told you I was eating a good but not great beef hamburger. The fact that it was entirely plant-based is impressive. It was savory, the texture was well within expectations for a beef hamburger.</p>
<p dir="ltr">Kelly tells me it nutritionally matches beef so it's not necessarily healthier. There's still <u>the</u> reduced environmental impact over beef. I could even see versions that compromise: including perhaps 10% beef for the extra flavor.</p>
<p dir="ltr">It was expensive though. Our two single-patty burgers with fries and drink was $31. I'd like this to become more popular so the price can go down. I would happily pay a dollar extra per patty for this as an substitution option at fast food places.</p>
<p dir="ltr">I'm really impressed by this as a first product and look forward to where this is going.</p>
Jason Mansfieldhttp://www.blogger.com/profile/01931242304514657402noreply@blogger.com0tag:blogger.com,1999:blog-7468008418270845225.post-65794679690769323692018-01-05T10:00:00.000-08:002018-01-05T10:00:25.322-08:00Black Mirror - S4E1: Roko's BasiliskThe first episode of Black Mirror season 4 is quite good. It brought to mind <a href="https://rationalwiki.org/wiki/Roko's_basilisk" target="_blank">Roko's Basilisk</a>. Go read about the silly thing human brain's get up to. I'll be here when you get back.Jason Mansfieldhttp://www.blogger.com/profile/01931242304514657402noreply@blogger.com0tag:blogger.com,1999:blog-7468008418270845225.post-65600323037728955452018-01-02T18:16:00.000-08:002018-01-14T14:28:56.433-08:00FPS: Wolfenstein II: The New Colossus - More BJ For Frau EngelSteam Sale, <a href="http://store.steampowered.com/app/612880/Wolfenstein_II_The_New_Colossus/" target="_blank"><i>Wolfenstein II</i></a>. Yes.<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuva572H6UYRo_cfivIPYbveD82ShmPxGkBsVPKb9UYoVpgycnR-VX3KZICKgpgi9WHigOBT2znaRj859sHlOLY_n5QIQp6HOIg-UIRaSQ0f9rsVvue4Am2pUqIdu8NOtY5XzWEo1C3oI/s1600/WOLF-04.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="150" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuva572H6UYRo_cfivIPYbveD82ShmPxGkBsVPKb9UYoVpgycnR-VX3KZICKgpgi9WHigOBT2znaRj859sHlOLY_n5QIQp6HOIg-UIRaSQ0f9rsVvue4Am2pUqIdu8NOtY5XzWEo1C3oI/s200/WOLF-04.png" width="200" /></a><br />
I can reasonably say I've been playing <i>Wolfenstein</i> as long as there's been <i>Wolfenstein</i>. I even played the MS-DOS port of the <a href="https://en.wikipedia.org/wiki/Castle_Wolfenstein" target="_blank">original 8-bit game</a> a few times. I played <i>Spear of Destiny</i>, <i>Blake Stone</i>, etc. <i><a href="http://store.steampowered.com/app/201810/Wolfenstein_The_New_Order/" target="_blank">Wolfenstein: The New Order</a></i> is almost flawless.<br />
<br />
<i>The New Colossus</i> is a solidly decent game. There's more of the unexpectedly good story and character development from the previous game. This is definitely not the fungible, faceless protagonist of previous entries in the series, or even most FPS in general. Your character has thoughts, emotions, demons, loves, etc. The supporting cast of characters are dynamic and lifelike and it's definitely worth getting through the game just to see how the events played out. The ending actually felt fulfilling; moreso than in <i>The New Order</i> in my opinion.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbuqzTrSuG1OMeKX9OdswK-IJeyajNmuQFA4gXsHdyJFVai5ZJD6VO3zUos2to6KoZttnOWgYmH6PGjoOGdloF476cS-sCemg2onJ1TPSW_RTG2p57IGcngacQwYExNxW6YHm6x_kfa4k/s1600/jump.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="314" data-original-width="472" height="212" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbuqzTrSuG1OMeKX9OdswK-IJeyajNmuQFA4gXsHdyJFVai5ZJD6VO3zUos2to6KoZttnOWgYmH6PGjoOGdloF476cS-sCemg2onJ1TPSW_RTG2p57IGcngacQwYExNxW6YHm6x_kfa4k/s320/jump.png" width="320" /></a></div>
About halfway through though the story jumps the shark in spectacular fashion. In the first half the protagonist is put at a significant disadvantage and you're made to wonder how it might be overcome. The way in which it is overcome is completely absurd and thankfully it is quickly put behind you. When you get to the shark jumping, groan your way through it and press on.<br />
<br />
Combat is <i>mostly</i> satisfying. You get the same optional dual-wield mechanic as in <i>The New Order</i> though in this instance it's rare that <i>not</i> dual-wielding is the way to go. The way you choose which weapons are in which hands is unnecessarily clumsy on PC; I suspect it's made for console controller. As you explore you can find hidden weapon upgrade kits that allow you choose from three upgrades per weapon that are not mutually exclusive. Each weapon has an upgrade that can be toggled. For example, the machinepistol upgrade makes your rounds incendiary while dramatically reducing the rate of fire. Incendiary MP is magical against the panzerhunden but much less effective against human targets. I found triple-shot shotgun in the left hand and single-shot assault rifle in the right to be a great combination allowing effective firepower against both close and far targets.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinZvIPRm3d05Yta9PqO8PQKnfz9XPhNc8A0xdY-dYWAubtTvS1_Z0JIw8HIY1mlVfefx2DMMQtSKh5BAicXVcwzPf9cpwDOap7O-QcW8GZPIOgFiD2XGaXDxPNd8ngLTymf-_Gc9IGCaU/s1600/stealth-snatch.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="339" data-original-width="480" height="226" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinZvIPRm3d05Yta9PqO8PQKnfz9XPhNc8A0xdY-dYWAubtTvS1_Z0JIw8HIY1mlVfefx2DMMQtSKh5BAicXVcwzPf9cpwDOap7O-QcW8GZPIOgFiD2XGaXDxPNd8ngLTymf-_Gc9IGCaU/s320/stealth-snatch.png" width="320" /></a></div>
In principle <i>The New Colossus</i> allows you to choose stealth over bravado the same way <i>The New Order</i> did. In practice you can stealthily take out the first 2-6 enemies in a stage before things go sideways, more or less regardless of your execution. If a guard finds the body of a fallen enemy they go into a heightened state, which is fantastic for gameplay. Unfortunately, you have no mechanism for moving or hiding those bodies, even given ample time. This can be quite frustrating. <i>Doom (2016)</i> also gave you no reliable means for stealth combat, or even space to conduct ambushes but in <i>Doom</i> the whole flow of the game is in high-velocity, run and gun combat. <i>The New Colossus</i> holds stealth combat out and then snatches it away, just when you think you've got it.<br />
<br />
Run and gun works well and as mentioned you're mostly forced down this path. That said, most enemies have no special tactical weaknesses (less armor on the back) so few fights benefit from flanking. The super soldiers are weak in the back but it's easy to bait them into charging past you for easy back shots. Overall most fights I ended up just finding the most advantageous ambush point and waiting for the enemies to come to me. And they did... I would really like an FPS developer add an "I see the bodies of my comrades piled in this door way so maybe I should take a different route" mechanic to enemy AI. Which is a shame because many of the combat spaces are <i>huge</i> with multiple tiers, multiple places for cover, resupply, etc. Usually though I couldn't be bothered and just explored the space at my leisure after dispatching the majority of my foes with the stupidest possible ambush. Eventually though I decided to not pick up heavy weapons, to not ambush as much, just to keep the combat more dynamic, even on a higher difficulty setting.<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRlrV1UbDYX5l-KnX1M8ffCvaNT5eDkyfuzmVT_sVIr7jz8JTD8NFWY1DosKBK4ZhvrQO7n4xz61TSUmC5sL7ZPYm-5OpxiywnfQ4jNXwZ3bWQjauhHbePqUKdRswHTwUpYGrwB7Ddjd4/s1600/98e48ed13ae5f53e27761b6021bee495--vegan-humor-food-diary.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="151" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRlrV1UbDYX5l-KnX1M8ffCvaNT5eDkyfuzmVT_sVIr7jz8JTD8NFWY1DosKBK4ZhvrQO7n4xz61TSUmC5sL7ZPYm-5OpxiywnfQ4jNXwZ3bWQjauhHbePqUKdRswHTwUpYGrwB7Ddjd4/s200/98e48ed13ae5f53e27761b6021bee495--vegan-humor-food-diary.jpg" width="200" /></a><br />
After you've hit the game's shark-hurdling portion you get to choose a "contraption" that gives you a special ability. As the game progresses you get to pick up the other two. The difference between these choices basically boils down to, "to get into the next room do I crawl under, climb over, or smash through?". Regardless of your choice you still end up in the same room so the choice is somewhat illusory. It's mostly a question of what kind of entrance do you want to make to the party.<br />
<br />
Criticisms aside, it's quite a fun game and worth a playthrough. Grab it on sale and rock out!Jason Mansfieldhttp://www.blogger.com/profile/01931242304514657402noreply@blogger.com0tag:blogger.com,1999:blog-7468008418270845225.post-16212744253912518302018-01-01T20:54:00.000-08:002018-01-08T14:26:36.768-08:00Cards Against Humanity EnhancementsOne of my friend groups plays a fair amount of Cards Against Humanity. I've come up with some enhancements.<br />
<div>
<ol>
<li>Use a shoebox, game box top, or some other box for white card discard. It's much neater than discard piles.</li>
<li>Use a hat or something similar to collect white card submissions. It genuinely speeds things up.</li>
<li>Provide multiple stacks of white and black cards respectively for people to pull from. It makes it much easier for people to get their next cards and keeps the game moving.</li>
<li>If the black card with three blanks is in your deck, pull it out permanently.</li>
<li>When a black card with two blanks comes up, the judge draws a white card to fill in one of the blanks of their choosing. Everyone submitting two white cards slows the game down. Filling in one of the blanks with a random white card means the black cards with two blanks become unique every time they appear, rather than tedious.</li>
<li>During the initial deal-in for each player, give them three extra white cards. Each player discards three before the game begins. This provides a funnier set of initial cards for everyone.</li>
<li>Draw two, keep one. After each player submits their white card they must draw a replacement white card. Instead, each player should draw two white cards and discard one of the two they just drew, their choice. This reduces the accumulation of bad white cards.</li>
</ol>
</div>
Jason Mansfieldhttp://www.blogger.com/profile/01931242304514657402noreply@blogger.com0tag:blogger.com,1999:blog-7468008418270845225.post-41790947915164092252017-09-04T16:27:00.000-07:002017-09-04T16:27:05.465-07:00Fighting Acute Depression<span style="font-family: inherit;"><span style="background-color: white; color: #1d2129; font-size: 14px;">My only reliable strategy for fighting acute depression:</span></span><br />
<ul>
<li><span style="font-family: inherit;">Identify something simple (simple does not equal easy) that I don't want to do but will feel good having made progress on. Dishes is a good one for me. Laundry is another.</span></li>
<li><span style="font-family: inherit;">Identify the smallest thing that constitutes progress. Washing one dish, putting away one piece of clothing.</span></li>
<li><span style="font-family: inherit;">Focus on the fact that doing that smallest thing represents progress. If I get more done, great! If not, I made progress when feeling overwhelmed.</span></li>
<li><span style="font-family: inherit;">Go do that smallest thing. Chances are more will get done since starting is the hardest part. If not, I don't beat myself up. I made progress and the next progress will be easier.</span></li>
<li><span style="font-family: inherit;">It doesn't always work and that's okay too. When getting started is the hardest part, trying is a small amount of success which fuels further success.</span></li>
</ul>
Jason Mansfieldhttp://www.blogger.com/profile/01931242304514657402noreply@blogger.com0tag:blogger.com,1999:blog-7468008418270845225.post-31539066191393370772017-01-27T10:52:00.000-08:002017-01-27T10:52:16.290-08:00Vault: Error checking seal status: ... Forbidden<blockquote class="tr_bq">
<span style="font-family: Courier New, Courier, monospace;">$ vault status<br />Error checking seal status: Get https://vault.internal-domain:8200/v1/sys/seal-status: Forbidden</span></blockquote>
There was <a href="https://github.com/hashicorp/vault/issues/2175" target="_blank">this issue</a> suggesting it was a problem with the storage backend. In my case it was having <span style="font-family: Courier New, Courier, monospace;">HTTP_PROXY</span> set in the environment and the proxy won't allow the connection. <span style="font-family: Courier New, Courier, monospace;">unset HTTP_PROXY</span> fixed the issue.Jason Mansfieldhttp://www.blogger.com/profile/01931242304514657402noreply@blogger.com1tag:blogger.com,1999:blog-7468008418270845225.post-36346892081045647352017-01-05T15:04:00.000-08:002017-01-05T15:04:28.025-08:00Golang Install: Uanapproved caller. SecurityAgent may only be invoked by Apple software.Trying to install golang 1.7.4 on Mac OS X I got the following error:<br />
<br />
<blockquote class="tr_bq">
<b>Uanapproved caller.</b></blockquote>
<br />
<blockquote class="tr_bq">
<b></b>SecurityAgent may only be invoked by Apple software.</blockquote>
Reboot.Jason Mansfieldhttp://www.blogger.com/profile/01931242304514657402noreply@blogger.com0tag:blogger.com,1999:blog-7468008418270845225.post-12009698925585784692016-06-17T12:29:00.001-07:002016-06-17T12:29:58.130-07:00AcceptanceSince Dayna passed away I've felt strange in that I haven't felt guilty. I took responsibility for her care and have maintained that for years so ostensibly I bear some responsibility for her passing. I've worried that my lack of guilt indicated that there was something wrong with me.<br />
<br />
I've begun to accept that I did everything I could. <i>Everything I could</i> was limited by my capacity. I was also in a very difficult situation and my failures and missed opportunities were expressions of that. She was the only one who could save her but she was <i>stuck</i>, trapped by her illness and the habits it had instilled in her.<br />
<br />
I made mistakes but I never intentionally acted against her. Were there a solution available to me I would have acted on it. No one can know if there was a solution available to her. That weight was only hers to carry and she couldn't bear it. Many of us reached out to her because we felt responsible for helping her but we could to nothing to <i>fix</i> her. Sometimes that's just how things work out.Jason Mansfieldhttp://www.blogger.com/profile/01931242304514657402noreply@blogger.com1tag:blogger.com,1999:blog-7468008418270845225.post-56099363814011138392016-04-04T20:22:00.002-07:002016-04-04T20:22:34.385-07:00Dayna GordetskyOn Friday the 1st of April I learned that Dayna had passed away, having taken her own life. She had suffered a great deal both physically and emotionally. I don't think any of us will ever really understand what she was going through but we're coming together as a family to work on accepting it. If you're in the San Diego area and would like to attend the funeral, contact me privately for information.Jason Mansfieldhttp://www.blogger.com/profile/01931242304514657402noreply@blogger.com2tag:blogger.com,1999:blog-7468008418270845225.post-4034845005580907112016-02-13T18:28:00.000-08:002016-02-13T18:28:14.623-08:00How I Became a Monster, Part II don't really know how it happened and that's part of the problem. It's been like boiling a frog; the changes have been so slow it was hard to see the change happening. I'm writing this via stream-of-consciousness so it's not going to flow well.<br />
<br />
Dayna and I have been friends since 2000, and partners since 2002. I'll skip the love story and get to the plot. I've been abusive, neglectful, unsupportive, unsympathetic, and deceptive to her.<br />
<br />
I'm compelled to clarify <i>abusive</i>: I've never been directly physically abusive toward her. I've never struck her or anything like that. It would have easily been the last thing I ever did, plus it's just not my way. My sister and I were disciplined with spanking as children up until the point where my father left a bruise on my sister. He never spanked us again and after that he was able to bluff his way through spanking situations. That's how I remember it, anyway. I've never felt physically abused and actually respect my parents' approach to spanking.<br />
<br />
I've been an angry, anxious, depressed person for a long time. I took a lot of that out on Dayna. The rest I habitually keep inside until it boils over to take out on Dayna. It's taken me <i>years</i> to recognize the smallest part of this and I know I still don't see all of it. It's hard to know where to start as this has been ongoing for over a decade as part of everyday life.<br />
<br />
The first thing to know is that I have a grossly overdeveloped sense of responsibility. I cannot overstate this enough. If I <i>can</i> take care of something I will probably take responsibility for it. I can't possibly <i>do</i> everything so I fail to deliver on most things. Luckily for me this failure is silent. Few if any people know I've taken responsibility for this or that so I can feel inwardly guilty and unconfident but look capable and hard-working and dependable on the outside. I exist in a continuous state of being overwhelmed by my (notional) responsibilities and undermined by my (notional) failures. Saying out it out loud hasn't done much to reign this in.<br />
<br />
When Dayna and I really started getting to know each other she told me about a number of mental disorders she suffered from. Growing up I saw my mother as constantly under great stress from her work and my father doing everything he could to make things easier for her. Being a kid I could have been way off but I internalized what I thought I saw. So I took responsibility for making her life better. Had this been something I said out loud I'm sure Dayna would have said this was ludicrous. One person can't <i>fix</i> another, they can just be accepting and supportive. I was pretty accepting and supportive in the beginning but eventually this was overridden by my own shortcomings and misguided ideas how on people help each other.<br />
<br />
Things have gotten particularly bad between us over the last few months. Long-standing injuries to Dayna's spine have been getting worse, putting her consistently in a great deal of pain. The pain has made her irritable and my inability to really help her has been triggering my need to fix things. I haven't been fixing her so my mind has been bouncing between "I'm failing" and "There's nothing I can do". I've been physically supportive by way of getting her things, trying to make her comfortable, etc, but I've been almost completely emotionally detached and unwilling to accept that it's not for me to fix. This detachment made me completely <i>emotionally</i> unsupportive. My lack of sympathy and support causes her stress and anxiety which increases her pain and irritability which in turn causes me to be stressed, anxious, angry, and unsupportive. I've been there for her in ways that are completely superficial and rarely in ways that were genuinely meaningful.<br />
<br />
<i>To be continued...</i>Jason Mansfieldhttp://www.blogger.com/profile/01931242304514657402noreply@blogger.com4tag:blogger.com,1999:blog-7468008418270845225.post-10539987669346504462015-08-21T09:05:00.000-07:002015-08-21T09:05:58.330-07:00Phone Interv(iew|ention)<div style="text-align: justify;">
I participate in the hiring process at work through phone and onsite interviews. The phone interviews are initial screenings for candidates intended to determine who we want to bring in for the onsite interview gauntlet. Our process has me doing Q&A with the candidate then I write up details of the interview and my recommendations. Then, one or more people higher up the chain make decisions about how to proceed.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
For both types of interviews there's a fixed time slot. Sometimes during a phone interview I'm confident within the first 10-20 minutes that the candidate isn't qualified. Often in these situations I <i>like</i> the candidate and would happily hang out with them over drinks and geekery but I wouldn't want my project to depend on them.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
I come to the conclusion that the candidate won't fit usually after I've given them a few extra meters of rope to climb up with and they just hang themselves more. If it seems unlikely that more rope is going to rescue them my standard bailout is "Those were all the questions I had for today. Do you have any questions for me?" It's polite but dodgy and it seems unlikely to me that the candidate doesn't realize that this is a signal that they've bombed.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
On at least one occasion the "questions for me" portion developed into a friendly conversation about skill development and I was able to provide a book recommendation that flowed naturally with that conversation. What I'd really like to do is just cut the interview, be forthcoming, say "the higher-ups may disagree but I think you still need to develop for a position like this", and then provide guidance about what the candidate can learn and practice to up their game.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
I see a few potential risks here. First, it's sufficiently out of normal (impersonal) interview protocol as to feel vaguely unprofessional. That said, is it really important to stay 100% professional (impersonal) when I can actually help someone? It's not just me though, I'm also representing my company.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Second, I'm essentially cutting out the higher ups here and decisions about a candidate's progress through the hiring pipeline are under their purview. But, they make those decisions based on my view of the candidate so if I'm confident the candidate isn't a good fit it would be exceedingly unlikely for them to disregard that.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Third, there's the risk that I might come across as providing a prescription for getting the job in the future. I can mitigate that to some extent by being as forthcoming about my intent as possible. Still, there will always be a candidate who gets the wrong idea despite my best efforts. If I've done my best I guess I can just let it be "their problem" but that feels kind of irresponsible.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
The easiest thing to do is just get out of the call and forget about them and that's mostly what I've been doing so far. I was asked recently by a candidate if I share my knowledge publicly and it surprised me to say "no". I used to but I haven't been recently and I feel that's kind of a loss. I should be teaching more.</div>
Jason Mansfieldhttp://www.blogger.com/profile/01931242304514657402noreply@blogger.com0tag:blogger.com,1999:blog-7468008418270845225.post-74960616572993976962015-06-08T16:36:00.004-07:002017-10-11T13:49:38.206-07:00Le DietI've been dieting for a couple of months now. I'm down about 9.5 pounds which isn't huge, but it's progress and it's been with minimal suffering. Here's what I've been doing:<br />
<ul>
<li>Drink lots of water. I don't enjoy drinking water but it's a lot more palatable when it's ice cold. I also like adding a bit of lemon juice. There are "flavor drops" you can buy for the same purpose.</li>
<li>I gave up soft drinks and artificial sweetener. There's no scientific reason for this. Logically speaking the artificial things aren't needed in our diet. They may or may not be harmless. More than anything I did it to see if I could do it. It took about two weeks for the cravings for soda to go away.</li>
<li>I do drink coffee. I'll have a large (16oz) with about a Tbsp of real sugar once or twice a day. This is my flavored drink. Tea would also be fine but I haven't felt like having tea. I don't consider this part of the diet per se, it's just here for completeness.</li>
<li>Protein smoothies - this is one of the main facets of my diet. They're high in protein, high in fiber, relatively low in calories, and really tasty and refreshing.</li>
<ul>
<li>A smoothie blender. I have an <a href="http://www.oster.com/blenders-and-juicers/personal-blenders/oster--blendn-go-my-blend-blender---green/BLSTPB-WGN-000.html#start=6" target="_blank">Oster</a>.</li>
<li>A cup or so of frozen fruit. I buy 1-3lb bags at the grocery story and they're usually in the frozen dessert section.</li>
<li>8oz of cold water. This is called for by the protein powder.</li>
<li>About 1Tbsp of table sugar. You can use more, less, or artificial to taste.</li>
<li>One serving (two scoops) of <a href="http://eas.com/products/lean15" target="_blank">EAS Lean 15 powder</a>.</li>
<li>A generous splash of lemon juice. I love lemon juice.</li>
<li>Ice cubes until the fluid is almost at the top.</li>
<li>I screw on the bladed cap thingie and shake it until the ice moves around freely (so the blender doesn't jam) and there aren't powder clumps on the wall of the bottle.</li>
<li>Blend it until it looks like it's about an even consistency. The fluid should be spinning all the way to the top.</li>
<li>This makes a nice fruit lemonade.</li>
<li>Instead of lemonade, I also do frozen banana (peel, break in half, stick in sealable bag), cocoa powder, sugar, powder, water, <a href="https://www.bellplantation.com/" target="_blank">PB2</a>, and ice. Also very tasty.</li>
<li>I have 2-3 of these per day. 3 is almost 100% of your daily protein.</li>
</ul>
<li>Chunky soups. Fairly low calorie, filling, vegetables, etc. I get tired of them sometimes but they have the benefit of being trivial to prepare. Any of these with chicken broth benefits from a squirt of sriracha if you're into that sort of thing.</li>
<li>Low-calorie frozen entrees. I get the "Eating Right" brand and stick to ones that are under 300 calories.</li>
<li>Occasionally, a sandwich. Whole wheat, medium cheddar, turkey breast, chipotle mayo, pickles, peppercinis. These pickles and peppercinis make it for me. This is probably the highest calorie thing I eat.</li>
<li>Fresh fruit. Apples and bananas mostly because they're really convenient. When my excess bananas start to turn brown I freeze them as above.</li>
<li><a href="http://www.clifbar.com/products/builders/builders" target="_blank">Chocolate Mint Builder's Bar</a>. They have other flavors. These are relatively high calorie but sometimes you need a treat. They're tasty, have a satisfying texture, are silly high in protein, low glycemic index, etc. Pairs nicely with a fresh apple. No more than one per day.</li>
<li>Gummy multivitamins, just in case.</li>
<li>Fish oil pills because my cholesterol is a little high.</li>
</ul>
<h3>
Goals</h3>
<div>
American portions are way too large. We get conditioned to eat an amount of food in one sitting that we can't effectively deal with. Our stomachs become accustomed to getting a lot of food and get pretty large. We get trained to where we don't feel full unless we're stuffed and have had too many calories. The overall goal is to maximize the feeling of fullness while minimizing calories.</div>
<div>
<br /></div>
<div>
One thing to work on is getting accustomed to being full on less food. This means eating smaller portions. This is tough while you're used to big portions. I think this is the part you just have to tough out. Any time I eat something that has about 300 calories in it, I wait 30 minutes before eating something else. I try to drink 8oz of water or so after I've eaten to increase the feeling of fullness. While I'm working on portion size it doesn't matter how much I eat overall. So if I'm eating every 30 minutes until I'm full, that's basically okay. The key is to get used to eating small amounts. Succeeding here is essential to getting away from poor styles of eating. It took a couple of weeks to adjust to the size of meal. During this phase I found it beneficial to prefer 300 calorie servings that were more food.</div>
<div>
<br /></div>
<div>
Be ready to fart. A lot. The normal American diet doesn't include nearly this much fiber. We can't digest fiber. The bacteria in our digestive tracts will happily digest the fiber and their waste product is gas. I fart a lot. The way I poop has also changed. Drinking lots of water helps some.</div>
<div>
<br /></div>
<div>
My weight fluctuates throughout the day and throughout the week. Suddenly weighing one or two more pounds will easily happen as I eat, drink, and eliminate. I can't really judge your progress day to day. Weekly or every two weeks is better; I need to pay attention to the trends and not the day to day weight.</div>
Jason Mansfieldhttp://www.blogger.com/profile/01931242304514657402noreply@blogger.com0tag:blogger.com,1999:blog-7468008418270845225.post-56857712864451261202014-12-28T20:23:00.000-08:002014-12-28T20:23:00.710-08:003D Print Host Log Book<div style="text-align: justify;">
What isn't always clear to the uninitiated is that 3D printing is a hobby of tinkering whether you want it to be or not. The output of a print will vary by:</div>
<br />
<ul>
<li style="text-align: justify;">Printer make/model</li>
<li style="text-align: justify;">Revision of printer hardware</li>
<li style="text-align: justify;">Revision of printer firmware</li>
<li style="text-align: justify;">Host software</li>
<li style="text-align: justify;">Slicing software</li>
<li style="text-align: justify;">Temperature of heating element(s)</li>
<li style="text-align: justify;">Speed the print is run at</li>
<li style="text-align: justify;">Type of filament (PLA, ABS, nylon, etc)</li>
<li style="text-align: justify;">Brand of filament</li>
<li style="text-align: justify;">Batch of filament</li>
<li style="text-align: justify;">Progress through a single spool of filament</li>
<li style="text-align: justify;">Print surface (metal, wood, glass, tape)</li>
<li style="text-align: justify;">Condition of print surface (cleanliness, presence/absence of adhesives)</li>
<li style="text-align: justify;">Ambient temperature</li>
<li style="text-align: justify;">Humidity</li>
<li style="text-align: justify;">Nearby airflow</li>
</ul>
<div style="text-align: justify;">
For better or worse there's also not a single set of conditions/settings that will make your prints successful. You may get very similar results to a given print by increasing print speed if you also increase temperature (or maybe the opposite).</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Lately I've been working quite a bit at dialing in settings that work well for me. The MG Chemicals glow PLA I've been using for my <a href="https://plus.google.com/u/0/116775222297299631121/posts/jAbDh3zhYub" target="_blank">train track project</a> has been giving me no end of grief. In my setup it's all I can do to get it to adhere well enough to the print surface, let alone <i>reduce</i> corner warping enough to get usable output. Two different spools of Shaxon (natural, blue) work great, a spool of glow green I got from <a href="http://filamentsupply.com/">filamentsupply.com</a> is also great. With the MG Chem I'll print a part and get good results, then print the same thing again and it will be a disaster. I've basically given up on it and switched to the filamentsupply glow green.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
As an aside, my process now that's working pretty well is:</div>
<div style="text-align: justify;">
<ol>
<li>Preheat the extruder to working temp +5C and let it sit so other surfaces can heat and expand.</li>
<li>Adjust surface tape as needed. So far if I wipe with alcohol I get so much adhesion that it's difficult to <i>sand</i> the tape off the part.</li>
<li>Set the slicer to run at 30% speed for the first layer.</li>
<li>Set the slicer to produce a 3mm brim.</li>
<li>Set the slicer to not engage the fan for the first 3 layers.</li>
<li>Because I have my printer basically in a hallway, I make sure that there are no nearby doors open that will permit a draft.</li>
<li>Take my heat gun and heat the surface until it registers around 110F with my IR temp gun.</li>
<li>Start the print and watch it until the first two layers are complete. If I get there, it's like 95% success chance.</li>
</ol>
<div>
I've started keeping a log of my prep steps, settings, and materials and then recording notes on the results for the output. I expect this to be a handy troubleshooting tool.</div>
<div>
<br /></div>
<div>
<i>Given that this is an inherently tinkering hobby, the host software should have this built in</i>. When I go to do a print, it should record everything it knows. Printer settings (temp, etc), slicer settings, any calculated output the slicer provides (bounding volume, footprint, estimated volume of material, estimated print time), and actual print time. Having integrated cameras to take high-res photos every so often during the print would be amazing. It should of course provide an interface for notes so I can record anything the software doesn't, particularly observations of the output.</div>
</div>
Jason Mansfieldhttp://www.blogger.com/profile/01931242304514657402noreply@blogger.com0tag:blogger.com,1999:blog-7468008418270845225.post-18717038503558118582014-12-14T19:50:00.000-08:002014-12-14T19:50:08.268-08:00Dreidel, dreidel, dreidel, I Made It Out of Polylactic Acid Plastic<br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; text-align: justify;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKiVNYi0r7UO1CIqnBYikVXEGEAFsWwmBcVqHCIcLsQTz47SxR7MlWZJPwFHXGAqE7CWFvTJJ9X19ulASnAOtJ4oDUdHZlOCfcLRyYcH24YmptoWhyphenhyphenYYEWPpXBPnv50J1jLOfSYF-tRi0/s1600/IMG_20141203_190635910_HDR.jpg" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img alt="3D printed dreidel body and separate handle lying on newsprint. The handle is painted with white nail polish, the body is unpainted." border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKiVNYi0r7UO1CIqnBYikVXEGEAFsWwmBcVqHCIcLsQTz47SxR7MlWZJPwFHXGAqE7CWFvTJJ9X19ulASnAOtJ4oDUdHZlOCfcLRyYcH24YmptoWhyphenhyphenYYEWPpXBPnv50J1jLOfSYF-tRi0/s1600/IMG_20141203_190635910_HDR.jpg" height="320" title="" width="180" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">3D printed in PLA with handle painted.</td></tr>
</tbody></table>
<div style="text-align: justify;">
That's how the song goes, right? Well, in spirit at least.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
We're a multi-cultural household and I have a 3D printer I'll continually need to justify buying. So, I decided to print a dreidel and see how I could do finishing it.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
I'm working with 1.75mm Shaxon PLA in "natural" tone which is vaguely clear-ish. I'm using a <a href="http://printrbot.com/shop/assembled-simple-metal/" target="_blank">Printrbot Simple Metal</a> at .2mm for all layers using <a href="http://www.repetier.com/" target="_blank">Reptier-Host</a> Mac and the newest stable <a href="http://slic3r.org/" target="_blank">Slic3r</a>.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Right off the printer it was smooth and solid. The handle did not fit into the socket on the top of the body so I first tried sanding the star of the handle. This did not progress quickly so I stepped up to small hand files. This was faster, but still pretty tedious so I pulled out the dremel. This! Was! Fast! I still had to finish with the files as even at low speed the dremel melts the plastic as much as it sands it. Some quick filing and the burrs were off and it had a nice fit that was tight enough to stay together through friction. It spins well too!</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
A youtube video I saw suggested nail varnish (nail polish here in The States) for finishing prints. The idea is that it's viscous enough to fill in the ridges between layers. Also, it's intended to provide a durable finish. I thought it would be a good way to test out finishing without buying a lot of painting tools. As an aside, some of this was $2 for 0.5fl oz, some was $10.50 for 0.5fl oz. This should be considered an elicit substance!</div>
<div style="text-align: justify;">
<br /></div>
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="clear: right; float: right; margin-bottom: 1em; text-align: justify;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhu6YXWm8d0Xx5ScBN9l48KyRI1u2HTaQEjLjn1sSQEULsYKHS100lftZlC2ImUcrL7Wj_K7eGABn0dX2kYn6siOq8XuHhWgvMQ35MgtpRYM-nb0LdbO-V4Mzw1tqYeAVpwEHx9fF7Vwgg/s1600/IMG_20141203_190942757_HDR.jpg" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img alt="Dreidel on newsprint, glyphs are painted with white overflowing the shape of the glyph." border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhu6YXWm8d0Xx5ScBN9l48KyRI1u2HTaQEjLjn1sSQEULsYKHS100lftZlC2ImUcrL7Wj_K7eGABn0dX2kYn6siOq8XuHhWgvMQ35MgtpRYM-nb0LdbO-V4Mzw1tqYeAVpwEHx9fF7Vwgg/s1600/IMG_20141203_190942757_HDR.jpg" height="320" title="" width="180" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Glyphs painted in white.</td></tr>
</tbody></table>
<div style="text-align: justify;">
For bulk-coating it works pretty well. For getting into detailed spaces it's not so great, at least not with the included brush. I already planned to overflow the glyphs and sand off the overflow.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
This did not work as expected. The paint really does seep into the ridges which meant sanding off all the overflow would require completely sanding down the surface of the body, at least around the glyphs. Oh well, it's white paint so adding color over it should be just fine.</div>
<div style="text-align: justify;">
<br /></div>
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7xPN91skxSlaUxrPDHWRoyLSzilzBIkoX-g7uTX_vbarQS_eTxph-P2kmAh-cb8E2T-48N3HoW5F0K1vGB1w1xucrMbaECzhwHLLIpchl94p5eQRaPOLFSyIo9OgCB2J2IzQBFL82sSw/s1600/IMG_20141203_211413672_HDR.jpg" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img alt="Dreidel lying on newsprint, glyph painted with white paint. Some of the paint is sanded off but ridges on the surface filled with white paint are still visible." border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7xPN91skxSlaUxrPDHWRoyLSzilzBIkoX-g7uTX_vbarQS_eTxph-P2kmAh-cb8E2T-48N3HoW5F0K1vGB1w1xucrMbaECzhwHLLIpchl94p5eQRaPOLFSyIo9OgCB2J2IzQBFL82sSw/s1600/IMG_20141203_211413672_HDR.jpg" height="320" title="" width="180" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Sanded the overflow, kind of.</td></tr>
</tbody></table>
<div style="text-align: justify;">
It didn't quite. My blue paint was kind of a blue tint with fairly subtle sparkles. The white showed through easily but the sparkles were nice. This is where I see that this is intended to be going over nails and being translucent can make for a cool effect. In this case it is still kind of a cool effect because it smooths out the layering ridges while leaving them visible. This might be really neat for some projects.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Even though the handle stays in easily through friction alone, I figure at some point I'm going to drop it, it will bang on something, and the handle will go shooting off, only to be found next time I move to a new home.</div>
<div style="text-align: justify;">
<br /></div>
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="clear: left; float: left; margin-bottom: 1em; text-align: left;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiGQ6ko1OL5Mqez90n1IRMB_6cpB-eRzjREpQQFoe995pmNz4qI318QMWpT18zzD7uHFRonA2C-Ysi2re3N6ulPPdMqM_coUh5V63tq6-ZxkYtaO5r1paf_joCg6IaK6vWsS_4tVQu_jCU/s1600/IMG_20141203_212406958.jpg" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img alt="dreidel is lying on newsprint with the tip pointing up and the handle separate. The surface is a shiny translucent blue." border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiGQ6ko1OL5Mqez90n1IRMB_6cpB-eRzjREpQQFoe995pmNz4qI318QMWpT18zzD7uHFRonA2C-Ysi2re3N6ulPPdMqM_coUh5V63tq6-ZxkYtaO5r1paf_joCg6IaK6vWsS_4tVQu_jCU/s1600/IMG_20141203_212406958.jpg" height="320" title="" width="180" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">First coat of blue.. with sparkles!</td></tr>
</tbody></table>
<div style="text-align: justify;">
Ultimately I'm pretty pleased with the results. I'm kind of clumsy doing fine painting but I have virtual no practice and absolutely none recently.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-bottom: 1em; margin-right: 1em; text-align: left;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-LogbP7FhVYzqJQw-gF92gY6-pF9xZsk627gmqR5k0WCRNWiUYGxpnw8SmAIo9oGaalvSUaxJSQd_qsfRSa7D4sAUdouUq2gQMMI3q5F1XoqQtWyRrbY2DgE8LDi8a_BIKsO4lpz9y6U/s1600/IMG_20141214_170446246.jpg" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img alt="Dreidel is sitting tip down in a soda bottle cap. It is a much deeper blue. The handle is lying beside it." border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-LogbP7FhVYzqJQw-gF92gY6-pF9xZsk627gmqR5k0WCRNWiUYGxpnw8SmAIo9oGaalvSUaxJSQd_qsfRSa7D4sAUdouUq2gQMMI3q5F1XoqQtWyRrbY2DgE8LDi8a_BIKsO4lpz9y6U/s1600/IMG_20141214_170446246.jpg" height="320" title="" width="180" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">A couple of coats in, looking snazzy!</td></tr>
</tbody></table>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhM2DZfOCAd7YHsupvaEnZOndeCdgQveZr9MBFRcuFGyo0x1JWfo7oH13vT0gNdqBhSnax-T3sbZRerUYQoNrBcquTRuyBoLaPQOCBG7bXVDOlZ0ZneBqZ-gt-p3Sfd9Nbw2bvzUTrx3qQ/s1600/IMG_20141214_190901018.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img alt="Finished dreidel sitting tip down in a soda bottle cap. It's a shiny deep blue with white glyphs. The handle is attached and a hot glue gun is visible beside it." border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhM2DZfOCAd7YHsupvaEnZOndeCdgQveZr9MBFRcuFGyo0x1JWfo7oH13vT0gNdqBhSnax-T3sbZRerUYQoNrBcquTRuyBoLaPQOCBG7bXVDOlZ0ZneBqZ-gt-p3Sfd9Nbw2bvzUTrx3qQ/s1600/IMG_20141214_190901018.jpg" height="180" title="" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">All done!</td></tr>
</tbody></table>
Jason Mansfieldhttp://www.blogger.com/profile/01931242304514657402noreply@blogger.com1